diff --git a/.clang-format b/.clang-format index ba409869bd3..1d0f396dd8f 100644 --- a/.clang-format +++ b/.clang-format @@ -45,9 +45,11 @@ DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - - Regex: '^<(BeastConfig)' + - Regex: '^<(test)/' Priority: 0 - - Regex: '^<(ripple)/' + - Regex: '^<(xrpld)/' + Priority: 1 + - Regex: '^<(xrpl)/' Priority: 2 - Regex: '^<(boost)/' Priority: 3 @@ -56,6 +58,7 @@ IncludeCategories: IncludeIsMainRegex: '$' IndentCaseLabels: true IndentFunctionDeclarationAfterType: false +IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false @@ -71,6 +74,7 @@ PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true +RequiresClausePosition: OwnLine SortIncludes: true SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true diff --git a/.codecov.yml b/.codecov.yml index 3e6f09d58ae..6df3786197f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -33,5 +33,5 @@ slack_app: false ignore: - "src/test/" - - "src/ripple/beast/test/" - - "src/ripple/beast/unit_test/" + - "include/xrpl/beast/test/" + - "include/xrpl/beast/unit_test/" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index de2d90d36ec..a72fc4afd83 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -6,3 +6,8 @@ e2384885f5f630c8f0ffe4bf21a169b433a16858 241b9ddde9e11beb7480600fd5ed90e1ef109b21 760f16f56835663d9286bd29294d074de26a7ba6 0eebe6a5f4246fced516d52b83ec4e7f47373edd +2189cc950c0cebb89e4e2fa3b2d8817205bf7cef +b9d007813378ad0ff45660dc07285b823c7e9855 +fe9a5365b8a52d4acc42eb27369247e6f238a4f9 +9a93577314e6a8d4b4a8368cc9d2b15a5d8303e8 +552377c76f55b403a1c876df873a23d780fcc81c diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 79bbe9af075..6714369155f 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -20,6 +20,8 @@ runs: ${{ inputs.generator && format('-G "{0}"', inputs.generator) || '' }} \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \ + -Dtests=TRUE \ + -Dxrpld=TRUE \ ${{ inputs.cmake-args }} \ .. - name: build diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml index d9687ff121e..50e2999018a 100644 --- a/.github/actions/dependencies/action.yml +++ b/.github/actions/dependencies/action.yml @@ -50,6 +50,8 @@ runs: conan install \ --output-folder . \ --build missing \ + --options tests=True \ + --options xrpld=True \ --settings build_type=${{ inputs.configuration }} \ .. - name: upload dependencies to remote diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 8c915ec00ff..91bbff48c09 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -4,9 +4,9 @@ on: [push, pull_request] jobs: check: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 env: - CLANG_VERSION: 10 + CLANG_VERSION: 18 steps: - uses: actions/checkout@v4 - name: Install clang-format @@ -19,10 +19,8 @@ jobs: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add sudo apt-get update sudo apt-get install clang-format-${CLANG_VERSION} - - name: Format src/ripple - run: find src/ripple -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format-${CLANG_VERSION} -i - - name: Format src/test - run: find src/test -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format-${CLANG_VERSION} -i + - name: Format first-party sources + run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-${CLANG_VERSION} -i {} + - name: Check for differences id: assert run: | @@ -52,7 +50,7 @@ jobs: To fix it, you can do one of two things: 1. Download and apply the patch generated as an artifact of this job to your repo, commit, and push. - 2. Run 'git-clang-format --extensions c,cpp,h,cxx,ipp develop' + 2. Run 'git-clang-format --extensions cpp,h,hpp,ipp develop' in your repo, commit, and push. run: | echo "${PREAMBLE}" diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 10a1465192a..e2265d1b83b 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -4,6 +4,7 @@ on: push: branches: - develop + - doxygen concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/libxrpl.yml b/.github/workflows/libxrpl.yml new file mode 100644 index 00000000000..fe4a2c3e220 --- /dev/null +++ b/.github/workflows/libxrpl.yml @@ -0,0 +1,88 @@ +name: Check libXRPL compatibility with Clio +env: + CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod + CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }} + CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} +on: + pull_request: + paths: + - 'src/libxrpl/protocol/BuildInfo.cpp' + - '.github/workflows/libxrpl.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + publish: + name: Publish libXRPL + outputs: + outcome: ${{ steps.upload.outputs.outcome }} + version: ${{ steps.version.outputs.version }} + channel: ${{ steps.channel.outputs.channel }} + runs-on: [self-hosted, heavy] + container: rippleci/rippled-build-ubuntu:aaf5e3e + steps: + - name: Wait for essential checks to succeed + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + running-workflow-name: wait-for-check-regexp + check-regexp: '(dependencies|test).*linux.*' # Ignore windows and mac tests but make sure linux passes + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + - name: Checkout + uses: actions/checkout@v4 + - name: Generate channel + id: channel + shell: bash + run: | + echo channel="clio/pr_${{ github.event.pull_request.number }}" | tee ${GITHUB_OUTPUT} + - name: Export new package + shell: bash + run: | + conan export . ${{ steps.channel.outputs.channel }} + - name: Add Ripple Conan remote + shell: bash + run: | + conan remote list + conan remote remove ripple || true + # Do not quote the URL. An empty string will be accepted (with a non-fatal warning), but a missing argument will not. + conan remote add ripple ${{ env.CONAN_URL }} --insert 0 + - name: Parse new version + id: version + shell: bash + run: | + echo version="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" \ + | awk -F '"' '{print $2}')" | tee ${GITHUB_OUTPUT} + - name: Try to authenticate to Ripple Conan remote + id: remote + shell: bash + run: | + # `conan user` implicitly uses the environment variables CONAN_LOGIN_USERNAME_ and CONAN_PASSWORD_. + # https://docs.conan.io/1/reference/commands/misc/user.html#using-environment-variables + # https://docs.conan.io/1/reference/env_vars.html#conan-login-username-conan-login-username-remote-name + # https://docs.conan.io/1/reference/env_vars.html#conan-password-conan-password-remote-name + echo outcome=$(conan user --remote ripple --password >&2 \ + && echo success || echo failure) | tee ${GITHUB_OUTPUT} + - name: Upload new package + id: upload + if: (steps.remote.outputs.outcome == 'success') + shell: bash + run: | + echo "conan upload version ${{ steps.version.outputs.version }} on channel ${{ steps.channel.outputs.channel }}" + echo outcome=$(conan upload xrpl/${{ steps.version.outputs.version }}@${{ steps.channel.outputs.channel }} --remote ripple --confirm >&2 \ + && echo success || echo failure) | tee ${GITHUB_OUTPUT} + notify_clio: + name: Notify Clio + runs-on: ubuntu-latest + needs: publish + env: + GH_TOKEN: ${{ secrets.CLIO_NOTIFY_TOKEN }} + steps: + - name: Notify Clio about new version + if: (needs.publish.outputs.outcome == 'success') + shell: bash + run: | + gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \ + -F "client_payload[version]=${{ needs.publish.outputs.version }}@${{ needs.publish.outputs.channel }}" diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index da61963b3f2..6b8261c5d69 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -222,7 +222,7 @@ jobs: - name: upload coverage report uses: wandalen/wretry.action@v1.4.10 with: - action: codecov/codecov-action@v4.3.0 + action: codecov/codecov-action@v4.5.0 with: | files: coverage.xml fail_ci_if_error: true @@ -232,3 +232,53 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} attempt_limit: 5 attempt_delay: 210000 # in milliseconds + + conan: + needs: dependencies + runs-on: [self-hosted, heavy] + container: rippleci/rippled-build-ubuntu:aaf5e3e + env: + build_dir: .build + configuration: Release + steps: + - name: download cache + uses: actions/download-artifact@v3 + with: + name: linux-gcc-${{ env.configuration }} + - name: extract cache + run: | + mkdir -p ~/.conan + tar -xzf conan.tar -C ~/.conan + - name: check environment + run: | + env | sort + echo ${PATH} | tr ':' '\n' + conan --version + cmake --version + - name: checkout + uses: actions/checkout@v4 + - name: dependencies + uses: ./.github/actions/dependencies + env: + CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod + with: + configuration: ${{ env.configuration }} + - name: export + run: | + version=$(conan inspect --raw version .) + reference="xrpl/${version}@local/test" + conan remove -f ${reference} || true + conan export . local/test + echo "reference=${reference}" >> "${GITHUB_ENV}" + - name: build + run: | + cd examples/example + mkdir ${build_dir} + cd ${build_dir} + conan install .. --output-folder . \ + --require-override ${reference} --build missing + cmake .. \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=./build/${configuration}/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=${configuration} + cmake --build . + ./example | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 04893e956f0..9f69d413797 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ # .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v10.0.1 + rev: v18.1.3 hooks: - id: clang-format diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index 58ceed0cde1..1c0595b0081 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -8,35 +8,98 @@ The API version controls the API behavior you see. This includes what properties For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`rippled`) release. -## API Version 1 +## API Version 2 -This version is supported by all `rippled` versions. At time of writing, it is the default API version, used when no `api_version` is specified. When a new API version is introduced, the command line interface will default to the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code. +API version 2 is available in `rippled` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request. -### Idiosyncrasies +#### Removed methods -#### V1 account_info response +In API version 2, the following deprecated methods are no longer available: (https://github.com/XRPLF/rippled/pull/4759) -In [the response to the `account_info` command](https://xrpl.org/account_info.html#response-format), there is `account_data` - which is supposed to be an `AccountRoot` object - and `signer_lists` is returned in this object. However, the docs say that `signer_lists` should be at the root level of the reponse. +- `tx_history` - Instead, use other methods such as `account_tx` or `ledger` with the `transactions` field set to `true`. +- `ledger_header` - Instead, use the `ledger` method. -It makes sense for `signer_lists` to be at the root level because signer lists are not part of the AccountRoot object. (First reported in [xrpl-dev-portal#938](https://github.com/XRPLF/xrpl-dev-portal/issues/938).) +#### Modifications to JSON transaction element in V2 -In `api_version: 2`, the `signer_lists` field [will be moved](#modifications-to-account_info-response-in-v2) to the root level of the account_info response. (https://github.com/XRPLF/rippled/pull/3770) +In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. (https://github.com/XRPLF/rippled/pull/4775) +This helps to unify the JSON serialization format of transactions. (https://github.com/XRPLF/clio/issues/722, https://github.com/XRPLF/rippled/issues/4727) -#### server_info - network_id +- JSON transaction element is named `tx_json` +- Binary transaction element is named `tx_blob` +- JSON transaction metadata element is named `meta` +- Binary transaction metadata element is named `meta_blob` -The `network_id` field was added in the `server_info` response in version 1.5.0 (2019), but it is not returned in [reporting mode](https://xrpl.org/rippled-server-modes.html#reporting-mode). +Additionally, these elements are now consistently available next to `tx_json` (i.e. sibling elements), where possible: -## XRP Ledger server version 2.0.0 +- `hash` - Transaction ID. This data was stored inside transaction output in API version 1, but in API version 2 is a sibling element. +- `ledger_index` - Ledger index (only set on validated ledgers) +- `ledger_hash` - Ledger hash (only set on closed or validated ledgers) +- `close_time_iso` - Ledger close time expressed in ISO 8601 time format (only set on validated ledgers) +- `validated` - Bool element set to `true` if the transaction is in a validated ledger, otherwise `false` -### Additions in 2.2 +This change affects the following methods: -Additions are intended to be non-breaking (because they are purely additive). +- `tx` - Transaction data moved into element `tx_json` (was inline inside `result`) or, if binary output was requested, moved from `tx` to `tx_blob`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements +- `account_tx` - Renamed transaction element from `tx` to `tx_json`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements +- `transaction_entry` - Renamed transaction metadata element from `metadata` to `meta`. Changed location of `hash` and added new elements +- `subscribe` - Renamed transaction element from `transaction` to `tx_json`. Changed location of `hash` and added new elements +- `sign`, `sign_for`, `submit` and `submit_multisigned` - Changed location of `hash` element. -- `feature`: A non-admin mode that uses the same formatting as admin RPC, but hides potentially-sensitive data. +#### Modification to `Payment` transaction JSON schema -### Additions in 2.0 +When reading Payments, the `Amount` field should generally **not** be used. Instead, use [delivered_amount](https://xrpl.org/partial-payments.html#the-delivered_amount-field) to see the amount that the Payment delivered. To clarify its meaning, the `Amount` field is being renamed to `DeliverMax`. (https://github.com/XRPLF/rippled/pull/4733) -Additions are intended to be non-breaking (because they are purely additive). +- In `Payment` transaction type, JSON RPC field `Amount` is renamed to `DeliverMax`. To enable smooth client transition, `Amount` is still handled, as described below: (https://github.com/XRPLF/rippled/pull/4733) + - On JSON RPC input (e.g. `submit_multisigned` etc. methods), `Amount` is recognized as an alias to `DeliverMax` for both API version 1 and version 2 clients. + - On JSON RPC input, submitting both `Amount` and `DeliverMax` fields is allowed _only_ if they are identical; otherwise such input is rejected with `rpcINVALID_PARAMS` error. + - On JSON RPC output (e.g. `subscribe`, `account_tx` etc. methods), `DeliverMax` is present in both API version 1 and version 2. + - On JSON RPC output, `Amount` is only present in API version 1 and _not_ in version 2. + +#### Modifications to account_info response + +- `signer_lists` is returned in the root of the response. (In API version 1, it was nested under `account_data`.) (https://github.com/XRPLF/rippled/pull/3770) +- When using an invalid `signer_lists` value, the API now returns an "invalidParams" error. (https://github.com/XRPLF/rippled/pull/4585) + - (`signer_lists` must be a boolean. In API version 1, strings were accepted and may return a normal response - i.e. as if `signer_lists` were `true`.) + +#### Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response + +- Using `ledger_index_min`, `ledger_index_max`, and `ledger_index` returns `invalidParams` because if you use `ledger_index_min` or `ledger_index_max`, then it does not make sense to also specify `ledger_index`. In API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4571) + - The same applies for `ledger_index_min`, `ledger_index_max`, and `ledger_hash`. (https://github.com/XRPLF/rippled/issues/4545#issuecomment-1565065579) +- Using a `ledger_index_min` or `ledger_index_max` beyond the range of ledgers that the server has: + - returns `lgrIdxMalformed` in API version 2. Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/issues/4288) +- Attempting to use a non-boolean value (such as a string) for the `binary` or `forward` parameters returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) + +#### Modifications to [noripple_check](https://xrpl.org/noripple_check.html#noripple_check) response + +- Attempting to use a non-boolean value (such as a string) for the `transactions` parameter returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) + +## API Version 1 + +This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified. + +The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#commandline-format) always uses the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code. + +### Inconsistency: server_info - network_id + +The `network_id` field was added in the `server_info` response in version 1.5.0 (2019), but it is not returned in [reporting mode](https://xrpl.org/rippled-server-modes.html#reporting-mode). However, use of reporting mode is now discouraged, in favor of using [Clio](https://github.com/XRPLF/clio) instead. + +## XRP Ledger server version 2.2.0 + +The following is a non-breaking addition to the API. + +- The `feature` method now has a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781)) + +### Breaking change in 2.3 + +- `book_changes`: If the requested ledger version is not available on this node, a `ledgerNotFound` error is returned and the node does not attempt to acquire the ledger from the p2p network (as with other non-admin RPCs). + +Admins can still attempt to retrieve old ledgers with the `ledger_request` RPC. + +### Addition in 2.3 + +- `book_changes`: Returns a `validated` field in its response, which was missing in prior versions. + +The following additions are non-breaking (because they are purely additive). - `server_definitions`: A new RPC that generates a `definitions.json`-like output that can be used in XRPL libraries. - In `Payment` transactions, `DeliverMax` has been added. This is a replacement for the `Amount` field, which should not be used. Typically, the `delivered_amount` (in transaction metadata) should be used. To ease the transition, `DeliverMax` is present regardless of API version, since adding a field is non-breaking. @@ -44,11 +107,7 @@ Additions are intended to be non-breaking (because they are purely additive). ## XRP Ledger server version 1.12.0 -[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. - -### Additions in 1.12 - -Additions are intended to be non-breaking (because they are purely additive). +[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. The following additions are non-breaking (because they are purely additive). - `server_info`: Added `ports`, an array which advertises the RPC and WebSocket ports. This information is also included in the `/crawl` endpoint (which calls `server_info` internally). `grpc` and `peer` ports are also included. (https://github.com/XRPLF/rippled/pull/4427) - `ports` contains objects, each containing a `port` for the listening port (a number string), and a `protocol` array listing the supported protocols on that port. @@ -125,71 +184,6 @@ was released on Mar 14, 2023. removed from the [ledger subscription stream](https://xrpl.org/subscribe.html#ledger-stream), because it will no longer have any meaning. -## API Version 2 - -API version 2 is introduced in `rippled` version 2.0. Users can request it explicitly by specifying `"api_version" : 2`. - -#### Removed methods - -In API version 2, the following deprecated methods are no longer available: (https://github.com/XRPLF/rippled/pull/4759) - -- `tx_history` - Instead, use other methods such as `account_tx` or `ledger` with the `transactions` field set to `true`. -- `ledger_header` - Instead, use the `ledger` method. - -#### Modifications to JSON transaction element in V2 - -In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. (https://github.com/XRPLF/rippled/pull/4775) -This helps to unify the JSON serialization format of transactions. (https://github.com/XRPLF/clio/issues/722, https://github.com/XRPLF/rippled/issues/4727) - -- JSON transaction element is named `tx_json` -- Binary transaction element is named `tx_blob` -- JSON transaction metadata element is named `meta` -- Binary transaction metadata element is named `meta_blob` - -Additionally, these elements are now consistently available next to `tx_json` (i.e. sibling elements), where possible: - -- `hash` - Transaction ID. This data was stored inside transaction output in API version 1, but in API version 2 is a sibling element. -- `ledger_index` - Ledger index (only set on validated ledgers) -- `ledger_hash` - Ledger hash (only set on closed or validated ledgers) -- `close_time_iso` - Ledger close time expressed in ISO 8601 time format (only set on validated ledgers) -- `validated` - Bool element set to `true` if the transaction is in a validated ledger, otherwise `false` - -This change affects the following methods: - -- `tx` - Transaction data moved into element `tx_json` (was inline inside `result`) or, if binary output was requested, moved from `tx` to `tx_blob`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements -- `account_tx` - Renamed transaction element from `tx` to `tx_json`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements -- `transaction_entry` - Renamed transaction metadata element from `metadata` to `meta`. Changed location of `hash` and added new elements -- `subscribe` - Renamed transaction element from `transaction` to `tx_json`. Changed location of `hash` and added new elements -- `sign`, `sign_for`, `submit` and `submit_multisigned` - Changed location of `hash` element. - -#### Modification to `Payment` transaction JSON schema - -When reading Payments, the `Amount` field should generally **not** be used. Instead, use [delivered_amount](https://xrpl.org/partial-payments.html#the-delivered_amount-field) to see the amount that the Payment delivered. To clarify its meaning, the `Amount` field is being renamed to `DeliverMax`. (https://github.com/XRPLF/rippled/pull/4733) - -- In `Payment` transaction type, JSON RPC field `Amount` is renamed to `DeliverMax`. To enable smooth client transition, `Amount` is still handled, as described below: (https://github.com/XRPLF/rippled/pull/4733) - - On JSON RPC input (e.g. `submit_multisigned` etc. methods), `Amount` is recognized as an alias to `DeliverMax` for both API version 1 and version 2 clients. - - On JSON RPC input, submitting both `Amount` and `DeliverMax` fields is allowed _only_ if they are identical; otherwise such input is rejected with `rpcINVALID_PARAMS` error. - - On JSON RPC output (e.g. `subscribe`, `account_tx` etc. methods), `DeliverMax` is present in both API version 1 and version 2. - - On JSON RPC output, `Amount` is only present in API version 1 and _not_ in version 2. - -#### Modifications to account_info response - -- `signer_lists` is returned in the root of the response. In API version 1, it was nested under `account_data`. (https://github.com/XRPLF/rippled/pull/3770) -- When using an invalid `signer_lists` value, the API now returns an "invalidParams" error. (https://github.com/XRPLF/rippled/pull/4585) - - (`signer_lists` must be a boolean. In API version 1, strings were accepted and may return a normal response - i.e. as if `signer_lists` were `true`.) - -#### Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response - -- Using `ledger_index_min`, `ledger_index_max`, and `ledger_index` returns `invalidParams` because if you use `ledger_index_min` or `ledger_index_max`, then it does not make sense to also specify `ledger_index`. In API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4571) - - The same applies for `ledger_index_min`, `ledger_index_max`, and `ledger_hash`. (https://github.com/XRPLF/rippled/issues/4545#issuecomment-1565065579) -- Using a `ledger_index_min` or `ledger_index_max` beyond the range of ledgers that the server has: - - returns `lgrIdxMalformed` in API version 2. Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/issues/4288) -- Attempting to use a non-boolean value (such as a string) for the `binary` or `forward` parameters returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) - -#### Modifications to [noripple_check](https://xrpl.org/noripple_check.html#noripple_check) response - -- Attempting to use a non-boolean value (such as a string) for the `transactions` parameter returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) - # Unit tests for API changes The following information is useful to developers contributing to this project: diff --git a/BUILD.md b/BUILD.md index 60ee31d42d1..31755c36919 100644 --- a/BUILD.md +++ b/BUILD.md @@ -36,9 +36,15 @@ See [System Requirements](https://xrpl.org/system-requirements.html). Building rippled generally requires git, Python, Conan, CMake, and a C++ compiler. Some guidance on setting up such a [C++ development environment can be found here](./docs/build/environment.md). - [Python 3.7](https://www.python.org/downloads/) -- [Conan 1.55](https://conan.io/downloads.html) +- [Conan 1.60](https://conan.io/downloads.html)[^1] - [CMake 3.16](https://cmake.org/download/) +[^1]: It is possible to build with Conan 2.x, +but the instructions are significantly different, +which is why we are not recommending it yet. +Notably, the `conan profile update` command is removed in 2.x. +Profiles must be edited by hand. + `rippled` is written in the C++20 dialect and includes the `` header. The [minimum compiler versions][2] required are: @@ -67,9 +73,6 @@ Here are [sample instructions for setting up a C++ development environment on ma Windows is not recommended for production use at this time. - Additionally, 32-bit Windows development is not supported. -- Visual Studio 2022 is not yet supported. - - rippled generally requires [Boost][] 1.77, which Conan cannot build with VS 2022. - - Until rippled is updated for compatibility with later versions of Boost, Windows developers may need to use Visual Studio 2019. [Boost]: https://www.boost.org/ @@ -95,6 +98,12 @@ Update the compiler settings: conan profile update settings.compiler.cppstd=20 default ``` +Configure Conan (1.x only) to use recipe revisions: + + ``` + conan config set general.revisions_enabled=1 + ``` + **Linux** developers will commonly have a default Conan [profile][] that compiles with GCC and links with libstdc++. If you are linking with libstdc++ (see profile setting `compiler.libcxx`), @@ -104,6 +113,20 @@ then you will need to choose the `libstdc++11` ABI: conan profile update settings.compiler.libcxx=libstdc++11 default ``` + +Ensure inter-operability between `boost::string_view` and `std::string_view` types: + +``` +conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_BEAST_USE_STD_STRING_VIEW"]' default +conan profile update 'env.CXXFLAGS="-DBOOST_BEAST_USE_STD_STRING_VIEW"' default +``` + +If you have other flags in the `conf.tools.build` or `env.CXXFLAGS` sections, make sure to retain the existing flags and append the new ones. You can check them with: +``` +conan profile show default +``` + + **Windows** developers may need to use the x64 native build tools. An easy way to do that is to run the shortcut "x64 Native Tools Command Prompt" for the version of Visual Studio that you have installed. @@ -144,21 +167,41 @@ It does not explicitly link the C++ standard library, which allows you to statically link it with GCC, if you want. ``` + # Conan 1.x conan export external/snappy snappy/1.1.10@ + # Conan 2.x + conan export --version 1.1.10 external/snappy ``` Export our [Conan recipe for RocksDB](./external/rocksdb). It does not override paths to dependencies when building with Visual Studio. ``` + # Conan 1.x conan export external/rocksdb rocksdb/6.29.5@ + # Conan 2.x + conan export --version 6.29.5 external/rocksdb ``` Export our [Conan recipe for SOCI](./external/soci). It patches their CMake to correctly import its dependencies. ``` + # Conan 1.x conan export external/soci soci/4.0.3@ + # Conan 2.x + conan export --version 4.0.3 external/soci + ``` + +Export our [Conan recipe for NuDB](./external/nudb). +It fixes some source files to add missing `#include`s. + + + ``` + # Conan 1.x + conan export external/nudb nudb/2.0.8@ + # Conan 2.x + conan export --version 2.0.8 external/nudb ``` ### Build and Test @@ -196,13 +239,13 @@ It patches their CMake to correctly import its dependencies. generated by the first. You can pass the build type on the command line with `--settings build_type=$BUILD_TYPE` or in the profile itself, under the section `[settings]` with the key `build_type`. - + If you are using a Microsoft Visual C++ compiler, then you will need to ensure consistency between the `build_type` setting and the `compiler.runtime` setting. - + When `build_type` is `Release`, `compiler.runtime` should be `MT`. - + When `build_type` is `Debug`, `compiler.runtime` should be `MTd`. ``` @@ -216,7 +259,7 @@ It patches their CMake to correctly import its dependencies. Single-config generators: ``` - cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release -Dxrpld=ON -Dtests=ON .. ``` Pass the CMake variable [`CMAKE_BUILD_TYPE`][build_type] @@ -226,7 +269,7 @@ It patches their CMake to correctly import its dependencies. Multi-config generators: ``` - cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. + cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -Dxrpld=ON -Dtests=ON .. ``` **Note:** You can pass build options for `rippled` in this step. @@ -317,7 +360,7 @@ Example use with some cmake variables set: ``` cd .build conan install .. --output-folder . --build missing --settings build_type=Debug -cmake -DCMAKE_BUILD_TYPE=Debug -Dcoverage=ON -Dcoverage_test_parallelism=2 -Dcoverage_format=html-details -Dcoverage_extra_args="--json coverage.json" -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. +cmake -DCMAKE_BUILD_TYPE=Debug -Dcoverage=ON -Dxrpld=ON -Dtests=ON -Dcoverage_test_parallelism=2 -Dcoverage_format=html-details -Dcoverage_extra_args="--json coverage.json" -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. cmake --build . --target coverage ``` @@ -333,11 +376,11 @@ stored inside the build directory, as either of: | Option | Default Value | Description | | --- | ---| ---| | `assert` | OFF | Enable assertions. -| `reporting` | OFF | Build the reporting mode feature. | | `coverage` | OFF | Prepare the coverage report. | -| `tests` | ON | Build tests. | -| `unity` | ON | Configure a unity build. | | `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. | +| `tests` | OFF | Build tests. | +| `unity` | ON | Configure a unity build. | +| `xrpld` | OFF | Build the xrpld (`rippled`) application, and not just the libxrpl library. | [Unity builds][5] may be faster for the first build (at the cost of much more memory) since they concatenate sources into fewer diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake deleted file mode 100644 index 44d7061d739..00000000000 --- a/Builds/CMake/RippledCore.cmake +++ /dev/null @@ -1,1212 +0,0 @@ -#[===================================================================[ - xrpl_core - core functionality, useable by some client software perhaps -#]===================================================================] - -include(target_protobuf_sources) - -file (GLOB_RECURSE rb_headers - src/ripple/beast/*.h) - -# Protocol buffers cannot participate in a unity build, -# because all the generated sources -# define a bunch of `static const` variables with the same names, -# so we just build them as a separate library. -add_library(xrpl.libpb) -target_protobuf_sources(xrpl.libpb ripple/proto - LANGUAGE cpp - IMPORT_DIRS src/ripple/proto - PROTOS src/ripple/proto/ripple.proto -) - -file(GLOB_RECURSE protos "src/ripple/proto/org/*.proto") -target_protobuf_sources(xrpl.libpb ripple/proto - LANGUAGE cpp - IMPORT_DIRS src/ripple/proto - PROTOS "${protos}" -) -target_protobuf_sources(xrpl.libpb ripple/proto - LANGUAGE grpc - IMPORT_DIRS src/ripple/proto - PROTOS "${protos}" - PLUGIN protoc-gen-grpc=$ - GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc -) - -target_compile_options(xrpl.libpb - PUBLIC - $<$:-wd4996> - $<$: - --system-header-prefix="google/protobuf" - -Wno-deprecated-dynamic-exception-spec - > - PRIVATE - $<$:-wd4065> - $<$>:-Wno-deprecated-declarations> -) - -target_link_libraries(xrpl.libpb - PUBLIC - protobuf::libprotobuf - gRPC::grpc++ -) - -add_library (xrpl_core - ${rb_headers}) ## headers added here for benefit of IDEs -if (unity) - set_target_properties(xrpl_core PROPERTIES UNITY_BUILD ON) -endif () - -add_library(libxrpl INTERFACE) -target_link_libraries(libxrpl INTERFACE xrpl_core) -add_library(xrpl::libxrpl ALIAS libxrpl) - -#[===============================[ - beast/legacy FILES: - TODO: review these sources for removal or replacement -#]===============================] -# BEGIN LIBXRPL SOURCES -target_sources (xrpl_core PRIVATE - src/ripple/beast/clock/basic_seconds_clock.cpp - src/ripple/beast/core/CurrentThreadName.cpp - src/ripple/beast/core/SemanticVersion.cpp - src/ripple/beast/insight/impl/Collector.cpp - src/ripple/beast/insight/impl/Groups.cpp - src/ripple/beast/insight/impl/Hook.cpp - src/ripple/beast/insight/impl/Metric.cpp - src/ripple/beast/insight/impl/NullCollector.cpp - src/ripple/beast/insight/impl/StatsDCollector.cpp - src/ripple/beast/net/impl/IPAddressConversion.cpp - src/ripple/beast/net/impl/IPAddressV4.cpp - src/ripple/beast/net/impl/IPAddressV6.cpp - src/ripple/beast/net/impl/IPEndpoint.cpp - src/ripple/beast/utility/src/beast_Journal.cpp - src/ripple/beast/utility/src/beast_PropertyStream.cpp) - -#[===============================[ - core sources -#]===============================] -target_sources (xrpl_core PRIVATE - #[===============================[ - main sources: - subdir: basics - #]===============================] - src/ripple/basics/impl/Archive.cpp - src/ripple/basics/impl/base64.cpp - src/ripple/basics/impl/BasicConfig.cpp - src/ripple/basics/impl/contract.cpp - src/ripple/basics/impl/CountedObject.cpp - src/ripple/basics/impl/FileUtilities.cpp - src/ripple/basics/impl/IOUAmount.cpp - src/ripple/basics/impl/Log.cpp - src/ripple/basics/impl/make_SSLContext.cpp - src/ripple/basics/impl/mulDiv.cpp - src/ripple/basics/impl/Number.cpp - src/ripple/basics/impl/partitioned_unordered_map.cpp - src/ripple/basics/impl/ResolverAsio.cpp - src/ripple/basics/impl/StringUtilities.cpp - src/ripple/basics/impl/UptimeClock.cpp - #[===============================[ - main sources: - subdir: json - #]===============================] - src/ripple/json/impl/JsonPropertyStream.cpp - src/ripple/json/impl/Object.cpp - src/ripple/json/impl/Output.cpp - src/ripple/json/impl/Writer.cpp - src/ripple/json/impl/json_reader.cpp - src/ripple/json/impl/json_value.cpp - src/ripple/json/impl/json_valueiterator.cpp - src/ripple/json/impl/json_writer.cpp - src/ripple/json/impl/to_string.cpp - #[===============================[ - main sources: - subdir: protocol - #]===============================] - src/ripple/protocol/impl/AccountID.cpp - src/ripple/protocol/impl/AMMCore.cpp - src/ripple/protocol/impl/Book.cpp - src/ripple/protocol/impl/BuildInfo.cpp - src/ripple/protocol/impl/ErrorCodes.cpp - src/ripple/protocol/impl/Feature.cpp - src/ripple/protocol/impl/Indexes.cpp - src/ripple/protocol/impl/InnerObjectFormats.cpp - src/ripple/protocol/impl/Issue.cpp - src/ripple/protocol/impl/STIssue.cpp - src/ripple/protocol/impl/Keylet.cpp - src/ripple/protocol/impl/LedgerFormats.cpp - src/ripple/protocol/impl/LedgerHeader.cpp - src/ripple/protocol/impl/PublicKey.cpp - src/ripple/protocol/impl/Quality.cpp - src/ripple/protocol/impl/QualityFunction.cpp - src/ripple/protocol/impl/RPCErr.cpp - src/ripple/protocol/impl/Rate2.cpp - src/ripple/protocol/impl/Rules.cpp - src/ripple/protocol/impl/SField.cpp - src/ripple/protocol/impl/SOTemplate.cpp - src/ripple/protocol/impl/STAccount.cpp - src/ripple/protocol/impl/STAmount.cpp - src/ripple/protocol/impl/STArray.cpp - src/ripple/protocol/impl/STBase.cpp - src/ripple/protocol/impl/STBlob.cpp - src/ripple/protocol/impl/STCurrency.cpp - src/ripple/protocol/impl/STInteger.cpp - src/ripple/protocol/impl/STLedgerEntry.cpp - src/ripple/protocol/impl/STObject.cpp - src/ripple/protocol/impl/STParsedJSON.cpp - src/ripple/protocol/impl/STPathSet.cpp - src/ripple/protocol/impl/STXChainBridge.cpp - src/ripple/protocol/impl/STTx.cpp - src/ripple/protocol/impl/XChainAttestations.cpp - src/ripple/protocol/impl/STValidation.cpp - src/ripple/protocol/impl/STVar.cpp - src/ripple/protocol/impl/STVector256.cpp - src/ripple/protocol/impl/SecretKey.cpp - src/ripple/protocol/impl/Seed.cpp - src/ripple/protocol/impl/Serializer.cpp - src/ripple/protocol/impl/Sign.cpp - src/ripple/protocol/impl/TER.cpp - src/ripple/protocol/impl/TxFormats.cpp - src/ripple/protocol/impl/TxMeta.cpp - src/ripple/protocol/impl/UintTypes.cpp - src/ripple/protocol/impl/digest.cpp - src/ripple/protocol/impl/tokens.cpp - src/ripple/protocol/impl/NFTSyntheticSerializer.cpp - src/ripple/protocol/impl/NFTokenID.cpp - src/ripple/protocol/impl/NFTokenOfferID.cpp - #[===============================[ - main sources: - subdir: resource - #]===============================] - src/ripple/resource/impl/Charge.cpp - src/ripple/resource/impl/Consumer.cpp - src/ripple/resource/impl/Fees.cpp - src/ripple/resource/impl/ResourceManager.cpp - #[===============================[ - main sources: - subdir: server - #]===============================] - src/ripple/server/impl/JSONRPCUtil.cpp - src/ripple/server/impl/Port.cpp - #[===============================[ - main sources: - subdir: crypto - #]===============================] - src/ripple/crypto/impl/RFC1751.cpp - src/ripple/crypto/impl/csprng.cpp - src/ripple/crypto/impl/secure_erase.cpp) -# END LIBXRPL SOURCES - -add_library (Ripple::xrpl_core ALIAS xrpl_core) -target_include_directories (xrpl_core - PUBLIC - $ - $) - -target_compile_definitions(xrpl_core - PUBLIC - BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT - BOOST_CONTAINER_FWD_BAD_DEQUE - HAS_UNCAUGHT_EXCEPTIONS=1) -target_compile_options (xrpl_core - PUBLIC - $<$:-Wno-maybe-uninitialized>) -target_link_libraries (xrpl_core - PUBLIC - date::date - ed25519::ed25519 - LibArchive::LibArchive - OpenSSL::Crypto - Ripple::boost - Ripple::opts - Ripple::syslibs - secp256k1::secp256k1 - xrpl.libpb - xxHash::xxhash) -#[=================================[ - main/core headers installation -#]=================================] -# BEGIN LIBXRPL HEADERS -install ( - FILES - src/ripple/basics/Archive.h - src/ripple/basics/BasicConfig.h - src/ripple/basics/Blob.h - src/ripple/basics/Buffer.h - src/ripple/basics/ByteUtilities.h - src/ripple/basics/CompressionAlgorithms.h - src/ripple/basics/CountedObject.h - src/ripple/basics/DecayingSample.h - src/ripple/basics/Expected.h - src/ripple/basics/FeeUnits.h - src/ripple/basics/FileUtilities.h - src/ripple/basics/IOUAmount.h - src/ripple/basics/KeyCache.h - src/ripple/basics/LocalValue.h - src/ripple/basics/Log.h - src/ripple/basics/MathUtilities.h - src/ripple/basics/Number.h - src/ripple/basics/PerfLog.h - src/ripple/basics/README.md - src/ripple/basics/RangeSet.h - src/ripple/basics/Resolver.h - src/ripple/basics/ResolverAsio.h - src/ripple/basics/SHAMapHash.h - src/ripple/basics/Slice.h - src/ripple/basics/StringUtilities.h - src/ripple/basics/TaggedCache.h - src/ripple/basics/ThreadSafetyAnalysis.h - src/ripple/basics/ToString.h - src/ripple/basics/UnorderedContainers.h - src/ripple/basics/UptimeClock.h - src/ripple/basics/XRPAmount.h - src/ripple/basics/algorithm.h - src/ripple/basics/base64.h - src/ripple/basics/base_uint.h - src/ripple/basics/chrono.h - src/ripple/basics/comparators.h - src/ripple/basics/contract.h - src/ripple/basics/hardened_hash.h - src/ripple/basics/join.h - src/ripple/basics/make_SSLContext.h - src/ripple/basics/mulDiv.h - src/ripple/basics/partitioned_unordered_map.h - src/ripple/basics/random.h - src/ripple/basics/safe_cast.h - src/ripple/basics/scope.h - src/ripple/basics/spinlock.h - src/ripple/basics/strHex.h - src/ripple/basics/tagged_integer.h - DESTINATION include/ripple/basics) -install ( - FILES - src/ripple/crypto/RFC1751.h - src/ripple/crypto/csprng.h - src/ripple/crypto/secure_erase.h - DESTINATION include/ripple/crypto) -install ( - FILES - src/ripple/json/JsonPropertyStream.h - src/ripple/json/Object.h - src/ripple/json/Output.h - src/ripple/json/Writer.h - src/ripple/json/json_forwards.h - src/ripple/json/json_reader.h - src/ripple/json/json_value.h - src/ripple/json/json_writer.h - src/ripple/json/to_string.h - DESTINATION include/ripple/json) -install ( - FILES - src/ripple/json/impl/json_assert.h - DESTINATION include/ripple/json/impl) -install ( - FILES - src/ripple/protocol/AccountID.h - src/ripple/protocol/AMMCore.h - src/ripple/protocol/AmountConversions.h - src/ripple/protocol/ApiVersion.h - src/ripple/protocol/Book.h - src/ripple/protocol/BuildInfo.h - src/ripple/protocol/ErrorCodes.h - src/ripple/protocol/Feature.h - src/ripple/protocol/Fees.h - src/ripple/protocol/HashPrefix.h - src/ripple/protocol/Indexes.h - src/ripple/protocol/InnerObjectFormats.h - src/ripple/protocol/Issue.h - src/ripple/protocol/json_get_or_throw.h - src/ripple/protocol/Keylet.h - src/ripple/protocol/KeyType.h - src/ripple/protocol/KnownFormats.h - src/ripple/protocol/LedgerFormats.h - src/ripple/protocol/LedgerHeader.h - src/ripple/protocol/MultiApiJson.h - src/ripple/protocol/NFTSyntheticSerializer.h - src/ripple/protocol/NFTokenID.h - src/ripple/protocol/NFTokenOfferID.h - src/ripple/protocol/NFTSyntheticSerializer.h - src/ripple/protocol/Protocol.h - src/ripple/protocol/PublicKey.h - src/ripple/protocol/Quality.h - src/ripple/protocol/QualityFunction.h - src/ripple/protocol/Rate.h - src/ripple/protocol/RPCErr.h - src/ripple/protocol/Rules.h - src/ripple/protocol/SecretKey.h - src/ripple/protocol/Seed.h - src/ripple/protocol/SeqProxy.h - src/ripple/protocol/Serializer.h - src/ripple/protocol/SField.h - src/ripple/protocol/Sign.h - src/ripple/protocol/SOTemplate.h - src/ripple/protocol/STAccount.h - src/ripple/protocol/STAmount.h - src/ripple/protocol/STArray.h - src/ripple/protocol/STBase.h - src/ripple/protocol/STBitString.h - src/ripple/protocol/STBlob.h - src/ripple/protocol/STCurrency.h - src/ripple/protocol/STExchange.h - src/ripple/protocol/STInteger.h - src/ripple/protocol/STIssue.h - src/ripple/protocol/STLedgerEntry.h - src/ripple/protocol/STObject.h - src/ripple/protocol/STParsedJSON.h - src/ripple/protocol/STPathSet.h - src/ripple/protocol/STTx.h - src/ripple/protocol/STValidation.h - src/ripple/protocol/STVector256.h - src/ripple/protocol/STXChainBridge.h - src/ripple/protocol/SystemParameters.h - src/ripple/protocol/TER.h - src/ripple/protocol/TxFlags.h - src/ripple/protocol/TxFormats.h - src/ripple/protocol/TxMeta.h - src/ripple/protocol/UintTypes.h - src/ripple/protocol/XChainAttestations.h - src/ripple/protocol/digest.h - src/ripple/protocol/jss.h - src/ripple/protocol/nft.h - src/ripple/protocol/nftPageMask.h - src/ripple/protocol/serialize.h - src/ripple/protocol/tokens.h - DESTINATION include/ripple/protocol) -install ( - FILES - src/ripple/protocol/impl/STVar.h - src/ripple/protocol/impl/b58_utils.h - src/ripple/protocol/impl/secp256k1.h - src/ripple/protocol/impl/token_errors.h - DESTINATION include/ripple/protocol/impl) -install ( - FILES - src/ripple/resource/Charge.h - src/ripple/resource/Consumer.h - src/ripple/resource/Disposition.h - src/ripple/resource/Fees.h - src/ripple/resource/Gossip.h - src/ripple/resource/ResourceManager.h - src/ripple/resource/Types.h - DESTINATION include/ripple/resource) -install ( - FILES - src/ripple/resource/impl/Entry.h - src/ripple/resource/impl/Import.h - src/ripple/resource/impl/Key.h - src/ripple/resource/impl/Kind.h - src/ripple/resource/impl/Logic.h - src/ripple/resource/impl/Tuning.h - DESTINATION include/ripple/resource/impl) -install ( - FILES - src/ripple/server/Handoff.h - src/ripple/server/Port.h - src/ripple/server/Server.h - src/ripple/server/Session.h - src/ripple/server/SimpleWriter.h - src/ripple/server/Writer.h - src/ripple/server/WSSession.h - DESTINATION include/ripple/server) -install ( - FILES - src/ripple/server/impl/BaseHTTPPeer.h - src/ripple/server/impl/BasePeer.h - src/ripple/server/impl/BaseWSPeer.h - src/ripple/server/impl/Door.h - src/ripple/server/impl/JSONRPCUtil.h - src/ripple/server/impl/LowestLayer.h - src/ripple/server/impl/PlainHTTPPeer.h - src/ripple/server/impl/PlainWSPeer.h - src/ripple/server/impl/ServerImpl.h - src/ripple/server/impl/SSLHTTPPeer.h - src/ripple/server/impl/SSLWSPeer.h - src/ripple/server/impl/io_list.h - DESTINATION include/ripple/server/impl) -#[===================================[ - beast/legacy headers installation -#]===================================] -install ( - FILES - src/ripple/beast/clock/abstract_clock.h - src/ripple/beast/clock/basic_seconds_clock.h - src/ripple/beast/clock/manual_clock.h - DESTINATION include/ripple/beast/clock) -install ( - FILES - src/ripple/beast/core/CurrentThreadName.h - src/ripple/beast/core/LexicalCast.h - src/ripple/beast/core/List.h - src/ripple/beast/core/SemanticVersion.h - DESTINATION include/ripple/beast/core) -install ( - FILES - src/ripple/beast/hash/hash_append.h - src/ripple/beast/hash/uhash.h - src/ripple/beast/hash/xxhasher.h - DESTINATION include/ripple/beast/hash) -install ( - FILES - src/ripple/beast/net/IPAddress.h - src/ripple/beast/net/IPAddressConversion.h - src/ripple/beast/net/IPAddressV4.h - src/ripple/beast/net/IPAddressV6.h - src/ripple/beast/net/IPEndpoint.h - DESTINATION include/ripple/beast/net) -install ( - FILES - src/ripple/beast/rfc2616.h - src/ripple/beast/type_name.h - src/ripple/beast/unit_test.h - src/ripple/beast/xor_shift_engine.h - DESTINATION include/ripple/beast) -install ( - FILES - src/ripple/beast/unit_test/amount.h - src/ripple/beast/unit_test/dstream.h - src/ripple/beast/unit_test/global_suites.h - src/ripple/beast/unit_test/match.h - src/ripple/beast/unit_test/recorder.h - src/ripple/beast/unit_test/reporter.h - src/ripple/beast/unit_test/results.h - src/ripple/beast/unit_test/runner.h - src/ripple/beast/unit_test/suite_info.h - src/ripple/beast/unit_test/suite_list.h - src/ripple/beast/unit_test/suite.h - src/ripple/beast/unit_test/thread.h - DESTINATION include/ripple/beast/unit_test) -install ( - FILES - src/ripple/beast/unit_test/detail/const_container.h - DESTINATION include/ripple/beast/unit_test/detail) -install ( - FILES - src/ripple/beast/utility/Journal.h - src/ripple/beast/utility/PropertyStream.h - src/ripple/beast/utility/WrappedSink.h - src/ripple/beast/utility/Zero.h - src/ripple/beast/utility/rngfill.h - DESTINATION include/ripple/beast/utility) -# END LIBXRPL HEADERS -#[===================================================================[ - rippled executable -#]===================================================================] - -#[=========================================================[ - this one header is added as source just to keep older - versions of cmake happy. cmake 3.10+ allows - add_executable with no sources -#]=========================================================] -add_executable (rippled src/ripple/app/main/Application.h) -if (unity) - set_target_properties(rippled PROPERTIES UNITY_BUILD ON) -endif () -if (tests) - target_compile_definitions(rippled PUBLIC ENABLE_TESTS) -endif() -# BEGIN XRPLD SOURCES -target_sources (rippled PRIVATE - #[===============================[ - main sources: - subdir: app - #]===============================] - src/ripple/app/consensus/RCLConsensus.cpp - src/ripple/app/consensus/RCLCxPeerPos.cpp - src/ripple/app/consensus/RCLValidations.cpp - src/ripple/app/ledger/AcceptedLedger.cpp - src/ripple/app/ledger/AcceptedLedgerTx.cpp - src/ripple/app/ledger/AccountStateSF.cpp - src/ripple/app/ledger/BookListeners.cpp - src/ripple/app/ledger/ConsensusTransSetSF.cpp - src/ripple/app/ledger/Ledger.cpp - src/ripple/app/ledger/LedgerHistory.cpp - src/ripple/app/ledger/OrderBookDB.cpp - src/ripple/app/ledger/TransactionStateSF.cpp - src/ripple/app/ledger/impl/BuildLedger.cpp - src/ripple/app/ledger/impl/InboundLedger.cpp - src/ripple/app/ledger/impl/InboundLedgers.cpp - src/ripple/app/ledger/impl/InboundTransactions.cpp - src/ripple/app/ledger/impl/LedgerCleaner.cpp - src/ripple/app/ledger/impl/LedgerDeltaAcquire.cpp - src/ripple/app/ledger/impl/LedgerMaster.cpp - src/ripple/app/ledger/impl/LedgerReplay.cpp - src/ripple/app/ledger/impl/LedgerReplayer.cpp - src/ripple/app/ledger/impl/LedgerReplayMsgHandler.cpp - src/ripple/app/ledger/impl/LedgerReplayTask.cpp - src/ripple/app/ledger/impl/LedgerToJson.cpp - src/ripple/app/ledger/impl/LocalTxs.cpp - src/ripple/app/ledger/impl/OpenLedger.cpp - src/ripple/app/ledger/impl/SkipListAcquire.cpp - src/ripple/app/ledger/impl/TimeoutCounter.cpp - src/ripple/app/ledger/impl/TransactionAcquire.cpp - src/ripple/app/ledger/impl/TransactionMaster.cpp - src/ripple/app/main/Application.cpp - src/ripple/app/main/BasicApp.cpp - src/ripple/app/main/CollectorManager.cpp - src/ripple/app/main/GRPCServer.cpp - src/ripple/app/main/LoadManager.cpp - src/ripple/app/main/Main.cpp - src/ripple/app/main/NodeIdentity.cpp - src/ripple/app/main/NodeStoreScheduler.cpp - src/ripple/app/reporting/ReportingETL.cpp - src/ripple/app/reporting/ETLSource.cpp - src/ripple/app/reporting/P2pProxy.cpp - src/ripple/app/misc/impl/AMMHelpers.cpp - src/ripple/app/misc/impl/AMMUtils.cpp - src/ripple/app/misc/CanonicalTXSet.cpp - src/ripple/app/misc/FeeVoteImpl.cpp - src/ripple/app/misc/HashRouter.cpp - src/ripple/app/misc/NegativeUNLVote.cpp - src/ripple/app/misc/NetworkOPs.cpp - src/ripple/app/misc/SHAMapStoreImp.cpp - src/ripple/app/misc/detail/impl/WorkSSL.cpp - src/ripple/app/misc/impl/AccountTxPaging.cpp - src/ripple/app/misc/impl/AmendmentTable.cpp - src/ripple/app/misc/impl/DeliverMax.cpp - src/ripple/app/misc/impl/LoadFeeTrack.cpp - src/ripple/app/misc/impl/Manifest.cpp - src/ripple/app/misc/impl/Transaction.cpp - src/ripple/app/misc/impl/TxQ.cpp - src/ripple/app/misc/impl/ValidatorKeys.cpp - src/ripple/app/misc/impl/ValidatorList.cpp - src/ripple/app/misc/impl/ValidatorSite.cpp - src/ripple/app/paths/AccountCurrencies.cpp - src/ripple/app/paths/Credit.cpp - src/ripple/app/paths/Flow.cpp - src/ripple/app/paths/PathRequest.cpp - src/ripple/app/paths/PathRequests.cpp - src/ripple/app/paths/Pathfinder.cpp - src/ripple/app/paths/RippleCalc.cpp - src/ripple/app/paths/RippleLineCache.cpp - src/ripple/app/paths/TrustLine.cpp - src/ripple/app/paths/impl/AMMLiquidity.cpp - src/ripple/app/paths/impl/AMMOffer.cpp - src/ripple/app/paths/impl/BookStep.cpp - src/ripple/app/paths/impl/DirectStep.cpp - src/ripple/app/paths/impl/PaySteps.cpp - src/ripple/app/paths/impl/XRPEndpointStep.cpp - src/ripple/app/rdb/backend/detail/impl/Node.cpp - src/ripple/app/rdb/backend/detail/impl/Shard.cpp - src/ripple/app/rdb/backend/impl/PostgresDatabase.cpp - src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp - src/ripple/app/rdb/impl/Download.cpp - src/ripple/app/rdb/impl/PeerFinder.cpp - src/ripple/app/rdb/impl/RelationalDatabase.cpp - src/ripple/app/rdb/impl/ShardArchive.cpp - src/ripple/app/rdb/impl/State.cpp - src/ripple/app/rdb/impl/UnitaryShard.cpp - src/ripple/app/rdb/impl/Vacuum.cpp - src/ripple/app/rdb/impl/Wallet.cpp - src/ripple/app/tx/impl/AMMBid.cpp - src/ripple/app/tx/impl/AMMCreate.cpp - src/ripple/app/tx/impl/AMMDelete.cpp - src/ripple/app/tx/impl/AMMDeposit.cpp - src/ripple/app/tx/impl/AMMVote.cpp - src/ripple/app/tx/impl/AMMWithdraw.cpp - src/ripple/app/tx/impl/ApplyContext.cpp - src/ripple/app/tx/impl/BookTip.cpp - src/ripple/app/tx/impl/CancelCheck.cpp - src/ripple/app/tx/impl/CancelOffer.cpp - src/ripple/app/tx/impl/CashCheck.cpp - src/ripple/app/tx/impl/Change.cpp - src/ripple/app/tx/impl/Clawback.cpp - src/ripple/app/tx/impl/CreateCheck.cpp - src/ripple/app/tx/impl/CreateOffer.cpp - src/ripple/app/tx/impl/CreateTicket.cpp - src/ripple/app/tx/impl/DeleteAccount.cpp - src/ripple/app/tx/impl/DeleteOracle.cpp - src/ripple/app/tx/impl/DepositPreauth.cpp - src/ripple/app/tx/impl/DID.cpp - src/ripple/app/tx/impl/Escrow.cpp - src/ripple/app/tx/impl/InvariantCheck.cpp - src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp - src/ripple/app/tx/impl/NFTokenBurn.cpp - src/ripple/app/tx/impl/NFTokenCancelOffer.cpp - src/ripple/app/tx/impl/NFTokenCreateOffer.cpp - src/ripple/app/tx/impl/NFTokenMint.cpp - src/ripple/app/tx/impl/OfferStream.cpp - src/ripple/app/tx/impl/PayChan.cpp - src/ripple/app/tx/impl/Payment.cpp - src/ripple/app/tx/impl/SetAccount.cpp - src/ripple/app/tx/impl/SetOracle.cpp - src/ripple/app/tx/impl/SetRegularKey.cpp - src/ripple/app/tx/impl/SetSignerList.cpp - src/ripple/app/tx/impl/SetTrust.cpp - src/ripple/app/tx/impl/XChainBridge.cpp - src/ripple/app/tx/impl/SignerEntries.cpp - src/ripple/app/tx/impl/Taker.cpp - src/ripple/app/tx/impl/Transactor.cpp - src/ripple/app/tx/impl/apply.cpp - src/ripple/app/tx/impl/applySteps.cpp - src/ripple/app/tx/impl/details/NFTokenUtils.cpp - #[===============================[ - main sources: - subdir: conditions - #]===============================] - src/ripple/conditions/impl/Condition.cpp - src/ripple/conditions/impl/Fulfillment.cpp - src/ripple/conditions/impl/error.cpp - #[===============================[ - main sources: - subdir: core - #]===============================] - src/ripple/core/impl/Config.cpp - src/ripple/core/impl/DatabaseCon.cpp - src/ripple/core/impl/Job.cpp - src/ripple/core/impl/JobQueue.cpp - src/ripple/core/impl/LoadEvent.cpp - src/ripple/core/impl/LoadMonitor.cpp - src/ripple/core/impl/SociDB.cpp - src/ripple/core/impl/Workers.cpp - src/ripple/core/Pg.cpp - #[===============================[ - main sources: - subdir: consensus - #]===============================] - src/ripple/consensus/Consensus.cpp - #[===============================[ - main sources: - subdir: ledger - #]===============================] - src/ripple/ledger/impl/ApplyStateTable.cpp - src/ripple/ledger/impl/ApplyView.cpp - src/ripple/ledger/impl/ApplyViewBase.cpp - src/ripple/ledger/impl/ApplyViewImpl.cpp - src/ripple/ledger/impl/BookDirs.cpp - src/ripple/ledger/impl/CachedView.cpp - src/ripple/ledger/impl/Directory.cpp - src/ripple/ledger/impl/OpenView.cpp - src/ripple/ledger/impl/PaymentSandbox.cpp - src/ripple/ledger/impl/RawStateTable.cpp - src/ripple/ledger/impl/ReadView.cpp - src/ripple/ledger/impl/View.cpp - #[===============================[ - main sources: - subdir: net - #]===============================] - src/ripple/net/impl/DatabaseDownloader.cpp - src/ripple/net/impl/HTTPClient.cpp - src/ripple/net/impl/HTTPDownloader.cpp - src/ripple/net/impl/HTTPStream.cpp - src/ripple/net/impl/InfoSub.cpp - src/ripple/net/impl/RPCCall.cpp - src/ripple/net/impl/RPCSub.cpp - src/ripple/net/impl/RegisterSSLCerts.cpp - #[===============================[ - main sources: - subdir: nodestore - #]===============================] - src/ripple/nodestore/backend/CassandraFactory.cpp - src/ripple/nodestore/backend/MemoryFactory.cpp - src/ripple/nodestore/backend/NuDBFactory.cpp - src/ripple/nodestore/backend/NullFactory.cpp - src/ripple/nodestore/backend/RocksDBFactory.cpp - src/ripple/nodestore/impl/BatchWriter.cpp - src/ripple/nodestore/impl/Database.cpp - src/ripple/nodestore/impl/DatabaseNodeImp.cpp - src/ripple/nodestore/impl/DatabaseRotatingImp.cpp - src/ripple/nodestore/impl/DatabaseShardImp.cpp - src/ripple/nodestore/impl/DeterministicShard.cpp - src/ripple/nodestore/impl/DecodedBlob.cpp - src/ripple/nodestore/impl/DummyScheduler.cpp - src/ripple/nodestore/impl/ManagerImp.cpp - src/ripple/nodestore/impl/NodeObject.cpp - src/ripple/nodestore/impl/Shard.cpp - src/ripple/nodestore/impl/ShardInfo.cpp - src/ripple/nodestore/impl/TaskQueue.cpp - #[===============================[ - main sources: - subdir: overlay - #]===============================] - src/ripple/overlay/impl/Cluster.cpp - src/ripple/overlay/impl/ConnectAttempt.cpp - src/ripple/overlay/impl/Handshake.cpp - src/ripple/overlay/impl/Message.cpp - src/ripple/overlay/impl/OverlayImpl.cpp - src/ripple/overlay/impl/PeerImp.cpp - src/ripple/overlay/impl/PeerReservationTable.cpp - src/ripple/overlay/impl/PeerSet.cpp - src/ripple/overlay/impl/ProtocolVersion.cpp - src/ripple/overlay/impl/TrafficCount.cpp - src/ripple/overlay/impl/TxMetrics.cpp - #[===============================[ - main sources: - subdir: peerfinder - #]===============================] - src/ripple/peerfinder/impl/Bootcache.cpp - src/ripple/peerfinder/impl/Endpoint.cpp - src/ripple/peerfinder/impl/PeerfinderConfig.cpp - src/ripple/peerfinder/impl/PeerfinderManager.cpp - src/ripple/peerfinder/impl/SlotImp.cpp - src/ripple/peerfinder/impl/SourceStrings.cpp - #[===============================[ - main sources: - subdir: rpc - #]===============================] - src/ripple/rpc/handlers/AccountChannels.cpp - src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp - src/ripple/rpc/handlers/AccountInfo.cpp - src/ripple/rpc/handlers/AccountLines.cpp - src/ripple/rpc/handlers/AccountObjects.cpp - src/ripple/rpc/handlers/AccountOffers.cpp - src/ripple/rpc/handlers/AccountTx.cpp - src/ripple/rpc/handlers/AMMInfo.cpp - src/ripple/rpc/handlers/BlackList.cpp - src/ripple/rpc/handlers/BookOffers.cpp - src/ripple/rpc/handlers/CanDelete.cpp - src/ripple/rpc/handlers/Connect.cpp - src/ripple/rpc/handlers/ConsensusInfo.cpp - src/ripple/rpc/handlers/CrawlShards.cpp - src/ripple/rpc/handlers/DepositAuthorized.cpp - src/ripple/rpc/handlers/DownloadShard.cpp - src/ripple/rpc/handlers/Feature1.cpp - src/ripple/rpc/handlers/Fee1.cpp - src/ripple/rpc/handlers/FetchInfo.cpp - src/ripple/rpc/handlers/GatewayBalances.cpp - src/ripple/rpc/handlers/GetCounts.cpp - src/ripple/rpc/handlers/GetAggregatePrice.cpp - src/ripple/rpc/handlers/LedgerAccept.cpp - src/ripple/rpc/handlers/LedgerCleanerHandler.cpp - src/ripple/rpc/handlers/LedgerClosed.cpp - src/ripple/rpc/handlers/LedgerCurrent.cpp - src/ripple/rpc/handlers/LedgerData.cpp - src/ripple/rpc/handlers/LedgerDiff.cpp - src/ripple/rpc/handlers/LedgerEntry.cpp - src/ripple/rpc/handlers/LedgerHandler.cpp - src/ripple/rpc/handlers/LedgerHeader.cpp - src/ripple/rpc/handlers/LedgerRequest.cpp - src/ripple/rpc/handlers/LogLevel.cpp - src/ripple/rpc/handlers/LogRotate.cpp - src/ripple/rpc/handlers/Manifest.cpp - src/ripple/rpc/handlers/NFTOffers.cpp - src/ripple/rpc/handlers/NodeToShard.cpp - src/ripple/rpc/handlers/NoRippleCheck.cpp - src/ripple/rpc/handlers/OwnerInfo.cpp - src/ripple/rpc/handlers/PathFind.cpp - src/ripple/rpc/handlers/PayChanClaim.cpp - src/ripple/rpc/handlers/Peers.cpp - src/ripple/rpc/handlers/Ping.cpp - src/ripple/rpc/handlers/Print.cpp - src/ripple/rpc/handlers/Random.cpp - src/ripple/rpc/handlers/Reservations.cpp - src/ripple/rpc/handlers/RipplePathFind.cpp - src/ripple/rpc/handlers/ServerInfo.cpp - src/ripple/rpc/handlers/ServerState.cpp - src/ripple/rpc/handlers/SignFor.cpp - src/ripple/rpc/handlers/SignHandler.cpp - src/ripple/rpc/handlers/Stop.cpp - src/ripple/rpc/handlers/Submit.cpp - src/ripple/rpc/handlers/SubmitMultiSigned.cpp - src/ripple/rpc/handlers/Subscribe.cpp - src/ripple/rpc/handlers/TransactionEntry.cpp - src/ripple/rpc/handlers/Tx.cpp - src/ripple/rpc/handlers/TxHistory.cpp - src/ripple/rpc/handlers/TxReduceRelay.cpp - src/ripple/rpc/handlers/UnlList.cpp - src/ripple/rpc/handlers/Unsubscribe.cpp - src/ripple/rpc/handlers/ValidationCreate.cpp - src/ripple/rpc/handlers/ValidatorInfo.cpp - src/ripple/rpc/handlers/ValidatorListSites.cpp - src/ripple/rpc/handlers/Validators.cpp - src/ripple/rpc/handlers/WalletPropose.cpp - src/ripple/rpc/impl/DeliveredAmount.cpp - src/ripple/rpc/impl/Handler.cpp - src/ripple/rpc/impl/LegacyPathFind.cpp - src/ripple/rpc/impl/RPCHandler.cpp - src/ripple/rpc/impl/RPCHelpers.cpp - src/ripple/rpc/impl/Role.cpp - src/ripple/rpc/impl/ServerHandler.cpp - src/ripple/rpc/impl/ShardArchiveHandler.cpp - src/ripple/rpc/impl/ShardVerificationScheduler.cpp - src/ripple/rpc/impl/Status.cpp - src/ripple/rpc/impl/TransactionSign.cpp - #[===============================[ - main sources: - subdir: perflog - #]===============================] - src/ripple/perflog/impl/PerfLogImp.cpp - #[===============================[ - main sources: - subdir: shamap - #]===============================] - src/ripple/shamap/impl/NodeFamily.cpp - src/ripple/shamap/impl/SHAMap.cpp - src/ripple/shamap/impl/SHAMapDelta.cpp - src/ripple/shamap/impl/SHAMapInnerNode.cpp - src/ripple/shamap/impl/SHAMapLeafNode.cpp - src/ripple/shamap/impl/SHAMapNodeID.cpp - src/ripple/shamap/impl/SHAMapSync.cpp - src/ripple/shamap/impl/SHAMapTreeNode.cpp - src/ripple/shamap/impl/ShardFamily.cpp) -# END XRPLD SOURCES - - #[===============================[ - test sources: - subdir: app - #]===============================] -if (tests) - target_sources (rippled PRIVATE - src/test/app/AccountDelete_test.cpp - src/test/app/AccountTxPaging_test.cpp - src/test/app/AmendmentTable_test.cpp - src/test/app/AMM_test.cpp - src/test/app/AMMCalc_test.cpp - src/test/app/AMMExtended_test.cpp - src/test/app/Check_test.cpp - src/test/app/Clawback_test.cpp - src/test/app/CrossingLimits_test.cpp - src/test/app/DeliverMin_test.cpp - src/test/app/DepositAuth_test.cpp - src/test/app/Discrepancy_test.cpp - src/test/app/DID_test.cpp - src/test/app/DNS_test.cpp - src/test/app/Escrow_test.cpp - src/test/app/FeeVote_test.cpp - src/test/app/Flow_test.cpp - src/test/app/Freeze_test.cpp - src/test/app/HashRouter_test.cpp - src/test/app/LedgerHistory_test.cpp - src/test/app/LedgerLoad_test.cpp - src/test/app/LedgerMaster_test.cpp - src/test/app/LedgerReplay_test.cpp - src/test/app/LoadFeeTrack_test.cpp - src/test/app/Manifest_test.cpp - src/test/app/MultiSign_test.cpp - src/test/app/NetworkID_test.cpp - src/test/app/NFToken_test.cpp - src/test/app/NFTokenBurn_test.cpp - src/test/app/NFTokenDir_test.cpp - src/test/app/OfferStream_test.cpp - src/test/app/Offer_test.cpp - src/test/app/Oracle_test.cpp - src/test/app/OversizeMeta_test.cpp - src/test/app/Path_test.cpp - src/test/app/PayChan_test.cpp - src/test/app/PayStrand_test.cpp - src/test/app/PseudoTx_test.cpp - src/test/app/RCLCensorshipDetector_test.cpp - src/test/app/RCLValidations_test.cpp - src/test/app/ReducedOffer_test.cpp - src/test/app/Regression_test.cpp - src/test/app/SHAMapStore_test.cpp - src/test/app/XChain_test.cpp - src/test/app/SetAuth_test.cpp - src/test/app/SetRegularKey_test.cpp - src/test/app/SetTrust_test.cpp - src/test/app/Taker_test.cpp - src/test/app/TheoreticalQuality_test.cpp - src/test/app/Ticket_test.cpp - src/test/app/Transaction_ordering_test.cpp - src/test/app/TrustAndBalance_test.cpp - src/test/app/TxQ_test.cpp - src/test/app/ValidatorKeys_test.cpp - src/test/app/ValidatorList_test.cpp - src/test/app/ValidatorSite_test.cpp - src/test/app/tx/apply_test.cpp - #[===============================[ - test sources: - subdir: basics - #]===============================] - src/test/basics/Buffer_test.cpp - src/test/basics/DetectCrash_test.cpp - src/test/basics/Expected_test.cpp - src/test/basics/FileUtilities_test.cpp - src/test/basics/IOUAmount_test.cpp - src/test/basics/KeyCache_test.cpp - src/test/basics/Number_test.cpp - src/test/basics/PerfLog_test.cpp - src/test/basics/RangeSet_test.cpp - src/test/basics/scope_test.cpp - src/test/basics/Slice_test.cpp - src/test/basics/StringUtilities_test.cpp - src/test/basics/TaggedCache_test.cpp - src/test/basics/XRPAmount_test.cpp - src/test/basics/base58_test.cpp - src/test/basics/base64_test.cpp - src/test/basics/base_uint_test.cpp - src/test/basics/contract_test.cpp - src/test/basics/FeeUnits_test.cpp - src/test/basics/hardened_hash_test.cpp - src/test/basics/join_test.cpp - src/test/basics/mulDiv_test.cpp - src/test/basics/tagged_integer_test.cpp - #[===============================[ - test sources: - subdir: beast - #]===============================] - src/test/beast/IPEndpoint_test.cpp - src/test/beast/LexicalCast_test.cpp - src/test/beast/SemanticVersion_test.cpp - src/test/beast/aged_associative_container_test.cpp - src/test/beast/beast_CurrentThreadName_test.cpp - src/test/beast/beast_Journal_test.cpp - src/test/beast/beast_PropertyStream_test.cpp - src/test/beast/beast_Zero_test.cpp - src/test/beast/beast_abstract_clock_test.cpp - src/test/beast/beast_basic_seconds_clock_test.cpp - src/test/beast/beast_io_latency_probe_test.cpp - src/test/beast/define_print.cpp - #[===============================[ - test sources: - subdir: conditions - #]===============================] - src/test/conditions/PreimageSha256_test.cpp - #[===============================[ - test sources: - subdir: consensus - #]===============================] - src/test/consensus/ByzantineFailureSim_test.cpp - src/test/consensus/Consensus_test.cpp - src/test/consensus/DistributedValidatorsSim_test.cpp - src/test/consensus/LedgerTiming_test.cpp - src/test/consensus/LedgerTrie_test.cpp - src/test/consensus/NegativeUNL_test.cpp - src/test/consensus/ScaleFreeSim_test.cpp - src/test/consensus/Validations_test.cpp - #[===============================[ - test sources: - subdir: core - #]===============================] - src/test/core/ClosureCounter_test.cpp - src/test/core/Config_test.cpp - src/test/core/Coroutine_test.cpp - src/test/core/CryptoPRNG_test.cpp - src/test/core/JobQueue_test.cpp - src/test/core/SociDB_test.cpp - src/test/core/Workers_test.cpp - #[===============================[ - test sources: - subdir: csf - #]===============================] - src/test/csf/BasicNetwork_test.cpp - src/test/csf/Digraph_test.cpp - src/test/csf/Histogram_test.cpp - src/test/csf/Scheduler_test.cpp - src/test/csf/impl/Sim.cpp - src/test/csf/impl/ledgers.cpp - #[===============================[ - test sources: - subdir: json - #]===============================] - src/test/json/Object_test.cpp - src/test/json/Output_test.cpp - src/test/json/Writer_test.cpp - src/test/json/json_value_test.cpp - #[===============================[ - test sources: - subdir: jtx - #]===============================] - src/test/jtx/Env_test.cpp - src/test/jtx/WSClient_test.cpp - src/test/jtx/impl/Account.cpp - src/test/jtx/impl/AMM.cpp - src/test/jtx/impl/AMMTest.cpp - src/test/jtx/impl/Env.cpp - src/test/jtx/impl/JSONRPCClient.cpp - src/test/jtx/impl/Oracle.cpp - src/test/jtx/impl/TestHelpers.cpp - src/test/jtx/impl/WSClient.cpp - src/test/jtx/impl/acctdelete.cpp - src/test/jtx/impl/account_txn_id.cpp - src/test/jtx/impl/amount.cpp - src/test/jtx/impl/attester.cpp - src/test/jtx/impl/balance.cpp - src/test/jtx/impl/check.cpp - src/test/jtx/impl/delivermin.cpp - src/test/jtx/impl/deposit.cpp - src/test/jtx/impl/did.cpp - src/test/jtx/impl/envconfig.cpp - src/test/jtx/impl/fee.cpp - src/test/jtx/impl/flags.cpp - src/test/jtx/impl/invoice_id.cpp - src/test/jtx/impl/jtx_json.cpp - src/test/jtx/impl/last_ledger_sequence.cpp - src/test/jtx/impl/memo.cpp - src/test/jtx/impl/multisign.cpp - src/test/jtx/impl/offer.cpp - src/test/jtx/impl/owners.cpp - src/test/jtx/impl/paths.cpp - src/test/jtx/impl/pay.cpp - src/test/jtx/impl/quality2.cpp - src/test/jtx/impl/rate.cpp - src/test/jtx/impl/regkey.cpp - src/test/jtx/impl/sendmax.cpp - src/test/jtx/impl/seq.cpp - src/test/jtx/impl/xchain_bridge.cpp - src/test/jtx/impl/sig.cpp - src/test/jtx/impl/tag.cpp - src/test/jtx/impl/ticket.cpp - src/test/jtx/impl/token.cpp - src/test/jtx/impl/trust.cpp - src/test/jtx/impl/txflags.cpp - src/test/jtx/impl/utility.cpp - - #[===============================[ - test sources: - subdir: ledger - #]===============================] - src/test/ledger/BookDirs_test.cpp - src/test/ledger/Directory_test.cpp - src/test/ledger/Invariants_test.cpp - src/test/ledger/PaymentSandbox_test.cpp - src/test/ledger/PendingSaves_test.cpp - src/test/ledger/SkipList_test.cpp - src/test/ledger/View_test.cpp - #[===============================[ - test sources: - subdir: net - #]===============================] - src/test/net/DatabaseDownloader_test.cpp - #[===============================[ - test sources: - subdir: nodestore - #]===============================] - src/test/nodestore/Backend_test.cpp - src/test/nodestore/Basics_test.cpp - src/test/nodestore/DatabaseShard_test.cpp - src/test/nodestore/Database_test.cpp - src/test/nodestore/Timing_test.cpp - src/test/nodestore/import_test.cpp - src/test/nodestore/varint_test.cpp - #[===============================[ - test sources: - subdir: overlay - #]===============================] - src/test/overlay/ProtocolVersion_test.cpp - src/test/overlay/cluster_test.cpp - src/test/overlay/short_read_test.cpp - src/test/overlay/compression_test.cpp - src/test/overlay/reduce_relay_test.cpp - src/test/overlay/handshake_test.cpp - src/test/overlay/tx_reduce_relay_test.cpp - #[===============================[ - test sources: - subdir: peerfinder - #]===============================] - src/test/peerfinder/Livecache_test.cpp - src/test/peerfinder/PeerFinder_test.cpp - #[===============================[ - test sources: - subdir: protocol - #]===============================] - src/test/protocol/ApiVersion_test.cpp - src/test/protocol/BuildInfo_test.cpp - src/test/protocol/InnerObjectFormats_test.cpp - src/test/protocol/Issue_test.cpp - src/test/protocol/Hooks_test.cpp - src/test/protocol/Memo_test.cpp - src/test/protocol/MultiApiJson_test.cpp - src/test/protocol/PublicKey_test.cpp - src/test/protocol/Quality_test.cpp - src/test/protocol/STAccount_test.cpp - src/test/protocol/STAmount_test.cpp - src/test/protocol/STObject_test.cpp - src/test/protocol/STTx_test.cpp - src/test/protocol/STValidation_test.cpp - src/test/protocol/SecretKey_test.cpp - src/test/protocol/Seed_test.cpp - src/test/protocol/SeqProxy_test.cpp - src/test/protocol/TER_test.cpp - src/test/protocol/types_test.cpp - #[===============================[ - test sources: - subdir: resource - #]===============================] - src/test/resource/Logic_test.cpp - #[===============================[ - test sources: - subdir: rpc - #]===============================] - src/test/rpc/AccountCurrencies_test.cpp - src/test/rpc/AccountInfo_test.cpp - src/test/rpc/AccountLines_test.cpp - src/test/rpc/AccountObjects_test.cpp - src/test/rpc/AccountOffers_test.cpp - src/test/rpc/AccountSet_test.cpp - src/test/rpc/AccountTx_test.cpp - src/test/rpc/AmendmentBlocked_test.cpp - src/test/rpc/AMMInfo_test.cpp - src/test/rpc/Book_test.cpp - src/test/rpc/DepositAuthorized_test.cpp - src/test/rpc/DeliveredAmount_test.cpp - src/test/rpc/Feature_test.cpp - src/test/rpc/GatewayBalances_test.cpp - src/test/rpc/GetAggregatePrice_test.cpp - src/test/rpc/GetCounts_test.cpp - src/test/rpc/JSONRPC_test.cpp - src/test/rpc/KeyGeneration_test.cpp - src/test/rpc/LedgerClosed_test.cpp - src/test/rpc/LedgerData_test.cpp - src/test/rpc/LedgerHeader_test.cpp - src/test/rpc/LedgerRPC_test.cpp - src/test/rpc/LedgerRequestRPC_test.cpp - src/test/rpc/ManifestRPC_test.cpp - src/test/rpc/NodeToShardRPC_test.cpp - src/test/rpc/NoRippleCheck_test.cpp - src/test/rpc/NoRipple_test.cpp - src/test/rpc/OwnerInfo_test.cpp - src/test/rpc/Peers_test.cpp - src/test/rpc/ReportingETL_test.cpp - src/test/rpc/Roles_test.cpp - src/test/rpc/RPCCall_test.cpp - src/test/rpc/RPCOverload_test.cpp - src/test/rpc/RobustTransaction_test.cpp - src/test/rpc/ServerInfo_test.cpp - src/test/rpc/ShardArchiveHandler_test.cpp - src/test/rpc/Status_test.cpp - src/test/rpc/Subscribe_test.cpp - src/test/rpc/Transaction_test.cpp - src/test/rpc/TransactionEntry_test.cpp - src/test/rpc/TransactionHistory_test.cpp - src/test/rpc/ValidatorInfo_test.cpp - src/test/rpc/ValidatorRPC_test.cpp - src/test/rpc/Version_test.cpp - src/test/rpc/Handler_test.cpp - #[===============================[ - test sources: - subdir: server - #]===============================] - src/test/server/ServerStatus_test.cpp - src/test/server/Server_test.cpp - #[===============================[ - test sources: - subdir: shamap - #]===============================] - src/test/shamap/FetchPack_test.cpp - src/test/shamap/SHAMapSync_test.cpp - src/test/shamap/SHAMap_test.cpp - #[===============================[ - test sources: - subdir: unit_test - #]===============================] - src/test/unit_test/multi_runner.cpp) -endif () #tests - -target_link_libraries (rippled - Ripple::boost - Ripple::opts - Ripple::libs - Ripple::xrpl_core - ) -exclude_if_included (rippled) -# define a macro for tests that might need to -# be exluded or run differently in CI environment -if (is_ci) - target_compile_definitions(rippled PRIVATE RIPPLED_RUNNING_IN_CI) -endif () - -if(reporting) -set_target_properties(rippled PROPERTIES OUTPUT_NAME rippled-reporting) -get_target_property(BIN_NAME rippled OUTPUT_NAME) -message(STATUS "Reporting mode build: rippled renamed ${BIN_NAME}") - target_compile_definitions(rippled PRIVATE RIPPLED_REPORTING) -endif() - -# any files that don't play well with unity should be added here -if (tests) - set_source_files_properties( - # these two seem to produce conflicts in beast teardown template methods - src/test/rpc/ValidatorRPC_test.cpp - src/test/rpc/ShardArchiveHandler_test.cpp - PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) -endif () #tests diff --git a/Builds/CMake/RippledMultiConfig.cmake b/Builds/CMake/RippledMultiConfig.cmake deleted file mode 100644 index 6df48a3d964..00000000000 --- a/Builds/CMake/RippledMultiConfig.cmake +++ /dev/null @@ -1,39 +0,0 @@ -#[===================================================================[ - multiconfig misc -#]===================================================================] - -if (is_multiconfig) - # This code finds all source files in the src subdirectory for inclusion - # in the IDE file tree as non-compiled sources. Since this file list will - # have some overlap with files we have already added to our targets to - # be compiled, we explicitly remove any of these target source files from - # this list. - file (GLOB_RECURSE all_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - CONFIGURE_DEPENDS - src/*.* Builds/*.md docs/*.md src/*.md Builds/*.cmake) - file(GLOB md_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS - *.md) - LIST(APPEND all_sources ${md_files}) - foreach (_target secp256k1::secp256k1 ed25519::ed25519 xrpl_core rippled) - get_target_property (_type ${_target} TYPE) - if(_type STREQUAL "INTERFACE_LIBRARY") - continue() - endif() - get_target_property (_src ${_target} SOURCES) - list (REMOVE_ITEM all_sources ${_src}) - endforeach () - target_sources (rippled PRIVATE ${all_sources}) - set_property ( - SOURCE ${all_sources} - APPEND - PROPERTY HEADER_FILE_ONLY true) - if (MSVC) - set_property( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTY VS_STARTUP_PROJECT rippled) - endif () - - group_sources(src) - group_sources(docs) - group_sources(Builds) -endif () diff --git a/Builds/CMake/RippledVersion.cmake b/Builds/CMake/RippledVersion.cmake deleted file mode 100644 index 936852af58b..00000000000 --- a/Builds/CMake/RippledVersion.cmake +++ /dev/null @@ -1,15 +0,0 @@ -#[===================================================================[ - read version from source -#]===================================================================] - -file (STRINGS src/ripple/protocol/impl/BuildInfo.cpp BUILD_INFO) -foreach (line_ ${BUILD_INFO}) - if (line_ MATCHES "versionString[ ]*=[ ]*\"(.+)\"") - set (rippled_version ${CMAKE_MATCH_1}) - endif () -endforeach () -if (rippled_version) - message (STATUS "rippled version: ${rippled_version}") -else () - message (FATAL_ERROR "unable to determine rippled version") -endif () diff --git a/Builds/levelization/levelization.sh b/Builds/levelization/levelization.sh index 34487f7464a..3c43a23092f 100755 --- a/Builds/levelization/levelization.sh +++ b/Builds/levelization/levelization.sh @@ -13,12 +13,15 @@ then git clean -ix fi +# Ensure all sorting is ASCII-order consistently across platforms. +export LANG=C + rm -rfv results mkdir results includes="$( pwd )/results/rawincludes.txt" pushd ../.. echo Raw includes: -grep -r '#include.*/.*\.h' src/ripple/ src/test/ | \ +grep -r '#include.*/.*\.h' include src | \ grep -v boost | tee ${includes} popd pushd results diff --git a/Builds/levelization/results/loops.txt b/Builds/levelization/results/loops.txt index cb137f497cb..669fb6bbe33 100644 --- a/Builds/levelization/results/loops.txt +++ b/Builds/levelization/results/loops.txt @@ -1,51 +1,45 @@ -Loop: ripple.app ripple.core - ripple.app > ripple.core - -Loop: ripple.app ripple.ledger - ripple.app > ripple.ledger - -Loop: ripple.app ripple.net - ripple.app > ripple.net +Loop: test.jtx test.toplevel + test.toplevel > test.jtx -Loop: ripple.app ripple.nodestore - ripple.app > ripple.nodestore +Loop: test.jtx test.unit_test + test.unit_test == test.jtx -Loop: ripple.app ripple.overlay - ripple.overlay ~= ripple.app +Loop: xrpl.basics xrpl.json + xrpl.json == xrpl.basics -Loop: ripple.app ripple.peerfinder - ripple.app > ripple.peerfinder +Loop: xrpld.app xrpld.core + xrpld.app > xrpld.core -Loop: ripple.app ripple.rpc - ripple.rpc > ripple.app +Loop: xrpld.app xrpld.ledger + xrpld.app > xrpld.ledger -Loop: ripple.app ripple.shamap - ripple.app > ripple.shamap +Loop: xrpld.app xrpld.net + xrpld.app > xrpld.net -Loop: ripple.basics ripple.core - ripple.core > ripple.basics +Loop: xrpld.app xrpld.overlay + xrpld.overlay == xrpld.app -Loop: ripple.basics ripple.json - ripple.json ~= ripple.basics +Loop: xrpld.app xrpld.peerfinder + xrpld.app > xrpld.peerfinder -Loop: ripple.basics ripple.protocol - ripple.protocol > ripple.basics +Loop: xrpld.app xrpld.rpc + xrpld.rpc > xrpld.app -Loop: ripple.core ripple.net - ripple.net > ripple.core +Loop: xrpld.app xrpld.shamap + xrpld.app > xrpld.shamap -Loop: ripple.net ripple.rpc - ripple.rpc > ripple.net +Loop: xrpld.core xrpld.net + xrpld.net > xrpld.core -Loop: ripple.nodestore ripple.overlay - ripple.overlay ~= ripple.nodestore +Loop: xrpld.core xrpld.perflog + xrpld.perflog == xrpld.core -Loop: ripple.overlay ripple.rpc - ripple.rpc ~= ripple.overlay +Loop: xrpld.net xrpld.rpc + xrpld.rpc ~= xrpld.net -Loop: test.jtx test.toplevel - test.toplevel > test.jtx +Loop: xrpld.overlay xrpld.rpc + xrpld.rpc ~= xrpld.overlay -Loop: test.jtx test.unit_test - test.unit_test == test.jtx +Loop: xrpld.perflog xrpld.rpc + xrpld.rpc ~= xrpld.perflog diff --git a/Builds/levelization/results/ordering.txt b/Builds/levelization/results/ordering.txt index ed54065d03e..396c99cb711 100644 --- a/Builds/levelization/results/ordering.txt +++ b/Builds/levelization/results/ordering.txt @@ -1,229 +1,195 @@ -ripple.app > ripple.basics -ripple.app > ripple.beast -ripple.app > ripple.conditions -ripple.app > ripple.consensus -ripple.app > ripple.crypto -ripple.app > ripple.json -ripple.app > ripple.protocol -ripple.app > ripple.resource -ripple.app > test.unit_test -ripple.basics > ripple.beast -ripple.conditions > ripple.basics -ripple.conditions > ripple.protocol -ripple.consensus > ripple.basics -ripple.consensus > ripple.beast -ripple.consensus > ripple.json -ripple.consensus > ripple.protocol -ripple.core > ripple.beast -ripple.core > ripple.json -ripple.core > ripple.protocol -ripple.crypto > ripple.basics -ripple.json > ripple.beast -ripple.ledger > ripple.basics -ripple.ledger > ripple.beast -ripple.ledger > ripple.core -ripple.ledger > ripple.json -ripple.ledger > ripple.protocol -ripple.net > ripple.basics -ripple.net > ripple.beast -ripple.net > ripple.json -ripple.net > ripple.protocol -ripple.net > ripple.resource -ripple.nodestore > ripple.basics -ripple.nodestore > ripple.beast -ripple.nodestore > ripple.core -ripple.nodestore > ripple.json -ripple.nodestore > ripple.protocol -ripple.nodestore > ripple.unity -ripple.overlay > ripple.basics -ripple.overlay > ripple.beast -ripple.overlay > ripple.core -ripple.overlay > ripple.json -ripple.overlay > ripple.peerfinder -ripple.overlay > ripple.protocol -ripple.overlay > ripple.resource -ripple.overlay > ripple.server -ripple.peerfinder > ripple.basics -ripple.peerfinder > ripple.beast -ripple.peerfinder > ripple.core -ripple.peerfinder > ripple.protocol -ripple.perflog > ripple.basics -ripple.perflog > ripple.beast -ripple.perflog > ripple.core -ripple.perflog > ripple.json -ripple.perflog > ripple.nodestore -ripple.perflog > ripple.protocol -ripple.perflog > ripple.rpc -ripple.protocol > ripple.beast -ripple.protocol > ripple.crypto -ripple.protocol > ripple.json -ripple.resource > ripple.basics -ripple.resource > ripple.beast -ripple.resource > ripple.json -ripple.resource > ripple.protocol -ripple.rpc > ripple.basics -ripple.rpc > ripple.beast -ripple.rpc > ripple.core -ripple.rpc > ripple.crypto -ripple.rpc > ripple.json -ripple.rpc > ripple.ledger -ripple.rpc > ripple.nodestore -ripple.rpc > ripple.protocol -ripple.rpc > ripple.resource -ripple.rpc > ripple.server -ripple.rpc > ripple.shamap -ripple.server > ripple.basics -ripple.server > ripple.beast -ripple.server > ripple.crypto -ripple.server > ripple.json -ripple.server > ripple.protocol -ripple.shamap > ripple.basics -ripple.shamap > ripple.beast -ripple.shamap > ripple.crypto -ripple.shamap > ripple.nodestore -ripple.shamap > ripple.protocol -test.app > ripple.app -test.app > ripple.basics -test.app > ripple.beast -test.app > ripple.core -test.app > ripple.json -test.app > ripple.ledger -test.app > ripple.overlay -test.app > ripple.protocol -test.app > ripple.resource -test.app > ripple.rpc +libxrpl.basics > xrpl.basics +libxrpl.basics > xrpl.protocol +libxrpl.crypto > xrpl.basics +libxrpl.json > xrpl.basics +libxrpl.json > xrpl.json +libxrpl.protocol > xrpl.basics +libxrpl.protocol > xrpl.json +libxrpl.protocol > xrpl.protocol +libxrpl.resource > xrpl.basics +libxrpl.resource > xrpl.resource +libxrpl.server > xrpl.basics +libxrpl.server > xrpl.json +libxrpl.server > xrpl.protocol +libxrpl.server > xrpl.server test.app > test.jtx test.app > test.rpc test.app > test.toplevel test.app > test.unit_test -test.basics > ripple.basics -test.basics > ripple.beast -test.basics > ripple.json -test.basics > ripple.protocol -test.basics > ripple.rpc +test.app > xrpl.basics +test.app > xrpld.app +test.app > xrpld.core +test.app > xrpld.ledger +test.app > xrpld.overlay +test.app > xrpld.rpc +test.app > xrpl.json +test.app > xrpl.protocol +test.app > xrpl.resource test.basics > test.jtx test.basics > test.unit_test -test.beast > ripple.basics -test.beast > ripple.beast -test.conditions > ripple.basics -test.conditions > ripple.beast -test.conditions > ripple.conditions -test.consensus > ripple.app -test.consensus > ripple.basics -test.consensus > ripple.beast -test.consensus > ripple.consensus -test.consensus > ripple.ledger +test.basics > xrpl.basics +test.basics > xrpld.perflog +test.basics > xrpld.rpc +test.basics > xrpl.json +test.basics > xrpl.protocol +test.beast > xrpl.basics +test.conditions > xrpl.basics +test.conditions > xrpld.conditions test.consensus > test.csf test.consensus > test.toplevel test.consensus > test.unit_test -test.core > ripple.basics -test.core > ripple.beast -test.core > ripple.core -test.core > ripple.crypto -test.core > ripple.json -test.core > ripple.server +test.consensus > xrpl.basics +test.consensus > xrpld.app +test.consensus > xrpld.consensus +test.consensus > xrpld.ledger test.core > test.jtx test.core > test.toplevel test.core > test.unit_test -test.csf > ripple.basics -test.csf > ripple.beast -test.csf > ripple.consensus -test.csf > ripple.json -test.csf > ripple.protocol -test.json > ripple.beast -test.json > ripple.json +test.core > xrpl.basics +test.core > xrpld.core +test.core > xrpld.perflog +test.core > xrpl.json +test.core > xrpl.server +test.csf > xrpl.basics +test.csf > xrpld.consensus +test.csf > xrpl.json +test.csf > xrpl.protocol test.json > test.jtx -test.jtx > ripple.app -test.jtx > ripple.basics -test.jtx > ripple.beast -test.jtx > ripple.consensus -test.jtx > ripple.core -test.jtx > ripple.json -test.jtx > ripple.ledger -test.jtx > ripple.net -test.jtx > ripple.protocol -test.jtx > ripple.resource -test.jtx > ripple.rpc -test.jtx > ripple.server -test.ledger > ripple.app -test.ledger > ripple.basics -test.ledger > ripple.beast -test.ledger > ripple.core -test.ledger > ripple.ledger -test.ledger > ripple.protocol +test.json > xrpl.json +test.jtx > xrpl.basics +test.jtx > xrpld.app +test.jtx > xrpld.consensus +test.jtx > xrpld.core +test.jtx > xrpld.ledger +test.jtx > xrpld.net +test.jtx > xrpld.rpc +test.jtx > xrpl.json +test.jtx > xrpl.protocol +test.jtx > xrpl.resource +test.jtx > xrpl.server test.ledger > test.jtx test.ledger > test.toplevel -test.net > ripple.net -test.net > test.jtx -test.net > test.toplevel -test.net > test.unit_test -test.nodestore > ripple.app -test.nodestore > ripple.basics -test.nodestore > ripple.beast -test.nodestore > ripple.core -test.nodestore > ripple.nodestore -test.nodestore > ripple.protocol -test.nodestore > ripple.unity +test.ledger > xrpl.basics +test.ledger > xrpld.app +test.ledger > xrpld.core +test.ledger > xrpld.ledger +test.ledger > xrpl.protocol test.nodestore > test.jtx test.nodestore > test.toplevel test.nodestore > test.unit_test -test.overlay > ripple.app -test.overlay > ripple.basics -test.overlay > ripple.beast -test.overlay > ripple.overlay -test.overlay > ripple.peerfinder -test.overlay > ripple.protocol -test.overlay > ripple.shamap +test.nodestore > xrpl.basics +test.nodestore > xrpld.core +test.nodestore > xrpld.nodestore +test.nodestore > xrpld.unity test.overlay > test.jtx test.overlay > test.unit_test -test.peerfinder > ripple.basics -test.peerfinder > ripple.beast -test.peerfinder > ripple.core -test.peerfinder > ripple.peerfinder -test.peerfinder > ripple.protocol +test.overlay > xrpl.basics +test.overlay > xrpld.app +test.overlay > xrpld.overlay +test.overlay > xrpld.peerfinder +test.overlay > xrpld.shamap +test.overlay > xrpl.protocol test.peerfinder > test.beast test.peerfinder > test.unit_test -test.protocol > ripple.basics -test.protocol > ripple.beast -test.protocol > ripple.crypto -test.protocol > ripple.json -test.protocol > ripple.protocol +test.peerfinder > xrpl.basics +test.peerfinder > xrpld.core +test.peerfinder > xrpld.peerfinder +test.peerfinder > xrpl.protocol test.protocol > test.toplevel -test.resource > ripple.basics -test.resource > ripple.beast -test.resource > ripple.resource +test.protocol > xrpl.basics +test.protocol > xrpl.json +test.protocol > xrpl.protocol test.resource > test.unit_test -test.rpc > ripple.app -test.rpc > ripple.basics -test.rpc > ripple.beast -test.rpc > ripple.core -test.rpc > ripple.json -test.rpc > ripple.net -test.rpc > ripple.nodestore -test.rpc > ripple.overlay -test.rpc > ripple.protocol -test.rpc > ripple.resource -test.rpc > ripple.rpc +test.resource > xrpl.basics +test.resource > xrpl.resource test.rpc > test.jtx -test.rpc > test.nodestore test.rpc > test.toplevel -test.server > ripple.app -test.server > ripple.basics -test.server > ripple.beast -test.server > ripple.core -test.server > ripple.json -test.server > ripple.rpc -test.server > ripple.server +test.rpc > xrpl.basics +test.rpc > xrpld.app +test.rpc > xrpld.core +test.rpc > xrpld.net +test.rpc > xrpld.overlay +test.rpc > xrpld.rpc +test.rpc > xrpl.json +test.rpc > xrpl.protocol +test.rpc > xrpl.resource test.server > test.jtx test.server > test.toplevel test.server > test.unit_test -test.shamap > ripple.basics -test.shamap > ripple.beast -test.shamap > ripple.nodestore -test.shamap > ripple.protocol -test.shamap > ripple.shamap +test.server > xrpl.basics +test.server > xrpld.app +test.server > xrpld.core +test.server > xrpld.rpc +test.server > xrpl.json +test.server > xrpl.server test.shamap > test.unit_test -test.toplevel > ripple.json +test.shamap > xrpl.basics +test.shamap > xrpld.nodestore +test.shamap > xrpld.shamap +test.shamap > xrpl.protocol test.toplevel > test.csf -test.unit_test > ripple.basics -test.unit_test > ripple.beast +test.toplevel > xrpl.json +test.unit_test > xrpl.basics +xrpl.protocol > xrpl.basics +xrpl.protocol > xrpl.json +xrpl.resource > xrpl.basics +xrpl.resource > xrpl.json +xrpl.resource > xrpl.protocol +xrpl.server > xrpl.basics +xrpl.server > xrpl.json +xrpl.server > xrpl.protocol +xrpld.app > test.unit_test +xrpld.app > xrpl.basics +xrpld.app > xrpld.conditions +xrpld.app > xrpld.consensus +xrpld.app > xrpld.nodestore +xrpld.app > xrpld.perflog +xrpld.app > xrpl.json +xrpld.app > xrpl.protocol +xrpld.app > xrpl.resource +xrpld.conditions > xrpl.basics +xrpld.conditions > xrpl.protocol +xrpld.consensus > xrpl.basics +xrpld.consensus > xrpl.json +xrpld.consensus > xrpl.protocol +xrpld.core > xrpl.basics +xrpld.core > xrpl.json +xrpld.core > xrpl.protocol +xrpld.ledger > xrpl.basics +xrpld.ledger > xrpld.core +xrpld.ledger > xrpl.json +xrpld.ledger > xrpl.protocol +xrpld.net > xrpl.basics +xrpld.net > xrpl.json +xrpld.net > xrpl.protocol +xrpld.net > xrpl.resource +xrpld.nodestore > xrpl.basics +xrpld.nodestore > xrpld.core +xrpld.nodestore > xrpld.unity +xrpld.nodestore > xrpl.json +xrpld.nodestore > xrpl.protocol +xrpld.overlay > xrpl.basics +xrpld.overlay > xrpld.core +xrpld.overlay > xrpld.peerfinder +xrpld.overlay > xrpld.perflog +xrpld.overlay > xrpl.json +xrpld.overlay > xrpl.protocol +xrpld.overlay > xrpl.resource +xrpld.overlay > xrpl.server +xrpld.peerfinder > xrpl.basics +xrpld.peerfinder > xrpld.core +xrpld.peerfinder > xrpl.protocol +xrpld.perflog > xrpl.basics +xrpld.perflog > xrpl.json +xrpld.perflog > xrpl.protocol +xrpld.rpc > xrpl.basics +xrpld.rpc > xrpld.core +xrpld.rpc > xrpld.ledger +xrpld.rpc > xrpld.nodestore +xrpld.rpc > xrpl.json +xrpld.rpc > xrpl.protocol +xrpld.rpc > xrpl.resource +xrpld.rpc > xrpl.server +xrpld.shamap > xrpl.basics +xrpld.shamap > xrpld.nodestore +xrpld.shamap > xrpl.protocol diff --git a/CMakeLists.txt b/CMakeLists.txt index dcb493e5293..0c34f89397d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,9 @@ endif() # Fix "unrecognized escape" issues when passing CMAKE_MODULE_PATH on Windows. file(TO_CMAKE_PATH "${CMAKE_MODULE_PATH}" CMAKE_MODULE_PATH) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Builds/CMake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -project(rippled) +project(xrpl) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -81,7 +81,6 @@ find_package(lz4 REQUIRED) find_package(LibArchive REQUIRED) find_package(SOCI REQUIRED) find_package(SQLite3 REQUIRED) -find_package(Snappy REQUIRED) option(rocksdb "Enable RocksDB" ON) if(rocksdb) @@ -116,22 +115,11 @@ else() endif() target_link_libraries(ripple_libs INTERFACE ${nudb}) -if(reporting) - find_package(cassandra-cpp-driver REQUIRED) - find_package(PostgreSQL REQUIRED) - target_link_libraries(ripple_libs INTERFACE - cassandra-cpp-driver::cassandra-cpp-driver - PostgreSQL::PostgreSQL - ) -endif() - if(coverage) include(RippledCov) endif() -### - +set(PROJECT_EXPORT_SET RippleExports) include(RippledCore) include(RippledInstall) -include(RippledMultiConfig) include(RippledValidatorKeys) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75616eb6b4e..ceca1eaa6fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,27 +73,235 @@ The source must be formatted according to the style guide below. Header includes must be [levelized](./Builds/levelization). +Changes should be usually squashed down into a single commit. +Some larger or more complicated change sets make more sense, +and are easier to review if organized into multiple logical commits. +Either way, all commits should fit the following criteria: +* Changes should be presented in a single commit or a logical + sequence of commits. + Specifically, chronological commits that simply + reflect the history of how the author implemented + the change, "warts and all", are not useful to + reviewers. +* Every commit should have a [good message](#good-commit-messages). + to explain a specific aspects of the change. +* Every commit should be signed. +* Every commit should be well-formed (builds successfully, + unit tests passing), as this helps to resolve merge + conflicts, and makes it easier to use `git bisect` + to find bugs. + +### Good commit messages + +Refer to +["How to Write a Git Commit Message"](https://cbea.ms/git-commit/) +for general rules on writing a good commit message. + +In addition to those guidelines, please add one of the following +prefixes to the subject line if appropriate. +* `fix:` - The primary purpose is to fix an existing bug. +* `perf:` - The primary purpose is performance improvements. +* `refactor:` - The changes refactor code without affecting + functionality. +* `test:` - The changes _only_ affect unit tests. +* `docs:` - The changes _only_ affect documentation. This can + include code comments in addition to `.md` files like this one. +* `build:` - The changes _only_ affect the build process, + including CMake and/or Conan settings. +* `chore:` - Other tasks that don't affect the binary, but don't fit + any of the other cases. e.g. formatting, git settings, updating + Github Actions jobs. + +Whenever possible, when updating commits after the PR is open, please +add the PR number to the end of the subject line. e.g. `test: Add +unit tests for Feature X (#1234)`. ## Pull requests In general, pull requests use `develop` as the base branch. - (Hotfixes are an exception.) -Changes to pull requests must be added as new commits. -Once code reviewers have started looking at your code, please avoid -force-pushing a branch in a pull request. +If your changes are not quite ready, but you want to make it easily available +for preliminary examination or review, you can create a "Draft" pull request. +While a pull request is marked as a "Draft", you can rebase or reorganize the +commits in the pull request as desired. + +Github pull requests are created as "Ready" by default, or you can mark +a "Draft" pull request as "Ready". +Once a pull request is marked as "Ready", +any changes must be added as new commits. Do not +force-push to a branch in a pull request under review. +(This includes rebasing your branch onto the updated base branch. +Use a merge operation, instead or hit the "Update branch" button +at the bottom of the Github PR page.) This preserves the ability for reviewers to filter changes since their last review. -A pull request must obtain **approvals from at least two reviewers** before it -can be considered for merge by a Maintainer. +A pull request must obtain **approvals from at least two reviewers** +before it can be considered for merge by a Maintainer. Maintainers retain discretion to require more approvals if they feel the credibility of the existing approvals is insufficient. Pull requests must be merged by [squash-and-merge][2] to preserve a linear history for the `develop` branch. +### When and how to merge pull requests + +#### "Passed" + +A pull request should only have the "Passed" label added when it +meets a few criteria: + +1. It must have two approving reviews [as described + above](#pull-requests). (Exception: PRs that are deemed "trivial" + only need one approval.) +2. All CI checks must be complete and passed. (One-off failures may + be acceptable if they are related to a known issue.) +3. The PR must have a [good commit message](#good-commit-messages). + * If the PR started with a good commit message, and it doesn't + need to be updated, the author can indicate that in a comment. + * Any contributor, preferably the author, can leave a comment + suggesting a commit message. + * If the author squashes and rebases the code in preparation for + merge, they should also ensure the commit message(s) are updated + as well. +4. The PR branch must be up to date with the base branch (usually + `develop`). This is usually accomplised by merging the base branch + into the feature branch, but if the other criteria are met, the + changes can be squashed and rebased on top of the base branch. +5. Finally, and most importantly, the author of the PR must + positively indicate that the PR is ready to merge. That can be + accomplished by adding the "Passed" label if their role allows, + or by leaving a comment to the effect that the PR is ready to + merge. + +Once the "Passed" label is added, a maintainer may merge the PR at +any time, so don't use it lightly. + +#### Instructions for maintainers + +The maintainer should double-check that the PR has met all the +necessary criteria, and can request additional information from the +owner, or additional reviews, and can always feel free to remove the +"Passed" label if appropriate. The maintainer has final say on +whether a PR gets merged, and are encouraged to communicate and +issues or concerns to other maintainers. + +##### Most pull requests: "Squash and merge" + +Most pull requests don't need special handling, and can simply be +merged using the "Squash and merge" button on the Github UI. Update +the suggested commit message if necessary. + +##### Slightly more complicated pull requests + +Some pull requests need to be pushed to `develop` as more than one +commit. There are multiple ways to accomplish this. If the author +describes a process, and it is reasonable, follow it. Otherwise, do +a fast forward only merge (`--ff-only`) on the command line and push. + +Either way, check that: +* The commits are based on the current tip of `develop`. +* The commits are clean: No merge commits (except when reverse + merging), no "[FOLD]" or "fixup!" messages. +* All commits are signed. If the commits are not signed by the author, use + `git commit --amend -S` to sign them yourself. +* At least one (but preferably all) of the commits has the PR number + in the commit message. + +**Never use the "Create a merge commit" or "Rebase and merge" + functions!** + +##### Releases, release candidates, and betas + +All releases, including release candidates and betas, are handled +differently from typical PRs. Most importantly, never use +the Github UI to merge a release. + +1. There are two possible conditions that the `develop` branch will + be in when preparing a release. + 1. Ready or almost ready to go: There may be one or two PRs that + need to be merged, but otherwise, the only change needed is to + update the version number in `BuildInfo.cpp`. In this case, + merge those PRs as appropriate, updating the second one, and + waiting for CI to finish in between. Then update + `BuildInfo.cpp`. + 2. Several pending PRs: In this case, do not use the Github UI, + because the delays waiting for CI in between each merge will be + unnecessarily onerous. Instead, create a working branch (e.g. + `develop-next`) based off of `develop`. Squash the changes + from each PR onto the branch, one commit each (unless + more are needed), being sure to sign each commit and update + the commit message to include the PR number. You may be able + to use a fast-forward merge for the first PR. The workflow may + look something like: +``` +git fetch upstream +git checkout upstream/develop +git checkout -b develop-next +# Use -S on the ff-only merge if prbranch1 isn't signed. +# Or do another branch first. +git merge --ff-only user1/prbranch1 +git merge --squash user2/prbranch2 +git commit -S +git merge --squash user3/prbranch3 +git commit -S +[...] +git push --set-upstream origin develop-next + +``` +2. Create the Pull Request with `release` as the base branch. If any + of the included PRs are still open, + [use closing keywords](https://docs.github.com/articles/closing-issues-using-keywords) + in the description to ensure they are closed when the code is + released. e.g. "Closes #1234" +3. Instead of the default template, reuse and update the message from + the previous release. Include the following verbiage somewhere in + the description: +``` +The base branch is release. All releases (including betas) go in +release. This PR will be merged with --ff-only (not squashed or +rebased, and not using the GitHub UI) to both release and develop. +``` +4. Sign-offs for the three platforms usually occur offline, but at + least one approval will be needed on the PR. +5. Once everything is ready to go, open a terminal, and do the + fast-forward merges manually. Do not push any branches until you + verify that all of them update correctly. +``` +git fetch upstream +git checkout -b upstream--develop -t upstream/develop || git checkout upstream--develop +git reset --hard upstream/develop +# develop-next must be signed already! +git merge --ff-only origin/develop-next +git checkout -b upstream--release -t upstream/release || git checkout upstream--release +git reset --hard upstream/release +git merge --ff-only origin/develop-next +# Only do these 3 steps if pushing a release. No betas or RCs +git checkout -b upstream--master -t upstream/master || git checkout upstream--master +git reset --hard upstream/master +git merge --ff-only origin/develop-next +# Check that all of the branches are updated +git log -1 --oneline +# The output should look like: +# 02ec8b7962 (HEAD -> upstream--master, origin/develop-next, upstream--release, upstream--develop, develop-next) Set version to 2.2.0-rc1 +# Note that all of the upstream--develop/release/master are on this commit. +# (Master will be missing for betas, etc.) +# Just to be safe, do a dry run first: +git push --dry-run upstream-push HEAD:develop +git push --dry-run upstream-push HEAD:release +# git push --dry-run upstream-push HEAD:master +# Now push +git push upstream-push HEAD:develop +git push upstream-push HEAD:release +# git push upstream-push HEAD:master +# Don't forget to tag the release, too. +git tag +git push upstream-push +``` +6. Finally +[create a new release on Github](https://github.com/XRPLF/rippled/releases). + # Style guide @@ -119,6 +327,16 @@ this: You can format individual files in place by running `clang-format -i ...` from any directory within this project. +There is a Continuous Integration job that runs clang-format on pull requests. If the code doesn't comply, a patch file that corrects auto-fixable formatting issues is generated. + +To download the patch file: + +1. Next to `clang-format / check (pull_request) Failing after #s` -> click **Details** to open the details page. +2. Left menu -> click **Summary** +3. Scroll down to near the bottom-right under `Artifacts` -> click **clang-format.patch** +4. Download the zip file and extract it to your local git repository. Run `git apply [patch-file-name]`. +5. Commit and push. + You can install a pre-commit hook to automatically run `clang-format` before every commit: ``` pip3 install pre-commit diff --git a/README.md b/README.md index 45dc2005ea2..cc002a2dd82 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The [XRP Ledger](https://xrpl.org/) is a decentralized cryptographic ledger powe ## rippled The server software that powers the XRP Ledger is called `rippled` and is available in this repository under the permissive [ISC open-source license](LICENSE.md). The `rippled` server software is written primarily in C++ and runs on a variety of platforms. The `rippled` server software can run in several modes depending on its [configuration](https://xrpl.org/rippled-server-modes.html). -If you are interested in running an **API Server** (including a **Full History Server**) or a **Reporting Mode** server, take a look at [Clio](https://github.com/XRPLF/clio). rippled Reporting Mode is expected to be replaced by Clio. +If you are interested in running an **API Server** (including a **Full History Server**), take a look at [Clio](https://github.com/XRPLF/clio). (rippled Reporting Mode has been replaced by Clio.) ### Build from Source diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 37f884b578f..c6e8266e348 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,79 @@ This document contains the release notes for `rippled`, the reference server imp Have new ideas? Need help with setting up your node? [Please open an issue here](https://github.com/xrplf/rippled/issues/new/choose). +# Version 2.3.0 + +Version 2.3.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release includes 8 new amendments, including Multi-Purpose Tokens, Credentials, Clawback support for AMMs, and the ability to make offers as part of minting NFTs. Additionally, this release includes important fixes for stability, so server operators are encouraged to upgrade as soon as possible. + + +## Action Required + +If you run an XRP Ledger server, upgrade to version 2.3.0 as soon as possible to ensure service continuity. + +Additionally, new amendments are now open for voting according to the XRP Ledger's [amendment process](https://xrpl.org/amendments.html), which enables protocol changes following two weeks of >80% support from trusted validators. The exact time that protocol changes take effect depends on the voting decisions of the decentralized network. + +## Full Changelog + +### Amendments + +The following amendments are open for voting with this release: + +- **XLS-70 Credentials** - Users can issue Credentials on the ledger and use Credentials to pre-approve incoming payments when using Deposit Authorization instead of individually approving payers. ([#5103](https://github.com/XRPLF/rippled/pull/5103)) + - related fix: #5189 (https://github.com/XRPLF/rippled/pull/5189) +- **XLS-33 Multi-Purpose Tokens** - A new type of fungible token optimized for institutional DeFi including stablecoins. ([#5143](https://github.com/XRPLF/rippled/pull/5143)) +- **XLS-37 AMM Clawback** - Allows clawback-enabled tokens to be used in AMMs, with appropriate guardrails. ([#5142](https://github.com/XRPLF/rippled/pull/5142)) +- **XLS-52 NFTokenMintOffer** - Allows creating an NFT sell offer as part of minting a new NFT. ([#4845](https://github.com/XRPLF/rippled/pull/4845)) +- **fixAMMv1_2** - Fixes two bugs in Automated Market Maker (AMM) transaction processing. ([#5176](https://github.com/XRPLF/rippled/pull/5176)) +- **fixNFTokenPageLinks** - Fixes a bug that can cause NFT directories to have missing links, and introduces a transaction to repair corrupted ledger state. ([#4945](https://github.com/XRPLF/rippled/pull/4945)) +- **fixEnforceNFTokenTrustline** - Fixes two bugs in the interaction between NFT offers and trust lines. ([#4946](https://github.com/XRPLF/rippled/pull/4946)) +- **fixInnerObjTemplate2** - Standardizes the way inner objects are enforced across all transaction and ledger data. ([#5047](https://github.com/XRPLF/rippled/pull/5047)) + +The following amendment is partially implemented but not open for voting: + +- **InvariantsV1_1** - Adds new invariants to ensure transactions process as intended, starting with an invariant to ensure that ledger entries owned by an account are deleted when the account is deleted. ([#4663](https://github.com/XRPLF/rippled/pull/4663)) + +### New Features + +- Allow configuration of SQLite database page size. ([#5135](https://github.com/XRPLF/rippled/pull/5135), [#5140](https://github.com/XRPLF/rippled/pull/5140)) +- In the `libxrpl` C++ library, provide a list of known amendments. ([#5026](https://github.com/XRPLF/rippled/pull/5026)) + +### Deprecations + +- History Shards are removed. ([#5066](https://github.com/XRPLF/rippled/pull/5066)) +- Reporting mode is removed. ([#5092](https://github.com/XRPLF/rippled/pull/5092)) + +For users wanting to store more ledger history, it is recommended to run a Clio server instead. + +### Bug fixes + +- Fix a crash in debug builds when amm_info request contains an invalid AMM account ID. ([#5188](https://github.com/XRPLF/rippled/pull/5188)) +- Fix a crash caused by a race condition in peer-to-peer code. ([#5071](https://github.com/XRPLF/rippled/pull/5071)) +- Fix a crash in certain situations +- Fix several bugs in the book_changes API method. ([#5096](https://github.com/XRPLF/rippled/pull/5096)) +- Fix bug triggered by providing an invalid marker to the account_nfts API method. ([#5045](https://github.com/XRPLF/rippled/pull/5045)) +- Accept lower-case hexadecimal in compact transaction identifier (CTID) parameters in API methods. ([#5049](https://github.com/XRPLF/rippled/pull/5049)) +- Disallow filtering by types that an account can't own in the account_objects API method. ([#5056](https://github.com/XRPLF/rippled/pull/5056)) +- Fix error code returned by the feature API method when providing an invalid parameter. ([#5063](https://github.com/XRPLF/rippled/pull/5063)) +- (API v3) Fix error code returned by amm_info when providing invalid parameters. ([#4924](https://github.com/XRPLF/rippled/pull/4924)) + +### Other Improvements + +- Adds a new default hub, hubs.xrpkuwait.com, to the config file and bootstrapping code. ([#5169](https://github.com/XRPLF/rippled/pull/5169)) +- Improve error message when commandline interface fails with `rpcInternal` because there was no response from the server. ([#4959](https://github.com/XRPLF/rippled/pull/4959)) +- Add tools for debugging specific transactions via replay. ([#5027](https://github.com/XRPLF/rippled/pull/5027), [#5087](https://github.com/XRPLF/rippled/pull/5087)) +- Major reorganization of source code files. ([#4997](https://github.com/XRPLF/rippled/pull/4997)) +- Add new unit tests. ([#4886](https://github.com/XRPLF/rippled/pull/4886)) +- Various improvements to build tools and contributor documentation. ([#5001](https://github.com/XRPLF/rippled/pull/5001), [#5028](https://github.com/XRPLF/rippled/pull/5028), [#5052](https://github.com/XRPLF/rippled/pull/5052), [#5091](https://github.com/XRPLF/rippled/pull/5091), [#5084](https://github.com/XRPLF/rippled/pull/5084), [#5120](https://github.com/XRPLF/rippled/pull/5120), [#5010](https://github.com/XRPLF/rippled/pull/5010). [#5055](https://github.com/XRPLF/rippled/pull/5055), [#5067](https://github.com/XRPLF/rippled/pull/5067), [#5061](https://github.com/XRPLF/rippled/pull/5061), [#5072](https://github.com/XRPLF/rippled/pull/5072), [#5044](https://github.com/XRPLF/rippled/pull/5044) ) +- Various code cleanup and refactoring. ([#4509](https://github.com/XRPLF/rippled/pull/4509), [#4521](https://github.com/XRPLF/rippled/pull/4521), [#4856](https://github.com/XRPLF/rippled/pull/4856), [#5190](https://github.com/XRPLF/rippled/pull/5190), [#5081](https://github.com/XRPLF/rippled/pull/5081), [#5053](https://github.com/XRPLF/rippled/pull/5053), [#5058](https://github.com/XRPLF/rippled/pull/5058), [#5122](https://github.com/XRPLF/rippled/pull/5122), [#5059](https://github.com/XRPLF/rippled/pull/5059), [#5041](https://github.com/XRPLF/rippled/pull/5041)) + + +Bug Bounties and Responsible Disclosures: + +We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find. + +To report a bug, please send a detailed report to: + + # Version 2.2.3 Version 2.2.3 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release fixes a problem that can cause full-history servers to run out of space in their SQLite databases, depending on configuration. There are no new amendments in this release. @@ -557,7 +630,7 @@ If you operate an XRP Ledger server, upgrade to version 2.0.0 by January 22, 202 - Switched to Unity builds to speed up Windows CI. [#4780](https://github.com/XRPLF/rippled/pull/4780) -- Clarified what makes concensus healthy in `FeeEscalation.md`. [#4729](https://github.com/XRPLF/rippled/pull/4729) +- Clarified what makes consensus healthy in `FeeEscalation.md`. [#4729](https://github.com/XRPLF/rippled/pull/4729) - Removed a dependency on the header for unit tests. [#4788](https://github.com/XRPLF/rippled/pull/4788) diff --git a/bin/ci/README.md b/bin/ci/README.md deleted file mode 100644 index 36d4fc1d310..00000000000 --- a/bin/ci/README.md +++ /dev/null @@ -1,24 +0,0 @@ -In this directory are two scripts, `build.sh` and `test.sh` used for building -and testing rippled. - -(For now, they assume Bash and Linux. Once I get Windows containers for -testing, I'll try them there, but if Bash is not available, then they will -soon be joined by PowerShell scripts `build.ps` and `test.ps`.) - -We don't want these scripts to require arcane invocations that can only be -pieced together from within a CI configuration. We want something that humans -can easily invoke, read, and understand, for when we eventually have to test -and debug them interactively. That means: - -(1) They should work with no arguments. -(2) They should document their arguments. -(3) They should expand short arguments into long arguments. - -While we want to provide options for common use cases, we don't need to offer -the kitchen sink. We can rightfully expect users with esoteric, complicated -needs to write their own scripts. - -To make argument-handling easy for us, the implementers, we can just take all -arguments from environment variables. They have the nice advantage that every -command-line uses named arguments. For the benefit of us and our users, we -document those variables at the top of each script. diff --git a/bin/ci/build.sh b/bin/ci/build.sh deleted file mode 100755 index fa7a0c96829..00000000000 --- a/bin/ci/build.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -set -o xtrace -set -o errexit - -# The build system. Either 'Unix Makefiles' or 'Ninja'. -GENERATOR=${GENERATOR:-Unix Makefiles} -# The compiler. Either 'gcc' or 'clang'. -COMPILER=${COMPILER:-gcc} -# The build type. Either 'Debug' or 'Release'. -BUILD_TYPE=${BUILD_TYPE:-Debug} -# Additional arguments to CMake. -# We use the `-` substitution here instead of `:-` so that callers can erase -# the default by setting `$CMAKE_ARGS` to the empty string. -CMAKE_ARGS=${CMAKE_ARGS-'-Dwerr=ON'} - -# https://gitlab.kitware.com/cmake/cmake/issues/18865 -CMAKE_ARGS="-DBoost_NO_BOOST_CMAKE=ON ${CMAKE_ARGS}" - -if [[ ${COMPILER} == 'gcc' ]]; then - export CC='gcc' - export CXX='g++' -elif [[ ${COMPILER} == 'clang' ]]; then - export CC='clang' - export CXX='clang++' -fi - -mkdir build -cd build -cmake -G "${GENERATOR}" -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${CMAKE_ARGS} .. -cmake --build . -- -j $(nproc) diff --git a/bin/ci/test.sh b/bin/ci/test.sh deleted file mode 100755 index 11615d732b7..00000000000 --- a/bin/ci/test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -o xtrace -set -o errexit - -# Set to 'true' to run the known "manual" tests in rippled. -MANUAL_TESTS=${MANUAL_TESTS:-false} -# The maximum number of concurrent tests. -CONCURRENT_TESTS=${CONCURRENT_TESTS:-$(nproc)} -# The path to rippled. -RIPPLED=${RIPPLED:-build/rippled} -# Additional arguments to rippled. -RIPPLED_ARGS=${RIPPLED_ARGS:-} - -function join_by { local IFS="$1"; shift; echo "$*"; } - -declare -a manual_tests=( - 'beast.chrono.abstract_clock' - 'beast.unit_test.print' - 'ripple.NodeStore.Timing' - 'ripple.app.Flow_manual' - 'ripple.app.NoRippleCheckLimits' - 'ripple.app.PayStrandAllPairs' - 'ripple.consensus.ByzantineFailureSim' - 'ripple.consensus.DistributedValidators' - 'ripple.consensus.ScaleFreeSim' - 'ripple.tx.CrossingLimits' - 'ripple.tx.FindOversizeCross' - 'ripple.tx.Offer_manual' - 'ripple.tx.OversizeMeta' - 'ripple.tx.PlumpBook' -) - -if [[ ${MANUAL_TESTS} == 'true' ]]; then - RIPPLED_ARGS+=" --unittest=$(join_by , "${manual_tests[@]}")" -else - RIPPLED_ARGS+=" --unittest --quiet --unittest-log" -fi -RIPPLED_ARGS+=" --unittest-jobs ${CONCURRENT_TESTS}" - -${RIPPLED} ${RIPPLED_ARGS} diff --git a/bin/ci/ubuntu/build-and-test.sh b/bin/ci/ubuntu/build-and-test.sh deleted file mode 100755 index 2c1734863fb..00000000000 --- a/bin/ci/ubuntu/build-and-test.sh +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env bash -set -ex - -function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } - -__dirname=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -echo "using CC: ${CC}" -"${CC}" --version -export CC - -COMPNAME=$(basename $CC) -echo "using CXX: ${CXX:-notset}" -if [[ $CXX ]]; then - "${CXX}" --version - export CXX -fi -: ${BUILD_TYPE:=Debug} -echo "BUILD TYPE: ${BUILD_TYPE}" - -: ${TARGET:=install} -echo "BUILD TARGET: ${TARGET}" - -JOBS=${NUM_PROCESSORS:-2} -if [[ ${TRAVIS:-false} != "true" ]]; then - JOBS=$((JOBS+1)) -fi - -if [[ ! -z "${CMAKE_EXE:-}" ]] ; then - export PATH="$(dirname ${CMAKE_EXE}):$PATH" -fi - -if [ -x /usr/bin/time ] ; then - : ${TIME:="Duration: %E"} - export TIME - time=/usr/bin/time -else - time= -fi - -echo "Building rippled" -: ${CMAKE_EXTRA_ARGS:=""} -if [[ ${NINJA_BUILD:-} == true ]]; then - CMAKE_EXTRA_ARGS+=" -G Ninja" -fi - -coverage=false -if [[ "${TARGET}" == "coverage" ]] ; then - echo "coverage option detected." - coverage=true -fi - -cmake --version -CMAKE_VER=$(cmake --version | cut -d " " -f 3 | head -1) - -# -# allow explicit setting of the name of the build -# dir, otherwise default to the compiler.build_type -# -: "${BUILD_DIR:=${COMPNAME}.${BUILD_TYPE}}" -BUILDARGS="--target ${TARGET}" -BUILDTOOLARGS="" -if version_ge $CMAKE_VER "3.12.0" ; then - BUILDARGS+=" --parallel" -fi - -if [[ ${NINJA_BUILD:-} == false ]]; then - if version_ge $CMAKE_VER "3.12.0" ; then - BUILDARGS+=" ${JOBS}" - else - BUILDTOOLARGS+=" -j ${JOBS}" - fi -fi - -if [[ ${VERBOSE_BUILD:-} == true ]]; then - CMAKE_EXTRA_ARGS+=" -DCMAKE_VERBOSE_MAKEFILE=ON" - if version_ge $CMAKE_VER "3.14.0" ; then - BUILDARGS+=" --verbose" - else - if [[ ${NINJA_BUILD:-} == false ]]; then - BUILDTOOLARGS+=" verbose=1" - else - BUILDTOOLARGS+=" -v" - fi - fi -fi - -if [[ ${USE_CCACHE:-} == true ]]; then - echo "using ccache with basedir [${CCACHE_BASEDIR:-}]" - CMAKE_EXTRA_ARGS+=" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" -fi -if [ -d "build/${BUILD_DIR}" ]; then - rm -rf "build/${BUILD_DIR}" -fi - -mkdir -p "build/${BUILD_DIR}" -pushd "build/${BUILD_DIR}" - -# cleanup possible artifacts -rm -fv CMakeFiles/CMakeOutput.log CMakeFiles/CMakeError.log -# Clean up NIH directories which should be git repos, but aren't -for nih_path in ${NIH_CACHE_ROOT}/*/*/*/src ${NIH_CACHE_ROOT}/*/*/src -do - for dir in lz4 snappy rocksdb - do - if [ -e ${nih_path}/${dir} -a \! -e ${nih_path}/${dir}/.git ] - then - ls -la ${nih_path}/${dir}* - rm -rfv ${nih_path}/${dir}* - fi - done -done - -# generate -${time} cmake ../.. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${CMAKE_EXTRA_ARGS} -# Display the cmake output, to help with debugging if something fails -for file in CMakeOutput.log CMakeError.log -do - if [ -f CMakeFiles/${file} ] - then - ls -l CMakeFiles/${file} - cat CMakeFiles/${file} - fi -done -# build -export DESTDIR=$(pwd)/_INSTALLED_ - -${time} eval cmake --build . ${BUILDARGS} -- ${BUILDTOOLARGS} - -if [[ ${TARGET} == "docs" ]]; then - ## mimic the standard test output for docs build - ## to make controlling processes like jenkins happy - if [ -f docs/html/index.html ]; then - echo "1 case, 1 test total, 0 failures" - else - echo "1 case, 1 test total, 1 failures" - fi - exit -fi -popd - -if [[ "${TARGET}" == "validator-keys" ]] ; then - export APP_PATH="$PWD/build/${BUILD_DIR}/validator-keys/validator-keys" -else - export APP_PATH="$PWD/build/${BUILD_DIR}/rippled" -fi -echo "using APP_PATH: ${APP_PATH}" - -# See what we've actually built -ldd ${APP_PATH} - -: ${APP_ARGS:=} - -if [[ "${TARGET}" == "validator-keys" ]] ; then - APP_ARGS="--unittest" -else - function join_by { local IFS="$1"; shift; echo "$*"; } - - # This is a list of manual tests - # in rippled that we want to run - # ORDER matters here...sorted in approximately - # descending execution time (longest running tests at top) - declare -a manual_tests=( - 'ripple.ripple_data.reduce_relay_simulate' - 'ripple.tx.Offer_manual' - 'ripple.tx.CrossingLimits' - 'ripple.tx.PlumpBook' - 'ripple.app.Flow_manual' - 'ripple.tx.OversizeMeta' - 'ripple.consensus.DistributedValidators' - 'ripple.app.NoRippleCheckLimits' - 'ripple.ripple_data.compression' - 'ripple.NodeStore.Timing' - 'ripple.consensus.ByzantineFailureSim' - 'beast.chrono.abstract_clock' - 'beast.unit_test.print' - ) - if [[ ${TRAVIS:-false} != "true" ]]; then - # these two tests cause travis CI to run out of memory. - # TODO: investigate possible workarounds. - manual_tests=( - 'ripple.consensus.ScaleFreeSim' - 'ripple.tx.FindOversizeCross' - "${manual_tests[@]}" - ) - fi - - if [[ ${MANUAL_TESTS:-} == true ]]; then - APP_ARGS+=" --unittest=$(join_by , "${manual_tests[@]}")" - else - APP_ARGS+=" --unittest --quiet --unittest-log" - fi - if [[ ${coverage} == false && ${PARALLEL_TESTS:-} == true ]]; then - APP_ARGS+=" --unittest-jobs ${JOBS}" - fi - - if [[ ${IPV6_TESTS:-} == true ]]; then - APP_ARGS+=" --unittest-ipv6" - fi -fi - -if [[ ${coverage} == true && $CC =~ ^gcc ]]; then - # Push the results (lcov.info) to codecov - codecov -X gcov # don't even try and look for .gcov files ;) - find . -name "*.gcda" | xargs rm -f -fi - -if [[ ${SKIP_TESTS:-} == true ]]; then - echo "skipping tests." - exit -fi - -ulimit -a -corepat=$(cat /proc/sys/kernel/core_pattern) -if [[ ${corepat} =~ ^[:space:]*\| ]] ; then - echo "WARNING: core pattern is piping - can't search for core files" - look_core=false -else - look_core=true - coredir=$(dirname ${corepat}) -fi -if [[ ${look_core} == true ]]; then - before=$(ls -A1 ${coredir}) -fi - -set +e -echo "Running tests for ${APP_PATH}" -if [[ ${MANUAL_TESTS:-} == true && ${PARALLEL_TESTS:-} != true ]]; then - for t in "${manual_tests[@]}" ; do - ${APP_PATH} --unittest=${t} - TEST_STAT=$? - if [[ $TEST_STAT -ne 0 ]] ; then - break - fi - done -else - ${APP_PATH} ${APP_ARGS} - TEST_STAT=$? -fi -set -e - -if [[ ${look_core} == true ]]; then - after=$(ls -A1 ${coredir}) - oIFS="${IFS}" - IFS=$'\n\r' - found_core=false - for l in $(diff -w --suppress-common-lines <(echo "$before") <(echo "$after")) ; do - if [[ "$l" =~ ^[[:space:]]*\>[[:space:]]*(.+)$ ]] ; then - corefile="${BASH_REMATCH[1]}" - echo "FOUND core dump file at '${coredir}/${corefile}'" - gdb_output=$(/bin/mktemp /tmp/gdb_output_XXXXXXXXXX.txt) - found_core=true - gdb \ - -ex "set height 0" \ - -ex "set logging file ${gdb_output}" \ - -ex "set logging on" \ - -ex "print 'ripple::BuildInfo::versionString'" \ - -ex "thread apply all backtrace full" \ - -ex "info inferiors" \ - -ex quit \ - "$APP_PATH" \ - "${coredir}/${corefile}" &> /dev/null - - echo -e "CORE INFO: \n\n $(cat ${gdb_output}) \n\n)" - fi - done - IFS="${oIFS}" -fi - -if [[ ${found_core} == true ]]; then - exit -1 -else - exit $TEST_STAT -fi - diff --git a/bin/ci/ubuntu/build-in-docker.sh b/bin/ci/ubuntu/build-in-docker.sh deleted file mode 100755 index feeabb1189a..00000000000 --- a/bin/ci/ubuntu/build-in-docker.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -# run our build script in a docker container -# using travis-ci hosts -set -eux - -function join_by { local IFS="$1"; shift; echo "$*"; } - -set +x -echo "VERBOSE_BUILD=true" > /tmp/co.env -matchers=( - 'TRAVIS.*' 'CI' 'CC' 'CXX' - 'BUILD_TYPE' 'TARGET' 'MAX_TIME' - 'CODECOV.+' 'CMAKE.*' '.+_TESTS' - '.+_OPTIONS' 'NINJA.*' 'NUM_.+' - 'NIH_.+' 'BOOST.*' '.*CCACHE.*') - -matchstring=$(join_by '|' "${matchers[@]}") -echo "MATCHSTRING IS:: $matchstring" -env | grep -E "^(${matchstring})=" >> /tmp/co.env -set -x -# need to eliminate TRAVIS_CMD...don't want to pass it to the container -cat /tmp/co.env | grep -v TRAVIS_CMD > /tmp/co.env.2 -mv /tmp/co.env.2 /tmp/co.env -cat /tmp/co.env -mkdir -p -m 0777 ${TRAVIS_BUILD_DIR}/cores -echo "${TRAVIS_BUILD_DIR}/cores/%e.%p" | sudo tee /proc/sys/kernel/core_pattern -docker run \ - -t --env-file /tmp/co.env \ - -v ${TRAVIS_HOME}:${TRAVIS_HOME} \ - -w ${TRAVIS_BUILD_DIR} \ - --cap-add SYS_PTRACE \ - --ulimit "core=-1" \ - $DOCKER_IMAGE \ - /bin/bash -c 'if [[ $CC =~ ([[:alpha:]]+)-([[:digit:].]+) ]] ; then sudo update-alternatives --set ${BASH_REMATCH[1]} /usr/bin/$CC; fi; bin/ci/ubuntu/build-and-test.sh' - - diff --git a/bin/ci/ubuntu/travis-cache-start.sh b/bin/ci/ubuntu/travis-cache-start.sh deleted file mode 100755 index 6811acb9043..00000000000 --- a/bin/ci/ubuntu/travis-cache-start.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# some cached files create churn, so save them here for -# later restoration before packing the cache -set -eux -clean_cache="travis_clean_cache" -if [[ ! ( "${TRAVIS_JOB_NAME}" =~ "windows" || \ - "${TRAVIS_JOB_NAME}" =~ "prereq-keep" ) ]] && \ - ( [[ "${TRAVIS_COMMIT_MESSAGE}" =~ "${clean_cache}" ]] || \ - ( [[ -v TRAVIS_PULL_REQUEST_SHA && \ - "${TRAVIS_PULL_REQUEST_SHA}" != "" ]] && \ - git log -1 "${TRAVIS_PULL_REQUEST_SHA}" | grep -cq "${clean_cache}" - - ) - ) -then - find ${TRAVIS_HOME}/_cache -maxdepth 2 -type d - rm -rf ${TRAVIS_HOME}/_cache - mkdir -p ${TRAVIS_HOME}/_cache -fi - -pushd ${TRAVIS_HOME} -if [ -f cache_ignore.tar ] ; then - rm -f cache_ignore.tar -fi - -if [ -d _cache/nih_c ] ; then - find _cache/nih_c -name "build.ninja" | tar rf cache_ignore.tar --files-from - - find _cache/nih_c -name ".ninja_deps" | tar rf cache_ignore.tar --files-from - - find _cache/nih_c -name ".ninja_log" | tar rf cache_ignore.tar --files-from - - find _cache/nih_c -name "*.log" | tar rf cache_ignore.tar --files-from - - find _cache/nih_c -name "*.tlog" | tar rf cache_ignore.tar --files-from - - # show .a files in the cache, for sanity checking - find _cache/nih_c -name "*.a" -ls -fi - -if [ -d _cache/ccache ] ; then - find _cache/ccache -name "stats" | tar rf cache_ignore.tar --files-from - -fi - -if [ -f cache_ignore.tar ] ; then - tar -tf cache_ignore.tar -fi -popd - - diff --git a/bin/physical.sh b/bin/physical.sh new file mode 100755 index 00000000000..c2c5aad68db --- /dev/null +++ b/bin/physical.sh @@ -0,0 +1,218 @@ +#!/bin/bash + +set -o errexit + +marker_base=985c80fbc6131f3a8cedd0da7e8af98dfceb13c7 +marker_commit=${1:-${marker_base}} + +if [ $(git merge-base ${marker_commit} ${marker_base}) != ${marker_base} ]; then + echo "first marker commit not an ancestor: ${marker_commit}" + exit 1 +fi + +if [ $(git merge-base ${marker_commit} HEAD) != $(git rev-parse --verify ${marker_commit}) ]; then + echo "given marker commit not an ancestor: ${marker_commit}" + exit 1 +fi + +if [ -e Builds/CMake ]; then + echo move CMake + git mv Builds/CMake cmake + git add --update . + git commit -m 'Move CMake directory' --author 'Pretty Printer ' +fi + +if [ -e src/ripple ]; then + + echo move protocol buffers + mkdir -p include/xrpl + if [ -e src/ripple/proto ]; then + git mv src/ripple/proto include/xrpl + fi + + extract_list() { + git show ${marker_commit}:Builds/CMake/RippledCore.cmake | \ + awk "/END ${1}/ { p = 0 } p && /src\/ripple/; /BEGIN ${1}/ { p = 1 }" | \ + sed -e 's#src/ripple/##' -e 's#[^a-z]\+$##' + } + + move_files() { + oldroot="$1"; shift + newroot="$1"; shift + detail="$1"; shift + files=("$@") + for file in ${files[@]}; do + if [ ! -e ${oldroot}/${file} ]; then + continue + fi + dir=$(dirname ${file}) + if [ $(basename ${dir}) == 'details' ]; then + dir=$(dirname ${dir}) + fi + if [ $(basename ${dir}) == 'impl' ]; then + dir="$(dirname ${dir})/${detail}" + fi + mkdir -p ${newroot}/${dir} + git mv ${oldroot}/${file} ${newroot}/${dir} + done + } + + echo move libxrpl headers + files=$(extract_list 'LIBXRPL HEADERS') + files+=( + basics/SlabAllocator.h + + beast/asio/io_latency_probe.h + beast/container/aged_container.h + beast/container/aged_container_utility.h + beast/container/aged_map.h + beast/container/aged_multimap.h + beast/container/aged_multiset.h + beast/container/aged_set.h + beast/container/aged_unordered_map.h + beast/container/aged_unordered_multimap.h + beast/container/aged_unordered_multiset.h + beast/container/aged_unordered_set.h + beast/container/detail/aged_associative_container.h + beast/container/detail/aged_container_iterator.h + beast/container/detail/aged_ordered_container.h + beast/container/detail/aged_unordered_container.h + beast/container/detail/empty_base_optimization.h + beast/core/LockFreeStack.h + beast/insight/Collector.h + beast/insight/Counter.h + beast/insight/CounterImpl.h + beast/insight/Event.h + beast/insight/EventImpl.h + beast/insight/Gauge.h + beast/insight/GaugeImpl.h + beast/insight/Group.h + beast/insight/Groups.h + beast/insight/Hook.h + beast/insight/HookImpl.h + beast/insight/Insight.h + beast/insight/Meter.h + beast/insight/MeterImpl.h + beast/insight/NullCollector.h + beast/insight/StatsDCollector.h + beast/test/fail_counter.h + beast/test/fail_stream.h + beast/test/pipe_stream.h + beast/test/sig_wait.h + beast/test/string_iostream.h + beast/test/string_istream.h + beast/test/string_ostream.h + beast/test/test_allocator.h + beast/test/yield_to.h + beast/utility/hash_pair.h + beast/utility/maybe_const.h + beast/utility/temp_dir.h + + # included by only json/impl/json_assert.h + json/json_errors.h + + protocol/PayChan.h + protocol/RippleLedgerHash.h + protocol/messages.h + protocol/st.h + ) + files+=( + basics/README.md + crypto/README.md + json/README.md + protocol/README.md + resource/README.md + ) + move_files src/ripple include/xrpl detail ${files[@]} + + echo move libxrpl sources + files=$(extract_list 'LIBXRPL SOURCES') + move_files src/ripple src/libxrpl "" ${files[@]} + + echo check leftovers + dirs=$(cd include/xrpl; ls -d */) + dirs=$(cd src/ripple; ls -d ${dirs} 2>/dev/null || true) + files="$(cd src/ripple; find ${dirs} -type f)" + if [ -n "${files}" ]; then + echo "leftover files:" + echo ${files} + exit + fi + + echo remove empty directories + empty_dirs="$(cd src/ripple; find ${dirs} -depth -type d)" + for dir in ${empty_dirs[@]}; do + if [ -e ${dir} ]; then + rmdir ${dir} + fi + done + + echo move xrpld sources + files=$( + extract_list 'XRPLD SOURCES' + cd src/ripple + find * -regex '.*\.\(h\|ipp\|md\|pu\|uml\|png\)' + ) + move_files src/ripple src/xrpld detail ${files[@]} + + files="$(cd src/ripple; find . -type f)" + if [ -n "${files}" ]; then + echo "leftover files:" + echo ${files} + exit + fi + +fi + +rm -rf src/ripple + +echo rename .hpp to .h +find include src -name '*.hpp' -exec bash -c 'f="{}"; git mv "${f}" "${f%hpp}h"' \; + +echo move PerfLog.h +if [ -e include/xrpl/basics/PerfLog.h ]; then + git mv include/xrpl/basics/PerfLog.h src/xrpld/perflog +fi + +# Make sure all protobuf includes have the correct prefix. +protobuf_replace='s:^#include\s*["<].*org/xrpl\([^">]\+\)[">]:#include :' +# Make sure first-party includes use angle brackets and .h extension. +ripple_replace='s:include\s*["<]ripple/\(.*\)\.h\(pp\)\?[">]:include :' +beast_replace='s:include\s*:#include :" \ + -e "s:^#include ' +find include src -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-10 -i {} + +git add --update . +git commit -m 'Rewrite includes' --author 'Pretty Printer ' +./Builds/levelization/levelization.sh +git add --update . +git commit -m 'Recompute loops' --author 'Pretty Printer ' diff --git a/cfg/initdb.sh b/cfg/initdb.sh deleted file mode 100755 index 9ca02ed5632..00000000000 --- a/cfg/initdb.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# Execute this script with a running Postgres server on the current host. -# It should work with the most generic installation of Postgres, -# and is necessary for rippled to store data in Postgres. - -# usage: sudo -u postgres ./initdb.sh -psql -c "CREATE USER rippled" -psql -c "CREATE DATABASE rippled WITH OWNER = rippled" - diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 2ba2afa727d..6fabe980cc1 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -13,7 +13,7 @@ # # 4. HTTPS Client # -# 5. Reporting Mode +# 5. # # 6. Database # @@ -417,6 +417,7 @@ # The default list of entries is: # - r.ripple.com 51235 # - sahyadri.isrdc.in 51235 +# - hubs.xrpkuwait.com 51235 # # Examples: # @@ -883,119 +884,6 @@ # #------------------------------------------------------------------------------- # -# 5. Reporting Mode -# -#------------ -# -# rippled has an optional operating mode called Reporting Mode. In Reporting -# Mode, rippled does not connect to the peer to peer network. Instead, rippled -# will continuously extract data from one or more rippled servers that are -# connected to the peer to peer network (referred to as an ETL source). -# Reporting mode servers will forward RPC requests that require access to the -# peer to peer network (submit, fee, etc) to an ETL source. -# -# [reporting] Settings for Reporting Mode. If and only if this section is -# present, rippled will start in reporting mode. This section -# contains a list of ETL source names, and key-value pairs. The -# ETL source names each correspond to a configuration file -# section; the names must match exactly. The key-value pairs are -# optional. -# -# -# [] -# -# A series of key/value pairs that specify an ETL source. -# -# source_ip = -# -# Required. IP address of the ETL source. Can also be a DNS record. -# -# source_ws_port = -# -# Required. Port on which ETL source is accepting unencrypted websocket -# connections. -# -# source_grpc_port = -# -# Required for ETL. Port on which ETL source is accepting gRPC requests. -# If this option is ommitted, this ETL source cannot actually be used for -# ETL; the Reporting Mode server can still forward RPCs to this ETL -# source, but cannot extract data from this ETL source. -# -# -# Key-value pairs (all optional): -# -# read_only Valid values: 0, 1. Default is 0. If set to 1, the server -# will start in strict read-only mode, and will not perform -# ETL. The server will still handle RPC requests, and will -# still forward RPC requests that require access to the p2p -# network. -# -# start_sequence -# Sequence of first ledger to extract if the database is empty. -# ETL extracts ledgers in order. If this setting is absent and -# the database is empty, ETL will start with the next ledger -# validated by the network. If this setting is present and the -# database is not empty, an exception is thrown. -# -# num_markers Degree of parallelism used during the initial ledger -# download. Only used if the database is empty. Valid values -# are 1-256. A higher degree of parallelism results in a -# faster download, but puts more load on the ETL source. -# Default is 2. -# -# Example: -# -# [reporting] -# etl_source1 -# etl_source2 -# read_only=0 -# start_sequence=32570 -# num_markers=8 -# -# [etl_source1] -# source_ip=1.2.3.4 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# [etl_source2] -# source_ip=5.6.7.8 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# Minimal Example: -# -# [reporting] -# etl_source1 -# -# [etl_source1] -# source_ip=1.2.3.4 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# -# Notes: -# -# Reporting Mode requires Postgres (instead of SQLite). The Postgres -# connection info is specified under the [ledger_tx_tables] config section; -# see the Database section for further documentation. -# -# Each ETL source specified must have gRPC enabled (by adding a [port_grpc] -# section to the config). It is recommended to add a secure_gateway entry to -# the gRPC section, in order to bypass the server's rate limiting. -# This section needs to be added to the config of the ETL source, not -# the config of the reporting node. In the example below, the -# reporting server is running at 127.0.0.1. Multiple IPs can be -# specified in secure_gateway via a comma separated list. -# -# [port_grpc] -# ip = 0.0.0.0 -# port = 50051 -# secure_gateway = 127.0.0.1 -# -# -#------------------------------------------------------------------------------- -# # 6. Database # #------------ @@ -1003,13 +891,7 @@ # rippled creates 4 SQLite database to hold bookkeeping information # about transactions, local credentials, and various other things. # It also creates the NodeDB, which holds all the objects that -# make up the current and historical ledgers. In Reporting Mode, rippled -# uses a Postgres database instead of SQLite. -# -# The simplest way to work with Postgres is to install it locally. -# When it is running, execute the initdb.sh script in the current -# directory as: sudo -u postgres ./initdb.sh -# This will create the rippled user and an empty database of the same name. +# make up the current and historical ledgers. # # The size of the NodeDB grows in proportion to the amount of new data and the # amount of historical data (a configurable setting) so the performance of the @@ -1051,33 +933,10 @@ # keeping full history is not advised, and using online delete is # recommended. # -# type = Cassandra -# -# Apache Cassandra is an open-source, distributed key-value store - see -# https://cassandra.apache.org/ for more details. -# -# Cassandra is an alternative backend to be used only with Reporting Mode. -# See the Reporting Mode section for more details about Reporting Mode. -# # Required keys for NuDB and RocksDB: # # path Location to store the database # -# Required keys for Cassandra: -# -# contact_points IP of a node in the Cassandra cluster -# -# port CQL Native Transport Port -# -# secure_connect_bundle -# Absolute path to a secure connect bundle. When using -# a secure connect bundle, contact_points and port are -# not required. -# -# keyspace Name of Cassandra keyspace to use -# -# table_name Name of table in above keyspace to use -# # Optional keys # # cache_size Size of cache for database records. Default is 16384. @@ -1094,7 +953,7 @@ # default value for the unspecified parameter. # # Note: the cache will not be created if online_delete -# is specified, or if shards are used. +# is specified. # # fast_load Boolean. If set, load the last persisted ledger # from disk upon process start before syncing to @@ -1107,10 +966,6 @@ # earliest_seq The default is 32570 to match the XRP ledger # network's earliest allowed sequence. Alternate # networks may set this value. Minimum value of 1. -# If a [shard_db] section is defined, and this -# value is present either [node_db] or [shard_db], -# it must be defined with the same value in both -# sections. # # online_delete Minimum value of 256. Enable automatic purging # of older ledger information. Maintain at least this @@ -1157,25 +1012,6 @@ # checking until healthy. # Default is 5. # -# Optional keys for Cassandra: -# -# username Username to use if Cassandra cluster requires -# authentication -# -# password Password to use if Cassandra cluster requires -# authentication -# -# max_requests_outstanding -# Limits the maximum number of concurrent database -# writes. Default is 10 million. For slower clusters, -# large numbers of concurrent writes can overload the -# cluster. Setting this option can help eliminate -# write timeouts and other write errors due to the -# cluster being overloaded. -# io_threads -# Set the number of IO threads used by the -# Cassandra driver. Defaults to 4. -# # Notes: # The 'node_db' entry configures the primary, persistent storage. # @@ -1192,32 +1028,6 @@ # your rippled.cfg file. # Partial pathnames are relative to the location of the rippled executable. # -# [shard_db] Settings for the Shard Database (optional) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example: -# path=db/shards/nudb -# -# Required keys: -# path Location to store the database -# -# Optional keys: -# max_historical_shards -# The maximum number of historical shards -# to store. -# -# [historical_shard_paths] Additional storage paths for the Shard Database (optional) -# -# Format (without spaces): -# One or more lines, each expressing a full path for storing historical shards: -# /mnt/disk1 -# /mnt/disk2 -# ... -# # [sqlite] Tuning settings for the SQLite databases (optional) # # Format (without spaces): @@ -1297,40 +1107,18 @@ # This setting may not be combined with the # "safety_level" setting. # -# [ledger_tx_tables] (optional) -# -# conninfo Info for connecting to Postgres. Format is -# postgres://[username]:[password]@[ip]/[database]. -# The database and user must already exist. If this -# section is missing and rippled is running in -# Reporting Mode, rippled will connect as the -# user running rippled to a database with the -# same name. On Linux and Mac OS X, the connection -# will take place using the server's UNIX domain -# socket. On Windows, through the localhost IP -# address. Default is empty. -# -# use_tx_tables Valid values: 1, 0 -# The default is 1 (true). Determines whether to use -# the SQLite transaction database. If set to 0, -# rippled will not write to the transaction database, -# and will reject tx, account_tx and tx_history RPCs. -# In Reporting Mode, this setting is ignored. -# -# max_connections Valid values: any positive integer up to 64 bit -# storage length. This configures the maximum -# number of concurrent connections to postgres. -# Default is the maximum possible value to -# fit in a 64 bit integer. -# -# timeout Number of seconds after which idle postgres -# connections are discconnected. If set to 0, -# connections never timeout. Default is 600. -# +# page_size Valid values: integer (MUST be power of 2 between 512 and 65536) +# The default is 4096 bytes. This setting determines +# the size of a page in the transaction.db file. +# See https://www.sqlite.org/pragma.html#pragma_page_size +# for more details about the available options. # -# remember_ip Value values: 1, 0 -# Default is 1 (true). Whether to cache host and -# port connection settings. +# journal_size_limit Valid values: integer +# The default is 1582080. This setting limits +# the size of the journal for transaction.db file. When the limit is +# reached, older entries will be deleted. +# See https://www.sqlite.org/pragma.html#pragma_journal_size_limit +# for more details about the available options. # # #------------------------------------------------------------------------------- @@ -1596,6 +1384,12 @@ # Admin level API commands over Secure Websockets, when originating # from the same machine (via the loopback adapter at 127.0.0.1). # +# "grpc" +# +# ETL commands for Clio. We recommend setting secure_gateway +# in this section to a comma-separated list of the addresses +# of your Clio servers, in order to bypass rippled's rate limiting. +# # This port is commented out but can be enabled by removing # the '#' from each corresponding line including the entry under [server] # @@ -1674,34 +1468,10 @@ path=/var/lib/rippled/db/nudb online_delete=512 advisory_delete=0 -# This is the persistent datastore for shards. It is important for the health -# of the ripple network that rippled operators shard as much as practical. -# NuDB requires SSD storage. Helpful information can be found at -# https://xrpl.org/history-sharding.html -#[shard_db] -#path=/var/lib/rippled/db/shards/nudb -#max_historical_shards=50 -# -# This optional section can be configured with a list -# of paths to use for storing historical shards. Each -# path must correspond to a unique filesystem. -#[historical_shard_paths] -#/path/1 -#/path/2 - [database_path] /var/lib/rippled/db -# To use Postgres, uncomment this section and fill in the appropriate connection -# info. Postgres can only be used in Reporting Mode. -# To disable writing to the transaction database, uncomment this section, and -# set use_tx_tables=0 -# [ledger_tx_tables] -# conninfo = postgres://[username]:[password]@[ip]/[database] -# use_tx_tables=1 - - # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] @@ -1729,15 +1499,3 @@ validators.txt # set to ssl_verify to 0. [ssl_verify] 1 - - -# To run in Reporting Mode, uncomment this section and fill in the appropriate -# connection info for one or more ETL sources. -# [reporting] -# etl_source -# -# -# [etl_source] -# source_grpc_port=50051 -# source_ws_port=6005 -# source_ip=127.0.0.1 diff --git a/cfg/rippled-reporting.cfg b/cfg/rippled-reporting.cfg deleted file mode 100644 index 290bcc5418a..00000000000 --- a/cfg/rippled-reporting.cfg +++ /dev/null @@ -1,1683 +0,0 @@ -#------------------------------------------------------------------------------- -# -# -#------------------------------------------------------------------------------- -# -# Contents -# -# 1. Server -# -# 2. Peer Protocol -# -# 3. Ripple Protocol -# -# 4. HTTPS Client -# -# 5. Reporting Mode -# -# 6. Database -# -# 7. Diagnostics -# -# 8. Voting -# -# 9. Misc Settings -# -# 10. Example Settings -# -#------------------------------------------------------------------------------- -# -# Purpose -# -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it -# looks for a file with the following name: -# -# rippled.cfg -# -# For more information on where the rippled server instance searches for the -# file, visit: -# -# https://xrpl.org/commandline-usage.html#generic-options -# -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, -# or Mac style end of lines. Blank lines and lines beginning with '#' are -# ignored. Undefined sections are reserved. No escapes are currently defined. -# -# Notation -# -# In this document a simple BNF notation is used. Angle brackets denote -# required elements, square brackets denote optional elements, and single -# quotes indicate string literals. A vertical bar separating 1 or more -# elements is a logical "or"; any one of the elements may be chosen. -# Parentheses are notational only, and used to group elements; they are not -# part of the syntax unless they appear in quotes. White space may always -# appear between elements, it has no effect on values. -# -# A required identifier -# '=' The equals sign character -# | Logical "or" -# ( ) Used for grouping -# -# -# An identifier is a string of upper or lower case letters, digits, or -# underscores subject to the requirement that the first character of an -# identifier must be a letter. Identifiers are not case sensitive (but -# values may be). -# -# Some configuration sections contain key/value pairs. A line containing -# a key/value pair has this syntax: -# -# '=' -# -# Depending on the section and key, different value types are possible: -# -# A signed integer -# An unsigned integer -# A boolean. 1 = true/yes/on, 0 = false/no/off. -# -# Consult the documentation on the key in question to determine the possible -# value types. -# -# -# -#------------------------------------------------------------------------------- -# -# 1. Server -# -#---------- -# -# -# -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports -# which may be configured to handshake in one or more of the available -# supported protocols. These universal ports simplify administration: -# A single open port can be used for multiple protocols. -# -# NOTE At least one server port must be defined in order -# to accept incoming network connections. -# -# -# [server] -# -# A list of port names and key/value pairs. A port name must start with a -# letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file -# section with the same name and use it to create a listening port. The -# name is informational only; the choice of name does not affect the function -# of the listening port. -# -# Key/value pairs specified in this section are optional, and apply to all -# listening ports unless the port overrides the value in its section. They -# may be considered default values. -# -# Suggestion: -# -# To avoid a conflict with port names and future configuration sections, -# we recommend prepending "port_" to the port name. This prefix is not -# required, but suggested. -# -# This example defines two ports with different port numbers and settings: -# -# [server] -# port_public -# port_private -# port = 80 -# -# [port_public] -# ip = 0.0.0.0 -# port = 443 -# protocol = peer,https -# -# [port_private] -# ip = 127.0.0.1 -# protocol = http -# -# When rippled is used as a command line client (for example, issuing a -# server stop command), the first port advertising the http or https -# protocol will be used to make the connection. -# -# -# -# [] -# -# A series of key/value pairs that define the settings for the port with -# the corresponding name. These keys are possible: -# -# ip = -# -# Required. Determines the IP address of the network interface to bind -# to. To bind to all available IPv4 interfaces, use 0.0.0.0 -# To binding to all IPv4 and IPv6 interfaces, use :: -# -# NOTE if the ip value is ::, then any incoming IPv4 connections will -# be made as mapped IPv4 addresses. -# -# port = -# -# Required. Sets the port number to use for this port. -# -# protocol = [ http, https, peer ] -# -# Required. A comma-separated list of protocols to support: -# -# http JSON-RPC over HTTP -# https JSON-RPC over HTTPS -# ws Websockets -# wss Secure Websockets -# peer Peer Protocol -# -# Restrictions: -# -# Only one port may be configured to support the peer protocol. -# A port cannot have websocket and non websocket protocols at the -# same time. It is possible have both Websockets and Secure Websockets -# together in one port. -# -# NOTE If no ports support the peer protocol, rippled cannot -# receive incoming peer connections or become a superpeer. -# -# limit = -# -# Optional. An integer value that will limit the number of connected -# clients that the port will accept. Once the limit is reached, new -# connections will be refused until other clients disconnect. -# Omit or set to 0 to allow unlimited numbers of clients. -# -# user = -# password = -# -# When set, these credentials will be required on HTTP/S requests. -# The credentials must be provided using HTTP's Basic Authentication -# headers. If either or both fields are empty, then no credentials are -# required. IP address restrictions, if any, will be checked in addition -# to the credentials specified here. -# -# When acting in the client role, rippled will supply these credentials -# using HTTP's Basic Authentication headers when making outbound HTTP/S -# requests. -# -# admin = [ IP, IP, IP, ... ] -# -# A comma-separated list of IP addresses. -# -# When set, grants administrative command access to the specified IP -# addresses. These commands may be issued over http, https, ws, or wss -# if configured on the port. If not provided, the default is to not allow -# administrative commands. -# -# NOTE A common configuration value for the admin field is "localhost". -# If you are listening on all IPv4/IPv6 addresses by specifing -# ip = :: then you can use admin = ::ffff:127.0.0.1,::1 to allow -# administrative access from both IPv4 and IPv6 localhost -# connections. -# -# *SECURITY WARNING* -# 0.0.0.0 or :: may be used to allow access from any IP address. It must -# be the only address specified and cannot be combined with other IPs. -# Use of this address can compromise server security, please consider its -# use carefully. -# -# admin_user = -# admin_password = -# -# When set, clients must provide these credentials in the submitted -# JSON for any administrative command requests submitted to the HTTP/S, -# WS, or WSS protocol interfaces. If administrative commands are -# disabled for a port, these credentials have no effect. -# -# When acting in the client role, rippled will supply these credentials -# in the submitted JSON for any administrative command requests when -# invoking JSON-RPC commands on remote servers. -# -# secure_gateway = [ IP, IP, IP, ... ] -# -# A comma-separated list of IP addresses. -# -# When set, allows the specified IP addresses to pass HTTP headers -# containing username and remote IP address for each session. If a -# non-empty username is passed in this way, then resource controls -# such as often resulting in "tooBusy" errors will be lifted. However, -# administrative RPC commands such as "stop" will not be allowed. -# The HTTP headers that secure_gateway hosts can set are X-User and -# X-Forwarded-For. Only the X-User header affects resource controls. -# However, both header values are logged to help identify user activity. -# If no X-User header is passed, or if its value is empty, then -# resource controls will default to those for non-administrative users. -# -# The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be -# responsible for properly authenticating the remote user. -# -# The same IP address cannot be used in both "admin" and "secure_gateway" -# lists for the same port. In this case, rippled will abort with an error -# message to the console shortly after startup -# -# ssl_key = -# ssl_cert = -# ssl_chain = -# -# Use the specified files when configuring SSL on the port. -# -# NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. -# -# The files have these meanings: -# -# ssl_key -# -# Specifies the filename holding the SSL key in PEM format. -# -# ssl_cert -# -# Specifies the path to the SSL certificate file in PEM format. -# This is not needed if the chain includes it. -# -# ssl_chain -# -# If you need a certificate chain, specify the path to the -# certificate chain here. The chain may include the end certificate. -# -# ssl_ciphers = -# -# Control the ciphers which the server will support over SSL on the port, -# specified using the OpenSSL "cipher list format". -# -# NOTE If unspecified, rippled will automatically configure a modern -# cipher suite. This default suite should be widely supported. -# -# You should not modify this string unless you have a specific -# reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or -# prevent RPC and WebSocket clients from connecting. -# -# send_queue_limit = [1..65535] -# -# A Websocket will disconnect when its send queue exceeds this limit. -# The default is 100. A larger value may help with erratic disconnects but -# may adversely affect server performance. -# -# WebSocket permessage-deflate extension options -# -# These settings configure the optional permessage-deflate extension -# options and may appear on any port configuration entry. They are meaningful -# only to ports which have enabled a WebSocket protocol. -# -# permessage_deflate = -# -# Determines if permessage_deflate extension negotiations are enabled. -# When enabled, clients may request the extension and the server will -# offer the enabled extension in response. -# -# client_max_window_bits = [9..15] -# server_max_window_bits = [9..15] -# client_no_context_takeover = -# server_no_context_takeover = -# -# These optional settings control options related to the permessage-deflate -# extension negotiation. For precise definitions of these fields please see -# the RFC 7692, "Compression Extensions for WebSocket": -# https://tools.ietf.org/html/rfc7692 -# -# compress_level = [0..9] -# -# When set, determines the amount of compression attempted, where 0 is -# the least amount and 9 is the most amount. Higher levels require more -# CPU resources. Levels 1 through 3 use a fast compression algorithm, -# while levels 4 through 9 use a more compact algorithm which uses more -# CPU resources. If unspecified, a default of 3 is used. -# -# memory_level = [1..9] -# -# When set, determines the relative amount of memory used to hold -# intermediate compression data. Higher numbers can give better compression -# ratios at the cost of higher memory and CPU resources. -# -# [rpc_startup] -# -# Specify a list of RPC commands to run at startup. -# -# Examples: -# { "command" : "server_info" } -# { "command" : "log_level", "partition" : "ripplecalc", "severity" : "trace" } -# -# -# -# [websocket_ping_frequency] -# -# -# -# The amount of time to wait in seconds, before sending a websocket 'ping' -# message. Ping messages are used to determine if the remote end of the -# connection is no longer available. -# -# -# [server_domain] -# -# domain name -# -# The domain under which a TOML file applicable to this server can be -# found. A server may lie about its domain so the TOML should contain -# a reference to this server by pubkey in the [nodes] array. -# -# -#------------------------------------------------------------------------------- -# -# 2. Peer Protocol -# -#----------------- -# -# These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the -# Ripple Payment protocol. It is over peer connections that transactions -# and validations are passed from to machine to machine, to determine the -# contents of validated ledgers. -# -# -# -# [ips] -# -# List of hostnames or ips where the Ripple protocol is served. A default -# starter list is included in the code and used if no other hostnames are -# available. -# -# One address or domain name per line is allowed. A port may must be -# specified after adding a space to the address. The ordering of entries -# does not generally matter. -# -# The default list of entries is: -# - r.ripple.com 51235 -# - sahyadri.isrdc.in 51235 -# -# Examples: -# -# [ips] -# 192.168.0.1 -# 192.168.0.1 2459 -# r.ripple.com 51235 -# -# -# [ips_fixed] -# -# List of IP addresses or hostnames to which rippled should always attempt to -# maintain peer connections with. This is useful for manually forming private -# networks, for example to configure a validation server that connects to the -# Ripple network through a public-facing server, or for building a set -# of cluster peers. -# -# One address or domain names per line is allowed. A port must be specified -# after adding a space to the address. -# -# -# -# [peer_private] -# -# 0 or 1. -# -# 0: Request peers to broadcast your address. Normal outbound peer connections [default] -# 1: Request peers not broadcast your address. Only connect to configured peers. -# -# -# -# [peers_max] -# -# The largest number of desired peer connections (incoming or outgoing). -# Cluster and fixed peers do not count towards this total. There are -# implementation-defined lower limits imposed on this value for security -# purposes. -# -# -# -# [node_seed] -# -# This is used for clustering. To force a particular node seed or key, the -# key can be set here. The format is the same as the validation_seed field. -# To obtain a validation seed, use the validation_create command. -# -# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE -# shfArahZT9Q9ckTf3s1psJ7C7qzVN -# -# -# -# [cluster_nodes] -# -# To extend full trust to other nodes, place their node public keys here. -# Generally, you should only do this for nodes under common administration. -# Node public keys start with an 'n'. To give a node a name for identification -# place a space after the public key and then the name. -# -# -# -# [max_transactions] -# -# Configure the maximum number of transactions to have in the job queue -# -# Must be a number between 100 and 1000, defaults to 250 -# -# -# [overlay] -# -# Controls settings related to the peer to peer overlay. -# -# A set of key/value pair parameters to configure the overlay. -# -# public_ip = -# -# If the server has a known, fixed public IPv4 address, -# specify that IP address here in dotted decimal notation. -# Peers will use this information to reject attempt to proxy -# connections to or from this server. -# -# ip_limit = -# -# The maximum number of incoming peer connections allowed by a single -# IP that isn't classified as "private" in RFC1918. The implementation -# imposes some hard and soft upper limits on this value to prevent a -# single host from consuming all inbound slots. If the value is not -# present the server will autoconfigure an appropriate limit. -# -# max_unknown_time = -# -# The maximum amount of time, in seconds, that an outbound connection -# is allowed to stay in the "unknown" tracking state. This option can -# take any value between 300 and 1800 seconds, inclusive. If the option -# is not present the server will autoconfigure an appropriate limit. -# -# The current default (which is subject to change) is 600 seconds. -# -# max_diverged_time = -# -# The maximum amount of time, in seconds, that an outbound connection -# is allowed to stay in the "diverged" tracking state. The option can -# take any value between 60 and 900 seconds, inclusive. If the option -# is not present the server will autoconfigure an appropriate limit. -# -# The current default (which is subject to change) is 300 seconds. -# -# -# [transaction_queue] EXPERIMENTAL -# -# This section is EXPERIMENTAL, and should not be -# present for production configuration settings. -# -# A set of key/value pair parameters to tune the performance of the -# transaction queue. -# -# ledgers_in_queue = -# -# The queue will be limited to this of average ledgers' -# worth of transactions. If the queue fills up, the transactions -# with the lowest fee levels will be dropped from the queue any -# time a transaction with a higher fee level is added. -# Default: 20. -# -# minimum_queue_size = -# -# The queue will always be able to hold at least this of -# transactions, regardless of recent ledger sizes or the value of -# ledgers_in_queue. Default: 2000. -# -# retry_sequence_percent = -# -# If a client replaces a transaction in the queue (same sequence -# number as a transaction already in the queue), the new -# transaction's fee must be more than percent higher -# than the original transaction's fee, or meet the current open -# ledger fee to be considered. Default: 25. -# -# minimum_escalation_multiplier = -# -# At ledger close time, the median fee level of the transactions -# in that ledger is used as a multiplier in escalation -# calculations of the next ledger. This minimum value ensures that -# the escalation is significant. Default: 500. -# -# minimum_txn_in_ledger = -# -# Minimum number of transactions that must be allowed into the -# ledger at the minimum required fee before the required fee -# escalates. Default: 5. -# -# minimum_txn_in_ledger_standalone = -# -# Like minimum_txn_in_ledger when rippled is running in standalone -# mode. Default: 1000. -# -# target_txn_in_ledger = -# -# Number of transactions allowed into the ledger at the minimum -# required fee that the queue will "work toward" as long as -# consensus stays healthy. The limit will grow quickly until it -# reaches or exceeds this number. After that the limit may still -# change, but will stay above the target. If consensus is not -# healthy, the limit will be clamped to this value or lower. -# Default: 50. -# -# maximum_txn_in_ledger = -# -# (Optional) Maximum number of transactions that will be allowed -# into the ledger at the minimum required fee before the required -# fee escalates. Default: no maximum. -# -# normal_consensus_increase_percent = -# -# (Optional) When the ledger has more transactions than "expected", -# and performance is humming along nicely, the expected ledger size -# is updated to the previous ledger size plus this percentage. -# Default: 20 -# -# slow_consensus_decrease_percent = -# -# (Optional) When consensus takes longer than appropriate, the -# expected ledger size is updated to the minimum of the previous -# ledger size or the "expected" ledger size minus this percentage. -# Default: 50 -# -# maximum_txn_per_account = -# -# Maximum number of transactions that one account can have in the -# queue at any given time. Default: 10. -# -# minimum_last_ledger_buffer = -# -# If a transaction has a LastLedgerSequence, it must be at least -# this much larger than the current open ledger sequence number. -# Default: 2. -# -# zero_basefee_transaction_feelevel = -# -# So we don't deal with infinite fee levels, treat any transaction -# with a 0 base fee (ie. SetRegularKey password recovery) as -# having this fee level. -# Default: 256000. -# -# -#------------------------------------------------------------------------------- -# -# 3. Protocol -# -#------------------- -# -# These settings affect the behavior of the server instance with respect -# to protocol level activities such as validating and closing ledgers -# adjusting fees in response to server overloads. -# -# -# -# -# [relay_proposals] -# -# Controls the relaying behavior for proposals received by this server that -# are issued by validators that are not on the server's UNL. -# -# Legal values are: "trusted" and "all". The default is "trusted". -# -# -# [relay_validations] -# -# Controls the relaying behavior for validations received by this server that -# are issued by validators that are not on the server's UNL. -# -# Legal values are: "trusted" and "all". The default is "all". -# -# -# -# -# -# [ledger_history] -# -# The number of past ledgers to acquire on server startup and the minimum to -# maintain while running. -# -# To serve clients, servers need historical ledger data. Servers that don't -# need to serve clients can set this to "none". Servers that want complete -# history can set this to "full". -# -# This must be less than or equal to online_delete (if online_delete is used) -# -# The default is: 256 -# -# -# -# [fetch_depth] -# -# The number of past ledgers to serve to other peers that request historical -# ledger data (or "full" for no limit). -# -# Servers that require low latency and high local performance may wish to -# restrict the historical ledgers they are willing to serve. Setting this -# below 32 can harm network stability as servers require easy access to -# recent history to stay in sync. Values below 128 are not recommended. -# -# The default is: full -# -# -# -# [validation_seed] -# -# To perform validation, this section should contain either a validation seed -# or key. The validation seed is used to generate the validation -# public/private key pair. To obtain a validation seed, use the -# validation_create command. -# -# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE -# shfArahZT9Q9ckTf3s1psJ7C7qzVN -# -# -# -# [validator_token] -# -# This is an alternative to [validation_seed] that allows rippled to perform -# validation without having to store the validator keys on the network -# connected server. The field should contain a single token in the form of a -# base64-encoded blob. -# An external tool is available for generating validator keys and tokens. -# -# -# -# [validator_key_revocation] -# -# If a validator's secret key has been compromised, a revocation must be -# generated and added to this field. The revocation notifies peers that it is -# no longer safe to trust the revoked key. The field should contain a single -# revocation in the form of a base64-encoded blob. -# An external tool is available for generating and revoking validator keys. -# -# -# -# [validators_file] -# -# Path or name of a file that determines the nodes to always accept as validators. -# -# The contents of the file should include a [validators] and/or -# [validator_list_sites] and [validator_list_keys] entries. -# [validators] should be followed by a list of validation public keys of -# nodes, one per line. -# [validator_list_sites] should be followed by a list of URIs each serving a -# list of recommended validators. -# [validator_list_keys] should be followed by a list of keys belonging to -# trusted validator list publishers. Validator lists fetched from configured -# sites will only be considered if the list is accompanied by a valid -# signature from a trusted publisher key. -# -# Specify the file by its name or path. -# Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. -# -# Examples: -# /home/ripple/validators.txt -# C:/home/ripple/validators.txt -# -# Example content: -# [validators] -# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 -# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj -# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C -# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS -# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA -# -# -# -# [path_search] -# When searching for paths, the default search aggressiveness. This can take -# exponentially more resources as the size is increased. -# -# The default is: 7 -# -# [path_search_fast] -# [path_search_max] -# When searching for paths, the minimum and maximum search aggressiveness. -# -# If you do not need pathfinding, you can set path_search_max to zero to -# disable it and avoid some expensive bookkeeping. -# -# The default for 'path_search_fast' is 2. The default for 'path_search_max' is 10. -# -# [path_search_old] -# -# For clients that use the legacy path finding interfaces, the search -# aggressiveness to use. The default is 7. -# -# -# -# [fee_default] -# -# Sets the base cost of a transaction in drops. Used when the server has -# no other source of fee information, such as signing transactions offline. -# -# -# -# [workers] -# -# Configures the number of threads for processing work submitted by peers -# and clients. If not specified, then the value is automatically set to the -# number of processor threads plus 2 for networked nodes. Nodes running in -# stand alone mode default to 1 worker. -# -# -# -# [network_id] -# -# Specify the network which this server is configured to connect to and -# track. If set, the server will not establish connections with servers -# that are explicitly configured to track another network. -# -# Network identifiers are usually unsigned integers in the range 0 to -# 4294967295 inclusive. The server also maps the following well-known -# names to the corresponding numerical identifier: -# -# main -> 0 -# testnet -> 1 -# devnet -> 2 -# -# If this value is not specified the server is not explicitly configured -# to track a particular network. -# -# -# [ledger_replay] -# -# 0 or 1. -# -# 0: Disable the ledger replay feature [default] -# 1: Enable the ledger replay feature. With this feature enabled, when -# acquiring a ledger from the network, a rippled node only downloads -# the ledger header and the transactions instead of the whole ledger. -# And the ledger is built by applying the transactions to the parent -# ledger. -# -#------------------------------------------------------------------------------- -# -# 4. HTTPS Client -# -#---------------- -# -# The rippled server instance uses HTTPS GET requests in a variety of -# circumstances, including but not limited to contacting trusted domains to -# fetch information such as mapping an email address to a Ripple Payment -# Network address. -# -# [ssl_verify] -# -# 0 or 1. -# -# 0. HTTPS client connections will not verify certificates. -# 1. Certificates will be checked for HTTPS client connections. -# -# If not specified, this parameter defaults to 1. -# -# -# -# [ssl_verify_file] -# -# -# -# A file system path leading to the certificate verification file for -# HTTPS client requests. -# -# -# -# [ssl_verify_dir] -# -# -# -# -# A file system path leading to a file or directory containing the root -# certificates that the server will accept for verifying HTTP servers. -# Used only for outbound HTTPS client connections. -# -#------------------------------------------------------------------------------- -# -# 5. Reporting Mode -# -#------------ -# -# rippled has an optional operating mode called Reporting Mode. In Reporting -# Mode, rippled does not connect to the peer to peer network. Instead, rippled -# will continuously extract data from one or more rippled servers that are -# connected to the peer to peer network (referred to as an ETL source). -# Reporting mode servers will forward RPC requests that require access to the -# peer to peer network (submit, fee, etc) to an ETL source. -# -# [reporting] Settings for Reporting Mode. If and only if this section is -# present, rippled will start in reporting mode. This section -# contains a list of ETL source names, and key-value pairs. The -# ETL source names each correspond to a configuration file -# section; the names must match exactly. The key-value pairs are -# optional. -# -# -# [] -# -# A series of key/value pairs that specify an ETL source. -# -# source_ip = -# -# Required. IP address of the ETL source. Can also be a DNS record. -# -# source_ws_port = -# -# Required. Port on which ETL source is accepting unencrypted websocket -# connections. -# -# source_grpc_port = -# -# Required for ETL. Port on which ETL source is accepting gRPC requests. -# If this option is ommitted, this ETL source cannot actually be used for -# ETL; the Reporting Mode server can still forward RPCs to this ETL -# source, but cannot extract data from this ETL source. -# -# -# Key-value pairs (all optional): -# -# read_only Valid values: 0, 1. Default is 0. If set to 1, the server -# will start in strict read-only mode, and will not perform -# ETL. The server will still handle RPC requests, and will -# still forward RPC requests that require access to the p2p -# network. -# -# start_sequence -# Sequence of first ledger to extract if the database is empty. -# ETL extracts ledgers in order. If this setting is absent and -# the database is empty, ETL will start with the next ledger -# validated by the network. If this setting is present and the -# database is not empty, an exception is thrown. -# -# num_markers Degree of parallelism used during the initial ledger -# download. Only used if the database is empty. Valid values -# are 1-256. A higher degree of parallelism results in a -# faster download, but puts more load on the ETL source. -# Default is 2. -# -# Example: -# -# [reporting] -# etl_source1 -# etl_source2 -# read_only=0 -# start_sequence=32570 -# num_markers=8 -# -# [etl_source1] -# source_ip=1.2.3.4 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# [etl_source2] -# source_ip=5.6.7.8 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# Minimal Example: -# -# [reporting] -# etl_source1 -# -# [etl_source1] -# source_ip=1.2.3.4 -# source_ws_port=6005 -# source_grpc_port=50051 -# -# -# Notes: -# -# Reporting Mode requires Postgres (instead of SQLite). The Postgres -# connection info is specified under the [ledger_tx_tables] config section; -# see the Database section for further documentation. -# -# Each ETL source specified must have gRPC enabled (by adding a [port_grpc] -# section to the config). It is recommended to add a secure_gateway entry to -# the gRPC section, in order to bypass the server's rate limiting. -# This section needs to be added to the config of the ETL source, not -# the config of the reporting node. In the example below, the -# reporting server is running at 127.0.0.1. Multiple IPs can be -# specified in secure_gateway via a comma separated list. -# -# [port_grpc] -# ip = 0.0.0.0 -# port = 50051 -# secure_gateway = 127.0.0.1 -# -# -#------------------------------------------------------------------------------- -# -# 6. Database -# -#------------ -# -# rippled creates 4 SQLite database to hold bookkeeping information -# about transactions, local credentials, and various other things. -# It also creates the NodeDB, which holds all the objects that -# make up the current and historical ledgers. In Reporting Mode, rippled -# uses a Postgres database instead of SQLite. -# -# The simplest way to work with Postgres is to install it locally. -# When it is running, execute the initdb.sh script in the current -# directory as: sudo -u postgres ./initdb.sh -# This will create the rippled user and an empty database of the same name. -# -# The size of the NodeDB grows in proportion to the amount of new data and the -# amount of historical data (a configurable setting) so the performance of the -# underlying storage media where the NodeDB is placed can significantly affect -# the performance of the server. -# -# Partial pathnames will be considered relative to the location of -# the rippled.cfg file. -# -# [node_db] Settings for the Node Database (required) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example: -# type=nudb -# path=db/nudb -# -# The "type" field must be present and controls the choice of backend: -# -# type = NuDB -# -# NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. -# -# NuDB maintains its high speed regardless of the amount of history -# stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. -# -# type = RocksDB -# -# RocksDB is an open-source, general-purpose key/value store - see -# http://rocksdb.org/ for more details. -# -# RocksDB is an alternative backend for systems that don't use solid-state -# drives. Because RocksDB's performance degrades as it stores more data, -# keeping full history is not advised, and using online delete is -# recommended. -# -# type = Cassandra -# -# Apache Cassandra is an open-source, distributed key-value store - see -# https://cassandra.apache.org/ for more details. -# -# Cassandra is an alternative backend to be used only with Reporting Mode. -# See the Reporting Mode section for more details about Reporting Mode. -# -# Required keys for NuDB and RocksDB: -# -# path Location to store the database -# -# Required keys for Cassandra: -# -# contact_points IP of a node in the Cassandra cluster -# -# port CQL Native Transport Port -# -# secure_connect_bundle -# Absolute path to a secure connect bundle. When using -# a secure connect bundle, contact_points and port are -# not required. -# -# keyspace Name of Cassandra keyspace to use -# -# table_name Name of table in above keyspace to use -# -# Optional keys -# -# cache_size Size of cache for database records. Default is 16384. -# Setting this value to 0 will use the default value. -# -# cache_age Length of time in minutes to keep database records -# cached. Default is 5 minutes. Setting this value to -# 0 will use the default value. -# -# Note: if neither cache_size nor cache_age is -# specified, the cache for database records will not -# be created. If only one of cache_size or cache_age -# is specified, the cache will be created using the -# default value for the unspecified parameter. -# -# Note: the cache will not be created if online_delete -# is specified, or if shards are used. -# -# Optional keys for NuDB or RocksDB: -# -# earliest_seq The default is 32570 to match the XRP ledger -# network's earliest allowed sequence. Alternate -# networks may set this value. Minimum value of 1. -# If a [shard_db] section is defined, and this -# value is present either [node_db] or [shard_db], -# it must be defined with the same value in both -# sections. -# -# online_delete Minimum value of 256. Enable automatic purging -# of older ledger information. Maintain at least this -# number of ledger records online. Must be greater -# than or equal to ledger_history. -# -# These keys modify the behavior of online_delete, and thus are only -# relevant if online_delete is defined and non-zero: -# -# advisory_delete 0 for disabled, 1 for enabled. If set, the -# administrative RPC call "can_delete" is required -# to enable online deletion of ledger records. -# Online deletion does not run automatically if -# non-zero and the last deletion was on a ledger -# greater than the current "can_delete" setting. -# Default is 0. -# -# delete_batch When automatically purging, SQLite database -# records are deleted in batches. This value -# controls the maximum size of each batch. Larger -# batches keep the databases locked for more time, -# which may cause other functions to fall behind, -# and thus cause the node to lose sync. -# Default is 100. -# -# back_off_milliseconds -# Number of milliseconds to wait between -# online_delete batches to allow other functions -# to catch up. -# Default is 100. -# -# age_threshold_seconds -# The online delete process will only run if the -# latest validated ledger is younger than this -# number of seconds. -# Default is 60. -# -# recovery_wait_seconds -# The online delete process checks periodically -# that rippled is still in sync with the network, -# and that the validated ledger is less than -# 'age_threshold_seconds' old. By default, if it -# is not the online delete process aborts and -# tries again later. If 'recovery_wait_seconds' -# is set and rippled is out of sync, but likely to -# recover quickly, then online delete will wait -# this number of seconds for rippled to get back -# into sync before it aborts. -# Set this value if the node is otherwise staying -# in sync, or recovering quickly, but the online -# delete process is unable to finish. -# Default is unset. -# -# Optional keys for Cassandra: -# -# username Username to use if Cassandra cluster requires -# authentication -# -# password Password to use if Cassandra cluster requires -# authentication -# -# max_requests_outstanding -# Limits the maximum number of concurrent database -# writes. Default is 10 million. For slower clusters, -# large numbers of concurrent writes can overload the -# cluster. Setting this option can help eliminate -# write timeouts and other write errors due to the -# cluster being overloaded. -# -# Notes: -# The 'node_db' entry configures the primary, persistent storage. -# -# The 'import_db' is used with the '--import' command line option to -# migrate the specified database into the current database given -# in the [node_db] section. -# -# [import_db] Settings for performing a one-time import (optional) -# [database_path] Path to the book-keeping databases. -# -# The server creates and maintains 4 to 5 bookkeeping SQLite databases in -# the 'database_path' location. If you omit this configuration setting, -# the server creates a directory called "db" located in the same place as -# your rippled.cfg file. -# Partial pathnames are relative to the location of the rippled executable. -# -# [shard_db] Settings for the Shard Database (optional) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example: -# path=db/shards/nudb -# -# Required keys: -# path Location to store the database -# -# Optional keys: -# max_historical_shards -# The maximum number of historical shards -# to store. -# -# [historical_shard_paths] Additional storage paths for the Shard Database (optional) -# -# Format (without spaces): -# One or more lines, each expressing a full path for storing historical shards: -# /mnt/disk1 -# /mnt/disk2 -# ... -# -# [sqlite] Tuning settings for the SQLite databases (optional) -# -# Format (without spaces): -# One or more lines of case-insensitive key / value pairs: -# '=' -# ... -# -# Example 1: -# safety_level=low -# -# Example 2: -# journal_mode=off -# synchronous=off -# -# WARNING: These settings can have significant effects on data integrity, -# particularly in systemic failure scenarios. It is strongly recommended -# that they be left at their defaults unless the server is having -# performance issues during normal operation or during automatic purging -# (online_delete) operations. A warning will be logged on startup if -# 'ledger_history' is configured to store more than 10,000,000 ledgers and -# any of these settings are less safe than the default. This is due to the -# inordinate amount of time and bandwidth it will take to safely rebuild a -# corrupted database of that size from other peers. -# -# Optional keys: -# -# safety_level Valid values: high, low -# The default is "high", which tunes the SQLite -# databases in the most reliable mode, and is -# equivalent to: -# journal_mode=wal -# synchronous=normal -# temp_store=file -# "low" is equivalent to: -# journal_mode=memory -# synchronous=off -# temp_store=memory -# These "low" settings trade speed and reduced I/O -# for a higher risk of data loss. See the -# individual settings below for more information. -# This setting may not be combined with any of the -# other tuning settings: "journal_mode", -# "synchronous", or "temp_store". -# -# journal_mode Valid values: delete, truncate, persist, memory, wal, off -# The default is "wal", which uses a write-ahead -# log to implement database transactions. -# Alternately, "memory" saves disk I/O, but if -# rippled crashes during a transaction, the -# database is likely to be corrupted. -# See https://www.sqlite.org/pragma.html#pragma_journal_mode -# for more details about the available options. -# This setting may not be combined with the -# "safety_level" setting. -# -# synchronous Valid values: off, normal, full, extra -# The default is "normal", which works well with -# the "wal" journal mode. Alternatively, "off" -# allows rippled to continue as soon as data is -# passed to the OS, which can significantly -# increase speed, but risks data corruption if -# the host computer crashes before writing that -# data to disk. -# See https://www.sqlite.org/pragma.html#pragma_synchronous -# for more details about the available options. -# This setting may not be combined with the -# "safety_level" setting. -# -# temp_store Valid values: default, file, memory -# The default is "file", which will use files -# for temporary database tables and indices. -# Alternatively, "memory" may save I/O, but -# rippled does not currently use many, if any, -# of these temporary objects. -# See https://www.sqlite.org/pragma.html#pragma_temp_store -# for more details about the available options. -# This setting may not be combined with the -# "safety_level" setting. -# -# [ledger_tx_tables] (optional) -# -# conninfo Info for connecting to Postgres. Format is -# postgres://[username]:[password]@[ip]/[database]. -# The database and user must already exist. If this -# section is missing and rippled is running in -# Reporting Mode, rippled will connect as the -# user running rippled to a database with the -# same name. On Linux and Mac OS X, the connection -# will take place using the server's UNIX domain -# socket. On Windows, through the localhost IP -# address. Default is empty. -# -# use_tx_tables Valid values: 1, 0 -# The default is 1 (true). Determines whether to use -# the SQLite transaction database. If set to 0, -# rippled will not write to the transaction database, -# and will reject tx, account_tx and tx_history RPCs. -# In Reporting Mode, this setting is ignored. -# -# max_connections Valid values: any positive integer up to 64 bit -# storage length. This configures the maximum -# number of concurrent connections to postgres. -# Default is the maximum possible value to -# fit in a 64 bit integer. -# -# timeout Number of seconds after which idle postgres -# connections are discconnected. If set to 0, -# connections never timeout. Default is 600. -# -# -# remember_ip Value values: 1, 0 -# Default is 1 (true). Whether to cache host and -# port connection settings. -# -# -#------------------------------------------------------------------------------- -# -# 7. Diagnostics -# -#--------------- -# -# These settings are designed to help server administrators diagnose -# problems, and obtain detailed information about the activities being -# performed by the rippled process. -# -# -# -# [debug_logfile] -# -# Specifies where a debug logfile is kept. By default, no debug log is kept. -# Unless absolute, the path is relative the directory containing this file. -# -# Example: debug.log -# -# -# -# [insight] -# -# Configuration parameters for the Beast. Insight stats collection module. -# -# Insight is a module that collects information from the areas of rippled -# that have instrumentation. The configuration parameters control where the -# collection metrics are sent. The parameters are expressed as key = value -# pairs with no white space. The main parameter is the choice of server: -# -# "server" -# -# Choice of server to send metrics to. Currently the only choice is -# "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is -# available here: -# https://github.com/b/statsd_spec -# -# When server=statsd, these additional keys are used: -# -# "address" The UDP address and port of the listening StatsD server, -# in the format, n.n.n.n:port. -# -# "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. -# -# If this section is missing, or the server type is unspecified or unknown, -# statistics are not collected or reported. -# -# Example: -# -# [insight] -# server=statsd -# address=192.168.0.95:4201 -# prefix=my_validator -# -# [perf] -# -# Configuration of performance logging. If enabled, write Json-formatted -# performance-oriented data periodically to a distinct log file. -# -# "perf_log" A string specifying the pathname of the performance log -# file. A relative pathname will log relative to the -# configuration directory. Required to enable -# performance logging. -# -# "log_interval" Integer value for number of seconds between writing -# to performance log. Default 1. -# -# Example: -# [perf] -# perf_log=/var/log/rippled/perf.log -# log_interval=2 -# -#------------------------------------------------------------------------------- -# -# 8. Voting -# -#---------- -# -# The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide -# settings, these choices become part of the instance's vote during the -# consensus process for each voting ledger. -# -# [voting] -# -# A set of key/value pair parameters used during voting ledgers. -# -# reference_fee = -# -# The cost of the reference transaction fee, specified in drops. -# The reference transaction is the simplest form of transaction. -# It represents an XRP payment between two parties. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# reference_fee = 10 # 10 drops -# -# account_reserve = -# -# The account reserve requirement is specified in drops. The portion of an -# account's XRP balance that is at or below the reserve may only be -# spent on transaction fees, and not transferred out of the account. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# account_reserve = 10000000 # 10 XRP -# -# owner_reserve = -# -# The owner reserve is the amount of XRP reserved in the account for -# each ledger item owned by the account. Ledger items an account may -# own include trust lines, open orders, and tickets. -# -# If this parameter is unspecified, rippled will use an internal -# default. Don't change this without understanding the consequences. -# -# Example: -# owner_reserve = 2000000 # 2 XRP -# -#------------------------------------------------------------------------------- -# -# 9. Misc Settings -# -#----------------- -# -# [node_size] -# -# Tunes the servers based on the expected load and available memory. Legal -# sizes are "tiny", "small", "medium", "large", and "huge". We recommend -# you start at the default and raise the setting if you have extra memory. -# -# The code attempts to automatically determine the appropriate size for -# this parameter based on the amount of RAM and the number of execution -# cores available to the server. The current decision matrix is: -# -# | | Cores | -# |---------|------------------------| -# | RAM | 1 | 2 or 3 | ≥ 4 | -# |---------|------|--------|--------| -# | < ~8GB | tiny | tiny | tiny | -# | < ~12GB | tiny | small | small | -# | < ~16GB | tiny | small | medium | -# | < ~24GB | tiny | small | large | -# | < ~32GB | tiny | small | huge | -# -# [signing_support] -# -# Specifies whether the server will accept "sign" and "sign_for" commands -# from remote users. Even if the commands are sent over a secure protocol -# like secure websocket, this should generally be discouraged, because it -# requires sending the secret to use for signing to the server. In order -# to sign transactions, users should prefer to use a standalone signing -# tool instead. -# -# This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. -# -# The default value of this field is "false" -# -# Example: -# -# [signing_support] -# true -# -# [crawl] -# -# List of options to control what data is reported through the /crawl endpoint -# See https://xrpl.org/peer-crawler.html -# -# -# -# Enable or disable access to /crawl requests. Default is '1' which -# enables access. -# -# overlay = -# -# Report information about peers this server is connected to, similar -# to the "peers" RPC API. Default is '1' which means to report peer -# overlay info. -# -# server = -# -# Report information about the local server, similar to the "server_state" -# RPC API. Default is '1' which means to report local server info. -# -# counts = -# -# Report information about the local server health counters, similar to -# the "get_counts" RPC API. Default is '0' which means not to report -# server counts. -# -# unl = -# -# Report information about the local server's validator lists, similar to -# the "validators" and "validator_list_sites" RPC APIs. Default is '1' -# which means to report server validator lists. -# -# Examples: -# -# [crawl] -# 0 -# -# [crawl] -# overlay = 1 -# server = 1 -# counts = 0 -# unl = 1 -# -# [vl] -# -# Options to control what data is reported through the /vl endpoint -# See [...] -# -# enable = -# -# Enable or disable access to /vl requests. Default is '1' which -# enables access. -# -# [beta_rpc_api] -# -# 0 or 1. -# -# 0: Disable the beta API version for JSON-RPC and WebSocket [default] -# 1: Enable the beta API version for testing. The beta API version -# contains breaking changes that require a new API version number. -# They are not ready for public consumption. -# -#------------------------------------------------------------------------------- -# -# 10. Example Settings -# -#-------------------- -# -# Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure -# it meets the business requirements for the organization. -# -# Server -# -# These example configuration settings create these ports: -# -# "peer" -# -# Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic -# or manual outgoing Peer protocol connections. -# -# "rpc" -# -# Administrative RPC commands over HTTPS, when originating from -# the same machine (via the loopback adapter at 127.0.0.1). -# -# "wss_admin" -# -# Admin level API commands over Secure Websockets, when originating -# from the same machine (via the loopback adapter at 127.0.0.1). -# -# This port is commented out but can be enabled by removing -# the '#' from each corresponding line including the entry under [server] -# -# "wss_public" -# -# Guest level API commands over Secure Websockets, open to everyone. -# -# For HTTPS and Secure Websockets ports, if no certificate and key file -# are specified then a self-signed certificate will be generated on startup. -# If you have a certificate and key file, uncomment the corresponding lines -# and ensure the paths to the files are correct. -# -# NOTE -# -# To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. - -[server] -port_rpc_admin_local -port_peer -port_ws_admin_local -port_ws_public -#port_grpc -#ssl_key = /etc/ssl/private/server.key -#ssl_cert = /etc/ssl/certs/server.crt - -[port_rpc_admin_local] -port = 5006 -ip = 127.0.0.1 -admin = 127.0.0.1 -protocol = http - -[port_peer] -port = 51235 -ip = 0.0.0.0 -# alternatively, to accept connections on IPv4 + IPv6, use: -#ip = :: -protocol = peer - -[port_ws_admin_local] -port = 6007 -ip = 127.0.0.1 -admin = 127.0.0.1 -protocol = ws - -#[port_grpc#] -#port = 50051 -#ip = 0.0.0.0 -#secure_gateway = 127.0.0.1 - -[port_ws_public] -port = 6008 -ip = 127.0.0.1 -protocol = ws - -#------------------------------------------------------------------------------- - -# This is primary persistent datastore for rippled. This includes transaction -# metadata, account states, and ledger headers. Helpful information can be -# found at https://xrpl.org/capacity-planning.html#node-db-type -# type=NuDB is recommended for non-validators with fast SSDs. Validators or -# slow / spinning disks should use RocksDB. Caution: Spinning disks are -# not recommended. They do not perform well enough to consistently remain -# synced to the network. -# online_delete=512 is recommended to delete old ledgers while maintaining at -# least 512. -# advisory_delete=0 allows the online delete process to run automatically -# when the node has approximately two times the "online_delete" value of -# ledgers. No external administrative command is required to initiate -# deletion. -[node_db] -type=NuDB -path=/var/lib/rippled-reporting/db/nudb -# online_delete=512 # -advisory_delete=0 - -# This is the persistent datastore for shards. It is important for the health -# of the ripple network that rippled operators shard as much as practical. -# NuDB requires SSD storage. Helpful information can be found at -# https://xrpl.org/history-sharding.html -#[shard_db] -#path=/var/lib/rippled/db/shards/nudb -#max_historical_shards=50 -# -# This optional section can be configured with a list -# of paths to use for storing historical shards. Each -# path must correspond to a unique filesystem. -#[historical_shard_paths] -#/path/1 -#/path/2 - -[database_path] -/var/lib/rippled-reporting/db - -# To use Postgres, uncomment this section and fill in the appropriate connection -# info. Postgres can only be used in Reporting Mode. -# To disable writing to the transaction database, uncomment this section, and -# set use_tx_tables=0 -# [ledger_tx_tables] -# conninfo = postgres://:@localhost/ -# use_tx_tables=1 - - -# This needs to be an absolute directory reference, not a relative one. -# Modify this value as required. -[debug_logfile] -/var/log/rippled-reporting/debug.log - -# To use the XRP test network -# (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), -# use the following [ips] section: -# [ips] -# r.altnet.rippletest.net 51235 - -# File containing trusted validator keys or validator list publishers. -# Unless an absolute path is specified, it will be considered relative to the -# folder in which the rippled.cfg file is located. -[validators_file] -/opt/rippled-reporting/etc/validators.txt - -# Turn down default logging to save disk space in the long run. -# Valid values here are trace, debug, info, warning, error, and fatal -[rpc_startup] -{ "command": "log_level", "severity": "info" } - -# If ssl_verify is 1, certificates will be validated. -# To allow the use of self-signed certificates for development or internal use, -# set to ssl_verify to 0. -[ssl_verify] -1 - - -# To run in Reporting Mode, uncomment this section and fill in the appropriate -# connection info for one or more ETL sources. -[reporting] -etl_source - -[etl_source] -source_grpc_port=50051 -source_ws_port=6005 -source_ip=127.0.0.1 diff --git a/Builds/CMake/CMakeFuncs.cmake b/cmake/CMakeFuncs.cmake similarity index 100% rename from Builds/CMake/CMakeFuncs.cmake rename to cmake/CMakeFuncs.cmake diff --git a/Builds/CMake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake similarity index 100% rename from Builds/CMake/CodeCoverage.cmake rename to cmake/CodeCoverage.cmake diff --git a/Builds/CMake/RippleConfig.cmake b/cmake/RippleConfig.cmake similarity index 100% rename from Builds/CMake/RippleConfig.cmake rename to cmake/RippleConfig.cmake diff --git a/Builds/CMake/RippledCompiler.cmake b/cmake/RippledCompiler.cmake similarity index 100% rename from Builds/CMake/RippledCompiler.cmake rename to cmake/RippledCompiler.cmake diff --git a/cmake/RippledCore.cmake b/cmake/RippledCore.cmake new file mode 100644 index 00000000000..3b850354ed4 --- /dev/null +++ b/cmake/RippledCore.cmake @@ -0,0 +1,140 @@ +#[===================================================================[ + Exported targets. +#]===================================================================] + +include(target_protobuf_sources) + +# Protocol buffers cannot participate in a unity build, +# because all the generated sources +# define a bunch of `static const` variables with the same names, +# so we just build them as a separate library. +add_library(xrpl.libpb) +target_protobuf_sources(xrpl.libpb xrpl/proto + LANGUAGE cpp + IMPORT_DIRS include/xrpl/proto + PROTOS include/xrpl/proto/ripple.proto +) + +file(GLOB_RECURSE protos "include/xrpl/proto/org/*.proto") +target_protobuf_sources(xrpl.libpb xrpl/proto + LANGUAGE cpp + IMPORT_DIRS include/xrpl/proto + PROTOS "${protos}" +) +target_protobuf_sources(xrpl.libpb xrpl/proto + LANGUAGE grpc + IMPORT_DIRS include/xrpl/proto + PROTOS "${protos}" + PLUGIN protoc-gen-grpc=$ + GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc +) + +target_compile_options(xrpl.libpb + PUBLIC + $<$:-wd4996> + $<$: + --system-header-prefix="google/protobuf" + -Wno-deprecated-dynamic-exception-spec + > + PRIVATE + $<$:-wd4065> + $<$>:-Wno-deprecated-declarations> +) + +target_link_libraries(xrpl.libpb + PUBLIC + protobuf::libprotobuf + gRPC::grpc++ +) + +add_library(xrpl.libxrpl) +set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl) +if(unity) + set_target_properties(xrpl.libxrpl PROPERTIES UNITY_BUILD ON) +endif() + +add_library(xrpl::libxrpl ALIAS xrpl.libxrpl) + +file(GLOB_RECURSE sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/libxrpl/*.cpp" +) +target_sources(xrpl.libxrpl PRIVATE ${sources}) + +target_include_directories(xrpl.libxrpl + PUBLIC + $ + $) + +target_compile_definitions(xrpl.libxrpl + PUBLIC + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT + BOOST_CONTAINER_FWD_BAD_DEQUE + HAS_UNCAUGHT_EXCEPTIONS=1) + +target_compile_options(xrpl.libxrpl + PUBLIC + $<$:-Wno-maybe-uninitialized> +) + +target_link_libraries(xrpl.libxrpl + PUBLIC + LibArchive::LibArchive + OpenSSL::Crypto + Ripple::boost + Ripple::opts + Ripple::syslibs + absl::random_random + date::date + ed25519::ed25519 + secp256k1::secp256k1 + xrpl.libpb + xxHash::xxhash +) + +if(xrpld) + add_executable(rippled) + if(unity) + set_target_properties(rippled PROPERTIES UNITY_BUILD ON) + endif() + if(tests) + target_compile_definitions(rippled PUBLIC ENABLE_TESTS) + endif() + target_include_directories(rippled + PRIVATE + $ + ) + + file(GLOB_RECURSE sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/*.cpp" + ) + target_sources(rippled PRIVATE ${sources}) + + if(tests) + file(GLOB_RECURSE sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp" + ) + target_sources(rippled PRIVATE ${sources}) + endif() + + target_link_libraries(rippled + Ripple::boost + Ripple::opts + Ripple::libs + xrpl.libxrpl + ) + exclude_if_included(rippled) + # define a macro for tests that might need to + # be exluded or run differently in CI environment + if(is_ci) + target_compile_definitions(rippled PRIVATE RIPPLED_RUNNING_IN_CI) + endif () + + # any files that don't play well with unity should be added here + if(tests) + set_source_files_properties( + # these two seem to produce conflicts in beast teardown template methods + src/test/rpc/ValidatorRPC_test.cpp + src/test/ledger/Invariants_test.cpp + PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) + endif() +endif() diff --git a/Builds/CMake/RippledCov.cmake b/cmake/RippledCov.cmake similarity index 91% rename from Builds/CMake/RippledCov.cmake rename to cmake/RippledCov.cmake index ce7536e8eeb..3c48bb1c145 100644 --- a/Builds/CMake/RippledCov.cmake +++ b/cmake/RippledCov.cmake @@ -33,6 +33,6 @@ setup_target_for_coverage_gcovr( FORMAT ${coverage_format} EXECUTABLE rippled EXECUTABLE_ARGS --unittest$<$:=${coverage_test}> --unittest-jobs ${coverage_test_parallelism} --quiet --unittest-log - EXCLUDE "src/test" "${CMAKE_BINARY_DIR}/proto_gen" "${CMAKE_BINARY_DIR}/proto_gen_grpc" + EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" DEPENDENCIES rippled ) diff --git a/Builds/CMake/RippledDocs.cmake b/cmake/RippledDocs.cmake similarity index 94% rename from Builds/CMake/RippledDocs.cmake rename to cmake/RippledDocs.cmake index e7c42942a77..d93bc119c0d 100644 --- a/Builds/CMake/RippledDocs.cmake +++ b/cmake/RippledDocs.cmake @@ -21,16 +21,17 @@ set(doxyfile "${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile") file(GLOB_RECURSE doxygen_input docs/*.md - src/ripple/*.h - src/ripple/*.cpp - src/ripple/*.md - src/test/*.h - src/test/*.md - Builds/*/README.md) + include/*.h + include/*.cpp + include/*.md + src/*.h + src/*.cpp + src/*.md + Builds/*.md + *.md) list(APPEND doxygen_input - README.md - RELEASENOTES.md - src/README.md) + external/README.md + ) set(dependencies "${doxygen_input}" "${doxyfile}") function(verbose_find_path variable name) diff --git a/Builds/CMake/RippledInstall.cmake b/cmake/RippledInstall.cmake similarity index 70% rename from Builds/CMake/RippledInstall.cmake rename to cmake/RippledInstall.cmake index b9dd44cfc2c..3199c9a19b8 100644 --- a/Builds/CMake/RippledInstall.cmake +++ b/cmake/RippledInstall.cmake @@ -8,14 +8,26 @@ install ( opts ripple_syslibs ripple_boost - xrpl_core xrpl.libpb + xrpl.libxrpl EXPORT RippleExports LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include) +install( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +if(NOT WIN32) + install( + CODE "file(CREATE_LINK xrpl \ + \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/ripple SYMBOLIC)" + ) +endif() + install (EXPORT RippleExports FILE RippleTargets.cmake NAMESPACE Ripple:: @@ -26,14 +38,9 @@ write_basic_package_version_file ( VERSION ${rippled_version} COMPATIBILITY SameMajorVersion) -if (is_root_project) +if (is_root_project AND TARGET rippled) install (TARGETS rippled RUNTIME DESTINATION bin) set_target_properties(rippled PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON) - install ( - FILES - ${CMAKE_CURRENT_SOURCE_DIR}/Builds/CMake/RippleConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/RippleConfigVersion.cmake - DESTINATION lib/cmake/ripple) # sample configs should not overwrite existing files # install if-not-exists workaround as suggested by # https://cmake.org/Bug/view.php?id=12646 @@ -48,4 +55,16 @@ if (is_root_project) copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/rippled-example.cfg\" etc rippled.cfg) copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt) ") + if(NOT WIN32) + install( + CODE "file(CREATE_LINK rippled${suffix} \ + \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/xrpld${suffix} SYMBOLIC)" + ) + endif() endif () + +install ( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/RippleConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/RippleConfigVersion.cmake + DESTINATION lib/cmake/ripple) diff --git a/Builds/CMake/RippledInterface.cmake b/cmake/RippledInterface.cmake similarity index 100% rename from Builds/CMake/RippledInterface.cmake rename to cmake/RippledInterface.cmake diff --git a/Builds/CMake/RippledSanity.cmake b/cmake/RippledSanity.cmake similarity index 100% rename from Builds/CMake/RippledSanity.cmake rename to cmake/RippledSanity.cmake diff --git a/Builds/CMake/RippledSettings.cmake b/cmake/RippledSettings.cmake similarity index 98% rename from Builds/CMake/RippledSettings.cmake rename to cmake/RippledSettings.cmake index fae09cc5d3f..b81843cd5b5 100644 --- a/Builds/CMake/RippledSettings.cmake +++ b/cmake/RippledSettings.cmake @@ -8,7 +8,7 @@ ProcessorCount(PROCESSOR_COUNT) option(assert "Enables asserts, even in release builds" OFF) -option(reporting "Build rippled with reporting mode enabled" OFF) +option(xrpld "Build xrpld" ON) option(tests "Build tests" ON) diff --git a/Builds/CMake/RippledValidatorKeys.cmake b/cmake/RippledValidatorKeys.cmake similarity index 100% rename from Builds/CMake/RippledValidatorKeys.cmake rename to cmake/RippledValidatorKeys.cmake diff --git a/cmake/RippledVersion.cmake b/cmake/RippledVersion.cmake new file mode 100644 index 00000000000..fda3cb6ff7b --- /dev/null +++ b/cmake/RippledVersion.cmake @@ -0,0 +1,15 @@ +#[===================================================================[ + read version from source +#]===================================================================] + +file(STRINGS src/libxrpl/protocol/BuildInfo.cpp BUILD_INFO) +foreach(line_ ${BUILD_INFO}) + if(line_ MATCHES "versionString[ ]*=[ ]*\"(.+)\"") + set(rippled_version ${CMAKE_MATCH_1}) + endif() +endforeach() +if(rippled_version) + message(STATUS "rippled version: ${rippled_version}") +else() + message(FATAL_ERROR "unable to determine rippled version") +endif() diff --git a/Builds/CMake/deps/Boost.cmake b/cmake/deps/Boost.cmake similarity index 100% rename from Builds/CMake/deps/Boost.cmake rename to cmake/deps/Boost.cmake diff --git a/Builds/CMake/target_protobuf_sources.cmake b/cmake/target_protobuf_sources.cmake similarity index 100% rename from Builds/CMake/target_protobuf_sources.cmake rename to cmake/target_protobuf_sources.cmake diff --git a/conanfile.py b/conanfile.py index 10eeb6e3ca7..14fc49a1946 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,28 +15,23 @@ class Xrpl(ConanFile): 'coverage': [True, False], 'fPIC': [True, False], 'jemalloc': [True, False], - 'reporting': [True, False], 'rocksdb': [True, False], 'shared': [True, False], 'static': [True, False], 'tests': [True, False], 'unity': [True, False], + 'xrpld': [True, False], } requires = [ - 'boost/1.82.0', 'date/3.0.1', 'grpc/1.50.1', 'libarchive/3.6.2', - 'lz4/1.9.3', 'nudb/2.0.8', 'openssl/1.1.1u', - 'protobuf/3.21.9', - 'snappy/1.1.10', 'soci/4.0.3', - 'sqlite3/3.42.0', - 'zlib/1.2.13', 'xxhash/0.8.2', + 'zlib/1.2.13', ] tool_requires = [ @@ -48,54 +43,51 @@ class Xrpl(ConanFile): 'coverage': False, 'fPIC': True, 'jemalloc': False, - 'reporting': False, 'rocksdb': True, 'shared': False, 'static': True, - 'tests': True, + 'tests': False, 'unity': False, + 'xrpld': False, - 'cassandra-cpp-driver:shared': False, - 'cassandra-cpp-driver:use_atomic': None, - 'date:header_only': True, - 'grpc:shared': False, - 'grpc:secure': True, - 'libarchive:shared': False, - 'libarchive:with_acl': False, - 'libarchive:with_bzip2': False, - 'libarchive:with_cng': False, - 'libarchive:with_expat': False, - 'libarchive:with_iconv': False, - 'libarchive:with_libxml2': False, - 'libarchive:with_lz4': True, - 'libarchive:with_lzma': False, - 'libarchive:with_lzo': False, - 'libarchive:with_nettle': False, - 'libarchive:with_openssl': False, - 'libarchive:with_pcreposix': False, - 'libarchive:with_xattr': False, - 'libarchive:with_zlib': False, - 'libpq:shared': False, - 'lz4:shared': False, - 'openssl:shared': False, - 'protobuf:shared': False, - 'protobuf:with_zlib': True, - 'rocksdb:enable_sse': False, - 'rocksdb:lite': False, - 'rocksdb:shared': False, - 'rocksdb:use_rtti': True, - 'rocksdb:with_jemalloc': False, - 'rocksdb:with_lz4': True, - 'rocksdb:with_snappy': True, - 'snappy:shared': False, - 'soci:shared': False, - 'soci:with_sqlite3': True, - 'soci:with_boost': True, - 'xxhash:shared': False, + 'date/*:header_only': True, + 'grpc/*:shared': False, + 'grpc/*:secure': True, + 'libarchive/*:shared': False, + 'libarchive/*:with_acl': False, + 'libarchive/*:with_bzip2': False, + 'libarchive/*:with_cng': False, + 'libarchive/*:with_expat': False, + 'libarchive/*:with_iconv': False, + 'libarchive/*:with_libxml2': False, + 'libarchive/*:with_lz4': True, + 'libarchive/*:with_lzma': False, + 'libarchive/*:with_lzo': False, + 'libarchive/*:with_nettle': False, + 'libarchive/*:with_openssl': False, + 'libarchive/*:with_pcreposix': False, + 'libarchive/*:with_xattr': False, + 'libarchive/*:with_zlib': False, + 'lz4/*:shared': False, + 'openssl/*:shared': False, + 'protobuf/*:shared': False, + 'protobuf/*:with_zlib': True, + 'rocksdb/*:enable_sse': False, + 'rocksdb/*:lite': False, + 'rocksdb/*:shared': False, + 'rocksdb/*:use_rtti': True, + 'rocksdb/*:with_jemalloc': False, + 'rocksdb/*:with_lz4': True, + 'rocksdb/*:with_snappy': True, + 'snappy/*:shared': False, + 'soci/*:shared': False, + 'soci/*:with_sqlite3': True, + 'soci/*:with_boost': True, + 'xxhash/*:shared': False, } def set_version(self): - path = f'{self.recipe_folder}/src/ripple/protocol/impl/BuildInfo.cpp' + path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp' regex = r'versionString\s?=\s?\"(.*)\"' with open(path, 'r') as file: matches = (re.search(regex, line) for line in file) @@ -107,16 +99,23 @@ def configure(self): self.options['boost'].visibility = 'global' def requirements(self): + self.requires('boost/1.82.0', force=True) + self.requires('lz4/1.9.3', force=True) + self.requires('protobuf/3.21.9', force=True) + self.requires('sqlite3/3.42.0', force=True) if self.options.jemalloc: self.requires('jemalloc/5.3.0') - if self.options.reporting: - self.requires('cassandra-cpp-driver/2.15.3') - self.requires('libpq/14.7') if self.options.rocksdb: self.requires('rocksdb/6.29.5') exports_sources = ( - 'CMakeLists.txt', 'Builds/*', 'bin/getRippledInfo', 'src/*', 'cfg/*', 'external/*' + 'CMakeLists.txt', + 'bin/getRippledInfo', + 'cfg/*', + 'cmake/*', + 'external/*', + 'include/*', + 'src/*', ) def layout(self): @@ -132,11 +131,11 @@ def generate(self): tc.variables['assert'] = self.options.assertions tc.variables['coverage'] = self.options.coverage tc.variables['jemalloc'] = self.options.jemalloc - tc.variables['reporting'] = self.options.reporting tc.variables['rocksdb'] = self.options.rocksdb tc.variables['BUILD_SHARED_LIBS'] = self.options.shared tc.variables['static'] = self.options.static tc.variables['unity'] = self.options.unity + tc.variables['xrpld'] = self.options.xrpld tc.generate() def build(self): @@ -153,7 +152,7 @@ def package(self): def package_info(self): libxrpl = self.cpp_info.components['libxrpl'] libxrpl.libs = [ - 'xrpl_core', + 'xrpl', 'xrpl.libpb', 'ed25519', 'secp256k1', @@ -163,8 +162,17 @@ def package_info(self): libxrpl.includedirs = ['include', 'include/ripple/proto'] libxrpl.requires = [ 'boost::boost', - 'openssl::crypto', 'date::date', 'grpc::grpc++', + 'libarchive::libarchive', + 'lz4::lz4', + 'nudb::nudb', + 'openssl::crypto', + 'protobuf::libprotobuf', + 'soci::soci', + 'sqlite3::sqlite', 'xxhash::xxhash', + 'zlib::zlib', ] + if self.options.rocksdb: + libxrpl.requires.append('rocksdb::librocksdb') diff --git a/docs/Doxyfile b/docs/Doxyfile index 48a0b5d1e1a..750ae0fb649 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -103,18 +103,17 @@ WARN_LOGFILE = # Configuration options related to the input files #--------------------------------------------------------------------------- INPUT = \ - docs \ - src/ripple \ - src/test \ - src/README.md \ - README.md \ - RELEASENOTES.md \ + . \ INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.h *.cpp *.md RECURSIVE = YES -EXCLUDE = +EXCLUDE = \ + .github \ + external/ed25519-donna \ + external/secp256k1 \ + EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXCLUDE_SYMBOLS = @@ -130,7 +129,7 @@ INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = -USE_MDFILE_AS_MAINPAGE = src/README.md +USE_MDFILE_AS_MAINPAGE = ./README.md #--------------------------------------------------------------------------- # Configuration options related to source browsing diff --git a/docs/build/environment.md b/docs/build/environment.md index a204cd2c197..7fe89ffb49f 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -23,7 +23,7 @@ direction. ``` apt update -apt install --yes curl git libssl-dev python3.10-dev python3-pip make g++-11 +apt install --yes curl git libssl-dev python3.10-dev python3-pip make g++-11 libprotobuf-dev protobuf-compiler curl --location --remote-name \ "https://github.com/Kitware/CMake/releases/download/v3.25.1/cmake-3.25.1.tar.gz" diff --git a/examples/example/CMakeLists.txt b/examples/example/CMakeLists.txt new file mode 100644 index 00000000000..83aa24880d1 --- /dev/null +++ b/examples/example/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.21) + +set(name example) +set(version 0.1.0) + +project( + ${name} + VERSION ${version} + LANGUAGES CXX +) + +find_package(xrpl REQUIRED) + +add_executable(example) +target_sources(example PRIVATE src/example.cpp) +target_link_libraries(example PRIVATE xrpl::libxrpl) diff --git a/examples/example/conanfile.py b/examples/example/conanfile.py new file mode 100644 index 00000000000..be3750bf9e9 --- /dev/null +++ b/examples/example/conanfile.py @@ -0,0 +1,59 @@ +from conan import ConanFile, conan_version +from conan.tools.cmake import CMake, cmake_layout + +class Example(ConanFile): + + def set_name(self): + if self.name is None: + self.name = 'example' + + def set_version(self): + if self.version is None: + self.version = '0.1.0' + + license = 'ISC' + author = 'John Freeman ' + + settings = 'os', 'compiler', 'build_type', 'arch' + options = {'shared': [True, False], 'fPIC': [True, False]} + default_options = { + 'shared': False, + 'fPIC': True, + 'xrpl:xrpld': False, + } + + requires = ['xrpl/2.2.0-rc1@jfreeman/nodestore'] + generators = ['CMakeDeps', 'CMakeToolchain'] + + exports_sources = [ + 'CMakeLists.txt', + 'cmake/*', + 'external/*', + 'include/*', + 'src/*', + ] + + # For out-of-source build. + # https://docs.conan.io/en/latest/reference/build_helpers/cmake.html#configure + no_copy_source = True + + def layout(self): + cmake_layout(self) + + def config_options(self): + if self.settings.os == 'Windows': + del self.options.fPIC + + def build(self): + cmake = CMake(self) + cmake.configure(variables={'BUILD_TESTING': 'NO'}) + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + path = f'{self.package_folder}/share/{self.name}/cpp_info.py' + with open(path, 'r') as file: + exec(file.read(), {}, {'self': self.cpp_info}) diff --git a/examples/example/src/example.cpp b/examples/example/src/example.cpp new file mode 100644 index 00000000000..7ff07f6ea4d --- /dev/null +++ b/examples/example/src/example.cpp @@ -0,0 +1,8 @@ +#include + +#include + +int main(int argc, char const** argv) { + std::printf("%s\n", ripple::BuildInfo::getVersionString().c_str()); + return 0; +} diff --git a/external/README.md b/external/README.md index f45f80965a5..25ae577ba58 100644 --- a/external/README.md +++ b/external/README.md @@ -1,3 +1,5 @@ +# External Conan recipes + The subdirectories in this directory contain either copies or Conan recipes of external libraries used by rippled. The Conan recipes include patches we have not yet pushed upstream. diff --git a/external/nudb/conandata.yml b/external/nudb/conandata.yml new file mode 100644 index 00000000000..721129f88e7 --- /dev/null +++ b/external/nudb/conandata.yml @@ -0,0 +1,10 @@ +sources: + "2.0.8": + url: "https://github.com/CPPAlliance/NuDB/archive/2.0.8.tar.gz" + sha256: "9b71903d8ba111cd893ab064b9a8b6ac4124ed8bd6b4f67250205bc43c7f13a8" +patches: + "2.0.8": + - patch_file: "patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch" + patch_description: "Fix build for MSVC by including stdexcept" + patch_type: "portability" + patch_source: "https://github.com/cppalliance/NuDB/pull/100/files" diff --git a/external/nudb/conanfile.py b/external/nudb/conanfile.py new file mode 100644 index 00000000000..a046e2ba898 --- /dev/null +++ b/external/nudb/conanfile.py @@ -0,0 +1,72 @@ +import os + +from conan import ConanFile +from conan.tools.build import check_min_cppstd +from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get +from conan.tools.layout import basic_layout + +required_conan_version = ">=1.52.0" + + +class NudbConan(ConanFile): + name = "nudb" + description = "A fast key/value insert-only database for SSD drives in C++11" + license = "BSL-1.0" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://github.com/CPPAlliance/NuDB" + topics = ("header-only", "KVS", "insert-only") + + package_type = "header-library" + settings = "os", "arch", "compiler", "build_type" + no_copy_source = True + + @property + def _min_cppstd(self): + return 11 + + def export_sources(self): + export_conandata_patches(self) + + def layout(self): + basic_layout(self, src_folder="src") + + def requirements(self): + self.requires("boost/1.83.0") + + def package_id(self): + self.info.clear() + + def validate(self): + if self.settings.compiler.cppstd: + check_min_cppstd(self, self._min_cppstd) + + def source(self): + get(self, **self.conan_data["sources"][self.version], strip_root=True) + + def build(self): + apply_conandata_patches(self) + + def package(self): + copy(self, "LICENSE*", + dst=os.path.join(self.package_folder, "licenses"), + src=self.source_folder) + copy(self, "*", + dst=os.path.join(self.package_folder, "include"), + src=os.path.join(self.source_folder, "include")) + + def package_info(self): + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] + + self.cpp_info.set_property("cmake_target_name", "NuDB") + self.cpp_info.set_property("cmake_target_aliases", ["NuDB::nudb"]) + self.cpp_info.set_property("cmake_find_mode", "both") + + self.cpp_info.components["core"].set_property("cmake_target_name", "nudb") + self.cpp_info.components["core"].names["cmake_find_package"] = "nudb" + self.cpp_info.components["core"].names["cmake_find_package_multi"] = "nudb" + self.cpp_info.components["core"].requires = ["boost::thread", "boost::system"] + + # TODO: to remove in conan v2 once cmake_find_package_* generators removed + self.cpp_info.names["cmake_find_package"] = "NuDB" + self.cpp_info.names["cmake_find_package_multi"] = "NuDB" diff --git a/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch b/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch new file mode 100644 index 00000000000..2d5264f3ce4 --- /dev/null +++ b/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch @@ -0,0 +1,24 @@ +diff --git a/include/nudb/detail/stream.hpp b/include/nudb/detail/stream.hpp +index 6c07bf1..e0ce8ed 100644 +--- a/include/nudb/detail/stream.hpp ++++ b/include/nudb/detail/stream.hpp +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + namespace nudb { + namespace detail { +diff --git a/include/nudb/impl/context.ipp b/include/nudb/impl/context.ipp +index beb7058..ffde0b3 100644 +--- a/include/nudb/impl/context.ipp ++++ b/include/nudb/impl/context.ipp +@@ -9,6 +9,7 @@ + #define NUDB_IMPL_CONTEXT_IPP + + #include ++#include + + namespace nudb { + diff --git a/external/secp256k1/src/secp256k1.c b/external/secp256k1/src/secp256k1.c index bdbd97cc408..a95992c5dd2 100644 --- a/external/secp256k1/src/secp256k1.c +++ b/external/secp256k1/src/secp256k1.c @@ -526,7 +526,7 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc break; } is_nonce_valid = secp256k1_scalar_set_b32_seckey(&non, nonce32); - /* The nonce is still secret here, but it being invalid is is less likely than 1:2^255. */ + /* The nonce is still secret here, but it being invalid is less likely than 1:2^255. */ secp256k1_declassify(ctx, &is_nonce_valid, sizeof(is_nonce_valid)); if (is_nonce_valid) { ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, r, s, &sec, &msg, &non, recid); diff --git a/src/ripple/basics/Archive.h b/include/xrpl/basics/Archive.h similarity index 100% rename from src/ripple/basics/Archive.h rename to include/xrpl/basics/Archive.h diff --git a/src/ripple/basics/BasicConfig.h b/include/xrpl/basics/BasicConfig.h similarity index 99% rename from src/ripple/basics/BasicConfig.h rename to include/xrpl/basics/BasicConfig.h index db293979f13..8f522cdd2f2 100644 --- a/src/ripple/basics/BasicConfig.h +++ b/include/xrpl/basics/BasicConfig.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_BASICCONFIG_H_INCLUDED #define RIPPLE_BASICS_BASICCONFIG_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/Blob.h b/include/xrpl/basics/Blob.h similarity index 100% rename from src/ripple/basics/Blob.h rename to include/xrpl/basics/Blob.h diff --git a/src/ripple/basics/Buffer.h b/include/xrpl/basics/Buffer.h similarity index 99% rename from src/ripple/basics/Buffer.h rename to include/xrpl/basics/Buffer.h index 706b8b627f1..25ae8ca315c 100644 --- a/src/ripple/basics/Buffer.h +++ b/include/xrpl/basics/Buffer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_BUFFER_H_INCLUDED #define RIPPLE_BASICS_BUFFER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/ByteUtilities.h b/include/xrpl/basics/ByteUtilities.h similarity index 100% rename from src/ripple/basics/ByteUtilities.h rename to include/xrpl/basics/ByteUtilities.h diff --git a/src/ripple/basics/CompressionAlgorithms.h b/include/xrpl/basics/CompressionAlgorithms.h similarity index 99% rename from src/ripple/basics/CompressionAlgorithms.h rename to include/xrpl/basics/CompressionAlgorithms.h index eef2608916d..4defd5a4904 100644 --- a/src/ripple/basics/CompressionAlgorithms.h +++ b/include/xrpl/basics/CompressionAlgorithms.h @@ -20,7 +20,7 @@ #ifndef RIPPLED_COMPRESSIONALGORITHMS_H_INCLUDED #define RIPPLED_COMPRESSIONALGORITHMS_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/CountedObject.h b/include/xrpl/basics/CountedObject.h similarity index 99% rename from src/ripple/basics/CountedObject.h rename to include/xrpl/basics/CountedObject.h index 690841fe1ef..9ea76aa3bd0 100644 --- a/src/ripple/basics/CountedObject.h +++ b/include/xrpl/basics/CountedObject.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED #define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/DecayingSample.h b/include/xrpl/basics/DecayingSample.h similarity index 100% rename from src/ripple/basics/DecayingSample.h rename to include/xrpl/basics/DecayingSample.h diff --git a/src/ripple/basics/Expected.h b/include/xrpl/basics/Expected.h similarity index 83% rename from src/ripple/basics/Expected.h rename to include/xrpl/basics/Expected.h index bb699579b17..10f188af11c 100644 --- a/src/ripple/basics/Expected.h +++ b/include/xrpl/basics/Expected.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_EXPECTED_H_INCLUDED #define RIPPLE_BASICS_EXPECTED_H_INCLUDED -#include +#include #include @@ -136,44 +136,49 @@ class [[nodiscard]] Expected public: template - requires std::convertible_to constexpr Expected(U && r) - : Base(T(std::forward(r))) + requires std::convertible_to + constexpr Expected(U&& r) : Base(T(std::forward(r))) { } template - requires std::convertible_to && - (!std::is_reference_v)constexpr Expected(Unexpected e) - : Base(E(std::move(e.value()))) + requires std::convertible_to && (!std::is_reference_v) + constexpr Expected(Unexpected e) : Base(E(std::move(e.value()))) { } - constexpr bool has_value() const + constexpr bool + has_value() const { return Base::has_value(); } - constexpr T const& value() const + constexpr T const& + value() const { return Base::value(); } - constexpr T& value() + constexpr T& + value() { return Base::value(); } - constexpr E const& error() const + constexpr E const& + error() const { return Base::error(); } - constexpr E& error() + constexpr E& + error() { return Base::error(); } - constexpr explicit operator bool() const + constexpr explicit + operator bool() const { return has_value(); } @@ -181,22 +186,26 @@ class [[nodiscard]] Expected // Add operator* and operator-> so the Expected API looks a bit more like // what std::expected is likely to look like. See: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html - [[nodiscard]] constexpr T& operator*() + [[nodiscard]] constexpr T& + operator*() { return this->value(); } - [[nodiscard]] constexpr T const& operator*() const + [[nodiscard]] constexpr T const& + operator*() const { return this->value(); } - [[nodiscard]] constexpr T* operator->() + [[nodiscard]] constexpr T* + operator->() { return &this->value(); } - [[nodiscard]] constexpr T const* operator->() const + [[nodiscard]] constexpr T const* + operator->() const { return &this->value(); } @@ -218,23 +227,25 @@ class [[nodiscard]] Expected } template - requires std::convertible_to && - (!std::is_reference_v)constexpr Expected(Unexpected e) - : Base(E(std::move(e.value()))) + requires std::convertible_to && (!std::is_reference_v) + constexpr Expected(Unexpected e) : Base(E(std::move(e.value()))) { } - constexpr E const& error() const + constexpr E const& + error() const { return Base::error(); } - constexpr E& error() + constexpr E& + error() { return Base::error(); } - constexpr explicit operator bool() const + constexpr explicit + operator bool() const { return Base::has_value(); } diff --git a/src/ripple/basics/FeeUnits.h b/include/xrpl/basics/FeeUnits.h similarity index 95% rename from src/ripple/basics/FeeUnits.h rename to include/xrpl/basics/FeeUnits.h index 35e0ff24d05..c63a169f00e 100644 --- a/src/ripple/basics/FeeUnits.h +++ b/include/xrpl/basics/FeeUnits.h @@ -19,7 +19,7 @@ #ifndef BASICS_FEES_H_INCLUDED #define BASICS_FEES_H_INCLUDED -#include +#include #include #include #include @@ -84,13 +84,13 @@ class TaggedFee : private boost::totally_ordered>, protected: template static constexpr bool is_compatible_v = - std::is_arithmetic_v&& std::is_arithmetic_v&& - std::is_convertible_v; + std::is_arithmetic_v && std::is_arithmetic_v && + std::is_convertible_v; template > static constexpr bool is_compatiblefee_v = - is_compatible_v&& - std::is_same_v; + is_compatible_v && + std::is_same_v; template using enable_if_compatible_t = @@ -110,7 +110,8 @@ class TaggedFee : private boost::totally_ordered>, { } - constexpr TaggedFee& operator=(beast::Zero) + constexpr TaggedFee& + operator=(beast::Zero) { fee_ = 0; return *this; @@ -250,7 +251,8 @@ class TaggedFee : private boost::totally_ordered>, } /** Returns true if the amount is not zero */ - explicit constexpr operator bool() const noexcept + explicit constexpr + operator bool() const noexcept { return fee_ != 0; } @@ -344,8 +346,8 @@ constexpr bool can_muldiv_source_v = template > constexpr bool can_muldiv_dest_v = - can_muldiv_source_v&& // Dest is also a source - std::is_convertible_v && + can_muldiv_source_v && // Dest is also a source + std::is_convertible_v && sizeof(typename Dest::value_type) >= sizeof(std::uint64_t); template < @@ -354,8 +356,8 @@ template < class = enable_if_unit_t, class = enable_if_unit_t> constexpr bool can_muldiv_sources_v = - can_muldiv_source_v&& can_muldiv_source_v&& std:: - is_same_v; + can_muldiv_source_v && can_muldiv_source_v && + std::is_same_v; template < class Source1, @@ -365,7 +367,7 @@ template < class = enable_if_unit_t, class = enable_if_unit_t> constexpr bool can_muldiv_v = - can_muldiv_sources_v&& can_muldiv_dest_v; + can_muldiv_sources_v && can_muldiv_dest_v; // Source and Dest can be the same by default template < diff --git a/src/ripple/basics/FileUtilities.h b/include/xrpl/basics/FileUtilities.h similarity index 100% rename from src/ripple/basics/FileUtilities.h rename to include/xrpl/basics/FileUtilities.h diff --git a/src/ripple/basics/IOUAmount.h b/include/xrpl/basics/IOUAmount.h similarity index 96% rename from src/ripple/basics/IOUAmount.h rename to include/xrpl/basics/IOUAmount.h index 2380a7d15e1..e89feb123d0 100644 --- a/src/ripple/basics/IOUAmount.h +++ b/include/xrpl/basics/IOUAmount.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_BASICS_IOUAMOUNT_H_INCLUDED #define RIPPLE_BASICS_IOUAMOUNT_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include @@ -82,7 +82,8 @@ class IOUAmount : private boost::totally_ordered, operator<(IOUAmount const& other) const; /** Returns true if the amount is not zero */ - explicit operator bool() const noexcept; + explicit + operator bool() const noexcept; /** Return the sign of the amount */ int @@ -109,7 +110,8 @@ inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent) normalize(); } -inline IOUAmount& IOUAmount::operator=(beast::Zero) +inline IOUAmount& +IOUAmount::operator=(beast::Zero) { // The -100 is used to allow 0 to sort less than small positive values // which will have a large negative exponent. diff --git a/src/ripple/basics/KeyCache.h b/include/xrpl/basics/KeyCache.h similarity index 94% rename from src/ripple/basics/KeyCache.h rename to include/xrpl/basics/KeyCache.h index d8fa4910ab9..1439a8b3344 100644 --- a/src/ripple/basics/KeyCache.h +++ b/include/xrpl/basics/KeyCache.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_KEYCACHE_H #define RIPPLE_BASICS_KEYCACHE_H -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/basics/LocalValue.h b/include/xrpl/basics/LocalValue.h similarity index 100% rename from src/ripple/basics/LocalValue.h rename to include/xrpl/basics/LocalValue.h diff --git a/src/ripple/basics/Log.h b/include/xrpl/basics/Log.h similarity index 98% rename from src/ripple/basics/Log.h rename to include/xrpl/basics/Log.h index 929225c0433..0117f2f9aab 100644 --- a/src/ripple/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_LOG_H_INCLUDED #define RIPPLE_BASICS_LOG_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/include/xrpl/basics/MPTAmount.h b/include/xrpl/basics/MPTAmount.h new file mode 100644 index 00000000000..34f747a21be --- /dev/null +++ b/include/xrpl/basics/MPTAmount.h @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_MPTAMOUNT_H_INCLUDED +#define RIPPLE_BASICS_MPTAMOUNT_H_INCLUDED + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace ripple { + +class MPTAmount : private boost::totally_ordered, + private boost::additive, + private boost::equality_comparable, + private boost::additive +{ +public: + using value_type = std::int64_t; + +protected: + value_type value_; + +public: + MPTAmount() = default; + constexpr MPTAmount(MPTAmount const& other) = default; + constexpr MPTAmount& + operator=(MPTAmount const& other) = default; + + constexpr explicit MPTAmount(value_type value); + + constexpr MPTAmount& operator=(beast::Zero); + + MPTAmount& + operator+=(MPTAmount const& other); + + MPTAmount& + operator-=(MPTAmount const& other); + + MPTAmount + operator-() const; + + bool + operator==(MPTAmount const& other) const; + + bool + operator==(value_type other) const; + + bool + operator<(MPTAmount const& other) const; + + /** Returns true if the amount is not zero */ + explicit constexpr + operator bool() const noexcept; + + /** Return the sign of the amount */ + constexpr int + signum() const noexcept; + + /** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. + */ + constexpr value_type + value() const; + + static MPTAmount + minPositiveAmount(); +}; + +constexpr MPTAmount::MPTAmount(value_type value) : value_(value) +{ +} + +constexpr MPTAmount& +MPTAmount::operator=(beast::Zero) +{ + value_ = 0; + return *this; +} + +/** Returns true if the amount is not zero */ +constexpr MPTAmount::operator bool() const noexcept +{ + return value_ != 0; +} + +/** Return the sign of the amount */ +constexpr int +MPTAmount::signum() const noexcept +{ + return (value_ < 0) ? -1 : (value_ ? 1 : 0); +} + +/** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. +*/ +constexpr MPTAmount::value_type +MPTAmount::value() const +{ + return value_; +} + +inline std::string +to_string(MPTAmount const& amount) +{ + return std::to_string(amount.value()); +} + +inline MPTAmount +mulRatio( + MPTAmount const& amt, + std::uint32_t num, + std::uint32_t den, + bool roundUp) +{ + using namespace boost::multiprecision; + + if (!den) + Throw("division by zero"); + + int128_t const amt128(amt.value()); + auto const neg = amt.value() < 0; + auto const m = amt128 * num; + auto r = m / den; + if (m % den) + { + if (!neg && roundUp) + r += 1; + if (neg && !roundUp) + r -= 1; + } + if (r > std::numeric_limits::max()) + Throw("MPT mulRatio overflow"); + return MPTAmount(r.convert_to()); +} + +} // namespace ripple + +#endif // RIPPLE_BASICS_MPTAMOUNT_H_INCLUDED diff --git a/src/ripple/basics/MathUtilities.h b/include/xrpl/basics/MathUtilities.h similarity index 100% rename from src/ripple/basics/MathUtilities.h rename to include/xrpl/basics/MathUtilities.h diff --git a/src/ripple/basics/Number.h b/include/xrpl/basics/Number.h similarity index 92% rename from src/ripple/basics/Number.h rename to include/xrpl/basics/Number.h index cdc25b3b27d..70baf5d1e47 100644 --- a/src/ripple/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -20,7 +20,8 @@ #ifndef RIPPLE_BASICS_NUMBER_H_INCLUDED #define RIPPLE_BASICS_NUMBER_H_INCLUDED -#include +#include +#include #include #include #include @@ -40,6 +41,14 @@ class Number int exponent_{std::numeric_limits::lowest()}; public: + // The range for the mantissa when normalized + constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; + constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL; + + // The range for the exponent when normalized + constexpr static int minExponent = -32768; + constexpr static int maxExponent = 32768; + struct unchecked { explicit unchecked() = default; @@ -52,6 +61,7 @@ class Number explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept; Number(XRPAmount const& x); + Number(MPTAmount const& x); constexpr rep mantissa() const noexcept; @@ -88,8 +98,17 @@ class Number static constexpr Number lowest() noexcept; - explicit operator XRPAmount() const; // round to nearest, even on tie - explicit operator rep() const; // round to nearest, even on tie + /** Conversions to Number are implicit and conversions away from Number + * are explicit. This design encourages and facilitates the use of Number + * as the preferred type for floating point arithmetic as it makes + * "mixed mode" more convenient, e.g. MPTAmount + Number. + */ + explicit + operator XRPAmount() const; // round to nearest, even on tie + explicit + operator MPTAmount() const; // round to nearest, even on tie + explicit + operator rep() const; // round to nearest, even on tie friend constexpr bool operator==(Number const& x, Number const& y) noexcept @@ -180,14 +199,6 @@ class Number constexpr bool isnormal() const noexcept; - // The range for the mantissa when normalized - constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; - constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL; - - // The range for the exponent when normalized - constexpr static int minExponent = -32768; - constexpr static int maxExponent = 32768; - class Guard; }; @@ -210,6 +221,10 @@ inline Number::Number(XRPAmount const& x) : Number{x.drops()} { } +inline Number::Number(MPTAmount const& x) : Number{x.value()} +{ +} + inline constexpr Number::rep Number::mantissa() const noexcept { diff --git a/src/ripple/basics/README.md b/include/xrpl/basics/README.md similarity index 100% rename from src/ripple/basics/README.md rename to include/xrpl/basics/README.md diff --git a/src/ripple/basics/RangeSet.h b/include/xrpl/basics/RangeSet.h similarity index 99% rename from src/ripple/basics/RangeSet.h rename to include/xrpl/basics/RangeSet.h index 3a9e470ddfb..0ffed2db1e2 100644 --- a/src/ripple/basics/RangeSet.h +++ b/include/xrpl/basics/RangeSet.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_RANGESET_H_INCLUDED #define RIPPLE_BASICS_RANGESET_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/basics/Resolver.h b/include/xrpl/basics/Resolver.h similarity index 98% rename from src/ripple/basics/Resolver.h rename to include/xrpl/basics/Resolver.h index abf04005707..6cba352e0a0 100644 --- a/src/ripple/basics/Resolver.h +++ b/include/xrpl/basics/Resolver.h @@ -23,7 +23,7 @@ #include #include -#include +#include namespace ripple { diff --git a/src/ripple/basics/ResolverAsio.h b/include/xrpl/basics/ResolverAsio.h similarity index 94% rename from src/ripple/basics/ResolverAsio.h rename to include/xrpl/basics/ResolverAsio.h index 191cdc097ad..51fcbdfb0d8 100644 --- a/src/ripple/basics/ResolverAsio.h +++ b/include/xrpl/basics/ResolverAsio.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_RESOLVERASIO_H_INCLUDED #define RIPPLE_BASICS_RESOLVERASIO_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/basics/SHAMapHash.h b/include/xrpl/basics/SHAMapHash.h similarity index 98% rename from src/ripple/basics/SHAMapHash.h rename to include/xrpl/basics/SHAMapHash.h index 796510ba182..7e93ead78d3 100644 --- a/src/ripple/basics/SHAMapHash.h +++ b/include/xrpl/basics/SHAMapHash.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_SHAMAP_HASH_H_INCLUDED #define RIPPLE_BASICS_SHAMAP_HASH_H_INCLUDED -#include +#include #include diff --git a/src/ripple/basics/SlabAllocator.h b/include/xrpl/basics/SlabAllocator.h similarity index 99% rename from src/ripple/basics/SlabAllocator.h rename to include/xrpl/basics/SlabAllocator.h index ece96d0b873..5c4cba343cf 100644 --- a/src/ripple/basics/SlabAllocator.h +++ b/include/xrpl/basics/SlabAllocator.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED #define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/basics/Slice.h b/include/xrpl/basics/Slice.h similarity index 98% rename from src/ripple/basics/Slice.h rename to include/xrpl/basics/Slice.h index 0ba6a94b62b..00126f8882d 100644 --- a/src/ripple/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_SLICE_H_INCLUDED #define RIPPLE_BASICS_SLICE_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h similarity index 95% rename from src/ripple/basics/StringUtilities.h rename to include/xrpl/basics/StringUtilities.h index 8af81a37403..23d60e2db49 100644 --- a/src/ripple/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_STRINGUTILITIES_H_INCLUDED #define RIPPLE_BASICS_STRINGUTILITIES_H_INCLUDED -#include -#include +#include +#include #include #include @@ -110,7 +110,7 @@ strUnHex(std::string const& strSrc) } inline std::optional -strViewUnHex(boost::string_view const& strSrc) +strViewUnHex(std::string_view strSrc) { return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend()); } @@ -150,7 +150,7 @@ to_uint64(std::string const& s); doesn't check whether the TLD is valid. */ bool -isProperlyFormedTomlDomain(std::string const& domain); +isProperlyFormedTomlDomain(std::string_view domain); } // namespace ripple diff --git a/src/ripple/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h similarity index 99% rename from src/ripple/basics/TaggedCache.h rename to include/xrpl/basics/TaggedCache.h index 6765ff16bee..1fcdc3707b6 100644 --- a/src/ripple/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED #define RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/basics/ToString.h b/include/xrpl/basics/ToString.h similarity index 100% rename from src/ripple/basics/ToString.h rename to include/xrpl/basics/ToString.h diff --git a/src/ripple/basics/UnorderedContainers.h b/include/xrpl/basics/UnorderedContainers.h similarity index 94% rename from src/ripple/basics/UnorderedContainers.h rename to include/xrpl/basics/UnorderedContainers.h index e929ebec898..b689a1b6ac8 100644 --- a/src/ripple/basics/UnorderedContainers.h +++ b/include/xrpl/basics/UnorderedContainers.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_BASICS_UNORDEREDCONTAINERS_H_INCLUDED #define RIPPLE_BASICS_UNORDEREDCONTAINERS_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/basics/UptimeClock.h b/include/xrpl/basics/UptimeClock.h similarity index 100% rename from src/ripple/basics/UptimeClock.h rename to include/xrpl/basics/UptimeClock.h diff --git a/src/ripple/basics/XRPAmount.h b/include/xrpl/basics/XRPAmount.h similarity index 93% rename from src/ripple/basics/XRPAmount.h rename to include/xrpl/basics/XRPAmount.h index 08f82b1752e..30b194845c9 100644 --- a/src/ripple/basics/XRPAmount.h +++ b/include/xrpl/basics/XRPAmount.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_BASICS_XRPAMOUNT_H_INCLUDED #define RIPPLE_BASICS_XRPAMOUNT_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -65,7 +65,8 @@ class XRPAmount : private boost::totally_ordered, { } - constexpr XRPAmount& operator=(beast::Zero) + constexpr XRPAmount& + operator=(beast::Zero) { drops_ = 0; return *this; @@ -155,7 +156,8 @@ class XRPAmount : private boost::totally_ordered, } /** Returns true if the amount is not zero */ - explicit constexpr operator bool() const noexcept + explicit constexpr + operator bool() const noexcept { return drops_ != 0; } @@ -205,6 +207,10 @@ class XRPAmount : private boost::totally_ordered, return dropsAs().value_or(defaultValue.drops()); } + /* Clips a 64-bit value to a 32-bit JSON number. It is only used + * in contexts that don't expect the value to ever approach + * the 32-bit limits (i.e. fees and reserves). + */ Json::Value jsonClipped() const { diff --git a/src/ripple/basics/algorithm.h b/include/xrpl/basics/algorithm.h similarity index 100% rename from src/ripple/basics/algorithm.h rename to include/xrpl/basics/algorithm.h diff --git a/src/ripple/basics/base64.h b/include/xrpl/basics/base64.h similarity index 98% rename from src/ripple/basics/base64.h rename to include/xrpl/basics/base64.h index 05a61133f83..515a7584e56 100644 --- a/src/ripple/basics/base64.h +++ b/include/xrpl/basics/base64.h @@ -73,7 +73,7 @@ base64_encode(std::string const& s) } std::string -base64_decode(std::string const& data); +base64_decode(std::string_view data); } // namespace ripple diff --git a/src/ripple/basics/base_uint.h b/include/xrpl/basics/base_uint.h similarity index 97% rename from src/ripple/basics/base_uint.h rename to include/xrpl/basics/base_uint.h index 8b15b082647..ae5aa17a63e 100644 --- a/src/ripple/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -25,12 +25,12 @@ #ifndef RIPPLE_BASICS_BASE_UINT_H_INCLUDED #define RIPPLE_BASICS_BASE_UINT_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -521,7 +521,8 @@ class base_uint return bytes; } - base_uint& operator=(beast::Zero) + base_uint& + operator=(beast::Zero) { data_.fill(0); return *this; @@ -548,6 +549,7 @@ class base_uint using uint128 = base_uint<128>; using uint160 = base_uint<160>; using uint256 = base_uint<256>; +using uint192 = base_uint<192>; template [[nodiscard]] inline constexpr std::strong_ordering @@ -633,6 +635,7 @@ operator<<(std::ostream& out, base_uint const& u) #ifndef __INTELLISENSE__ static_assert(sizeof(uint128) == 128 / 8, "There should be no padding bytes"); static_assert(sizeof(uint160) == 160 / 8, "There should be no padding bytes"); +static_assert(sizeof(uint192) == 192 / 8, "There should be no padding bytes"); static_assert(sizeof(uint256) == 256 / 8, "There should be no padding bytes"); #endif diff --git a/src/ripple/basics/chrono.h b/include/xrpl/basics/chrono.h similarity index 96% rename from src/ripple/basics/chrono.h rename to include/xrpl/basics/chrono.h index ea82f928b7e..d739b6bf44b 100644 --- a/src/ripple/basics/chrono.h +++ b/include/xrpl/basics/chrono.h @@ -22,9 +22,9 @@ #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/basics/comparators.h b/include/xrpl/basics/comparators.h similarity index 100% rename from src/ripple/basics/comparators.h rename to include/xrpl/basics/comparators.h diff --git a/src/ripple/basics/contract.h b/include/xrpl/basics/contract.h similarity index 98% rename from src/ripple/basics/contract.h rename to include/xrpl/basics/contract.h index 80aeee7a387..bdfc8aac362 100644 --- a/src/ripple/basics/contract.h +++ b/include/xrpl/basics/contract.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_CONTRACT_H_INCLUDED #define RIPPLE_BASICS_CONTRACT_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h similarity index 97% rename from src/ripple/basics/hardened_hash.h rename to include/xrpl/basics/hardened_hash.h index bc20409286e..0b77b0a07a8 100644 --- a/src/ripple/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_BASICS_HARDENED_HASH_H_INCLUDED #define RIPPLE_BASICS_HARDENED_HASH_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/basics/join.h b/include/xrpl/basics/join.h similarity index 100% rename from src/ripple/basics/join.h rename to include/xrpl/basics/join.h diff --git a/src/ripple/basics/make_SSLContext.h b/include/xrpl/basics/make_SSLContext.h similarity index 100% rename from src/ripple/basics/make_SSLContext.h rename to include/xrpl/basics/make_SSLContext.h diff --git a/src/ripple/basics/mulDiv.h b/include/xrpl/basics/mulDiv.h similarity index 100% rename from src/ripple/basics/mulDiv.h rename to include/xrpl/basics/mulDiv.h diff --git a/src/ripple/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h similarity index 100% rename from src/ripple/basics/partitioned_unordered_map.h rename to include/xrpl/basics/partitioned_unordered_map.h diff --git a/src/ripple/basics/random.h b/include/xrpl/basics/random.h similarity index 99% rename from src/ripple/basics/random.h rename to include/xrpl/basics/random.h index 3af8be54109..87b303bf6d5 100644 --- a/src/ripple/basics/random.h +++ b/include/xrpl/basics/random.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_RANDOM_H_INCLUDED #define RIPPLE_BASICS_RANDOM_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/basics/safe_cast.h b/include/xrpl/basics/safe_cast.h similarity index 96% rename from src/ripple/basics/safe_cast.h rename to include/xrpl/basics/safe_cast.h index e52bceca759..f193f1793f2 100644 --- a/src/ripple/basics/safe_cast.h +++ b/include/xrpl/basics/safe_cast.h @@ -30,8 +30,8 @@ namespace ripple { template static constexpr bool is_safetocasttovalue_v = - (std::is_integral_v && std::is_integral_v)&&( - std::is_signed::value || std::is_unsigned::value) && + (std::is_integral_v && std::is_integral_v) && + (std::is_signed::value || std::is_unsigned::value) && (std::is_signed::value != std::is_signed::value ? sizeof(Dest) > sizeof(Src) : sizeof(Dest) >= sizeof(Src)); diff --git a/src/ripple/basics/scope.h b/include/xrpl/basics/scope.h similarity index 79% rename from src/ripple/basics/scope.h rename to include/xrpl/basics/scope.h index 54c05998fa4..b1d13eae8ce 100644 --- a/src/ripple/basics/scope.h +++ b/include/xrpl/basics/scope.h @@ -21,6 +21,7 @@ #define RIPPLE_BASICS_SCOPE_H_INCLUDED #include +#include #include #include @@ -186,6 +187,70 @@ class scope_success template scope_success(EF) -> scope_success; +/** + Automatically unlocks and re-locks a unique_lock object. + + This is the reverse of a std::unique_lock object - instead of locking the + mutex for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock mutexes that aren't actually locked! + + This is essentially a less-versatile boost::reverse_lock. + + e.g. @code + + std::mutex mut; + + for (;;) + { + std::unique_lock myScopedLock{mut}; + // mut is now locked + + ... do some stuff with it locked .. + + while (xyz) + { + ... do some stuff with it locked .. + + scope_unlock unlocker{myScopedLock}; + + // mut is now unlocked for the remainder of this block, + // and re-locked at the end. + + ...do some stuff with it unlocked ... + } // mut gets locked here. + + } // mut gets unlocked here + @endcode +*/ + +template +class scope_unlock +{ + std::unique_lock* plock; + +public: + explicit scope_unlock(std::unique_lock& lock) noexcept(true) + : plock(&lock) + { + assert(plock->owns_lock()); + plock->unlock(); + } + + // Immovable type + scope_unlock(scope_unlock const&) = delete; + scope_unlock& + operator=(scope_unlock const&) = delete; + + ~scope_unlock() noexcept(true) + { + plock->lock(); + } +}; + +template +scope_unlock(std::unique_lock&) -> scope_unlock; + } // namespace ripple #endif diff --git a/src/ripple/basics/spinlock.h b/include/xrpl/basics/spinlock.h similarity index 100% rename from src/ripple/basics/spinlock.h rename to include/xrpl/basics/spinlock.h diff --git a/src/ripple/basics/strHex.h b/include/xrpl/basics/strHex.h similarity index 100% rename from src/ripple/basics/strHex.h rename to include/xrpl/basics/strHex.h diff --git a/src/ripple/basics/tagged_integer.h b/include/xrpl/basics/tagged_integer.h similarity index 98% rename from src/ripple/basics/tagged_integer.h rename to include/xrpl/basics/tagged_integer.h index fe11f882726..4629e830916 100644 --- a/src/ripple/basics/tagged_integer.h +++ b/include/xrpl/basics/tagged_integer.h @@ -20,7 +20,7 @@ #ifndef BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED #define BEAST_UTILITY_TAGGED_INTEGER_H_INCLUDED -#include +#include #include #include #include @@ -187,7 +187,8 @@ class tagged_integer return *this; } - explicit operator Int() const noexcept + explicit + operator Int() const noexcept { return m_value; } diff --git a/src/ripple/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h similarity index 100% rename from src/ripple/beast/asio/io_latency_probe.h rename to include/xrpl/beast/asio/io_latency_probe.h diff --git a/src/ripple/beast/clock/abstract_clock.h b/include/xrpl/beast/clock/abstract_clock.h similarity index 100% rename from src/ripple/beast/clock/abstract_clock.h rename to include/xrpl/beast/clock/abstract_clock.h diff --git a/src/ripple/beast/clock/basic_seconds_clock.h b/include/xrpl/beast/clock/basic_seconds_clock.h similarity index 100% rename from src/ripple/beast/clock/basic_seconds_clock.h rename to include/xrpl/beast/clock/basic_seconds_clock.h diff --git a/src/ripple/beast/clock/manual_clock.h b/include/xrpl/beast/clock/manual_clock.h similarity index 98% rename from src/ripple/beast/clock/manual_clock.h rename to include/xrpl/beast/clock/manual_clock.h index 808b0071006..97be8e79b90 100644 --- a/src/ripple/beast/clock/manual_clock.h +++ b/include/xrpl/beast/clock/manual_clock.h @@ -20,7 +20,7 @@ #ifndef BEAST_CHRONO_MANUAL_CLOCK_H_INCLUDED #define BEAST_CHRONO_MANUAL_CLOCK_H_INCLUDED -#include +#include #include namespace beast { diff --git a/src/ripple/beast/container/aged_container.h b/include/xrpl/beast/container/aged_container.h similarity index 100% rename from src/ripple/beast/container/aged_container.h rename to include/xrpl/beast/container/aged_container.h diff --git a/src/ripple/beast/container/aged_container_utility.h b/include/xrpl/beast/container/aged_container_utility.h similarity index 97% rename from src/ripple/beast/container/aged_container_utility.h rename to include/xrpl/beast/container/aged_container_utility.h index dc57c229fb5..b64cefbf5ad 100644 --- a/src/ripple/beast/container/aged_container_utility.h +++ b/include/xrpl/beast/container/aged_container_utility.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_CONTAINER_UTILITY_H_INCLUDED #define BEAST_CONTAINER_AGED_CONTAINER_UTILITY_H_INCLUDED -#include +#include #include diff --git a/src/ripple/beast/container/aged_map.h b/include/xrpl/beast/container/aged_map.h similarity index 95% rename from src/ripple/beast/container/aged_map.h rename to include/xrpl/beast/container/aged_map.h index f675846fa64..5b56cde9625 100644 --- a/src/ripple/beast/container/aged_map.h +++ b/include/xrpl/beast/container/aged_map.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_MAP_H_INCLUDED #define BEAST_CONTAINER_AGED_MAP_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_multimap.h b/include/xrpl/beast/container/aged_multimap.h similarity index 95% rename from src/ripple/beast/container/aged_multimap.h rename to include/xrpl/beast/container/aged_multimap.h index b4668851c3d..aa6c01f5b32 100644 --- a/src/ripple/beast/container/aged_multimap.h +++ b/include/xrpl/beast/container/aged_multimap.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_MULTIMAP_H_INCLUDED #define BEAST_CONTAINER_AGED_MULTIMAP_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_multiset.h b/include/xrpl/beast/container/aged_multiset.h similarity index 95% rename from src/ripple/beast/container/aged_multiset.h rename to include/xrpl/beast/container/aged_multiset.h index 6d62bcbeb36..d43cc8d5a70 100644 --- a/src/ripple/beast/container/aged_multiset.h +++ b/include/xrpl/beast/container/aged_multiset.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_MULTISET_H_INCLUDED #define BEAST_CONTAINER_AGED_MULTISET_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_set.h b/include/xrpl/beast/container/aged_set.h similarity index 95% rename from src/ripple/beast/container/aged_set.h rename to include/xrpl/beast/container/aged_set.h index 0a9d82f6cb2..aa31a47af4a 100644 --- a/src/ripple/beast/container/aged_set.h +++ b/include/xrpl/beast/container/aged_set.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_SET_H_INCLUDED #define BEAST_CONTAINER_AGED_SET_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_unordered_map.h b/include/xrpl/beast/container/aged_unordered_map.h similarity index 95% rename from src/ripple/beast/container/aged_unordered_map.h rename to include/xrpl/beast/container/aged_unordered_map.h index 1b6a147ef03..b466c87b3ff 100644 --- a/src/ripple/beast/container/aged_unordered_map.h +++ b/include/xrpl/beast/container/aged_unordered_map.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_UNORDERED_MAP_H_INCLUDED #define BEAST_CONTAINER_AGED_UNORDERED_MAP_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_unordered_multimap.h b/include/xrpl/beast/container/aged_unordered_multimap.h similarity index 96% rename from src/ripple/beast/container/aged_unordered_multimap.h rename to include/xrpl/beast/container/aged_unordered_multimap.h index 1298cd51d0e..e64c8415c61 100644 --- a/src/ripple/beast/container/aged_unordered_multimap.h +++ b/include/xrpl/beast/container/aged_unordered_multimap.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_UNORDERED_MULTIMAP_H_INCLUDED #define BEAST_CONTAINER_AGED_UNORDERED_MULTIMAP_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_unordered_multiset.h b/include/xrpl/beast/container/aged_unordered_multiset.h similarity index 95% rename from src/ripple/beast/container/aged_unordered_multiset.h rename to include/xrpl/beast/container/aged_unordered_multiset.h index 5e9f682aa6b..499dc7d6780 100644 --- a/src/ripple/beast/container/aged_unordered_multiset.h +++ b/include/xrpl/beast/container/aged_unordered_multiset.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_UNORDERED_MULTISET_H_INCLUDED #define BEAST_CONTAINER_AGED_UNORDERED_MULTISET_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/aged_unordered_set.h b/include/xrpl/beast/container/aged_unordered_set.h similarity index 95% rename from src/ripple/beast/container/aged_unordered_set.h rename to include/xrpl/beast/container/aged_unordered_set.h index adb411c344c..45fc6cd0ed8 100644 --- a/src/ripple/beast/container/aged_unordered_set.h +++ b/include/xrpl/beast/container/aged_unordered_set.h @@ -20,7 +20,7 @@ #ifndef BEAST_CONTAINER_AGED_UNORDERED_SET_H_INCLUDED #define BEAST_CONTAINER_AGED_UNORDERED_SET_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/container/detail/aged_associative_container.h b/include/xrpl/beast/container/detail/aged_associative_container.h similarity index 100% rename from src/ripple/beast/container/detail/aged_associative_container.h rename to include/xrpl/beast/container/detail/aged_associative_container.h diff --git a/src/ripple/beast/container/detail/aged_container_iterator.h b/include/xrpl/beast/container/detail/aged_container_iterator.h similarity index 100% rename from src/ripple/beast/container/detail/aged_container_iterator.h rename to include/xrpl/beast/container/detail/aged_container_iterator.h diff --git a/src/ripple/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h similarity index 99% rename from src/ripple/beast/container/detail/aged_ordered_container.h rename to include/xrpl/beast/container/detail/aged_ordered_container.h index 10dca962b13..ad807e92cfe 100644 --- a/src/ripple/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -20,11 +20,11 @@ #ifndef BEAST_CONTAINER_DETAIL_AGED_ORDERED_CONTAINER_H_INCLUDED #define BEAST_CONTAINER_DETAIL_AGED_ORDERED_CONTAINER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -846,8 +846,9 @@ class aged_ordered_container // set template auto - insert(value_type&& value) -> typename std:: - enable_if>::type; + insert(value_type&& value) -> typename std::enable_if< + !maybe_multi && !maybe_map, + std::pair>::type; // multiset template @@ -1795,7 +1796,8 @@ template < template auto aged_ordered_container:: - insert(value_type&& value) -> typename std:: + insert(value_type&& value) -> + typename std:: enable_if>::type { typename cont_type::insert_commit_data d; diff --git a/src/ripple/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h similarity index 99% rename from src/ripple/beast/container/detail/aged_unordered_container.h rename to include/xrpl/beast/container/detail/aged_unordered_container.h index fcdccd2a637..72e3334801b 100644 --- a/src/ripple/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -20,11 +20,11 @@ #ifndef BEAST_CONTAINER_DETAIL_AGED_UNORDERED_CONTAINER_H_INCLUDED #define BEAST_CONTAINER_DETAIL_AGED_UNORDERED_CONTAINER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -1057,8 +1057,9 @@ class aged_unordered_container // map, set template auto - insert(value_type&& value) -> typename std:: - enable_if>::type; + insert(value_type&& value) -> typename std::enable_if< + !maybe_multi && !maybe_map, + std::pair>::type; // multimap, multiset template @@ -2799,8 +2800,9 @@ aged_unordered_container< Clock, Hash, KeyEqual, - Allocator>::insert(value_type&& value) -> typename std:: - enable_if>::type + Allocator>::insert(value_type&& value) -> + typename std:: + enable_if>::type { maybe_rehash(1); typename cont_type::insert_commit_data d; diff --git a/src/ripple/beast/container/detail/empty_base_optimization.h b/include/xrpl/beast/container/detail/empty_base_optimization.h similarity index 100% rename from src/ripple/beast/container/detail/empty_base_optimization.h rename to include/xrpl/beast/container/detail/empty_base_optimization.h diff --git a/src/ripple/beast/core/CurrentThreadName.h b/include/xrpl/beast/core/CurrentThreadName.h similarity index 100% rename from src/ripple/beast/core/CurrentThreadName.h rename to include/xrpl/beast/core/CurrentThreadName.h diff --git a/src/ripple/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h similarity index 75% rename from src/ripple/beast/core/LexicalCast.h rename to include/xrpl/beast/core/LexicalCast.h index f4c78341b91..e0fa24ca9f5 100644 --- a/src/ripple/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -20,6 +20,7 @@ #ifndef BEAST_MODULE_CORE_TEXT_LEXICALCAST_H_INCLUDED #define BEAST_MODULE_CORE_TEXT_LEXICALCAST_H_INCLUDED +#include #include #include #include @@ -64,9 +65,9 @@ struct LexicalCast } }; -// Parse std::string to number -template -struct LexicalCast +// Parse a std::string_view into a number +template +struct LexicalCast { explicit LexicalCast() = default; @@ -78,7 +79,7 @@ struct LexicalCast std::enable_if_t< std::is_integral_v && !std::is_same_v, bool> - operator()(Integral& out, std::string const& in) const + operator()(Integral& out, std::string_view in) const { auto first = in.data(); auto last = in.data() + in.size(); @@ -92,20 +93,23 @@ struct LexicalCast } bool - operator()(bool& out, std::string in) const + operator()(bool& out, std::string_view in) const { + std::string result; + // Convert the input to lowercase - std::transform(in.begin(), in.end(), in.begin(), [](auto c) { - return std::tolower(static_cast(c)); - }); + std::transform( + in.begin(), in.end(), std::back_inserter(result), [](auto c) { + return std::tolower(static_cast(c)); + }); - if (in == "1" || in == "true") + if (result == "1" || result == "true") { out = true; return true; } - if (in == "0" || in == "false") + if (result == "0" || result == "false") { out = false; return true; @@ -114,9 +118,38 @@ struct LexicalCast return false; } }; - //------------------------------------------------------------------------------ +// Parse boost library's string_view to number or boolean value +// Note: As of Jan 2024, Boost contains three different types of string_view +// (boost::core::basic_string_view, boost::string_ref and +// boost::string_view). The below template specialization is included because +// it is used in the handshake.cpp file +template +struct LexicalCast> +{ + explicit LexicalCast() = default; + + bool + operator()(Out& out, boost::core::basic_string_view in) const + { + return LexicalCast()(out, in); + } +}; + +// Parse std::string to number or boolean value +template +struct LexicalCast +{ + explicit LexicalCast() = default; + + bool + operator()(Out& out, std::string in) const + { + return LexicalCast()(out, in); + } +}; + // Conversion from null terminated char const* template struct LexicalCast @@ -126,7 +159,8 @@ struct LexicalCast bool operator()(Out& out, char const* in) const { - return LexicalCast()(out, in); + assert(in); + return LexicalCast()(out, in); } }; @@ -140,7 +174,8 @@ struct LexicalCast bool operator()(Out& out, char* in) const { - return LexicalCast()(out, in); + assert(in); + return LexicalCast()(out, in); } }; diff --git a/src/ripple/beast/core/List.h b/include/xrpl/beast/core/List.h similarity index 100% rename from src/ripple/beast/core/List.h rename to include/xrpl/beast/core/List.h diff --git a/src/ripple/beast/core/LockFreeStack.h b/include/xrpl/beast/core/LockFreeStack.h similarity index 100% rename from src/ripple/beast/core/LockFreeStack.h rename to include/xrpl/beast/core/LockFreeStack.h diff --git a/src/ripple/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h similarity index 100% rename from src/ripple/beast/core/SemanticVersion.h rename to include/xrpl/beast/core/SemanticVersion.h diff --git a/src/ripple/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h similarity index 100% rename from src/ripple/beast/hash/hash_append.h rename to include/xrpl/beast/hash/hash_append.h diff --git a/src/ripple/beast/hash/uhash.h b/include/xrpl/beast/hash/uhash.h similarity index 95% rename from src/ripple/beast/hash/uhash.h rename to include/xrpl/beast/hash/uhash.h index 921ea3c9840..ab3eaad0392 100644 --- a/src/ripple/beast/hash/uhash.h +++ b/include/xrpl/beast/hash/uhash.h @@ -21,8 +21,8 @@ #ifndef BEAST_HASH_UHASH_H_INCLUDED #define BEAST_HASH_UHASH_H_INCLUDED -#include -#include +#include +#include namespace beast { diff --git a/src/ripple/beast/hash/xxhasher.h b/include/xrpl/beast/hash/xxhasher.h similarity index 98% rename from src/ripple/beast/hash/xxhasher.h rename to include/xrpl/beast/hash/xxhasher.h index 1a6fc502321..30af05ef0a1 100644 --- a/src/ripple/beast/hash/xxhasher.h +++ b/include/xrpl/beast/hash/xxhasher.h @@ -90,7 +90,8 @@ class xxhasher XXH3_64bits_update(state_, key, len); } - explicit operator std::size_t() noexcept + explicit + operator std::size_t() noexcept { return XXH3_64bits_digest(state_); } diff --git a/src/ripple/beast/insight/Collector.h b/include/xrpl/beast/insight/Collector.h similarity index 95% rename from src/ripple/beast/insight/Collector.h rename to include/xrpl/beast/insight/Collector.h index a71b1092c85..eab2bd7d70c 100644 --- a/src/ripple/beast/insight/Collector.h +++ b/include/xrpl/beast/insight/Collector.h @@ -20,11 +20,11 @@ #ifndef BEAST_INSIGHT_COLLECTOR_H_INCLUDED #define BEAST_INSIGHT_COLLECTOR_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/beast/insight/Counter.h b/include/xrpl/beast/insight/Counter.h similarity index 98% rename from src/ripple/beast/insight/Counter.h rename to include/xrpl/beast/insight/Counter.h index 069505e4ce5..3f3a251de89 100644 --- a/src/ripple/beast/insight/Counter.h +++ b/include/xrpl/beast/insight/Counter.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_COUNTER_H_INCLUDED #define BEAST_INSIGHT_COUNTER_H_INCLUDED -#include +#include #include diff --git a/src/ripple/beast/insight/CounterImpl.h b/include/xrpl/beast/insight/CounterImpl.h similarity index 100% rename from src/ripple/beast/insight/CounterImpl.h rename to include/xrpl/beast/insight/CounterImpl.h diff --git a/src/ripple/beast/insight/Event.h b/include/xrpl/beast/insight/Event.h similarity index 98% rename from src/ripple/beast/insight/Event.h rename to include/xrpl/beast/insight/Event.h index 5319a9a6677..407dd233e95 100644 --- a/src/ripple/beast/insight/Event.h +++ b/include/xrpl/beast/insight/Event.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_EVENT_H_INCLUDED #define BEAST_INSIGHT_EVENT_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/insight/EventImpl.h b/include/xrpl/beast/insight/EventImpl.h similarity index 100% rename from src/ripple/beast/insight/EventImpl.h rename to include/xrpl/beast/insight/EventImpl.h diff --git a/src/ripple/beast/insight/Gauge.h b/include/xrpl/beast/insight/Gauge.h similarity index 98% rename from src/ripple/beast/insight/Gauge.h rename to include/xrpl/beast/insight/Gauge.h index 76f84080766..b1e8bedfac2 100644 --- a/src/ripple/beast/insight/Gauge.h +++ b/include/xrpl/beast/insight/Gauge.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_GAUGE_H_INCLUDED #define BEAST_INSIGHT_GAUGE_H_INCLUDED -#include +#include #include diff --git a/src/ripple/beast/insight/GaugeImpl.h b/include/xrpl/beast/insight/GaugeImpl.h similarity index 100% rename from src/ripple/beast/insight/GaugeImpl.h rename to include/xrpl/beast/insight/GaugeImpl.h diff --git a/src/ripple/beast/insight/Group.h b/include/xrpl/beast/insight/Group.h similarity index 97% rename from src/ripple/beast/insight/Group.h rename to include/xrpl/beast/insight/Group.h index a37656b81a5..f11b1397c8b 100644 --- a/src/ripple/beast/insight/Group.h +++ b/include/xrpl/beast/insight/Group.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_GROUP_H_INCLUDED #define BEAST_INSIGHT_GROUP_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/insight/Groups.h b/include/xrpl/beast/insight/Groups.h similarity index 95% rename from src/ripple/beast/insight/Groups.h rename to include/xrpl/beast/insight/Groups.h index 817ad87bd1a..456deb79073 100644 --- a/src/ripple/beast/insight/Groups.h +++ b/include/xrpl/beast/insight/Groups.h @@ -20,8 +20,8 @@ #ifndef BEAST_INSIGHT_GROUPS_H_INCLUDED #define BEAST_INSIGHT_GROUPS_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/beast/insight/Hook.h b/include/xrpl/beast/insight/Hook.h similarity index 97% rename from src/ripple/beast/insight/Hook.h rename to include/xrpl/beast/insight/Hook.h index 04647f4b632..affa42bb828 100644 --- a/src/ripple/beast/insight/Hook.h +++ b/include/xrpl/beast/insight/Hook.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_HOOK_H_INCLUDED #define BEAST_INSIGHT_HOOK_H_INCLUDED -#include +#include #include diff --git a/src/ripple/beast/insight/HookImpl.h b/include/xrpl/beast/insight/HookImpl.h similarity index 100% rename from src/ripple/beast/insight/HookImpl.h rename to include/xrpl/beast/insight/HookImpl.h diff --git a/src/ripple/beast/insight/Insight.h b/include/xrpl/beast/insight/Insight.h similarity index 66% rename from src/ripple/beast/insight/Insight.h rename to include/xrpl/beast/insight/Insight.h index f34f7317483..1c2c1375a28 100644 --- a/src/ripple/beast/insight/Insight.h +++ b/include/xrpl/beast/insight/Insight.h @@ -20,18 +20,18 @@ #ifndef BEAST_INSIGHT_H_INCLUDED #define BEAST_INSIGHT_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif diff --git a/src/ripple/beast/insight/Meter.h b/include/xrpl/beast/insight/Meter.h similarity index 98% rename from src/ripple/beast/insight/Meter.h rename to include/xrpl/beast/insight/Meter.h index 376dcae0521..febd5cb3dc4 100644 --- a/src/ripple/beast/insight/Meter.h +++ b/include/xrpl/beast/insight/Meter.h @@ -22,7 +22,7 @@ #include -#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/insight/MeterImpl.h b/include/xrpl/beast/insight/MeterImpl.h similarity index 100% rename from src/ripple/beast/insight/MeterImpl.h rename to include/xrpl/beast/insight/MeterImpl.h diff --git a/src/ripple/beast/insight/NullCollector.h b/include/xrpl/beast/insight/NullCollector.h similarity index 97% rename from src/ripple/beast/insight/NullCollector.h rename to include/xrpl/beast/insight/NullCollector.h index db98153836e..4379c3ceb98 100644 --- a/src/ripple/beast/insight/NullCollector.h +++ b/include/xrpl/beast/insight/NullCollector.h @@ -20,7 +20,7 @@ #ifndef BEAST_INSIGHT_NULLCOLLECTOR_H_INCLUDED #define BEAST_INSIGHT_NULLCOLLECTOR_H_INCLUDED -#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/insight/StatsDCollector.h b/include/xrpl/beast/insight/StatsDCollector.h similarity index 93% rename from src/ripple/beast/insight/StatsDCollector.h rename to include/xrpl/beast/insight/StatsDCollector.h index d95aee04d0b..78195048d7f 100644 --- a/src/ripple/beast/insight/StatsDCollector.h +++ b/include/xrpl/beast/insight/StatsDCollector.h @@ -20,10 +20,10 @@ #ifndef BEAST_INSIGHT_STATSDCOLLECTOR_H_INCLUDED #define BEAST_INSIGHT_STATSDCOLLECTOR_H_INCLUDED -#include +#include -#include -#include +#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h similarity index 94% rename from src/ripple/beast/net/IPAddress.h rename to include/xrpl/beast/net/IPAddress.h index d8820ec40b5..f3a1a7348f2 100644 --- a/src/ripple/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -20,10 +20,10 @@ #ifndef BEAST_NET_IPADDRESS_H_INCLUDED #define BEAST_NET_IPADDRESS_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/beast/net/IPAddressConversion.h b/include/xrpl/beast/net/IPAddressConversion.h similarity index 98% rename from src/ripple/beast/net/IPAddressConversion.h rename to include/xrpl/beast/net/IPAddressConversion.h index e37dd5b6c61..982a8e50f26 100644 --- a/src/ripple/beast/net/IPAddressConversion.h +++ b/include/xrpl/beast/net/IPAddressConversion.h @@ -20,7 +20,7 @@ #ifndef BEAST_NET_IPADDRESSCONVERSION_H_INCLUDED #define BEAST_NET_IPADDRESSCONVERSION_H_INCLUDED -#include +#include #include diff --git a/src/ripple/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h similarity index 97% rename from src/ripple/beast/net/IPAddressV4.h rename to include/xrpl/beast/net/IPAddressV4.h index 2c6f6a8cced..7711a970dec 100644 --- a/src/ripple/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -20,7 +20,7 @@ #ifndef BEAST_NET_IPADDRESSV4_H_INCLUDED #define BEAST_NET_IPADDRESSV4_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h similarity index 100% rename from src/ripple/beast/net/IPAddressV6.h rename to include/xrpl/beast/net/IPAddressV6.h diff --git a/src/ripple/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h similarity index 97% rename from src/ripple/beast/net/IPEndpoint.h rename to include/xrpl/beast/net/IPEndpoint.h index 19ce36dcc3b..e66e7f4caae 100644 --- a/src/ripple/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -20,9 +20,9 @@ #ifndef BEAST_NET_IPENDPOINT_H_INCLUDED #define BEAST_NET_IPENDPOINT_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h similarity index 100% rename from src/ripple/beast/rfc2616.h rename to include/xrpl/beast/rfc2616.h diff --git a/src/ripple/beast/test/yield_to.h b/include/xrpl/beast/test/yield_to.h similarity index 100% rename from src/ripple/beast/test/yield_to.h rename to include/xrpl/beast/test/yield_to.h diff --git a/src/ripple/beast/type_name.h b/include/xrpl/beast/type_name.h similarity index 100% rename from src/ripple/beast/type_name.h rename to include/xrpl/beast/type_name.h diff --git a/src/ripple/beast/unit_test.h b/include/xrpl/beast/unit_test.h similarity index 72% rename from src/ripple/beast/unit_test.h rename to include/xrpl/beast/unit_test.h index 70747ea341b..bf33b205e62 100644 --- a/src/ripple/beast/unit_test.h +++ b/include/xrpl/beast/unit_test.h @@ -20,21 +20,21 @@ #ifndef BEAST_UNIT_TEST_H_INCLUDED #define BEAST_UNIT_TEST_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifndef BEAST_EXPECT #define BEAST_EXPECT_S1(x) #x #define BEAST_EXPECT_S2(x) BEAST_EXPECT_S1(x) -//#define BEAST_EXPECT(cond) {expect(cond, __FILE__ ":" ## +// #define BEAST_EXPECT(cond) {expect(cond, __FILE__ ":" ## //__LINE__);}while(false){} #define BEAST_EXPECT(cond) expect(cond, __FILE__ ":" BEAST_EXPECT_S2(__LINE__)) #endif diff --git a/src/ripple/beast/unit_test/amount.h b/include/xrpl/beast/unit_test/amount.h similarity index 100% rename from src/ripple/beast/unit_test/amount.h rename to include/xrpl/beast/unit_test/amount.h diff --git a/src/ripple/beast/unit_test/detail/const_container.h b/include/xrpl/beast/unit_test/detail/const_container.h similarity index 100% rename from src/ripple/beast/unit_test/detail/const_container.h rename to include/xrpl/beast/unit_test/detail/const_container.h diff --git a/src/ripple/beast/unit_test/global_suites.h b/include/xrpl/beast/unit_test/global_suites.h similarity index 95% rename from src/ripple/beast/unit_test/global_suites.h rename to include/xrpl/beast/unit_test/global_suites.h index 3fcdcc052bf..64bbbc7a268 100644 --- a/src/ripple/beast/unit_test/global_suites.h +++ b/include/xrpl/beast/unit_test/global_suites.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_GLOBAL_SUITES_HPP #define BEAST_UNIT_TEST_GLOBAL_SUITES_HPP -#include +#include namespace beast { namespace unit_test { diff --git a/src/ripple/beast/unit_test/match.h b/include/xrpl/beast/unit_test/match.h similarity index 98% rename from src/ripple/beast/unit_test/match.h rename to include/xrpl/beast/unit_test/match.h index 306bc569982..e8e12bd5568 100644 --- a/src/ripple/beast/unit_test/match.h +++ b/include/xrpl/beast/unit_test/match.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_MATCH_HPP #define BEAST_UNIT_TEST_MATCH_HPP -#include +#include #include namespace beast { diff --git a/src/ripple/beast/unit_test/recorder.h b/include/xrpl/beast/unit_test/recorder.h similarity index 94% rename from src/ripple/beast/unit_test/recorder.h rename to include/xrpl/beast/unit_test/recorder.h index 439e194c335..fbe6ab6c10d 100644 --- a/src/ripple/beast/unit_test/recorder.h +++ b/include/xrpl/beast/unit_test/recorder.h @@ -8,8 +8,8 @@ #ifndef BEAST_UNIT_TEST_RECORDER_HPP #define BEAST_UNIT_TEST_RECORDER_HPP -#include -#include +#include +#include namespace beast { namespace unit_test { diff --git a/src/ripple/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h similarity index 98% rename from src/ripple/beast/unit_test/reporter.h rename to include/xrpl/beast/unit_test/reporter.h index 76b65a8b1e5..956def1c30d 100644 --- a/src/ripple/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -8,8 +8,8 @@ #ifndef BEAST_UNIT_TEST_REPORTER_HPP #define BEAST_UNIT_TEST_REPORTER_HPP -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/beast/unit_test/results.h b/include/xrpl/beast/unit_test/results.h similarity index 98% rename from src/ripple/beast/unit_test/results.h rename to include/xrpl/beast/unit_test/results.h index dd43fb17a72..96fedc9b75f 100644 --- a/src/ripple/beast/unit_test/results.h +++ b/include/xrpl/beast/unit_test/results.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_RESULTS_HPP #define BEAST_UNIT_TEST_RESULTS_HPP -#include +#include #include #include diff --git a/src/ripple/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h similarity index 99% rename from src/ripple/beast/unit_test/runner.h rename to include/xrpl/beast/unit_test/runner.h index bdee7aee5a8..6330f2c8c81 100644 --- a/src/ripple/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_RUNNER_H_INCLUDED #define BEAST_UNIT_TEST_RUNNER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/beast/unit_test/suite.h b/include/xrpl/beast/unit_test/suite.h similarity index 99% rename from src/ripple/beast/unit_test/suite.h rename to include/xrpl/beast/unit_test/suite.h index 23c4c4fad1f..d49730d4d5f 100644 --- a/src/ripple/beast/unit_test/suite.h +++ b/include/xrpl/beast/unit_test/suite.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_SUITE_HPP #define BEAST_UNIT_TEST_SUITE_HPP -#include +#include #include #include #include @@ -646,7 +646,7 @@ suite::run(runner& r) #define BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Class, Module, Library, Priority) #else -#include +#include #define BEAST_DEFINE_TESTSUITE(Class, Module, Library) \ BEAST_DEFINE_TESTSUITE_INSERT(Class, Module, Library, false, 0) #define BEAST_DEFINE_TESTSUITE_MANUAL(Class, Module, Library) \ diff --git a/src/ripple/beast/unit_test/suite_info.h b/include/xrpl/beast/unit_test/suite_info.h similarity index 100% rename from src/ripple/beast/unit_test/suite_info.h rename to include/xrpl/beast/unit_test/suite_info.h diff --git a/src/ripple/beast/unit_test/suite_list.h b/include/xrpl/beast/unit_test/suite_list.h similarity index 94% rename from src/ripple/beast/unit_test/suite_list.h rename to include/xrpl/beast/unit_test/suite_list.h index a1aed563c71..5856b4c7dba 100644 --- a/src/ripple/beast/unit_test/suite_list.h +++ b/include/xrpl/beast/unit_test/suite_list.h @@ -8,8 +8,8 @@ #ifndef BEAST_UNIT_TEST_SUITE_LIST_HPP #define BEAST_UNIT_TEST_SUITE_LIST_HPP -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/beast/unit_test/thread.h b/include/xrpl/beast/unit_test/thread.h similarity index 98% rename from src/ripple/beast/unit_test/thread.h rename to include/xrpl/beast/unit_test/thread.h index 8b2d024a840..e94108f7879 100644 --- a/src/ripple/beast/unit_test/thread.h +++ b/include/xrpl/beast/unit_test/thread.h @@ -8,7 +8,7 @@ #ifndef BEAST_UNIT_TEST_THREAD_HPP #define BEAST_UNIT_TEST_THREAD_HPP -#include +#include #include #include #include diff --git a/src/ripple/beast/utility/Journal.h b/include/xrpl/beast/utility/Journal.h similarity index 99% rename from src/ripple/beast/utility/Journal.h rename to include/xrpl/beast/utility/Journal.h index 0738748b6c5..059355ccae7 100644 --- a/src/ripple/beast/utility/Journal.h +++ b/include/xrpl/beast/utility/Journal.h @@ -238,7 +238,8 @@ class Journal return m_sink.active(m_level); } - explicit operator bool() const + explicit + operator bool() const { return active(); } diff --git a/src/ripple/beast/utility/PropertyStream.h b/include/xrpl/beast/utility/PropertyStream.h similarity index 99% rename from src/ripple/beast/utility/PropertyStream.h rename to include/xrpl/beast/utility/PropertyStream.h index dbcc8a2d793..5eaf70453e8 100644 --- a/src/ripple/beast/utility/PropertyStream.h +++ b/include/xrpl/beast/utility/PropertyStream.h @@ -20,7 +20,7 @@ #ifndef BEAST_UTILITY_PROPERTYSTREAM_H_INCLUDED #define BEAST_UTILITY_PROPERTYSTREAM_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/beast/utility/WrappedSink.h b/include/xrpl/beast/utility/WrappedSink.h similarity index 98% rename from src/ripple/beast/utility/WrappedSink.h rename to include/xrpl/beast/utility/WrappedSink.h index ba714248a06..f22455a52c4 100644 --- a/src/ripple/beast/utility/WrappedSink.h +++ b/include/xrpl/beast/utility/WrappedSink.h @@ -20,7 +20,7 @@ #ifndef BEAST_UTILITY_WRAPPEDSINK_H_INCLUDED #define BEAST_UTILITY_WRAPPEDSINK_H_INCLUDED -#include +#include namespace beast { diff --git a/src/ripple/beast/utility/Zero.h b/include/xrpl/beast/utility/Zero.h similarity index 100% rename from src/ripple/beast/utility/Zero.h rename to include/xrpl/beast/utility/Zero.h diff --git a/src/ripple/beast/utility/maybe_const.h b/include/xrpl/beast/utility/maybe_const.h similarity index 100% rename from src/ripple/beast/utility/maybe_const.h rename to include/xrpl/beast/utility/maybe_const.h diff --git a/src/ripple/beast/utility/rngfill.h b/include/xrpl/beast/utility/rngfill.h similarity index 100% rename from src/ripple/beast/utility/rngfill.h rename to include/xrpl/beast/utility/rngfill.h diff --git a/src/ripple/beast/utility/temp_dir.h b/include/xrpl/beast/utility/temp_dir.h similarity index 100% rename from src/ripple/beast/utility/temp_dir.h rename to include/xrpl/beast/utility/temp_dir.h diff --git a/src/ripple/beast/xor_shift_engine.h b/include/xrpl/beast/xor_shift_engine.h similarity index 100% rename from src/ripple/beast/xor_shift_engine.h rename to include/xrpl/beast/xor_shift_engine.h diff --git a/src/ripple/crypto/README.md b/include/xrpl/crypto/README.md similarity index 100% rename from src/ripple/crypto/README.md rename to include/xrpl/crypto/README.md diff --git a/src/ripple/crypto/RFC1751.h b/include/xrpl/crypto/RFC1751.h similarity index 100% rename from src/ripple/crypto/RFC1751.h rename to include/xrpl/crypto/RFC1751.h diff --git a/src/ripple/crypto/csprng.h b/include/xrpl/crypto/csprng.h similarity index 100% rename from src/ripple/crypto/csprng.h rename to include/xrpl/crypto/csprng.h diff --git a/src/ripple/crypto/secure_erase.h b/include/xrpl/crypto/secure_erase.h similarity index 100% rename from src/ripple/crypto/secure_erase.h rename to include/xrpl/crypto/secure_erase.h diff --git a/src/ripple/json/JsonPropertyStream.h b/include/xrpl/json/JsonPropertyStream.h similarity index 96% rename from src/ripple/json/JsonPropertyStream.h rename to include/xrpl/json/JsonPropertyStream.h index 0f25e59eb59..fdc21971978 100644 --- a/src/ripple/json/JsonPropertyStream.h +++ b/include/xrpl/json/JsonPropertyStream.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_JSON_JSONPROPERTYSTREAM_H_INCLUDED #define RIPPLE_JSON_JSONPROPERTYSTREAM_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/json/Object.h b/include/xrpl/json/Object.h similarity index 99% rename from src/ripple/json/Object.h rename to include/xrpl/json/Object.h index 04501033335..ec60e562028 100644 --- a/src/ripple/json/Object.h +++ b/include/xrpl/json/Object.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_JSON_OBJECT_H_INCLUDED #define RIPPLE_JSON_OBJECT_H_INCLUDED -#include +#include #include namespace Json { diff --git a/src/ripple/json/Output.h b/include/xrpl/json/Output.h similarity index 99% rename from src/ripple/json/Output.h rename to include/xrpl/json/Output.h index 74aaa65269d..96905c20ba9 100644 --- a/src/ripple/json/Output.h +++ b/include/xrpl/json/Output.h @@ -22,6 +22,7 @@ #include #include +#include namespace Json { diff --git a/src/ripple/json/README.md b/include/xrpl/json/README.md similarity index 100% rename from src/ripple/json/README.md rename to include/xrpl/json/README.md diff --git a/src/ripple/json/Writer.h b/include/xrpl/json/Writer.h similarity index 98% rename from src/ripple/json/Writer.h rename to include/xrpl/json/Writer.h index 5801caf8514..882e944093e 100644 --- a/src/ripple/json/Writer.h +++ b/include/xrpl/json/Writer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_JSON_WRITER_H_INCLUDED #define RIPPLE_JSON_WRITER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace Json { diff --git a/src/ripple/json/impl/json_assert.h b/include/xrpl/json/detail/json_assert.h similarity index 97% rename from src/ripple/json/impl/json_assert.h rename to include/xrpl/json/detail/json_assert.h index b7297e016c6..c401ccf7200 100644 --- a/src/ripple/json/impl/json_assert.h +++ b/include/xrpl/json/detail/json_assert.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_JSON_JSON_ASSERT_H_INCLUDED #define RIPPLE_JSON_JSON_ASSERT_H_INCLUDED -#include +#include #define JSON_ASSERT_UNREACHABLE assert(false) #define JSON_ASSERT(condition) \ diff --git a/src/ripple/json/json_errors.h b/include/xrpl/json/json_errors.h similarity index 100% rename from src/ripple/json/json_errors.h rename to include/xrpl/json/json_errors.h diff --git a/src/ripple/json/json_forwards.h b/include/xrpl/json/json_forwards.h similarity index 100% rename from src/ripple/json/json_forwards.h rename to include/xrpl/json/json_forwards.h diff --git a/src/ripple/json/json_reader.h b/include/xrpl/json/json_reader.h similarity index 98% rename from src/ripple/json/json_reader.h rename to include/xrpl/json/json_reader.h index caa657eb765..6fb07c318d8 100644 --- a/src/ripple/json/json_reader.h +++ b/include/xrpl/json/json_reader.h @@ -22,8 +22,8 @@ #define CPPTL_JSON_READER_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/json/json_value.h b/include/xrpl/json/json_value.h similarity index 99% rename from src/ripple/json/json_value.h rename to include/xrpl/json/json_value.h index c8312e51448..e419940171e 100644 --- a/src/ripple/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_JSON_JSON_VALUE_H_INCLUDED #define RIPPLE_JSON_JSON_VALUE_H_INCLUDED -#include +#include #include #include #include @@ -64,7 +64,8 @@ class StaticString { } - constexpr operator const char*() const + constexpr + operator const char*() const { return str_; } @@ -296,7 +297,8 @@ class Value /** Returns false if this is an empty array, empty object, empty string, or null. */ - explicit operator bool() const; + explicit + operator bool() const; /// Remove all object members and array elements. /// \pre type() is arrayValue, objectValue, or nullValue diff --git a/src/ripple/json/json_writer.h b/include/xrpl/json/json_writer.h similarity index 99% rename from src/ripple/json/json_writer.h rename to include/xrpl/json/json_writer.h index d1ddcd4decf..86a5ecd984a 100644 --- a/src/ripple/json/json_writer.h +++ b/include/xrpl/json/json_writer.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_JSON_JSON_WRITER_H_INCLUDED #define RIPPLE_JSON_JSON_WRITER_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/json/to_string.h b/include/xrpl/json/to_string.h similarity index 100% rename from src/ripple/json/to_string.h rename to include/xrpl/json/to_string.h diff --git a/src/ripple/proto/README.md b/include/xrpl/proto/README.md similarity index 100% rename from src/ripple/proto/README.md rename to include/xrpl/proto/README.md diff --git a/src/ripple/proto/org/xrpl/rpc/v1/README.md b/include/xrpl/proto/org/xrpl/rpc/v1/README.md similarity index 99% rename from src/ripple/proto/org/xrpl/rpc/v1/README.md rename to include/xrpl/proto/org/xrpl/rpc/v1/README.md index c5000104257..9268439847d 100644 --- a/src/ripple/proto/org/xrpl/rpc/v1/README.md +++ b/include/xrpl/proto/org/xrpl/rpc/v1/README.md @@ -1,3 +1,5 @@ +# Protocol buffer definitions for gRPC + This folder contains the protocol buffer definitions used by the rippled gRPC API. The gRPC API attempts to mimic the JSON/Websocket API as much as possible. As of April 2020, the gRPC API supports a subset of the full rippled API: diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_ledger.proto b/include/xrpl/proto/org/xrpl/rpc/v1/get_ledger.proto similarity index 100% rename from src/ripple/proto/org/xrpl/rpc/v1/get_ledger.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/get_ledger.proto diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_ledger_data.proto b/include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_data.proto similarity index 100% rename from src/ripple/proto/org/xrpl/rpc/v1/get_ledger_data.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_data.proto diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_ledger_diff.proto b/include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_diff.proto similarity index 100% rename from src/ripple/proto/org/xrpl/rpc/v1/get_ledger_diff.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_diff.proto diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_ledger_entry.proto b/include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_entry.proto similarity index 100% rename from src/ripple/proto/org/xrpl/rpc/v1/get_ledger_entry.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/get_ledger_entry.proto diff --git a/src/ripple/proto/org/xrpl/rpc/v1/ledger.proto b/include/xrpl/proto/org/xrpl/rpc/v1/ledger.proto similarity index 100% rename from src/ripple/proto/org/xrpl/rpc/v1/ledger.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/ledger.proto diff --git a/src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto b/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto similarity index 93% rename from src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto rename to include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto index 995edba48a1..01a23fbe375 100644 --- a/src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto +++ b/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto @@ -11,7 +11,7 @@ import "org/xrpl/rpc/v1/get_ledger_diff.proto"; // These methods are binary only methods for retrieiving arbitrary ledger state -// via gRPC. These methods are used by clio and reporting mode, but can also be +// via gRPC. These methods are used by clio, but can also be // used by any client that wants to extract ledger state in an efficient manner. // They do not directly mimic the JSON equivalent methods. service XRPLedgerAPIService { diff --git a/src/ripple/proto/ripple.proto b/include/xrpl/proto/ripple.proto similarity index 84% rename from src/ripple/proto/ripple.proto rename to include/xrpl/proto/ripple.proto index 74cbfe8f6cb..a06bbd9a311 100644 --- a/src/ripple/proto/ripple.proto +++ b/include/xrpl/proto/ripple.proto @@ -18,10 +18,6 @@ enum MessageType mtHAVE_SET = 35; mtVALIDATION = 41; mtGET_OBJECTS = 42; - mtGET_SHARD_INFO = 50; - mtSHARD_INFO = 51; - mtGET_PEER_SHARD_INFO = 52; - mtPEER_SHARD_INFO = 53; mtVALIDATORLIST = 54; mtSQUELCH = 55; mtVALIDATORLISTCOLLECTION = 56; @@ -29,8 +25,6 @@ enum MessageType mtPROOF_PATH_RESPONSE = 58; mtREPLAY_DELTA_REQ = 59; mtREPLAY_DELTA_RESPONSE = 60; - mtGET_PEER_SHARD_INFO_V2 = 61; - mtPEER_SHARD_INFO_V2 = 62; mtHAVE_TRANSACTIONS = 63; mtTRANSACTIONS = 64; } @@ -89,71 +83,12 @@ message TMLink required bytes nodePubKey = 1 [deprecated=true]; // node public key } -// Request info on shards held -message TMGetPeerShardInfo -{ - required uint32 hops = 1 [deprecated=true]; // number of hops to travel - optional bool lastLink = 2 [deprecated=true]; // true if last link in the peer chain - repeated TMLink peerChain = 3 [deprecated=true]; // public keys used to route messages -} - -// Info about shards held -message TMPeerShardInfo -{ - required string shardIndexes = 1 [deprecated=true]; // rangeSet of shard indexes - optional bytes nodePubKey = 2 [deprecated=true]; // node public key - optional string endpoint = 3 [deprecated=true]; // ipv6 or ipv4 address - optional bool lastLink = 4 [deprecated=true]; // true if last link in the peer chain - repeated TMLink peerChain = 5 [deprecated=true]; // public keys used to route messages -} - // Peer public key message TMPublicKey { required bytes publicKey = 1; } -// Request peer shard information -message TMGetPeerShardInfoV2 -{ - // Peer public keys used to route messages - repeated TMPublicKey peerChain = 1; - - // Remaining times to relay - required uint32 relays = 2; -} - -// Peer shard information -message TMPeerShardInfoV2 -{ - message TMIncomplete - { - required uint32 shardIndex = 1; - required uint32 state = 2; - - // State completion percent, 1 - 100 - optional uint32 progress = 3; - } - - // Message creation time - required uint32 timestamp = 1; - - // Incomplete shards being acquired or verified - repeated TMIncomplete incomplete = 2; - - // Verified immutable shards (RangeSet) - optional string finalized = 3; - - // Public key of node that authored the shard info - required bytes publicKey = 4; - - // Digital signature of node that authored the shard info - required bytes signature = 5; - - // Peer public keys used to route messages - repeated TMPublicKey peerChain = 6; -} - // A transaction can have only one input and one output. // If you want to send an amount that is greater than any single address of yours // you must first combine coins from one address to another. diff --git a/src/ripple/protocol/AMMCore.h b/include/xrpl/protocol/AMMCore.h similarity index 95% rename from src/ripple/protocol/AMMCore.h rename to include/xrpl/protocol/AMMCore.h index 816bf86214b..32988af5fc7 100644 --- a/src/ripple/protocol/AMMCore.h +++ b/include/xrpl/protocol/AMMCore.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PROTOCOL_AMMCORE_H_INCLUDED #define RIPPLE_PROTOCOL_AMMCORE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h similarity index 94% rename from src/ripple/protocol/AccountID.h rename to include/xrpl/protocol/AccountID.h index 27e1f452293..7edf8d388f7 100644 --- a/src/ripple/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_PROTOCOL_ACCOUNTID_H_INCLUDED #define RIPPLE_PROTOCOL_ACCOUNTID_H_INCLUDED -#include +#include // VFALCO Uncomment when the header issues are resolved -//#include -#include -#include -#include -#include +// #include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/AmountConversions.h b/include/xrpl/protocol/AmountConversions.h similarity index 95% rename from src/ripple/protocol/AmountConversions.h rename to include/xrpl/protocol/AmountConversions.h index dc0defe6972..270d009b916 100644 --- a/src/ripple/protocol/AmountConversions.h +++ b/include/xrpl/protocol/AmountConversions.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED #define RIPPLE_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED -#include -#include -#include +#include +#include +#include #include @@ -33,13 +33,7 @@ toSTAmount(IOUAmount const& iou, Issue const& iss) { bool const isNeg = iou.signum() < 0; std::uint64_t const umant = isNeg ? -iou.mantissa() : iou.mantissa(); - return STAmount( - iss, - umant, - iou.exponent(), - /*native*/ false, - isNeg, - STAmount::unchecked()); + return STAmount(iss, umant, iou.exponent(), isNeg, STAmount::unchecked()); } inline STAmount diff --git a/src/ripple/protocol/ApiVersion.h b/include/xrpl/protocol/ApiVersion.h similarity index 81% rename from src/ripple/protocol/ApiVersion.h rename to include/xrpl/protocol/ApiVersion.h index 1cd03b0651d..dd09cf6bd1a 100644 --- a/src/ripple/protocol/ApiVersion.h +++ b/include/xrpl/protocol/ApiVersion.h @@ -76,38 +76,35 @@ static_assert(apiMaximumValidVersion >= apiMaximumSupportedVersion); } // namespace RPC template - void - forApiVersions(Fn const& fn, Args&&... args) requires // - (maxVer >= minVer) && // - (minVer >= RPC::apiMinimumSupportedVersion) && // - (RPC::apiMaximumValidVersion >= maxVer) && - requires -{ - fn(std::integral_constant{}, - std::forward(args)...); - fn(std::integral_constant{}, - std::forward(args)...); -} +void +forApiVersions(Fn const& fn, Args&&... args) + requires // + (maxVer >= minVer) && // + (minVer >= RPC::apiMinimumSupportedVersion) && // + (RPC::apiMaximumValidVersion >= maxVer) && requires { + fn(std::integral_constant{}, + std::forward(args)...); + fn(std::integral_constant{}, + std::forward(args)...); + } { constexpr auto size = maxVer + 1 - minVer; - [&](std::index_sequence) - { + [&](std::index_sequence) { (((void)fn( std::integral_constant{}, std::forward(args)...)), ...); - } - (std::make_index_sequence{}); + }(std::make_index_sequence{}); } template void -forAllApiVersions(Fn const& fn, Args&&... args) requires requires -{ - forApiVersions< - RPC::apiMinimumSupportedVersion, - RPC::apiMaximumValidVersion>(fn, std::forward(args)...); -} +forAllApiVersions(Fn const& fn, Args&&... args) + requires requires { + forApiVersions< + RPC::apiMinimumSupportedVersion, + RPC::apiMaximumValidVersion>(fn, std::forward(args)...); + } { forApiVersions< RPC::apiMinimumSupportedVersion, diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h new file mode 100644 index 00000000000..2cccc28bd41 --- /dev/null +++ b/include/xrpl/protocol/Asset.h @@ -0,0 +1,206 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_ASSET_H_INCLUDED +#define RIPPLE_PROTOCOL_ASSET_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +template +concept ValidIssueType = + std::is_same_v || std::is_same_v; + +/* Asset is an abstraction of three different issue types: XRP, IOU, MPT. + * For historical reasons, two issue types XRP and IOU are wrapped in Issue + * type. Many functions and classes there were first written for Issue + * have been rewritten for Asset. + */ +class Asset +{ +private: + using value_type = std::variant; + value_type issue_; + +public: + Asset() = default; + + /** Conversions to Asset are implicit and conversions to specific issue + * type are explicit. This design facilitates the use of Asset. + */ + Asset(Issue const& issue) : issue_(issue) + { + } + + Asset(MPTIssue const& mptIssue) : issue_(mptIssue) + { + } + + Asset(MPTID const& issuanceID) : issue_(MPTIssue{issuanceID}) + { + } + + AccountID const& + getIssuer() const; + + template + constexpr TIss const& + get() const; + + template + TIss& + get(); + + template + constexpr bool + holds() const; + + std::string + getText() const; + + constexpr value_type const& + value() const; + + void + setJson(Json::Value& jv) const; + + bool + native() const + { + return holds() && get().native(); + } + + friend constexpr bool + operator==(Asset const& lhs, Asset const& rhs); + + friend constexpr bool + operator!=(Asset const& lhs, Asset const& rhs); + + friend constexpr bool + operator==(Currency const& lhs, Asset const& rhs); + + /** Return true if both assets refer to the same currency (regardless of + * issuer) or MPT issuance. Otherwise return false. + */ + friend constexpr bool + equalTokens(Asset const& lhs, Asset const& rhs); +}; + +template +constexpr bool +Asset::holds() const +{ + return std::holds_alternative(issue_); +} + +template +constexpr TIss const& +Asset::get() const +{ + if (!std::holds_alternative(issue_)) + Throw("Asset is not a requested issue"); + return std::get(issue_); +} + +template +TIss& +Asset::get() +{ + if (!std::holds_alternative(issue_)) + Throw("Asset is not a requested issue"); + return std::get(issue_); +} + +constexpr Asset::value_type const& +Asset::value() const +{ + return issue_; +} + +constexpr bool +operator==(Asset const& lhs, Asset const& rhs) +{ + return std::visit( + [&]( + TLhs const& issLhs, TRhs const& issRhs) { + if constexpr (std::is_same_v) + return issLhs == issRhs; + else + return false; + }, + lhs.issue_, + rhs.issue_); +} + +constexpr bool +operator!=(Asset const& lhs, Asset const& rhs) +{ + return !(lhs == rhs); +} + +constexpr bool +operator==(Currency const& lhs, Asset const& rhs) +{ + return rhs.holds() && rhs.get().currency == lhs; +} + +constexpr bool +equalTokens(Asset const& lhs, Asset const& rhs) +{ + return std::visit( + [&]( + TLhs const& issLhs, TRhs const& issRhs) { + if constexpr ( + std::is_same_v && std::is_same_v) + return issLhs.currency == issRhs.currency; + else if constexpr ( + std::is_same_v && + std::is_same_v) + return issLhs.getMptID() == issRhs.getMptID(); + else + return false; + }, + lhs.issue_, + rhs.issue_); +} + +inline bool +isXRP(Asset const& asset) +{ + return asset.native(); +} + +std::string +to_string(Asset const& asset); + +bool +validJSONAsset(Json::Value const& jv); + +Asset +assetFromJson(Json::Value const& jv); + +Json::Value +to_json(Asset const& asset); + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_ASSET_H_INCLUDED diff --git a/src/ripple/protocol/Book.h b/include/xrpl/protocol/Book.h similarity index 98% rename from src/ripple/protocol/Book.h rename to include/xrpl/protocol/Book.h index 609989062c0..164a5ccfa99 100644 --- a/src/ripple/protocol/Book.h +++ b/include/xrpl/protocol/Book.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_BOOK_H_INCLUDED #define RIPPLE_PROTOCOL_BOOK_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h similarity index 100% rename from src/ripple/protocol/BuildInfo.h rename to include/xrpl/protocol/BuildInfo.h diff --git a/src/ripple/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h similarity index 96% rename from src/ripple/protocol/ErrorCodes.h rename to include/xrpl/protocol/ErrorCodes.h index ad849f2b4ef..39cfa9369cd 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_ERRORCODES_H_INCLUDED #define RIPPLE_PROTOCOL_ERRORCODES_H_INCLUDED -#include -#include +#include +#include namespace ripple { @@ -136,8 +136,8 @@ enum error_code_i { rpcINVALID_LGR_RANGE = 79, rpcEXPIRED_VALIDATOR_LIST = 80, - // Reporting - rpcFAILED_TO_FORWARD = 90, + // unused = 90, + // DEPRECATED. New code must not use this value. rpcREPORTING_UNSUPPORTED = 91, rpcOBJECT_NOT_FOUND = 92, @@ -148,8 +148,10 @@ enum error_code_i { // Oracle rpcORACLE_MALFORMED = 94, - rpcLAST = - rpcORACLE_MALFORMED // rpcLAST should always equal the last code.= + // deposit_authorized + credentials + rpcBAD_CREDENTIALS = 95, + + rpcLAST = rpcBAD_CREDENTIALS // rpcLAST should always equal the last code. }; /** Codes returned in the `warnings` array of certain RPC commands. @@ -160,7 +162,7 @@ enum warning_code_i { warnRPC_UNSUPPORTED_MAJORITY = 1001, warnRPC_AMENDMENT_BLOCKED = 1002, warnRPC_EXPIRED_VALIDATOR_LIST = 1003, - warnRPC_REPORTING = 1004 + // unused = 1004 }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/Feature.h b/include/xrpl/protocol/Feature.h similarity index 77% rename from src/ripple/protocol/Feature.h rename to include/xrpl/protocol/Feature.h index 57cd9513eea..90a81c55ef4 100644 --- a/src/ripple/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -20,10 +20,11 @@ #ifndef RIPPLE_PROTOCOL_FEATURE_H_INCLUDED #define RIPPLE_PROTOCOL_FEATURE_H_INCLUDED -#include +#include #include #include #include +#include #include #include @@ -67,6 +68,11 @@ namespace ripple { enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes }; +enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported }; + +/** All amendments libxrpl knows about. */ +std::map const& +allAmendments(); namespace detail { @@ -74,7 +80,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 73; +static constexpr std::size_t numFeatures = 83; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -302,64 +308,20 @@ foreachFeature(FeatureBitset bs, F&& f) f(bitsetIndexToFeature(i)); } -extern uint256 const featureOwnerPaysFee; -extern uint256 const featureFlow; -extern uint256 const featureFlowCross; -extern uint256 const featureCryptoConditionsSuite; -extern uint256 const fix1513; -extern uint256 const featureDepositAuth; -extern uint256 const featureChecks; -extern uint256 const fix1571; -extern uint256 const fix1543; -extern uint256 const fix1623; -extern uint256 const featureDepositPreauth; -extern uint256 const fix1515; -extern uint256 const fix1578; -extern uint256 const featureMultiSignReserve; -extern uint256 const fixTakerDryOfferRemoval; -extern uint256 const fixMasterKeyAsRegularKey; -extern uint256 const fixCheckThreading; -extern uint256 const fixPayChanRecipientOwnerDir; -extern uint256 const featureDeletableAccounts; -extern uint256 const fixQualityUpperBound; -extern uint256 const featureRequireFullyCanonicalSig; -extern uint256 const fix1781; -extern uint256 const featureHardenedValidations; -extern uint256 const fixAmendmentMajorityCalc; -extern uint256 const featureNegativeUNL; -extern uint256 const featureTicketBatch; -extern uint256 const featureFlowSortStrands; -extern uint256 const fixSTAmountCanonicalize; -extern uint256 const fixRmSmallIncreasedQOffers; -extern uint256 const featureCheckCashMakesTrustLine; -extern uint256 const featureNonFungibleTokensV1; -extern uint256 const featureExpandedSignerList; -extern uint256 const fixNFTokenDirV1; -extern uint256 const fixNFTokenNegOffer; -extern uint256 const featureNonFungibleTokensV1_1; -extern uint256 const fixTrustLinesToSelf; -extern uint256 const fixRemoveNFTokenAutoTrustLine; -extern uint256 const featureImmediateOfferKilled; -extern uint256 const featureDisallowIncoming; -extern uint256 const featureXRPFees; -extern uint256 const featureAMM; -extern uint256 const fixUniversalNumber; -extern uint256 const fixNonFungibleTokensV1_2; -extern uint256 const fixNFTokenRemint; -extern uint256 const fixReducedOffersV1; -extern uint256 const featureClawback; -extern uint256 const featureXChainBridge; -extern uint256 const fixDisallowIncomingV1; -extern uint256 const featureDID; -extern uint256 const fixFillOrKill; -extern uint256 const fixNFTokenReserve; -extern uint256 const fixInnerObjTemplate; -extern uint256 const fixAMMOverflowOffer; -extern uint256 const featurePriceOracle; -extern uint256 const fixEmptyDID; -extern uint256 const fixXChainRewardRounding; -extern uint256 const fixPreviousTxnID; -extern uint256 const fixAMMv1_1; +#pragma push_macro("XRPL_FEATURE") +#undef XRPL_FEATURE +#pragma push_macro("XRPL_FIX") +#undef XRPL_FIX + +#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name; +#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name; + +#include + +#undef XRPL_FIX +#pragma pop_macro("XRPL_FIX") +#undef XRPL_FEATURE +#pragma pop_macro("XRPL_FEATURE") } // namespace ripple diff --git a/src/ripple/protocol/Fees.h b/include/xrpl/protocol/Fees.h similarity index 98% rename from src/ripple/protocol/Fees.h rename to include/xrpl/protocol/Fees.h index d155b869d1d..7b4671a91d9 100644 --- a/src/ripple/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_FEES_H_INCLUDED #define RIPPLE_PROTOCOL_FEES_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/protocol/HashPrefix.h b/include/xrpl/protocol/HashPrefix.h similarity index 96% rename from src/ripple/protocol/HashPrefix.h rename to include/xrpl/protocol/HashPrefix.h index 409f9de9b51..0b6ddda4921 100644 --- a/src/ripple/protocol/HashPrefix.h +++ b/include/xrpl/protocol/HashPrefix.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_HASHPREFIX_H_INCLUDED #define RIPPLE_PROTOCOL_HASHPREFIX_H_INCLUDED -#include +#include #include namespace ripple { @@ -85,8 +85,8 @@ enum class HashPrefix : std::uint32_t { /** Payment Channel Claim */ paymentChannelClaim = detail::make_hash_prefix('C', 'L', 'M'), - /** shard info for signing */ - shardInfo = detail::make_hash_prefix('S', 'H', 'D'), + /** Credentials signature */ + credential = detail::make_hash_prefix('C', 'R', 'D'), }; template diff --git a/src/ripple/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h similarity index 79% rename from src/ripple/protocol/Indexes.h rename to include/xrpl/protocol/Indexes.h index d83599f892f..72cf0b527b1 100644 --- a/src/ripple/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -20,15 +20,17 @@ #ifndef RIPPLE_PROTOCOL_INDEXES_H_INCLUDED #define RIPPLE_PROTOCOL_INDEXES_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include namespace ripple { @@ -188,6 +190,11 @@ check(uint256 const& key) noexcept Keylet depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept; +Keylet +depositPreauth( + AccountID const& owner, + std::set> const& authCreds) noexcept; + inline Keylet depositPreauth(uint256 const& key) noexcept { @@ -286,6 +293,42 @@ did(AccountID const& account) noexcept; Keylet oracle(AccountID const& account, std::uint32_t const& documentID) noexcept; +Keylet +credential( + AccountID const& subject, + AccountID const& issuer, + Slice const& credType) noexcept; + +inline Keylet +credential(uint256 const& key) noexcept +{ + return {ltCREDENTIAL, key}; +} + +Keylet +mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept; + +Keylet +mptIssuance(MPTID const& issuanceID) noexcept; + +inline Keylet +mptIssuance(uint256 const& issuanceKey) +{ + return {ltMPTOKEN_ISSUANCE, issuanceKey}; +} + +Keylet +mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept; + +inline Keylet +mptoken(uint256 const& mptokenKey) +{ + return {ltMPTOKEN, mptokenKey}; +} + +Keylet +mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept; + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: @@ -306,6 +349,29 @@ getTicketIndex(AccountID const& account, std::uint32_t uSequence); uint256 getTicketIndex(AccountID const& account, SeqProxy ticketSeq); +template +struct keyletDesc +{ + std::function function; + Json::StaticString expectedLEName; + bool includeInTests; +}; + +// This list should include all of the keylet functions that take a single +// AccountID parameter. +std::array, 6> const directAccountKeylets{ + {{&keylet::account, jss::AccountRoot, false}, + {&keylet::ownerDir, jss::DirectoryNode, true}, + {&keylet::signers, jss::SignerList, true}, + // It's normally impossible to create an item at nftpage_min, but + // test it anyway, since the invariant checks for it. + {&keylet::nftpage_min, jss::NFTokenPage, true}, + {&keylet::nftpage_max, jss::NFTokenPage, true}, + {&keylet::did, jss::DID, true}}}; + +MPTID +makeMptID(std::uint32_t sequence, AccountID const& account); + } // namespace ripple #endif diff --git a/src/ripple/protocol/InnerObjectFormats.h b/include/xrpl/protocol/InnerObjectFormats.h similarity index 97% rename from src/ripple/protocol/InnerObjectFormats.h rename to include/xrpl/protocol/InnerObjectFormats.h index 33a155d03cb..06ca5af385c 100644 --- a/src/ripple/protocol/InnerObjectFormats.h +++ b/include/xrpl/protocol/InnerObjectFormats.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED #define RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/protocol/Issue.h b/include/xrpl/protocol/Issue.h similarity index 90% rename from src/ripple/protocol/Issue.h rename to include/xrpl/protocol/Issue.h index 1956b942e2b..335dd91354a 100644 --- a/src/ripple/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_ISSUE_H_INCLUDED #define RIPPLE_PROTOCOL_ISSUE_H_INCLUDED -#include -#include +#include +#include #include #include @@ -38,16 +38,26 @@ class Issue Currency currency{}; AccountID account{}; - Issue() + Issue() = default; + + Issue(Currency const& c, AccountID const& a) : currency(c), account(a) { } - Issue(Currency const& c, AccountID const& a) : currency(c), account(a) + AccountID const& + getIssuer() const { + return account; } std::string getText() const; + + void + setJson(Json::Value& jv) const; + + bool + native() const; }; bool @@ -116,6 +126,12 @@ noIssue() return issue; } +inline bool +isXRP(Issue const& issue) +{ + return issue.native(); +} + } // namespace ripple #endif diff --git a/src/ripple/protocol/KeyType.h b/include/xrpl/protocol/KeyType.h similarity index 100% rename from src/ripple/protocol/KeyType.h rename to include/xrpl/protocol/KeyType.h diff --git a/src/ripple/protocol/Keylet.h b/include/xrpl/protocol/Keylet.h similarity index 95% rename from src/ripple/protocol/Keylet.h rename to include/xrpl/protocol/Keylet.h index 1c08ce855c6..d3bda103314 100644 --- a/src/ripple/protocol/Keylet.h +++ b/include/xrpl/protocol/Keylet.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_KEYLET_H_INCLUDED #define RIPPLE_PROTOCOL_KEYLET_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h similarity index 98% rename from src/ripple/protocol/KnownFormats.h rename to include/xrpl/protocol/KnownFormats.h index b59742259ad..5f5a04be1b3 100644 --- a/src/ripple/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_KNOWNFORMATS_H_INCLUDED #define RIPPLE_PROTOCOL_KNOWNFORMATS_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h similarity index 67% rename from src/ripple/protocol/LedgerFormats.h rename to include/xrpl/protocol/LedgerFormats.h index e0ea7bf6f46..4f3eef4919d 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_LEDGERFORMATS_H_INCLUDED #define RIPPLE_PROTOCOL_LEDGERFORMATS_H_INCLUDED -#include +#include namespace ripple { @@ -52,150 +52,16 @@ namespace ripple { // clang-format off enum LedgerEntryType : std::uint16_t { - /** A ledger object which describes an account. - \sa keylet::account - */ - ltACCOUNT_ROOT = 0x0061, - - /** A ledger object which contains a list of object identifiers. - - \sa keylet::page, keylet::quality, keylet::book, keylet::next and - keylet::ownerDir - */ - ltDIR_NODE = 0x0064, - - /** A ledger object which describes a bidirectional trust line. - - @note Per Vinnie Falco this should be renamed to ltTRUST_LINE - - \sa keylet::line - */ - ltRIPPLE_STATE = 0x0072, - - /** A ledger object which describes a ticket. +#pragma push_macro("LEDGER_ENTRY") +#undef LEDGER_ENTRY - \sa keylet::ticket - */ - ltTICKET = 0x0054, - - /** A ledger object which contains a signer list for an account. +#define LEDGER_ENTRY(tag, value, name, fields) tag = value, - \sa keylet::signers - */ - ltSIGNER_LIST = 0x0053, +#include - /** A ledger object which describes an offer on the DEX. - - \sa keylet::offer - */ - ltOFFER = 0x006f, - - - /** The ledger object which lists details about sidechains. - - \sa keylet::bridge - */ - ltBRIDGE = 0x0069, - - /** A ledger object that contains a list of ledger hashes. - - This type is used to store the ledger hashes which the protocol uses - to implement skip lists that allow for efficient backwards (and, in - theory, forward) forward iteration across large ledger ranges. - - \sa keylet::skip - */ - ltLEDGER_HASHES = 0x0068, - - /** The ledger object which lists details about amendments on the network. - - \note This is a singleton: only one such object exists in the ledger. - - \sa keylet::amendments - */ - ltAMENDMENTS = 0x0066, - - /** A claim id for a cross chain transaction. - - \sa keylet::xChainClaimID - */ - ltXCHAIN_OWNED_CLAIM_ID = 0x0071, - - /** A claim id for a cross chain create account transaction. - - \sa keylet::xChainCreateAccountClaimID - */ - ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID = 0x0074, - - /** The ledger object which lists the network's fee settings. - - \note This is a singleton: only one such object exists in the ledger. - - \sa keylet::fees - */ - ltFEE_SETTINGS = 0x0073, - - /** A ledger object describing a single escrow. - - \sa keylet::escrow - */ - ltESCROW = 0x0075, - - /** A ledger object describing a single unidirectional XRP payment channel. - - \sa keylet::payChan - */ - ltPAYCHAN = 0x0078, - - /** A ledger object which describes a check. - - \sa keylet::check - */ - ltCHECK = 0x0043, - - /** A ledger object which describes a deposit preauthorization. - - \sa keylet::depositPreauth - */ - ltDEPOSIT_PREAUTH = 0x0070, - - /** The ledger object which tracks the current negative UNL state. - - \note This is a singleton: only one such object exists in the ledger. - - \sa keylet::negativeUNL - */ - ltNEGATIVE_UNL = 0x004e, - - /** A ledger object which contains a list of NFTs - - \sa keylet::nftpage_min, keylet::nftpage_max, keylet::nftpage - */ - ltNFTOKEN_PAGE = 0x0050, - - /** A ledger object which identifies an offer to buy or sell an NFT. - - \sa keylet::nftoffer - */ - ltNFTOKEN_OFFER = 0x0037, - - /** The ledger object which tracks the AMM. - - \sa keylet::amm - */ - ltAMM = 0x0079, - - /** The ledger object which tracks the DID. - - \sa keylet::did - */ - ltDID = 0x0049, - - /** A ledger object which tracks Oracle - \sa keylet::oracle - */ - ltORACLE = 0x0080, +#undef LEDGER_ENTRY +#pragma pop_macro("LEDGER_ENTRY") //--------------------------------------------------------------------------- /** A special type, matching any ledger entry type. @@ -308,6 +174,21 @@ enum LedgerSpecificFlags { // ltNFTOKEN_OFFER lsfSellNFToken = 0x00000001, + + // ltMPTOKEN_ISSUANCE + lsfMPTLocked = 0x00000001, // Also used in ltMPTOKEN + lsfMPTCanLock = 0x00000002, + lsfMPTRequireAuth = 0x00000004, + lsfMPTCanEscrow = 0x00000008, + lsfMPTCanTrade = 0x00000010, + lsfMPTCanTransfer = 0x00000020, + lsfMPTCanClawback = 0x00000040, + + // ltMPTOKEN + lsfMPTAuthorized = 0x00000002, + + // ltCREDENTIAL + lsfAccepted = 0x00010000, }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/LedgerHeader.h b/include/xrpl/protocol/LedgerHeader.h similarity index 93% rename from src/ripple/protocol/LedgerHeader.h rename to include/xrpl/protocol/LedgerHeader.h index 2adcfcfd209..663eb709be2 100644 --- a/src/ripple/protocol/LedgerHeader.h +++ b/include/xrpl/protocol/LedgerHeader.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_PROTOCOL_LEDGERHEADER_H_INCLUDED #define RIPPLE_PROTOCOL_LEDGERHEADER_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h new file mode 100644 index 00000000000..06f55686caf --- /dev/null +++ b/include/xrpl/protocol/MPTIssue.h @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED +#define RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED + +#include +#include + +namespace ripple { + +/* Adapt MPTID to provide the same interface as Issue. Enables using static + * polymorphism by Asset and other classes. MPTID is a 192-bit concatenation + * of a 32-bit account sequence and a 160-bit account id. + */ +class MPTIssue +{ +private: + MPTID mptID_; + +public: + MPTIssue() = default; + + explicit MPTIssue(MPTID const& issuanceID); + + AccountID const& + getIssuer() const; + + MPTID const& + getMptID() const; + + std::string + getText() const; + + void + setJson(Json::Value& jv) const; + + friend constexpr bool + operator==(MPTIssue const& lhs, MPTIssue const& rhs); + + friend constexpr bool + operator!=(MPTIssue const& lhs, MPTIssue const& rhs); + + bool + native() const + { + return false; + } +}; + +constexpr bool +operator==(MPTIssue const& lhs, MPTIssue const& rhs) +{ + return lhs.mptID_ == rhs.mptID_; +} + +constexpr bool +operator!=(MPTIssue const& lhs, MPTIssue const& rhs) +{ + return !(lhs == rhs); +} + +/** MPT is a non-native token. + */ +inline bool +isXRP(MPTID const&) +{ + return false; +} + +Json::Value +to_json(MPTIssue const& mptIssue); + +std::string +to_string(MPTIssue const& mptIssue); + +MPTIssue +mptIssueFromJson(Json::Value const& jv); + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED diff --git a/src/ripple/protocol/MultiApiJson.h b/include/xrpl/protocol/MultiApiJson.h similarity index 77% rename from src/ripple/protocol/MultiApiJson.h rename to include/xrpl/protocol/MultiApiJson.h index b6d1843ae69..73c274ff7f7 100644 --- a/src/ripple/protocol/MultiApiJson.h +++ b/include/xrpl/protocol/MultiApiJson.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_JSON_MULTIAPIJSON_H_INCLUDED #define RIPPLE_JSON_MULTIAPIJSON_H_INCLUDED -#include -#include +#include +#include #include #include @@ -80,9 +80,8 @@ struct MultiApiJson } void - set(const char* key, - auto const& - v) requires std::constructible_from + set(const char* key, auto const& v) + requires std::constructible_from { for (auto& a : this->val) a[key] = v; @@ -110,7 +109,8 @@ struct MultiApiJson unsigned int Version, typename... Args, typename Fn> - requires std::same_as, MultiApiJson> auto + requires std::same_as, MultiApiJson> + auto operator()( Json& json, std::integral_constant const version, @@ -133,7 +133,8 @@ struct MultiApiJson // integral_constant version, Json only template - requires std::same_as, MultiApiJson> auto + requires std::same_as, MultiApiJson> + auto operator()( Json& json, std::integral_constant const, @@ -151,10 +152,10 @@ struct MultiApiJson typename... Args, typename Fn> requires(!some_integral_constant) && - std::convertible_to&& std::same_as< - std::remove_cvref_t, - MultiApiJson> auto - operator()(Json& json, Version version, Fn fn, Args&&... args) const + std::convertible_to && + std::same_as, MultiApiJson> + auto + operator()(Json& json, Version version, Fn fn, Args&&... args) const -> std:: invoke_result_t { @@ -170,9 +171,10 @@ struct MultiApiJson // unsigned int version, Json only template requires(!some_integral_constant) && - std::convertible_to&& std:: - same_as, MultiApiJson> auto - operator()(Json& json, Version version, Fn fn) const + std::convertible_to && + std::same_as, MultiApiJson> + auto + operator()(Json& json, Version version, Fn fn) const -> std::invoke_result_t { assert( @@ -184,53 +186,43 @@ struct MultiApiJson auto visit() { - return [self = this](auto... args) requires requires - { - visitor( - std::declval(), - std::declval()...); - } - { - return visitor(*self, std::forward(args)...); - }; + return [self = this](auto... args) + requires requires { + visitor( + std::declval(), + std::declval()...); + } + { return visitor(*self, std::forward(args)...); }; } auto visit() const { - return [self = this](auto... args) requires requires - { - visitor( - std::declval(), - std::declval()...); - } - { - return visitor(*self, std::forward(args)...); - }; + return [self = this](auto... args) + requires requires { + visitor( + std::declval(), + std::declval()...); + } + { return visitor(*self, std::forward(args)...); }; } template - auto - visit(Args... args) - -> std::invoke_result_t requires( - sizeof...(args) > 0) && - requires - { - visitor(*this, std::forward(args)...); - } + auto + visit(Args... args) + -> std::invoke_result_t + requires(sizeof...(args) > 0) && + requires { visitor(*this, std::forward(args)...); } { return visitor(*this, std::forward(args)...); } template - auto - visit(Args... args) const -> std:: - invoke_result_t requires( - sizeof...(args) > 0) && - requires - { - visitor(*this, std::forward(args)...); - } + auto + visit(Args... args) const + -> std::invoke_result_t + requires(sizeof...(args) > 0) && + requires { visitor(*this, std::forward(args)...); } { return visitor(*this, std::forward(args)...); } diff --git a/src/ripple/protocol/NFTSyntheticSerializer.h b/include/xrpl/protocol/NFTSyntheticSerializer.h similarity index 92% rename from src/ripple/protocol/NFTSyntheticSerializer.h rename to include/xrpl/protocol/NFTSyntheticSerializer.h index f9a0cd50a46..e57b3ff71c9 100644 --- a/src/ripple/protocol/NFTSyntheticSerializer.h +++ b/include/xrpl/protocol/NFTSyntheticSerializer.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_NFTSYNTHETICSERIALIZER_H_INCLUDED #define RIPPLE_PROTOCOL_NFTSYNTHETICSERIALIZER_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/protocol/NFTokenID.h b/include/xrpl/protocol/NFTokenID.h similarity index 92% rename from src/ripple/protocol/NFTokenID.h rename to include/xrpl/protocol/NFTokenID.h index f29713aba7b..b9ea75d2036 100644 --- a/src/ripple/protocol/NFTokenID.h +++ b/include/xrpl/protocol/NFTokenID.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_NFTOKENID_H_INCLUDED #define RIPPLE_PROTOCOL_NFTOKENID_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/NFTokenOfferID.h b/include/xrpl/protocol/NFTokenOfferID.h similarity index 92% rename from src/ripple/protocol/NFTokenOfferID.h rename to include/xrpl/protocol/NFTokenOfferID.h index 777324f4243..9645b0b34da 100644 --- a/src/ripple/protocol/NFTokenOfferID.h +++ b/include/xrpl/protocol/NFTokenOfferID.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_NFTOKENOFFERID_H_INCLUDED #define RIPPLE_PROTOCOL_NFTOKENOFFERID_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/PayChan.h b/include/xrpl/protocol/PayChan.h similarity index 90% rename from src/ripple/protocol/PayChan.h rename to include/xrpl/protocol/PayChan.h index 216835de270..8344120ccd6 100644 --- a/src/ripple/protocol/PayChan.h +++ b/include/xrpl/protocol/PayChan.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_PAYCHAN_H_INCLUDED #define RIPPLE_PROTOCOL_PAYCHAN_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h similarity index 88% rename from src/ripple/protocol/Protocol.h rename to include/xrpl/protocol/Protocol.h index bd723627494..a9bd10a6fd1 100644 --- a/src/ripple/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_PROTOCOL_H_INCLUDED #define RIPPLE_PROTOCOL_PROTOCOL_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { @@ -95,6 +95,21 @@ std::size_t constexpr maxDIDAttestationLength = 256; /** The maximum length of a domain */ std::size_t constexpr maxDomainLength = 256; +/** The maximum length of a URI inside a Credential */ +std::size_t constexpr maxCredentialURILength = 256; + +/** The maximum length of a CredentialType inside a Credential */ +std::size_t constexpr maxCredentialTypeLength = 64; + +/** The maximum number of credentials can be passed in array */ +std::size_t constexpr maxCredentialsArraySize = 8; + +/** The maximum length of MPTokenMetadata */ +std::size_t constexpr maxMPTokenMetadataLength = 1024; + +/** The maximum amount of MPTokenIssuance */ +std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; + /** A ledger index. */ using LedgerIndex = std::uint32_t; diff --git a/src/ripple/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h similarity index 96% rename from src/ripple/protocol/PublicKey.h rename to include/xrpl/protocol/PublicKey.h index 9cf1a456953..c68656877c9 100644 --- a/src/ripple/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_PROTOCOL_PUBLICKEY_H_INCLUDED #define RIPPLE_PROTOCOL_PUBLICKEY_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/Quality.h b/include/xrpl/protocol/Quality.h similarity index 66% rename from src/ripple/protocol/Quality.h rename to include/xrpl/protocol/Quality.h index 840d8d444e1..1ee2cc9f686 100644 --- a/src/ripple/protocol/Quality.h +++ b/include/xrpl/protocol/Quality.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_QUALITY_H_INCLUDED #define RIPPLE_PROTOCOL_QUALITY_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -181,71 +181,71 @@ class Quality Math is avoided if the result is exact. The output is clamped to prevent money creation. */ - Amounts + [[nodiscard]] Amounts ceil_in(Amounts const& amount, STAmount const& limit) const; template - TAmounts - ceil_in(TAmounts const& amount, In const& limit) const - { - if (amount.in <= limit) - return amount; - - // Use the existing STAmount implementation for now, but consider - // replacing with code specific to IOUAMount and XRPAmount - Amounts stAmt(toSTAmount(amount.in), toSTAmount(amount.out)); - STAmount stLim(toSTAmount(limit)); - auto const stRes = ceil_in(stAmt, stLim); - return TAmounts( - toAmount(stRes.in), toAmount(stRes.out)); - } + [[nodiscard]] TAmounts + ceil_in(TAmounts const& amount, In const& limit) const; + + // Some of the underlying rounding functions called by ceil_in() ignored + // low order bits that could influence rounding decisions. This "strict" + // method uses underlying functions that pay attention to all the bits. + [[nodiscard]] Amounts + ceil_in_strict(Amounts const& amount, STAmount const& limit, bool roundUp) + const; + + template + [[nodiscard]] TAmounts + ceil_in_strict( + TAmounts const& amount, + In const& limit, + bool roundUp) const; /** Returns the scaled amount with out capped. Math is avoided if the result is exact. The input is clamped to prevent money creation. */ - Amounts + [[nodiscard]] Amounts ceil_out(Amounts const& amount, STAmount const& limit) const; template - TAmounts - ceil_out(TAmounts const& amount, Out const& limit) const - { - if (amount.out <= limit) - return amount; - - // Use the existing STAmount implementation for now, but consider - // replacing with code specific to IOUAMount and XRPAmount - Amounts stAmt(toSTAmount(amount.in), toSTAmount(amount.out)); - STAmount stLim(toSTAmount(limit)); - auto const stRes = ceil_out(stAmt, stLim); - return TAmounts( - toAmount(stRes.in), toAmount(stRes.out)); - } + [[nodiscard]] TAmounts + ceil_out(TAmounts const& amount, Out const& limit) const; - Amounts + // Some of the underlying rounding functions called by ceil_out() ignored + // low order bits that could influence rounding decisions. This "strict" + // method uses underlying functions that pay attention to all the bits. + [[nodiscard]] Amounts ceil_out_strict(Amounts const& amount, STAmount const& limit, bool roundUp) const; template - TAmounts + [[nodiscard]] TAmounts ceil_out_strict( TAmounts const& amount, Out const& limit, - bool roundUp) const - { - if (amount.out <= limit) - return amount; - - // Use the existing STAmount implementation for now, but consider - // replacing with code specific to IOUAMount and XRPAmount - Amounts stAmt(toSTAmount(amount.in), toSTAmount(amount.out)); - STAmount stLim(toSTAmount(limit)); - auto const stRes = ceil_out_strict(stAmt, stLim, roundUp); - return TAmounts( - toAmount(stRes.in), toAmount(stRes.out)); - } + bool roundUp) const; +private: + // The ceil_in and ceil_out methods that deal in TAmount all convert + // their arguments to STAoumout and convert the result back to TAmount. + // This helper function takes care of all the conversion operations. + template < + class In, + class Out, + class Lim, + typename FnPtr, + std::same_as... Round> + [[nodiscard]] TAmounts + ceil_TAmounts_helper( + TAmounts const& amount, + Lim const& limit, + Lim const& limit_cmp, + FnPtr ceil_function, + Round... round) const; + +public: /** Returns `true` if lhs is lower quality than `rhs`. Lower quality means the taker receives a worse deal. Higher quality is better for the taker. @@ -327,6 +327,84 @@ class Quality } }; +template < + class In, + class Out, + class Lim, + typename FnPtr, + std::same_as... Round> +TAmounts +Quality::ceil_TAmounts_helper( + TAmounts const& amount, + Lim const& limit, + Lim const& limit_cmp, + FnPtr ceil_function, + Round... roundUp) const +{ + if (limit_cmp <= limit) + return amount; + + // Use the existing STAmount implementation for now, but consider + // replacing with code specific to IOUAMount and XRPAmount + Amounts stAmt(toSTAmount(amount.in), toSTAmount(amount.out)); + STAmount stLim(toSTAmount(limit)); + Amounts const stRes = ((*this).*ceil_function)(stAmt, stLim, roundUp...); + return TAmounts(toAmount(stRes.in), toAmount(stRes.out)); +} + +template +TAmounts +Quality::ceil_in(TAmounts const& amount, In const& limit) const +{ + // Construct a function pointer to the function we want to call. + static constexpr Amounts (Quality::*ceil_in_fn_ptr)( + Amounts const&, STAmount const&) const = &Quality::ceil_in; + + return ceil_TAmounts_helper(amount, limit, amount.in, ceil_in_fn_ptr); +} + +template +TAmounts +Quality::ceil_in_strict( + TAmounts const& amount, + In const& limit, + bool roundUp) const +{ + // Construct a function pointer to the function we want to call. + static constexpr Amounts (Quality::*ceil_in_fn_ptr)( + Amounts const&, STAmount const&, bool) const = &Quality::ceil_in_strict; + + return ceil_TAmounts_helper( + amount, limit, amount.in, ceil_in_fn_ptr, roundUp); +} + +template +TAmounts +Quality::ceil_out(TAmounts const& amount, Out const& limit) const +{ + // Construct a function pointer to the function we want to call. + static constexpr Amounts (Quality::*ceil_out_fn_ptr)( + Amounts const&, STAmount const&) const = &Quality::ceil_out; + + return ceil_TAmounts_helper(amount, limit, amount.out, ceil_out_fn_ptr); +} + +template +TAmounts +Quality::ceil_out_strict( + TAmounts const& amount, + Out const& limit, + bool roundUp) const +{ + // Construct a function pointer to the function we want to call. + static constexpr Amounts (Quality::*ceil_out_fn_ptr)( + Amounts const&, STAmount const&, bool) const = + &Quality::ceil_out_strict; + + return ceil_TAmounts_helper( + amount, limit, amount.out, ceil_out_fn_ptr, roundUp); +} + /** Calculate the quality of a two-hop path given the two hops. @param lhs The first leg of the path: input to intermediate. @param rhs The second leg of the path: intermediate to output. diff --git a/src/ripple/protocol/QualityFunction.h b/include/xrpl/protocol/QualityFunction.h similarity index 96% rename from src/ripple/protocol/QualityFunction.h rename to include/xrpl/protocol/QualityFunction.h index 5e08bdf8cd3..f7184881489 100644 --- a/src/ripple/protocol/QualityFunction.h +++ b/include/xrpl/protocol/QualityFunction.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_QUALITYFUNCTION_H_INCLUDED #define RIPPLE_PROTOCOL_QUALITYFUNCTION_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/README.md b/include/xrpl/protocol/README.md similarity index 100% rename from src/ripple/protocol/README.md rename to include/xrpl/protocol/README.md diff --git a/src/ripple/protocol/RPCErr.h b/include/xrpl/protocol/RPCErr.h similarity index 97% rename from src/ripple/protocol/RPCErr.h rename to include/xrpl/protocol/RPCErr.h index e49e96b3df1..cb106b2f0da 100644 --- a/src/ripple/protocol/RPCErr.h +++ b/include/xrpl/protocol/RPCErr.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NET_RPCERR_H_INCLUDED #define RIPPLE_NET_RPCERR_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/protocol/Rate.h b/include/xrpl/protocol/Rate.h similarity index 96% rename from src/ripple/protocol/Rate.h rename to include/xrpl/protocol/Rate.h index 3524eabb627..6970d9c16a8 100644 --- a/src/ripple/protocol/Rate.h +++ b/include/xrpl/protocol/Rate.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_RATE_H_INCLUDED #define RIPPLE_PROTOCOL_RATE_H_INCLUDED -#include +#include #include #include #include @@ -74,7 +74,7 @@ STAmount multiplyRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp); STAmount @@ -87,7 +87,7 @@ STAmount divideRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp); namespace nft { diff --git a/src/ripple/protocol/RippleLedgerHash.h b/include/xrpl/protocol/RippleLedgerHash.h similarity index 97% rename from src/ripple/protocol/RippleLedgerHash.h rename to include/xrpl/protocol/RippleLedgerHash.h index 2a7b5728342..19ba803b823 100644 --- a/src/ripple/protocol/RippleLedgerHash.h +++ b/include/xrpl/protocol/RippleLedgerHash.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_RIPPLELEDGERHASH_H_INCLUDED #define RIPPLE_PROTOCOL_RIPPLELEDGERHASH_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/protocol/Rules.h b/include/xrpl/protocol/Rules.h similarity index 96% rename from src/ripple/protocol/Rules.h rename to include/xrpl/protocol/Rules.h index f8ff8c00c0c..6b22d01afe0 100644 --- a/src/ripple/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_RULES_H_INCLUDED #define RIPPLE_LEDGER_RULES_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h new file mode 100644 index 00000000000..01909b19862 --- /dev/null +++ b/include/xrpl/protocol/SField.h @@ -0,0 +1,390 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_SFIELD_H_INCLUDED +#define RIPPLE_PROTOCOL_SFIELD_H_INCLUDED + +#include +#include + +#include +#include +#include + +namespace ripple { + +/* + +Some fields have a different meaning for their + default value versus not present. + Example: + QualityIn on a TrustLine + +*/ + +//------------------------------------------------------------------------------ + +// Forwards +class STAccount; +class STAmount; +class STIssue; +class STBlob; +template +class STBitString; +template +class STInteger; +class STNumber; +class STXChainBridge; +class STVector256; +class STCurrency; + +#pragma push_macro("XMACRO") +#undef XMACRO + +#define XMACRO(STYPE) \ + /* special types */ \ + STYPE(STI_UNKNOWN, -2) \ + STYPE(STI_NOTPRESENT, 0) \ + STYPE(STI_UINT16, 1) \ + \ + /* types (common) */ \ + STYPE(STI_UINT32, 2) \ + STYPE(STI_UINT64, 3) \ + STYPE(STI_UINT128, 4) \ + STYPE(STI_UINT256, 5) \ + STYPE(STI_AMOUNT, 6) \ + STYPE(STI_VL, 7) \ + STYPE(STI_ACCOUNT, 8) \ + STYPE(STI_NUMBER, 9) \ + \ + /* 10-13 are reserved */ \ + STYPE(STI_OBJECT, 14) \ + STYPE(STI_ARRAY, 15) \ + \ + /* types (uncommon) */ \ + STYPE(STI_UINT8, 16) \ + STYPE(STI_UINT160, 17) \ + STYPE(STI_PATHSET, 18) \ + STYPE(STI_VECTOR256, 19) \ + STYPE(STI_UINT96, 20) \ + STYPE(STI_UINT192, 21) \ + STYPE(STI_UINT384, 22) \ + STYPE(STI_UINT512, 23) \ + STYPE(STI_ISSUE, 24) \ + STYPE(STI_XCHAIN_BRIDGE, 25) \ + STYPE(STI_CURRENCY, 26) \ + \ + /* high-level types */ \ + /* cannot be serialized inside other types */ \ + STYPE(STI_TRANSACTION, 10001) \ + STYPE(STI_LEDGERENTRY, 10002) \ + STYPE(STI_VALIDATION, 10003) \ + STYPE(STI_METADATA, 10004) + +#pragma push_macro("TO_ENUM") +#undef TO_ENUM +#pragma push_macro("TO_MAP") +#undef TO_MAP + +#define TO_ENUM(name, value) name = value, +#define TO_MAP(name, value) {#name, value}, + +enum SerializedTypeID { XMACRO(TO_ENUM) }; + +static std::map const sTypeMap = {XMACRO(TO_MAP)}; + +#undef XMACRO +#undef TO_ENUM + +#pragma pop_macro("XMACRO") +#pragma pop_macro("TO_ENUM") +#pragma pop_macro("TO_MAP") + +// constexpr +inline int +field_code(SerializedTypeID id, int index) +{ + return (safe_cast(id) << 16) | index; +} + +// constexpr +inline int +field_code(int id, int index) +{ + return (id << 16) | index; +} + +/** Identifies fields. + + Fields are necessary to tag data in signed transactions so that + the binary format of the transaction can be canonicalized. All + SFields are created at compile time. + + Each SField, once constructed, lives until program termination, and there + is only one instance per fieldType/fieldValue pair which serves the entire + application. +*/ +class SField +{ +public: + enum { + sMD_Never = 0x00, + sMD_ChangeOrig = 0x01, // original value when it changes + sMD_ChangeNew = 0x02, // new value when it changes + sMD_DeleteFinal = 0x04, // final value when it is deleted + sMD_Create = 0x08, // value when it's created + sMD_Always = 0x10, // value when node containing it is affected at all + sMD_BaseTen = 0x20, + sMD_Default = + sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create + }; + + enum class IsSigning : unsigned char { no, yes }; + static IsSigning const notSigning = IsSigning::no; + + int const fieldCode; // (type<<16)|index + SerializedTypeID const fieldType; // STI_* + int const fieldValue; // Code number for protocol + std::string const fieldName; + int const fieldMeta; + int const fieldNum; + IsSigning const signingField; + Json::StaticString const jsonName; + + SField(SField const&) = delete; + SField& + operator=(SField const&) = delete; + SField(SField&&) = delete; + SField& + operator=(SField&&) = delete; + +public: + struct private_access_tag_t; // public, but still an implementation detail + + // These constructors can only be called from SField.cpp + SField( + private_access_tag_t, + SerializedTypeID tid, + int fv, + const char* fn, + int meta = sMD_Default, + IsSigning signing = IsSigning::yes); + explicit SField(private_access_tag_t, int fc); + + static const SField& + getField(int fieldCode); + static const SField& + getField(std::string const& fieldName); + static const SField& + getField(int type, int value) + { + return getField(field_code(type, value)); + } + + static const SField& + getField(SerializedTypeID type, int value) + { + return getField(field_code(type, value)); + } + + std::string const& + getName() const + { + return fieldName; + } + + bool + hasName() const + { + return fieldCode > 0; + } + + Json::StaticString const& + getJsonName() const + { + return jsonName; + } + + operator Json::StaticString const&() const + { + return jsonName; + } + + bool + isInvalid() const + { + return fieldCode == -1; + } + + bool + isUseful() const + { + return fieldCode > 0; + } + + bool + isBinary() const + { + return fieldValue < 256; + } + + // A discardable field is one that cannot be serialized, and + // should be discarded during serialization,like 'hash'. + // You cannot serialize an object's hash inside that object, + // but you can have it in the JSON representation. + bool + isDiscardable() const + { + return fieldValue > 256; + } + + int + getCode() const + { + return fieldCode; + } + int + getNum() const + { + return fieldNum; + } + static int + getNumFields() + { + return num; + } + + bool + shouldMeta(int c) const + { + return (fieldMeta & c) != 0; + } + + bool + shouldInclude(bool withSigningField) const + { + return (fieldValue < 256) && + (withSigningField || (signingField == IsSigning::yes)); + } + + bool + operator==(const SField& f) const + { + return fieldCode == f.fieldCode; + } + + bool + operator!=(const SField& f) const + { + return fieldCode != f.fieldCode; + } + + static int + compare(const SField& f1, const SField& f2); + + static std::map const& + getKnownCodeToField() + { + return knownCodeToField; + } + +private: + static int num; + static std::map knownCodeToField; +}; + +/** A field with a type known at compile time. */ +template +struct TypedField : SField +{ + using type = T; + + template + explicit TypedField(private_access_tag_t pat, Args&&... args); +}; + +/** Indicate std::optional field semantics. */ +template +struct OptionaledField +{ + TypedField const* f; + + explicit OptionaledField(TypedField const& f_) : f(&f_) + { + } +}; + +template +inline OptionaledField +operator~(TypedField const& f) +{ + return OptionaledField(f); +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +using SF_UINT8 = TypedField>; +using SF_UINT16 = TypedField>; +using SF_UINT32 = TypedField>; +using SF_UINT64 = TypedField>; +using SF_UINT96 = TypedField>; +using SF_UINT128 = TypedField>; +using SF_UINT160 = TypedField>; +using SF_UINT192 = TypedField>; +using SF_UINT256 = TypedField>; +using SF_UINT384 = TypedField>; +using SF_UINT512 = TypedField>; + +using SF_ACCOUNT = TypedField; +using SF_AMOUNT = TypedField; +using SF_ISSUE = TypedField; +using SF_CURRENCY = TypedField; +using SF_NUMBER = TypedField; +using SF_VL = TypedField; +using SF_VECTOR256 = TypedField; +using SF_XCHAIN_BRIDGE = TypedField; + +//------------------------------------------------------------------------------ + +// Use macros for most SField construction to enforce naming conventions. +#pragma push_macro("UNTYPED_SFIELD") +#undef UNTYPED_SFIELD +#pragma push_macro("TYPED_SFIELD") +#undef TYPED_SFIELD + +#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ + extern SField const sfName; +#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ + extern SF_##stiSuffix const sfName; + +extern SField const sfInvalid; +extern SField const sfGeneric; + +#include + +#undef TYPED_SFIELD +#pragma pop_macro("TYPED_SFIELD") +#undef UNTYPED_SFIELD +#pragma pop_macro("UNTYPED_SFIELD") + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h similarity index 86% rename from src/ripple/protocol/SOTemplate.h rename to include/xrpl/protocol/SOTemplate.h index 609c2d2c80b..95cd35fead2 100644 --- a/src/ripple/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_SOTEMPLATE_H_INCLUDED #define RIPPLE_PROTOCOL_SOTEMPLATE_H_INCLUDED -#include -#include +#include +#include #include #include #include @@ -39,6 +39,9 @@ enum SOEStyle { // constructed with STObject::makeInnerObject() }; +/** Amount fields that can support MPT */ +enum SOETxMPTAmount { soeMPTNone, soeMPTSupported, soeMPTNotSupported }; + //------------------------------------------------------------------------------ /** An element in a SOTemplate. */ @@ -47,10 +50,11 @@ class SOElement // Use std::reference_wrapper so SOElement can be stored in a std::vector. std::reference_wrapper sField_; SOEStyle style_; + SOETxMPTAmount supportMpt_ = soeMPTNone; -public: - SOElement(SField const& fieldName, SOEStyle style) - : sField_(fieldName), style_(style) +private: + void + init(SField const& fieldName) const { if (!sField_.get().isUseful()) { @@ -62,6 +66,21 @@ class SOElement } } +public: + SOElement(SField const& fieldName, SOEStyle style) + : sField_(fieldName), style_(style) + { + init(fieldName); + } + SOElement( + TypedField const& fieldName, + SOEStyle style, + SOETxMPTAmount supportMpt = soeMPTNotSupported) + : sField_(fieldName), style_(style), supportMpt_(supportMpt) + { + init(fieldName); + } + SField const& sField() const { @@ -73,6 +92,12 @@ class SOElement { return style_; } + + SOETxMPTAmount + supportMPT() const + { + return supportMpt_; + } }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/STAccount.h b/include/xrpl/protocol/STAccount.h similarity index 96% rename from src/ripple/protocol/STAccount.h rename to include/xrpl/protocol/STAccount.h index c44327fe566..537a336e5db 100644 --- a/src/ripple/protocol/STAccount.h +++ b/include/xrpl/protocol/STAccount.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_STACCOUNT_H_INCLUDED #define RIPPLE_PROTOCOL_STACCOUNT_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h similarity index 69% rename from src/ripple/protocol/STAmount.h rename to include/xrpl/protocol/STAmount.h index 1de1568ae03..e0a6c1eca08 100644 --- a/src/ripple/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -20,19 +20,25 @@ #ifndef RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED #define RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { +template +concept AssetType = + std::is_same_v || std::is_convertible_v || + std::is_convertible_v || std::is_convertible_v; + // Internal form: // 1: If amount is zero, then value is zero and offset is -100 // 2: Otherwise: @@ -51,10 +57,9 @@ class STAmount final : public STBase, public CountedObject using rep = std::pair; private: - Issue mIssue; + Asset mAsset; mantissa_type mValue; exponent_type mOffset; - bool mIsNative; // A shorthand for isXRP(mIssue). bool mIsNegative; public: @@ -70,8 +75,10 @@ class STAmount final : public STBase, public CountedObject // Max native value on network. static const std::uint64_t cMaxNativeN = 100000000000000000ull; - static const std::uint64_t cNotNative = 0x8000000000000000ull; - static const std::uint64_t cPosNative = 0x4000000000000000ull; + static const std::uint64_t cIssuedCurrency = 0x8000000000000000ull; + static const std::uint64_t cPositive = 0x4000000000000000ull; + static const std::uint64_t cMPToken = 0x2000000000000000ull; + static const std::uint64_t cValueMask = ~(cPositive | cMPToken); static std::uint64_t const uRateOne; @@ -84,31 +91,31 @@ class STAmount final : public STBase, public CountedObject }; // Do not call canonicalize + template STAmount( SField const& name, - Issue const& issue, + A const& asset, mantissa_type mantissa, exponent_type exponent, - bool native, bool negative, unchecked); + template STAmount( - Issue const& issue, + A const& asset, mantissa_type mantissa, exponent_type exponent, - bool native, bool negative, unchecked); // Call canonicalize + template STAmount( SField const& name, - Issue const& issue, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative); + A const& asset, + mantissa_type mantissa = 0, + exponent_type exponent = 0, + bool negative = false); STAmount(SField const& name, std::int64_t mantissa); @@ -117,37 +124,42 @@ class STAmount final : public STBase, public CountedObject std::uint64_t mantissa = 0, bool negative = false); - STAmount( - SField const& name, - Issue const& issue, - std::uint64_t mantissa = 0, - int exponent = 0, - bool negative = false); - explicit STAmount(std::uint64_t mantissa = 0, bool negative = false); explicit STAmount(SField const& name, STAmount const& amt); + template STAmount( - Issue const& issue, + A const& asset, std::uint64_t mantissa = 0, int exponent = 0, - bool negative = false); + bool negative = false) + : mAsset(asset) + , mValue(mantissa) + , mOffset(exponent) + , mIsNegative(negative) + { + canonicalize(); + } // VFALCO Is this needed when we have the previous signature? + template STAmount( - Issue const& issue, + A const& asset, std::uint32_t mantissa, int exponent = 0, bool negative = false); - STAmount(Issue const& issue, std::int64_t mantissa, int exponent = 0); + template + STAmount(A const& asset, std::int64_t mantissa, int exponent = 0); - STAmount(Issue const& issue, int mantissa, int exponent = 0); + template + STAmount(A const& asset, int mantissa, int exponent = 0); // Legacy support for new-style amounts STAmount(IOUAmount const& amount, Issue const& issue); STAmount(XRPAmount const& amount); + STAmount(MPTAmount const& amount, MPTIssue const& mptIssue); operator Number() const; //-------------------------------------------------------------------------- @@ -162,12 +174,23 @@ class STAmount final : public STBase, public CountedObject bool native() const noexcept; + template + constexpr bool + holds() const noexcept; + bool negative() const noexcept; std::uint64_t mantissa() const noexcept; + Asset const& + asset() const; + + template + constexpr TIss const& + get() const; + Issue const& issue() const; @@ -197,7 +220,8 @@ class STAmount final : public STBase, public CountedObject // //-------------------------------------------------------------------------- - explicit operator bool() const noexcept; + explicit + operator bool() const noexcept; STAmount& operator+=(STAmount const&); @@ -223,17 +247,14 @@ class STAmount final : public STBase, public CountedObject // Zero while copying currency and issuer. void - clear(STAmount const& saTmpl); - - void - clear(Issue const& issue); + clear(Asset const& asset); void setIssuer(AccountID const& uIssuer); - /** Set the Issue for this amount and update mIsNative. */ + /** Set the Issue for this amount. */ void - setIssue(Issue const& issue); + setIssue(Asset const& asset); //-------------------------------------------------------------------------- // @@ -265,6 +286,8 @@ class STAmount final : public STBase, public CountedObject xrp() const; IOUAmount iou() const; + MPTAmount + mpt() const; private: static std::unique_ptr @@ -289,6 +312,100 @@ class STAmount final : public STBase, public CountedObject operator+(STAmount const& v1, STAmount const& v2); }; +template +STAmount::STAmount( + SField const& name, + A const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool negative, + unchecked) + : STBase(name) + , mAsset(asset) + , mValue(mantissa) + , mOffset(exponent) + , mIsNegative(negative) +{ +} + +template +STAmount::STAmount( + A const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool negative, + unchecked) + : mAsset(asset), mValue(mantissa), mOffset(exponent), mIsNegative(negative) +{ +} + +template +STAmount::STAmount( + SField const& name, + A const& asset, + std::uint64_t mantissa, + int exponent, + bool negative) + : STBase(name) + , mAsset(asset) + , mValue(mantissa) + , mOffset(exponent) + , mIsNegative(negative) +{ + // mValue is uint64, but needs to fit in the range of int64 + assert(mValue <= std::numeric_limits::max()); + canonicalize(); +} + +template +STAmount::STAmount(A const& asset, std::int64_t mantissa, int exponent) + : mAsset(asset), mOffset(exponent) +{ + set(mantissa); + canonicalize(); +} + +template +STAmount::STAmount( + A const& asset, + std::uint32_t mantissa, + int exponent, + bool negative) + : STAmount(asset, safe_cast(mantissa), exponent, negative) +{ +} + +template +STAmount::STAmount(A const& asset, int mantissa, int exponent) + : STAmount(asset, safe_cast(mantissa), exponent) +{ +} + +// Legacy support for new-style amounts +inline STAmount::STAmount(IOUAmount const& amount, Issue const& issue) + : mAsset(issue) + , mOffset(amount.exponent()) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = unsafe_cast(-amount.mantissa()); + else + mValue = unsafe_cast(amount.mantissa()); + + canonicalize(); +} + +inline STAmount::STAmount(MPTAmount const& amount, MPTIssue const& mptIssue) + : mAsset(mptIssue), mOffset(0), mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = unsafe_cast(-amount.value()); + else + mValue = unsafe_cast(amount.value()); + + canonicalize(); +} + //------------------------------------------------------------------------------ // // Creation @@ -300,7 +417,7 @@ STAmount amountFromQuality(std::uint64_t rate); STAmount -amountFromString(Issue const& issue, std::string const& amount); +amountFromString(Asset const& issue, std::string const& amount); STAmount amountFromJson(SField const& name, Json::Value const& v); @@ -331,7 +448,14 @@ STAmount::exponent() const noexcept inline bool STAmount::native() const noexcept { - return mIsNative; + return mAsset.native(); +} + +template +constexpr bool +STAmount::holds() const noexcept +{ + return mAsset.holds(); } inline bool @@ -346,22 +470,35 @@ STAmount::mantissa() const noexcept return mValue; } +inline Asset const& +STAmount::asset() const +{ + return mAsset; +} + +template +constexpr TIss const& +STAmount::get() const +{ + return mAsset.get(); +} + inline Issue const& STAmount::issue() const { - return mIssue; + return get(); } inline Currency const& STAmount::getCurrency() const { - return mIssue.currency; + return mAsset.get().currency; } inline AccountID const& STAmount::getIssuer() const { - return mIssue.account; + return mAsset.getIssuer(); } inline int @@ -373,7 +510,7 @@ STAmount::signum() const noexcept inline STAmount STAmount::zeroed() const { - return STAmount(mIssue); + return STAmount(mAsset); } inline STAmount::operator bool() const noexcept @@ -383,12 +520,15 @@ inline STAmount::operator bool() const noexcept inline STAmount::operator Number() const { - if (mIsNative) + if (native()) return xrp(); + if (mAsset.holds()) + return mpt(); return iou(); } -inline STAmount& STAmount::operator=(beast::Zero) +inline STAmount& +STAmount::operator=(beast::Zero) { clear(); return *this; @@ -413,30 +553,22 @@ STAmount::clear() { // The -100 is used to allow 0 to sort less than a small positive values // which have a negative exponent. - mOffset = mIsNative ? 0 : -100; + mOffset = native() ? 0 : -100; mValue = 0; mIsNegative = false; } -// Zero while copying currency and issuer. -inline void -STAmount::clear(STAmount const& saTmpl) -{ - clear(saTmpl.mIssue); -} - inline void -STAmount::clear(Issue const& issue) +STAmount::clear(Asset const& asset) { - setIssue(issue); + setIssue(asset); clear(); } inline void STAmount::setIssuer(AccountID const& uIssuer) { - mIssue.account = uIssuer; - setIssue(mIssue); + mAsset.get().account = uIssuer; } inline STAmount const& @@ -501,17 +633,17 @@ STAmount operator-(STAmount const& v1, STAmount const& v2); STAmount -divide(STAmount const& v1, STAmount const& v2, Issue const& issue); +divide(STAmount const& v1, STAmount const& v2, Asset const& asset); STAmount -multiply(STAmount const& v1, STAmount const& v2, Issue const& issue); +multiply(STAmount const& v1, STAmount const& v2, Asset const& asset); // multiply rounding result in specified direction STAmount mulRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // multiply following the rounding directions more precisely. @@ -519,7 +651,7 @@ STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // divide rounding result in specified direction @@ -527,7 +659,7 @@ STAmount divRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // divide following the rounding directions more precisely. @@ -535,7 +667,7 @@ STAmount divRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // Someone is offering X for Y, what is the rate? @@ -549,7 +681,7 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); inline bool isXRP(STAmount const& amount) { - return isXRP(amount.issue().currency); + return amount.native(); } // Since `canonicalize` does not have access to a ledger, this is needed to put diff --git a/src/ripple/protocol/STArray.h b/include/xrpl/protocol/STArray.h similarity index 98% rename from src/ripple/protocol/STArray.h rename to include/xrpl/protocol/STArray.h index 8c3833d3fc8..7fa2ecad834 100644 --- a/src/ripple/protocol/STArray.h +++ b/include/xrpl/protocol/STArray.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_STARRAY_H_INCLUDED #define RIPPLE_PROTOCOL_STARRAY_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/STBase.h b/include/xrpl/protocol/STBase.h similarity index 95% rename from src/ripple/protocol/STBase.h rename to include/xrpl/protocol/STBase.h index ec8c34a9ddd..097341384f3 100644 --- a/src/ripple/protocol/STBase.h +++ b/include/xrpl/protocol/STBase.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_STBASE_H_INCLUDED #define RIPPLE_PROTOCOL_STBASE_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include @@ -52,11 +52,13 @@ struct JsonOptions { } - [[nodiscard]] constexpr explicit operator underlying_t() const noexcept + [[nodiscard]] constexpr explicit + operator underlying_t() const noexcept { return value; } - [[nodiscard]] constexpr explicit operator bool() const noexcept + [[nodiscard]] constexpr explicit + operator bool() const noexcept { return value != 0u; } diff --git a/src/ripple/protocol/STBitString.h b/include/xrpl/protocol/STBitString.h similarity index 95% rename from src/ripple/protocol/STBitString.h rename to include/xrpl/protocol/STBitString.h index decdfa64861..f3a74f2fc54 100644 --- a/src/ripple/protocol/STBitString.h +++ b/include/xrpl/protocol/STBitString.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_STBITSTRING_H_INCLUDED #define RIPPLE_PROTOCOL_STBITSTRING_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { @@ -84,6 +84,7 @@ class STBitString final : public STBase, public CountedObject> using STUInt128 = STBitString<128>; using STUInt160 = STBitString<160>; +using STUInt192 = STBitString<192>; using STUInt256 = STBitString<256>; template @@ -136,6 +137,13 @@ STUInt160::getSType() const return STI_UINT160; } +template <> +inline SerializedTypeID +STUInt192::getSType() const +{ + return STI_UINT192; +} + template <> inline SerializedTypeID STUInt256::getSType() const diff --git a/src/ripple/protocol/STBlob.h b/include/xrpl/protocol/STBlob.h similarity index 95% rename from src/ripple/protocol/STBlob.h rename to include/xrpl/protocol/STBlob.h index 3b2731be7f0..bdedbd92105 100644 --- a/src/ripple/protocol/STBlob.h +++ b/include/xrpl/protocol/STBlob.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_STBLOB_H_INCLUDED #define RIPPLE_PROTOCOL_STBLOB_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/STCurrency.h b/include/xrpl/protocol/STCurrency.h similarity index 94% rename from src/ripple/protocol/STCurrency.h rename to include/xrpl/protocol/STCurrency.h index f855c24832e..3383137fb3a 100644 --- a/src/ripple/protocol/STCurrency.h +++ b/include/xrpl/protocol/STCurrency.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PROTOCOL_STCURRENCY_H_INCLUDED #define RIPPLE_PROTOCOL_STCURRENCY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/STExchange.h b/include/xrpl/protocol/STExchange.h similarity index 93% rename from src/ripple/protocol/STExchange.h rename to include/xrpl/protocol/STExchange.h index e22d75b08ee..e1a1215dbdd 100644 --- a/src/ripple/protocol/STExchange.h +++ b/include/xrpl/protocol/STExchange.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_PROTOCOL_STEXCHANGE_H_INCLUDED #define RIPPLE_PROTOCOL_STEXCHANGE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/STInteger.h b/include/xrpl/protocol/STInteger.h similarity index 98% rename from src/ripple/protocol/STInteger.h rename to include/xrpl/protocol/STInteger.h index aaf0f8c904e..6bae2cc3152 100644 --- a/src/ripple/protocol/STInteger.h +++ b/include/xrpl/protocol/STInteger.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED #define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/STIssue.h b/include/xrpl/protocol/STIssue.h similarity index 94% rename from src/ripple/protocol/STIssue.h rename to include/xrpl/protocol/STIssue.h index 223798a8911..a0dfbd4faec 100644 --- a/src/ripple/protocol/STIssue.h +++ b/include/xrpl/protocol/STIssue.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PROTOCOL_STISSUE_H_INCLUDED #define RIPPLE_PROTOCOL_STISSUE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/STLedgerEntry.h b/include/xrpl/protocol/STLedgerEntry.h similarity index 97% rename from src/ripple/protocol/STLedgerEntry.h rename to include/xrpl/protocol/STLedgerEntry.h index 6fd50aa154e..6a57b32451a 100644 --- a/src/ripple/protocol/STLedgerEntry.h +++ b/include/xrpl/protocol/STLedgerEntry.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_STLEDGERENTRY_H_INCLUDED #define RIPPLE_PROTOCOL_STLEDGERENTRY_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/include/xrpl/protocol/STNumber.h b/include/xrpl/protocol/STNumber.h new file mode 100644 index 00000000000..c0fce572c8c --- /dev/null +++ b/include/xrpl/protocol/STNumber.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef XRPL_PROTOCOL_STNUMBER_H_INCLUDED +#define XRPL_PROTOCOL_STNUMBER_H_INCLUDED + +#include +#include +#include + +#include + +namespace ripple { + +/** + * A serializable number. + * + * This type is-a `Number`, and can be used everywhere that is accepted. + * This type simply integrates `Number` with the serialization framework, + * letting it be used for fields in ledger entries and transactions. + * It is effectively an `STAmount` sans `Asset`: + * it can represent a value of any token type (XRP, IOU, or MPT) + * without paying the storage cost of duplicating asset information + * that may be deduced from the context. + */ +class STNumber : public STBase, public CountedObject +{ +private: + Number value_; + +public: + using value_type = Number; + + STNumber() = default; + explicit STNumber(SField const& field, Number const& value = Number()); + STNumber(SerialIter& sit, SField const& field); + + SerializedTypeID + getSType() const override; + std::string + getText() const override; + void + add(Serializer& s) const override; + + Number const& + value() const; + void + setValue(Number const& v); + + bool + isEquivalent(STBase const& t) const override; + bool + isDefault() const override; + + operator Number() const + { + return value_; + } + +private: + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; +}; + +std::ostream& +operator<<(std::ostream& out, STNumber const& rhs); + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/STObject.h b/include/xrpl/protocol/STObject.h similarity index 94% rename from src/ripple/protocol/STObject.h rename to include/xrpl/protocol/STObject.h index 38678f67a55..748a2b5d685 100644 --- a/src/ripple/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -20,20 +20,20 @@ #ifndef RIPPLE_PROTOCOL_STOBJECT_H_INCLUDED #define RIPPLE_PROTOCOL_STOBJECT_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -44,7 +44,6 @@ namespace ripple { class STArray; -class Rules; inline void throwFieldNotFound(SField const& field) @@ -105,7 +104,7 @@ class STObject : public STBase, public CountedObject explicit STObject(SField const& name); static STObject - makeInnerObject(SField const& name, Rules const& rules); + makeInnerObject(SField const& name); iterator begin() const; @@ -227,6 +226,8 @@ class STObject : public STBase, public CountedObject uint160 getFieldH160(SField const& field) const; + uint192 + getFieldH192(SField const& field) const; uint256 getFieldH256(SField const& field) const; AccountID @@ -244,6 +245,8 @@ class STObject : public STBase, public CountedObject getFieldArray(SField const& field) const; const STCurrency& getFieldCurrency(SField const& field) const; + STNumber const& + getFieldNumber(SField const& field) const; /** Get the value of a field. @param A TypedField built from an SField value representing the desired @@ -375,6 +378,8 @@ class STObject : public STBase, public CountedObject void setFieldCurrency(SField const& field, STCurrency const&); void + setFieldNumber(SField const& field, STNumber const&); + void setFieldPathSet(SField const& field, STPathSet const&); void setFieldV256(SField const& field, STVector256 const& v); @@ -499,6 +504,11 @@ class STObject::Proxy assign(U&& u); }; +// Constraint += and -= ValueProxy operators +// to value types that support arithmetic operations +template +concept IsArithmetic = std::is_arithmetic_v || std::is_same_v; + template class STObject::ValueProxy : private Proxy { @@ -514,6 +524,16 @@ class STObject::ValueProxy : private Proxy std::enable_if_t, ValueProxy&> operator=(U&& u); + // Convenience operators for value types supporting + // arithmetic operations + template + ValueProxy& + operator+=(U const& u); + + template + ValueProxy& + operator-=(U const& u); + operator value_type() const; private: @@ -540,7 +560,8 @@ class STObject::OptionalProxy : private Proxy Fields with soeDEFAULT and set to the default value will return `true` */ - explicit operator bool() const noexcept; + explicit + operator bool() const noexcept; /** Return the contained value @@ -731,6 +752,24 @@ STObject::ValueProxy::operator=(U&& u) return *this; } +template +template +STObject::ValueProxy& +STObject::ValueProxy::operator+=(U const& u) +{ + this->assign(this->value() + u); + return *this; +} + +template +template +STObject::ValueProxy& +STObject::ValueProxy::operator-=(U const& u) +{ + this->assign(this->value() - u); + return *this; +} + template STObject::ValueProxy::operator value_type() const { diff --git a/src/ripple/protocol/STParsedJSON.h b/include/xrpl/protocol/STParsedJSON.h similarity index 98% rename from src/ripple/protocol/STParsedJSON.h rename to include/xrpl/protocol/STParsedJSON.h index 11cba574027..d2052bf1f4c 100644 --- a/src/ripple/protocol/STParsedJSON.h +++ b/include/xrpl/protocol/STParsedJSON.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_STPARSEDJSON_H_INCLUDED #define RIPPLE_PROTOCOL_STPARSEDJSON_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/STPathSet.h b/include/xrpl/protocol/STPathSet.h similarity index 98% rename from src/ripple/protocol/STPathSet.h rename to include/xrpl/protocol/STPathSet.h index 8102bc76eb0..473086368fb 100644 --- a/src/ripple/protocol/STPathSet.h +++ b/include/xrpl/protocol/STPathSet.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED #define RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/STTx.h b/include/xrpl/protocol/STTx.h similarity index 94% rename from src/ripple/protocol/STTx.h rename to include/xrpl/protocol/STTx.h index e166eb20dd4..08b9a1bad10 100644 --- a/src/ripple/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_PROTOCOL_STTX_H_INCLUDED #define RIPPLE_PROTOCOL_STTX_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h similarity index 97% rename from src/ripple/protocol/STValidation.h rename to include/xrpl/protocol/STValidation.h index edd922e7bfa..6cae0971f51 100644 --- a/src/ripple/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED #define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/STVector256.h b/include/xrpl/protocol/STVector256.h similarity index 96% rename from src/ripple/protocol/STVector256.h rename to include/xrpl/protocol/STVector256.h index bf4a1cbec44..d81ddf977ff 100644 --- a/src/ripple/protocol/STVector256.h +++ b/include/xrpl/protocol/STVector256.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_STVECTOR256_H_INCLUDED #define RIPPLE_PROTOCOL_STVECTOR256_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -65,7 +65,8 @@ class STVector256 : public STBase, public CountedObject setValue(const STVector256& v); /** Retrieve a copy of the vector we contain */ - explicit operator std::vector() const; + explicit + operator std::vector() const; std::size_t size() const; diff --git a/src/ripple/protocol/STXChainBridge.h b/include/xrpl/protocol/STXChainBridge.h similarity index 97% rename from src/ripple/protocol/STXChainBridge.h rename to include/xrpl/protocol/STXChainBridge.h index 537a1d160b2..38db1912c70 100644 --- a/src/ripple/protocol/STXChainBridge.h +++ b/include/xrpl/protocol/STXChainBridge.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_STXCHAINBRIDGE_H_INCLUDED #define RIPPLE_PROTOCOL_STXCHAINBRIDGE_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/SecretKey.h b/include/xrpl/protocol/SecretKey.h similarity index 95% rename from src/ripple/protocol/SecretKey.h rename to include/xrpl/protocol/SecretKey.h index 824ae9b1e0f..67dc9c4ca59 100644 --- a/src/ripple/protocol/SecretKey.h +++ b/include/xrpl/protocol/SecretKey.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_PROTOCOL_SECRETKEY_H_INCLUDED #define RIPPLE_PROTOCOL_SECRETKEY_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/Seed.h b/include/xrpl/protocol/Seed.h similarity index 96% rename from src/ripple/protocol/Seed.h rename to include/xrpl/protocol/Seed.h index 2ebc64970f0..1d0a736db8a 100644 --- a/src/ripple/protocol/Seed.h +++ b/include/xrpl/protocol/Seed.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_SEED_H_INCLUDED #define RIPPLE_PROTOCOL_SEED_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/SeqProxy.h b/include/xrpl/protocol/SeqProxy.h similarity index 100% rename from src/ripple/protocol/SeqProxy.h rename to include/xrpl/protocol/SeqProxy.h diff --git a/src/ripple/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h similarity index 82% rename from src/ripple/protocol/Serializer.h rename to include/xrpl/protocol/Serializer.h index e3ef00eaf65..0e96078ed14 100644 --- a/src/ripple/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -20,15 +20,15 @@ #ifndef RIPPLE_PROTOCOL_SERIALIZER_H_INCLUDED #define RIPPLE_PROTOCOL_SERIALIZER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -83,12 +83,43 @@ class Serializer add8(unsigned char i); int add16(std::uint16_t i); + + template + requires(std::is_same_v< + std::make_unsigned_t>, + std::uint32_t>) int - add32(std::uint32_t i); // ledger indexes, account sequence, timestamps + add32(T i) + { + int ret = mData.size(); + mData.push_back(static_cast((i >> 24) & 0xff)); + mData.push_back(static_cast((i >> 16) & 0xff)); + mData.push_back(static_cast((i >> 8) & 0xff)); + mData.push_back(static_cast(i & 0xff)); + return ret; + } + int add32(HashPrefix p); + + template + requires(std::is_same_v< + std::make_unsigned_t>, + std::uint64_t>) int - add64(std::uint64_t i); // native currency amounts + add64(T i) + { + int ret = mData.size(); + mData.push_back(static_cast((i >> 56) & 0xff)); + mData.push_back(static_cast((i >> 48) & 0xff)); + mData.push_back(static_cast((i >> 40) & 0xff)); + mData.push_back(static_cast((i >> 32) & 0xff)); + mData.push_back(static_cast((i >> 24) & 0xff)); + mData.push_back(static_cast((i >> 16) & 0xff)); + mData.push_back(static_cast((i >> 8) & 0xff)); + mData.push_back(static_cast(i & 0xff)); + return ret; + } template int addInteger(Integer); @@ -353,9 +384,13 @@ class SerialIter std::uint32_t get32(); + std::int32_t + geti32(); std::uint64_t get64(); + std::int64_t + geti64(); template base_uint @@ -373,6 +408,12 @@ class SerialIter return getBitString<160>(); } + uint192 + get192() + { + return getBitString<192>(); + } + uint256 get256() { diff --git a/src/ripple/protocol/Sign.h b/include/xrpl/protocol/Sign.h similarity index 95% rename from src/ripple/protocol/Sign.h rename to include/xrpl/protocol/Sign.h index 6af29530486..30fbb26244b 100644 --- a/src/ripple/protocol/Sign.h +++ b/include/xrpl/protocol/Sign.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_SIGN_H_INCLUDED #define RIPPLE_PROTOCOL_SIGN_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/SystemParameters.h b/include/xrpl/protocol/SystemParameters.h similarity index 94% rename from src/ripple/protocol/SystemParameters.h rename to include/xrpl/protocol/SystemParameters.h index bc2f7136ff9..7531a0d5fb9 100644 --- a/src/ripple/protocol/SystemParameters.h +++ b/include/xrpl/protocol/SystemParameters.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED #define RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED -#include -#include +#include +#include #include #include @@ -72,9 +72,6 @@ static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{32570u}; * used in asserts and tests. */ static constexpr std::uint32_t XRP_LEDGER_EARLIEST_FEES{562177u}; -/** The number of ledgers in a shard */ -static constexpr std::uint32_t DEFAULT_LEDGERS_PER_SHARD{16384u}; - /** The minimum amount of support an amendment should have. @note This value is used by legacy code and will become obsolete diff --git a/src/ripple/protocol/TER.h b/include/xrpl/protocol/TER.h similarity index 92% rename from src/ripple/protocol/TER.h rename to include/xrpl/protocol/TER.h index 41c23a2d6a8..317e9c2c978 100644 --- a/src/ripple/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_TER_H_INCLUDED #define RIPPLE_PROTOCOL_TER_H_INCLUDED -#include -#include +#include +#include #include #include @@ -139,6 +139,8 @@ enum TEMcodes : TERUnderlyingType { temARRAY_EMPTY, temARRAY_TOO_LARGE, + + temBAD_TRANSFER_FEE, }; //------------------------------------------------------------------------------ @@ -182,6 +184,7 @@ enum TEFcodes : TERUnderlyingType { tefTOO_BIG, tefNO_TICKET, tefNFTOKEN_IS_NOT_TRANSFERABLE, + tefINVALID_LEDGER_FIX_TYPE, }; //------------------------------------------------------------------------------ @@ -338,7 +341,9 @@ enum TECcodes : TERUnderlyingType { tecINVALID_UPDATE_TIME = 188, tecTOKEN_PAIR_NOT_FOUND = 189, tecARRAY_EMPTY = 190, - tecARRAY_TOO_LARGE = 191 + tecARRAY_TOO_LARGE = 191, + tecLOCKED = 192, + tecBAD_CREDENTIALS = 193, }; //------------------------------------------------------------------------------ @@ -433,7 +438,8 @@ class TERSubset } // Conversion to bool. - explicit operator bool() const + explicit + operator bool() const { return code_ != tesSUCCESS; } @@ -479,60 +485,66 @@ class TERSubset // Only enabled if both arguments return int if TERtiInt is called with them. template constexpr auto -operator==(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator==(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) == TERtoInt(rhs); } template constexpr auto -operator!=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator!=(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) != TERtoInt(rhs); } template constexpr auto -operator<(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator<(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) < TERtoInt(rhs); } template constexpr auto -operator<=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator<=(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) <= TERtoInt(rhs); } template constexpr auto -operator>(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator>(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) > TERtoInt(rhs); } template constexpr auto -operator>=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, - bool> +operator>=(L const& lhs, R const& rhs) + -> std::enable_if_t< + std::is_same::value && + std::is_same::value, + bool> { return TERtoInt(lhs) >= TERtoInt(rhs); } diff --git a/src/ripple/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h similarity index 84% rename from src/ripple/protocol/TxFlags.h rename to include/xrpl/protocol/TxFlags.h index ba2b97562db..c293798f7d7 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -22,6 +22,8 @@ #include +#include + namespace ripple { /** Transaction flags. @@ -104,6 +106,7 @@ constexpr std::uint32_t tfPartialPayment = 0x00020000; constexpr std::uint32_t tfLimitQuality = 0x00040000; constexpr std::uint32_t tfPaymentMask = ~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect); +constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment); // TrustSet flags: constexpr std::uint32_t tfSetfAuth = 0x00010000; @@ -130,6 +133,29 @@ constexpr std::uint32_t const tfOnlyXRP = 0x00000002; constexpr std::uint32_t const tfTrustLine = 0x00000004; constexpr std::uint32_t const tfTransferable = 0x00000008; +// MPTokenIssuanceCreate flags: +// NOTE - there is intentionally no flag here for lsfMPTLocked, which this transaction cannot mutate. +constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock; +constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth; +constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; +constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade; +constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer; +constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; +constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = + ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); + +// MPTokenAuthorize flags: +constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; +constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUnauthorize); + +// MPTokenIssuanceSet flags: +constexpr std::uint32_t const tfMPTLock = 0x00000001; +constexpr std::uint32_t const tfMPTUnlock = 0x00000002; +constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); + +// MPTokenIssuanceDestroy flags: +constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; + // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between // accounts allowed a TrustLine to be added to the issuer of that token // without explicit permission from that issuer. This was enabled by @@ -181,10 +207,13 @@ constexpr std::uint32_t tfDepositSubTx = constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx); constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx); +// AMMClawback flags: +constexpr std::uint32_t tfClawTwoAssets = 0x00000001; +constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets); + // BridgeModify flags: constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); - // clang-format on } // namespace ripple diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h new file mode 100644 index 00000000000..2f9121cecb4 --- /dev/null +++ b/include/xrpl/protocol/TxFormats.h @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_TXFORMATS_H_INCLUDED +#define RIPPLE_PROTOCOL_TXFORMATS_H_INCLUDED + +#include + +namespace ripple { + +/** Transaction type identifiers. + + These are part of the binary message format. + + @ingroup protocol +*/ +/** Transaction type identifieers + + Each ledger object requires a unique type identifier, which is stored + within the object itself; this makes it possible to iterate the entire + ledger and determine each object's type and verify that the object you + retrieved from a given hash matches the expected type. + + @warning Since these values are included in transactions, which are signed + objects, and used by the code to determine the type of transaction + being invoked, they are part of the protocol. **Changing them + should be avoided because without special handling, this will + result in a hard fork.** + + @note When retiring types, the specific values should not be removed but + should be marked as [[deprecated]]. This is to avoid accidental + reuse of identifiers. + + @todo The C++ language does not enable checking for duplicate values + here. If it becomes possible then we should do this. + + @ingroup protocol +*/ +// clang-format off +enum TxType : std::uint16_t +{ + +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, fields) tag = value, + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + + /** This transaction type is deprecated; it is retained for historical purposes. */ + ttNICKNAME_SET [[deprecated("This transaction type is not supported and should not be used.")]] = 6, + + /** This transaction type is deprecated; it is retained for historical purposes. */ + ttCONTRACT [[deprecated("This transaction type is not supported and should not be used.")]] = 9, + + /** This identifier was never used, but the slot is reserved for historical purposes. */ + ttSPINAL_TAP [[deprecated("This transaction type is not supported and should not be used.")]] = 11, + + /** This transaction type installs a hook. */ + ttHOOK_SET [[maybe_unused]] = 22, +}; +// clang-format on + +/** Manages the list of known transaction formats. + */ +class TxFormats : public KnownFormats +{ +private: + /** Create the object. + This will load the object with all the known transaction formats. + */ + TxFormats(); + +public: + static TxFormats const& + getInstance(); +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h similarity index 95% rename from src/ripple/protocol/TxMeta.h rename to include/xrpl/protocol/TxMeta.h index 0a6578b1930..7932a4c55a3 100644 --- a/src/ripple/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_TX_TRANSACTIONMETA_H_INCLUDED #define RIPPLE_APP_TX_TRANSACTIONMETA_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h similarity index 88% rename from src/ripple/protocol/UintTypes.h rename to include/xrpl/protocol/UintTypes.h index 6fb685551f8..9a7284158e7 100644 --- a/src/ripple/protocol/UintTypes.h +++ b/include/xrpl/protocol/UintTypes.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_UINTTYPES_H_INCLUDED #define RIPPLE_PROTOCOL_UINTTYPES_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { namespace detail { @@ -58,6 +58,11 @@ using Currency = base_uint<160, detail::CurrencyTag>; /** NodeID is a 160-bit hash representing one node. */ using NodeID = base_uint<160, detail::NodeIDTag>; +/** MPTID is a 192-bit value representing MPT Issuance ID, + * which is a concatenation of a 32-bit sequence (big endian) + * and a 160-bit account */ +using MPTID = base_uint<192>; + /** XRP currency. */ Currency const& xrpCurrency(); @@ -129,6 +134,12 @@ struct hash : ripple::Directory::hasher explicit hash() = default; }; +template <> +struct hash : ripple::uint256::hasher +{ + explicit hash() = default; +}; + } // namespace std #endif diff --git a/src/ripple/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h similarity index 97% rename from src/ripple/protocol/XChainAttestations.h rename to include/xrpl/protocol/XChainAttestations.h index b99a0b59a4b..721950ca9c1 100644 --- a/src/ripple/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -20,16 +20,16 @@ #ifndef RIPPLE_PROTOCOL_STXATTESTATIONS_H_INCLUDED #define RIPPLE_PROTOCOL_STXATTESTATIONS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/impl/STVar.h b/include/xrpl/protocol/detail/STVar.h similarity index 80% rename from src/ripple/protocol/impl/STVar.h rename to include/xrpl/protocol/detail/STVar.h index 73863edbbe0..4a830cf8d7c 100644 --- a/src/ripple/protocol/impl/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -20,11 +20,13 @@ #ifndef RIPPLE_PROTOCOL_STVAR_H_INCLUDED #define RIPPLE_PROTOCOL_STVAR_H_INCLUDED -#include -#include -#include +#include +#include +#include +#include #include #include +#include #include #include @@ -44,6 +46,19 @@ struct nonPresentObject_t extern defaultObject_t defaultObject; extern nonPresentObject_t nonPresentObject; +// Concept to constrain STVar constructors, which +// instantiate ST* types from SerializedTypeID +// clang-format off +template +concept ValidConstructSTArgs = + (std::is_same_v< + std::tuple...>, + std::tuple> || + std::is_same_v< + std::tuple...>, + std::tuple>); +// clang-format on + // "variant" that can hold any type of serialized object // and includes a small-object allocation optimization. class STVar @@ -131,6 +146,15 @@ class STVar p_ = new (&d_) T(std::forward(args)...); } + /** Construct requested Serializable Type according to id. + * The variadic args are: (SField), or (SerialIter, SField). + * depth is ignored in former case. + */ + template + requires ValidConstructSTArgs + void + constructST(SerializedTypeID id, int depth, Args&&... arg); + bool on_heap() const { diff --git a/src/ripple/protocol/impl/b58_utils.h b/include/xrpl/protocol/detail/b58_utils.h similarity index 98% rename from src/ripple/protocol/impl/b58_utils.h rename to include/xrpl/protocol/detail/b58_utils.h index 1e7519f0eb0..b060fc7e166 100644 --- a/src/ripple/protocol/impl/b58_utils.h +++ b/include/xrpl/protocol/detail/b58_utils.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_B58_UTILS_H_INCLUDED #define RIPPLE_PROTOCOL_B58_UTILS_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro new file mode 100644 index 00000000000..31fc90cef80 --- /dev/null +++ b/include/xrpl/protocol/detail/features.macro @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#if !defined(XRPL_FEATURE) +#error "undefined macro: XRPL_FEATURE" +#endif +#if !defined(XRPL_FIX) +#error "undefined macro: XRPL_FIX" +#endif + +// Add new amendments to the top of this list. +// Keep it sorted in reverse chronological order. +// If you add an amendment here, then do not forget to increment `numFeatures` +// in include/xrpl/protocol/Feature.h. + +XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (AMMv1_2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) +// InvariantsV1_1 will be changes to Supported::yes when all the +// invariants expected to be included under it are complete. +XRPL_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (InnerObjTemplate2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (EnforceNFTokenTrustline, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (ReducedOffersV2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(NFTokenMintOffer, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (AMMv1_1, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (PreviousTxnID, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (XChainRewardRounding, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (EmptyDID, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (AMMOverflowOffer, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (InnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (NFTokenReserve, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (FillOrKill, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(DID, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(XChainBridge, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (NFTokenRemint, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (NonFungibleTokensV1_2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (UniversalNumber, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(ImmediateOfferKilled, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (TrustLinesToSelf, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(NonFungibleTokensV1_1, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(ExpandedSignerList, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(CheckCashMakesTrustLine, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (RmSmallIncreasedQOffers, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (STAmountCanonicalize, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(TicketBatch, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (AmendmentMajorityCalc, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(HardenedValidations, Supported::yes, VoteBehavior::DefaultYes) +// fix1781: XRPEndpointSteps should be included in the circular payment check +XRPL_FIX (1781, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes) +// fixQualityUpperBound should be activated before FlowCross +XRPL_FIX (QualityUpperBound, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (PayChanRecipientOwnerDir, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (CheckThreading, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (MasterKeyAsRegularKey, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (TakerDryOfferRemoval, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (1578, Supported::yes, VoteBehavior::DefaultYes) +// fix1515: Use liquidity from strands that consume max offers, but mark as dry +XRPL_FIX (1515, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(DepositPreauth, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (1623, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (1543, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (1571, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo) + + +// The following amendments are obsolete, but must remain supported +// because they could potentially get enabled. +// +// Obsolete features are (usually) not in the ledger, and may have code +// controlled by the feature. They need to be supported because at some +// time in the past, the feature was supported and votable, but never +// passed. So the feature needs to be supported in case it is ever +// enabled (added to the ledger). +// +// If a feature remains obsolete for long enough that no clients are able +// to vote for it, the feature can be removed (entirely?) from the code. +XRPL_FIX (NFTokenNegOffer, Supported::yes, VoteBehavior::Obsolete) +XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) +XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) +XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro new file mode 100644 index 00000000000..0cb1ec3416a --- /dev/null +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -0,0 +1,438 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#if !defined(LEDGER_ENTRY) +#error "undefined macro: LEDGER_ENTRY" +#endif + +/** + * These objects are listed in order of increasing ledger type ID. + * There are many gaps between these IDs. + * You are welcome to fill them with new object types. + */ + +/** A ledger object which identifies an offer to buy or sell an NFT. + + \sa keylet::nftoffer + */ +LEDGER_ENTRY(ltNFTOKEN_OFFER, 0x0037, NFTokenOffer, ({ + {sfOwner, soeREQUIRED}, + {sfNFTokenID, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfNFTokenOfferNode, soeREQUIRED}, + {sfDestination, soeOPTIONAL}, + {sfExpiration, soeOPTIONAL}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which describes a check. + + \sa keylet::check + */ +LEDGER_ENTRY(ltCHECK, 0x0043, Check, ({ + {sfAccount, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfSendMax, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfDestinationNode, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfInvoiceID, soeOPTIONAL}, + {sfSourceTag, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** The ledger object which tracks the DID. + + \sa keylet::did +*/ +LEDGER_ENTRY(ltDID, 0x0049, DID, ({ + {sfAccount, soeREQUIRED}, + {sfDIDDocument, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, + {sfData, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** The ledger object which tracks the current negative UNL state. + + \note This is a singleton: only one such object exists in the ledger. + + \sa keylet::negativeUNL + */ +LEDGER_ENTRY(ltNEGATIVE_UNL, 0x004e, NegativeUNL, ({ + {sfDisabledValidators, soeOPTIONAL}, + {sfValidatorToDisable, soeOPTIONAL}, + {sfValidatorToReEnable, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, +})) + +/** A ledger object which contains a list of NFTs + + \sa keylet::nftpage_min, keylet::nftpage_max, keylet::nftpage + */ +LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, ({ + {sfPreviousPageMin, soeOPTIONAL}, + {sfNextPageMin, soeOPTIONAL}, + {sfNFTokens, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which contains a signer list for an account. + + \sa keylet::signers + */ +// All fields are soeREQUIRED because there is always a SignerEntries. +// If there are no SignerEntries the node is deleted. +LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, ({ + {sfOwnerNode, soeREQUIRED}, + {sfSignerQuorum, soeREQUIRED}, + {sfSignerEntries, soeREQUIRED}, + {sfSignerListID, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which describes a ticket. + + \sa keylet::ticket + */ +LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ({ + {sfAccount, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfTicketSequence, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which describes an account. + + \sa keylet::account + */ +LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, ({ + {sfAccount, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfBalance, soeREQUIRED}, + {sfOwnerCount, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfAccountTxnID, soeOPTIONAL}, + {sfRegularKey, soeOPTIONAL}, + {sfEmailHash, soeOPTIONAL}, + {sfWalletLocator, soeOPTIONAL}, + {sfWalletSize, soeOPTIONAL}, + {sfMessageKey, soeOPTIONAL}, + {sfTransferRate, soeOPTIONAL}, + {sfDomain, soeOPTIONAL}, + {sfTickSize, soeOPTIONAL}, + {sfTicketCount, soeOPTIONAL}, + {sfNFTokenMinter, soeOPTIONAL}, + {sfMintedNFTokens, soeDEFAULT}, + {sfBurnedNFTokens, soeDEFAULT}, + {sfFirstNFTokenSequence, soeOPTIONAL}, + {sfAMMID, soeOPTIONAL}, +})) + +/** A ledger object which contains a list of object identifiers. + + \sa keylet::page, keylet::quality, keylet::book, keylet::next and + keylet::ownerDir + */ +LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, ({ + {sfOwner, soeOPTIONAL}, // for owner directories + {sfTakerPaysCurrency, soeOPTIONAL}, // order book directories + {sfTakerPaysIssuer, soeOPTIONAL}, // order book directories + {sfTakerGetsCurrency, soeOPTIONAL}, // order book directories + {sfTakerGetsIssuer, soeOPTIONAL}, // order book directories + {sfExchangeRate, soeOPTIONAL}, // order book directories + {sfIndexes, soeREQUIRED}, + {sfRootIndex, soeREQUIRED}, + {sfIndexNext, soeOPTIONAL}, + {sfIndexPrevious, soeOPTIONAL}, + {sfNFTokenID, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, +})) + +/** The ledger object which lists details about amendments on the network. + + \note This is a singleton: only one such object exists in the ledger. + + \sa keylet::amendments + */ +LEDGER_ENTRY(ltAMENDMENTS, 0x0066, Amendments, ({ + {sfAmendments, soeOPTIONAL}, // Enabled + {sfMajorities, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, +})) + +/** A ledger object that contains a list of ledger hashes. + + This type is used to store the ledger hashes which the protocol uses + to implement skip lists that allow for efficient backwards (and, in + theory, forward) forward iteration across large ledger ranges. + + \sa keylet::skip + */ +LEDGER_ENTRY(ltLEDGER_HASHES, 0x0068, LedgerHashes, ({ + {sfFirstLedgerSequence, soeOPTIONAL}, + {sfLastLedgerSequence, soeOPTIONAL}, + {sfHashes, soeREQUIRED}, +})) + +/** The ledger object which lists details about sidechains. + + \sa keylet::bridge +*/ +LEDGER_ENTRY(ltBRIDGE, 0x0069, Bridge, ({ + {sfAccount, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + {sfMinAccountCreateAmount, soeOPTIONAL}, + {sfXChainBridge, soeREQUIRED}, + {sfXChainClaimID, soeREQUIRED}, + {sfXChainAccountCreateCount, soeREQUIRED}, + {sfXChainAccountClaimCount, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which describes an offer on the DEX. + + \sa keylet::offer + */ +LEDGER_ENTRY(ltOFFER, 0x006f, Offer, ({ + {sfAccount, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfTakerPays, soeREQUIRED}, + {sfTakerGets, soeREQUIRED}, + {sfBookDirectory, soeREQUIRED}, + {sfBookNode, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, +})) + +/** A ledger object which describes a deposit preauthorization. + + \sa keylet::depositPreauth + */ +LEDGER_ENTRY(ltDEPOSIT_PREAUTH, 0x0070, DepositPreauth, ({ + {sfAccount, soeREQUIRED}, + {sfAuthorize, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfAuthorizeCredentials, soeOPTIONAL}, +})) + +/** A claim id for a cross chain transaction. + + \sa keylet::xChainClaimID +*/ +LEDGER_ENTRY(ltXCHAIN_OWNED_CLAIM_ID, 0x0071, XChainOwnedClaimID, ({ + {sfAccount, soeREQUIRED}, + {sfXChainBridge, soeREQUIRED}, + {sfXChainClaimID, soeREQUIRED}, + {sfOtherChainSource, soeREQUIRED}, + {sfXChainClaimAttestations, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which describes a bidirectional trust line. + + @note Per Vinnie Falco this should be renamed to ltTRUST_LINE + + \sa keylet::line + */ +LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, ({ + {sfBalance, soeREQUIRED}, + {sfLowLimit, soeREQUIRED}, + {sfHighLimit, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfLowNode, soeOPTIONAL}, + {sfLowQualityIn, soeOPTIONAL}, + {sfLowQualityOut, soeOPTIONAL}, + {sfHighNode, soeOPTIONAL}, + {sfHighQualityIn, soeOPTIONAL}, + {sfHighQualityOut, soeOPTIONAL}, +})) + +/** The ledger object which lists the network's fee settings. + + \note This is a singleton: only one such object exists in the ledger. + + \sa keylet::fees + */ +LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, ({ + // Old version uses raw numbers + {sfBaseFee, soeOPTIONAL}, + {sfReferenceFeeUnits, soeOPTIONAL}, + {sfReserveBase, soeOPTIONAL}, + {sfReserveIncrement, soeOPTIONAL}, + // New version uses Amounts + {sfBaseFeeDrops, soeOPTIONAL}, + {sfReserveBaseDrops, soeOPTIONAL}, + {sfReserveIncrementDrops, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, +})) + +/** A claim id for a cross chain create account transaction. + + \sa keylet::xChainCreateAccountClaimID +*/ +LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAccountClaimID, ({ + {sfAccount, soeREQUIRED}, + {sfXChainBridge, soeREQUIRED}, + {sfXChainAccountCreateCount, soeREQUIRED}, + {sfXChainCreateAccountAttestations, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object describing a single escrow. + + \sa keylet::escrow + */ +LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, ({ + {sfAccount, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfCondition, soeOPTIONAL}, + {sfCancelAfter, soeOPTIONAL}, + {sfFinishAfter, soeOPTIONAL}, + {sfSourceTag, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfDestinationNode, soeOPTIONAL}, +})) + +/** A ledger object describing a single unidirectional XRP payment channel. + + \sa keylet::payChan + */ +LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, ({ + {sfAccount, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfBalance, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfSettleDelay, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfCancelAfter, soeOPTIONAL}, + {sfSourceTag, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfDestinationNode, soeOPTIONAL}, +})) + +/** The ledger object which tracks the AMM. + + \sa keylet::amm +*/ +LEDGER_ENTRY(ltAMM, 0x0079, AMM, ({ + {sfAccount, soeREQUIRED}, + {sfTradingFee, soeDEFAULT}, + {sfVoteSlots, soeOPTIONAL}, + {sfAuctionSlot, soeOPTIONAL}, + {sfLPTokenBalance, soeREQUIRED}, + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, +})) + +/** A ledger object which tracks Oracle + \sa keylet::oracle + */ +LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, ({ + {sfOwner, soeREQUIRED}, + {sfProvider, soeREQUIRED}, + {sfPriceDataSeries, soeREQUIRED}, + {sfAssetClass, soeREQUIRED}, + {sfLastUpdateTime, soeREQUIRED}, + {sfURI, soeOPTIONAL}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which tracks MPTokenIssuance + \sa keylet::mptIssuance + */ +LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, ({ + {sfIssuer, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfTransferFee, soeDEFAULT}, + {sfOwnerNode, soeREQUIRED}, + {sfAssetScale, soeDEFAULT}, + {sfMaximumAmount, soeOPTIONAL}, + {sfOutstandingAmount, soeREQUIRED}, + {sfMPTokenMetadata, soeOPTIONAL}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which tracks MPToken + \sa keylet::mptoken + */ +LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, ({ + {sfAccount, soeREQUIRED}, + {sfMPTokenIssuanceID, soeREQUIRED}, + {sfMPTAmount, soeDEFAULT}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) + +/** A ledger object which tracks Credential + \sa keylet::credential + */ +LEDGER_ENTRY(ltCREDENTIAL, 0x0081, Credential, ({ + {sfSubject, soeREQUIRED}, + {sfIssuer, soeREQUIRED}, + {sfCredentialType, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, + {sfIssuerNode, soeREQUIRED}, + {sfSubjectNode, soeREQUIRED}, + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, +})) diff --git a/src/ripple/protocol/impl/secp256k1.h b/include/xrpl/protocol/detail/secp256k1.h similarity index 100% rename from src/ripple/protocol/impl/secp256k1.h rename to include/xrpl/protocol/detail/secp256k1.h diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro new file mode 100644 index 00000000000..8384025ee3b --- /dev/null +++ b/include/xrpl/protocol/detail/sfields.macro @@ -0,0 +1,377 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#if !defined(UNTYPED_SFIELD) +#error "undefined macro: UNTYPED_SFIELD" +#endif +#if !defined(TYPED_SFIELD) +#error "undefined macro: TYPED_SFIELD" +#endif + +// untyped +UNTYPED_SFIELD(sfLedgerEntry, LEDGERENTRY, 257) +UNTYPED_SFIELD(sfTransaction, TRANSACTION, 257) +UNTYPED_SFIELD(sfValidation, VALIDATION, 257) +UNTYPED_SFIELD(sfMetadata, METADATA, 257) + +// 8-bit integers (common) +TYPED_SFIELD(sfCloseResolution, UINT8, 1) +TYPED_SFIELD(sfMethod, UINT8, 2) +TYPED_SFIELD(sfTransactionResult, UINT8, 3) +TYPED_SFIELD(sfScale, UINT8, 4) +TYPED_SFIELD(sfAssetScale, UINT8, 5) + +// 8-bit integers (uncommon) +TYPED_SFIELD(sfTickSize, UINT8, 16) +TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) +TYPED_SFIELD(sfHookResult, UINT8, 18) +TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) + +// 16-bit integers (common) +TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never) +TYPED_SFIELD(sfTransactionType, UINT16, 2) +TYPED_SFIELD(sfSignerWeight, UINT16, 3) +TYPED_SFIELD(sfTransferFee, UINT16, 4) +TYPED_SFIELD(sfTradingFee, UINT16, 5) +TYPED_SFIELD(sfDiscountedFee, UINT16, 6) + +// 16-bit integers (uncommon) +TYPED_SFIELD(sfVersion, UINT16, 16) +TYPED_SFIELD(sfHookStateChangeCount, UINT16, 17) +TYPED_SFIELD(sfHookEmitCount, UINT16, 18) +TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19) +TYPED_SFIELD(sfHookApiVersion, UINT16, 20) +TYPED_SFIELD(sfLedgerFixType, UINT16, 21) + +// 32-bit integers (common) +TYPED_SFIELD(sfNetworkID, UINT32, 1) +TYPED_SFIELD(sfFlags, UINT32, 2) +TYPED_SFIELD(sfSourceTag, UINT32, 3) +TYPED_SFIELD(sfSequence, UINT32, 4) +TYPED_SFIELD(sfPreviousTxnLgrSeq, UINT32, 5, SField::sMD_DeleteFinal) +TYPED_SFIELD(sfLedgerSequence, UINT32, 6) +TYPED_SFIELD(sfCloseTime, UINT32, 7) +TYPED_SFIELD(sfParentCloseTime, UINT32, 8) +TYPED_SFIELD(sfSigningTime, UINT32, 9) +TYPED_SFIELD(sfExpiration, UINT32, 10) +TYPED_SFIELD(sfTransferRate, UINT32, 11) +TYPED_SFIELD(sfWalletSize, UINT32, 12) +TYPED_SFIELD(sfOwnerCount, UINT32, 13) +TYPED_SFIELD(sfDestinationTag, UINT32, 14) +TYPED_SFIELD(sfLastUpdateTime, UINT32, 15) + +// 32-bit integers (uncommon) +TYPED_SFIELD(sfHighQualityIn, UINT32, 16) +TYPED_SFIELD(sfHighQualityOut, UINT32, 17) +TYPED_SFIELD(sfLowQualityIn, UINT32, 18) +TYPED_SFIELD(sfLowQualityOut, UINT32, 19) +TYPED_SFIELD(sfQualityIn, UINT32, 20) +TYPED_SFIELD(sfQualityOut, UINT32, 21) +TYPED_SFIELD(sfStampEscrow, UINT32, 22) +TYPED_SFIELD(sfBondAmount, UINT32, 23) +TYPED_SFIELD(sfLoadFee, UINT32, 24) +TYPED_SFIELD(sfOfferSequence, UINT32, 25) +TYPED_SFIELD(sfFirstLedgerSequence, UINT32, 26) +TYPED_SFIELD(sfLastLedgerSequence, UINT32, 27) +TYPED_SFIELD(sfTransactionIndex, UINT32, 28) +TYPED_SFIELD(sfOperationLimit, UINT32, 29) +TYPED_SFIELD(sfReferenceFeeUnits, UINT32, 30) +TYPED_SFIELD(sfReserveBase, UINT32, 31) +TYPED_SFIELD(sfReserveIncrement, UINT32, 32) +TYPED_SFIELD(sfSetFlag, UINT32, 33) +TYPED_SFIELD(sfClearFlag, UINT32, 34) +TYPED_SFIELD(sfSignerQuorum, UINT32, 35) +TYPED_SFIELD(sfCancelAfter, UINT32, 36) +TYPED_SFIELD(sfFinishAfter, UINT32, 37) +TYPED_SFIELD(sfSignerListID, UINT32, 38) +TYPED_SFIELD(sfSettleDelay, UINT32, 39) +TYPED_SFIELD(sfTicketCount, UINT32, 40) +TYPED_SFIELD(sfTicketSequence, UINT32, 41) +TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42) +TYPED_SFIELD(sfMintedNFTokens, UINT32, 43) +TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44) +TYPED_SFIELD(sfHookStateCount, UINT32, 45) +TYPED_SFIELD(sfEmitGeneration, UINT32, 46) +// 47 reserved for Hooks +TYPED_SFIELD(sfVoteWeight, UINT32, 48) +TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) +TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) + +// 64-bit integers (common) +TYPED_SFIELD(sfIndexNext, UINT64, 1) +TYPED_SFIELD(sfIndexPrevious, UINT64, 2) +TYPED_SFIELD(sfBookNode, UINT64, 3) +TYPED_SFIELD(sfOwnerNode, UINT64, 4) +TYPED_SFIELD(sfBaseFee, UINT64, 5) +TYPED_SFIELD(sfExchangeRate, UINT64, 6) +TYPED_SFIELD(sfLowNode, UINT64, 7) +TYPED_SFIELD(sfHighNode, UINT64, 8) +TYPED_SFIELD(sfDestinationNode, UINT64, 9) +TYPED_SFIELD(sfCookie, UINT64, 10) +TYPED_SFIELD(sfServerVersion, UINT64, 11) +TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12) +TYPED_SFIELD(sfEmitBurden, UINT64, 13) + +// 64-bit integers (uncommon) +TYPED_SFIELD(sfHookOn, UINT64, 16) +TYPED_SFIELD(sfHookInstructionCount, UINT64, 17) +TYPED_SFIELD(sfHookReturnCode, UINT64, 18) +TYPED_SFIELD(sfReferenceCount, UINT64, 19) +TYPED_SFIELD(sfXChainClaimID, UINT64, 20) +TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21) +TYPED_SFIELD(sfXChainAccountClaimCount, UINT64, 22) +TYPED_SFIELD(sfAssetPrice, UINT64, 23) +TYPED_SFIELD(sfMaximumAmount, UINT64, 24, SField::sMD_BaseTen|SField::sMD_Default) +TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SField::sMD_Default) +TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default) +TYPED_SFIELD(sfIssuerNode, UINT64, 27) +TYPED_SFIELD(sfSubjectNode, UINT64, 28) + +// 128-bit +TYPED_SFIELD(sfEmailHash, UINT128, 1) + +// 160-bit (common) +TYPED_SFIELD(sfTakerPaysCurrency, UINT160, 1) +TYPED_SFIELD(sfTakerPaysIssuer, UINT160, 2) +TYPED_SFIELD(sfTakerGetsCurrency, UINT160, 3) +TYPED_SFIELD(sfTakerGetsIssuer, UINT160, 4) + +// 192-bit (common) +TYPED_SFIELD(sfMPTokenIssuanceID, UINT192, 1) + +// 256-bit (common) +TYPED_SFIELD(sfLedgerHash, UINT256, 1) +TYPED_SFIELD(sfParentHash, UINT256, 2) +TYPED_SFIELD(sfTransactionHash, UINT256, 3) +TYPED_SFIELD(sfAccountHash, UINT256, 4) +TYPED_SFIELD(sfPreviousTxnID, UINT256, 5, SField::sMD_DeleteFinal) +TYPED_SFIELD(sfLedgerIndex, UINT256, 6) +TYPED_SFIELD(sfWalletLocator, UINT256, 7) +TYPED_SFIELD(sfRootIndex, UINT256, 8, SField::sMD_Always) +TYPED_SFIELD(sfAccountTxnID, UINT256, 9) +TYPED_SFIELD(sfNFTokenID, UINT256, 10) +TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11) +TYPED_SFIELD(sfEmitNonce, UINT256, 12) +TYPED_SFIELD(sfEmitHookHash, UINT256, 13) +TYPED_SFIELD(sfAMMID, UINT256, 14) + +// 256-bit (uncommon) +TYPED_SFIELD(sfBookDirectory, UINT256, 16) +TYPED_SFIELD(sfInvoiceID, UINT256, 17) +TYPED_SFIELD(sfNickname, UINT256, 18) +TYPED_SFIELD(sfAmendment, UINT256, 19) +// 20 unused +TYPED_SFIELD(sfDigest, UINT256, 21) +TYPED_SFIELD(sfChannel, UINT256, 22) +TYPED_SFIELD(sfConsensusHash, UINT256, 23) +TYPED_SFIELD(sfCheckID, UINT256, 24) +TYPED_SFIELD(sfValidatedHash, UINT256, 25) +TYPED_SFIELD(sfPreviousPageMin, UINT256, 26) +TYPED_SFIELD(sfNextPageMin, UINT256, 27) +TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28) +TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29) +TYPED_SFIELD(sfHookStateKey, UINT256, 30) +TYPED_SFIELD(sfHookHash, UINT256, 31) +TYPED_SFIELD(sfHookNamespace, UINT256, 32) +TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) + +// number (common) +TYPED_SFIELD(sfNumber, NUMBER, 1) + +// currency amount (common) +TYPED_SFIELD(sfAmount, AMOUNT, 1) +TYPED_SFIELD(sfBalance, AMOUNT, 2) +TYPED_SFIELD(sfLimitAmount, AMOUNT, 3) +TYPED_SFIELD(sfTakerPays, AMOUNT, 4) +TYPED_SFIELD(sfTakerGets, AMOUNT, 5) +TYPED_SFIELD(sfLowLimit, AMOUNT, 6) +TYPED_SFIELD(sfHighLimit, AMOUNT, 7) +TYPED_SFIELD(sfFee, AMOUNT, 8) +TYPED_SFIELD(sfSendMax, AMOUNT, 9) +TYPED_SFIELD(sfDeliverMin, AMOUNT, 10) +TYPED_SFIELD(sfAmount2, AMOUNT, 11) +TYPED_SFIELD(sfBidMin, AMOUNT, 12) +TYPED_SFIELD(sfBidMax, AMOUNT, 13) + +// currency amount (uncommon) +TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16) +TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17) +TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18) +TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19) + +// Reserve 20 & 21 for Hooks. + +// currency amount (fees) +TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22) +TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23) +TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24) + +// currency amount (AMM) +TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25) +TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26) +TYPED_SFIELD(sfEPrice, AMOUNT, 27) +TYPED_SFIELD(sfPrice, AMOUNT, 28) +TYPED_SFIELD(sfSignatureReward, AMOUNT, 29) +TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30) +TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31) + +// variable length (common) +TYPED_SFIELD(sfPublicKey, VL, 1) +TYPED_SFIELD(sfMessageKey, VL, 2) +TYPED_SFIELD(sfSigningPubKey, VL, 3) +TYPED_SFIELD(sfTxnSignature, VL, 4, SField::sMD_Default, SField::notSigning) +TYPED_SFIELD(sfURI, VL, 5) +TYPED_SFIELD(sfSignature, VL, 6, SField::sMD_Default, SField::notSigning) +TYPED_SFIELD(sfDomain, VL, 7) +TYPED_SFIELD(sfFundCode, VL, 8) +TYPED_SFIELD(sfRemoveCode, VL, 9) +TYPED_SFIELD(sfExpireCode, VL, 10) +TYPED_SFIELD(sfCreateCode, VL, 11) +TYPED_SFIELD(sfMemoType, VL, 12) +TYPED_SFIELD(sfMemoData, VL, 13) +TYPED_SFIELD(sfMemoFormat, VL, 14) + +// variable length (uncommon) +TYPED_SFIELD(sfFulfillment, VL, 16) +TYPED_SFIELD(sfCondition, VL, 17) +TYPED_SFIELD(sfMasterSignature, VL, 18, SField::sMD_Default, SField::notSigning) +TYPED_SFIELD(sfUNLModifyValidator, VL, 19) +TYPED_SFIELD(sfValidatorToDisable, VL, 20) +TYPED_SFIELD(sfValidatorToReEnable, VL, 21) +TYPED_SFIELD(sfHookStateData, VL, 22) +TYPED_SFIELD(sfHookReturnString, VL, 23) +TYPED_SFIELD(sfHookParameterName, VL, 24) +TYPED_SFIELD(sfHookParameterValue, VL, 25) +TYPED_SFIELD(sfDIDDocument, VL, 26) +TYPED_SFIELD(sfData, VL, 27) +TYPED_SFIELD(sfAssetClass, VL, 28) +TYPED_SFIELD(sfProvider, VL, 29) +TYPED_SFIELD(sfMPTokenMetadata, VL, 30) +TYPED_SFIELD(sfCredentialType, VL, 31) + +// account (common) +TYPED_SFIELD(sfAccount, ACCOUNT, 1) +TYPED_SFIELD(sfOwner, ACCOUNT, 2) +TYPED_SFIELD(sfDestination, ACCOUNT, 3) +TYPED_SFIELD(sfIssuer, ACCOUNT, 4) +TYPED_SFIELD(sfAuthorize, ACCOUNT, 5) +TYPED_SFIELD(sfUnauthorize, ACCOUNT, 6) +// 7 unused +TYPED_SFIELD(sfRegularKey, ACCOUNT, 8) +TYPED_SFIELD(sfNFTokenMinter, ACCOUNT, 9) +TYPED_SFIELD(sfEmitCallback, ACCOUNT, 10) +TYPED_SFIELD(sfHolder, ACCOUNT, 11) + +// account (uncommon) +TYPED_SFIELD(sfHookAccount, ACCOUNT, 16) +TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18) +TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19) +TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20) +TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21) +TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22) +TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) +TYPED_SFIELD(sfSubject, ACCOUNT, 24) + +// vector of 256-bit +TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never) +TYPED_SFIELD(sfHashes, VECTOR256, 2) +TYPED_SFIELD(sfAmendments, VECTOR256, 3) +TYPED_SFIELD(sfNFTokenOffers, VECTOR256, 4) +TYPED_SFIELD(sfCredentialIDs, VECTOR256, 5) + +// path set +UNTYPED_SFIELD(sfPaths, PATHSET, 1) + +// currency +TYPED_SFIELD(sfBaseAsset, CURRENCY, 1) +TYPED_SFIELD(sfQuoteAsset, CURRENCY, 2) + +// issue +TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1) +TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2) +TYPED_SFIELD(sfAsset, ISSUE, 3) +TYPED_SFIELD(sfAsset2, ISSUE, 4) + +// bridge +TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1) + +// inner object +// OBJECT/1 is reserved for end of object +UNTYPED_SFIELD(sfTransactionMetaData, OBJECT, 2) +UNTYPED_SFIELD(sfCreatedNode, OBJECT, 3) +UNTYPED_SFIELD(sfDeletedNode, OBJECT, 4) +UNTYPED_SFIELD(sfModifiedNode, OBJECT, 5) +UNTYPED_SFIELD(sfPreviousFields, OBJECT, 6) +UNTYPED_SFIELD(sfFinalFields, OBJECT, 7) +UNTYPED_SFIELD(sfNewFields, OBJECT, 8) +UNTYPED_SFIELD(sfTemplateEntry, OBJECT, 9) +UNTYPED_SFIELD(sfMemo, OBJECT, 10) +UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11) +UNTYPED_SFIELD(sfNFToken, OBJECT, 12) +UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13) +UNTYPED_SFIELD(sfHook, OBJECT, 14) + +// inner object (uncommon) +UNTYPED_SFIELD(sfSigner, OBJECT, 16) +// 17 unused +UNTYPED_SFIELD(sfMajority, OBJECT, 18) +UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19) +UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20) +UNTYPED_SFIELD(sfHookExecution, OBJECT, 21) +UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22) +UNTYPED_SFIELD(sfHookParameter, OBJECT, 23) +UNTYPED_SFIELD(sfHookGrant, OBJECT, 24) +UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25) +UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26) +UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27) +UNTYPED_SFIELD(sfXChainClaimProofSig, OBJECT, 28) +UNTYPED_SFIELD(sfXChainCreateAccountProofSig, OBJECT, 29) +UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30) +UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31) +UNTYPED_SFIELD(sfPriceData, OBJECT, 32) +UNTYPED_SFIELD(sfCredential, OBJECT, 33) + +// array of objects (common) +// ARRAY/1 is reserved for end of array +// sfSigningAccounts has never been used. +//UNTYPED_SFIELD(sfSigningAccounts, ARRAY, 2) +UNTYPED_SFIELD(sfSigners, ARRAY, 3, SField::sMD_Default, SField::notSigning) +UNTYPED_SFIELD(sfSignerEntries, ARRAY, 4) +UNTYPED_SFIELD(sfTemplate, ARRAY, 5) +UNTYPED_SFIELD(sfNecessary, ARRAY, 6) +UNTYPED_SFIELD(sfSufficient, ARRAY, 7) +UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8) +UNTYPED_SFIELD(sfMemos, ARRAY, 9) +UNTYPED_SFIELD(sfNFTokens, ARRAY, 10) +UNTYPED_SFIELD(sfHooks, ARRAY, 11) +UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12) + +// array of objects (uncommon) +UNTYPED_SFIELD(sfMajorities, ARRAY, 16) +UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17) +UNTYPED_SFIELD(sfHookExecutions, ARRAY, 18) +UNTYPED_SFIELD(sfHookParameters, ARRAY, 19) +UNTYPED_SFIELD(sfHookGrants, ARRAY, 20) +UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21) +UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22) +// 23 unused +UNTYPED_SFIELD(sfPriceDataSeries, ARRAY, 24) +UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25) +UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26) +UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27) diff --git a/src/ripple/protocol/impl/token_errors.h b/include/xrpl/protocol/detail/token_errors.h similarity index 100% rename from src/ripple/protocol/impl/token_errors.h rename to include/xrpl/protocol/detail/token_errors.h diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro new file mode 100644 index 00000000000..4f4c8f12595 --- /dev/null +++ b/include/xrpl/protocol/detail/transactions.macro @@ -0,0 +1,486 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#if !defined(TRANSACTION) +#error "undefined macro: TRANSACTION" +#endif + +/** + * TRANSACTION(tag, value, name, fields) + * + * You must define a transactor class in the `ripple` namespace named `name`, + * and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`. + */ + +/** This transaction type executes a payment. */ +TRANSACTION(ttPAYMENT, 0, Payment, ({ + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, + {sfSendMax, soeOPTIONAL, soeMPTSupported}, + {sfPaths, soeDEFAULT}, + {sfInvoiceID, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, + {sfDeliverMin, soeOPTIONAL, soeMPTSupported}, + {sfCredentialIDs, soeOPTIONAL}, +})) + +/** This transaction type creates an escrow object. */ +TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({ + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfCondition, soeOPTIONAL}, + {sfCancelAfter, soeOPTIONAL}, + {sfFinishAfter, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, +})) + +/** This transaction type completes an existing escrow. */ +TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({ + {sfOwner, soeREQUIRED}, + {sfOfferSequence, soeREQUIRED}, + {sfFulfillment, soeOPTIONAL}, + {sfCondition, soeOPTIONAL}, + {sfCredentialIDs, soeOPTIONAL}, +})) + + +/** This transaction type adjusts various account settings. */ +TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({ + {sfEmailHash, soeOPTIONAL}, + {sfWalletLocator, soeOPTIONAL}, + {sfWalletSize, soeOPTIONAL}, + {sfMessageKey, soeOPTIONAL}, + {sfDomain, soeOPTIONAL}, + {sfTransferRate, soeOPTIONAL}, + {sfSetFlag, soeOPTIONAL}, + {sfClearFlag, soeOPTIONAL}, + {sfTickSize, soeOPTIONAL}, + {sfNFTokenMinter, soeOPTIONAL}, +})) + +/** This transaction type cancels an existing escrow. */ +TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({ + {sfOwner, soeREQUIRED}, + {sfOfferSequence, soeREQUIRED}, +})) + +/** This transaction type sets or clears an account's "regular key". */ +TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({ + {sfRegularKey, soeOPTIONAL}, +})) + +// 6 deprecated + +/** This transaction type creates an offer to trade one asset for another. */ +TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({ + {sfTakerPays, soeREQUIRED}, + {sfTakerGets, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfOfferSequence, soeOPTIONAL}, +})) + +/** This transaction type cancels existing offers to trade one asset for another. */ +TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({ + {sfOfferSequence, soeREQUIRED}, +})) + +// 9 deprecated + +/** This transaction type creates a new set of tickets. */ +TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({ + {sfTicketCount, soeREQUIRED}, +})) + +// 11 deprecated + +/** This transaction type modifies the signer list associated with an account. */ +// The SignerEntries are optional because a SignerList is deleted by +// setting the SignerQuorum to zero and omitting SignerEntries. +TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({ + {sfSignerQuorum, soeREQUIRED}, + {sfSignerEntries, soeOPTIONAL}, +})) + +/** This transaction type creates a new unidirectional XRP payment channel. */ +TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({ + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfSettleDelay, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfCancelAfter, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, +})) + +/** This transaction type funds an existing unidirectional XRP payment channel. */ +TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({ + {sfChannel, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, +})) + +/** This transaction type submits a claim against an existing unidirectional payment channel. */ +TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({ + {sfChannel, soeREQUIRED}, + {sfAmount, soeOPTIONAL}, + {sfBalance, soeOPTIONAL}, + {sfSignature, soeOPTIONAL}, + {sfPublicKey, soeOPTIONAL}, + {sfCredentialIDs, soeOPTIONAL}, +})) + +/** This transaction type creates a new check. */ +TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({ + {sfDestination, soeREQUIRED}, + {sfSendMax, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, + {sfInvoiceID, soeOPTIONAL}, +})) + +/** This transaction type cashes an existing check. */ +TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({ + {sfCheckID, soeREQUIRED}, + {sfAmount, soeOPTIONAL}, + {sfDeliverMin, soeOPTIONAL}, +})) + +/** This transaction type cancels an existing check. */ +TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({ + {sfCheckID, soeREQUIRED}, +})) + +/** This transaction type grants or revokes authorization to transfer funds. */ +TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({ + {sfAuthorize, soeOPTIONAL}, + {sfUnauthorize, soeOPTIONAL}, + {sfAuthorizeCredentials, soeOPTIONAL}, + {sfUnauthorizeCredentials, soeOPTIONAL}, +})) + +/** This transaction type modifies a trustline between two accounts. */ +TRANSACTION(ttTRUST_SET, 20, TrustSet, ({ + {sfLimitAmount, soeOPTIONAL}, + {sfQualityIn, soeOPTIONAL}, + {sfQualityOut, soeOPTIONAL}, +})) + +/** This transaction type deletes an existing account. */ +TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({ + {sfDestination, soeREQUIRED}, + {sfDestinationTag, soeOPTIONAL}, + {sfCredentialIDs, soeOPTIONAL}, +})) + +// 22 reserved + +/** This transaction mints a new NFT. */ +TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({ + {sfNFTokenTaxon, soeREQUIRED}, + {sfTransferFee, soeOPTIONAL}, + {sfIssuer, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, + {sfAmount, soeOPTIONAL}, + {sfDestination, soeOPTIONAL}, + {sfExpiration, soeOPTIONAL}, +})) + +/** This transaction burns (i.e. destroys) an existing NFT. */ +TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({ + {sfNFTokenID, soeREQUIRED}, + {sfOwner, soeOPTIONAL}, +})) + +/** This transaction creates a new offer to buy or sell an NFT. */ +TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({ + {sfNFTokenID, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfDestination, soeOPTIONAL}, + {sfOwner, soeOPTIONAL}, + {sfExpiration, soeOPTIONAL}, +})) + +/** This transaction cancels an existing offer to buy or sell an existing NFT. */ +TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({ + {sfNFTokenOffers, soeREQUIRED}, +})) + +/** This transaction accepts an existing offer to buy or sell an existing NFT. */ +TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({ + {sfNFTokenBuyOffer, soeOPTIONAL}, + {sfNFTokenSellOffer, soeOPTIONAL}, + {sfNFTokenBrokerFee, soeOPTIONAL}, +})) + +/** This transaction claws back issued tokens. */ +TRANSACTION(ttCLAWBACK, 30, Clawback, ({ + {sfAmount, soeREQUIRED, soeMPTSupported}, + {sfHolder, soeOPTIONAL}, +})) + +/** This transaction claws back tokens from an AMM pool. */ +TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({ + {sfHolder, soeREQUIRED}, + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfAmount, soeOPTIONAL}, +})) + +/** This transaction type creates an AMM instance */ +TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({ + {sfAmount, soeREQUIRED}, + {sfAmount2, soeREQUIRED}, + {sfTradingFee, soeREQUIRED}, +})) + +/** This transaction type deposits into an AMM instance */ +TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({ + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfAmount, soeOPTIONAL}, + {sfAmount2, soeOPTIONAL}, + {sfEPrice, soeOPTIONAL}, + {sfLPTokenOut, soeOPTIONAL}, + {sfTradingFee, soeOPTIONAL}, +})) + +/** This transaction type withdraws from an AMM instance */ +TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({ + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfAmount, soeOPTIONAL}, + {sfAmount2, soeOPTIONAL}, + {sfEPrice, soeOPTIONAL}, + {sfLPTokenIn, soeOPTIONAL}, +})) + +/** This transaction type votes for the trading fee */ +TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({ + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfTradingFee, soeREQUIRED}, +})) + +/** This transaction type bids for the auction slot */ +TRANSACTION(ttAMM_BID, 39, AMMBid, ({ + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfBidMin, soeOPTIONAL}, + {sfBidMax, soeOPTIONAL}, + {sfAuthAccounts, soeOPTIONAL}, +})) + +/** This transaction type deletes AMM in the empty state */ +TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({ + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, +})) + +/** This transactions creates a crosschain sequence number */ +TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, ({ + {sfXChainBridge, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + {sfOtherChainSource, soeREQUIRED}, +})) + +/** This transactions initiates a crosschain transaction */ +TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, ({ + {sfXChainBridge, soeREQUIRED}, + {sfXChainClaimID, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfOtherChainDestination, soeOPTIONAL}, +})) + +/** This transaction completes a crosschain transaction */ +TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, ({ + {sfXChainBridge, soeREQUIRED}, + {sfXChainClaimID, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfDestinationTag, soeOPTIONAL}, + {sfAmount, soeREQUIRED}, +})) + +/** This transaction initiates a crosschain account create transaction */ +TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, ({ + {sfXChainBridge, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, +})) + +/** This transaction adds an attestation to a claim */ +TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, ({ + {sfXChainBridge, soeREQUIRED}, + + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfSignature, soeREQUIRED}, + {sfOtherChainSource, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + + {sfXChainClaimID, soeREQUIRED}, + {sfDestination, soeOPTIONAL}, +})) + +/** This transaction adds an attestation to an account */ +TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, ({ + {sfXChainBridge, soeREQUIRED}, + + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfSignature, soeREQUIRED}, + {sfOtherChainSource, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + + {sfXChainAccountCreateCount, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, +})) + +/** This transaction modifies a sidechain */ +TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, ({ + {sfXChainBridge, soeREQUIRED}, + {sfSignatureReward, soeOPTIONAL}, + {sfMinAccountCreateAmount, soeOPTIONAL}, +})) + +/** This transactions creates a sidechain */ +TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, ({ + {sfXChainBridge, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + {sfMinAccountCreateAmount, soeOPTIONAL}, +})) + +/** This transaction type creates or updates a DID */ +TRANSACTION(ttDID_SET, 49, DIDSet, ({ + {sfDIDDocument, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, + {sfData, soeOPTIONAL}, +})) + +/** This transaction type deletes a DID */ +TRANSACTION(ttDID_DELETE, 50, DIDDelete, ({})) + +/** This transaction type creates an Oracle instance */ +TRANSACTION(ttORACLE_SET, 51, OracleSet, ({ + {sfOracleDocumentID, soeREQUIRED}, + {sfProvider, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, + {sfAssetClass, soeOPTIONAL}, + {sfLastUpdateTime, soeREQUIRED}, + {sfPriceDataSeries, soeREQUIRED}, +})) + +/** This transaction type deletes an Oracle instance */ +TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, ({ + {sfOracleDocumentID, soeREQUIRED}, +})) + +/** This transaction type fixes a problem in the ledger state */ +TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, ({ + {sfLedgerFixType, soeREQUIRED}, + {sfOwner, soeOPTIONAL}, +})) + +/** This transaction type creates a MPTokensIssuance instance */ +TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, ({ + {sfAssetScale, soeOPTIONAL}, + {sfTransferFee, soeOPTIONAL}, + {sfMaximumAmount, soeOPTIONAL}, + {sfMPTokenMetadata, soeOPTIONAL}, +})) + +/** This transaction type destroys a MPTokensIssuance instance */ +TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, ({ + {sfMPTokenIssuanceID, soeREQUIRED}, +})) + +/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ +TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, ({ + {sfMPTokenIssuanceID, soeREQUIRED}, + {sfHolder, soeOPTIONAL}, +})) + +/** This transaction type authorizes a MPToken instance */ +TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, ({ + {sfMPTokenIssuanceID, soeREQUIRED}, + {sfHolder, soeOPTIONAL}, +})) + +/** This transaction type create an Credential instance */ +TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, ({ + {sfSubject, soeREQUIRED}, + {sfCredentialType, soeREQUIRED}, + {sfExpiration, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, +})) + +/** This transaction type accept an Credential object */ +TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, ({ + {sfIssuer, soeREQUIRED}, + {sfCredentialType, soeREQUIRED}, +})) + +/** This transaction type delete an Credential object */ +TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, ({ + {sfSubject, soeOPTIONAL}, + {sfIssuer, soeOPTIONAL}, + {sfCredentialType, soeREQUIRED}, +})) + + +/** This system-generated transaction type is used to update the status of the various amendments. + + For details, see: https://xrpl.org/amendments.html + */ +TRANSACTION(ttAMENDMENT, 100, EnableAmendment, ({ + {sfLedgerSequence, soeREQUIRED}, + {sfAmendment, soeREQUIRED}, +})) + +/** This system-generated transaction type is used to update the network's fee settings. + + For details, see: https://xrpl.org/fee-voting.html + */ +TRANSACTION(ttFEE, 101, SetFee, ({ + {sfLedgerSequence, soeOPTIONAL}, + // Old version uses raw numbers + {sfBaseFee, soeOPTIONAL}, + {sfReferenceFeeUnits, soeOPTIONAL}, + {sfReserveBase, soeOPTIONAL}, + {sfReserveIncrement, soeOPTIONAL}, + // New version uses Amounts + {sfBaseFeeDrops, soeOPTIONAL}, + {sfReserveBaseDrops, soeOPTIONAL}, + {sfReserveIncrementDrops, soeOPTIONAL}, +})) + +/** This system-generated transaction type is used to update the network's negative UNL + + For details, see: https://xrpl.org/negative-unl.html + */ +TRANSACTION(ttUNL_MODIFY, 102, UNLModify, ({ + {sfUNLModifyDisabling, soeREQUIRED}, + {sfLedgerSequence, soeREQUIRED}, + {sfUNLModifyValidator, soeREQUIRED}, +})) + diff --git a/src/ripple/protocol/digest.h b/include/xrpl/protocol/digest.h similarity index 93% rename from src/ripple/protocol/digest.h rename to include/xrpl/protocol/digest.h index 6507057dcdc..b3c7a014100 100644 --- a/src/ripple/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_DIGEST_H_INCLUDED #define RIPPLE_PROTOCOL_DIGEST_H_INCLUDED -#include -#include +#include +#include #include #include #include @@ -55,7 +55,8 @@ struct openssl_ripemd160_hasher void operator()(void const* data, std::size_t size) noexcept; - explicit operator result_type() noexcept; + explicit + operator result_type() noexcept; private: char ctx_[96]; @@ -77,7 +78,8 @@ struct openssl_sha512_hasher void operator()(void const* data, std::size_t size) noexcept; - explicit operator result_type() noexcept; + explicit + operator result_type() noexcept; private: char ctx_[216]; @@ -99,7 +101,8 @@ struct openssl_sha256_hasher void operator()(void const* data, std::size_t size) noexcept; - explicit operator result_type() noexcept; + explicit + operator result_type() noexcept; private: char ctx_[112]; @@ -144,7 +147,8 @@ struct ripesha_hasher h_(data, size); } - explicit operator result_type() noexcept + explicit + operator result_type() noexcept { auto const d0 = sha256_hasher::result_type(h_); ripemd160_hasher rh; @@ -184,18 +188,21 @@ struct basic_sha512_half_hasher h_(data, size); } - explicit operator result_type() noexcept + explicit + operator result_type() noexcept { auto const digest = sha512_hasher::result_type(h_); return result_type::fromVoid(digest.data()); } private: - inline void erase(std::false_type) + inline void + erase(std::false_type) { } - inline void erase(std::true_type) + inline void + erase(std::true_type) { secure_erase(&h_, sizeof(h_)); } diff --git a/src/ripple/protocol/json_get_or_throw.h b/include/xrpl/protocol/json_get_or_throw.h similarity index 95% rename from src/ripple/protocol/json_get_or_throw.h rename to include/xrpl/protocol/json_get_or_throw.h index 86bd5924d3e..5277ee86484 100644 --- a/src/ripple/protocol/json_get_or_throw.h +++ b/include/xrpl/protocol/json_get_or_throw.h @@ -1,11 +1,11 @@ #ifndef PROTOCOL_GET_OR_THROW_H_ #define PROTOCOL_GET_OR_THROW_H_ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h new file mode 100644 index 00000000000..f9e0db24949 --- /dev/null +++ b/include/xrpl/protocol/jss.h @@ -0,0 +1,755 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_JSONFIELDS_H_INCLUDED +#define RIPPLE_PROTOCOL_JSONFIELDS_H_INCLUDED + +#include + +namespace ripple { +namespace jss { + +// JSON static strings + +#define JSS(x) constexpr ::Json::StaticString x(#x) + +/* These "StaticString" field names are used instead of string literals to + optimize the performance of accessing properties of Json::Value objects. + + Most strings have a trailing comment. Here is the legend: + + in: Read by the given RPC handler from its `Json::Value` parameter. + out: Assigned by the given RPC handler in the `Json::Value` it returns. + field: A field of at least one type of transaction. + RPC: Common properties of RPC requests and responses. + error: Common properties of RPC error responses. +*/ + +// clang-format off +JSS(AL_size); // out: GetCounts +JSS(AL_hit_rate); // out: GetCounts +JSS(Account); // in: TransactionSign; field. +JSS(AccountRoot); // ledger type. +JSS(AMM); // ledger type +JSS(AMMID); // field +JSS(Amendments); // ledger type. +JSS(Amount); // in: TransactionSign; field. +JSS(Amount2); // in/out: AMM IOU/XRP pool, deposit, withdraw amount +JSS(Asset); // in: AMM Asset1 +JSS(Asset2); // in: AMM Asset2 +JSS(AssetClass); // in: Oracle +JSS(AssetPrice); // in: Oracle +JSS(AuthAccount); // in: AMM Auction Slot +JSS(AuthAccounts); // in: AMM Auction Slot +JSS(BaseAsset); // in: Oracle +JSS(BidMax); // in: AMM Bid +JSS(BidMin); // in: AMM Bid +JSS(Bridge); // ledger type. +JSS(Check); // ledger type. +JSS(ClearFlag); // field. +JSS(Credential); // ledger type. +JSS(DID); // ledger type. +JSS(DeliverMax); // out: alias to Amount +JSS(DeliverMin); // in: TransactionSign +JSS(Destination); // in: TransactionSign; field. +JSS(DirectoryNode); // ledger type. +JSS(EPrice); // in: AMM Deposit option +JSS(Escrow); // ledger type. +JSS(Fee); // in/out: TransactionSign; field. +JSS(FeeSettings); // ledger type. +JSS(Flags); // in/out: TransactionSign; field. +JSS(Holder); // field. +JSS(Invalid); // +JSS(Issuer); // in: Credential transactions +JSS(LastLedgerSequence); // in: TransactionSign; field +JSS(LastUpdateTime); // field. +JSS(LedgerHashes); // ledger type. +JSS(LimitAmount); // field. +JSS(MPToken); // ledger type. +JSS(MPTokenIssuance); // ledger type. +JSS(NetworkID); // field. +JSS(NFTokenOffer); // ledger type. +JSS(NFTokenPage); // ledger type. +JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens +JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens +JSS(LPToken); // out: AMM Liquidity Provider tokens info +JSS(Offer); // ledger type. +JSS(OfferSequence); // field. +JSS(Oracle); // ledger type. +JSS(OracleDocumentID); // field +JSS(Owner); // field +JSS(Paths); // in/out: TransactionSign +JSS(PayChannel); // ledger type. +JSS(PriceDataSeries); // field. +JSS(PriceData); // field. +JSS(Provider); // field. +JSS(QuoteAsset); // in: Oracle. +JSS(RippleState); // ledger type. +JSS(SLE_hit_rate); // out: GetCounts. +JSS(Scale); // field. +JSS(SettleDelay); // in: TransactionSign +JSS(SendMax); // in: TransactionSign +JSS(Sequence); // in/out: TransactionSign; field. +JSS(SetFlag); // field. +JSS(SignerList); // ledger type. +JSS(SigningPubKey); // field. +JSS(Subject); // in: Credential transactions +JSS(TakerGets); // field. +JSS(TakerPays); // field. +JSS(Ticket); // ledger type. +JSS(TxnSignature); // field. +JSS(TradingFee); // in/out: AMM trading fee +JSS(TransactionType); // in: TransactionSign. +JSS(TransferRate); // in: TransferRate. +JSS(URI); // field. +JSS(VoteSlots); // out: AMM Vote +JSS(XChainOwnedClaimID); // ledger type. +JSS(XChainOwnedCreateAccountClaimID); // ledger type. +JSS(aborted); // out: InboundLedger +JSS(accepted); // out: LedgerToJson, OwnerInfo, SubmitTransaction +JSS(account); // in/out: many +JSS(accountState); // out: LedgerToJson +JSS(accountTreeHash); // out: ledger/Ledger.cpp +JSS(account_data); // out: AccountInfo +JSS(account_flags); // out: AccountInfo +JSS(account_hash); // out: LedgerToJson +JSS(account_id); // out: WalletPropose +JSS(account_nfts); // out: AccountNFTs +JSS(account_objects); // out: AccountObjects +JSS(account_root); // in: LedgerEntry +JSS(account_sequence_next); // out: SubmitTransaction +JSS(account_sequence_available); // out: SubmitTransaction +JSS(account_history_tx_stream); // in: Subscribe, Unsubscribe +JSS(account_history_tx_index); // out: Account txn history subscribe + +JSS(account_history_tx_first); // out: Account txn history subscribe +JSS(account_history_boundary); // out: Account txn history subscribe +JSS(accounts); // in: LedgerEntry, Subscribe, + // handlers/Ledger, Unsubscribe +JSS(accounts_proposed); // in: Subscribe, Unsubscribe +JSS(action); +JSS(acquiring); // out: LedgerRequest +JSS(address); // out: PeerImp +JSS(affected); // out: AcceptedLedgerTx +JSS(age); // out: NetworkOPs, Peers +JSS(alternatives); // out: PathRequest, RipplePathFind +JSS(amendment_blocked); // out: NetworkOPs +JSS(amendments); // in: AccountObjects, out: NetworkOPs +JSS(amm); // out: amm_info +JSS(amm_account); // in: amm_info +JSS(amount); // out: AccountChannels, amm_info +JSS(amount2); // out: amm_info +JSS(api_version); // in: many, out: Version +JSS(api_version_low); // out: Version +JSS(applied); // out: SubmitTransaction +JSS(asks); // out: Subscribe +JSS(asset); // in: amm_info +JSS(asset2); // in: amm_info +JSS(assets); // out: GatewayBalances +JSS(asset_frozen); // out: amm_info +JSS(asset2_frozen); // out: amm_info +JSS(attestations); +JSS(attestation_reward_account); +JSS(auction_slot); // out: amm_info +JSS(authorized); // out: AccountLines +JSS(authorized_credentials); // in: ledger_entry DepositPreauth +JSS(auth_accounts); // out: amm_info +JSS(auth_change); // out: AccountInfo +JSS(auth_change_queued); // out: AccountInfo +JSS(available); // out: ValidatorList +JSS(avg_bps_recv); // out: Peers +JSS(avg_bps_sent); // out: Peers +JSS(balance); // out: AccountLines +JSS(balances); // out: GatewayBalances +JSS(base); // out: LogLevel +JSS(base_asset); // in: get_aggregate_price +JSS(base_fee); // out: NetworkOPs +JSS(base_fee_xrp); // out: NetworkOPs +JSS(bids); // out: Subscribe +JSS(binary); // in: AccountTX, LedgerEntry, + // AccountTxOld, Tx LedgerData +JSS(blob); // out: ValidatorList +JSS(blobs_v2); // out: ValidatorList + // in: UNL +JSS(books); // in: Subscribe, Unsubscribe +JSS(both); // in: Subscribe, Unsubscribe +JSS(both_sides); // in: Subscribe, Unsubscribe +JSS(broadcast); // out: SubmitTransaction +JSS(bridge); // in: LedgerEntry +JSS(bridge_account); // in: LedgerEntry +JSS(build_path); // in: TransactionSign +JSS(build_version); // out: NetworkOPs +JSS(cancel_after); // out: AccountChannels +JSS(can_delete); // out: CanDelete +JSS(mpt_amount); // out: mpt_holders +JSS(mpt_issuance); // in: LedgerEntry, AccountObjects +JSS(mpt_issuance_id); // in: Payment, mpt_holders +JSS(mptoken); // in: LedgerEntry, AccountObjects +JSS(mptoken_index); // out: mpt_holders +JSS(changes); // out: BookChanges +JSS(channel_id); // out: AccountChannels +JSS(channels); // out: AccountChannels +JSS(check); // in: AccountObjects +JSS(check_nodes); // in: LedgerCleaner +JSS(clear); // in/out: FetchInfo +JSS(close); // out: BookChanges +JSS(close_flags); // out: LedgerToJson +JSS(close_time); // in: Application, out: NetworkOPs, + // RCLCxPeerPos, LedgerToJson +JSS(close_time_iso); // out: Tx, NetworkOPs, TransactionEntry + // AccountTx, LedgerToJson +JSS(close_time_estimated); // in: Application, out: LedgerToJson +JSS(close_time_human); // out: LedgerToJson +JSS(close_time_offset); // out: NetworkOPs +JSS(close_time_resolution); // in: Application; out: LedgerToJson +JSS(closed); // out: NetworkOPs, LedgerToJson, + // handlers/Ledger +JSS(closed_ledger); // out: NetworkOPs +JSS(cluster); // out: PeerImp +JSS(code); // out: errors +JSS(command); // in: RPCHandler +JSS(complete); // out: NetworkOPs, InboundLedger +JSS(complete_ledgers); // out: NetworkOPs, PeerImp +JSS(consensus); // out: NetworkOPs, LedgerConsensus +JSS(converge_time); // out: NetworkOPs +JSS(converge_time_s); // out: NetworkOPs +JSS(cookie); // out: NetworkOPs +JSS(count); // in: AccountTx*, ValidatorList +JSS(counters); // in/out: retrieve counters +JSS(credential); // in: LedgerEntry Credential +JSS(credentials); // in: deposit_authorized +JSS(credential_type); // in: LedgerEntry DepositPreauth +JSS(ctid); // in/out: Tx RPC +JSS(currency_a); // out: BookChanges +JSS(currency_b); // out: BookChanges +JSS(currency); // in: paths/PathRequest, STAmount + // out: STPathSet, STAmount, + // AccountLines +JSS(current); // out: OwnerInfo +JSS(current_activities); +JSS(current_ledger_size); // out: TxQ +JSS(current_queue_size); // out: TxQ +JSS(data); // out: LedgerData +JSS(date); // out: tx/Transaction, NetworkOPs +JSS(dbKBLedger); // out: getCounts +JSS(dbKBTotal); // out: getCounts +JSS(dbKBTransaction); // out: getCounts +JSS(debug_signing); // in: TransactionSign +JSS(deletion_blockers_only); // in: AccountObjects +JSS(delivered_amount); // out: insertDeliveredAmount +JSS(deposit_authorized); // out: deposit_authorized +JSS(deposit_preauth); // in: AccountObjects, LedgerData +JSS(deprecated); // out +JSS(descending); // in: AccountTx* +JSS(description); // in/out: Reservations +JSS(destination); // in: nft_buy_offers, nft_sell_offers +JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines + // out: AccountChannels +JSS(destination_amount); // in: PathRequest, RipplePathFind +JSS(destination_currencies); // in: PathRequest, RipplePathFind +JSS(destination_tag); // in: PathRequest + // out: AccountChannels +JSS(details); // out: Manifest, server_info +JSS(did); // in: LedgerEntry +JSS(dir_entry); // out: DirectoryEntryIterator +JSS(dir_index); // out: DirectoryEntryIterator +JSS(dir_root); // out: DirectoryEntryIterator +JSS(directory); // in: LedgerEntry +JSS(discounted_fee); // out: amm_info +JSS(domain); // out: ValidatorInfo, Manifest +JSS(drops); // out: TxQ +JSS(duration_us); // out: NetworkOPs +JSS(effective); // out: ValidatorList + // in: UNL +JSS(enabled); // out: AmendmentTable +JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit +JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit +JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit +JSS(entire_set); // out: get_aggregate_price +JSS(ephemeral_key); // out: ValidatorInfo + // in/out: Manifest +JSS(error); // out: error +JSS(errored); +JSS(error_code); // out: error +JSS(error_exception); // out: Submit +JSS(error_message); // out: error +JSS(escrow); // in: LedgerEntry +JSS(expand); // in: handler/Ledger +JSS(expected_date); // out: any (warnings) +JSS(expected_date_UTC); // out: any (warnings) +JSS(expected_ledger_size); // out: TxQ +JSS(expiration); // out: AccountOffers, AccountChannels, + // ValidatorList, amm_info +JSS(fail_hard); // in: Sign, Submit +JSS(failed); // out: InboundLedger +JSS(feature); // in: Feature +JSS(features); // out: Feature +JSS(fee); // out: NetworkOPs, Peers +JSS(fee_base); // out: NetworkOPs +JSS(fee_div_max); // in: TransactionSign +JSS(fee_level); // out: AccountInfo +JSS(fee_mult_max); // in: TransactionSign +JSS(fee_ref); // out: NetworkOPs, DEPRECATED +JSS(fetch_pack); // out: NetworkOPs +JSS(FIELDS); // out: RPC server_definitions + // matches definitions.json format +JSS(first); // out: rpc/Version +JSS(finished); +JSS(fix_txns); // in: LedgerCleaner +JSS(flags); // out: AccountOffers, + // NetworkOPs +JSS(forward); // in: AccountTx +JSS(freeze); // out: AccountLines +JSS(freeze_peer); // out: AccountLines +JSS(frozen_balances); // out: GatewayBalances +JSS(full); // in: LedgerClearer, handlers/Ledger +JSS(full_reply); // out: PathFind +JSS(fullbelow_size); // out: GetCounts +JSS(good); // out: RPCVersion +JSS(hash); // out: NetworkOPs, InboundLedger, + // LedgerToJson, STTx; field +JSS(hashes); // in: AccountObjects +JSS(have_header); // out: InboundLedger +JSS(have_state); // out: InboundLedger +JSS(have_transactions); // out: InboundLedger +JSS(high); // out: BookChanges +JSS(highest_sequence); // out: AccountInfo +JSS(highest_ticket); // out: AccountInfo +JSS(historical_perminute); // historical_perminute. +JSS(holders); // out: MPTHolders +JSS(hostid); // out: NetworkOPs +JSS(hotwallet); // in: GatewayBalances +JSS(id); // websocket. +JSS(ident); // in: AccountCurrencies, AccountInfo, + // OwnerInfo +JSS(ignore_default); // in: AccountLines +JSS(inLedger); // out: tx/Transaction +JSS(inbound); // out: PeerImp +JSS(index); // in: LedgerEntry + // out: STLedgerEntry, + // LedgerEntry, TxHistory, LedgerData +JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo +JSS(initial_sync_duration_us); +JSS(internal_command); // in: Internal +JSS(invalid_API_version); // out: Many, when a request has an invalid + // version +JSS(io_latency_ms); // out: NetworkOPs +JSS(ip); // in: Connect, out: OverlayImpl +JSS(is_burned); // out: nft_info (clio) +JSS(isSerialized); // out: RPC server_definitions + // matches definitions.json format +JSS(isSigningField); // out: RPC server_definitions + // matches definitions.json format +JSS(isVLEncoded); // out: RPC server_definitions + // matches definitions.json format +JSS(issuer); // in: RipplePathFind, Subscribe, + // Unsubscribe, BookOffers + // out: STPathSet, STAmount +JSS(job); +JSS(job_queue); +JSS(jobs); +JSS(jsonrpc); // json version +JSS(jq_trans_overflow); // JobQueue transaction limit overflow. +JSS(kept); // out: SubmitTransaction +JSS(key); // out +JSS(key_type); // in/out: WalletPropose, TransactionSign +JSS(latency); // out: PeerImp +JSS(last); // out: RPCVersion +JSS(last_close); // out: NetworkOPs +JSS(last_refresh_time); // out: ValidatorSite +JSS(last_refresh_status); // out: ValidatorSite +JSS(last_refresh_message); // out: ValidatorSite +JSS(ledger); // in: NetworkOPs, LedgerCleaner, + // RPCHelpers + // out: NetworkOPs, PeerImp +JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, + // LedgerCurrent, LedgerAccept, + // AccountLines +JSS(ledger_data); // out: LedgerHeader +JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, + // RipplePathFind, TransactionEntry, + // handlers/Ledger + // out: NetworkOPs, RPCHelpers, + // LedgerClosed, LedgerData, + // AccountLines +JSS(ledger_hit_rate); // out: GetCounts +JSS(ledger_index); // in/out: many +JSS(ledger_index_max); // in, out: AccountTx* +JSS(ledger_index_min); // in, out: AccountTx* +JSS(ledger_max); // in, out: AccountTx* +JSS(ledger_min); // in, out: AccountTx* +JSS(ledger_time); // out: NetworkOPs +JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions + // matches definitions.json format +JSS(levels); // LogLevels +JSS(limit); // in/out: AccountTx*, AccountOffers, + // AccountLines, AccountObjects + // in: LedgerData, BookOffers +JSS(limit_peer); // out: AccountLines +JSS(lines); // out: AccountLines +JSS(list); // out: ValidatorList +JSS(load); // out: NetworkOPs, PeerImp +JSS(load_base); // out: NetworkOPs +JSS(load_factor); // out: NetworkOPs +JSS(load_factor_cluster); // out: NetworkOPs +JSS(load_factor_fee_escalation); // out: NetworkOPs +JSS(load_factor_fee_queue); // out: NetworkOPs +JSS(load_factor_fee_reference); // out: NetworkOPs +JSS(load_factor_local); // out: NetworkOPs +JSS(load_factor_net); // out: NetworkOPs +JSS(load_factor_server); // out: NetworkOPs +JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs +JSS(local); // out: resource/Logic.h +JSS(local_txs); // out: GetCounts +JSS(local_static_keys); // out: ValidatorList +JSS(low); // out: BookChanges +JSS(lowest_sequence); // out: AccountInfo +JSS(lowest_ticket); // out: AccountInfo +JSS(lp_token); // out: amm_info +JSS(majority); // out: RPC feature +JSS(manifest); // out: ValidatorInfo, Manifest +JSS(marker); // in/out: AccountTx, AccountOffers, + // AccountLines, AccountObjects, + // LedgerData + // in: BookOffers +JSS(master_key); // out: WalletPropose, NetworkOPs, + // ValidatorInfo + // in/out: Manifest +JSS(master_seed); // out: WalletPropose +JSS(master_seed_hex); // out: WalletPropose +JSS(master_signature); // out: pubManifest +JSS(max_ledger); // in/out: LedgerCleaner +JSS(max_queue_size); // out: TxQ +JSS(max_spend_drops); // out: AccountInfo +JSS(max_spend_drops_total); // out: AccountInfo +JSS(mean); // out: get_aggregate_price +JSS(median); // out: get_aggregate_price +JSS(median_fee); // out: TxQ +JSS(median_level); // out: TxQ +JSS(message); // error. +JSS(meta); // out: NetworkOPs, AccountTx*, Tx +JSS(meta_blob); // out: NetworkOPs, AccountTx*, Tx +JSS(metaData); +JSS(metadata); // out: TransactionEntry +JSS(method); // RPC +JSS(methods); +JSS(metrics); // out: Peers +JSS(min_count); // in: GetCounts +JSS(min_ledger); // in: LedgerCleaner +JSS(minimum_fee); // out: TxQ +JSS(minimum_level); // out: TxQ +JSS(missingCommand); // error +JSS(name); // out: AmendmentTableImpl, PeerImp +JSS(needed_state_hashes); // out: InboundLedger +JSS(needed_transaction_hashes); // out: InboundLedger +JSS(network_id); // out: NetworkOPs +JSS(network_ledger); // out: NetworkOPs +JSS(next_refresh_time); // out: ValidatorSite +JSS(nft_id); // in: nft_sell_offers, nft_buy_offers +JSS(nft_offer); // in: LedgerEntry +JSS(nft_offer_index); // out nft_buy_offers, nft_sell_offers +JSS(nft_page); // in: LedgerEntry +JSS(nft_serial); // out: account_nfts +JSS(nft_taxon); // out: nft_info (clio) +JSS(nftoken_id); // out: insertNFTokenID +JSS(nftoken_ids); // out: insertNFTokenID +JSS(no_ripple); // out: AccountLines +JSS(no_ripple_peer); // out: AccountLines +JSS(node); // out: LedgerEntry +JSS(node_binary); // out: LedgerEntry +JSS(node_read_bytes); // out: GetCounts +JSS(node_read_errors); // out: GetCounts +JSS(node_read_retries); // out: GetCounts +JSS(node_reads_hit); // out: GetCounts +JSS(node_reads_total); // out: GetCounts +JSS(node_reads_duration_us); // out: GetCounts +JSS(node_size); // out: server_info +JSS(nodestore); // out: GetCounts +JSS(node_writes); // out: GetCounts +JSS(node_written_bytes); // out: GetCounts +JSS(node_writes_duration_us); // out: GetCounts +JSS(node_write_retries); // out: GetCounts +JSS(node_writes_delayed); // out::GetCounts +JSS(nth); // out: RPC server_definitions +JSS(nunl); // in: AccountObjects +JSS(obligations); // out: GatewayBalances +JSS(offer); // in: LedgerEntry +JSS(offers); // out: NetworkOPs, AccountOffers, Subscribe +JSS(offer_id); // out: insertNFTokenOfferID +JSS(offline); // in: TransactionSign +JSS(offset); // in/out: AccountTxOld +JSS(open); // out: handlers/Ledger +JSS(open_ledger_cost); // out: SubmitTransaction +JSS(open_ledger_fee); // out: TxQ +JSS(open_ledger_level); // out: TxQ +JSS(oracle); // in: LedgerEntry +JSS(oracles); // in: get_aggregate_price +JSS(oracle_document_id); // in: get_aggregate_price +JSS(owner); // in: LedgerEntry, out: NetworkOPs +JSS(owner_funds); // in/out: Ledger, NetworkOPs, AcceptedLedgerTx +JSS(page_index); +JSS(params); // RPC +JSS(parent_close_time); // out: LedgerToJson +JSS(parent_hash); // out: LedgerToJson +JSS(partition); // in: LogLevel +JSS(passphrase); // in: WalletPropose +JSS(password); // in: Subscribe +JSS(paths); // in: RipplePathFind +JSS(paths_canonical); // out: RipplePathFind +JSS(paths_computed); // out: PathRequest, RipplePathFind +JSS(payment_channel); // in: LedgerEntry +JSS(peer); // in: AccountLines +JSS(peer_authorized); // out: AccountLines +JSS(peer_id); // out: RCLCxPeerPos +JSS(peers); // out: InboundLedger, handlers/Peers, Overlay +JSS(peer_disconnects); // Severed peer connection counter. +JSS(peer_disconnects_resources); // Severed peer connections because of + // excess resource consumption. +JSS(port); // in: Connect, out: NetworkOPs +JSS(ports); // out: NetworkOPs +JSS(previous); // out: Reservations +JSS(previous_ledger); // out: LedgerPropose +JSS(price); // out: amm_info, AuctionSlot +JSS(proof); // in: BookOffers +JSS(propose_seq); // out: LedgerPropose +JSS(proposers); // out: NetworkOPs, LedgerConsensus +JSS(protocol); // out: NetworkOPs, PeerImp +JSS(proxied); // out: RPC ping +JSS(pubkey_node); // out: NetworkOPs +JSS(pubkey_publisher); // out: ValidatorList +JSS(pubkey_validator); // out: NetworkOPs, ValidatorList +JSS(public_key); // out: OverlayImpl, PeerImp, WalletPropose, + // ValidatorInfo + // in/out: Manifest +JSS(public_key_hex); // out: WalletPropose +JSS(published_ledger); // out: NetworkOPs +JSS(publisher_lists); // out: ValidatorList +JSS(quality); // out: NetworkOPs +JSS(quality_in); // out: AccountLines +JSS(quality_out); // out: AccountLines +JSS(queue); // in: AccountInfo +JSS(queue_data); // out: AccountInfo +JSS(queued); // out: SubmitTransaction +JSS(queued_duration_us); +JSS(quote_asset); // in: get_aggregate_price +JSS(random); // out: Random +JSS(raw_meta); // out: AcceptedLedgerTx +JSS(receive_currencies); // out: AccountCurrencies +JSS(reference_level); // out: TxQ +JSS(refresh_interval); // in: UNL +JSS(refresh_interval_min); // out: ValidatorSites +JSS(regular_seed); // in/out: LedgerEntry +JSS(remaining); // out: ValidatorList +JSS(remote); // out: Logic.h +JSS(request); // RPC +JSS(requested); // out: Manifest +JSS(reservations); // out: Reservations +JSS(reserve_base); // out: NetworkOPs +JSS(reserve_base_xrp); // out: NetworkOPs +JSS(reserve_inc); // out: NetworkOPs +JSS(reserve_inc_xrp); // out: NetworkOPs +JSS(response); // websocket +JSS(result); // RPC +JSS(ripple_lines); // out: NetworkOPs +JSS(ripple_state); // in: LedgerEntr +JSS(ripplerpc); // ripple RPC version +JSS(role); // out: Ping.cpp +JSS(rpc); +JSS(rt_accounts); // in: Subscribe, Unsubscribe +JSS(running_duration_us); +JSS(search_depth); // in: RipplePathFind +JSS(searched_all); // out: Tx +JSS(secret); // in: TransactionSign, + // ValidationCreate, ValidationSeed, + // channel_authorize +JSS(seed); // +JSS(seed_hex); // in: WalletPropose, TransactionSign +JSS(send_currencies); // out: AccountCurrencies +JSS(send_max); // in: PathRequest, RipplePathFind +JSS(seq); // in: LedgerEntry; + // out: NetworkOPs, RPCSub, AccountOffers, + // ValidatorList, ValidatorInfo, Manifest +JSS(sequence); // in: UNL +JSS(sequence_count); // out: AccountInfo +JSS(server_domain); // out: NetworkOPs +JSS(server_state); // out: NetworkOPs +JSS(server_state_duration_us);// out: NetworkOPs +JSS(server_status); // out: NetworkOPs +JSS(server_version); // out: NetworkOPs +JSS(settle_delay); // out: AccountChannels +JSS(severity); // in: LogLevel +JSS(signature); // out: NetworkOPs, ChannelAuthorize +JSS(signature_verified); // out: ChannelVerify +JSS(signing_key); // out: NetworkOPs +JSS(signing_keys); // out: ValidatorList +JSS(signing_time); // out: NetworkOPs +JSS(signer_list); // in: AccountObjects +JSS(signer_lists); // in/out: AccountInfo +JSS(size); // out: get_aggregate_price +JSS(snapshot); // in: Subscribe +JSS(source_account); // in: PathRequest, RipplePathFind +JSS(source_amount); // in: PathRequest, RipplePathFind +JSS(source_currencies); // in: PathRequest, RipplePathFind +JSS(source_tag); // out: AccountChannels +JSS(stand_alone); // out: NetworkOPs +JSS(standard_deviation); // out: get_aggregate_price +JSS(start); // in: TxHistory +JSS(started); +JSS(state); // out: Logic.h, ServerState, LedgerData +JSS(state_accounting); // out: NetworkOPs +JSS(state_now); // in: Subscribe +JSS(status); // error +JSS(stop); // in: LedgerCleaner +JSS(stop_history_tx_only); // in: Unsubscribe, stop history tx stream +JSS(streams); // in: Subscribe, Unsubscribe +JSS(strict); // in: AccountCurrencies, AccountInfo +JSS(sub_index); // in: LedgerEntry +JSS(subcommand); // in: PathFind +JSS(subject); // in: LedgerEntry Credential +JSS(success); // rpc +JSS(supported); // out: AmendmentTableImpl +JSS(sync_mode); // in: Submit +JSS(system_time_offset); // out: NetworkOPs +JSS(tag); // out: Peers +JSS(taker); // in: Subscribe, BookOffers +JSS(taker_gets); // in: Subscribe, Unsubscribe, BookOffers +JSS(taker_gets_funded); // out: NetworkOPs +JSS(taker_pays); // in: Subscribe, Unsubscribe, BookOffers +JSS(taker_pays_funded); // out: NetworkOPs +JSS(threshold); // in: Blacklist +JSS(ticket); // in: AccountObjects +JSS(ticket_count); // out: AccountInfo +JSS(ticket_seq); // in: LedgerEntry +JSS(time); +JSS(timeouts); // out: InboundLedger +JSS(time_threshold); // in/out: Oracle aggregate +JSS(time_interval); // out: AMM Auction Slot +JSS(track); // out: PeerImp +JSS(traffic); // out: Overlay +JSS(trim); // in: get_aggregate_price +JSS(trimmed_set); // out: get_aggregate_price +JSS(total); // out: counters +JSS(total_bytes_recv); // out: Peers +JSS(total_bytes_sent); // out: Peers +JSS(total_coins); // out: LedgerToJson +JSS(trading_fee); // out: amm_info +JSS(transTreeHash); // out: ledger/Ledger.cpp +JSS(transaction); // in: Tx + // out: NetworkOPs, AcceptedLedgerTx, +JSS(transaction_hash); // out: RCLCxPeerPos, LedgerToJson +JSS(transactions); // out: LedgerToJson, + // in: AccountTx*, Unsubscribe +JSS(TRANSACTION_RESULTS); // out: RPC server_definitions + // matches definitions.json format +JSS(TRANSACTION_TYPES); // out: RPC server_definitions + // matches definitions.json format +JSS(TYPES); // out: RPC server_definitions + // matches definitions.json format +JSS(transfer_rate); // out: nft_info (clio) +JSS(transitions); // out: NetworkOPs +JSS(treenode_cache_size); // out: GetCounts +JSS(treenode_track_size); // out: GetCounts +JSS(trusted); // out: UnlList +JSS(trusted_validator_keys); // out: ValidatorList +JSS(tx); // out: STTx, AccountTx* +JSS(tx_blob); // in/out: Submit, + // in: TransactionSign, AccountTx* +JSS(tx_hash); // in: TransactionEntry +JSS(tx_json); // in/out: TransactionSign + // out: TransactionEntry +JSS(tx_signing_hash); // out: TransactionSign +JSS(tx_unsigned); // out: TransactionSign +JSS(txn_count); // out: NetworkOPs +JSS(txr_tx_cnt); // out: protocol message tx's count +JSS(txr_tx_sz); // out: protocol message tx's size +JSS(txr_have_txs_cnt); // out: protocol message have tx count +JSS(txr_have_txs_sz); // out: protocol message have tx size +JSS(txr_get_ledger_cnt); // out: protocol message get ledger count +JSS(txr_get_ledger_sz); // out: protocol message get ledger size +JSS(txr_ledger_data_cnt); // out: protocol message ledger data count +JSS(txr_ledger_data_sz); // out: protocol message ledger data size +JSS(txr_transactions_cnt); // out: protocol message get object count +JSS(txr_transactions_sz); // out: protocol message get object size +JSS(txr_selected_cnt); // out: selected peers count +JSS(txr_suppressed_cnt); // out: suppressed peers count +JSS(txr_not_enabled_cnt); // out: peers with tx reduce-relay disabled count +JSS(txr_missing_tx_freq); // out: missing tx frequency average +JSS(txs); // out: TxHistory +JSS(type); // in: AccountObjects + // out: NetworkOPs, RPC server_definitions + // OverlayImpl, Logic +JSS(type_hex); // out: STPathSet +JSS(unl); // out: UnlList +JSS(unlimited); // out: Connection.h +JSS(uptime); // out: GetCounts +JSS(uri); // out: ValidatorSites +JSS(url); // in/out: Subscribe, Unsubscribe +JSS(url_password); // in: Subscribe +JSS(url_username); // in: Subscribe +JSS(urlgravatar); // +JSS(username); // in: Subscribe +JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx* + // Tx +JSS(validator_list_expires); // out: NetworkOps, ValidatorList +JSS(validator_list); // out: NetworkOps, ValidatorList +JSS(validators); +JSS(validated_hash); // out: NetworkOPs +JSS(validated_ledger); // out: NetworkOPs +JSS(validated_ledger_index); // out: SubmitTransaction +JSS(validated_ledgers); // out: NetworkOPs +JSS(validation_key); // out: ValidationCreate, ValidationSeed +JSS(validation_private_key); // out: ValidationCreate +JSS(validation_public_key); // out: ValidationCreate, ValidationSeed +JSS(validation_quorum); // out: NetworkOPs +JSS(validation_seed); // out: ValidationCreate, ValidationSeed +JSS(validations); // out: AmendmentTableImpl +JSS(validator_sites); // out: ValidatorSites +JSS(value); // out: STAmount +JSS(version); // out: RPCVersion +JSS(vetoed); // out: AmendmentTableImpl +JSS(volume_a); // out: BookChanges +JSS(volume_b); // out: BookChanges +JSS(vote); // in: Feature +JSS(vote_slots); // out: amm_info +JSS(vote_weight); // out: amm_info +JSS(warning); // rpc: +JSS(warnings); // out: server_info, server_state +JSS(workers); +JSS(write_load); // out: GetCounts +JSS(xchain_owned_claim_id); // in: LedgerEntry, AccountObjects +JSS(xchain_owned_create_account_claim_id); // in: LedgerEntry +JSS(NegativeUNL); // out: ValidatorList; ledger type +// clang-format on + +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, fields) JSS(name); + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + +#undef JSS + +} // namespace jss +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/messages.h b/include/xrpl/protocol/messages.h similarity index 97% rename from src/ripple/protocol/messages.h rename to include/xrpl/protocol/messages.h index e9c9cf60b21..640305185ad 100644 --- a/src/ripple/protocol/messages.h +++ b/include/xrpl/protocol/messages.h @@ -31,6 +31,6 @@ #undef TYPE_BOOL #endif -#include +#include #endif diff --git a/src/ripple/protocol/nft.h b/include/xrpl/protocol/nft.h similarity index 97% rename from src/ripple/protocol/nft.h rename to include/xrpl/protocol/nft.h index 2df8e0b89ce..839d872a63a 100644 --- a/src/ripple/protocol/nft.h +++ b/include/xrpl/protocol/nft.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_NFT_H_INCLUDED #define RIPPLE_PROTOCOL_NFT_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/protocol/nftPageMask.h b/include/xrpl/protocol/nftPageMask.h similarity index 97% rename from src/ripple/protocol/nftPageMask.h rename to include/xrpl/protocol/nftPageMask.h index a4890b460cd..1f8c39aa138 100644 --- a/src/ripple/protocol/nftPageMask.h +++ b/include/xrpl/protocol/nftPageMask.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PROTOCOL_NFT_PAGE_MASK_H_INCLUDED #define RIPPLE_PROTOCOL_NFT_PAGE_MASK_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/serialize.h b/include/xrpl/protocol/serialize.h similarity index 92% rename from src/ripple/protocol/serialize.h rename to include/xrpl/protocol/serialize.h index 93c59acfd0a..6d171c5e86b 100644 --- a/src/ripple/protocol/serialize.h +++ b/include/xrpl/protocol/serialize.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_SERIALIZE_H_INCLUDED #define RIPPLE_PROTOCOL_SERIALIZE_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/st.h b/include/xrpl/protocol/st.h similarity index 65% rename from src/ripple/protocol/st.h rename to include/xrpl/protocol/st.h index 7d43ef75f15..0035deaa1bc 100644 --- a/src/ripple/protocol/st.h +++ b/include/xrpl/protocol/st.h @@ -20,20 +20,21 @@ #ifndef RIPPLE_PROTOCOL_ST_H_INCLUDED #define RIPPLE_PROTOCOL_ST_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif diff --git a/src/ripple/protocol/tokens.h b/include/xrpl/protocol/tokens.h similarity index 97% rename from src/ripple/protocol/tokens.h rename to include/xrpl/protocol/tokens.h index f51c3f96f95..f0c4e79d43a 100644 --- a/src/ripple/protocol/tokens.h +++ b/include/xrpl/protocol/tokens.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PROTOCOL_TOKENS_H_INCLUDED #define RIPPLE_PROTOCOL_TOKENS_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/resource/Charge.h b/include/xrpl/resource/Charge.h similarity index 100% rename from src/ripple/resource/Charge.h rename to include/xrpl/resource/Charge.h diff --git a/src/ripple/resource/Consumer.h b/include/xrpl/resource/Consumer.h similarity index 96% rename from src/ripple/resource/Consumer.h rename to include/xrpl/resource/Consumer.h index 34fb02ee68f..00e4ad2c5f5 100644 --- a/src/ripple/resource/Consumer.h +++ b/include/xrpl/resource/Consumer.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_RESOURCE_CONSUMER_H_INCLUDED #define RIPPLE_RESOURCE_CONSUMER_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { namespace Resource { diff --git a/src/ripple/resource/Disposition.h b/include/xrpl/resource/Disposition.h similarity index 100% rename from src/ripple/resource/Disposition.h rename to include/xrpl/resource/Disposition.h diff --git a/src/ripple/resource/Fees.h b/include/xrpl/resource/Fees.h similarity index 56% rename from src/ripple/resource/Fees.h rename to include/xrpl/resource/Fees.h index d3fd5f72c49..1eb1a9bd725 100644 --- a/src/ripple/resource/Fees.h +++ b/include/xrpl/resource/Fees.h @@ -20,48 +20,43 @@ #ifndef RIPPLE_RESOURCE_FEES_H_INCLUDED #define RIPPLE_RESOURCE_FEES_H_INCLUDED -#include +#include namespace ripple { namespace Resource { +// clang-format off /** Schedule of fees charged for imposing load on the server. */ /** @{ */ -extern Charge const - feeInvalidRequest; // A request that we can immediately tell is invalid +extern Charge const feeInvalidRequest; // A request that we can immediately + // tell is invalid extern Charge const feeRequestNoReply; // A request that we cannot satisfy -extern Charge const feeInvalidSignature; // An object whose signature we had to - // check and it failed +extern Charge const feeInvalidSignature; // An object whose signature we had + // to check and it failed extern Charge const feeUnwantedData; // Data we have no use for -extern Charge const feeBadData; // Data we have to verify before rejecting +extern Charge const feeBadData; // Data we have to verify before + // rejecting // RPC loads -extern Charge const - feeInvalidRPC; // An RPC request that we can immediately tell is invalid. -extern Charge const feeReferenceRPC; // A default "reference" unspecified load -extern Charge const feeExceptionRPC; // An RPC load that causes an exception -extern Charge const feeLightRPC; // A normal RPC command -extern Charge const feeLowBurdenRPC; // A slightly burdensome RPC load -extern Charge const feeMediumBurdenRPC; // A somewhat burdensome RPC load -extern Charge const feeHighBurdenRPC; // A very burdensome RPC load -extern Charge const feePathFindUpdate; // An update to an existing PF request +extern Charge const feeInvalidRPC; // An RPC request that we can + // immediately tell is invalid. +extern Charge const feeReferenceRPC; // A default "reference" unspecified + // load +extern Charge const feeExceptionRPC; // RPC load that causes an exception +extern Charge const feeMediumBurdenRPC; // A somewhat burdensome RPC load +extern Charge const feeHighBurdenRPC; // A very burdensome RPC load // Peer loads extern Charge const feeLightPeer; // Requires no reply -extern Charge const feeLowBurdenPeer; // Quick/cheap, slight reply extern Charge const feeMediumBurdenPeer; // Requires some work extern Charge const feeHighBurdenPeer; // Extensive work -// Good things -extern Charge const - feeNewTrustedNote; // A new transaction/validation/proposal we trust -extern Charge const feeNewValidTx; // A new, valid transaction -extern Charge const feeSatisfiedRequest; // Data we requested - // Administrative -extern Charge const feeWarning; // The cost of receiving a warning -extern Charge const feeDrop; // The cost of being dropped for excess load +extern Charge const feeWarning; // The cost of receiving a warning +extern Charge const feeDrop; // The cost of being dropped for + // excess load /** @} */ +// clang-format on } // namespace Resource } // namespace ripple diff --git a/src/ripple/resource/Gossip.h b/include/xrpl/resource/Gossip.h similarity index 97% rename from src/ripple/resource/Gossip.h rename to include/xrpl/resource/Gossip.h index 742fad640af..6e2a86ecd7c 100644 --- a/src/ripple/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_RESOURCE_GOSSIP_H_INCLUDED #define RIPPLE_RESOURCE_GOSSIP_H_INCLUDED -#include +#include namespace ripple { namespace Resource { diff --git a/src/ripple/resource/README.md b/include/xrpl/resource/README.md similarity index 100% rename from src/ripple/resource/README.md rename to include/xrpl/resource/README.md diff --git a/src/ripple/resource/ResourceManager.h b/include/xrpl/resource/ResourceManager.h similarity index 88% rename from src/ripple/resource/ResourceManager.h rename to include/xrpl/resource/ResourceManager.h index 7471a37ab35..b33b141ee05 100644 --- a/src/ripple/resource/ResourceManager.h +++ b/include/xrpl/resource/ResourceManager.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_RESOURCE_MANAGER_H_INCLUDED #define RIPPLE_RESOURCE_MANAGER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -49,7 +49,7 @@ class Manager : public beast::PropertyStream::Source newInboundEndpoint( beast::IP::Endpoint const& address, bool const proxy, - boost::string_view const& forwardedFor) = 0; + std::string_view forwardedFor) = 0; /** Create a new endpoint keyed by outbound IP address and port. */ virtual Consumer diff --git a/src/ripple/resource/Types.h b/include/xrpl/resource/Types.h similarity index 100% rename from src/ripple/resource/Types.h rename to include/xrpl/resource/Types.h diff --git a/src/ripple/resource/impl/Entry.h b/include/xrpl/resource/detail/Entry.h similarity index 93% rename from src/ripple/resource/impl/Entry.h rename to include/xrpl/resource/detail/Entry.h index 126f2546456..d4f32080d9d 100644 --- a/src/ripple/resource/impl/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_RESOURCE_ENTRY_H_INCLUDED #define RIPPLE_RESOURCE_ENTRY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/resource/impl/Import.h b/include/xrpl/resource/detail/Import.h similarity index 95% rename from src/ripple/resource/impl/Import.h rename to include/xrpl/resource/detail/Import.h index 4eae3fafea0..52315644969 100644 --- a/src/ripple/resource/impl/Import.h +++ b/include/xrpl/resource/detail/Import.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RESOURCE_IMPORT_H_INCLUDED #define RIPPLE_RESOURCE_IMPORT_H_INCLUDED -#include -#include +#include +#include namespace ripple { namespace Resource { diff --git a/src/ripple/resource/impl/Key.h b/include/xrpl/resource/detail/Key.h similarity index 95% rename from src/ripple/resource/impl/Key.h rename to include/xrpl/resource/detail/Key.h index 929c13eebf6..3df2dce039d 100644 --- a/src/ripple/resource/impl/Key.h +++ b/include/xrpl/resource/detail/Key.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RESOURCE_KEY_H_INCLUDED #define RIPPLE_RESOURCE_KEY_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/resource/impl/Kind.h b/include/xrpl/resource/detail/Kind.h similarity index 100% rename from src/ripple/resource/impl/Kind.h rename to include/xrpl/resource/detail/Kind.h diff --git a/src/ripple/resource/impl/Logic.h b/include/xrpl/resource/detail/Logic.h similarity index 97% rename from src/ripple/resource/impl/Logic.h rename to include/xrpl/resource/detail/Logic.h index 07d89403b6a..a57e529e0a2 100644 --- a/src/ripple/resource/impl/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -20,17 +20,17 @@ #ifndef RIPPLE_RESOURCE_LOGIC_H_INCLUDED #define RIPPLE_RESOURCE_LOGIC_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/resource/impl/Tuning.h b/include/xrpl/resource/detail/Tuning.h similarity index 100% rename from src/ripple/resource/impl/Tuning.h rename to include/xrpl/resource/detail/Tuning.h diff --git a/src/ripple/server/Handoff.h b/include/xrpl/server/Handoff.h similarity index 98% rename from src/ripple/server/Handoff.h rename to include/xrpl/server/Handoff.h index 2b80d6179d6..f57c9ef57ec 100644 --- a/src/ripple/server/Handoff.h +++ b/include/xrpl/server/Handoff.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_SERVER_HANDOFF_H_INCLUDED #define RIPPLE_SERVER_HANDOFF_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/server/Port.h b/include/xrpl/server/Port.h similarity index 98% rename from src/ripple/server/Port.h rename to include/xrpl/server/Port.h index 9dccfdf9c08..4ab56efb459 100644 --- a/src/ripple/server/Port.h +++ b/include/xrpl/server/Port.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SERVER_PORT_H_INCLUDED #define RIPPLE_SERVER_PORT_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/server/Server.h b/include/xrpl/server/Server.h similarity index 90% rename from src/ripple/server/Server.h rename to include/xrpl/server/Server.h index a149e34fbfb..e8157c03c6c 100644 --- a/src/ripple/server/Server.h +++ b/include/xrpl/server/Server.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_SERVER_SERVER_H_INCLUDED #define RIPPLE_SERVER_SERVER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/server/Session.h b/include/xrpl/server/Session.h similarity index 96% rename from src/ripple/server/Session.h rename to include/xrpl/server/Session.h index 3b22c06b00b..25cf1ad747d 100644 --- a/src/ripple/server/Session.h +++ b/include/xrpl/server/Session.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_SERVER_SESSION_H_INCLUDED #define RIPPLE_SERVER_SESSION_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/server/SimpleWriter.h b/include/xrpl/server/SimpleWriter.h similarity index 98% rename from src/ripple/server/SimpleWriter.h rename to include/xrpl/server/SimpleWriter.h index 03582c00114..0e3f63b0d9d 100644 --- a/src/ripple/server/SimpleWriter.h +++ b/include/xrpl/server/SimpleWriter.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_SERVER_SIMPLEWRITER_H_INCLUDED #define RIPPLE_SERVER_SIMPLEWRITER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/server/WSSession.h b/include/xrpl/server/WSSession.h similarity index 97% rename from src/ripple/server/WSSession.h rename to include/xrpl/server/WSSession.h index 812099108a5..d739afcefb7 100644 --- a/src/ripple/server/WSSession.h +++ b/include/xrpl/server/WSSession.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_SERVER_WSSESSION_H_INCLUDED #define RIPPLE_SERVER_WSSESSION_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/server/Writer.h b/include/xrpl/server/Writer.h similarity index 100% rename from src/ripple/server/Writer.h rename to include/xrpl/server/Writer.h diff --git a/src/ripple/server/impl/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h similarity index 98% rename from src/ripple/server/impl/BaseHTTPPeer.h rename to include/xrpl/server/detail/BaseHTTPPeer.h index a6bbd18e1aa..32ec4d3009d 100644 --- a/src/ripple/server/impl/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_SERVER_BASEHTTPPEER_H_INCLUDED #define RIPPLE_SERVER_BASEHTTPPEER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -244,7 +244,7 @@ BaseHTTPPeer::close() return post( strand_, std::bind( - (void (BaseHTTPPeer::*)(void)) & BaseHTTPPeer::close, + (void(BaseHTTPPeer::*)(void)) & BaseHTTPPeer::close, impl().shared_from_this())); boost::beast::get_lowest_layer(impl().stream_).close(); } @@ -507,7 +507,7 @@ BaseHTTPPeer::close(bool graceful) return post( strand_, std::bind( - (void (BaseHTTPPeer::*)(bool)) & + (void(BaseHTTPPeer::*)(bool)) & BaseHTTPPeer::close, impl().shared_from_this(), graceful)); diff --git a/src/ripple/server/impl/BasePeer.h b/include/xrpl/server/detail/BasePeer.h similarity index 95% rename from src/ripple/server/impl/BasePeer.h rename to include/xrpl/server/detail/BasePeer.h index bc5375b87f7..a90eaabf6e4 100644 --- a/src/ripple/server/impl/BasePeer.h +++ b/include/xrpl/server/detail/BasePeer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_SERVER_BASEPEER_H_INCLUDED #define RIPPLE_SERVER_BASEPEER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h similarity index 98% rename from src/ripple/server/impl/BaseWSPeer.h rename to include/xrpl/server/detail/BaseWSPeer.h index fd1722df003..4c4049a8b9b 100644 --- a/src/ripple/server/impl/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_SERVER_BASEWSPEER_H_INCLUDED #define RIPPLE_SERVER_BASEWSPEER_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/Door.h b/include/xrpl/server/detail/Door.h similarity index 98% rename from src/ripple/server/impl/Door.h rename to include/xrpl/server/detail/Door.h index 94f39719569..20bdf421e38 100644 --- a/src/ripple/server/impl/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SERVER_DOOR_H_INCLUDED #define RIPPLE_SERVER_DOOR_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/JSONRPCUtil.h b/include/xrpl/server/detail/JSONRPCUtil.h similarity index 95% rename from src/ripple/server/impl/JSONRPCUtil.h rename to include/xrpl/server/detail/JSONRPCUtil.h index d6b67e123c4..7eb00096263 100644 --- a/src/ripple/server/impl/JSONRPCUtil.h +++ b/include/xrpl/server/detail/JSONRPCUtil.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SERVER_JSONRPCUTIL_H_INCLUDED #define RIPPLE_SERVER_JSONRPCUTIL_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/server/impl/LowestLayer.h b/include/xrpl/server/detail/LowestLayer.h similarity index 100% rename from src/ripple/server/impl/LowestLayer.h rename to include/xrpl/server/detail/LowestLayer.h diff --git a/src/ripple/server/impl/PlainHTTPPeer.h b/include/xrpl/server/detail/PlainHTTPPeer.h similarity index 97% rename from src/ripple/server/impl/PlainHTTPPeer.h rename to include/xrpl/server/detail/PlainHTTPPeer.h index 4c593ce9cb6..d4b24c01313 100644 --- a/src/ripple/server/impl/PlainHTTPPeer.h +++ b/include/xrpl/server/detail/PlainHTTPPeer.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_SERVER_PLAINHTTPPEER_H_INCLUDED #define RIPPLE_SERVER_PLAINHTTPPEER_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/server/impl/PlainWSPeer.h b/include/xrpl/server/detail/PlainWSPeer.h similarity index 98% rename from src/ripple/server/impl/PlainWSPeer.h rename to include/xrpl/server/detail/PlainWSPeer.h index 504b38baac7..8bcddee7e77 100644 --- a/src/ripple/server/impl/PlainWSPeer.h +++ b/include/xrpl/server/detail/PlainWSPeer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_SERVER_PLAINWSPEER_H_INCLUDED #define RIPPLE_SERVER_PLAINWSPEER_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/server/impl/SSLHTTPPeer.h b/include/xrpl/server/detail/SSLHTTPPeer.h similarity index 98% rename from src/ripple/server/impl/SSLHTTPPeer.h rename to include/xrpl/server/detail/SSLHTTPPeer.h index 8b5b4e38c4e..db6af46b5b3 100644 --- a/src/ripple/server/impl/SSLHTTPPeer.h +++ b/include/xrpl/server/detail/SSLHTTPPeer.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SERVER_SSLHTTPPEER_H_INCLUDED #define RIPPLE_SERVER_SSLHTTPPEER_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/SSLWSPeer.h b/include/xrpl/server/detail/SSLWSPeer.h similarity index 97% rename from src/ripple/server/impl/SSLWSPeer.h rename to include/xrpl/server/detail/SSLWSPeer.h index 28c3184fce6..9c88b275397 100644 --- a/src/ripple/server/impl/SSLWSPeer.h +++ b/include/xrpl/server/detail/SSLWSPeer.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SERVER_SSLWSPEER_H_INCLUDED #define RIPPLE_SERVER_SSLWSPEER_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h similarity index 96% rename from src/ripple/server/impl/ServerImpl.h rename to include/xrpl/server/detail/ServerImpl.h index a3abf78914e..00a9a263846 100644 --- a/src/ripple/server/impl/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SERVER_SERVERIMPL_H_INCLUDED #define RIPPLE_SERVER_SERVERIMPL_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/server/impl/io_list.h b/include/xrpl/server/detail/io_list.h similarity index 100% rename from src/ripple/server/impl/io_list.h rename to include/xrpl/server/detail/io_list.h diff --git a/src/ripple/basics/impl/Archive.cpp b/src/libxrpl/basics/Archive.cpp similarity index 98% rename from src/ripple/basics/impl/Archive.cpp rename to src/libxrpl/basics/Archive.cpp index 73e14a93606..e5672bd953f 100644 --- a/src/ripple/basics/impl/Archive.cpp +++ b/src/libxrpl/basics/Archive.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/basics/impl/BasicConfig.cpp b/src/libxrpl/basics/BasicConfig.cpp similarity index 98% rename from src/ripple/basics/impl/BasicConfig.cpp rename to src/libxrpl/basics/BasicConfig.cpp index f557d2e6d46..932eea346f6 100644 --- a/src/ripple/basics/impl/BasicConfig.cpp +++ b/src/libxrpl/basics/BasicConfig.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/basics/impl/CountedObject.cpp b/src/libxrpl/basics/CountedObject.cpp similarity index 97% rename from src/ripple/basics/impl/CountedObject.cpp rename to src/libxrpl/basics/CountedObject.cpp index 5b25091632d..c18152938a9 100644 --- a/src/ripple/basics/impl/CountedObject.cpp +++ b/src/libxrpl/basics/CountedObject.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include diff --git a/src/ripple/basics/impl/FileUtilities.cpp b/src/libxrpl/basics/FileUtilities.cpp similarity index 98% rename from src/ripple/basics/impl/FileUtilities.cpp rename to src/libxrpl/basics/FileUtilities.cpp index be60a3e5c3d..e1331433553 100644 --- a/src/ripple/basics/impl/FileUtilities.cpp +++ b/src/libxrpl/basics/FileUtilities.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/basics/impl/IOUAmount.cpp b/src/libxrpl/basics/IOUAmount.cpp similarity index 99% rename from src/ripple/basics/impl/IOUAmount.cpp rename to src/libxrpl/basics/IOUAmount.cpp index e3c3411057b..a24e1b917f7 100644 --- a/src/ripple/basics/impl/IOUAmount.cpp +++ b/src/libxrpl/basics/IOUAmount.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/basics/impl/Log.cpp b/src/libxrpl/basics/Log.cpp similarity index 99% rename from src/ripple/basics/impl/Log.cpp rename to src/libxrpl/basics/Log.cpp index c023bc16485..3d5963cc266 100644 --- a/src/ripple/basics/impl/Log.cpp +++ b/src/libxrpl/basics/Log.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/peerfinder/impl/Reporting.h b/src/libxrpl/basics/MPTAmount.cpp similarity index 59% rename from src/ripple/peerfinder/impl/Reporting.h rename to src/libxrpl/basics/MPTAmount.cpp index 25c36ea3f27..0481da67711 100644 --- a/src/ripple/peerfinder/impl/Reporting.h +++ b/src/libxrpl/basics/MPTAmount.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + Copyright (c) 2024 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,33 +17,52 @@ */ //============================================================================== -#ifndef RIPPLE_PEERFINDER_REPORTING_H_INCLUDED -#define RIPPLE_PEERFINDER_REPORTING_H_INCLUDED +#include namespace ripple { -namespace PeerFinder { -/** Severity levels for test reporting. - This allows more fine grained control over reporting for diagnostics. -*/ -struct Reporting +MPTAmount& +MPTAmount::operator+=(MPTAmount const& other) +{ + value_ += other.value(); + return *this; +} + +MPTAmount& +MPTAmount::operator-=(MPTAmount const& other) +{ + value_ -= other.value(); + return *this; +} + +MPTAmount +MPTAmount::operator-() const { - explicit Reporting() = default; + return MPTAmount{-value_}; +} - // Report simulation parameters - static bool const params = true; +bool +MPTAmount::operator==(MPTAmount const& other) const +{ + return value_ == other.value_; +} - // Report simulation crawl time-evolution - static bool const crawl = true; +bool +MPTAmount::operator==(value_type other) const +{ + return value_ == other; +} - // Report nodes aggregate statistics - static bool const nodes = true; +bool +MPTAmount::operator<(MPTAmount const& other) const +{ + return value_ < other.value_; +} - // Report nodes detailed information - static bool const dump_nodes = false; -}; +MPTAmount +MPTAmount::minPositiveAmount() +{ + return MPTAmount{1}; +} -} // namespace PeerFinder } // namespace ripple - -#endif diff --git a/src/ripple/basics/impl/Number.cpp b/src/libxrpl/basics/Number.cpp similarity index 99% rename from src/ripple/basics/impl/Number.cpp rename to src/libxrpl/basics/Number.cpp index 9b3247536f9..ebbfa0023c9 100644 --- a/src/ripple/basics/impl/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include @@ -504,6 +504,11 @@ Number::operator XRPAmount() const return XRPAmount{static_cast(*this)}; } +Number::operator MPTAmount() const +{ + return MPTAmount{static_cast(*this)}; +} + std::string to_string(Number const& amount) { diff --git a/src/ripple/basics/impl/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp similarity index 97% rename from src/ripple/basics/impl/ResolverAsio.cpp rename to src/libxrpl/basics/ResolverAsio.cpp index f75a390304b..7105eb12a74 100644 --- a/src/ripple/basics/impl/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -232,7 +232,8 @@ class ResolverAsioImpl : public ResolverAsio, //------------------------------------------------------------------------- // Resolver - void do_stop(CompletionCounter) + void + do_stop(CompletionCounter) { assert(m_stop_called == true); @@ -330,7 +331,8 @@ class ResolverAsioImpl : public ResolverAsio, std::string(port_first, port_last)); } - void do_work(CompletionCounter) + void + do_work(CompletionCounter) { if (m_stop_called == true) return; diff --git a/src/ripple/basics/impl/StringUtilities.cpp b/src/libxrpl/basics/StringUtilities.cpp similarity index 92% rename from src/ripple/basics/impl/StringUtilities.cpp rename to src/libxrpl/basics/StringUtilities.cpp index bebbe1ef80b..cd9bdfbd030 100644 --- a/src/ripple/basics/impl/StringUtilities.cpp +++ b/src/libxrpl/basics/StringUtilities.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -120,7 +120,7 @@ to_uint64(std::string const& s) } bool -isProperlyFormedTomlDomain(std::string const& domain) +isProperlyFormedTomlDomain(std::string_view domain) { // The domain must be between 4 and 128 characters long if (domain.size() < 4 || domain.size() > 128) @@ -143,7 +143,7 @@ isProperlyFormedTomlDomain(std::string const& domain) , boost::regex_constants::optimize); - return boost::regex_match(domain, re); + return boost::regex_match(domain.begin(), domain.end(), re); } } // namespace ripple diff --git a/src/ripple/basics/impl/UptimeClock.cpp b/src/libxrpl/basics/UptimeClock.cpp similarity index 98% rename from src/ripple/basics/impl/UptimeClock.cpp rename to src/libxrpl/basics/UptimeClock.cpp index 2dacaef46bf..4394d02f783 100644 --- a/src/ripple/basics/impl/UptimeClock.cpp +++ b/src/libxrpl/basics/UptimeClock.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/basics/impl/base64.cpp b/src/libxrpl/basics/base64.cpp similarity index 99% rename from src/ripple/basics/impl/base64.cpp rename to src/libxrpl/basics/base64.cpp index 39b615100e5..a8b4e352992 100644 --- a/src/ripple/basics/impl/base64.cpp +++ b/src/libxrpl/basics/base64.cpp @@ -54,7 +54,7 @@ */ -#include +#include #include #include @@ -242,7 +242,7 @@ base64_encode(std::uint8_t const* data, std::size_t len) } std::string -base64_decode(std::string const& data) +base64_decode(std::string_view data) { std::string dest; dest.resize(base64::decoded_size(data.size())); diff --git a/src/ripple/basics/impl/contract.cpp b/src/libxrpl/basics/contract.cpp similarity index 95% rename from src/ripple/basics/impl/contract.cpp rename to src/libxrpl/basics/contract.cpp index 3a50db38010..fbda83c7a63 100644 --- a/src/ripple/basics/impl/contract.cpp +++ b/src/libxrpl/basics/contract.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/basics/impl/make_SSLContext.cpp b/src/libxrpl/basics/make_SSLContext.cpp similarity index 99% rename from src/ripple/basics/impl/make_SSLContext.cpp rename to src/libxrpl/basics/make_SSLContext.cpp index 7d72d9e08b4..4a9ea321d54 100644 --- a/src/ripple/basics/impl/make_SSLContext.cpp +++ b/src/libxrpl/basics/make_SSLContext.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/basics/impl/mulDiv.cpp b/src/libxrpl/basics/mulDiv.cpp similarity index 97% rename from src/ripple/basics/impl/mulDiv.cpp rename to src/libxrpl/basics/mulDiv.cpp index 6dd01c71fe7..7f9ffa45267 100644 --- a/src/ripple/basics/impl/mulDiv.cpp +++ b/src/libxrpl/basics/mulDiv.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include diff --git a/src/ripple/basics/impl/partitioned_unordered_map.cpp b/src/libxrpl/basics/partitioned_unordered_map.cpp similarity index 91% rename from src/ripple/basics/impl/partitioned_unordered_map.cpp rename to src/libxrpl/basics/partitioned_unordered_map.cpp index 3ced32eddff..a6d02cd2c6f 100644 --- a/src/ripple/basics/impl/partitioned_unordered_map.cpp +++ b/src/libxrpl/basics/partitioned_unordered_map.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/beast/clock/basic_seconds_clock.cpp b/src/libxrpl/beast/clock/basic_seconds_clock.cpp similarity index 98% rename from src/ripple/beast/clock/basic_seconds_clock.cpp rename to src/libxrpl/beast/clock/basic_seconds_clock.cpp index 7c55a3f8bc0..f5af8bd16ec 100644 --- a/src/ripple/beast/clock/basic_seconds_clock.cpp +++ b/src/libxrpl/beast/clock/basic_seconds_clock.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include diff --git a/src/ripple/beast/core/CurrentThreadName.cpp b/src/libxrpl/beast/core/CurrentThreadName.cpp similarity index 98% rename from src/ripple/beast/core/CurrentThreadName.cpp rename to src/libxrpl/beast/core/CurrentThreadName.cpp index 80d275a1fff..71b5d21fec3 100644 --- a/src/ripple/beast/core/CurrentThreadName.cpp +++ b/src/libxrpl/beast/core/CurrentThreadName.cpp @@ -21,7 +21,7 @@ */ //============================================================================== -#include +#include #include //------------------------------------------------------------------------------ diff --git a/src/ripple/beast/core/SemanticVersion.cpp b/src/libxrpl/beast/core/SemanticVersion.cpp similarity index 98% rename from src/ripple/beast/core/SemanticVersion.cpp rename to src/libxrpl/beast/core/SemanticVersion.cpp index 9d127a09513..b33ed2f48f4 100644 --- a/src/ripple/beast/core/SemanticVersion.cpp +++ b/src/libxrpl/beast/core/SemanticVersion.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/beast/insight/impl/Collector.cpp b/src/libxrpl/beast/insight/Collector.cpp similarity index 96% rename from src/ripple/beast/insight/impl/Collector.cpp rename to src/libxrpl/beast/insight/Collector.cpp index e0f0e6ec644..4fdf6de984d 100644 --- a/src/ripple/beast/insight/impl/Collector.cpp +++ b/src/libxrpl/beast/insight/Collector.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/insight/impl/Groups.cpp b/src/libxrpl/beast/insight/Groups.cpp similarity index 96% rename from src/ripple/beast/insight/impl/Groups.cpp rename to src/libxrpl/beast/insight/Groups.cpp index 7877034b97a..fcf7e65787e 100644 --- a/src/ripple/beast/insight/impl/Groups.cpp +++ b/src/libxrpl/beast/insight/Groups.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/beast/insight/impl/Hook.cpp b/src/libxrpl/beast/insight/Hook.cpp similarity index 96% rename from src/ripple/beast/insight/impl/Hook.cpp rename to src/libxrpl/beast/insight/Hook.cpp index 53ae6475aad..62538846349 100644 --- a/src/ripple/beast/insight/impl/Hook.cpp +++ b/src/libxrpl/beast/insight/Hook.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/insight/impl/Metric.cpp b/src/libxrpl/beast/insight/Metric.cpp similarity index 87% rename from src/ripple/beast/insight/impl/Metric.cpp rename to src/libxrpl/beast/insight/Metric.cpp index aa38e2f8166..64a4082fcec 100644 --- a/src/ripple/beast/insight/impl/Metric.cpp +++ b/src/libxrpl/beast/insight/Metric.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace beast { namespace insight { diff --git a/src/ripple/beast/insight/impl/NullCollector.cpp b/src/libxrpl/beast/insight/NullCollector.cpp similarity index 94% rename from src/ripple/beast/insight/impl/NullCollector.cpp rename to src/libxrpl/beast/insight/NullCollector.cpp index b859de552a2..12ea817014e 100644 --- a/src/ripple/beast/insight/impl/NullCollector.cpp +++ b/src/libxrpl/beast/insight/NullCollector.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace beast { namespace insight { @@ -41,7 +41,8 @@ class NullCounterImpl : public CounterImpl public: explicit NullCounterImpl() = default; - void increment(value_type) override + void + increment(value_type) override { } @@ -74,11 +75,13 @@ class NullGaugeImpl : public GaugeImpl public: explicit NullGaugeImpl() = default; - void set(value_type) override + void + set(value_type) override { } - void increment(difference_type) override + void + increment(difference_type) override { } @@ -94,7 +97,8 @@ class NullMeterImpl : public MeterImpl public: explicit NullMeterImpl() = default; - void increment(value_type) override + void + increment(value_type) override { } diff --git a/src/ripple/beast/insight/impl/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp similarity index 98% rename from src/ripple/beast/insight/impl/StatsDCollector.cpp rename to src/libxrpl/beast/insight/StatsDCollector.cpp index 6949a7f86e7..76ae15b82d5 100644 --- a/src/ripple/beast/insight/impl/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/beast/net/impl/IPAddressConversion.cpp b/src/libxrpl/beast/net/IPAddressConversion.cpp similarity index 97% rename from src/ripple/beast/net/impl/IPAddressConversion.cpp rename to src/libxrpl/beast/net/IPAddressConversion.cpp index 6353c50dfba..2ab84c37015 100644 --- a/src/ripple/beast/net/impl/IPAddressConversion.cpp +++ b/src/libxrpl/beast/net/IPAddressConversion.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace beast { namespace IP { diff --git a/src/ripple/beast/net/impl/IPAddressV4.cpp b/src/libxrpl/beast/net/IPAddressV4.cpp similarity index 97% rename from src/ripple/beast/net/impl/IPAddressV4.cpp rename to src/libxrpl/beast/net/IPAddressV4.cpp index 7e428c4228e..83541479878 100644 --- a/src/ripple/beast/net/impl/IPAddressV4.cpp +++ b/src/libxrpl/beast/net/IPAddressV4.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include diff --git a/src/ripple/beast/net/impl/IPAddressV6.cpp b/src/libxrpl/beast/net/IPAddressV6.cpp similarity index 94% rename from src/ripple/beast/net/impl/IPAddressV6.cpp rename to src/libxrpl/beast/net/IPAddressV6.cpp index 07dd2baaee5..f90a6d066b0 100644 --- a/src/ripple/beast/net/impl/IPAddressV6.cpp +++ b/src/libxrpl/beast/net/IPAddressV6.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace beast { namespace IP { diff --git a/src/ripple/beast/net/impl/IPEndpoint.cpp b/src/libxrpl/beast/net/IPEndpoint.cpp similarity index 99% rename from src/ripple/beast/net/impl/IPEndpoint.cpp rename to src/libxrpl/beast/net/IPEndpoint.cpp index 4ad71262c27..490777111bd 100644 --- a/src/ripple/beast/net/impl/IPEndpoint.cpp +++ b/src/libxrpl/beast/net/IPEndpoint.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace beast { diff --git a/src/ripple/beast/utility/src/beast_Journal.cpp b/src/libxrpl/beast/utility/src/beast_Journal.cpp similarity index 95% rename from src/ripple/beast/utility/src/beast_Journal.cpp rename to src/libxrpl/beast/utility/src/beast_Journal.cpp index 7c332bf6b52..8ce7ea8d47c 100644 --- a/src/ripple/beast/utility/src/beast_Journal.cpp +++ b/src/libxrpl/beast/utility/src/beast_Journal.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace beast { @@ -34,7 +34,8 @@ class NullJournalSink : public Journal::Sink ~NullJournalSink() override = default; - bool active(severities::Severity) const override + bool + active(severities::Severity) const override { return false; } @@ -56,7 +57,8 @@ class NullJournalSink : public Journal::Sink return severities::kDisabled; } - void threshold(severities::Severity) override + void + threshold(severities::Severity) override { } diff --git a/src/ripple/beast/utility/src/beast_PropertyStream.cpp b/src/libxrpl/beast/utility/src/beast_PropertyStream.cpp similarity index 99% rename from src/ripple/beast/utility/src/beast_PropertyStream.cpp rename to src/libxrpl/beast/utility/src/beast_PropertyStream.cpp index ecd707e9554..56e862ca8c0 100644 --- a/src/ripple/beast/utility/src/beast_PropertyStream.cpp +++ b/src/libxrpl/beast/utility/src/beast_PropertyStream.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include diff --git a/src/ripple/crypto/impl/RFC1751.cpp b/src/libxrpl/crypto/RFC1751.cpp similarity index 99% rename from src/ripple/crypto/impl/RFC1751.cpp rename to src/libxrpl/crypto/RFC1751.cpp index 6b6b2c31fb1..1c6f28287b0 100644 --- a/src/ripple/crypto/impl/RFC1751.cpp +++ b/src/libxrpl/crypto/RFC1751.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include diff --git a/src/ripple/crypto/impl/csprng.cpp b/src/libxrpl/crypto/csprng.cpp similarity index 97% rename from src/ripple/crypto/impl/csprng.cpp rename to src/libxrpl/crypto/csprng.cpp index 04b3b3fc385..480d561eacf 100644 --- a/src/ripple/crypto/impl/csprng.cpp +++ b/src/libxrpl/crypto/csprng.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/crypto/impl/secure_erase.cpp b/src/libxrpl/crypto/secure_erase.cpp similarity index 96% rename from src/ripple/crypto/impl/secure_erase.cpp rename to src/libxrpl/crypto/secure_erase.cpp index 8299ed7a6c9..93f189d519c 100644 --- a/src/ripple/crypto/impl/secure_erase.cpp +++ b/src/libxrpl/crypto/secure_erase.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/json/impl/JsonPropertyStream.cpp b/src/libxrpl/json/JsonPropertyStream.cpp similarity index 97% rename from src/ripple/json/impl/JsonPropertyStream.cpp rename to src/libxrpl/json/JsonPropertyStream.cpp index 3083deeeee2..19387013b58 100644 --- a/src/ripple/json/impl/JsonPropertyStream.cpp +++ b/src/libxrpl/json/JsonPropertyStream.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/json/impl/Object.cpp b/src/libxrpl/json/Object.cpp similarity index 98% rename from src/ripple/json/impl/Object.cpp rename to src/libxrpl/json/Object.cpp index a855fc31a2c..179b0e31ef2 100644 --- a/src/ripple/json/impl/Object.cpp +++ b/src/libxrpl/json/Object.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace Json { diff --git a/src/ripple/json/impl/Output.cpp b/src/libxrpl/json/Output.cpp similarity index 97% rename from src/ripple/json/impl/Output.cpp rename to src/libxrpl/json/Output.cpp index b32e9ad5a67..c24c75b32dd 100644 --- a/src/ripple/json/impl/Output.cpp +++ b/src/libxrpl/json/Output.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace Json { diff --git a/src/ripple/json/impl/Writer.cpp b/src/libxrpl/json/Writer.cpp similarity index 98% rename from src/ripple/json/impl/Writer.cpp rename to src/libxrpl/json/Writer.cpp index 44d106aa336..2f7c0b523c6 100644 --- a/src/ripple/json/impl/Writer.cpp +++ b/src/libxrpl/json/Writer.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include @@ -285,7 +285,8 @@ Writer::output(double f) impl_->output({s.data(), lengthWithoutTrailingZeros(s)}); } -void Writer::output(std::nullptr_t) +void +Writer::output(std::nullptr_t) { impl_->output("null"); } diff --git a/src/ripple/json/impl/json_reader.cpp b/src/libxrpl/json/json_reader.cpp similarity index 99% rename from src/ripple/json/impl/json_reader.cpp rename to src/libxrpl/json/json_reader.cpp index 001049bcdbc..42488b3ec87 100644 --- a/src/ripple/json/impl/json_reader.cpp +++ b/src/libxrpl/json/json_reader.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/json/impl/json_value.cpp b/src/libxrpl/json/json_value.cpp similarity index 99% rename from src/ripple/json/impl/json_value.cpp rename to src/libxrpl/json/json_value.cpp index 024ad2c88a0..155c3e1e044 100644 --- a/src/ripple/json/impl/json_value.cpp +++ b/src/libxrpl/json/json_value.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace Json { diff --git a/src/ripple/json/impl/json_valueiterator.cpp b/src/libxrpl/json/json_valueiterator.cpp similarity index 99% rename from src/ripple/json/impl/json_valueiterator.cpp rename to src/libxrpl/json/json_valueiterator.cpp index 355bfad23cd..c1a1bcc56a0 100644 --- a/src/ripple/json/impl/json_valueiterator.cpp +++ b/src/libxrpl/json/json_valueiterator.cpp @@ -19,7 +19,7 @@ // included by json_value.cpp -#include +#include namespace Json { diff --git a/src/ripple/json/impl/json_writer.cpp b/src/libxrpl/json/json_writer.cpp similarity index 99% rename from src/ripple/json/impl/json_writer.cpp rename to src/libxrpl/json/json_writer.cpp index 4d696ab55bc..5459042cddb 100644 --- a/src/ripple/json/impl/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include diff --git a/src/ripple/json/impl/to_string.cpp b/src/libxrpl/json/to_string.cpp similarity index 94% rename from src/ripple/json/impl/to_string.cpp rename to src/libxrpl/json/to_string.cpp index b965417721e..7bf58241201 100644 --- a/src/ripple/json/impl/to_string.cpp +++ b/src/libxrpl/json/to_string.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace Json { diff --git a/src/ripple/protocol/impl/AMMCore.cpp b/src/libxrpl/protocol/AMMCore.cpp similarity index 94% rename from src/ripple/protocol/impl/AMMCore.cpp rename to src/libxrpl/protocol/AMMCore.cpp index b774ee4ee3b..7ac27041e76 100644 --- a/src/ripple/protocol/impl/AMMCore.cpp +++ b/src/libxrpl/protocol/AMMCore.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/AccountID.cpp b/src/libxrpl/protocol/AccountID.cpp similarity index 96% rename from src/ripple/protocol/impl/AccountID.cpp rename to src/libxrpl/protocol/AccountID.cpp index c615807cf84..5f0fa631851 100644 --- a/src/ripple/protocol/impl/AccountID.cpp +++ b/src/libxrpl/protocol/AccountID.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/libxrpl/protocol/Asset.cpp b/src/libxrpl/protocol/Asset.cpp new file mode 100644 index 00000000000..5a496352840 --- /dev/null +++ b/src/libxrpl/protocol/Asset.cpp @@ -0,0 +1,80 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include + +namespace ripple { + +AccountID const& +Asset::getIssuer() const +{ + return std::visit( + [&](auto&& issue) -> AccountID const& { return issue.getIssuer(); }, + issue_); +} + +std::string +Asset::getText() const +{ + return std::visit([&](auto&& issue) { return issue.getText(); }, issue_); +} + +void +Asset::setJson(Json::Value& jv) const +{ + std::visit([&](auto&& issue) { issue.setJson(jv); }, issue_); +} + +std::string +to_string(Asset const& asset) +{ + return std::visit( + [&](auto const& issue) { return to_string(issue); }, asset.value()); +} + +bool +validJSONAsset(Json::Value const& jv) +{ + if (jv.isMember(jss::mpt_issuance_id)) + return !(jv.isMember(jss::currency) || jv.isMember(jss::issuer)); + return jv.isMember(jss::currency); +} + +Asset +assetFromJson(Json::Value const& v) +{ + if (!v.isMember(jss::currency) && !v.isMember(jss::mpt_issuance_id)) + Throw( + "assetFromJson must contain currency or mpt_issuance_id"); + + if (v.isMember(jss::currency)) + return issueFromJson(v); + return mptIssueFromJson(v); +} + +Json::Value +to_json(Asset const& asset) +{ + return std::visit( + [&](auto const& issue) { return to_json(issue); }, asset.value()); +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/Book.cpp b/src/libxrpl/protocol/Book.cpp similarity index 97% rename from src/ripple/protocol/impl/Book.cpp rename to src/libxrpl/protocol/Book.cpp index 3ad22675d1b..c096dba2b4e 100644 --- a/src/ripple/protocol/impl/Book.cpp +++ b/src/libxrpl/protocol/Book.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp similarity index 96% rename from src/ripple/protocol/impl/BuildInfo.cpp rename to src/libxrpl/protocol/BuildInfo.cpp index fb86609f026..995df262885 100644 --- a/src/ripple/protocol/impl/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -33,7 +33,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "2.2.3" +char const* const versionString = "2.3.0" // clang-format on #if defined(DEBUG) || defined(SANITIZER) diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/libxrpl/protocol/ErrorCodes.cpp similarity index 97% rename from src/ripple/protocol/impl/ErrorCodes.cpp rename to src/libxrpl/protocol/ErrorCodes.cpp index 3af48891c78..c157d9b296c 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/libxrpl/protocol/ErrorCodes.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include @@ -71,7 +71,6 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed.", 400}, {rpcEXCESSIVE_LGR_RANGE, "excessiveLgrRange", "Ledger range exceeds 1000.", 400}, {rpcFORBIDDEN, "forbidden", "Bad credentials.", 403}, - {rpcFAILED_TO_FORWARD, "failedToForward", "Failed to forward request to p2p node", 503}, {rpcHIGH_FEE, "highFee", "Current transaction fee exceeds your limit.", 402}, {rpcINTERNAL, "internal", "Internal error.", 500}, {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid.", 400}, @@ -97,7 +96,6 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress.", 404}, {rpcOBJECT_NOT_FOUND, "objectNotFound", "The requested object was not found.", 404}, {rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed.", 400}, - {rpcREPORTING_UNSUPPORTED, "reportingUnsupported", "Requested operation not supported by reporting mode server", 405}, {rpcSENDMAX_MALFORMED, "sendMaxMalformed", "SendMax amount malformed.", 400}, {rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed.", 400}, {rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.", 429}, @@ -110,7 +108,8 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now.", 503}, {rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found.", 404}, {rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405}, - {rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400}}; + {rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400}, + {rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400}}; // clang-format on // Sort and validate unorderedErrorInfos at compile time. Should be diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/libxrpl/protocol/Feature.cpp similarity index 65% rename from src/ripple/protocol/impl/Feature.cpp rename to src/libxrpl/protocol/Feature.cpp index ae7a8291bb4..3f6e760577a 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include +#include -#include -#include -#include +#include +#include +#include #include #include #include @@ -116,6 +116,7 @@ class FeatureCollections // name, index, and uint256 feature identifier boost::multi_index::multi_index_container features; + std::map all; std::map supported; std::size_t upVotes = 0; std::size_t downVotes = 0; @@ -179,6 +180,13 @@ class FeatureCollections std::string featureToName(uint256 const& f) const; + /** All amendments that are registered within the table. */ + std::map const& + allAmendments() const + { + return all; + } + /** Amendments that this server supports. Whether they are enabled depends on the Rules defined in the validated ledger */ @@ -251,6 +259,14 @@ FeatureCollections::registerFeature( features.emplace_back(name, f); + auto const getAmendmentSupport = [=]() { + if (vote == VoteBehavior::Obsolete) + return AmendmentSupport::Retired; + return support == Supported::yes ? AmendmentSupport::Supported + : AmendmentSupport::Unsupported; + }; + all.emplace(name, getAmendmentSupport()); + if (support == Supported::yes) { supported.emplace(name, vote); @@ -266,6 +282,9 @@ FeatureCollections::registerFeature( check( supported.size() <= features.size(), "More supported features than defined features"); + check( + features.size() == all.size(), + "The 'all' features list is populated incorrectly"); return f; } else @@ -313,6 +332,13 @@ static FeatureCollections featureCollections; } // namespace +/** All amendments libxrpl knows of. */ +std::map const& +allAmendments() +{ + return featureCollections.allAmendments(); +} + /** Amendments that this server supports. Whether they are enabled depends on the Rules defined in the validated ledger */ @@ -383,106 +409,27 @@ featureToName(uint256 const& f) return featureCollections.featureToName(f); } -#pragma push_macro("REGISTER_FEATURE") -#undef REGISTER_FEATURE +// All known amendments must be registered either here or below with the +// "retired" amendments -/** -Takes the name of a feature, whether it's supported, and the default vote. Will -register the feature, and create a variable whose name is "feature" plus the -feature name. -*/ -#define REGISTER_FEATURE(fName, supported, votebehavior) \ - uint256 const feature##fName = \ - registerFeature(#fName, supported, votebehavior) +#pragma push_macro("XRPL_FEATURE") +#undef XRPL_FEATURE +#pragma push_macro("XRPL_FIX") +#undef XRPL_FIX -#pragma push_macro("REGISTER_FIX") -#undef REGISTER_FIX +#define XRPL_FEATURE(name, supported, vote) \ + uint256 const feature##name = registerFeature(#name, supported, vote); +#define XRPL_FIX(name, supported, vote) \ + uint256 const fix##name = registerFeature("fix" #name, supported, vote); -/** -Takes the name of a feature, whether it's supported, and the default vote. Will -register the feature, and create a variable whose name is the unmodified feature -name. -*/ -#define REGISTER_FIX(fName, supported, votebehavior) \ - uint256 const fName = registerFeature(#fName, supported, votebehavior) +#include -// clang-format off +#undef XRPL_FIX +#pragma pop_macro("XRPL_FIX") +#undef XRPL_FEATURE +#pragma pop_macro("XRPL_FEATURE") -// All known amendments must be registered either here or below with the -// "retired" amendments -REGISTER_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo); -REGISTER_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fix1513, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fix1571, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fix1543, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fix1623, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(DepositPreauth, Supported::yes, VoteBehavior::DefaultYes); -// Use liquidity from strands that consume max offers, but mark as dry -REGISTER_FIX (fix1515, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fix1578, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixTakerDryOfferRemoval, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixMasterKeyAsRegularKey, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixCheckThreading, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixPayChanRecipientOwnerDir, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes); -// fixQualityUpperBound should be activated before FlowCross -REGISTER_FIX (fixQualityUpperBound, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes); -// fix1781: XRPEndpointSteps should be included in the circular payment check -REGISTER_FIX (fix1781, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(HardenedValidations, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixAmendmentMajorityCalc, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(TicketBatch, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixSTAmountCanonicalize, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FIX (fixRmSmallIncreasedQOffers, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(CheckCashMakesTrustLine, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(ExpandedSignerList, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(NonFungibleTokensV1_1, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixTrustLinesToSelf, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixUniversalNumber, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixNonFungibleTokensV1_2, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixNFTokenRemint, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixReducedOffersV1, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(XChainBridge, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixDisallowIncomingV1, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FEATURE(DID, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixFillOrKill, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixNFTokenReserve, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixInnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixAMMOverflowOffer, Supported::yes, VoteBehavior::DefaultYes); -REGISTER_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixEmptyDID, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixXChainRewardRounding, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixPreviousTxnID, Supported::yes, VoteBehavior::DefaultNo); -REGISTER_FIX (fixAMMv1_1, Supported::yes, VoteBehavior::DefaultNo); - -// The following amendments are obsolete, but must remain supported -// because they could potentially get enabled. -// -// Obsolete features are (usually) not in the ledger, and may have code -// controlled by the feature. They need to be supported because at some -// time in the past, the feature was supported and votable, but never -// passed. So the feature needs to be supported in case it is ever -// enabled (added to the ledger). -// -// If a feature remains obsolete for long enough that no clients are able -// to vote for it, the feature can be removed (entirely?) from the code. -REGISTER_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete); -REGISTER_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete); -REGISTER_FIX (fixNFTokenDirV1, Supported::yes, VoteBehavior::Obsolete); -REGISTER_FIX (fixNFTokenNegOffer, Supported::yes, VoteBehavior::Obsolete); +// clang-format off // The following amendments have been active for at least two years. Their // pre-amendment code has been removed and the identifiers are deprecated. @@ -508,12 +455,6 @@ uint256 const // clang-format on -#undef REGISTER_FIX -#pragma pop_macro("REGISTER_FIX") - -#undef REGISTER_FEATURE -#pragma pop_macro("REGISTER_FEATURE") - // All of the features should now be registered, since variables in a cpp file // are initialized from top to bottom. // diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp similarity index 85% rename from src/ripple/protocol/impl/Indexes.cpp rename to src/libxrpl/protocol/Indexes.cpp index 0ee52aab297..12142879ad5 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -63,6 +63,7 @@ enum class LedgerNameSpace : std::uint16_t { XRP_PAYMENT_CHANNEL = 'x', CHECK = 'C', DEPOSIT_PREAUTH = 'p', + DEPOSIT_PREAUTH_CREDENTIALS = 'P', NEGATIVE_UNL = 'N', NFTOKEN_OFFER = 'q', NFTOKEN_BUY_OFFERS = 'h', @@ -73,6 +74,9 @@ enum class LedgerNameSpace : std::uint16_t { XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K', DID = 'I', ORACLE = 'R', + MPTOKEN_ISSUANCE = '~', + MPTOKEN = 't', + CREDENTIAL = 'D', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. @@ -135,6 +139,16 @@ getTicketIndex(AccountID const& account, SeqProxy ticketSeq) return getTicketIndex(account, ticketSeq.value()); } +MPTID +makeMptID(std::uint32_t sequence, AccountID const& account) +{ + MPTID u; + sequence = boost::endian::native_to_big(sequence); + memcpy(u.data(), &sequence, sizeof(sequence)); + memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account)); + return u; +} + //------------------------------------------------------------------------------ namespace keylet { @@ -301,6 +315,22 @@ depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept indexHash(LedgerNameSpace::DEPOSIT_PREAUTH, owner, preauthorized)}; } +// Credentials should be sorted here, use credentials::makeSorted +Keylet +depositPreauth( + AccountID const& owner, + std::set> const& authCreds) noexcept +{ + std::vector hashes; + hashes.reserve(authCreds.size()); + for (auto const& o : authCreds) + hashes.emplace_back(sha512Half(o.first, o.second)); + + return { + ltDEPOSIT_PREAUTH, + indexHash(LedgerNameSpace::DEPOSIT_PREAUTH_CREDENTIALS, owner, hashes)}; +} + //------------------------------------------------------------------------------ Keylet @@ -451,6 +481,44 @@ oracle(AccountID const& account, std::uint32_t const& documentID) noexcept return {ltORACLE, indexHash(LedgerNameSpace::ORACLE, account, documentID)}; } +Keylet +mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept +{ + return mptIssuance(makeMptID(seq, issuer)); +} + +Keylet +mptIssuance(MPTID const& issuanceID) noexcept +{ + return { + ltMPTOKEN_ISSUANCE, + indexHash(LedgerNameSpace::MPTOKEN_ISSUANCE, issuanceID)}; +} + +Keylet +mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept +{ + return mptoken(mptIssuance(issuanceID).key, holder); +} + +Keylet +mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept +{ + return { + ltMPTOKEN, indexHash(LedgerNameSpace::MPTOKEN, issuanceKey, holder)}; +} + +Keylet +credential( + AccountID const& subject, + AccountID const& issuer, + Slice const& credType) noexcept +{ + return { + ltCREDENTIAL, + indexHash(LedgerNameSpace::CREDENTIAL, subject, issuer, credType)}; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp similarity index 94% rename from src/ripple/protocol/impl/InnerObjectFormats.cpp rename to src/libxrpl/protocol/InnerObjectFormats.cpp index edebc57477e..87c42a8085f 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { @@ -147,6 +147,13 @@ InnerObjectFormats::InnerObjectFormats() {sfAssetPrice, soeOPTIONAL}, {sfScale, soeDEFAULT}, }); + + add(sfCredential.jsonName.c_str(), + sfCredential.getCode(), + { + {sfIssuer, soeREQUIRED}, + {sfCredentialType, soeREQUIRED}, + }); } InnerObjectFormats const& diff --git a/src/ripple/protocol/impl/Issue.cpp b/src/libxrpl/protocol/Issue.cpp similarity index 84% rename from src/ripple/protocol/impl/Issue.cpp rename to src/libxrpl/protocol/Issue.cpp index 623ce24bb15..179cb1eb14a 100644 --- a/src/ripple/protocol/impl/Issue.cpp +++ b/src/libxrpl/protocol/Issue.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -49,6 +49,20 @@ Issue::getText() const return ret; } +void +Issue::setJson(Json::Value& jv) const +{ + jv[jss::currency] = to_string(currency); + if (!isXRP(currency)) + jv[jss::issuer] = toBase58(account); +} + +bool +Issue::native() const +{ + return *this == xrpIssue(); +} + bool isConsistent(Issue const& ac) { @@ -68,9 +82,7 @@ Json::Value to_json(Issue const& is) { Json::Value jv; - jv[jss::currency] = to_string(is.currency); - if (!isXRP(is.currency)) - jv[jss::issuer] = toBase58(is.account); + is.setJson(jv); return jv; } @@ -83,6 +95,12 @@ issueFromJson(Json::Value const& v) "issueFromJson can only be specified with an 'object' Json value"); } + if (v.isMember(jss::mpt_issuance_id)) + { + Throw( + "issueFromJson, Issue should not have mpt_issuance_id"); + } + Json::Value const curStr = v[jss::currency]; Json::Value const issStr = v[jss::issuer]; diff --git a/src/ripple/protocol/impl/Keylet.cpp b/src/libxrpl/protocol/Keylet.cpp similarity index 94% rename from src/ripple/protocol/impl/Keylet.cpp rename to src/libxrpl/protocol/Keylet.cpp index 2488e8b45b6..23847811d3b 100644 --- a/src/ripple/protocol/impl/Keylet.cpp +++ b/src/libxrpl/protocol/Keylet.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/libxrpl/protocol/LedgerFormats.cpp b/src/libxrpl/protocol/LedgerFormats.cpp new file mode 100644 index 00000000000..d66b085e0d0 --- /dev/null +++ b/src/libxrpl/protocol/LedgerFormats.cpp @@ -0,0 +1,59 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include + +namespace ripple { + +LedgerFormats::LedgerFormats() +{ + // Fields shared by all ledger formats: + static const std::initializer_list commonFields{ + {sfLedgerIndex, soeOPTIONAL}, + {sfLedgerEntryType, soeREQUIRED}, + {sfFlags, soeREQUIRED}, + }; + +#pragma push_macro("UNWRAP") +#undef UNWRAP +#pragma push_macro("LEDGER_ENTRY") +#undef LEDGER_ENTRY + +#define UNWRAP(...) __VA_ARGS__ +#define LEDGER_ENTRY(tag, value, name, fields) \ + add(jss::name, tag, UNWRAP fields, commonFields); + +#include + +#undef LEDGER_ENTRY +#pragma pop_macro("LEDGER_ENTRY") +#undef UNWRAP +#pragma pop_macro("UNWRAP") +} + +LedgerFormats const& +LedgerFormats::getInstance() +{ + static LedgerFormats instance; + return instance; +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/LedgerHeader.cpp b/src/libxrpl/protocol/LedgerHeader.cpp similarity index 98% rename from src/ripple/protocol/impl/LedgerHeader.cpp rename to src/libxrpl/protocol/LedgerHeader.cpp index 6d13db224fd..284c4dc0557 100644 --- a/src/ripple/protocol/impl/LedgerHeader.cpp +++ b/src/libxrpl/protocol/LedgerHeader.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp new file mode 100644 index 00000000000..38022a0ed3a --- /dev/null +++ b/src/libxrpl/protocol/MPTIssue.cpp @@ -0,0 +1,107 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include + +namespace ripple { + +MPTIssue::MPTIssue(MPTID const& issuanceID) : mptID_(issuanceID) +{ +} + +AccountID const& +MPTIssue::getIssuer() const +{ + // MPTID is concatenation of sequence + account + static_assert(sizeof(MPTID) == (sizeof(std::uint32_t) + sizeof(AccountID))); + // copy from id skipping the sequence + AccountID const* account = reinterpret_cast( + mptID_.data() + sizeof(std::uint32_t)); + + return *account; +} + +MPTID const& +MPTIssue::getMptID() const +{ + return mptID_; +} + +std::string +MPTIssue::getText() const +{ + return to_string(mptID_); +} + +void +MPTIssue::setJson(Json::Value& jv) const +{ + jv[jss::mpt_issuance_id] = to_string(mptID_); +} + +Json::Value +to_json(MPTIssue const& mptIssue) +{ + Json::Value jv; + mptIssue.setJson(jv); + return jv; +} + +std::string +to_string(MPTIssue const& mptIssue) +{ + return to_string(mptIssue.getMptID()); +} + +MPTIssue +mptIssueFromJson(Json::Value const& v) +{ + if (!v.isObject()) + { + Throw( + "mptIssueFromJson can only be specified with an 'object' Json " + "value"); + } + + if (v.isMember(jss::currency) || v.isMember(jss::issuer)) + { + Throw( + "mptIssueFromJson, MPTIssue should not have currency or issuer"); + } + + Json::Value const& idStr = v[jss::mpt_issuance_id]; + + if (!idStr.isString()) + { + Throw( + "mptIssueFromJson MPTID must be a string Json value"); + } + + MPTID id; + if (!id.parseHex(idStr.asString())) + { + Throw("mptIssueFromJson MPTID is invalid"); + } + + return MPTIssue{id}; +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/NFTSyntheticSerializer.cpp b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp similarity index 89% rename from src/ripple/protocol/impl/NFTSyntheticSerializer.cpp rename to src/libxrpl/protocol/NFTSyntheticSerializer.cpp index e34397ada6a..03c9e79e265 100644 --- a/src/ripple/protocol/impl/NFTSyntheticSerializer.cpp +++ b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/NFTokenID.cpp b/src/libxrpl/protocol/NFTokenID.cpp similarity index 99% rename from src/ripple/protocol/impl/NFTokenID.cpp rename to src/libxrpl/protocol/NFTokenID.cpp index ea6ab984c19..72fb8b87bbe 100644 --- a/src/ripple/protocol/impl/NFTokenID.cpp +++ b/src/libxrpl/protocol/NFTokenID.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/NFTokenOfferID.cpp b/src/libxrpl/protocol/NFTokenOfferID.cpp similarity index 92% rename from src/ripple/protocol/impl/NFTokenOfferID.cpp rename to src/libxrpl/protocol/NFTokenOfferID.cpp index c9c3118cf2a..3d761e6b939 100644 --- a/src/ripple/protocol/impl/NFTokenOfferID.cpp +++ b/src/libxrpl/protocol/NFTokenOfferID.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { @@ -31,7 +31,8 @@ canHaveNFTokenOfferID( return false; TxType const tt = serializedTx->getTxnType(); - if (tt != ttNFTOKEN_CREATE_OFFER) + if (!(tt == ttNFTOKEN_MINT && serializedTx->isFieldPresent(sfAmount)) && + tt != ttNFTOKEN_CREATE_OFFER) return false; // if the transaction failed nothing could have been delivered. diff --git a/src/ripple/protocol/impl/PublicKey.cpp b/src/libxrpl/protocol/PublicKey.cpp similarity index 97% rename from src/ripple/protocol/impl/PublicKey.cpp rename to src/libxrpl/protocol/PublicKey.cpp index 22cb351e61c..fdab288cea0 100644 --- a/src/ripple/protocol/impl/PublicKey.cpp +++ b/src/libxrpl/protocol/PublicKey.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/impl/Quality.cpp b/src/libxrpl/protocol/Quality.cpp similarity index 83% rename from src/ripple/protocol/impl/Quality.cpp rename to src/libxrpl/protocol/Quality.cpp index f7b9d6b3c41..c6464eba9d2 100644 --- a/src/ripple/protocol/impl/Quality.cpp +++ b/src/libxrpl/protocol/Quality.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include @@ -64,13 +64,20 @@ Quality::operator--(int) return prev; } -Amounts -Quality::ceil_in(Amounts const& amount, STAmount const& limit) const +template +static Amounts +ceil_in_impl( + Amounts const& amount, + STAmount const& limit, + bool roundUp, + Quality const& quality) { if (amount.in > limit) { Amounts result( - limit, divRound(limit, rate(), amount.out.issue(), true)); + limit, + DivRoundFunc(limit, quality.rate(), amount.out.asset(), roundUp)); // Clamp out if (result.out > amount.out) result.out = amount.out; @@ -81,8 +88,23 @@ Quality::ceil_in(Amounts const& amount, STAmount const& limit) const return amount; } +Amounts +Quality::ceil_in(Amounts const& amount, STAmount const& limit) const +{ + return ceil_in_impl(amount, limit, /* roundUp */ true, *this); +} + +Amounts +Quality::ceil_in_strict( + Amounts const& amount, + STAmount const& limit, + bool roundUp) const +{ + return ceil_in_impl(amount, limit, roundUp, *this); +} + template + *MulRoundFunc)(STAmount const&, STAmount const&, Asset const&, bool)> static Amounts ceil_out_impl( Amounts const& amount, @@ -93,7 +115,7 @@ ceil_out_impl( if (amount.out > limit) { Amounts result( - MulRoundFunc(limit, quality.rate(), amount.in.issue(), roundUp), + MulRoundFunc(limit, quality.rate(), amount.in.asset(), roundUp), limit); // Clamp in if (result.in > amount.in) @@ -129,7 +151,7 @@ composed_quality(Quality const& lhs, Quality const& rhs) STAmount const rhs_rate(rhs.rate()); assert(rhs_rate != beast::zero); - STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.issue(), true)); + STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.asset(), true)); std::uint64_t const stored_exponent(rate.exponent() + 100); std::uint64_t const stored_mantissa(rate.mantissa()); diff --git a/src/ripple/protocol/impl/QualityFunction.cpp b/src/libxrpl/protocol/QualityFunction.cpp similarity index 95% rename from src/ripple/protocol/impl/QualityFunction.cpp rename to src/libxrpl/protocol/QualityFunction.cpp index 29270928480..64e5a36989c 100644 --- a/src/ripple/protocol/impl/QualityFunction.cpp +++ b/src/libxrpl/protocol/QualityFunction.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include +#include -#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/RPCErr.cpp b/src/libxrpl/protocol/RPCErr.cpp similarity index 94% rename from src/ripple/protocol/impl/RPCErr.cpp rename to src/libxrpl/protocol/RPCErr.cpp index ec0474fe8b0..e9ca3c3c1e6 100644 --- a/src/ripple/protocol/impl/RPCErr.cpp +++ b/src/libxrpl/protocol/RPCErr.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/Rate2.cpp b/src/libxrpl/protocol/Rate2.cpp similarity index 81% rename from src/ripple/protocol/impl/Rate2.cpp rename to src/libxrpl/protocol/Rate2.cpp index 340b6719bca..33bd9c5d0be 100644 --- a/src/ripple/protocol/impl/Rate2.cpp +++ b/src/libxrpl/protocol/Rate2.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { @@ -51,7 +51,7 @@ multiply(STAmount const& amount, Rate const& rate) if (rate == parityRate) return amount; - return multiply(amount, detail::as_amount(rate), amount.issue()); + return multiply(amount, detail::as_amount(rate), amount.asset()); } STAmount @@ -62,14 +62,14 @@ multiplyRound(STAmount const& amount, Rate const& rate, bool roundUp) if (rate == parityRate) return amount; - return mulRound(amount, detail::as_amount(rate), amount.issue(), roundUp); + return mulRound(amount, detail::as_amount(rate), amount.asset(), roundUp); } STAmount multiplyRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp) { assert(rate.value != 0); @@ -79,7 +79,7 @@ multiplyRound( return amount; } - return mulRound(amount, detail::as_amount(rate), issue, roundUp); + return mulRound(amount, detail::as_amount(rate), asset, roundUp); } STAmount @@ -90,7 +90,7 @@ divide(STAmount const& amount, Rate const& rate) if (rate == parityRate) return amount; - return divide(amount, detail::as_amount(rate), amount.issue()); + return divide(amount, detail::as_amount(rate), amount.asset()); } STAmount @@ -101,14 +101,14 @@ divideRound(STAmount const& amount, Rate const& rate, bool roundUp) if (rate == parityRate) return amount; - return divRound(amount, detail::as_amount(rate), amount.issue(), roundUp); + return divRound(amount, detail::as_amount(rate), amount.asset(), roundUp); } STAmount divideRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp) { assert(rate.value != 0); @@ -116,7 +116,7 @@ divideRound( if (rate == parityRate) return amount; - return divRound(amount, detail::as_amount(rate), issue, roundUp); + return divRound(amount, detail::as_amount(rate), asset, roundUp); } } // namespace ripple diff --git a/src/ripple/protocol/impl/Rules.cpp b/src/libxrpl/protocol/Rules.cpp similarity index 97% rename from src/ripple/protocol/impl/Rules.cpp rename to src/libxrpl/protocol/Rules.cpp index e65a9678f65..f47e966e138 100644 --- a/src/ripple/protocol/impl/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp new file mode 100644 index 00000000000..537fa557fcc --- /dev/null +++ b/src/libxrpl/protocol/SField.cpp @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { + +// Storage for static const members. +SField::IsSigning const SField::notSigning; +int SField::num = 0; +std::map SField::knownCodeToField; + +// Give only this translation unit permission to construct SFields +struct SField::private_access_tag_t +{ + explicit private_access_tag_t() = default; +}; + +static SField::private_access_tag_t access; + +template +template +TypedField::TypedField(private_access_tag_t pat, Args&&... args) + : SField(pat, std::forward(args)...) +{ +} + +// Construct all compile-time SFields, and register them in the knownCodeToField +// database: + +// Use macros for most SField construction to enforce naming conventions. +#pragma push_macro("UNTYPED_SFIELD") +#undef UNTYPED_SFIELD +#pragma push_macro("TYPED_SFIELD") +#undef TYPED_SFIELD + +#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ + SField const sfName( \ + access, \ + STI_##stiSuffix, \ + fieldValue, \ + std::string_view(#sfName).substr(2).data(), \ + ##__VA_ARGS__); +#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ + SF_##stiSuffix const sfName( \ + access, \ + STI_##stiSuffix, \ + fieldValue, \ + std::string_view(#sfName).substr(2).data(), \ + ##__VA_ARGS__); + +// SFields which, for historical reasons, do not follow naming conventions. +SField const sfInvalid(access, -1); +SField const sfGeneric(access, 0); +// The following two fields aren't used anywhere, but they break tests/have +// downstream effects. +SField const sfHash(access, STI_UINT256, 257, "hash"); +SField const sfIndex(access, STI_UINT256, 258, "index"); + +#include + +#undef TYPED_SFIELD +#pragma pop_macro("TYPED_SFIELD") +#undef UNTYPED_SFIELD +#pragma pop_macro("UNTYPED_SFIELD") + +SField::SField( + private_access_tag_t, + SerializedTypeID tid, + int fv, + const char* fn, + int meta, + IsSigning signing) + : fieldCode(field_code(tid, fv)) + , fieldType(tid) + , fieldValue(fv) + , fieldName(fn) + , fieldMeta(meta) + , fieldNum(++num) + , signingField(signing) + , jsonName(fieldName.c_str()) +{ + knownCodeToField[fieldCode] = this; +} + +SField::SField(private_access_tag_t, int fc) + : fieldCode(fc) + , fieldType(STI_UNKNOWN) + , fieldValue(0) + , fieldMeta(sMD_Never) + , fieldNum(++num) + , signingField(IsSigning::yes) + , jsonName(fieldName.c_str()) +{ + knownCodeToField[fieldCode] = this; +} + +SField const& +SField::getField(int code) +{ + auto it = knownCodeToField.find(code); + + if (it != knownCodeToField.end()) + { + return *(it->second); + } + return sfInvalid; +} + +int +SField::compare(SField const& f1, SField const& f2) +{ + // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2 + if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0)) + return 0; + + if (f1.fieldCode < f2.fieldCode) + return -1; + + if (f2.fieldCode < f1.fieldCode) + return 1; + + return 0; +} + +SField const& +SField::getField(std::string const& fieldName) +{ + for (auto const& [_, f] : knownCodeToField) + { + (void)_; + if (f->fieldName == fieldName) + return *f; + } + return sfInvalid; +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/SOTemplate.cpp b/src/libxrpl/protocol/SOTemplate.cpp similarity index 98% rename from src/ripple/protocol/impl/SOTemplate.cpp rename to src/libxrpl/protocol/SOTemplate.cpp index 8457b759126..d75261c0a64 100644 --- a/src/ripple/protocol/impl/SOTemplate.cpp +++ b/src/libxrpl/protocol/SOTemplate.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/STAccount.cpp b/src/libxrpl/protocol/STAccount.cpp similarity index 98% rename from src/ripple/protocol/impl/STAccount.cpp rename to src/libxrpl/protocol/STAccount.cpp index 5881ae5b264..8ae43f76863 100644 --- a/src/ripple/protocol/impl/STAccount.cpp +++ b/src/libxrpl/protocol/STAccount.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp similarity index 78% rename from src/ripple/protocol/impl/STAmount.cpp rename to src/libxrpl/protocol/STAmount.cpp index a02dc9e89e1..fe13118d88c 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -17,14 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -63,10 +64,11 @@ static const std::uint64_t tenTo17 = tenTo14 * 1000; //------------------------------------------------------------------------------ static std::int64_t -getSNValue(STAmount const& amount) +getInt64Value(STAmount const& amount, bool valid, const char* error) { - if (!amount.native()) - Throw("amount is not native!"); + if (!valid) + Throw(error); + assert(amount.exponent() == 0); auto ret = static_cast(amount.mantissa()); @@ -78,26 +80,53 @@ getSNValue(STAmount const& amount) return ret; } +static std::int64_t +getSNValue(STAmount const& amount) +{ + return getInt64Value(amount, amount.native(), "amount is not native!"); +} + +static std::int64_t +getMPTValue(STAmount const& amount) +{ + return getInt64Value( + amount, amount.holds(), "amount is not MPT!"); +} + static bool areComparable(STAmount const& v1, STAmount const& v2) { - return v1.native() == v2.native() && - v1.issue().currency == v2.issue().currency; + if (v1.holds() && v2.holds()) + return v1.native() == v2.native() && + v1.get().currency == v2.get().currency; + if (v1.holds() && v2.holds()) + return v1.get() == v2.get(); + return false; } STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) { std::uint64_t value = sit.get64(); - // native - if ((value & cNotNative) == 0) + // native or MPT + if ((value & cIssuedCurrency) == 0) { + if ((value & cMPToken) != 0) + { + // is MPT + mOffset = 0; + mIsNegative = (value & cPositive) == 0; + mValue = (value << 8) | sit.get8(); + mAsset = sit.get192(); + return; + } + // else is XRP + mAsset = xrpIssue(); // positive - if ((value & cPosNative) != 0) + if ((value & cPositive) != 0) { - mValue = value & ~cPosNative; + mValue = value & cValueMask; mOffset = 0; - mIsNative = true; mIsNegative = false; return; } @@ -106,9 +135,8 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) if (value == 0) Throw("negative zero is not canonical"); - mValue = value; + mValue = value & cValueMask; mOffset = 0; - mIsNative = true; mIsNegative = true; return; } @@ -140,7 +168,7 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) Throw("invalid currency value"); } - mIssue = issue; + mAsset = issue; mValue = value; mOffset = offset; mIsNegative = isNegative; @@ -151,97 +179,32 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) if (offset != 512) Throw("invalid currency value"); - mIssue = issue; + mAsset = issue; mValue = 0; mOffset = 0; mIsNegative = false; canonicalize(); } -STAmount::STAmount( - SField const& name, - Issue const& issue, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative, - unchecked) - : STBase(name) - , mIssue(issue) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) -{ -} - -STAmount::STAmount( - Issue const& issue, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative, - unchecked) - : mIssue(issue) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) -{ -} - -STAmount::STAmount( - SField const& name, - Issue const& issue, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative) - : STBase(name) - , mIssue(issue) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) -{ - canonicalize(); -} - STAmount::STAmount(SField const& name, std::int64_t mantissa) - : STBase(name), mOffset(0), mIsNative(true) + : STBase(name), mAsset(xrpIssue()), mOffset(0) { set(mantissa); } STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative) : STBase(name) + , mAsset(xrpIssue()) , mValue(mantissa) , mOffset(0) - , mIsNative(true) - , mIsNegative(negative) -{ - assert(mValue <= std::numeric_limits::max()); -} - -STAmount::STAmount( - SField const& name, - Issue const& issue, - std::uint64_t mantissa, - int exponent, - bool negative) - : STBase(name) - , mIssue(issue) - , mValue(mantissa) - , mOffset(exponent) , mIsNegative(negative) { assert(mValue <= std::numeric_limits::max()); - canonicalize(); } STAmount::STAmount(SField const& name, STAmount const& from) : STBase(name) - , mIssue(from.mIssue) + , mAsset(from.mAsset) , mValue(from.mValue) , mOffset(from.mOffset) , mIsNegative(from.mIsNegative) @@ -253,62 +216,16 @@ STAmount::STAmount(SField const& name, STAmount const& from) //------------------------------------------------------------------------------ STAmount::STAmount(std::uint64_t mantissa, bool negative) - : mValue(mantissa) + : mAsset(xrpIssue()) + , mValue(mantissa) , mOffset(0) - , mIsNative(true) , mIsNegative(mantissa != 0 && negative) { assert(mValue <= std::numeric_limits::max()); } -STAmount::STAmount( - Issue const& issue, - std::uint64_t mantissa, - int exponent, - bool negative) - : mIssue(issue), mValue(mantissa), mOffset(exponent), mIsNegative(negative) -{ - canonicalize(); -} - -STAmount::STAmount(Issue const& issue, std::int64_t mantissa, int exponent) - : mIssue(issue), mOffset(exponent) -{ - set(mantissa); - canonicalize(); -} - -STAmount::STAmount( - Issue const& issue, - std::uint32_t mantissa, - int exponent, - bool negative) - : STAmount(issue, safe_cast(mantissa), exponent, negative) -{ -} - -STAmount::STAmount(Issue const& issue, int mantissa, int exponent) - : STAmount(issue, safe_cast(mantissa), exponent) -{ -} - -// Legacy support for new-style amounts -STAmount::STAmount(IOUAmount const& amount, Issue const& issue) - : mIssue(issue) - , mOffset(amount.exponent()) - , mIsNative(false) - , mIsNegative(amount < beast::zero) -{ - if (mIsNegative) - mValue = static_cast(-amount.mantissa()); - else - mValue = static_cast(amount.mantissa()); - - canonicalize(); -} - STAmount::STAmount(XRPAmount const& amount) - : mOffset(0), mIsNative(true), mIsNegative(amount < beast::zero) + : mAsset(xrpIssue()), mOffset(0), mIsNegative(amount < beast::zero) { if (mIsNegative) mValue = unsafe_cast(-amount.drops()); @@ -344,7 +261,7 @@ STAmount::move(std::size_t n, void* buf) XRPAmount STAmount::xrp() const { - if (!mIsNative) + if (!native()) Throw( "Cannot return non-native STAmount as XRPAmount"); @@ -359,8 +276,8 @@ STAmount::xrp() const IOUAmount STAmount::iou() const { - if (mIsNative) - Throw("Cannot return native STAmount as IOUAmount"); + if (native() || !holds()) + Throw("Cannot return non-IOU STAmount as IOUAmount"); auto mantissa = static_cast(mValue); auto exponent = mOffset; @@ -371,10 +288,24 @@ STAmount::iou() const return {mantissa, exponent}; } +MPTAmount +STAmount::mpt() const +{ + if (!holds()) + Throw("Cannot return STAmount as MPTAmount"); + + auto value = static_cast(mValue); + + if (mIsNegative) + value = -value; + + return MPTAmount{value}; +} + STAmount& STAmount::operator=(IOUAmount const& iou) { - assert(mIsNative == false); + assert(native() == false); mOffset = iou.exponent(); mIsNegative = iou < beast::zero; if (mIsNegative) @@ -418,7 +349,7 @@ operator+(STAmount const& v1, STAmount const& v2) // Result must be in terms of v1 currency and issuer. return { v1.getFName(), - v1.issue(), + v1.asset(), v2.mantissa(), v2.exponent(), v2.negative()}; @@ -426,6 +357,8 @@ operator+(STAmount const& v1, STAmount const& v2) if (v1.native()) return {v1.getFName(), getSNValue(v1) + getSNValue(v2)}; + if (v1.holds()) + return {v1.mAsset, v1.mpt().value() + v2.mpt().value()}; if (getSTNumberSwitchover()) { @@ -462,18 +395,18 @@ operator+(STAmount const& v1, STAmount const& v2) std::int64_t fv = vv1 + vv2; if ((fv >= -10) && (fv <= 10)) - return {v1.getFName(), v1.issue()}; + return {v1.getFName(), v1.asset()}; if (fv >= 0) return STAmount{ v1.getFName(), - v1.issue(), + v1.asset(), static_cast(fv), ov1, false}; return STAmount{ - v1.getFName(), v1.issue(), static_cast(-fv), ov1, true}; + v1.getFName(), v1.asset(), static_cast(-fv), ov1, true}; } STAmount @@ -487,10 +420,9 @@ operator-(STAmount const& v1, STAmount const& v2) std::uint64_t const STAmount::uRateOne = getRate(STAmount(1), STAmount(1)); void -STAmount::setIssue(Issue const& issue) +STAmount::setIssue(Asset const& asset) { - mIssue = issue; - mIsNative = isXRP(*this); + mAsset = asset; } // Convert an offer into an index amount so they sort by rate. @@ -529,13 +461,12 @@ STAmount::setJson(Json::Value& elem) const { elem = Json::objectValue; - if (!mIsNative) + if (!native()) { // It is an error for currency or issuer not to be specified for valid // json. elem[jss::value] = getText(); - elem[jss::currency] = to_string(mIssue.currency); - elem[jss::issuer] = to_string(mIssue.account); + mAsset.setJson(elem); } else { @@ -561,7 +492,7 @@ STAmount::getFullText() const std::string ret; ret.reserve(64); - ret = getText() + "/" + mIssue.getText(); + ret = getText() + "/" + mAsset.getText(); return ret; } @@ -581,7 +512,7 @@ STAmount::getText() const bool const scientific( (mOffset != 0) && ((mOffset < -25) || (mOffset > -5))); - if (mIsNative || scientific) + if (native() || mAsset.holds() || scientific) { ret.append(raw_value); @@ -650,7 +581,8 @@ STAmount::getText() const return ret; } -Json::Value STAmount::getJson(JsonOptions) const +Json::Value +STAmount::getJson(JsonOptions) const { Json::Value elem; setJson(elem); @@ -660,19 +592,28 @@ Json::Value STAmount::getJson(JsonOptions) const void STAmount::add(Serializer& s) const { - if (mIsNative) + if (native()) { assert(mOffset == 0); if (!mIsNegative) - s.add64(mValue | cPosNative); + s.add64(mValue | cPositive); else s.add64(mValue); } + else if (mAsset.holds()) + { + auto u8 = static_cast(cMPToken >> 56); + if (!mIsNegative) + u8 |= static_cast(cPositive >> 56); + s.add8(u8); + s.add64(mValue); + s.addBitString(mAsset.get().getMptID()); + } else { if (*this == beast::zero) - s.add64(cNotNative); + s.add64(cIssuedCurrency); else if (mIsNegative) // 512 = not native s.add64( mValue | @@ -682,9 +623,8 @@ STAmount::add(Serializer& s) const mValue | (static_cast(mOffset + 512 + 256 + 97) << (64 - 10))); - - s.addBitString(mIssue.currency); - s.addBitString(mIssue.account); + s.addBitString(mAsset.get().currency); + s.addBitString(mAsset.get().account); } } @@ -698,7 +638,7 @@ STAmount::isEquivalent(const STBase& t) const bool STAmount::isDefault() const { - return (mValue == 0) && mIsNative; + return (mValue == 0) && native(); } //------------------------------------------------------------------------------ @@ -722,11 +662,9 @@ STAmount::isDefault() const void STAmount::canonicalize() { - if (isXRP(*this)) + if (native() || mAsset.holds()) { - // native currency amounts should always have an offset of zero - mIsNative = true; - + // native and MPT currency amounts should always have an offset of zero // log(2^64,10) ~ 19.2 if (mValue == 0 || mOffset <= -20) { @@ -739,18 +677,26 @@ STAmount::canonicalize() if (getSTAmountCanonicalizeSwitchover()) { // log(cMaxNativeN, 10) == 17 - if (mOffset > 17) + if (native() && mOffset > 17) Throw( "Native currency amount out of range"); + // log(maxMPTokenAmount, 10) ~ 18.96 + if (mAsset.holds() && mOffset > 18) + Throw("MPT amount out of range"); } if (getSTNumberSwitchover() && getSTAmountCanonicalizeSwitchover()) { Number num( mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); - XRPAmount xrp{num}; - mIsNegative = xrp.drops() < 0; - mValue = mIsNegative ? -xrp.drops() : xrp.drops(); + auto set = [&](auto const& val) { + mIsNegative = val.value() < 0; + mValue = mIsNegative ? -val.value() : val.value(); + }; + if (native()) + set(XRPAmount{num}); + else + set(MPTAmount{num}); mOffset = 0; } else @@ -767,23 +713,25 @@ STAmount::canonicalize() { // N.B. do not move the overflow check to after the // multiplication - if (mValue > cMaxNativeN) + if (native() && mValue > cMaxNativeN) Throw( "Native currency amount out of range"); + else if (!native() && mValue > maxMPTokenAmount) + Throw("MPT amount out of range"); } mValue *= 10; --mOffset; } } - if (mValue > cMaxNativeN) + if (native() && mValue > cMaxNativeN) Throw("Native currency amount out of range"); + else if (!native() && mValue > maxMPTokenAmount) + Throw("MPT amount out of range"); return; } - mIsNative = false; - if (getSTNumberSwitchover()) { *this = iou(); @@ -859,7 +807,7 @@ amountFromQuality(std::uint64_t rate) } STAmount -amountFromString(Issue const& issue, std::string const& amount) +amountFromString(Asset const& asset, std::string const& amount) { static boost::regex const reNumber( "^" // the beginning of the string @@ -891,9 +839,10 @@ amountFromString(Issue const& issue, std::string const& amount) bool negative = (match[1].matched && (match[1] == "-")); - // Can't specify XRP using fractional representation - if (isXRP(issue) && match[3].matched) - Throw("XRP must be specified in integral drops."); + // Can't specify XRP or MPT using fractional representation + if ((asset.native() || asset.holds()) && match[3].matched) + Throw( + "XRP and MPT must be specified as integral amount."); std::uint64_t mantissa; int exponent; @@ -920,7 +869,7 @@ amountFromString(Issue const& issue, std::string const& amount) exponent += beast::lexicalCastThrow(std::string(match[7])); } - return {issue, mantissa, exponent, negative}; + return {asset, mantissa, exponent, negative}; } STAmount @@ -929,11 +878,12 @@ amountFromJson(SField const& name, Json::Value const& v) STAmount::mantissa_type mantissa = 0; STAmount::exponent_type exponent = 0; bool negative = false; - Issue issue; + Asset asset; Json::Value value; - Json::Value currency; + Json::Value currencyOrMPTID; Json::Value issuer; + bool isMPT = false; if (v.isNull()) { @@ -942,14 +892,25 @@ amountFromJson(SField const& name, Json::Value const& v) } else if (v.isObject()) { + if (!validJSONAsset(v)) + Throw("Invalid Asset's Json specification"); + value = v[jss::value]; - currency = v[jss::currency]; - issuer = v[jss::issuer]; + if (v.isMember(jss::mpt_issuance_id)) + { + isMPT = true; + currencyOrMPTID = v[jss::mpt_issuance_id]; + } + else + { + currencyOrMPTID = v[jss::currency]; + issuer = v[jss::issuer]; + } } else if (v.isArray()) { value = v.get(Json::UInt(0), 0); - currency = v.get(Json::UInt(1), Json::nullValue); + currencyOrMPTID = v.get(Json::UInt(1), Json::nullValue); issuer = v.get(Json::UInt(2), Json::nullValue); } else if (v.isString()) @@ -964,7 +925,7 @@ amountFromJson(SField const& name, Json::Value const& v) value = elements[0]; if (elements.size() > 1) - currency = elements[1]; + currencyOrMPTID = elements[1]; if (elements.size() > 2) issuer = elements[2]; @@ -974,26 +935,38 @@ amountFromJson(SField const& name, Json::Value const& v) value = v; } - bool const native = !currency.isString() || currency.asString().empty() || - (currency.asString() == systemCurrencyCode()); + bool const native = !currencyOrMPTID.isString() || + currencyOrMPTID.asString().empty() || + (currencyOrMPTID.asString() == systemCurrencyCode()); if (native) { if (v.isObjectOrNull()) Throw("XRP may not be specified as an object"); - issue = xrpIssue(); + asset = xrpIssue(); } else { - // non-XRP - if (!to_currency(issue.currency, currency.asString())) - Throw("invalid currency"); - - if (!issuer.isString() || !to_issuer(issue.account, issuer.asString())) - Throw("invalid issuer"); - - if (isXRP(issue.currency)) - Throw("invalid issuer"); + if (isMPT) + { + // sequence (32 bits) + account (160 bits) + uint192 u; + if (!u.parseHex(currencyOrMPTID.asString())) + Throw("invalid MPTokenIssuanceID"); + asset = u; + } + else + { + Issue issue; + if (!to_currency(issue.currency, currencyOrMPTID.asString())) + Throw("invalid currency"); + if (!issuer.isString() || + !to_issuer(issue.account, issuer.asString())) + Throw("invalid issuer"); + if (issue.native()) + Throw("invalid issuer"); + asset = issue; + } } if (value.isInt()) @@ -1014,7 +987,7 @@ amountFromJson(SField const& name, Json::Value const& v) } else if (value.isString()) { - auto const ret = amountFromString(issue, value.asString()); + auto const ret = amountFromString(asset, value.asString()); mantissa = ret.mantissa(); exponent = ret.exponent(); @@ -1025,7 +998,7 @@ amountFromJson(SField const& name, Json::Value const& v) Throw("invalid amount type"); } - return {name, issue, mantissa, exponent, native, negative}; + return {name, asset, mantissa, exponent, negative}; } bool @@ -1099,10 +1072,9 @@ operator-(STAmount const& value) return value; return STAmount( value.getFName(), - value.issue(), + value.asset(), value.mantissa(), value.exponent(), - value.native(), !value.negative(), STAmount::unchecked{}); } @@ -1161,20 +1133,20 @@ muldiv_round( } STAmount -divide(STAmount const& num, STAmount const& den, Issue const& issue) +divide(STAmount const& num, STAmount const& den, Asset const& asset) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {issue}; + return {asset}; std::uint64_t numVal = num.mantissa(); std::uint64_t denVal = den.mantissa(); int numOffset = num.exponent(); int denOffset = den.exponent(); - if (num.native()) + if (num.native() || num.holds()) { while (numVal < STAmount::cMinValue) { @@ -1184,7 +1156,7 @@ divide(STAmount const& num, STAmount const& den, Issue const& issue) } } - if (den.native()) + if (den.native() || den.holds()) { while (denVal < STAmount::cMinValue) { @@ -1199,24 +1171,22 @@ divide(STAmount const& num, STAmount const& den, Issue const& issue) // 10^32 to 10^33) followed by a division, so the result // is in the range of 10^16 to 10^15. return STAmount( - issue, + asset, muldiv(numVal, tenTo17, denVal) + 5, numOffset - denOffset - 17, num.negative() != den.negative()); } STAmount -multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) +multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) { if (v1 == beast::zero || v2 == beast::zero) - return STAmount(issue); + return STAmount(asset); - if (v1.native() && v2.native() && isXRP(issue)) + if (v1.native() && v2.native() && asset.native()) { - std::uint64_t const minV = - getSNValue(v1) < getSNValue(v2) ? getSNValue(v1) : getSNValue(v2); - std::uint64_t const maxV = - getSNValue(v1) < getSNValue(v2) ? getSNValue(v2) : getSNValue(v1); + std::uint64_t const minV = std::min(getSNValue(v1), getSNValue(v2)); + std::uint64_t const maxV = std::max(getSNValue(v1), getSNValue(v2)); if (minV > 3000000000ull) // sqrt(cMaxNative) Throw("Native value overflow"); @@ -1226,16 +1196,32 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) return STAmount(v1.getFName(), minV * maxV); } + if (v1.holds() && v2.holds() && asset.holds()) + { + std::uint64_t const minV = std::min(getMPTValue(v1), getMPTValue(v2)); + std::uint64_t const maxV = std::max(getMPTValue(v1), getMPTValue(v2)); + + if (minV > 3037000499ull) // sqrt(maxMPTokenAmount) ~ 3037000499.98 + Throw("MPT value overflow"); + + if (((maxV >> 32) * minV) > 2147483648ull) // maxMPTokenAmount / 2^32 + Throw("MPT value overflow"); + + return STAmount(asset, minV * maxV); + } if (getSTNumberSwitchover()) - return {IOUAmount{Number{v1} * Number{v2}}, issue}; + { + auto const r = Number{v1} * Number{v2}; + return STAmount{asset, r.mantissa(), r.exponent()}; + } std::uint64_t value1 = v1.mantissa(); std::uint64_t value2 = v2.mantissa(); int offset1 = v1.exponent(); int offset2 = v2.exponent(); - if (v1.native()) + if (v1.native() || v1.holds()) { while (value1 < STAmount::cMinValue) { @@ -1244,7 +1230,7 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) } } - if (v2.native()) + if (v2.native() || v2.holds()) { while (value2 < STAmount::cMinValue) { @@ -1258,7 +1244,7 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) // range. Dividing their product by 10^14 maintains the // precision, by scaling the result to 10^16 to 10^18. return STAmount( - issue, + asset, muldiv(value1, value2, tenTo14) + 7, offset1 + offset2 + 14, v1.negative() != v2.negative()); @@ -1395,20 +1381,18 @@ static STAmount mulRoundImpl( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { if (v1 == beast::zero || v2 == beast::zero) - return {issue}; + return {asset}; - bool const xrp = isXRP(issue); + bool const xrp = asset.native(); if (v1.native() && v2.native() && xrp) { - std::uint64_t minV = - (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v1) : getSNValue(v2); - std::uint64_t maxV = - (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v2) : getSNValue(v1); + std::uint64_t minV = std::min(getSNValue(v1), getSNValue(v2)); + std::uint64_t maxV = std::max(getSNValue(v1), getSNValue(v2)); if (minV > 3000000000ull) // sqrt(cMaxNative) Throw("Native value overflow"); @@ -1419,10 +1403,24 @@ mulRoundImpl( return STAmount(v1.getFName(), minV * maxV); } + if (v1.holds() && v2.holds() && asset.holds()) + { + std::uint64_t const minV = std::min(getMPTValue(v1), getMPTValue(v2)); + std::uint64_t const maxV = std::max(getMPTValue(v1), getMPTValue(v2)); + + if (minV > 3037000499ull) // sqrt(maxMPTokenAmount) ~ 3037000499.98 + Throw("MPT value overflow"); + + if (((maxV >> 32) * minV) > 2147483648ull) // maxMPTokenAmount / 2^32 + Throw("MPT value overflow"); + + return STAmount(asset, minV * maxV); + } + std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); int offset1 = v1.exponent(), offset2 = v2.exponent(); - if (v1.native()) + if (v1.native() || v1.holds()) { while (value1 < STAmount::cMinValue) { @@ -1431,7 +1429,7 @@ mulRoundImpl( } } - if (v2.native()) + if (v2.native() || v2.holds()) { while (value2 < STAmount::cMinValue) { @@ -1462,7 +1460,7 @@ mulRoundImpl( // If appropriate, tell Number to round down. This gives the desired // result from STAmount::canonicalize. MightSaveRound const savedRound(Number::towards_zero); - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) @@ -1479,7 +1477,7 @@ mulRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); } return result; } @@ -1488,22 +1486,22 @@ STAmount mulRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { return mulRoundImpl( - v1, v2, issue, roundUp); + v1, v2, asset, roundUp); } STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { return mulRoundImpl( - v1, v2, issue, roundUp); + v1, v2, asset, roundUp); } // We might need to use NumberRoundModeGuard. Allow the caller @@ -1513,19 +1511,19 @@ static STAmount divRoundImpl( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {issue}; + return {asset}; std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); int numOffset = num.exponent(), denOffset = den.exponent(); - if (num.native()) + if (num.native() || num.holds()) { while (numVal < STAmount::cMinValue) { @@ -1534,7 +1532,7 @@ divRoundImpl( } } - if (den.native()) + if (den.native() || den.holds()) { while (denVal < STAmount::cMinValue) { @@ -1559,7 +1557,8 @@ divRoundImpl( int offset = numOffset - denOffset - 17; if (resultNegative != roundUp) - canonicalizeRound(isXRP(issue), amount, offset, roundUp); + canonicalizeRound( + asset.native() || asset.holds(), amount, offset, roundUp); STAmount result = [&]() { // If appropriate, tell Number the rounding mode we are using. @@ -1568,12 +1567,12 @@ divRoundImpl( using enum Number::rounding_mode; MightSaveRound const savedRound( roundUp ^ resultNegative ? upward : downward); - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) { - if (isXRP(issue)) + if (asset.native() || asset.holds()) { // return the smallest value above zero amount = 1; @@ -1585,7 +1584,7 @@ divRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); } return result; } @@ -1594,20 +1593,20 @@ STAmount divRound( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, issue, roundUp); + return divRoundImpl(num, den, asset, roundUp); } STAmount divRoundStrict( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, issue, roundUp); + return divRoundImpl(num, den, asset, roundUp); } } // namespace ripple diff --git a/src/ripple/protocol/impl/STArray.cpp b/src/libxrpl/protocol/STArray.cpp similarity index 96% rename from src/ripple/protocol/impl/STArray.cpp rename to src/libxrpl/protocol/STArray.cpp index 7ee3da1ff1a..0dea9c7665e 100644 --- a/src/ripple/protocol/impl/STArray.cpp +++ b/src/libxrpl/protocol/STArray.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/STBase.cpp b/src/libxrpl/protocol/STBase.cpp similarity index 96% rename from src/ripple/protocol/impl/STBase.cpp rename to src/libxrpl/protocol/STBase.cpp index b49b65bf25d..73565cbf765 100644 --- a/src/ripple/protocol/impl/STBase.cpp +++ b/src/libxrpl/protocol/STBase.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include @@ -96,7 +96,8 @@ STBase::getText() const return std::string(); } -Json::Value STBase::getJson(JsonOptions /*options*/) const +Json::Value +STBase::getJson(JsonOptions /*options*/) const { return getText(); } diff --git a/src/ripple/protocol/impl/STBlob.cpp b/src/libxrpl/protocol/STBlob.cpp similarity index 96% rename from src/ripple/protocol/impl/STBlob.cpp rename to src/libxrpl/protocol/STBlob.cpp index 871c535e9dd..63e7b4bf3da 100644 --- a/src/ripple/protocol/impl/STBlob.cpp +++ b/src/libxrpl/protocol/STBlob.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/STCurrency.cpp b/src/libxrpl/protocol/STCurrency.cpp similarity index 94% rename from src/ripple/protocol/impl/STCurrency.cpp rename to src/libxrpl/protocol/STCurrency.cpp index d2bc1b3bea7..56ac19da114 100644 --- a/src/ripple/protocol/impl/STCurrency.cpp +++ b/src/libxrpl/protocol/STCurrency.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include +#include +#include -#include +#include namespace ripple { @@ -50,7 +50,8 @@ STCurrency::getText() const return to_string(currency_); } -Json::Value STCurrency::getJson(JsonOptions) const +Json::Value +STCurrency::getJson(JsonOptions) const { return to_string(currency_); } diff --git a/src/ripple/protocol/impl/STInteger.cpp b/src/libxrpl/protocol/STInteger.cpp similarity index 78% rename from src/ripple/protocol/impl/STInteger.cpp rename to src/libxrpl/protocol/STInteger.cpp index 2b154e369e4..7dfcc50dea0 100644 --- a/src/ripple/protocol/impl/STInteger.cpp +++ b/src/libxrpl/protocol/STInteger.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -61,7 +61,8 @@ STUInt8::getText() const } template <> -Json::Value STUInt8::getJson(JsonOptions) const +Json::Value +STUInt8::getJson(JsonOptions) const { if (getFName() == sfTransactionResult) { @@ -118,7 +119,8 @@ STUInt16::getText() const } template <> -Json::Value STUInt16::getJson(JsonOptions) const +Json::Value +STUInt16::getJson(JsonOptions) const { if (getFName() == sfLedgerEntryType) { @@ -164,7 +166,8 @@ STUInt32::getText() const } template <> -Json::Value STUInt32::getJson(JsonOptions) const +Json::Value +STUInt32::getJson(JsonOptions) const { return value_; } @@ -192,13 +195,26 @@ STUInt64::getText() const } template <> -Json::Value STUInt64::getJson(JsonOptions) const +Json::Value +STUInt64::getJson(JsonOptions) const { - std::string str(16, 0); - auto ret = std::to_chars(str.data(), str.data() + str.size(), value_, 16); - assert(ret.ec == std::errc()); - str.resize(std::distance(str.data(), ret.ptr)); - return str; + auto convertToString = [](uint64_t const value, int const base) { + assert(base == 10 || base == 16); + std::string str( + base == 10 ? 20 : 16, 0); // Allocate space depending on base + auto ret = + std::to_chars(str.data(), str.data() + str.size(), value, base); + assert(ret.ec == std::errc()); + str.resize(std::distance(str.data(), ret.ptr)); + return str; + }; + + if (auto const& fName = getFName(); fName.shouldMeta(SField::sMD_BaseTen)) + { + return convertToString(value_, 10); // Convert to base 10 + } + + return convertToString(value_, 16); // Convert to base 16 } } // namespace ripple diff --git a/src/ripple/protocol/impl/STIssue.cpp b/src/libxrpl/protocol/STIssue.cpp similarity index 92% rename from src/ripple/protocol/impl/STIssue.cpp rename to src/libxrpl/protocol/STIssue.cpp index 356af438659..00fe86b3287 100644 --- a/src/ripple/protocol/impl/STIssue.cpp +++ b/src/libxrpl/protocol/STIssue.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -65,7 +65,8 @@ STIssue::getSType() const return STI_ISSUE; } -Json::Value STIssue::getJson(JsonOptions) const +Json::Value +STIssue::getJson(JsonOptions) const { return to_json(issue_); } diff --git a/src/ripple/protocol/impl/STLedgerEntry.cpp b/src/libxrpl/protocol/STLedgerEntry.cpp similarity index 90% rename from src/ripple/protocol/impl/STLedgerEntry.cpp rename to src/libxrpl/protocol/STLedgerEntry.cpp index 10ec5627aa3..1801149ab2a 100644 --- a/src/ripple/protocol/impl/STLedgerEntry.cpp +++ b/src/libxrpl/protocol/STLedgerEntry.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -125,6 +125,10 @@ STLedgerEntry::getJson(JsonOptions options) const ret[jss::index] = to_string(key_); + if (getType() == ltMPTOKEN_ISSUANCE) + ret[jss::mpt_issuance_id] = to_string( + makeMptID(getFieldU32(sfSequence), getAccountID(sfIssuer))); + return ret; } diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp new file mode 100644 index 00000000000..3a92bbb02f9 --- /dev/null +++ b/src/libxrpl/protocol/STNumber.cpp @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include + +namespace ripple { + +STNumber::STNumber(SField const& field, Number const& value) + : STBase(field), value_(value) +{ +} + +STNumber::STNumber(SerialIter& sit, SField const& field) : STBase(field) +{ + // We must call these methods in separate statements + // to guarantee their order of execution. + auto mantissa = sit.geti64(); + auto exponent = sit.geti32(); + value_ = Number{mantissa, exponent}; +} + +SerializedTypeID +STNumber::getSType() const +{ + return STI_NUMBER; +} + +std::string +STNumber::getText() const +{ + return to_string(value_); +} + +void +STNumber::add(Serializer& s) const +{ + assert(getFName().isBinary()); + assert(getFName().fieldType == getSType()); + s.add64(value_.mantissa()); + s.add32(value_.exponent()); +} + +Number const& +STNumber::value() const +{ + return value_; +} + +void +STNumber::setValue(Number const& v) +{ + value_ = v; +} + +STBase* +STNumber::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STNumber::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +bool +STNumber::isEquivalent(STBase const& t) const +{ + assert(t.getSType() == this->getSType()); + STNumber const& v = dynamic_cast(t); + return value_ == v; +} + +bool +STNumber::isDefault() const +{ + return value_ == Number(); +} + +std::ostream& +operator<<(std::ostream& out, STNumber const& rhs) +{ + return out << rhs.getText(); +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/STObject.cpp b/src/libxrpl/protocol/STObject.cpp similarity index 93% rename from src/ripple/protocol/impl/STObject.cpp rename to src/libxrpl/protocol/STObject.cpp index dbcb47e8794..c8fc88348e9 100644 --- a/src/ripple/protocol/impl/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -17,15 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -61,10 +62,19 @@ STObject::STObject(SerialIter& sit, SField const& name, int depth) noexcept( } STObject -STObject::makeInnerObject(SField const& name, Rules const& rules) +STObject::makeInnerObject(SField const& name) { STObject obj{name}; - if (rules.enabled(fixInnerObjTemplate)) + + // The if is complicated because inner object templates were added in + // two phases: + // 1. If there are no available Rules, then always apply the template. + // 2. fixInnerObjTemplate added templates to two AMM inner objects. + // 3. fixInnerObjTemplate2 added templates to all remaining inner objects. + std::optional const& rules = getCurrentTransactionRules(); + bool const isAMMObj = name == sfAuctionSlot || name == sfVoteEntry; + if (!rules || (rules->enabled(fixInnerObjTemplate) && isAMMObj) || + (rules->enabled(fixInnerObjTemplate2) && !isAMMObj)) { if (SOTemplate const* elements = InnerObjectFormats::getInstance().findSOTemplateBySField(name)) @@ -595,6 +605,12 @@ STObject::getFieldH160(SField const& field) const return getFieldByValue(field); } +uint192 +STObject::getFieldH192(SField const& field) const +{ + return getFieldByValue(field); +} + uint256 STObject::getFieldH256(SField const& field) const { @@ -650,6 +666,13 @@ STObject::getFieldCurrency(SField const& field) const return getFieldByConstRef(field, empty); } +STNumber const& +STObject::getFieldNumber(SField const& field) const +{ + static STNumber const empty{}; + return getFieldByConstRef(field, empty); +} + void STObject::set(std::unique_ptr v) { @@ -750,6 +773,12 @@ STObject::setFieldIssue(SField const& field, STIssue const& v) setFieldUsingAssignment(field, v); } +void +STObject::setFieldNumber(SField const& field, STNumber const& v) +{ + setFieldUsingAssignment(field, v); +} + void STObject::setFieldPathSet(SField const& field, STPathSet const& v) { diff --git a/src/ripple/protocol/impl/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp similarity index 94% rename from src/ripple/protocol/impl/STParsedJSON.cpp rename to src/libxrpl/protocol/STParsedJSON.cpp index 6727fe7388c..09b6b6679d7 100644 --- a/src/ripple/protocol/impl/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -17,29 +17,29 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -398,8 +398,15 @@ parseLeaf( std::uint64_t val; + bool const useBase10 = + field.shouldMeta(SField::sMD_BaseTen); + + // if the field is amount, serialize as base 10 auto [p, ec] = std::from_chars( - str.data(), str.data() + str.size(), val, 16); + str.data(), + str.data() + str.size(), + val, + useBase10 ? 10 : 16); if (ec != std::errc() || (p != str.data() + str.size())) Throw("invalid data"); @@ -454,6 +461,30 @@ parseLeaf( break; } + case STI_UINT192: { + if (!value.isString()) + { + error = bad_type(json_name, fieldName); + return ret; + } + + uint192 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + error = invalid_data(json_name, fieldName); + return ret; + } + + num.zero(); + } + + ret = detail::make_stvar(field, num); + break; + } + case STI_UINT160: { if (!value.isString()) { @@ -967,8 +998,7 @@ parseArray( Json::Value const objectFields(json[i][objectName]); std::stringstream ss; - ss << json_name << "." - << "[" << i << "]." << objectName; + ss << json_name << "." << "[" << i << "]." << objectName; auto ret = parseObject( ss.str(), objectFields, nameField, depth + 1, error); diff --git a/src/ripple/protocol/impl/STPathSet.cpp b/src/libxrpl/protocol/STPathSet.cpp similarity index 95% rename from src/ripple/protocol/impl/STPathSet.cpp rename to src/libxrpl/protocol/STPathSet.cpp index cc1e367ba45..78c47cadc66 100644 --- a/src/ripple/protocol/impl/STPathSet.cpp +++ b/src/libxrpl/protocol/STPathSet.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -163,7 +163,8 @@ STPath::hasSeen( return false; } -Json::Value STPath::getJson(JsonOptions) const +Json::Value +STPath::getJson(JsonOptions) const { Json::Value ret(Json::arrayValue); diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/libxrpl/protocol/STTx.cpp similarity index 91% rename from src/ripple/protocol/impl/STTx.cpp rename to src/libxrpl/protocol/STTx.cpp index 51fb11ad761..7bd25246c53 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -17,22 +17,22 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -543,6 +543,32 @@ isAccountFieldOkay(STObject const& st) return true; } +static bool +invalidMPTAmountInTx(STObject const& tx) +{ + auto const txType = tx[~sfTransactionType]; + if (!txType) + return false; + if (auto const* item = + TxFormats::getInstance().findByType(safe_cast(*txType))) + { + for (auto const& e : item->getSOTemplate()) + { + if (tx.isFieldPresent(e.sField()) && e.supportMPT() != soeMPTNone) + { + if (auto const& field = tx.peekAtField(e.sField()); + field.getSType() == STI_AMOUNT && + static_cast(field).holds()) + { + if (e.supportMPT() != soeMPTSupported) + return true; + } + } + } + } + return false; +} + bool passesLocalChecks(STObject const& st, std::string& reason) { @@ -560,6 +586,13 @@ passesLocalChecks(STObject const& st, std::string& reason) reason = "Cannot submit pseudo transactions."; return false; } + + if (invalidMPTAmountInTx(st)) + { + reason = "Amount can not be MPT."; + return false; + } + return true; } diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp similarity index 95% rename from src/ripple/protocol/impl/STValidation.cpp rename to src/libxrpl/protocol/STValidation.cpp index e62a81733bd..ca5ceb0d9ce 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/STVar.cpp b/src/libxrpl/protocol/STVar.cpp similarity index 53% rename from src/ripple/protocol/impl/STVar.cpp rename to src/libxrpl/protocol/STVar.cpp index adda165901f..55927cb33aa 100644 --- a/src/ripple/protocol/impl/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -17,23 +17,24 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace detail { @@ -115,141 +116,119 @@ STVar::STVar(SerialIter& sit, SField const& name, int depth) { if (depth > 10) Throw("Maximum nesting depth of STVar exceeded"); - switch (name.fieldType) - { - case STI_NOTPRESENT: - construct(name); - return; - case STI_UINT8: - construct(sit, name); - return; - case STI_UINT16: - construct(sit, name); - return; - case STI_UINT32: - construct(sit, name); - return; - case STI_UINT64: - construct(sit, name); - return; - case STI_AMOUNT: - construct(sit, name); - return; - case STI_UINT128: - construct(sit, name); - return; - case STI_UINT160: - construct(sit, name); - return; - case STI_UINT256: - construct(sit, name); - return; - case STI_VECTOR256: - construct(sit, name); - return; - case STI_VL: - construct(sit, name); - return; - case STI_ACCOUNT: - construct(sit, name); - return; - case STI_PATHSET: - construct(sit, name); - return; - case STI_OBJECT: - construct(sit, name, depth); - return; - case STI_ARRAY: - construct(sit, name, depth); - return; - case STI_ISSUE: - construct(sit, name); - return; - case STI_XCHAIN_BRIDGE: - construct(sit, name); - return; - case STI_CURRENCY: - construct(sit, name); - return; - default: - Throw("Unknown object type"); - } + constructST(name.fieldType, depth, sit, name); } STVar::STVar(SerializedTypeID id, SField const& name) { assert((id == STI_NOTPRESENT) || (id == name.fieldType)); + constructST(id, 0, name); +} + +void +STVar::destroy() +{ + if (on_heap()) + delete p_; + else + p_->~STBase(); + + p_ = nullptr; +} + +template + requires ValidConstructSTArgs +void +STVar::constructST(SerializedTypeID id, int depth, Args&&... args) +{ + auto constructWithDepth = [&]() { + if constexpr (std::is_same_v< + std::tuple...>, + std::tuple>) + { + construct(std::forward(args)...); + } + else if constexpr (std::is_same_v< + std::tuple...>, + std::tuple>) + { + construct(std::forward(args)..., depth); + } + else + { + constexpr bool alwaysFalse = + !std::is_same_v, std::tuple>; + static_assert(alwaysFalse, "Invalid STVar constructor arguments"); + } + }; + switch (id) { - case STI_NOTPRESENT: - construct(name); + case STI_NOTPRESENT: { + // Last argument is always SField + SField const& field = + std::get(std::forward_as_tuple(args...)); + construct(field); return; + } case STI_UINT8: - construct(name); + construct(std::forward(args)...); return; case STI_UINT16: - construct(name); + construct(std::forward(args)...); return; case STI_UINT32: - construct(name); + construct(std::forward(args)...); return; case STI_UINT64: - construct(name); + construct(std::forward(args)...); return; case STI_AMOUNT: - construct(name); + construct(std::forward(args)...); return; case STI_UINT128: - construct(name); + construct(std::forward(args)...); return; case STI_UINT160: - construct(name); + construct(std::forward(args)...); + return; + case STI_UINT192: + construct(std::forward(args)...); return; case STI_UINT256: - construct(name); + construct(std::forward(args)...); return; case STI_VECTOR256: - construct(name); + construct(std::forward(args)...); return; case STI_VL: - construct(name); + construct(std::forward(args)...); return; case STI_ACCOUNT: - construct(name); + construct(std::forward(args)...); return; case STI_PATHSET: - construct(name); + construct(std::forward(args)...); return; case STI_OBJECT: - construct(name); + constructWithDepth.template operator()(); return; case STI_ARRAY: - construct(name); + constructWithDepth.template operator()(); return; case STI_ISSUE: - construct(name); + construct(std::forward(args)...); return; case STI_XCHAIN_BRIDGE: - construct(name); + construct(std::forward(args)...); return; case STI_CURRENCY: - construct(name); + construct(std::forward(args)...); return; default: Throw("Unknown object type"); } } -void -STVar::destroy() -{ - if (on_heap()) - delete p_; - else - p_->~STBase(); - - p_ = nullptr; -} - } // namespace detail } // namespace ripple diff --git a/src/ripple/protocol/impl/STVector256.cpp b/src/libxrpl/protocol/STVector256.cpp similarity index 92% rename from src/ripple/protocol/impl/STVector256.cpp rename to src/libxrpl/protocol/STVector256.cpp index 0ef1295b16a..385c0ef76db 100644 --- a/src/ripple/protocol/impl/STVector256.cpp +++ b/src/libxrpl/protocol/STVector256.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -80,7 +80,8 @@ STVector256::isEquivalent(const STBase& t) const return v && (mValue == v->mValue); } -Json::Value STVector256::getJson(JsonOptions) const +Json::Value +STVector256::getJson(JsonOptions) const { Json::Value ret(Json::arrayValue); diff --git a/src/ripple/protocol/impl/STXChainBridge.cpp b/src/libxrpl/protocol/STXChainBridge.cpp similarity index 93% rename from src/ripple/protocol/impl/STXChainBridge.cpp rename to src/libxrpl/protocol/STXChainBridge.cpp index 8ff19ca7e3b..2347e63379a 100644 --- a/src/ripple/protocol/impl/STXChainBridge.cpp +++ b/src/libxrpl/protocol/STXChainBridge.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/protocol/impl/SecretKey.cpp b/src/libxrpl/protocol/SecretKey.cpp similarity index 97% rename from src/ripple/protocol/impl/SecretKey.cpp rename to src/libxrpl/protocol/SecretKey.cpp index e83068610c9..474c37ac802 100644 --- a/src/ripple/protocol/impl/SecretKey.cpp +++ b/src/libxrpl/protocol/SecretKey.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/protocol/impl/Seed.cpp b/src/libxrpl/protocol/Seed.cpp similarity index 89% rename from src/ripple/protocol/impl/Seed.cpp rename to src/libxrpl/protocol/Seed.cpp index 49da20a429d..453b4593bfb 100644 --- a/src/ripple/protocol/impl/Seed.cpp +++ b/src/libxrpl/protocol/Seed.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/protocol/impl/Serializer.cpp b/src/libxrpl/protocol/Serializer.cpp similarity index 90% rename from src/ripple/protocol/impl/Serializer.cpp rename to src/libxrpl/protocol/Serializer.cpp index 42f79cfc518..ceaf76faf34 100644 --- a/src/ripple/protocol/impl/Serializer.cpp +++ b/src/libxrpl/protocol/Serializer.cpp @@ -17,10 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -34,17 +35,6 @@ Serializer::add16(std::uint16_t i) return ret; } -int -Serializer::add32(std::uint32_t i) -{ - int ret = mData.size(); - mData.push_back(static_cast(i >> 24)); - mData.push_back(static_cast((i >> 16) & 0xff)); - mData.push_back(static_cast((i >> 8) & 0xff)); - mData.push_back(static_cast(i & 0xff)); - return ret; -} - int Serializer::add32(HashPrefix p) { @@ -56,21 +46,6 @@ Serializer::add32(HashPrefix p) return add32(safe_cast(p)); } -int -Serializer::add64(std::uint64_t i) -{ - int ret = mData.size(); - mData.push_back(static_cast(i >> 56)); - mData.push_back(static_cast((i >> 48) & 0xff)); - mData.push_back(static_cast((i >> 40) & 0xff)); - mData.push_back(static_cast((i >> 32) & 0xff)); - mData.push_back(static_cast((i >> 24) & 0xff)); - mData.push_back(static_cast((i >> 16) & 0xff)); - mData.push_back(static_cast((i >> 8) & 0xff)); - mData.push_back(static_cast(i & 0xff)); - return ret; -} - template <> int Serializer::addInteger(unsigned char i) @@ -410,6 +385,30 @@ SerialIter::get64() (std::uint64_t(t[6]) << 8) + std::uint64_t(t[7]); } +std::int32_t +SerialIter::geti32() +{ + if (remain_ < 4) + Throw("invalid SerialIter geti32"); + auto t = p_; + p_ += 4; + used_ += 4; + remain_ -= 4; + return boost::endian::load_big_s32(t); +} + +std::int64_t +SerialIter::geti64() +{ + if (remain_ < 8) + Throw("invalid SerialIter geti64"); + auto t = p_; + p_ += 8; + used_ += 8; + remain_ -= 8; + return boost::endian::load_big_s64(t); +} + void SerialIter::getFieldID(int& type, int& name) { diff --git a/src/ripple/protocol/impl/Sign.cpp b/src/libxrpl/protocol/Sign.cpp similarity index 99% rename from src/ripple/protocol/impl/Sign.cpp rename to src/libxrpl/protocol/Sign.cpp index b6313827f91..32645c3762c 100644 --- a/src/ripple/protocol/impl/Sign.cpp +++ b/src/libxrpl/protocol/Sign.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/TER.cpp b/src/libxrpl/protocol/TER.cpp similarity index 97% rename from src/ripple/protocol/impl/TER.cpp rename to src/libxrpl/protocol/TER.cpp index 93bc60a98ba..815b27c0018 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include @@ -115,6 +115,8 @@ transResults() MAKE_ERROR(tecTOKEN_PAIR_NOT_FOUND, "Token pair is not found in Oracle object."), MAKE_ERROR(tecARRAY_EMPTY, "Array is empty."), MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."), + MAKE_ERROR(tecLOCKED, "Fund is locked."), + MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -137,6 +139,7 @@ transResults() MAKE_ERROR(tefTOO_BIG, "Transaction affects too many items."), MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), + MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), @@ -158,7 +161,7 @@ transResults() MAKE_ERROR(temMALFORMED, "Malformed transaction."), MAKE_ERROR(temBAD_AMM_TOKENS, "Malformed: Invalid LPTokens."), - MAKE_ERROR(temBAD_AMOUNT, "Can only send positive amounts."), + MAKE_ERROR(temBAD_AMOUNT, "Malformed: Bad amount."), MAKE_ERROR(temBAD_CURRENCY, "Malformed: Bad currency."), MAKE_ERROR(temBAD_EXPIRATION, "Malformed: Bad expiration."), MAKE_ERROR(temBAD_FEE, "Invalid fee, negative or not XRP."), @@ -204,6 +207,7 @@ transResults() MAKE_ERROR(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, "Malformed: Bad reward amount."), MAKE_ERROR(temARRAY_EMPTY, "Malformed: Array is empty."), MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."), + MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp new file mode 100644 index 00000000000..76b1ae8ad4f --- /dev/null +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include +#include +#include + +namespace ripple { + +TxFormats::TxFormats() +{ + // Fields shared by all txFormats: + static const std::initializer_list commonFields{ + {sfTransactionType, soeREQUIRED}, + {sfFlags, soeOPTIONAL}, + {sfSourceTag, soeOPTIONAL}, + {sfAccount, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfPreviousTxnID, soeOPTIONAL}, // emulate027 + {sfLastLedgerSequence, soeOPTIONAL}, + {sfAccountTxnID, soeOPTIONAL}, + {sfFee, soeREQUIRED}, + {sfOperationLimit, soeOPTIONAL}, + {sfMemos, soeOPTIONAL}, + {sfSigningPubKey, soeREQUIRED}, + {sfTicketSequence, soeOPTIONAL}, + {sfTxnSignature, soeOPTIONAL}, + {sfSigners, soeOPTIONAL}, // submit_multisigned + {sfNetworkID, soeOPTIONAL}, + }; + +#pragma push_macro("UNWRAP") +#undef UNWRAP +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define UNWRAP(...) __VA_ARGS__ +#define TRANSACTION(tag, value, name, fields) \ + add(jss::name, tag, UNWRAP fields, commonFields); + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") +#undef UNWRAP +#pragma pop_macro("UNWRAP") +} + +TxFormats const& +TxFormats::getInstance() +{ + static TxFormats const instance; + return instance; +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp similarity index 97% rename from src/ripple/protocol/impl/TxMeta.cpp rename to src/libxrpl/protocol/TxMeta.cpp index 20fa61de2a8..253d00e8414 100644 --- a/src/ripple/protocol/impl/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/impl/UintTypes.cpp b/src/libxrpl/protocol/UintTypes.cpp similarity index 95% rename from src/ripple/protocol/impl/UintTypes.cpp rename to src/libxrpl/protocol/UintTypes.cpp index 821e81238b0..c57bcc153a3 100644 --- a/src/ripple/protocol/impl/UintTypes.cpp +++ b/src/libxrpl/protocol/UintTypes.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/impl/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp similarity index 95% rename from src/ripple/protocol/impl/XChainAttestations.cpp rename to src/libxrpl/protocol/XChainAttestations.cpp index 591b20ad5a0..82e73445693 100644 --- a/src/ripple/protocol/impl/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -17,23 +17,23 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -203,7 +203,8 @@ AttestationClaim::AttestationClaim(Json::Value const& v) STObject AttestationClaim::toSTObject() const { - STObject o{sfXChainClaimAttestationCollectionElement}; + STObject o = + STObject::makeInnerObject(sfXChainClaimAttestationCollectionElement); addHelper(o); o[sfXChainClaimID] = claimID; if (dst) @@ -345,7 +346,8 @@ AttestationCreateAccount::AttestationCreateAccount( STObject AttestationCreateAccount::toSTObject() const { - STObject o{sfXChainCreateAccountAttestationCollectionElement}; + STObject o = STObject::makeInnerObject( + sfXChainCreateAccountAttestationCollectionElement); addHelper(o); o[sfXChainAccountCreateCount] = createCount; @@ -497,7 +499,7 @@ XChainClaimAttestation::XChainClaimAttestation( STObject XChainClaimAttestation::toSTObject() const { - STObject o{sfXChainClaimProofSig}; + STObject o = STObject::makeInnerObject(sfXChainClaimProofSig); o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount}; o[sfPublicKey] = publicKey; @@ -609,7 +611,7 @@ XChainCreateAccountAttestation::XChainCreateAccountAttestation( STObject XChainCreateAccountAttestation::toSTObject() const { - STObject o{sfXChainCreateAccountProofSig}; + STObject o = STObject::makeInnerObject(sfXChainCreateAccountProofSig); o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount}; diff --git a/src/ripple/protocol/impl/digest.cpp b/src/libxrpl/protocol/digest.cpp similarity index 98% rename from src/ripple/protocol/impl/digest.cpp rename to src/libxrpl/protocol/digest.cpp index 444a71fb863..237e7aa49f0 100644 --- a/src/ripple/protocol/impl/digest.cpp +++ b/src/libxrpl/protocol/digest.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include #include diff --git a/src/ripple/protocol/impl/tokens.cpp b/src/libxrpl/protocol/tokens.cpp similarity index 99% rename from src/ripple/protocol/impl/tokens.cpp rename to src/libxrpl/protocol/tokens.cpp index a166ac733cd..ccae1fb8ed2 100644 --- a/src/ripple/protocol/impl/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -25,11 +25,11 @@ * file COPYING or http://www.opensource.org/licenses/mit-license.php. */ -#include +#include -#include -#include -#include +#include +#include +#include #include #include @@ -91,7 +91,7 @@ algorithm that converts a number to coefficients from base B2. There is a useful shortcut that can be used if one of the bases is a power of the other base. If B1 == B2^G, then each coefficient from base B1 can be -converted to base B2 independently to create a a group of "G" B2 coefficient. +converted to base B2 independently to create a group of "G" B2 coefficient. These coefficients can be simply concatenated together. Since 16 == 2^4, this property is what makes base 16 useful when dealing with binary numbers. For example consider converting the base 16 number "93" to binary. The base 16 diff --git a/src/ripple/resource/impl/Charge.cpp b/src/libxrpl/resource/Charge.cpp similarity index 98% rename from src/ripple/resource/impl/Charge.cpp rename to src/libxrpl/resource/Charge.cpp index 21cf2af3ef6..deec6b34eb6 100644 --- a/src/ripple/resource/impl/Charge.cpp +++ b/src/libxrpl/resource/Charge.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/resource/impl/Consumer.cpp b/src/libxrpl/resource/Consumer.cpp similarity index 96% rename from src/ripple/resource/impl/Consumer.cpp rename to src/libxrpl/resource/Consumer.cpp index 34edbbfcc71..b8652546841 100644 --- a/src/ripple/resource/impl/Consumer.cpp +++ b/src/libxrpl/resource/Consumer.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/resource/impl/Fees.cpp b/src/libxrpl/resource/Fees.cpp similarity index 98% rename from src/ripple/resource/impl/Fees.cpp rename to src/libxrpl/resource/Fees.cpp index 0752e49ca48..13ae38bc6ad 100644 --- a/src/ripple/resource/impl/Fees.cpp +++ b/src/libxrpl/resource/Fees.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace Resource { diff --git a/src/ripple/resource/impl/ResourceManager.cpp b/src/libxrpl/resource/ResourceManager.cpp similarity index 92% rename from src/ripple/resource/impl/ResourceManager.cpp rename to src/libxrpl/resource/ResourceManager.cpp index 1a7e74ec1f8..d73ca090024 100644 --- a/src/ripple/resource/impl/ResourceManager.cpp +++ b/src/libxrpl/resource/ResourceManager.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -77,14 +77,13 @@ class ManagerImp : public Manager newInboundEndpoint( beast::IP::Endpoint const& address, bool const proxy, - boost::string_view const& forwardedFor) override + std::string_view forwardedFor) override { if (!proxy) return newInboundEndpoint(address); boost::system::error_code ec; - auto const proxiedIp = - boost::asio::ip::make_address(forwardedFor.to_string(), ec); + auto const proxiedIp = boost::asio::ip::make_address(forwardedFor, ec); if (ec) { journal_.warn() diff --git a/src/ripple/server/impl/JSONRPCUtil.cpp b/src/libxrpl/server/JSONRPCUtil.cpp similarity index 95% rename from src/ripple/server/impl/JSONRPCUtil.cpp rename to src/libxrpl/server/JSONRPCUtil.cpp index 12d12829ca9..ce173b1e9eb 100644 --- a/src/ripple/server/impl/JSONRPCUtil.cpp +++ b/src/libxrpl/server/JSONRPCUtil.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/server/impl/Port.cpp b/src/libxrpl/server/Port.cpp similarity index 98% rename from src/ripple/server/impl/Port.cpp rename to src/libxrpl/server/Port.cpp index 1b869f6a5da..0554c8082a0 100644 --- a/src/ripple/server/impl/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/rdb/Download.h b/src/ripple/app/rdb/Download.h deleted file mode 100644 index a16c9aa8788..00000000000 --- a/src/ripple/app/rdb/Download.h +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_RDB_DOWNLOAD_H_INCLUDED -#define RIPPLE_APP_RDB_DOWNLOAD_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -/** - * @brief openDatabaseBodyDb Opens a database that will store the contents of a - * file being downloaded, returns its descriptor, and starts a new - * download process or continues an existing one. - * @param setup Path to the database and other opening parameters. - * @param path Path of the new file to download. - * @param j Journal. - * @return Pair containing a unique pointer to the database and the amount of - * bytes already downloaded if a download is being continued. - */ -std::pair, std::optional> -openDatabaseBodyDb( - DatabaseCon::Setup const& setup, - boost::filesystem::path const& path, - beast::Journal j); - -/** - * @brief databaseBodyDoPut Saves a new fragment of a downloaded file. - * @param session Session with the database. - * @param data Downloaded fragment of file data to save. - * @param path Path to the file currently being downloaded. - * @param fileSize Size of the portion of the file already downloaded. - * @param part The index of the most recently updated database row. - * @param maxRowSizePad A constant padding value that accounts for other data - * stored in each row of the database. - * @return Index of the most recently updated database row. - */ -std::uint64_t -databaseBodyDoPut( - soci::session& session, - std::string const& data, - std::string const& path, - std::uint64_t fileSize, - std::uint64_t part, - std::uint16_t maxRowSizePad); - -/** - * @brief databaseBodyFinish Finishes the download process and writes the file - * to disk. - * @param session Session with the database. - * @param fout Opened file into which the downloaded data from the database will - * be written. - */ -void -databaseBodyFinish(soci::session& session, std::ofstream& fout); - -} // namespace ripple - -#endif diff --git a/src/ripple/app/rdb/ShardArchive.h b/src/ripple/app/rdb/ShardArchive.h deleted file mode 100644 index 27db8279718..00000000000 --- a/src/ripple/app/rdb/ShardArchive.h +++ /dev/null @@ -1,82 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_RDB_SHARDARCHIVE_H_INCLUDED -#define RIPPLE_APP_RDB_SHARDARCHIVE_H_INCLUDED - -#include -#include - -namespace ripple { - -/** - * @brief makeArchiveDB Opens the shard archive database and returns its - * descriptor. - * @param dir Path to the database to open. - * @param dbName Name of the database. - * @param j Journal. - * @return Unique pointer to the opened database. - */ -std::unique_ptr -makeArchiveDB( - boost::filesystem::path const& dir, - std::string const& dbName, - beast::Journal j); - -/** - * @brief readArchiveDB Reads entries from the shard archive database and - * invokes the given callback for each entry. - * @param db Session with the database. - * @param func Callback to invoke for each entry. - */ -void -readArchiveDB( - DatabaseCon& db, - std::function const& func); - -/** - * @brief insertArchiveDB Adds an entry to the shard archive database. - * @param db Session with the database. - * @param shardIndex Shard index to add. - * @param url Shard download url to add. - */ -void -insertArchiveDB( - DatabaseCon& db, - std::uint32_t shardIndex, - std::string const& url); - -/** - * @brief deleteFromArchiveDB Deletes an entry from the shard archive database. - * @param db Session with the database. - * @param shardIndex Shard index to remove from the database. - */ -void -deleteFromArchiveDB(DatabaseCon& db, std::uint32_t shardIndex); - -/** - * @brief dropArchiveDB Removes a table in the shard archive database. - * @param db Session with the database. - */ -void -dropArchiveDB(DatabaseCon& db); - -} // namespace ripple - -#endif diff --git a/src/ripple/app/rdb/UnitaryShard.h b/src/ripple/app/rdb/UnitaryShard.h deleted file mode 100644 index e9a2ac93d2d..00000000000 --- a/src/ripple/app/rdb/UnitaryShard.h +++ /dev/null @@ -1,161 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_RDB_UNITARYSHARD_H_INCLUDED -#define RIPPLE_APP_RDB_UNITARYSHARD_H_INCLUDED - -#include -#include -#include -#include -#include - -namespace ripple { - -struct DatabasePair -{ - std::unique_ptr ledgerDb; - std::unique_ptr transactionDb; -}; - -/** - * @brief makeShardCompleteLedgerDBs Opens shard databases for verified shards - * and returns their descriptors. - * @param config Config object. - * @param setup Path to the databases and other opening parameters. - * @param j Journal. - * @return Pair of unique pointers to the opened ledger and transaction - * databases. - */ -DatabasePair -makeShardCompleteLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - beast::Journal j); - -/** - * @brief makeShardIncompleteLedgerDBs Opens shard databases for partially - * downloaded or unverified shards and returns their descriptors. - * @param config Config object. - * @param setup Path to the databases and other opening parameters. - * @param checkpointerSetup Checkpointer parameters. - * @param j Journal. - * @return Pair of unique pointers to the opened ledger and transaction - * databases. - */ -DatabasePair -makeShardIncompleteLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j); - -/** - * @brief updateLedgerDBs Saves the given ledger to shard databases. - * @param txdb Session with the transaction databases. - * @param lgrdb Session with the ledger databases. - * @param ledger Ledger to save. - * @param index Index of the shard that owns the ledger. - * @param stop Reference to an atomic flag that can stop the process if raised. - * @param j Journal - * @return True if the ledger was successfully saved. - */ -bool -updateLedgerDBs( - soci::session& txdb, - soci::session& lgrdb, - std::shared_ptr const& ledger, - std::uint32_t index, - std::atomic& stop, - beast::Journal j); - -/** - * @brief makeAcquireDB Opens the shard acquire database and returns its - * descriptor. - * @param setup Path to the database and other opening parameters. - * @param checkpointerSetup Checkpointer parameters. - * @param j Journal. - * @return Unique pointer to the opened database. - */ -std::unique_ptr -makeAcquireDB( - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j); - -/** - * @brief insertAcquireDBIndex Adds a new shard index to the shard acquire - * database. - * @param session Session with the database. - * @param index Index to add. - */ -void -insertAcquireDBIndex(soci::session& session, std::uint32_t index); - -/** - * @brief selectAcquireDBLedgerSeqs Returns the set of acquired ledgers for - * the given shard. - * @param session Session with the database. - * @param index Shard index. - * @return Pair which contains true if such an index was found in the database, - * and a string which contains the set of ledger sequences. - * If no sequences were saved then the optional will have no value. - */ -std::pair> -selectAcquireDBLedgerSeqs(soci::session& session, std::uint32_t index); - -struct AcquireShardSeqsHash -{ - std::optional sequences; - std::optional hash; -}; - -/** - * @brief selectAcquireDBLedgerSeqsHash Returns the set of acquired ledger - * sequences and the last ledger hash for the shard with the provided - * index. - * @param session Session with the database. - * @param index Shard index. - * @return Pair which contains true if such an index was found in the database - * and the AcquireShardSeqsHash structure which contains a string with - * the ledger sequences and a string with last ledger hash. If the set - * of sequences or hash were not saved then no value is returned. - */ -std::pair -selectAcquireDBLedgerSeqsHash(soci::session& session, std::uint32_t index); - -/** - * @brief updateAcquireDB Updates information in the acquire DB. - * @param session Session with the database. - * @param ledger Ledger to save into the database. - * @param index Shard index. - * @param lastSeq Last acquired ledger sequence. - * @param seqs Current set of acquired ledger sequences if it's not empty. - */ -void -updateAcquireDB( - soci::session& session, - std::shared_ptr const& ledger, - std::uint32_t index, - std::uint32_t lastSeq, - std::optional const& seqs); - -} // namespace ripple - -#endif diff --git a/src/ripple/app/rdb/backend/PostgresDatabase.h b/src/ripple/app/rdb/backend/PostgresDatabase.h deleted file mode 100644 index e8673611279..00000000000 --- a/src/ripple/app/rdb/backend/PostgresDatabase.h +++ /dev/null @@ -1,113 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_RDB_BACKEND_POSTGRESDATABASE_H_INCLUDED -#define RIPPLE_APP_RDB_BACKEND_POSTGRESDATABASE_H_INCLUDED - -#include - -namespace ripple { - -class PostgresDatabase : public RelationalDatabase -{ -public: - virtual void - stop() = 0; - - /** - * @brief sweep Sweeps the database. - */ - virtual void - sweep() = 0; - - /** - * @brief getCompleteLedgers Returns a string which contains a list of - * completed ledgers. - * @return String with completed ledger sequences - */ - virtual std::string - getCompleteLedgers() = 0; - - /** - * @brief getValidatedLedgerAge Returns the age of the last validated - * ledger. - * @return Age of the last validated ledger in seconds - */ - virtual std::chrono::seconds - getValidatedLedgerAge() = 0; - - /** - * @brief writeLedgerAndTransactions Writes new ledger and transaction data - * into the database. - * @param info Ledger info to write. - * @param accountTxData Transaction data to write - * @return True on success, false on failure. - */ - virtual bool - writeLedgerAndTransactions( - LedgerInfo const& info, - std::vector const& accountTxData) = 0; - - /** - * @brief getTxHashes Returns a vector of the hashes of transactions - * belonging to the ledger with the provided sequence. - * @param seq Ledger sequence - * @return Vector of transaction hashes - */ - virtual std::vector - getTxHashes(LedgerIndex seq) = 0; - - /** - * @brief getAccountTx Get the last account transactions specified by the - * AccountTxArgs struct. - * @param args Arguments which specify the account and which transactions to - * return. - * @return Vector of account transactions and the RPC status response. - */ - virtual std::pair - getAccountTx(AccountTxArgs const& args) = 0; - - /** - * @brief locateTransaction Returns information used to locate - * a transaction. - * @param id Hash of the transaction. - * @return Information used to locate a transaction. Contains a nodestore - * hash and a ledger sequence pair if the transaction was found. - * Otherwise, contains the range of ledgers present in the database - * at the time of search. - */ - virtual Transaction::Locator - locateTransaction(uint256 const& id) = 0; - - /** - * @brief isCaughtUp returns whether the database is caught up with the - * network - * @param[out] reason if the database is not caught up, reason contains a - * helpful message describing why - * @return false if the most recently written ledger has a close time - * over 3 minutes ago, or if there are no ledgers in the - * database. true otherwise - */ - virtual bool - isCaughtUp(std::string& reason) = 0; -}; - -} // namespace ripple - -#endif diff --git a/src/ripple/app/rdb/backend/detail/Shard.h b/src/ripple/app/rdb/backend/detail/Shard.h deleted file mode 100644 index 02e75d2aa64..00000000000 --- a/src/ripple/app/rdb/backend/detail/Shard.h +++ /dev/null @@ -1,92 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_RDB_BACKEND_DETAIL_SHARD_H_INCLUDED -#define RIPPLE_APP_RDB_BACKEND_DETAIL_SHARD_H_INCLUDED - -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace detail { - -/** - * @brief makeMetaDBs Opens ledger and transaction 'meta' databases which - * map ledger hashes and transaction IDs to the index of the shard - * that holds the ledger or transaction. - * @param config Config object. - * @param setup Path to database and opening parameters. - * @param checkpointerSetup Database checkpointer setup. - * @param j Journal. - * @return Struct DatabasePair which contains unique pointers to the ledger - * and transaction databases. - */ -DatabasePair -makeMetaDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j); - -/** - * @brief saveLedgerMeta Stores (transaction ID -> shard index) and - * (ledger hash -> shard index) mappings in the meta databases. - * @param ledger The ledger. - * @param app Application object. - * @param lgrMetaSession Session to ledger meta database. - * @param txnMetaSession Session to transaction meta database. - * @param shardIndex The index of the shard that contains this ledger. - * @return True on success. - */ -bool -saveLedgerMeta( - std::shared_ptr const& ledger, - Application& app, - soci::session& lgrMetaSession, - soci::session& txnMetaSession, - std::uint32_t shardIndex); - -/** - * @brief getShardIndexforLedger Queries the ledger meta database to - * retrieve the index of the shard that contains this ledger. - * @param session Session to the database. - * @param hash Hash of the ledger. - * @return The index of the shard on success, otherwise an unseated value. - */ -std::optional -getShardIndexforLedger(soci::session& session, LedgerHash const& hash); - -/** - * @brief getShardIndexforTransaction Queries the transaction meta database to - * retrieve the index of the shard that contains this transaction. - * @param session Session to the database. - * @param id ID of the transaction. - * @return The index of the shard on success, otherwise an unseated value. - */ -std::optional -getShardIndexforTransaction(soci::session& session, TxID const& id); - -} // namespace detail -} // namespace ripple - -#endif diff --git a/src/ripple/app/rdb/backend/detail/impl/Shard.cpp b/src/ripple/app/rdb/backend/detail/impl/Shard.cpp deleted file mode 100644 index 540e8d1d221..00000000000 --- a/src/ripple/app/rdb/backend/detail/impl/Shard.cpp +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace detail { - -DatabasePair -makeMetaDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j) -{ - // ledger meta database - auto lgrMetaDB{std::make_unique( - setup, - LgrMetaDBName, - LgrMetaDBPragma, - LgrMetaDBInit, - checkpointerSetup, - j)}; - - if (!config.useTxTables()) - return {std::move(lgrMetaDB), nullptr}; - - // transaction meta database - auto txMetaDB{std::make_unique( - setup, - TxMetaDBName, - TxMetaDBPragma, - TxMetaDBInit, - checkpointerSetup, - j)}; - - return {std::move(lgrMetaDB), std::move(txMetaDB)}; -} - -bool -saveLedgerMeta( - std::shared_ptr const& ledger, - Application& app, - soci::session& lgrMetaSession, - soci::session& txnMetaSession, - std::uint32_t const shardIndex) -{ - std::string_view constexpr lgrSQL = - R"sql(INSERT OR REPLACE INTO LedgerMeta VALUES - (:ledgerHash,:shardIndex);)sql"; - - auto const hash = to_string(ledger->info().hash); - lgrMetaSession << lgrSQL, soci::use(hash), soci::use(shardIndex); - - if (!app.config().useTxTables()) - return true; - - auto const aLedger = [&app, ledger]() -> std::shared_ptr { - try - { - auto aLedger = - app.getAcceptedLedgerCache().fetch(ledger->info().hash); - if (!aLedger) - { - aLedger = std::make_shared(ledger, app); - app.getAcceptedLedgerCache().canonicalize_replace_client( - ledger->info().hash, aLedger); - } - - return aLedger; - } - catch (std::exception const&) - { - JLOG(app.journal("Ledger").warn()) - << "An accepted ledger was missing nodes"; - } - - return {}; - }(); - - if (!aLedger) - return false; - - soci::transaction tr(txnMetaSession); - - for (auto const& acceptedLedgerTx : *aLedger) - { - std::string_view constexpr txnSQL = - R"sql(INSERT OR REPLACE INTO TransactionMeta VALUES - (:transactionID,:shardIndex);)sql"; - - auto const transactionID = - to_string(acceptedLedgerTx->getTransactionID()); - - txnMetaSession << txnSQL, soci::use(transactionID), - soci::use(shardIndex); - } - - tr.commit(); - return true; -} - -std::optional -getShardIndexforLedger(soci::session& session, LedgerHash const& hash) -{ - std::uint32_t shardIndex; - session << "SELECT ShardIndex FROM LedgerMeta WHERE LedgerHash = '" << hash - << "';", - soci::into(shardIndex); - - if (!session.got_data()) - return std::nullopt; - - return shardIndex; -} - -std::optional -getShardIndexforTransaction(soci::session& session, TxID const& id) -{ - std::uint32_t shardIndex; - session << "SELECT ShardIndex FROM TransactionMeta WHERE TransID = '" << id - << "';", - soci::into(shardIndex); - - if (!session.got_data()) - return std::nullopt; - - return shardIndex; -} - -} // namespace detail -} // namespace ripple diff --git a/src/ripple/app/rdb/backend/impl/PostgresDatabase.cpp b/src/ripple/app/rdb/backend/impl/PostgresDatabase.cpp deleted file mode 100644 index c57dee30610..00000000000 --- a/src/ripple/app/rdb/backend/impl/PostgresDatabase.cpp +++ /dev/null @@ -1,1073 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class PgPool; - -using AccountTxResult = RelationalDatabase::AccountTxResult; -using TxnsData = RelationalDatabase::AccountTxs; -using TxnsDataBinary = RelationalDatabase::MetaTxsList; - -class PostgresDatabaseImp final : public PostgresDatabase -{ -public: - PostgresDatabaseImp( - Application& app, - Config const& config, - JobQueue& jobQueue) - : app_(app) - , j_(app_.journal("PgPool")) - , pgPool_( -#ifdef RIPPLED_REPORTING - make_PgPool(config.section("ledger_tx_tables"), j_) -#endif - ) - { - assert(config.reporting()); -#ifdef RIPPLED_REPORTING - if (config.reporting() && !config.reportingReadOnly()) // use pg - { - initSchema(pgPool_); - } -#endif - } - - void - stop() override - { -#ifdef RIPPLED_REPORTING - pgPool_->stop(); -#endif - } - - void - sweep() override; - - std::optional - getMinLedgerSeq() override; - - std::optional - getMaxLedgerSeq() override; - - std::string - getCompleteLedgers() override; - - std::chrono::seconds - getValidatedLedgerAge() override; - - bool - writeLedgerAndTransactions( - LedgerInfo const& info, - std::vector const& accountTxData) override; - - std::optional - getLedgerInfoByIndex(LedgerIndex ledgerSeq) override; - - std::optional - getNewestLedgerInfo() override; - - std::optional - getLedgerInfoByHash(uint256 const& ledgerHash) override; - - uint256 - getHashByIndex(LedgerIndex ledgerIndex) override; - - std::optional - getHashesByIndex(LedgerIndex ledgerIndex) override; - - std::map - getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) override; - - std::vector - getTxHashes(LedgerIndex seq) override; - - std::vector> - getTxHistory(LedgerIndex startIndex) override; - - std::pair - getAccountTx(AccountTxArgs const& args) override; - - Transaction::Locator - locateTransaction(uint256 const& id) override; - - bool - ledgerDbHasSpace(Config const& config) override; - - bool - transactionDbHasSpace(Config const& config) override; - - bool - isCaughtUp(std::string& reason) override; - -private: - Application& app_; - beast::Journal j_; - std::shared_ptr pgPool_; - - bool - dbHasSpace(Config const& config); -}; - -/** - * @brief loadLedgerInfos Loads the ledger info for the specified - * ledger/s from the database - * @param pgPool Link to postgres database - * @param whichLedger Specifies the ledger to load via ledger sequence, - * ledger hash, a range of ledgers, or std::monostate - * (which loads the most recent) - * @param app Application - * @return Vector of LedgerInfos - */ -static std::vector -loadLedgerInfos( - std::shared_ptr const& pgPool, - std::variant< - std::monostate, - uint256, - uint32_t, - std::pair> const& whichLedger, - Application& app) -{ - std::vector infos; -#ifdef RIPPLED_REPORTING - auto log = app.journal("Ledger"); - assert(app.config().reporting()); - std::stringstream sql; - sql << "SELECT ledger_hash, prev_hash, account_set_hash, trans_set_hash, " - "total_coins, closing_time, prev_closing_time, close_time_res, " - "close_flags, ledger_seq FROM ledgers "; - - if (auto ledgerSeq = std::get_if(&whichLedger)) - { - sql << "WHERE ledger_seq = " + std::to_string(*ledgerSeq); - } - else if (auto ledgerHash = std::get_if(&whichLedger)) - { - sql << ("WHERE ledger_hash = \'\\x" + strHex(*ledgerHash) + "\'"); - } - else if ( - auto minAndMax = - std::get_if>(&whichLedger)) - { - sql - << ("WHERE ledger_seq >= " + std::to_string(minAndMax->first) + - " AND ledger_seq <= " + std::to_string(minAndMax->second)); - } - else - { - sql << ("ORDER BY ledger_seq desc LIMIT 1"); - } - sql << ";"; - - JLOG(log.trace()) << __func__ << " : sql = " << sql.str(); - - auto res = PgQuery(pgPool)(sql.str().data()); - if (!res) - { - JLOG(log.error()) << __func__ << " : Postgres response is null - sql = " - << sql.str(); - assert(false); - return {}; - } - else if (res.status() != PGRES_TUPLES_OK) - { - JLOG(log.error()) << __func__ - << " : Postgres response should have been " - "PGRES_TUPLES_OK but instead was " - << res.status() << " - msg = " << res.msg() - << " - sql = " << sql.str(); - assert(false); - return {}; - } - - JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg(); - - if (res.isNull() || res.ntuples() == 0) - { - JLOG(log.debug()) << __func__ - << " : Ledger not found. sql = " << sql.str(); - return {}; - } - else if (res.ntuples() > 0) - { - if (res.nfields() != 10) - { - JLOG(log.error()) << __func__ - << " : Wrong number of fields in Postgres " - "response. Expected 10, but got " - << res.nfields() << " . sql = " << sql.str(); - assert(false); - return {}; - } - } - - for (size_t i = 0; i < res.ntuples(); ++i) - { - char const* hash = res.c_str(i, 0); - char const* prevHash = res.c_str(i, 1); - char const* accountHash = res.c_str(i, 2); - char const* txHash = res.c_str(i, 3); - std::int64_t totalCoins = res.asBigInt(i, 4); - std::int64_t closeTime = res.asBigInt(i, 5); - std::int64_t parentCloseTime = res.asBigInt(i, 6); - std::int64_t closeTimeRes = res.asBigInt(i, 7); - std::int64_t closeFlags = res.asBigInt(i, 8); - std::int64_t ledgerSeq = res.asBigInt(i, 9); - - JLOG(log.trace()) << __func__ << " - Postgres response = " << hash - << " , " << prevHash << " , " << accountHash << " , " - << txHash << " , " << totalCoins << ", " << closeTime - << ", " << parentCloseTime << ", " << closeTimeRes - << ", " << closeFlags << ", " << ledgerSeq - << " - sql = " << sql.str(); - JLOG(log.debug()) << __func__ - << " - Successfully fetched ledger with sequence = " - << ledgerSeq << " from Postgres"; - - using time_point = NetClock::time_point; - using duration = NetClock::duration; - - LedgerInfo info; - if (!info.parentHash.parseHex(prevHash + 2)) - assert(false); - if (!info.txHash.parseHex(txHash + 2)) - assert(false); - if (!info.accountHash.parseHex(accountHash + 2)) - assert(false); - info.drops = totalCoins; - info.closeTime = time_point{duration{closeTime}}; - info.parentCloseTime = time_point{duration{parentCloseTime}}; - info.closeFlags = closeFlags; - info.closeTimeResolution = duration{closeTimeRes}; - info.seq = ledgerSeq; - if (!info.hash.parseHex(hash + 2)) - assert(false); - info.validated = true; - infos.push_back(info); - } - -#endif - return infos; -} - -/** - * @brief loadLedgerHelper Load a ledger info from Postgres - * @param pgPool Link to postgres database - * @param whichLedger Specifies sequence or hash of ledger. Passing - * std::monostate loads the most recent ledger - * @param app The Application - * @return Ledger info - */ -static std::optional -loadLedgerHelper( - std::shared_ptr const& pgPool, - std::variant const& whichLedger, - Application& app) -{ - std::vector infos; - std::visit( - [&infos, &app, &pgPool](auto&& arg) { - infos = loadLedgerInfos(pgPool, arg, app); - }, - whichLedger); - assert(infos.size() <= 1); - if (!infos.size()) - return {}; - return infos[0]; -} - -#ifdef RIPPLED_REPORTING -static bool -writeToLedgersDB(LedgerInfo const& info, PgQuery& pgQuery, beast::Journal& j) -{ - JLOG(j.debug()) << __func__; - auto cmd = boost::format( - R"(INSERT INTO ledgers - VALUES (%u,'\x%s', '\x%s',%u,%u,%u,%u,%u,'\x%s','\x%s'))"); - - auto ledgerInsert = boost::str( - cmd % info.seq % strHex(info.hash) % strHex(info.parentHash) % - info.drops.drops() % info.closeTime.time_since_epoch().count() % - info.parentCloseTime.time_since_epoch().count() % - info.closeTimeResolution.count() % info.closeFlags % - strHex(info.accountHash) % strHex(info.txHash)); - JLOG(j.trace()) << __func__ << " : " - << " : " - << "query string = " << ledgerInsert; - - auto res = pgQuery(ledgerInsert.data()); - - return res; -} - -enum class DataFormat { binary, expanded }; -static std::variant -flatFetchTransactions( - Application& app, - std::vector& nodestoreHashes, - std::vector& ledgerSequences, - DataFormat format) -{ - std::variant ret; - if (format == DataFormat::binary) - ret = TxnsDataBinary(); - else - ret = TxnsData(); - - std::vector< - std::pair, std::shared_ptr>> - txns = flatFetchTransactions(app, nodestoreHashes); - for (size_t i = 0; i < txns.size(); ++i) - { - auto& [txn, meta] = txns[i]; - if (format == DataFormat::binary) - { - auto& transactions = std::get(ret); - Serializer txnSer = txn->getSerializer(); - Serializer metaSer = meta->getSerializer(); - // SerialIter it(item->slice()); - Blob txnBlob = txnSer.getData(); - Blob metaBlob = metaSer.getData(); - transactions.push_back( - std::make_tuple(txnBlob, metaBlob, ledgerSequences[i])); - } - else - { - auto& transactions = std::get(ret); - std::string reason; - auto txnRet = std::make_shared(txn, reason, app); - txnRet->setLedger(ledgerSequences[i]); - txnRet->setStatus(COMMITTED); - auto txMeta = std::make_shared( - txnRet->getID(), ledgerSequences[i], *meta); - transactions.push_back(std::make_pair(txnRet, txMeta)); - } - } - return ret; -} - -static std::pair -processAccountTxStoredProcedureResult( - RelationalDatabase::AccountTxArgs const& args, - Json::Value& result, - Application& app, - beast::Journal j) -{ - AccountTxResult ret; - ret.limit = args.limit; - - try - { - if (result.isMember("transactions")) - { - std::vector nodestoreHashes; - std::vector ledgerSequences; - for (auto& t : result["transactions"]) - { - if (t.isMember("ledger_seq") && t.isMember("nodestore_hash")) - { - uint32_t ledgerSequence = t["ledger_seq"].asUInt(); - std::string nodestoreHashHex = - t["nodestore_hash"].asString(); - nodestoreHashHex.erase(0, 2); - uint256 nodestoreHash; - if (!nodestoreHash.parseHex(nodestoreHashHex)) - assert(false); - - if (nodestoreHash.isNonZero()) - { - ledgerSequences.push_back(ledgerSequence); - nodestoreHashes.push_back(nodestoreHash); - } - else - { - assert(false); - return {ret, {rpcINTERNAL, "nodestoreHash is zero"}}; - } - } - else - { - assert(false); - return {ret, {rpcINTERNAL, "missing postgres fields"}}; - } - } - - assert(nodestoreHashes.size() == ledgerSequences.size()); - ret.transactions = flatFetchTransactions( - app, - nodestoreHashes, - ledgerSequences, - args.binary ? DataFormat::binary : DataFormat::expanded); - - JLOG(j.trace()) << __func__ << " : processed db results"; - - if (result.isMember("marker")) - { - auto& marker = result["marker"]; - assert(marker.isMember("ledger")); - assert(marker.isMember("seq")); - ret.marker = { - marker["ledger"].asUInt(), marker["seq"].asUInt()}; - } - assert(result.isMember("ledger_index_min")); - assert(result.isMember("ledger_index_max")); - ret.ledgerRange = { - result["ledger_index_min"].asUInt(), - result["ledger_index_max"].asUInt()}; - return {ret, rpcSUCCESS}; - } - else if (result.isMember("error")) - { - JLOG(j.debug()) - << __func__ << " : error = " << result["error"].asString(); - return { - ret, - RPC::Status{rpcINVALID_PARAMS, result["error"].asString()}}; - } - else - { - return {ret, {rpcINTERNAL, "unexpected Postgres response"}}; - } - } - catch (std::exception& e) - { - JLOG(j.debug()) << __func__ << " : " - << "Caught exception : " << e.what(); - return {ret, {rpcINTERNAL, e.what()}}; - } -} -#endif - -void -PostgresDatabaseImp::sweep() -{ -#ifdef RIPPLED_REPORTING - pgPool_->idleSweeper(); -#endif -} - -std::optional -PostgresDatabaseImp::getMinLedgerSeq() -{ -#ifdef RIPPLED_REPORTING - auto seq = PgQuery(pgPool_)("SELECT min_ledger()"); - if (!seq) - { - JLOG(j_.error()) << "Error querying minimum ledger sequence."; - } - else if (!seq.isNull()) - return seq.asInt(); -#endif - return {}; -} - -std::optional -PostgresDatabaseImp::getMaxLedgerSeq() -{ -#ifdef RIPPLED_REPORTING - auto seq = PgQuery(pgPool_)("SELECT max_ledger()"); - if (seq && !seq.isNull()) - return seq.asBigInt(); -#endif - return {}; -} - -std::string -PostgresDatabaseImp::getCompleteLedgers() -{ -#ifdef RIPPLED_REPORTING - auto range = PgQuery(pgPool_)("SELECT complete_ledgers()"); - if (range) - return range.c_str(); -#endif - return "error"; -} - -std::chrono::seconds -PostgresDatabaseImp::getValidatedLedgerAge() -{ - using namespace std::chrono_literals; -#ifdef RIPPLED_REPORTING - auto age = PgQuery(pgPool_)("SELECT age()"); - if (!age || age.isNull()) - JLOG(j_.debug()) << "No ledgers in database"; - else - return std::chrono::seconds{age.asInt()}; -#endif - return weeks{2}; -} - -bool -PostgresDatabaseImp::writeLedgerAndTransactions( - LedgerInfo const& info, - std::vector const& accountTxData) -{ -#ifdef RIPPLED_REPORTING - JLOG(j_.debug()) << __func__ << " : " - << "Beginning write to Postgres"; - - try - { - // Create a PgQuery object to run multiple commands over the same - // connection in a single transaction block. - PgQuery pg(pgPool_); - auto res = pg("BEGIN"); - if (!res || res.status() != PGRES_COMMAND_OK) - { - std::stringstream msg; - msg << "bulkWriteToTable : Postgres insert error: " << res.msg(); - Throw(msg.str()); - } - - // Writing to the ledgers db fails if the ledger already exists in the - // db. In this situation, the ETL process has detected there is another - // writer, and falls back to only publishing - if (!writeToLedgersDB(info, pg, j_)) - { - JLOG(j_.warn()) << __func__ << " : " - << "Failed to write to ledgers database."; - return false; - } - - std::stringstream transactionsCopyBuffer; - std::stringstream accountTransactionsCopyBuffer; - for (auto const& data : accountTxData) - { - std::string txHash = strHex(data.txHash); - std::string nodestoreHash = strHex(data.nodestoreHash); - auto idx = data.transactionIndex; - auto ledgerSeq = data.ledgerSequence; - - transactionsCopyBuffer << std::to_string(ledgerSeq) << '\t' - << std::to_string(idx) << '\t' << "\\\\x" - << txHash << '\t' << "\\\\x" << nodestoreHash - << '\n'; - - for (auto const& a : data.accounts) - { - std::string acct = strHex(a); - accountTransactionsCopyBuffer - << "\\\\x" << acct << '\t' << std::to_string(ledgerSeq) - << '\t' << std::to_string(idx) << '\n'; - } - } - - pg.bulkInsert("transactions", transactionsCopyBuffer.str()); - pg.bulkInsert( - "account_transactions", accountTransactionsCopyBuffer.str()); - - res = pg("COMMIT"); - if (!res || res.status() != PGRES_COMMAND_OK) - { - std::stringstream msg; - msg << "bulkWriteToTable : Postgres insert error: " << res.msg(); - assert(false); - Throw(msg.str()); - } - - JLOG(j_.info()) << __func__ << " : " - << "Successfully wrote to Postgres"; - return true; - } - catch (std::exception& e) - { - JLOG(j_.error()) << __func__ - << "Caught exception writing to Postgres : " - << e.what(); - assert(false); - return false; - } -#else - return false; -#endif -} - -std::optional -PostgresDatabaseImp::getLedgerInfoByIndex(LedgerIndex ledgerSeq) -{ - return loadLedgerHelper(pgPool_, ledgerSeq, app_); -} - -std::optional -PostgresDatabaseImp::getNewestLedgerInfo() -{ - return loadLedgerHelper(pgPool_, {}, app_); -} - -std::optional -PostgresDatabaseImp::getLedgerInfoByHash(uint256 const& ledgerHash) -{ - return loadLedgerHelper(pgPool_, ledgerHash, app_); -} - -uint256 -PostgresDatabaseImp::getHashByIndex(LedgerIndex ledgerIndex) -{ - auto infos = loadLedgerInfos(pgPool_, ledgerIndex, app_); - assert(infos.size() <= 1); - if (infos.size()) - return infos[0].hash; - return {}; -} - -std::optional -PostgresDatabaseImp::getHashesByIndex(LedgerIndex ledgerIndex) -{ - LedgerHashPair p; - auto infos = loadLedgerInfos(pgPool_, ledgerIndex, app_); - assert(infos.size() <= 1); - if (infos.size()) - { - p.ledgerHash = infos[0].hash; - p.parentHash = infos[0].parentHash; - return p; - } - return {}; -} - -std::map -PostgresDatabaseImp::getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) -{ - std::map ret; - auto infos = loadLedgerInfos(pgPool_, std::make_pair(minSeq, maxSeq), app_); - for (auto& info : infos) - { - ret[info.seq] = {info.hash, info.parentHash}; - } - return ret; -} - -std::vector -PostgresDatabaseImp::getTxHashes(LedgerIndex seq) -{ - std::vector nodestoreHashes; - -#ifdef RIPPLED_REPORTING - auto log = app_.journal("Ledger"); - - std::string query = - "SELECT nodestore_hash" - " FROM transactions " - " WHERE ledger_seq = " + - std::to_string(seq); - auto res = PgQuery(pgPool_)(query.c_str()); - - if (!res) - { - JLOG(log.error()) << __func__ - << " : Postgres response is null - query = " << query; - assert(false); - return {}; - } - else if (res.status() != PGRES_TUPLES_OK) - { - JLOG(log.error()) << __func__ - << " : Postgres response should have been " - "PGRES_TUPLES_OK but instead was " - << res.status() << " - msg = " << res.msg() - << " - query = " << query; - assert(false); - return {}; - } - - JLOG(log.trace()) << __func__ << " Postgres result msg : " << res.msg(); - - if (res.isNull() || res.ntuples() == 0) - { - JLOG(log.debug()) << __func__ - << " : Ledger not found. query = " << query; - return {}; - } - else if (res.ntuples() > 0) - { - if (res.nfields() != 1) - { - JLOG(log.error()) << __func__ - << " : Wrong number of fields in Postgres " - "response. Expected 1, but got " - << res.nfields() << " . query = " << query; - assert(false); - return {}; - } - } - - JLOG(log.trace()) << __func__ << " : result = " << res.c_str() - << " : query = " << query; - for (size_t i = 0; i < res.ntuples(); ++i) - { - char const* nodestoreHash = res.c_str(i, 0); - uint256 hash; - if (!hash.parseHex(nodestoreHash + 2)) - assert(false); - - nodestoreHashes.push_back(hash); - } -#endif - - return nodestoreHashes; -} - -std::vector> -PostgresDatabaseImp::getTxHistory(LedgerIndex startIndex) -{ - std::vector> ret; - -#ifdef RIPPLED_REPORTING - if (!app_.config().reporting()) - { - assert(false); - Throw( - "called getTxHistory but not in reporting mode"); - } - - std::string sql = boost::str( - boost::format("SELECT nodestore_hash, ledger_seq " - " FROM transactions" - " ORDER BY ledger_seq DESC LIMIT 20 " - "OFFSET %u;") % - startIndex); - - auto res = PgQuery(pgPool_)(sql.data()); - - if (!res) - { - JLOG(j_.error()) << __func__ - << " : Postgres response is null - sql = " << sql; - assert(false); - return {}; - } - else if (res.status() != PGRES_TUPLES_OK) - { - JLOG(j_.error()) << __func__ - << " : Postgres response should have been " - "PGRES_TUPLES_OK but instead was " - << res.status() << " - msg = " << res.msg() - << " - sql = " << sql; - assert(false); - return {}; - } - - JLOG(j_.trace()) << __func__ << " Postgres result msg : " << res.msg(); - - if (res.isNull() || res.ntuples() == 0) - { - JLOG(j_.debug()) << __func__ << " : Empty postgres response"; - assert(false); - return {}; - } - else if (res.ntuples() > 0) - { - if (res.nfields() != 2) - { - JLOG(j_.error()) << __func__ - << " : Wrong number of fields in Postgres " - "response. Expected 1, but got " - << res.nfields() << " . sql = " << sql; - assert(false); - return {}; - } - } - - JLOG(j_.trace()) << __func__ << " : Postgres result = " << res.c_str(); - - std::vector nodestoreHashes; - std::vector ledgerSequences; - for (size_t i = 0; i < res.ntuples(); ++i) - { - uint256 hash; - if (!hash.parseHex(res.c_str(i, 0) + 2)) - assert(false); - nodestoreHashes.push_back(hash); - ledgerSequences.push_back(res.asBigInt(i, 1)); - } - - auto txns = flatFetchTransactions(app_, nodestoreHashes); - for (size_t i = 0; i < txns.size(); ++i) - { - auto const& [sttx, meta] = txns[i]; - assert(sttx); - - std::string reason; - auto txn = std::make_shared(sttx, reason, app_); - txn->setLedger(ledgerSequences[i]); - txn->setStatus(COMMITTED); - ret.push_back(txn); - } - -#endif - return ret; -} - -std::pair -PostgresDatabaseImp::getAccountTx(AccountTxArgs const& args) -{ -#ifdef RIPPLED_REPORTING - pg_params dbParams; - - char const*& command = dbParams.first; - std::vector>& values = dbParams.second; - command = - "SELECT account_tx($1::bytea, $2::bool, " - "$3::bigint, $4::bigint, $5::bigint, $6::bytea, " - "$7::bigint, $8::bool, $9::bigint, $10::bigint)"; - values.resize(10); - values[0] = "\\x" + strHex(args.account); - values[1] = args.forward ? "true" : "false"; - - static std::uint32_t const page_length(200); - if (args.limit == 0 || args.limit > page_length) - values[2] = std::to_string(page_length); - else - values[2] = std::to_string(args.limit); - - if (args.ledger) - { - if (auto range = std::get_if(&args.ledger.value())) - { - values[3] = std::to_string(range->min); - values[4] = std::to_string(range->max); - } - else if (auto hash = std::get_if(&args.ledger.value())) - { - values[5] = ("\\x" + strHex(*hash)); - } - else if ( - auto sequence = std::get_if(&args.ledger.value())) - { - values[6] = std::to_string(*sequence); - } - else if (std::get_if(&args.ledger.value())) - { - // current, closed and validated are all treated as validated - values[7] = "true"; - } - else - { - JLOG(j_.error()) << "doAccountTxStoredProcedure - " - << "Error parsing ledger args"; - return {}; - } - } - - if (args.marker) - { - values[8] = std::to_string(args.marker->ledgerSeq); - values[9] = std::to_string(args.marker->txnSeq); - } - for (size_t i = 0; i < values.size(); ++i) - { - JLOG(j_.trace()) << "value " << std::to_string(i) << " = " - << (values[i] ? values[i].value() : "null"); - } - - auto res = PgQuery(pgPool_)(dbParams); - if (!res) - { - JLOG(j_.error()) << __func__ - << " : Postgres response is null - account = " - << strHex(args.account); - assert(false); - return {{}, {rpcINTERNAL, "Postgres error"}}; - } - else if (res.status() != PGRES_TUPLES_OK) - { - JLOG(j_.error()) << __func__ - << " : Postgres response should have been " - "PGRES_TUPLES_OK but instead was " - << res.status() << " - msg = " << res.msg() - << " - account = " << strHex(args.account); - assert(false); - return {{}, {rpcINTERNAL, "Postgres error"}}; - } - - JLOG(j_.trace()) << __func__ << " Postgres result msg : " << res.msg(); - if (res.isNull() || res.ntuples() == 0) - { - JLOG(j_.debug()) << __func__ - << " : No data returned from Postgres : account = " - << strHex(args.account); - - assert(false); - return {{}, {rpcINTERNAL, "Postgres error"}}; - } - - char const* resultStr = res.c_str(); - JLOG(j_.trace()) << __func__ << " : " - << "postgres result = " << resultStr - << " : account = " << strHex(args.account); - - Json::Value v; - Json::Reader reader; - bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v); - if (success) - { - return processAccountTxStoredProcedureResult(args, v, app_, j_); - } -#endif - // This shouldn't happen. Postgres should return a parseable error - assert(false); - return {{}, {rpcINTERNAL, "Failed to deserialize Postgres result"}}; -} - -Transaction::Locator -PostgresDatabaseImp::locateTransaction(uint256 const& id) -{ -#ifdef RIPPLED_REPORTING - auto baseCmd = boost::format(R"(SELECT tx('%s');)"); - - std::string txHash = "\\x" + strHex(id); - std::string sql = boost::str(baseCmd % txHash); - - auto res = PgQuery(pgPool_)(sql.data()); - - if (!res) - { - JLOG(app_.journal("Transaction").error()) - << __func__ - << " : Postgres response is null - tx ID = " << strHex(id); - assert(false); - return {}; - } - else if (res.status() != PGRES_TUPLES_OK) - { - JLOG(app_.journal("Transaction").error()) - << __func__ - << " : Postgres response should have been " - "PGRES_TUPLES_OK but instead was " - << res.status() << " - msg = " << res.msg() - << " - tx ID = " << strHex(id); - assert(false); - return {}; - } - - JLOG(app_.journal("Transaction").trace()) - << __func__ << " Postgres result msg : " << res.msg(); - if (res.isNull() || res.ntuples() == 0) - { - JLOG(app_.journal("Transaction").debug()) - << __func__ - << " : No data returned from Postgres : tx ID = " << strHex(id); - // This shouldn't happen - assert(false); - return {}; - } - - char const* resultStr = res.c_str(); - JLOG(app_.journal("Transaction").debug()) - << "postgres result = " << resultStr; - - Json::Value v; - Json::Reader reader; - bool success = reader.parse(resultStr, resultStr + strlen(resultStr), v); - if (success) - { - if (v.isMember("nodestore_hash") && v.isMember("ledger_seq")) - { - uint256 nodestoreHash; - if (!nodestoreHash.parseHex( - v["nodestore_hash"].asString().substr(2))) - assert(false); - uint32_t ledgerSeq = v["ledger_seq"].asUInt(); - if (nodestoreHash.isNonZero()) - return {std::make_pair(nodestoreHash, ledgerSeq)}; - } - if (v.isMember("min_seq") && v.isMember("max_seq")) - { - return {ClosedInterval( - v["min_seq"].asUInt(), v["max_seq"].asUInt())}; - } - } -#endif - // Shouldn' happen. Postgres should return the ledger range searched if - // the transaction was not found - assert(false); - Throw( - "Transaction::Locate - Invalid Postgres response"); - return {}; -} - -bool -PostgresDatabaseImp::dbHasSpace(Config const& config) -{ - /* Postgres server could be running on a different machine. */ - - return true; -} - -bool -PostgresDatabaseImp::ledgerDbHasSpace(Config const& config) -{ - return dbHasSpace(config); -} - -bool -PostgresDatabaseImp::transactionDbHasSpace(Config const& config) -{ - return dbHasSpace(config); -} - -std::unique_ptr -getPostgresDatabase(Application& app, Config const& config, JobQueue& jobQueue) -{ - return std::make_unique(app, config, jobQueue); -} - -bool -PostgresDatabaseImp::isCaughtUp(std::string& reason) -{ -#ifdef RIPPLED_REPORTING - using namespace std::chrono_literals; - auto age = PgQuery(pgPool_)("SELECT age()"); - if (!age || age.isNull()) - { - reason = "No ledgers in database"; - return false; - } - if (std::chrono::seconds{age.asInt()} > 3min) - { - reason = "No recently-published ledger"; - return false; - } -#endif - return true; -} - -} // namespace ripple diff --git a/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp b/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp deleted file mode 100644 index 05a460819d0..00000000000 --- a/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp +++ /dev/null @@ -1,1741 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class SQLiteDatabaseImp final : public SQLiteDatabase -{ -public: - SQLiteDatabaseImp( - Application& app, - Config const& config, - JobQueue& jobQueue) - : app_(app) - , useTxTables_(config.useTxTables()) - , j_(app_.journal("SQLiteDatabaseImp")) - { - DatabaseCon::Setup const setup = setup_DatabaseCon(config, j_); - if (!makeLedgerDBs( - config, - setup, - DatabaseCon::CheckpointerSetup{&jobQueue, &app_.logs()})) - { - std::string_view constexpr error = - "Failed to create ledger databases"; - - JLOG(j_.fatal()) << error; - Throw(error.data()); - } - - if (app.getShardStore() && - !makeMetaDBs( - config, - setup, - DatabaseCon::CheckpointerSetup{&jobQueue, &app_.logs()})) - { - std::string_view constexpr error = - "Failed to create metadata databases"; - - JLOG(j_.fatal()) << error; - Throw(error.data()); - } - } - - std::optional - getMinLedgerSeq() override; - - std::optional - getTransactionsMinLedgerSeq() override; - - std::optional - getAccountTransactionsMinLedgerSeq() override; - - std::optional - getMaxLedgerSeq() override; - - void - deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq) override; - - void - deleteBeforeLedgerSeq(LedgerIndex ledgerSeq) override; - - void - deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) override; - - void - deleteAccountTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) override; - - std::size_t - getTransactionCount() override; - - std::size_t - getAccountTransactionCount() override; - - RelationalDatabase::CountMinMax - getLedgerCountMinMax() override; - - bool - saveValidatedLedger( - std::shared_ptr const& ledger, - bool current) override; - - std::optional - getLedgerInfoByIndex(LedgerIndex ledgerSeq) override; - - std::optional - getNewestLedgerInfo() override; - - std::optional - getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) override; - - std::optional - getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) override; - - std::optional - getLedgerInfoByHash(uint256 const& ledgerHash) override; - - uint256 - getHashByIndex(LedgerIndex ledgerIndex) override; - - std::optional - getHashesByIndex(LedgerIndex ledgerIndex) override; - - std::map - getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) override; - - std::vector> - getTxHistory(LedgerIndex startIndex) override; - - AccountTxs - getOldestAccountTxs(AccountTxOptions const& options) override; - - AccountTxs - getNewestAccountTxs(AccountTxOptions const& options) override; - - MetaTxsList - getOldestAccountTxsB(AccountTxOptions const& options) override; - - MetaTxsList - getNewestAccountTxsB(AccountTxOptions const& options) override; - - std::pair> - oldestAccountTxPage(AccountTxPageOptions const& options) override; - - std::pair> - newestAccountTxPage(AccountTxPageOptions const& options) override; - - std::pair> - oldestAccountTxPageB(AccountTxPageOptions const& options) override; - - std::pair> - newestAccountTxPageB(AccountTxPageOptions const& options) override; - - std::variant - getTransaction( - uint256 const& id, - std::optional> const& range, - error_code_i& ec) override; - - bool - ledgerDbHasSpace(Config const& config) override; - - bool - transactionDbHasSpace(Config const& config) override; - - std::uint32_t - getKBUsedAll() override; - - std::uint32_t - getKBUsedLedger() override; - - std::uint32_t - getKBUsedTransaction() override; - - void - closeLedgerDB() override; - - void - closeTransactionDB() override; - -private: - Application& app_; - bool const useTxTables_; - beast::Journal j_; - std::unique_ptr lgrdb_, txdb_; - std::unique_ptr lgrMetaDB_, txMetaDB_; - - /** - * @brief makeLedgerDBs Opens ledger and transaction databases for the node - * store, and stores their descriptors in private member variables. - * @param config Config object. - * @param setup Path to the databases and other opening parameters. - * @param checkpointerSetup Checkpointer parameters. - * @return True if node databases opened successfully. - */ - bool - makeLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup); - - /** - * @brief makeMetaDBs Opens shard index lookup databases, and stores - * their descriptors in private member variables. - * @param config Config object. - * @param setup Path to the databases and other opening parameters. - * @param checkpointerSetup Checkpointer parameters. - * @return True if node databases opened successfully. - */ - bool - makeMetaDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup); - - /** - * @brief seqToShardIndex Provides the index of the shard that stores the - * ledger with the given sequence. - * @param ledgerSeq Ledger sequence. - * @return Shard index. - */ - std::uint32_t - seqToShardIndex(LedgerIndex ledgerSeq) - { - return app_.getShardStore()->seqToShardIndex(ledgerSeq); - } - - /** - * @brief firstLedgerSeq Returns the sequence of the first ledger stored in - * the shard specified by the shard index parameter. - * @param shardIndex Shard Index. - * @return First ledger sequence. - */ - LedgerIndex - firstLedgerSeq(std::uint32_t shardIndex) - { - return app_.getShardStore()->firstLedgerSeq(shardIndex); - } - - /** - * @brief lastLedgerSeq Returns the sequence of the last ledger stored in - * the shard specified by the shard index parameter. - * @param shardIndex Shard Index. - * @return Last ledger sequence. - */ - LedgerIndex - lastLedgerSeq(std::uint32_t shardIndex) - { - return app_.getShardStore()->lastLedgerSeq(shardIndex); - } - - /** - * @brief existsLedger Checks if the node store ledger database exists. - * @return True if the node store ledger database exists. - */ - bool - existsLedger() - { - return static_cast(lgrdb_); - } - - /** - * @brief existsTransaction Checks if the node store transaction database - * exists. - * @return True if the node store transaction database exists. - */ - bool - existsTransaction() - { - return static_cast(txdb_); - } - - /** - * shardStoreExists Checks whether the shard store exists - * @return True if the shard store exists - */ - bool - shardStoreExists() - { - return app_.getShardStore() != nullptr; - } - - /** - * @brief checkoutTransaction Checks out and returns node store ledger - * database. - * @return Session to the node store ledger database. - */ - auto - checkoutLedger() - { - return lgrdb_->checkoutDb(); - } - - /** - * @brief checkoutTransaction Checks out and returns the node store - * transaction database. - * @return Session to the node store transaction database. - */ - auto - checkoutTransaction() - { - return txdb_->checkoutDb(); - } - - /** - * @brief doLedger Checks out the ledger database owned by the shard - * containing the given ledger, and invokes the provided callback - * with a session to that database. - * @param ledgerSeq Ledger sequence. - * @param callback Callback function to call. - * @return Value returned by callback function. - */ - bool - doLedger( - LedgerIndex ledgerSeq, - std::function const& callback) - { - return app_.getShardStore()->callForLedgerSQLByLedgerSeq( - ledgerSeq, callback); - } - - /** - * @brief doTransaction Checks out the transaction database owned by the - * shard containing the given ledger, and invokes the provided - * callback with a session to that database. - * @param ledgerSeq Ledger sequence. - * @param callback Callback function to call. - * @return Value returned by callback function. - */ - bool - doTransaction( - LedgerIndex ledgerSeq, - std::function const& callback) - { - return app_.getShardStore()->callForTransactionSQLByLedgerSeq( - ledgerSeq, callback); - } - - /** - * @brief iterateLedgerForward Checks out ledger databases for all shards in - * ascending order starting from the given shard index, until all - * shards in range have been visited or the callback returns false. - * For each visited shard, we invoke the provided callback with a - * session to the database and the current shard index. - * @param firstIndex First shard index to visit or no value if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateLedgerForward( - std::optional firstIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) - { - return app_.getShardStore()->iterateLedgerSQLsForward( - firstIndex, callback); - } - - /** - * @brief iterateTransactionForward Checks out transaction databases for all - * shards in ascending order starting from the given shard index, - * until all shards in range have been visited or the callback - * returns false. For each visited shard, we invoke the provided - * callback with a session to the database and the current shard - * index. - * @param firstIndex First shard index to visit or no value if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateTransactionForward( - std::optional firstIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) - { - return app_.getShardStore()->iterateLedgerSQLsForward( - firstIndex, callback); - } - - /** - * @brief iterateLedgerBack Checks out ledger databases for all - * shards in descending order starting from the given shard index, - * until all shards in range have been visited or the callback - * returns false. For each visited shard, we invoke the provided - * callback with a session to the database and the current shard - * index. - * @param firstIndex First shard index to visit or no value if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateLedgerBack( - std::optional firstIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) - { - return app_.getShardStore()->iterateLedgerSQLsBack( - firstIndex, callback); - } - - /** - * @brief iterateTransactionBack Checks out transaction databases for all - * shards in descending order starting from the given shard index, - * until all shards in range have been visited or the callback - * returns false. For each visited shard, we invoke the provided - * callback with a session to the database and the current shard - * index. - * @param firstIndex First shard index to visit or no value if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateTransactionBack( - std::optional firstIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) - { - return app_.getShardStore()->iterateLedgerSQLsBack( - firstIndex, callback); - } -}; - -bool -SQLiteDatabaseImp::makeLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup) -{ - auto [lgr, tx, res] = - detail::makeLedgerDBs(config, setup, checkpointerSetup, j_); - txdb_ = std::move(tx); - lgrdb_ = std::move(lgr); - return res; -} - -bool -SQLiteDatabaseImp::makeMetaDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup) -{ - auto [lgrMetaDB, txMetaDB] = - detail::makeMetaDBs(config, setup, checkpointerSetup, j_); - - txMetaDB_ = std::move(txMetaDB); - lgrMetaDB_ = std::move(lgrMetaDB); - - return true; -} - -std::optional -SQLiteDatabaseImp::getMinLedgerSeq() -{ - /* if databases exists, use it */ - if (existsLedger()) - { - auto db = checkoutLedger(); - return detail::getMinLedgerSeq(*db, detail::TableType::Ledgers); - } - - /* else use shard databases, if available */ - if (shardStoreExists()) - { - std::optional res; - iterateLedgerForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - res = detail::getMinLedgerSeq( - session, detail::TableType::Ledgers); - return !res; - }); - return res; - } - - /* else return empty value */ - return {}; -} - -std::optional -SQLiteDatabaseImp::getTransactionsMinLedgerSeq() -{ - if (!useTxTables_) - return {}; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getMinLedgerSeq(*db, detail::TableType::Transactions); - } - - if (shardStoreExists()) - { - std::optional res; - iterateTransactionForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - res = detail::getMinLedgerSeq( - session, detail::TableType::Transactions); - return !res; - }); - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getAccountTransactionsMinLedgerSeq() -{ - if (!useTxTables_) - return {}; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getMinLedgerSeq( - *db, detail::TableType::AccountTransactions); - } - - if (shardStoreExists()) - { - std::optional res; - iterateTransactionForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - res = detail::getMinLedgerSeq( - session, detail::TableType::AccountTransactions); - return !res; - }); - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getMaxLedgerSeq() -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - return detail::getMaxLedgerSeq(*db, detail::TableType::Ledgers); - } - - if (shardStoreExists()) - { - std::optional res; - iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - res = detail::getMaxLedgerSeq( - session, detail::TableType::Ledgers); - return !res; - }); - return res; - } - - return {}; -} - -void -SQLiteDatabaseImp::deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq) -{ - if (!useTxTables_) - return; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - detail::deleteByLedgerSeq( - *db, detail::TableType::Transactions, ledgerSeq); - return; - } - - if (shardStoreExists()) - { - doTransaction(ledgerSeq, [&](soci::session& session) { - detail::deleteByLedgerSeq( - session, detail::TableType::Transactions, ledgerSeq); - return true; - }); - } -} - -void -SQLiteDatabaseImp::deleteBeforeLedgerSeq(LedgerIndex ledgerSeq) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - detail::deleteBeforeLedgerSeq( - *db, detail::TableType::Ledgers, ledgerSeq); - return; - } - - if (shardStoreExists()) - { - iterateLedgerBack( - seqToShardIndex(ledgerSeq), - [&](soci::session& session, std::uint32_t shardIndex) { - detail::deleteBeforeLedgerSeq( - session, detail::TableType::Ledgers, ledgerSeq); - return true; - }); - } -} - -void -SQLiteDatabaseImp::deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) -{ - if (!useTxTables_) - return; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - detail::deleteBeforeLedgerSeq( - *db, detail::TableType::Transactions, ledgerSeq); - return; - } - - if (shardStoreExists()) - { - iterateTransactionBack( - seqToShardIndex(ledgerSeq), - [&](soci::session& session, std::uint32_t shardIndex) { - detail::deleteBeforeLedgerSeq( - session, detail::TableType::Transactions, ledgerSeq); - return true; - }); - } -} - -void -SQLiteDatabaseImp::deleteAccountTransactionsBeforeLedgerSeq( - LedgerIndex ledgerSeq) -{ - if (!useTxTables_) - return; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - detail::deleteBeforeLedgerSeq( - *db, detail::TableType::AccountTransactions, ledgerSeq); - return; - } - - if (shardStoreExists()) - { - iterateTransactionBack( - seqToShardIndex(ledgerSeq), - [&](soci::session& session, std::uint32_t shardIndex) { - detail::deleteBeforeLedgerSeq( - session, detail::TableType::AccountTransactions, ledgerSeq); - return true; - }); - } -} - -std::size_t -SQLiteDatabaseImp::getTransactionCount() -{ - if (!useTxTables_) - return 0; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getRows(*db, detail::TableType::Transactions); - } - - if (shardStoreExists()) - { - std::size_t rows = 0; - iterateTransactionForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - rows += - detail::getRows(session, detail::TableType::Transactions); - return true; - }); - return rows; - } - - return 0; -} - -std::size_t -SQLiteDatabaseImp::getAccountTransactionCount() -{ - if (!useTxTables_) - return 0; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getRows(*db, detail::TableType::AccountTransactions); - } - - if (shardStoreExists()) - { - std::size_t rows = 0; - iterateTransactionForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - rows += detail::getRows( - session, detail::TableType::AccountTransactions); - return true; - }); - return rows; - } - - return 0; -} - -RelationalDatabase::CountMinMax -SQLiteDatabaseImp::getLedgerCountMinMax() -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - return detail::getRowsMinMax(*db, detail::TableType::Ledgers); - } - - if (shardStoreExists()) - { - CountMinMax res{0, 0, 0}; - iterateLedgerForward( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - auto r = - detail::getRowsMinMax(session, detail::TableType::Ledgers); - if (r.numberOfRows) - { - res.numberOfRows += r.numberOfRows; - if (res.minLedgerSequence == 0) - res.minLedgerSequence = r.minLedgerSequence; - res.maxLedgerSequence = r.maxLedgerSequence; - } - return true; - }); - return res; - } - - return {0, 0, 0}; -} - -bool -SQLiteDatabaseImp::saveValidatedLedger( - std::shared_ptr const& ledger, - bool current) -{ - if (existsLedger()) - { - if (!detail::saveValidatedLedger( - *lgrdb_, *txdb_, app_, ledger, current)) - return false; - } - - if (auto shardStore = app_.getShardStore(); shardStore) - { - if (ledger->info().seq < shardStore->earliestLedgerSeq()) - // For the moment return false only when the ShardStore - // should accept the ledger, but fails when attempting - // to do so, i.e. when saveLedgerMeta fails. Later when - // the ShardStore supercedes the NodeStore, change this - // line to return false if the ledger is too early. - return true; - - auto lgrMetaSession = lgrMetaDB_->checkoutDb(); - auto txMetaSession = txMetaDB_->checkoutDb(); - - return detail::saveLedgerMeta( - ledger, - app_, - *lgrMetaSession, - *txMetaSession, - shardStore->seqToShardIndex(ledger->info().seq)); - } - - return true; -} - -std::optional -SQLiteDatabaseImp::getLedgerInfoByIndex(LedgerIndex ledgerSeq) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getLedgerInfoByIndex(*db, ledgerSeq, j_); - - if (res.has_value()) - return res; - } - - if (shardStoreExists()) - { - std::optional res; - doLedger(ledgerSeq, [&](soci::session& session) { - res = detail::getLedgerInfoByIndex(session, ledgerSeq, j_); - return true; - }); - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getNewestLedgerInfo() -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getNewestLedgerInfo(*db, j_); - - if (res.has_value()) - return res; - } - - if (shardStoreExists()) - { - std::optional res; - iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - if (auto info = detail::getNewestLedgerInfo(session, j_)) - { - res = info; - return false; - } - return true; - }); - - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = - detail::getLimitedOldestLedgerInfo(*db, ledgerFirstIndex, j_); - - if (res.has_value()) - return res; - } - - if (shardStoreExists()) - { - std::optional res; - iterateLedgerForward( - seqToShardIndex(ledgerFirstIndex), - [&](soci::session& session, std::uint32_t shardIndex) { - if (auto info = detail::getLimitedOldestLedgerInfo( - session, ledgerFirstIndex, j_)) - { - res = info; - return false; - } - return true; - }); - - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = - detail::getLimitedNewestLedgerInfo(*db, ledgerFirstIndex, j_); - - if (res.has_value()) - return res; - } - - if (shardStoreExists()) - { - std::optional res; - iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - if (auto info = detail::getLimitedNewestLedgerInfo( - session, ledgerFirstIndex, j_)) - { - res = info; - return false; - } - return shardIndex >= seqToShardIndex(ledgerFirstIndex); - }); - - return res; - } - - return {}; -} - -std::optional -SQLiteDatabaseImp::getLedgerInfoByHash(uint256 const& ledgerHash) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getLedgerInfoByHash(*db, ledgerHash, j_); - - if (res.has_value()) - return res; - } - - if (auto shardStore = app_.getShardStore()) - { - std::optional res; - auto lgrMetaSession = lgrMetaDB_->checkoutDb(); - - if (auto const shardIndex = - detail::getShardIndexforLedger(*lgrMetaSession, ledgerHash)) - { - shardStore->callForLedgerSQLByShardIndex( - *shardIndex, [&](soci::session& session) { - res = detail::getLedgerInfoByHash(session, ledgerHash, j_); - return false; // unused - }); - } - - return res; - } - - return {}; -} - -uint256 -SQLiteDatabaseImp::getHashByIndex(LedgerIndex ledgerIndex) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getHashByIndex(*db, ledgerIndex); - - if (res.isNonZero()) - return res; - } - - if (shardStoreExists()) - { - uint256 hash; - doLedger(ledgerIndex, [&](soci::session& session) { - hash = detail::getHashByIndex(session, ledgerIndex); - return true; - }); - return hash; - } - - return uint256(); -} - -std::optional -SQLiteDatabaseImp::getHashesByIndex(LedgerIndex ledgerIndex) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getHashesByIndex(*db, ledgerIndex, j_); - - if (res.has_value()) - return res; - } - - if (shardStoreExists()) - { - std::optional res; - doLedger(ledgerIndex, [&](soci::session& session) { - res = detail::getHashesByIndex(session, ledgerIndex, j_); - return true; - }); - return res; - } - - return {}; -} - -std::map -SQLiteDatabaseImp::getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - auto const res = detail::getHashesByIndex(*db, minSeq, maxSeq, j_); - - if (!res.empty()) - return res; - } - - if (shardStoreExists()) - { - std::map res; - while (minSeq <= maxSeq) - { - LedgerIndex shardMaxSeq = lastLedgerSeq(seqToShardIndex(minSeq)); - if (shardMaxSeq > maxSeq) - shardMaxSeq = maxSeq; - doLedger(minSeq, [&](soci::session& session) { - auto r = - detail::getHashesByIndex(session, minSeq, shardMaxSeq, j_); - res.insert(r.begin(), r.end()); - return true; - }); - minSeq = shardMaxSeq + 1; - } - - return res; - } - - return {}; -} - -std::vector> -SQLiteDatabaseImp::getTxHistory(LedgerIndex startIndex) -{ - if (!useTxTables_) - return {}; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - auto const res = - detail::getTxHistory(*db, app_, startIndex, 20, false).first; - - if (!res.empty()) - return res; - } - - if (shardStoreExists()) - { - std::vector> txs; - int quantity = 20; - iterateTransactionBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - auto [tx, total] = detail::getTxHistory( - session, app_, startIndex, quantity, true); - txs.insert(txs.end(), tx.begin(), tx.end()); - if (total > 0) - { - quantity -= total; - if (quantity <= 0) - return false; - startIndex = 0; - } - else - { - startIndex += total; - } - return true; - }); - - return txs; - } - - return {}; -} - -RelationalDatabase::AccountTxs -SQLiteDatabaseImp::getOldestAccountTxs(AccountTxOptions const& options) -{ - if (!useTxTables_) - return {}; - - LedgerMaster& ledgerMaster = app_.getLedgerMaster(); - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getOldestAccountTxs( - *db, app_, ledgerMaster, options, {}, j_) - .first; - } - - if (shardStoreExists()) - { - AccountTxs ret; - AccountTxOptions opt = options; - int limit_used = 0; - iterateTransactionForward( - opt.minLedger ? seqToShardIndex(opt.minLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.maxLedger && - shardIndex > seqToShardIndex(opt.maxLedger)) - return false; - auto [r, total] = detail::getOldestAccountTxs( - session, app_, ledgerMaster, opt, limit_used, j_); - ret.insert(ret.end(), r.begin(), r.end()); - if (!total) - return false; - if (total > 0) - { - limit_used += total; - opt.offset = 0; - } - else - { - /* - * If total < 0, then -total means number of transactions - * skipped, see definition of return value of function - * ripple::getOldestAccountTxs(). - */ - total = -total; - if (opt.offset <= total) - opt.offset = 0; - else - opt.offset -= total; - } - return true; - }); - - return ret; - } - - return {}; -} - -RelationalDatabase::AccountTxs -SQLiteDatabaseImp::getNewestAccountTxs(AccountTxOptions const& options) -{ - if (!useTxTables_) - return {}; - - LedgerMaster& ledgerMaster = app_.getLedgerMaster(); - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getNewestAccountTxs( - *db, app_, ledgerMaster, options, {}, j_) - .first; - } - - if (shardStoreExists()) - { - AccountTxs ret; - AccountTxOptions opt = options; - int limit_used = 0; - iterateTransactionBack( - opt.maxLedger ? seqToShardIndex(opt.maxLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.minLedger && - shardIndex < seqToShardIndex(opt.minLedger)) - return false; - auto [r, total] = detail::getNewestAccountTxs( - session, app_, ledgerMaster, opt, limit_used, j_); - ret.insert(ret.end(), r.begin(), r.end()); - if (!total) - return false; - if (total > 0) - { - limit_used += total; - opt.offset = 0; - } - else - { - /* - * If total < 0, then -total means number of transactions - * skipped, see definition of return value of function - * ripple::getNewestAccountTxs(). - */ - total = -total; - if (opt.offset <= total) - opt.offset = 0; - else - opt.offset -= total; - } - return true; - }); - - return ret; - } - - return {}; -} - -RelationalDatabase::MetaTxsList -SQLiteDatabaseImp::getOldestAccountTxsB(AccountTxOptions const& options) -{ - if (!useTxTables_) - return {}; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getOldestAccountTxsB(*db, app_, options, {}, j_).first; - } - - if (shardStoreExists()) - { - MetaTxsList ret; - AccountTxOptions opt = options; - int limit_used = 0; - iterateTransactionForward( - opt.minLedger ? seqToShardIndex(opt.minLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.maxLedger && - shardIndex > seqToShardIndex(opt.maxLedger)) - return false; - auto [r, total] = detail::getOldestAccountTxsB( - session, app_, opt, limit_used, j_); - ret.insert(ret.end(), r.begin(), r.end()); - if (!total) - return false; - if (total > 0) - { - limit_used += total; - opt.offset = 0; - } - else - { - /* - * If total < 0, then -total means number of transactions - * skipped, see definition of return value of function - * ripple::getOldestAccountTxsB(). - */ - total = -total; - if (opt.offset <= total) - opt.offset = 0; - else - opt.offset -= total; - } - return true; - }); - - return ret; - } - - return {}; -} - -RelationalDatabase::MetaTxsList -SQLiteDatabaseImp::getNewestAccountTxsB(AccountTxOptions const& options) -{ - if (!useTxTables_) - return {}; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getNewestAccountTxsB(*db, app_, options, {}, j_).first; - } - - if (shardStoreExists()) - { - MetaTxsList ret; - AccountTxOptions opt = options; - int limit_used = 0; - iterateTransactionBack( - opt.maxLedger ? seqToShardIndex(opt.maxLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.minLedger && - shardIndex < seqToShardIndex(opt.minLedger)) - return false; - auto [r, total] = detail::getNewestAccountTxsB( - session, app_, opt, limit_used, j_); - ret.insert(ret.end(), r.begin(), r.end()); - if (!total) - return false; - if (total > 0) - { - limit_used += total; - opt.offset = 0; - } - else - { - /* - * If total < 0, then -total means number of transactions - * skipped, see definition of return value of function - * ripple::getNewestAccountTxsB(). - */ - total = -total; - if (opt.offset <= total) - opt.offset = 0; - else - opt.offset -= total; - } - return true; - }); - - return ret; - } - - return {}; -} - -std::pair< - RelationalDatabase::AccountTxs, - std::optional> -SQLiteDatabaseImp::oldestAccountTxPage(AccountTxPageOptions const& options) -{ - if (!useTxTables_) - return {}; - - static std::uint32_t const page_length(200); - auto onUnsavedLedger = - std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); - AccountTxs ret; - Application& app = app_; - auto onTransaction = [&ret, &app]( - std::uint32_t ledger_index, - std::string const& status, - Blob&& rawTxn, - Blob&& rawMeta) { - convertBlobsToTxResult(ret, ledger_index, status, rawTxn, rawMeta, app); - }; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - auto newmarker = - detail::oldestAccountTxPage( - *db, onUnsavedLedger, onTransaction, options, 0, page_length) - .first; - return {ret, newmarker}; - } - - if (shardStoreExists()) - { - AccountTxPageOptions opt = options; - int limit_used = 0; - iterateTransactionForward( - opt.minLedger ? seqToShardIndex(opt.minLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.maxLedger != UINT32_MAX && - shardIndex > seqToShardIndex(opt.minLedger)) - return false; - auto [marker, total] = detail::oldestAccountTxPage( - session, - onUnsavedLedger, - onTransaction, - opt, - limit_used, - page_length); - opt.marker = marker; - if (total < 0) - return false; - limit_used += total; - return true; - }); - - return {ret, opt.marker}; - } - - return {}; -} - -std::pair< - RelationalDatabase::AccountTxs, - std::optional> -SQLiteDatabaseImp::newestAccountTxPage(AccountTxPageOptions const& options) -{ - if (!useTxTables_) - return {}; - - static std::uint32_t const page_length(200); - auto onUnsavedLedger = - std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); - AccountTxs ret; - Application& app = app_; - auto onTransaction = [&ret, &app]( - std::uint32_t ledger_index, - std::string const& status, - Blob&& rawTxn, - Blob&& rawMeta) { - convertBlobsToTxResult(ret, ledger_index, status, rawTxn, rawMeta, app); - }; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - auto newmarker = - detail::newestAccountTxPage( - *db, onUnsavedLedger, onTransaction, options, 0, page_length) - .first; - return {ret, newmarker}; - } - - if (shardStoreExists()) - { - AccountTxPageOptions opt = options; - int limit_used = 0; - iterateTransactionBack( - opt.maxLedger != UINT32_MAX ? seqToShardIndex(opt.maxLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.minLedger && - shardIndex < seqToShardIndex(opt.minLedger)) - return false; - auto [marker, total] = detail::newestAccountTxPage( - session, - onUnsavedLedger, - onTransaction, - opt, - limit_used, - page_length); - opt.marker = marker; - if (total < 0) - return false; - limit_used += total; - return true; - }); - - return {ret, opt.marker}; - } - - return {}; -} - -std::pair< - RelationalDatabase::MetaTxsList, - std::optional> -SQLiteDatabaseImp::oldestAccountTxPageB(AccountTxPageOptions const& options) -{ - if (!useTxTables_) - return {}; - - static std::uint32_t const page_length(500); - auto onUnsavedLedger = - std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); - MetaTxsList ret; - auto onTransaction = [&ret]( - std::uint32_t ledgerIndex, - std::string const& status, - Blob&& rawTxn, - Blob&& rawMeta) { - ret.emplace_back(std::move(rawTxn), std::move(rawMeta), ledgerIndex); - }; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - auto newmarker = - detail::oldestAccountTxPage( - *db, onUnsavedLedger, onTransaction, options, 0, page_length) - .first; - return {ret, newmarker}; - } - - if (shardStoreExists()) - { - AccountTxPageOptions opt = options; - int limit_used = 0; - iterateTransactionForward( - opt.minLedger ? seqToShardIndex(opt.minLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.maxLedger != UINT32_MAX && - shardIndex > seqToShardIndex(opt.minLedger)) - return false; - auto [marker, total] = detail::oldestAccountTxPage( - session, - onUnsavedLedger, - onTransaction, - opt, - limit_used, - page_length); - opt.marker = marker; - if (total < 0) - return false; - limit_used += total; - return true; - }); - - return {ret, opt.marker}; - } - - return {}; -} - -std::pair< - RelationalDatabase::MetaTxsList, - std::optional> -SQLiteDatabaseImp::newestAccountTxPageB(AccountTxPageOptions const& options) -{ - if (!useTxTables_) - return {}; - - static std::uint32_t const page_length(500); - auto onUnsavedLedger = - std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); - MetaTxsList ret; - auto onTransaction = [&ret]( - std::uint32_t ledgerIndex, - std::string const& status, - Blob&& rawTxn, - Blob&& rawMeta) { - ret.emplace_back(std::move(rawTxn), std::move(rawMeta), ledgerIndex); - }; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - auto newmarker = - detail::newestAccountTxPage( - *db, onUnsavedLedger, onTransaction, options, 0, page_length) - .first; - return {ret, newmarker}; - } - - if (shardStoreExists()) - { - AccountTxPageOptions opt = options; - int limit_used = 0; - iterateTransactionBack( - opt.maxLedger != UINT32_MAX ? seqToShardIndex(opt.maxLedger) - : std::optional(), - [&](soci::session& session, std::uint32_t shardIndex) { - if (opt.minLedger && - shardIndex < seqToShardIndex(opt.minLedger)) - return false; - auto [marker, total] = detail::newestAccountTxPage( - session, - onUnsavedLedger, - onTransaction, - opt, - limit_used, - page_length); - opt.marker = marker; - if (total < 0) - return false; - limit_used += total; - return true; - }); - - return {ret, opt.marker}; - } - - return {}; -} - -std::variant -SQLiteDatabaseImp::getTransaction( - uint256 const& id, - std::optional> const& range, - error_code_i& ec) -{ - if (!useTxTables_) - return TxSearched::unknown; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::getTransaction(*db, app_, id, range, ec); - } - - if (auto shardStore = app_.getShardStore(); shardStore) - { - std::variant res(TxSearched::unknown); - auto txMetaSession = txMetaDB_->checkoutDb(); - - if (auto const shardIndex = - detail::getShardIndexforTransaction(*txMetaSession, id)) - { - shardStore->callForTransactionSQLByShardIndex( - *shardIndex, [&](soci::session& session) { - std::optional> range1; - if (range) - { - std::uint32_t const low = std::max( - range->lower(), firstLedgerSeq(*shardIndex)); - std::uint32_t const high = std::min( - range->upper(), lastLedgerSeq(*shardIndex)); - if (low <= high) - range1 = ClosedInterval(low, high); - } - res = detail::getTransaction(session, app_, id, range1, ec); - - return res.index() == 1 && - std::get(res) != - TxSearched::unknown; // unused - }); - } - - return res; - } - - return TxSearched::unknown; -} - -bool -SQLiteDatabaseImp::ledgerDbHasSpace(Config const& config) -{ - if (existsLedger()) - { - auto db = checkoutLedger(); - return detail::dbHasSpace(*db, config, j_); - } - - if (shardStoreExists()) - { - return iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - return detail::dbHasSpace(session, config, j_); - }); - } - - return true; -} - -bool -SQLiteDatabaseImp::transactionDbHasSpace(Config const& config) -{ - if (!useTxTables_) - return true; - - if (existsTransaction()) - { - auto db = checkoutTransaction(); - return detail::dbHasSpace(*db, config, j_); - } - - if (shardStoreExists()) - { - return iterateTransactionBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - return detail::dbHasSpace(session, config, j_); - }); - } - - return true; -} - -std::uint32_t -SQLiteDatabaseImp::getKBUsedAll() -{ - if (existsLedger()) - { - return ripple::getKBUsedAll(lgrdb_->getSession()); - } - - if (shardStoreExists()) - { - std::uint32_t sum = 0; - iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - sum += ripple::getKBUsedAll(session); - return true; - }); - return sum; - } - - return 0; -} - -std::uint32_t -SQLiteDatabaseImp::getKBUsedLedger() -{ - if (existsLedger()) - { - return ripple::getKBUsedDB(lgrdb_->getSession()); - } - - if (shardStoreExists()) - { - std::uint32_t sum = 0; - iterateLedgerBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - sum += ripple::getKBUsedDB(session); - return true; - }); - return sum; - } - - return 0; -} - -std::uint32_t -SQLiteDatabaseImp::getKBUsedTransaction() -{ - if (!useTxTables_) - return 0; - - if (existsTransaction()) - { - return ripple::getKBUsedDB(txdb_->getSession()); - } - - if (shardStoreExists()) - { - std::uint32_t sum = 0; - iterateTransactionBack( - {}, [&](soci::session& session, std::uint32_t shardIndex) { - sum += ripple::getKBUsedDB(session); - return true; - }); - return sum; - } - - return 0; -} - -void -SQLiteDatabaseImp::closeLedgerDB() -{ - lgrdb_.reset(); -} - -void -SQLiteDatabaseImp::closeTransactionDB() -{ - txdb_.reset(); -} - -std::unique_ptr -getSQLiteDatabase(Application& app, Config const& config, JobQueue& jobQueue) -{ - return std::make_unique(app, config, jobQueue); -} - -} // namespace ripple diff --git a/src/ripple/app/rdb/impl/Download.cpp b/src/ripple/app/rdb/impl/Download.cpp deleted file mode 100644 index 6f6d6bf7577..00000000000 --- a/src/ripple/app/rdb/impl/Download.cpp +++ /dev/null @@ -1,153 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include - -namespace ripple { - -std::pair, std::optional> -openDatabaseBodyDb( - DatabaseCon::Setup const& setup, - boost::filesystem::path const& path, - beast::Journal j) -{ - // SOCI requires boost::optional (not std::optional) as the parameter. - boost::optional pathFromDb; - boost::optional size; - - auto conn = std::make_unique( - setup, "Download", DownloaderDBPragma, DatabaseBodyDBInit, j); - - auto& session = *conn->checkoutDb(); - - session << "SELECT Path FROM Download WHERE Part=0;", - soci::into(pathFromDb); - - // Try to reuse preexisting - // database. - if (pathFromDb) - { - // Can't resuse - database was - // from a different file download. - if (pathFromDb != path.string()) - { - session << "DROP TABLE Download;"; - } - - // Continuing a file download. - else - { - session << "SELECT SUM(LENGTH(Data)) FROM Download;", - soci::into(size); - } - } - - return {std::move(conn), (size ? *size : std::optional())}; -} - -std::uint64_t -databaseBodyDoPut( - soci::session& session, - std::string const& data, - std::string const& path, - std::uint64_t fileSize, - std::uint64_t part, - std::uint16_t maxRowSizePad) -{ - std::uint64_t rowSize = 0; - soci::indicator rti; - - std::uint64_t remainingInRow = 0; - - auto be = - dynamic_cast(session.get_backend()); - BOOST_ASSERT(be); - - // This limits how large we can make the blob - // in each row. Also subtract a pad value to - // account for the other values in the row. - auto const blobMaxSize = - sqlite_api::sqlite3_limit(be->conn_, SQLITE_LIMIT_LENGTH, -1) - - maxRowSizePad; - - std::string newpath; - - auto rowInit = [&] { - session << "INSERT INTO Download VALUES (:path, zeroblob(0), 0, :part)", - soci::use(newpath), soci::use(part); - - remainingInRow = blobMaxSize; - rowSize = 0; - }; - - session << "SELECT Path,Size,Part FROM Download ORDER BY Part DESC " - "LIMIT 1", - soci::into(newpath), soci::into(rowSize), soci::into(part, rti); - - if (!session.got_data()) - { - newpath = path; - rowInit(); - } - else - remainingInRow = blobMaxSize - rowSize; - - auto insert = [&session, &rowSize, &part, &fs = fileSize]( - auto const& data) { - std::uint64_t updatedSize = rowSize + data.size(); - - session << "UPDATE Download SET Data = CAST(Data || :data AS blob), " - "Size = :size WHERE Part = :part;", - soci::use(data), soci::use(updatedSize), soci::use(part); - - fs += data.size(); - }; - - size_t currentBase = 0; - - while (currentBase + remainingInRow < data.size()) - { - if (remainingInRow) - { - insert(data.substr(currentBase, remainingInRow)); - currentBase += remainingInRow; - } - - ++part; - rowInit(); - } - - insert(data.substr(currentBase)); - - return part; -} - -void -databaseBodyFinish(soci::session& session, std::ofstream& fout) -{ - soci::rowset rs = - (session.prepare << "SELECT Data FROM Download ORDER BY PART ASC;"); - - // iteration through the resultset: - for (auto it = rs.begin(); it != rs.end(); ++it) - fout.write(it->data(), it->size()); -} - -} // namespace ripple diff --git a/src/ripple/app/rdb/impl/ShardArchive.cpp b/src/ripple/app/rdb/impl/ShardArchive.cpp deleted file mode 100644 index edad36f7099..00000000000 --- a/src/ripple/app/rdb/impl/ShardArchive.cpp +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -namespace ripple { - -std::unique_ptr -makeArchiveDB( - boost::filesystem::path const& dir, - std::string const& dbName, - beast::Journal j) -{ - return std::make_unique( - dir, dbName, DownloaderDBPragma, ShardArchiveHandlerDBInit, j); -} - -void -readArchiveDB( - DatabaseCon& db, - std::function const& func) -{ - soci::rowset rs = - (db.getSession().prepare << "SELECT * FROM State;"); - - for (auto it = rs.begin(); it != rs.end(); ++it) - { - func(it->get(1), it->get(0)); - } -} - -void -insertArchiveDB( - DatabaseCon& db, - std::uint32_t shardIndex, - std::string const& url) -{ - db.getSession() << "INSERT INTO State VALUES (:index, :url);", - soci::use(shardIndex), soci::use(url); -} - -void -deleteFromArchiveDB(DatabaseCon& db, std::uint32_t shardIndex) -{ - db.getSession() << "DELETE FROM State WHERE ShardIndex = :index;", - soci::use(shardIndex); -} - -void -dropArchiveDB(DatabaseCon& db) -{ - db.getSession() << "DROP TABLE State;"; -} - -} // namespace ripple diff --git a/src/ripple/app/rdb/impl/UnitaryShard.cpp b/src/ripple/app/rdb/impl/UnitaryShard.cpp deleted file mode 100644 index 786340f5955..00000000000 --- a/src/ripple/app/rdb/impl/UnitaryShard.cpp +++ /dev/null @@ -1,324 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include - -namespace ripple { - -DatabasePair -makeShardCompleteLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - beast::Journal j) -{ - auto tx{std::make_unique( - setup, TxDBName, FinalShardDBPragma, TxDBInit, j)}; - tx->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::txnDBCache, std::nullopt))); - - auto lgr{std::make_unique( - setup, LgrDBName, FinalShardDBPragma, LgrDBInit, j)}; - lgr->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::lgrDBCache, std::nullopt))); - - return {std::move(lgr), std::move(tx)}; -} - -DatabasePair -makeShardIncompleteLedgerDBs( - Config const& config, - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j) -{ - // transaction database - auto tx{std::make_unique( - setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup, j)}; - tx->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::txnDBCache))); - - // ledger database - auto lgr{std::make_unique( - setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup, j)}; - lgr->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::lgrDBCache))); - - return {std::move(lgr), std::move(tx)}; -} - -bool -updateLedgerDBs( - soci::session& txsession, - soci::session& lgrsession, - std::shared_ptr const& ledger, - std::uint32_t index, - std::atomic& stop, - beast::Journal j) -{ - auto const ledgerSeq{ledger->info().seq}; - - // Update the transactions database - { - auto& session{txsession}; - soci::transaction tr(session); - - session << "DELETE FROM Transactions " - "WHERE LedgerSeq = :seq;", - soci::use(ledgerSeq); - session << "DELETE FROM AccountTransactions " - "WHERE LedgerSeq = :seq;", - soci::use(ledgerSeq); - - if (ledger->info().txHash.isNonZero()) - { - auto const sSeq{std::to_string(ledgerSeq)}; - if (!ledger->txMap().isValid()) - { - JLOG(j.error()) - << "shard " << index << " has an invalid transaction map" - << " on sequence " << sSeq; - return false; - } - - for (auto const& item : ledger->txs) - { - if (stop.load(std::memory_order_relaxed)) - return false; - - TxMeta const txMeta{ - item.first->getTransactionID(), - ledger->seq(), - *item.second}; - - auto const sTxID = to_string(txMeta.getTxID()); - - session << "DELETE FROM AccountTransactions " - "WHERE TransID = :txID;", - soci::use(sTxID); - - auto const& accounts = txMeta.getAffectedAccounts(); - if (!accounts.empty()) - { - auto const sTxnSeq{std::to_string(txMeta.getIndex())}; - auto const s{boost::str( - boost::format("('%s','%s',%s,%s)") % sTxID % "%s" % - sSeq % sTxnSeq)}; - std::string sql; - sql.reserve((accounts.size() + 1) * 128); - sql = - "INSERT INTO AccountTransactions " - "(TransID, Account, LedgerSeq, TxnSeq) VALUES "; - sql += boost::algorithm::join( - accounts | - boost::adaptors::transformed( - [&](AccountID const& accountID) { - return boost::str( - boost::format(s) % - ripple::toBase58(accountID)); - }), - ","); - sql += ';'; - session << sql; - - JLOG(j.trace()) - << "shard " << index << " account transaction: " << sql; - } - else if (!isPseudoTx(*item.first)) - { - // It's okay for pseudo transactions to not affect any - // accounts. But otherwise... - JLOG(j.warn()) - << "shard " << index << " transaction in ledger " - << sSeq << " affects no accounts"; - } - - Serializer s; - item.second->add(s); - session - << (STTx::getMetaSQLInsertReplaceHeader() + - item.first->getMetaSQL( - ledgerSeq, sqlBlobLiteral(s.modData())) + - ';'); - } - } - - tr.commit(); - } - - auto const sHash{to_string(ledger->info().hash)}; - - // Update the ledger database - { - auto& session{lgrsession}; - soci::transaction tr(session); - - auto const sParentHash{to_string(ledger->info().parentHash)}; - auto const sDrops{to_string(ledger->info().drops)}; - auto const closingTime{ - ledger->info().closeTime.time_since_epoch().count()}; - auto const prevClosingTime{ - ledger->info().parentCloseTime.time_since_epoch().count()}; - auto const closeTimeRes{ledger->info().closeTimeResolution.count()}; - auto const sAccountHash{to_string(ledger->info().accountHash)}; - auto const sTxHash{to_string(ledger->info().txHash)}; - - session << "DELETE FROM Ledgers " - "WHERE LedgerSeq = :seq;", - soci::use(ledgerSeq); - session << "INSERT OR REPLACE INTO Ledgers (" - "LedgerHash, LedgerSeq, PrevHash, TotalCoins, ClosingTime," - "PrevClosingTime, CloseTimeRes, CloseFlags, AccountSetHash," - "TransSetHash)" - "VALUES (" - ":ledgerHash, :ledgerSeq, :prevHash, :totalCoins," - ":closingTime, :prevClosingTime, :closeTimeRes," - ":closeFlags, :accountSetHash, :transSetHash);", - soci::use(sHash), soci::use(ledgerSeq), soci::use(sParentHash), - soci::use(sDrops), soci::use(closingTime), - soci::use(prevClosingTime), soci::use(closeTimeRes), - soci::use(ledger->info().closeFlags), soci::use(sAccountHash), - soci::use(sTxHash); - - tr.commit(); - } - - return true; -} - -std::unique_ptr -makeAcquireDB( - DatabaseCon::Setup const& setup, - DatabaseCon::CheckpointerSetup const& checkpointerSetup, - beast::Journal j) -{ - return std::make_unique( - setup, - AcquireShardDBName, - AcquireShardDBPragma, - AcquireShardDBInit, - checkpointerSetup, - j); -} - -void -insertAcquireDBIndex(soci::session& session, std::uint32_t index) -{ - session << "INSERT INTO Shard (ShardIndex) " - "VALUES (:shardIndex);", - soci::use(index); -} - -std::pair> -selectAcquireDBLedgerSeqs(soci::session& session, std::uint32_t index) -{ - // resIndex and must be boost::optional (not std) because that's - // what SOCI expects in its interface. - boost::optional resIndex; - soci::blob sociBlob(session); - soci::indicator blobPresent; - - session << "SELECT ShardIndex, StoredLedgerSeqs " - "FROM Shard " - "WHERE ShardIndex = :index;", - soci::into(resIndex), soci::into(sociBlob, blobPresent), - soci::use(index); - - if (!resIndex || index != resIndex) - return {false, {}}; - - if (blobPresent != soci::i_ok) - return {true, {}}; - - std::string s; - convert(sociBlob, s); - - return {true, s}; -} - -std::pair -selectAcquireDBLedgerSeqsHash(soci::session& session, std::uint32_t index) -{ - // resIndex and sHash0 must be boost::optional (not std) because that's - // what SOCI expects in its interface. - boost::optional resIndex; - boost::optional sHash0; - soci::blob sociBlob(session); - soci::indicator blobPresent; - - session << "SELECT ShardIndex, LastLedgerHash, StoredLedgerSeqs " - "FROM Shard " - "WHERE ShardIndex = :index;", - soci::into(resIndex), soci::into(sHash0), - soci::into(sociBlob, blobPresent), soci::use(index); - - std::optional sHash = - (sHash0 ? *sHash0 : std::optional()); - - if (!resIndex || index != resIndex) - return {false, {{}, {}}}; - - if (blobPresent != soci::i_ok) - return {true, {{}, sHash}}; - - std::string s; - convert(sociBlob, s); - - return {true, {s, sHash}}; -} - -void -updateAcquireDB( - soci::session& session, - std::shared_ptr const& ledger, - std::uint32_t index, - std::uint32_t lastSeq, - std::optional const& seqs) -{ - soci::blob sociBlob(session); - auto const sHash{to_string(ledger->info().hash)}; - - if (seqs) - convert(*seqs, sociBlob); - - if (ledger->info().seq == lastSeq) - { - // Store shard's last ledger hash - session << "UPDATE Shard " - "SET LastLedgerHash = :lastLedgerHash," - "StoredLedgerSeqs = :storedLedgerSeqs " - "WHERE ShardIndex = :shardIndex;", - soci::use(sHash), soci::use(sociBlob), soci::use(index); - } - else - { - session << "UPDATE Shard " - "SET StoredLedgerSeqs = :storedLedgerSeqs " - "WHERE ShardIndex = :shardIndex;", - soci::use(sociBlob), soci::use(index); - } -} - -} // namespace ripple diff --git a/src/ripple/app/reporting/ETLHelpers.h b/src/ripple/app/reporting/ETLHelpers.h deleted file mode 100644 index 04f282ca53e..00000000000 --- a/src/ripple/app/reporting/ETLHelpers.h +++ /dev/null @@ -1,195 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_REPORTING_ETLHELPERS_H_INCLUDED -#define RIPPLE_APP_REPORTING_ETLHELPERS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -/// This datastructure is used to keep track of the sequence of the most recent -/// ledger validated by the network. There are two methods that will wait until -/// certain conditions are met. This datastructure is able to be "stopped". When -/// the datastructure is stopped, any threads currently waiting are unblocked. -/// Any later calls to methods of this datastructure will not wait. Once the -/// datastructure is stopped, the datastructure remains stopped for the rest of -/// its lifetime. -class NetworkValidatedLedgers -{ - // max sequence validated by network - std::optional max_; - - mutable std::mutex m_; - - mutable std::condition_variable cv_; - - bool stopping_ = false; - -public: - /// Notify the datastructure that idx has been validated by the network - /// @param idx sequence validated by network - void - push(uint32_t idx) - { - std::lock_guard lck(m_); - if (!max_ || idx > *max_) - max_ = idx; - cv_.notify_all(); - } - - /// Get most recently validated sequence. If no ledgers are known to have - /// been validated, this function waits until the next ledger is validated - /// @return sequence of most recently validated ledger. empty optional if - /// the datastructure has been stopped - std::optional - getMostRecent() const - { - std::unique_lock lck(m_); - cv_.wait(lck, [this]() { return max_ || stopping_; }); - return max_; - } - - /// Get most recently validated sequence. - /// @return sequence of most recently validated ledger, or empty optional - /// if no ledgers are known to have been validated. - std::optional - tryGetMostRecent() const - { - std::unique_lock lk(m_); - return max_; - } - - /// Waits for the sequence to be validated by the network - /// @param sequence to wait for - /// @return true if sequence was validated, false otherwise - /// a return value of false means the datastructure has been stopped - bool - waitUntilValidatedByNetwork(uint32_t sequence) - { - std::unique_lock lck(m_); - cv_.wait(lck, [sequence, this]() { - return (max_ && sequence <= *max_) || stopping_; - }); - return !stopping_; - } - - /// Puts the datastructure in the stopped state - /// Future calls to this datastructure will not block - /// This operation cannot be reversed - void - stop() - { - std::lock_guard lck(m_); - stopping_ = true; - cv_.notify_all(); - } -}; - -/// Generic thread-safe queue with an optional maximum size -/// Note, we can't use a lockfree queue here, since we need the ability to wait -/// for an element to be added or removed from the queue. These waits are -/// blocking calls. -template -class ThreadSafeQueue -{ - std::queue queue_; - - mutable std::mutex m_; - std::condition_variable cv_; - std::optional maxSize_; - -public: - /// @param maxSize maximum size of the queue. Calls that would cause the - /// queue to exceed this size will block until free space is available - explicit ThreadSafeQueue(uint32_t maxSize) : maxSize_(maxSize) - { - } - - /// Create a queue with no maximum size - ThreadSafeQueue() = default; - - /// @param elt element to push onto queue - /// if maxSize is set, this method will block until free space is available - void - push(T const& elt) - { - std::unique_lock lck(m_); - // if queue has a max size, wait until not full - if (maxSize_) - cv_.wait(lck, [this]() { return queue_.size() <= *maxSize_; }); - queue_.push(elt); - cv_.notify_all(); - } - - /// @param elt element to push onto queue. elt is moved from - /// if maxSize is set, this method will block until free space is available - void - push(T&& elt) - { - std::unique_lock lck(m_); - // if queue has a max size, wait until not full - if (maxSize_) - cv_.wait(lck, [this]() { return queue_.size() <= *maxSize_; }); - queue_.push(std::move(elt)); - cv_.notify_all(); - } - - /// @return element popped from queue. Will block until queue is non-empty - T - pop() - { - std::unique_lock lck(m_); - cv_.wait(lck, [this]() { return !queue_.empty(); }); - T ret = std::move(queue_.front()); - queue_.pop(); - // if queue has a max size, unblock any possible pushers - if (maxSize_) - cv_.notify_all(); - return ret; - } -}; - -/// Parititions the uint256 keyspace into numMarkers partitions, each of equal -/// size. -inline std::vector -getMarkers(size_t numMarkers) -{ - assert(numMarkers <= 256); - - unsigned char incr = 256 / numMarkers; - - std::vector markers; - markers.reserve(numMarkers); - uint256 base{0}; - for (size_t i = 0; i < numMarkers; ++i) - { - markers.push_back(base); - base.data()[0] += incr; - } - return markers; -} - -} // namespace ripple -#endif diff --git a/src/ripple/app/reporting/ETLSource.cpp b/src/ripple/app/reporting/ETLSource.cpp deleted file mode 100644 index 73ba9816950..00000000000 --- a/src/ripple/app/reporting/ETLSource.cpp +++ /dev/null @@ -1,982 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include - -namespace ripple { - -// Create ETL source without grpc endpoint -// Fetch ledger and load initial ledger will fail for this source -// Primarily used in read-only mode, to monitor when ledgers are validated -ETLSource::ETLSource(std::string ip, std::string wsPort, ReportingETL& etl) - : ip_(ip) - , wsPort_(wsPort) - , etl_(etl) - , ioc_(etl.getApplication().getIOService()) - , ws_(std::make_unique< - boost::beast::websocket::stream>( - boost::asio::make_strand(ioc_))) - , resolver_(boost::asio::make_strand(ioc_)) - , networkValidatedLedgers_(etl_.getNetworkValidatedLedgers()) - , journal_(etl_.getApplication().journal("ReportingETL::ETLSource")) - , app_(etl_.getApplication()) - , timer_(ioc_) -{ -} - -ETLSource::ETLSource( - std::string ip, - std::string wsPort, - std::string grpcPort, - ReportingETL& etl) - : ip_(ip) - , wsPort_(wsPort) - , grpcPort_(grpcPort) - , etl_(etl) - , ioc_(etl.getApplication().getIOService()) - , ws_(std::make_unique< - boost::beast::websocket::stream>( - boost::asio::make_strand(ioc_))) - , resolver_(boost::asio::make_strand(ioc_)) - , networkValidatedLedgers_(etl_.getNetworkValidatedLedgers()) - , journal_(etl_.getApplication().journal("ReportingETL::ETLSource")) - , app_(etl_.getApplication()) - , timer_(ioc_) -{ - std::string connectionString; - try - { - connectionString = - beast::IP::Endpoint( - boost::asio::ip::make_address(ip_), std::stoi(grpcPort_)) - .to_string(); - - JLOG(journal_.info()) - << "Using IP to connect to ETL source: " << connectionString; - } - catch (std::exception const&) - { - connectionString = "dns:" + ip_ + ":" + grpcPort_; - JLOG(journal_.info()) - << "Using DNS to connect to ETL source: " << connectionString; - } - try - { - stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( - grpc::CreateChannel( - connectionString, grpc::InsecureChannelCredentials())); - JLOG(journal_.info()) << "Made stub for remote = " << toString(); - } - catch (std::exception const& e) - { - JLOG(journal_.error()) << "Exception while creating stub = " << e.what() - << " . Remote = " << toString(); - } -} - -void -ETLSource::reconnect(boost::beast::error_code ec) -{ - connected_ = false; - // These are somewhat normal errors. operation_aborted occurs on shutdown, - // when the timer is cancelled. connection_refused will occur repeatedly - // if we cannot connect to the transaction processing process - if (ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::connection_refused) - { - JLOG(journal_.error()) << __func__ << " : " - << "error code = " << ec << " - " << toString(); - } - else - { - JLOG(journal_.warn()) << __func__ << " : " - << "error code = " << ec << " - " << toString(); - } - - if (etl_.isStopping()) - { - JLOG(journal_.debug()) << __func__ << " : " << toString() - << " - etl is stopping. aborting reconnect"; - return; - } - - // exponentially increasing timeouts, with a max of 30 seconds - size_t waitTime = std::min(pow(2, numFailures_), 30.0); - numFailures_++; - timer_.expires_after(boost::asio::chrono::seconds(waitTime)); - timer_.async_wait([this, fname = __func__](auto ec) { - bool startAgain = (ec != boost::asio::error::operation_aborted); - JLOG(journal_.trace()) << fname << " async_wait : ec = " << ec; - close(startAgain); - }); -} - -void -ETLSource::close(bool startAgain) -{ - timer_.cancel(); - ioc_.post([this, startAgain]() { - if (closing_) - return; - - if (ws_->is_open()) - { - // onStop() also calls close(). If the async_close is called twice, - // an assertion fails. Using closing_ makes sure async_close is only - // called once - closing_ = true; - ws_->async_close( - boost::beast::websocket::close_code::normal, - [this, startAgain, fname = __func__](auto ec) { - if (ec) - { - JLOG(journal_.error()) - << fname << " async_close : " - << "error code = " << ec << " - " << toString(); - } - closing_ = false; - if (startAgain) - start(); - }); - } - else if (startAgain) - { - start(); - } - }); -} - -void -ETLSource::start() -{ - JLOG(journal_.trace()) << __func__ << " : " << toString(); - - auto const host = ip_; - auto const port = wsPort_; - - resolver_.async_resolve( - host, port, [this](auto ec, auto results) { onResolve(ec, results); }); -} - -void -ETLSource::onResolve( - boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type results) -{ - JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - " - << toString(); - if (ec) - { - // try again - reconnect(ec); - } - else - { - boost::beast::get_lowest_layer(*ws_).expires_after( - std::chrono::seconds(30)); - boost::beast::get_lowest_layer(*ws_).async_connect( - results, [this](auto ec, auto ep) { onConnect(ec, ep); }); - } -} - -void -ETLSource::onConnect( - boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint) -{ - JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - " - << toString(); - if (ec) - { - // start over - reconnect(ec); - } - else - { - numFailures_ = 0; - // Turn off timeout on the tcp stream, because websocket stream has it's - // own timeout system - boost::beast::get_lowest_layer(*ws_).expires_never(); - - // Set suggested timeout settings for the websocket - ws_->set_option( - boost::beast::websocket::stream_base::timeout::suggested( - boost::beast::role_type::client)); - - // Set a decorator to change the User-Agent of the handshake - ws_->set_option(boost::beast::websocket::stream_base::decorator( - [](boost::beast::websocket::request_type& req) { - req.set( - boost::beast::http::field::user_agent, - std::string(BOOST_BEAST_VERSION_STRING) + - " websocket-client-async"); - })); - - // Update the host_ string. This will provide the value of the - // Host HTTP header during the WebSocket handshake. - // See https://tools.ietf.org/html/rfc7230#section-5.4 - auto host = ip_ + ':' + std::to_string(endpoint.port()); - // Perform the websocket handshake - ws_->async_handshake(host, "/", [this](auto ec) { onHandshake(ec); }); - } -} - -void -ETLSource::onHandshake(boost::beast::error_code ec) -{ - JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - " - << toString(); - if (ec) - { - // start over - reconnect(ec); - } - else - { - Json::Value jv; - jv["command"] = "subscribe"; - - jv["streams"] = Json::arrayValue; - Json::Value ledgerStream("ledger"); - jv["streams"].append(ledgerStream); - Json::Value txnStream("transactions_proposed"); - jv["streams"].append(txnStream); - Json::Value validationStream("validations"); - jv["streams"].append(validationStream); - Json::Value manifestStream("manifests"); - jv["streams"].append(manifestStream); - Json::FastWriter fastWriter; - - JLOG(journal_.trace()) << "Sending subscribe stream message"; - // Send the message - ws_->async_write( - boost::asio::buffer(fastWriter.write(jv)), - [this](auto ec, size_t size) { onWrite(ec, size); }); - } -} - -void -ETLSource::onWrite(boost::beast::error_code ec, size_t bytesWritten) -{ - JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - " - << toString(); - if (ec) - { - // start over - reconnect(ec); - } - else - { - ws_->async_read( - readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); }); - } -} - -void -ETLSource::onRead(boost::beast::error_code ec, size_t size) -{ - JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - " - << toString(); - // if error or error reading message, start over - if (ec) - { - reconnect(ec); - } - else - { - handleMessage(); - boost::beast::flat_buffer buffer; - swap(readBuffer_, buffer); - - JLOG(journal_.trace()) - << __func__ << " : calling async_read - " << toString(); - ws_->async_read( - readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); }); - } -} - -bool -ETLSource::handleMessage() -{ - JLOG(journal_.trace()) << __func__ << " : " << toString(); - - setLastMsgTime(); - connected_ = true; - try - { - Json::Value response; - Json::Reader reader; - if (!reader.parse( - static_cast(readBuffer_.data().data()), response)) - { - JLOG(journal_.error()) - << __func__ << " : " - << "Error parsing stream message." - << " Message = " << readBuffer_.data().data(); - return false; - } - - uint32_t ledgerIndex = 0; - if (response.isMember("result")) - { - if (response["result"].isMember(jss::ledger_index)) - { - ledgerIndex = response["result"][jss::ledger_index].asUInt(); - } - if (response[jss::result].isMember(jss::validated_ledgers)) - { - setValidatedRange( - response[jss::result][jss::validated_ledgers].asString()); - } - JLOG(journal_.debug()) - << __func__ << " : " - << "Received a message on ledger " - << " subscription stream. Message : " - << response.toStyledString() << " - " << toString(); - } - else - { - if (etl_.getETLLoadBalancer().shouldPropagateStream(this)) - { - if (response.isMember(jss::transaction)) - { - etl_.getApplication().getOPs().forwardProposedTransaction( - response); - } - else if ( - response.isMember("type") && - response["type"] == "validationReceived") - { - etl_.getApplication().getOPs().forwardValidation(response); - } - else if ( - response.isMember("type") && - response["type"] == "manifestReceived") - { - etl_.getApplication().getOPs().forwardManifest(response); - } - } - - if (response.isMember("type") && response["type"] == "ledgerClosed") - { - JLOG(journal_.debug()) - << __func__ << " : " - << "Received a message on ledger " - << " subscription stream. Message : " - << response.toStyledString() << " - " << toString(); - if (response.isMember(jss::ledger_index)) - { - ledgerIndex = response[jss::ledger_index].asUInt(); - } - if (response.isMember(jss::validated_ledgers)) - { - setValidatedRange( - response[jss::validated_ledgers].asString()); - } - } - } - - if (ledgerIndex != 0) - { - JLOG(journal_.trace()) - << __func__ << " : " - << "Pushing ledger sequence = " << ledgerIndex << " - " - << toString(); - networkValidatedLedgers_.push(ledgerIndex); - } - return true; - } - catch (std::exception const& e) - { - JLOG(journal_.error()) << "Exception in handleMessage : " << e.what(); - return false; - } -} - -class AsyncCallData -{ - std::unique_ptr cur_; - std::unique_ptr next_; - - org::xrpl::rpc::v1::GetLedgerDataRequest request_; - std::unique_ptr context_; - - grpc::Status status_; - - unsigned char nextPrefix_; - - beast::Journal journal_; - -public: - AsyncCallData( - uint256& marker, - std::optional nextMarker, - uint32_t seq, - beast::Journal& j) - : journal_(j) - { - request_.mutable_ledger()->set_sequence(seq); - if (marker.isNonZero()) - { - request_.set_marker(marker.data(), marker.size()); - } - request_.set_user("ETL"); - nextPrefix_ = 0x00; - if (nextMarker) - nextPrefix_ = nextMarker->data()[0]; - - unsigned char prefix = marker.data()[0]; - - JLOG(journal_.debug()) - << "Setting up AsyncCallData. marker = " << strHex(marker) - << " . prefix = " << strHex(std::string(1, prefix)) - << " . nextPrefix_ = " << strHex(std::string(1, nextPrefix_)); - - assert(nextPrefix_ > prefix || nextPrefix_ == 0x00); - - cur_ = std::make_unique(); - - next_ = std::make_unique(); - - context_ = std::make_unique(); - } - - enum class CallStatus { MORE, DONE, ERRORED }; - CallStatus - process( - std::unique_ptr& stub, - grpc::CompletionQueue& cq, - ThreadSafeQueue>& queue, - bool abort = false) - { - JLOG(journal_.debug()) << "Processing calldata"; - if (abort) - { - JLOG(journal_.error()) << "AsyncCallData aborted"; - return CallStatus::ERRORED; - } - if (!status_.ok()) - { - JLOG(journal_.debug()) << "AsyncCallData status_ not ok: " - << " code = " << status_.error_code() - << " message = " << status_.error_message(); - return CallStatus::ERRORED; - } - if (!next_->is_unlimited()) - { - JLOG(journal_.warn()) - << "AsyncCallData is_unlimited is false. Make sure " - "secure_gateway is set correctly at the ETL source"; - assert(false); - } - - std::swap(cur_, next_); - - bool more = true; - - // if no marker returned, we are done - if (cur_->marker().size() == 0) - more = false; - - // if returned marker is greater than our end, we are done - unsigned char prefix = cur_->marker()[0]; - if (nextPrefix_ != 0x00 && prefix >= nextPrefix_) - more = false; - - // if we are not done, make the next async call - if (more) - { - request_.set_marker(std::move(cur_->marker())); - call(stub, cq); - } - - for (auto& obj : cur_->ledger_objects().objects()) - { - auto key = uint256::fromVoidChecked(obj.key()); - if (!key) - throw std::runtime_error("Received malformed object ID"); - - auto& data = obj.data(); - - SerialIter it{data.data(), data.size()}; - std::shared_ptr sle = std::make_shared(it, *key); - - queue.push(sle); - } - - return more ? CallStatus::MORE : CallStatus::DONE; - } - - void - call( - std::unique_ptr& stub, - grpc::CompletionQueue& cq) - { - context_ = std::make_unique(); - - std::unique_ptr> - rpc(stub->PrepareAsyncGetLedgerData(context_.get(), request_, &cq)); - - rpc->StartCall(); - - rpc->Finish(next_.get(), &status_, this); - } - - std::string - getMarkerPrefix() - { - if (next_->marker().size() == 0) - return ""; - else - return strHex(std::string{next_->marker().data()[0]}); - } -}; - -bool -ETLSource::loadInitialLedger( - uint32_t sequence, - ThreadSafeQueue>& writeQueue) -{ - if (!stub_) - return false; - - grpc::CompletionQueue cq; - - void* tag; - - bool ok = false; - - std::vector calls; - std::vector markers{getMarkers(etl_.getNumMarkers())}; - - for (size_t i = 0; i < markers.size(); ++i) - { - std::optional nextMarker; - if (i + 1 < markers.size()) - nextMarker = markers[i + 1]; - calls.emplace_back(markers[i], nextMarker, sequence, journal_); - } - - JLOG(journal_.debug()) << "Starting data download for ledger " << sequence - << ". Using source = " << toString(); - - for (auto& c : calls) - c.call(stub_, cq); - - size_t numFinished = 0; - bool abort = false; - while (numFinished < calls.size() && !etl_.isStopping() && - cq.Next(&tag, &ok)) - { - assert(tag); - - auto ptr = static_cast(tag); - - if (!ok) - { - JLOG(journal_.error()) << "loadInitialLedger - ok is false"; - return false; - // handle cancelled - } - else - { - JLOG(journal_.debug()) - << "Marker prefix = " << ptr->getMarkerPrefix(); - auto result = ptr->process(stub_, cq, writeQueue, abort); - if (result != AsyncCallData::CallStatus::MORE) - { - numFinished++; - JLOG(journal_.debug()) - << "Finished a marker. " - << "Current number of finished = " << numFinished; - } - if (result == AsyncCallData::CallStatus::ERRORED) - { - abort = true; - } - } - } - return !abort; -} - -std::pair -ETLSource::fetchLedger(uint32_t ledgerSequence, bool getObjects) -{ - org::xrpl::rpc::v1::GetLedgerResponse response; - if (!stub_) - return {{grpc::StatusCode::INTERNAL, "No Stub"}, response}; - - // ledger header with txns and metadata - org::xrpl::rpc::v1::GetLedgerRequest request; - grpc::ClientContext context; - request.mutable_ledger()->set_sequence(ledgerSequence); - request.set_transactions(true); - request.set_expand(true); - request.set_get_objects(getObjects); - request.set_user("ETL"); - grpc::Status status = stub_->GetLedger(&context, request, &response); - if (status.ok() && !response.is_unlimited()) - { - JLOG(journal_.warn()) << "ETLSource::fetchLedger - is_unlimited is " - "false. Make sure secure_gateway is set " - "correctly on the ETL source. source = " - << toString(); - assert(false); - } - return {status, std::move(response)}; -} - -ETLLoadBalancer::ETLLoadBalancer(ReportingETL& etl) - : etl_(etl) - , journal_(etl_.getApplication().journal("ReportingETL::LoadBalancer")) -{ -} - -void -ETLLoadBalancer::add( - std::string& host, - std::string& websocketPort, - std::string& grpcPort) -{ - std::unique_ptr ptr = - std::make_unique(host, websocketPort, grpcPort, etl_); - sources_.push_back(std::move(ptr)); - JLOG(journal_.info()) << __func__ << " : added etl source - " - << sources_.back()->toString(); -} - -void -ETLLoadBalancer::add(std::string& host, std::string& websocketPort) -{ - std::unique_ptr ptr = - std::make_unique(host, websocketPort, etl_); - sources_.push_back(std::move(ptr)); - JLOG(journal_.info()) << __func__ << " : added etl source - " - << sources_.back()->toString(); -} - -void -ETLLoadBalancer::loadInitialLedger( - uint32_t sequence, - ThreadSafeQueue>& writeQueue) -{ - execute( - [this, &sequence, &writeQueue](auto& source) { - bool res = source->loadInitialLedger(sequence, writeQueue); - if (!res) - { - JLOG(journal_.error()) << "Failed to download initial ledger. " - << " Sequence = " << sequence - << " source = " << source->toString(); - } - return res; - }, - sequence); -} - -std::optional -ETLLoadBalancer::fetchLedger(uint32_t ledgerSequence, bool getObjects) -{ - org::xrpl::rpc::v1::GetLedgerResponse response; - bool success = execute( - [&response, ledgerSequence, getObjects, this](auto& source) { - auto [status, data] = - source->fetchLedger(ledgerSequence, getObjects); - response = std::move(data); - if (status.ok() && response.validated()) - { - JLOG(journal_.info()) - << "Successfully fetched ledger = " << ledgerSequence - << " from source = " << source->toString(); - return true; - } - else - { - JLOG(journal_.warn()) - << "Error getting ledger = " << ledgerSequence - << " Reply : " << response.DebugString() - << " error_code : " << status.error_code() - << " error_msg : " << status.error_message() - << " source = " << source->toString(); - return false; - } - }, - ledgerSequence); - if (success) - return response; - else - return {}; -} - -std::unique_ptr -ETLLoadBalancer::getP2pForwardingStub() const -{ - if (sources_.size() == 0) - return nullptr; - srand((unsigned)time(0)); - auto sourceIdx = rand() % sources_.size(); - auto numAttempts = 0; - while (numAttempts < sources_.size()) - { - auto stub = sources_[sourceIdx]->getP2pForwardingStub(); - if (!stub) - { - sourceIdx = (sourceIdx + 1) % sources_.size(); - ++numAttempts; - continue; - } - return stub; - } - return nullptr; -} - -Json::Value -ETLLoadBalancer::forwardToP2p(RPC::JsonContext& context) const -{ - Json::Value res; - if (sources_.size() == 0) - return res; - srand((unsigned)time(0)); - auto sourceIdx = rand() % sources_.size(); - auto numAttempts = 0; - - auto mostRecent = etl_.getNetworkValidatedLedgers().tryGetMostRecent(); - while (numAttempts < sources_.size()) - { - auto increment = [&]() { - sourceIdx = (sourceIdx + 1) % sources_.size(); - ++numAttempts; - }; - auto& src = sources_[sourceIdx]; - if (mostRecent && !src->hasLedger(*mostRecent)) - { - increment(); - continue; - } - res = src->forwardToP2p(context); - if (!res.isMember("forwarded") || res["forwarded"] != true) - { - increment(); - continue; - } - return res; - } - RPC::Status err = {rpcFAILED_TO_FORWARD}; - err.inject(res); - return res; -} - -std::unique_ptr -ETLSource::getP2pForwardingStub() const -{ - if (!connected_) - return nullptr; - try - { - return org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( - grpc::CreateChannel( - beast::IP::Endpoint( - boost::asio::ip::make_address(ip_), std::stoi(grpcPort_)) - .to_string(), - grpc::InsecureChannelCredentials())); - } - catch (std::exception const&) - { - JLOG(journal_.error()) << "Failed to create grpc stub"; - return nullptr; - } -} - -Json::Value -ETLSource::forwardToP2p(RPC::JsonContext& context) const -{ - JLOG(journal_.debug()) << "Attempting to forward request to tx. " - << "request = " << context.params.toStyledString(); - - Json::Value response; - if (!connected_) - { - JLOG(journal_.error()) - << "Attempted to proxy but failed to connect to tx"; - return response; - } - namespace beast = boost::beast; // from - namespace http = beast::http; // from - namespace websocket = beast::websocket; // from - namespace net = boost::asio; // from - using tcp = boost::asio::ip::tcp; // from - Json::Value& request = context.params; - try - { - // The io_context is required for all I/O - net::io_context ioc; - - // These objects perform our I/O - tcp::resolver resolver{ioc}; - - JLOG(journal_.debug()) << "Creating websocket"; - auto ws = std::make_unique>(ioc); - - // Look up the domain name - auto const results = resolver.resolve(ip_, wsPort_); - - JLOG(journal_.debug()) << "Connecting websocket"; - // Make the connection on the IP address we get from a lookup - net::connect(ws->next_layer(), results.begin(), results.end()); - - // Set a decorator to change the User-Agent of the handshake - // and to tell rippled to charge the client IP for RPC - // resources. See "secure_gateway" in - // https://github.com/ripple/rippled/blob/develop/cfg/rippled-example.cfg - ws->set_option(websocket::stream_base::decorator( - [&context](websocket::request_type& req) { - req.set( - http::field::user_agent, - std::string(BOOST_BEAST_VERSION_STRING) + - " websocket-client-coro"); - req.set( - http::field::forwarded, - "for=" + context.consumer.to_string()); - })); - JLOG(journal_.debug()) << "client ip: " << context.consumer.to_string(); - - JLOG(journal_.debug()) << "Performing websocket handshake"; - // Perform the websocket handshake - ws->handshake(ip_, "/"); - - Json::FastWriter fastWriter; - - JLOG(journal_.debug()) << "Sending request"; - // Send the message - ws->write(net::buffer(fastWriter.write(request))); - - beast::flat_buffer buffer; - ws->read(buffer); - - Json::Reader reader; - if (!reader.parse( - static_cast(buffer.data().data()), response)) - { - JLOG(journal_.error()) << "Error parsing response"; - response[jss::error] = "Error parsing response from tx"; - } - JLOG(journal_.debug()) << "Successfully forward request"; - - response["forwarded"] = true; - return response; - } - catch (std::exception const& e) - { - JLOG(journal_.error()) << "Encountered exception : " << e.what(); - return response; - } -} - -template -bool -ETLLoadBalancer::execute(Func f, uint32_t ledgerSequence) -{ - srand((unsigned)time(0)); - auto sourceIdx = rand() % sources_.size(); - auto numAttempts = 0; - - while (!etl_.isStopping()) - { - auto& source = sources_[sourceIdx]; - - JLOG(journal_.debug()) - << __func__ << " : " - << "Attempting to execute func. ledger sequence = " - << ledgerSequence << " - source = " << source->toString(); - if (source->hasLedger(ledgerSequence)) - { - bool res = f(source); - if (res) - { - JLOG(journal_.debug()) - << __func__ << " : " - << "Successfully executed func at source = " - << source->toString() - << " - ledger sequence = " << ledgerSequence; - break; - } - else - { - JLOG(journal_.warn()) - << __func__ << " : " - << "Failed to execute func at source = " - << source->toString() - << " - ledger sequence = " << ledgerSequence; - } - } - else - { - JLOG(journal_.warn()) - << __func__ << " : " - << "Ledger not present at source = " << source->toString() - << " - ledger sequence = " << ledgerSequence; - } - sourceIdx = (sourceIdx + 1) % sources_.size(); - numAttempts++; - if (numAttempts % sources_.size() == 0) - { - // If another process loaded the ledger into the database, we can - // abort trying to fetch the ledger from a transaction processing - // process - if (etl_.getApplication().getLedgerMaster().getLedgerBySeq( - ledgerSequence)) - { - JLOG(journal_.warn()) - << __func__ << " : " - << "Error executing function. " - << " Tried all sources, but ledger was found in db." - << " Sequence = " << ledgerSequence; - return false; - } - JLOG(journal_.error()) - << __func__ << " : " - << "Error executing function " - << " - ledger sequence = " << ledgerSequence - << " - Tried all sources. Sleeping and trying again"; - std::this_thread::sleep_for(std::chrono::seconds(2)); - } - } - return !etl_.isStopping(); -} - -void -ETLLoadBalancer::start() -{ - for (auto& source : sources_) - source->start(); -} - -void -ETLLoadBalancer::stop() -{ - for (auto& source : sources_) - source->stop(); -} - -} // namespace ripple diff --git a/src/ripple/app/reporting/ETLSource.h b/src/ripple/app/reporting/ETLSource.h deleted file mode 100644 index b9f27a1bf1f..00000000000 --- a/src/ripple/app/reporting/ETLSource.h +++ /dev/null @@ -1,435 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_REPORTING_ETLSOURCE_H_INCLUDED -#define RIPPLE_APP_REPORTING_ETLSOURCE_H_INCLUDED -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -namespace ripple { - -class ReportingETL; - -/// This class manages a connection to a single ETL source. This is almost -/// always a p2p node, but really could be another reporting node. This class -/// subscribes to the ledgers and transactions_proposed streams of the -/// associated p2p node, and keeps track of which ledgers the p2p node has. This -/// class also has methods for extracting said ledgers. Lastly this class -/// forwards transactions received on the transactions_proposed streams to any -/// subscribers. -class ETLSource -{ - std::string ip_; - - std::string wsPort_; - - std::string grpcPort_; - - ReportingETL& etl_; - - // a reference to the applications io_service - boost::asio::io_context& ioc_; - - std::unique_ptr stub_; - - std::unique_ptr> - ws_; - boost::asio::ip::tcp::resolver resolver_; - - boost::beast::flat_buffer readBuffer_; - - std::vector> validatedLedgers_; - - std::string validatedLedgersRaw_; - - NetworkValidatedLedgers& networkValidatedLedgers_; - - beast::Journal journal_; - - Application& app_; - - mutable std::mutex mtx_; - - size_t numFailures_ = 0; - - std::atomic_bool closing_ = false; - - std::atomic_bool connected_ = false; - - // true if this ETL source is forwarding transactions received on the - // transactions_proposed stream. There are usually multiple ETL sources, - // so to avoid forwarding the same transaction multiple times, we only - // forward from one particular ETL source at a time. - std::atomic_bool forwardingStream_ = false; - - // The last time a message was received on the ledgers stream - std::chrono::system_clock::time_point lastMsgTime_; - mutable std::mutex lastMsgTimeMtx_; - - // used for retrying connections - boost::asio::steady_timer timer_; - -public: - bool - isConnected() const - { - return connected_; - } - - std::chrono::system_clock::time_point - getLastMsgTime() const - { - std::lock_guard lck(lastMsgTimeMtx_); - return lastMsgTime_; - } - - void - setLastMsgTime() - { - std::lock_guard lck(lastMsgTimeMtx_); - lastMsgTime_ = std::chrono::system_clock::now(); - } - - /// Create ETL source without gRPC endpoint - /// Fetch ledger and load initial ledger will fail for this source - /// Primarly used in read-only mode, to monitor when ledgers are validated - ETLSource(std::string ip, std::string wsPort, ReportingETL& etl); - - /// Create ETL source with gRPC endpoint - ETLSource( - std::string ip, - std::string wsPort, - std::string grpcPort, - ReportingETL& etl); - - /// @param sequence ledger sequence to check for - /// @return true if this source has the desired ledger - bool - hasLedger(uint32_t sequence) const - { - std::lock_guard lck(mtx_); - for (auto& pair : validatedLedgers_) - { - if (sequence >= pair.first && sequence <= pair.second) - { - return true; - } - else if (sequence < pair.first) - { - // validatedLedgers_ is a sorted list of disjoint ranges - // if the sequence comes before this range, the sequence will - // come before all subsequent ranges - return false; - } - } - return false; - } - - /// process the validated range received on the ledgers stream. set the - /// appropriate member variable - /// @param range validated range received on ledgers stream - void - setValidatedRange(std::string const& range) - { - std::vector> pairs; - std::vector ranges; - boost::split(ranges, range, boost::is_any_of(",")); - for (auto& pair : ranges) - { - std::vector minAndMax; - - boost::split(minAndMax, pair, boost::is_any_of("-")); - - if (minAndMax.size() == 1) - { - uint32_t sequence = std::stoll(minAndMax[0]); - pairs.push_back(std::make_pair(sequence, sequence)); - } - else - { - assert(minAndMax.size() == 2); - uint32_t min = std::stoll(minAndMax[0]); - uint32_t max = std::stoll(minAndMax[1]); - pairs.push_back(std::make_pair(min, max)); - } - } - std::sort(pairs.begin(), pairs.end(), [](auto left, auto right) { - return left.first < right.first; - }); - - // we only hold the lock here, to avoid blocking while string processing - std::lock_guard lck(mtx_); - validatedLedgers_ = std::move(pairs); - validatedLedgersRaw_ = range; - } - - /// @return the validated range of this source - /// @note this is only used by server_info - std::string - getValidatedRange() const - { - std::lock_guard lck(mtx_); - - return validatedLedgersRaw_; - } - - /// Close the underlying websocket - void - stop() - { - JLOG(journal_.debug()) << __func__ << " : " - << "Closing websocket"; - - assert(ws_); - close(false); - } - - /// Fetch the specified ledger - /// @param ledgerSequence sequence of the ledger to fetch - /// @getObjects whether to get the account state diff between this ledger - /// and the prior one - /// @return the extracted data and the result status - std::pair - fetchLedger(uint32_t ledgerSequence, bool getObjects = true); - - std::string - toString() const - { - return "{ validated_ledger : " + getValidatedRange() + - " , ip : " + ip_ + " , web socket port : " + wsPort_ + - ", grpc port : " + grpcPort_ + " }"; - } - - Json::Value - toJson() const - { - Json::Value result(Json::objectValue); - result["connected"] = connected_.load(); - result["validated_ledgers_range"] = getValidatedRange(); - result["ip"] = ip_; - result["websocket_port"] = wsPort_; - result["grpc_port"] = grpcPort_; - auto last = getLastMsgTime(); - if (last.time_since_epoch().count() != 0) - result["last_message_arrival_time"] = - to_string(std::chrono::floor(last)); - return result; - } - - /// Download a ledger in full - /// @param ledgerSequence sequence of the ledger to download - /// @param writeQueue queue to push downloaded ledger objects - /// @return true if the download was successful - bool - loadInitialLedger( - uint32_t ledgerSequence, - ThreadSafeQueue>& writeQueue); - - /// Begin sequence of operations to connect to the ETL source and subscribe - /// to ledgers and transactions_proposed - void - start(); - - /// Attempt to reconnect to the ETL source - void - reconnect(boost::beast::error_code ec); - - /// Callback - void - onResolve( - boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type results); - - /// Callback - void - onConnect( - boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint); - - /// Callback - void - onHandshake(boost::beast::error_code ec); - - /// Callback - void - onWrite(boost::beast::error_code ec, size_t size); - - /// Callback - void - onRead(boost::beast::error_code ec, size_t size); - - /// Handle the most recently received message - /// @return true if the message was handled successfully. false on error - bool - handleMessage(); - - /// Close the websocket - /// @param startAgain whether to reconnect - void - close(bool startAgain); - - /// Get grpc stub to forward requests to p2p node - /// @return stub to send requests to ETL source - std::unique_ptr - getP2pForwardingStub() const; - - /// Forward a JSON RPC request to a p2p node - /// @param context context of RPC request - /// @return response received from ETL source - Json::Value - forwardToP2p(RPC::JsonContext& context) const; -}; - -/// This class is used to manage connections to transaction processing processes -/// This class spawns a listener for each etl source, which listens to messages -/// on the ledgers stream (to keep track of which ledgers have been validated by -/// the network, and the range of ledgers each etl source has). This class also -/// allows requests for ledger data to be load balanced across all possible etl -/// sources. -class ETLLoadBalancer -{ -private: - ReportingETL& etl_; - - beast::Journal journal_; - - std::vector> sources_; - -public: - ETLLoadBalancer(ReportingETL& etl); - - /// Add an ETL source - /// @param host host or ip of ETL source - /// @param websocketPort port where ETL source accepts websocket connections - /// @param grpcPort port where ETL source accepts gRPC requests - void - add(std::string& host, std::string& websocketPort, std::string& grpcPort); - - /// Add an ETL source without gRPC support. This source will send messages - /// on the ledgers and transactions_proposed streams, but will not be able - /// to handle the gRPC requests that are used for ETL - /// @param host host or ip of ETL source - /// @param websocketPort port where ETL source accepts websocket connections - void - add(std::string& host, std::string& websocketPort); - - /// Load the initial ledger, writing data to the queue - /// @param sequence sequence of ledger to download - /// @param writeQueue queue to push downloaded data to - void - loadInitialLedger( - uint32_t sequence, - ThreadSafeQueue>& writeQueue); - - /// Fetch data for a specific ledger. This function will continuously try - /// to fetch data for the specified ledger until the fetch succeeds, the - /// ledger is found in the database, or the server is shutting down. - /// @param ledgerSequence sequence of ledger to fetch data for - /// @param getObjects if true, fetch diff between specified ledger and - /// previous - /// @return the extracted data, if extraction was successful. If the ledger - /// was found in the database or the server is shutting down, the optional - /// will be empty - std::optional - fetchLedger(uint32_t ledgerSequence, bool getObjects); - - /// Setup all of the ETL sources and subscribe to the necessary streams - void - start(); - - void - stop(); - - /// Determine whether messages received on the transactions_proposed stream - /// should be forwarded to subscribing clients. The server subscribes to - /// transactions_proposed, validations, and manifests on multiple - /// ETLSources, yet only forwards messages from one source at any given time - /// (to avoid sending duplicate messages to clients). - /// @param in ETLSource in question - /// @return true if messages should be forwarded - bool - shouldPropagateStream(ETLSource* in) const - { - for (auto& src : sources_) - { - assert(src); - // We pick the first ETLSource encountered that is connected - if (src->isConnected()) - { - if (src.get() == in) - return true; - else - return false; - } - } - - // If no sources connected, then this stream has not been forwarded. - return true; - } - - Json::Value - toJson() const - { - Json::Value ret(Json::arrayValue); - for (auto& src : sources_) - { - ret.append(src->toJson()); - } - return ret; - } - - /// Randomly select a p2p node to forward a gRPC request to - /// @return gRPC stub to forward requests to p2p node - std::unique_ptr - getP2pForwardingStub() const; - - /// Forward a JSON RPC request to a randomly selected p2p node - /// @param context context of the request - /// @return response received from p2p node - Json::Value - forwardToP2p(RPC::JsonContext& context) const; - -private: - /// f is a function that takes an ETLSource as an argument and returns a - /// bool. Attempt to execute f for one randomly chosen ETLSource that has - /// the specified ledger. If f returns false, another randomly chosen - /// ETLSource is used. The process repeats until f returns true. - /// @param f function to execute. This function takes the ETL source as an - /// argument, and returns a bool. - /// @param ledgerSequence f is executed for each ETLSource that has this - /// ledger - /// @return true if f was eventually executed successfully. false if the - /// ledger was found in the database or the server is shutting down - template - bool - execute(Func f, uint32_t ledgerSequence); -}; - -} // namespace ripple -#endif diff --git a/src/ripple/app/reporting/P2pProxy.cpp b/src/ripple/app/reporting/P2pProxy.cpp deleted file mode 100644 index ee04b68e6b5..00000000000 --- a/src/ripple/app/reporting/P2pProxy.cpp +++ /dev/null @@ -1,84 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include - -namespace ripple { - -Json::Value -forwardToP2p(RPC::JsonContext& context) -{ - return context.app.getReportingETL().getETLLoadBalancer().forwardToP2p( - context); -} - -std::unique_ptr -getP2pForwardingStub(RPC::Context& context) -{ - return context.app.getReportingETL() - .getETLLoadBalancer() - .getP2pForwardingStub(); -} - -// We only forward requests where ledger_index is "current" or "closed" -// otherwise, attempt to handle here -bool -shouldForwardToP2p(RPC::JsonContext& context) -{ - if (!context.app.config().reporting()) - return false; - - Json::Value& params = context.params; - std::string strCommand = params.isMember(jss::command) - ? params[jss::command].asString() - : params[jss::method].asString(); - - JLOG(context.j.trace()) << "COMMAND:" << strCommand; - JLOG(context.j.trace()) << "REQUEST:" << params; - auto handler = RPC::getHandler( - context.apiVersion, context.app.config().BETA_RPC_API, strCommand); - if (!handler) - { - JLOG(context.j.error()) - << "Error getting handler. command = " << strCommand; - return false; - } - - if (handler->condition_ == RPC::NEEDS_CURRENT_LEDGER || - handler->condition_ == RPC::NEEDS_CLOSED_LEDGER) - { - return true; - } - - if (params.isMember(jss::ledger_index)) - { - auto indexValue = params[jss::ledger_index]; - if (indexValue.isString()) - { - auto index = indexValue.asString(); - return index == "current" || index == "closed"; - } - } - return false; -} - -} // namespace ripple diff --git a/src/ripple/app/reporting/P2pProxy.h b/src/ripple/app/reporting/P2pProxy.h deleted file mode 100644 index 55ebc726726..00000000000 --- a/src/ripple/app/reporting/P2pProxy.h +++ /dev/null @@ -1,113 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_REPORTING_P2PPROXY_H_INCLUDED -#define RIPPLE_APP_REPORTING_P2PPROXY_H_INCLUDED - -#include -#include -#include -#include - -#include -#include - -namespace ripple { -/// Forward a JSON request to a p2p node and return the response -/// @param context context of the request -/// @return response from p2p node -Json::Value -forwardToP2p(RPC::JsonContext& context); - -/// Whether a request should be forwarded, based on request parameters -/// @param context context of the request -/// @return true if should be forwarded -bool -shouldForwardToP2p(RPC::JsonContext& context); - -template -bool -needCurrentOrClosed(Request& request) -{ - // These are the only gRPC requests that specify a ledger - if constexpr ( - std::is_same::value || - std::is_same:: - value || - std::is_same::value) - { - if (request.ledger().ledger_case() == - org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase::kShortcut) - { - if (request.ledger().shortcut() != - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED && - request.ledger().shortcut() != - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED) - return true; - } - } - // GetLedgerDiff specifies two ledgers - else if constexpr (std::is_same< - Request, - org::xrpl::rpc::v1::GetLedgerDiffRequest>::value) - { - auto help = [](auto specifier) { - if (specifier.ledger_case() == - org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase::kShortcut) - { - if (specifier.shortcut() != - org::xrpl::rpc::v1::LedgerSpecifier:: - SHORTCUT_VALIDATED && - specifier.shortcut() != - org::xrpl::rpc::v1::LedgerSpecifier:: - SHORTCUT_UNSPECIFIED) - return true; - } - return false; - }; - return help(request.base_ledger()) || help(request.desired_ledger()); - } - return false; -} - -/// Whether a request should be forwarded, based on request parameters -/// @param context context of the request -/// @condition required condition for the request -/// @return true if should be forwarded -template -bool -shouldForwardToP2p(RPC::GRPCContext& context, RPC::Condition condition) -{ - if (!context.app.config().reporting()) - return false; - if (condition == RPC::NEEDS_CURRENT_LEDGER || - condition == RPC::NEEDS_CLOSED_LEDGER) - return true; - - return needCurrentOrClosed(context.params); -} - -/// Get stub used to forward gRPC requests to a p2p node -/// @param context context of the request -/// @return stub to forward requests -std::unique_ptr -getP2pForwardingStub(RPC::Context& context); - -} // namespace ripple -#endif diff --git a/src/ripple/app/reporting/README.md b/src/ripple/app/reporting/README.md deleted file mode 100644 index 745cbb633a9..00000000000 --- a/src/ripple/app/reporting/README.md +++ /dev/null @@ -1,108 +0,0 @@ -Reporting mode is a special operating mode of rippled, designed to handle RPCs -for validated data. A server running in reporting mode does not connect to the -p2p network, but rather extracts validated data from a node that is connected -to the p2p network. To run rippled in reporting mode, you must also run a -separate rippled node in p2p mode, to use as an ETL source. Multiple reporting -nodes can share access to the same network accessible databases (Postgres and -Cassandra); at any given time, only one reporting node will be performing ETL -and writing to the databases, while the others simply read from the databases. -A server running in reporting mode will forward any requests that require access -to the p2p network to a p2p node. - -# Reporting ETL -A single reporting node has one or more ETL sources, specified in the config -file. A reporting node will subscribe to the "ledgers" stream of each of the ETL -sources. This stream sends a message whenever a new ledger is validated. Upon -receiving a message on the stream, reporting will then fetch the data associated -with the newly validated ledger from one of the ETL sources. The fetch is -performed via a gRPC request ("GetLedger"). This request returns the ledger -header, transactions+metadata blobs, and every ledger object -added/modified/deleted as part of this ledger. ETL then writes all of this data -to the databases, and moves on to the next ledger. ETL does not apply -transactions, but rather extracts the already computed results of those -transactions (all of the added/modified/deleted SHAMap leaf nodes of the state -tree). The new SHAMap inner nodes are computed by the ETL writer; this computation mainly -involves manipulating child pointers and recomputing hashes, logic which is -buried inside of SHAMap. - -If the database is entirely empty, ETL must download an entire ledger in full -(as opposed to just the diff, as described above). This download is done via the -"GetLedgerData" gRPC request. "GetLedgerData" allows clients to page through an -entire ledger over several RPC calls. ETL will page through an entire ledger, -and write each object to the database. - -If the database is not empty, the reporting node will first come up in a "soft" -read-only mode. In read-only mode, the server does not perform ETL and simply -publishes new ledgers as they are written to the database. -If the database is not updated within a certain time period -(currently hard coded at 20 seconds), the reporting node will begin the ETL -process and start writing to the database. Postgres will report an error when -trying to write a record with a key that already exists. ETL uses this error to -determine that another process is writing to the database, and subsequently -falls back to a soft read-only mode. Reporting nodes can also operate in strict -read-only mode, in which case they will never write to the database. - -# Database Nuances -The database schema for reporting mode does not allow any history gaps. -Attempting to write a ledger to a non-empty database where the previous ledger -does not exist will return an error. - -The databases must be set up prior to running reporting mode. This requires -creating the Postgres database, and setting up the Cassandra keyspace. Reporting -mode will create the objects table in Cassandra if the table does not yet exist. - -Creating the Postgres database: -``` -$ psql -h [host] -U [user] -postgres=# create database [database]; -``` -Creating the keyspace: -``` -$ cqlsh [host] [port] -> CREATE KEYSPACE rippled WITH REPLICATION = - {'class' : 'SimpleStrategy', 'replication_factor' : 3 }; -``` -A replication factor of 3 is recommended. However, when running locally, only a -replication factor of 1 is supported. - -Online delete is not supported by reporting mode and must be done manually. The -easiest way to do this would be to setup a second Cassandra keyspace and -Postgres database, bring up a single reporting mode instance that uses those -databases, and start ETL at a ledger of your choosing (via --startReporting on -the command line). Once this node is caught up, the other databases can be -deleted. - -To delete: -``` -$ psql -h [host] -U [user] -d [database] -reporting=$ truncate table ledgers cascade; -``` -``` -$ cqlsh [host] [port] -> truncate table objects; -``` -# Proxy -RPCs that require access to the p2p network and/or the open ledger are forwarded -from the reporting node to one of the ETL sources. The request is not processed -prior to forwarding, and the response is delivered as-is to the client. -Reporting will forward any requests that always require p2p/open ledger access -(fee and submit, for instance). In addition, any request that explicitly -requests data from the open or closed ledger (via setting -"ledger_index":"current" or "ledger_index":"closed"), will be forwarded to a -p2p node. - -For the stream "transactions_proposed" (AKA "rt_transactions"), reporting -subscribes to the "transactions_proposed" streams of each ETL source, and then -forwards those messages to any clients subscribed to the same stream on the -reporting node. A reporting node will subscribe to the stream on each ETL -source, but will only forward the messages from one of the streams at any given -time (to avoid sending the same message more than once to the same client). - -# API changes -A reporting node defaults to only returning validated data. If a ledger is not -specified, the most recently validated ledger is used. This is in contrast to -the normal rippled behavior, where the open ledger is used by default. - -Reporting will reject all subscribe requests for streams "server", "manifests", -"validations", "peer_status" and "consensus". - diff --git a/src/ripple/app/reporting/ReportingETL.cpp b/src/ripple/app/reporting/ReportingETL.cpp deleted file mode 100644 index d8d6af36881..00000000000 --- a/src/ripple/app/reporting/ReportingETL.cpp +++ /dev/null @@ -1,960 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -namespace detail { -/// Convenience function for printing out basic ledger info -std::string -toString(LedgerInfo const& info) -{ - std::stringstream ss; - ss << "LedgerInfo { Sequence : " << info.seq - << " Hash : " << strHex(info.hash) << " TxHash : " << strHex(info.txHash) - << " AccountHash : " << strHex(info.accountHash) - << " ParentHash : " << strHex(info.parentHash) << " }"; - return ss.str(); -} -} // namespace detail - -void -ReportingETL::consumeLedgerData( - std::shared_ptr& ledger, - ThreadSafeQueue>& writeQueue) -{ - std::shared_ptr sle; - size_t num = 0; - while (!stopping_ && (sle = writeQueue.pop())) - { - assert(sle); - if (!ledger->exists(sle->key())) - ledger->rawInsert(sle); - - if (flushInterval_ != 0 && (num % flushInterval_) == 0) - { - JLOG(journal_.debug()) << "Flushing! key = " << strHex(sle->key()); - ledger->stateMap().flushDirty(hotACCOUNT_NODE); - } - ++num; - } -} - -std::vector -ReportingETL::insertTransactions( - std::shared_ptr& ledger, - org::xrpl::rpc::v1::GetLedgerResponse& data) -{ - std::vector accountTxData; - for (auto& txn : data.transactions_list().transactions()) - { - auto& raw = txn.transaction_blob(); - - SerialIter it{raw.data(), raw.size()}; - STTx sttx{it}; - - auto txSerializer = std::make_shared(sttx.getSerializer()); - - TxMeta txMeta{ - sttx.getTransactionID(), ledger->info().seq, txn.metadata_blob()}; - - auto metaSerializer = - std::make_shared(txMeta.getAsObject().getSerializer()); - - JLOG(journal_.trace()) - << __func__ << " : " - << "Inserting transaction = " << sttx.getTransactionID(); - uint256 nodestoreHash = ledger->rawTxInsertWithHash( - sttx.getTransactionID(), txSerializer, metaSerializer); - accountTxData.emplace_back(txMeta, std::move(nodestoreHash), journal_); - } - return accountTxData; -} - -std::shared_ptr -ReportingETL::loadInitialLedger(uint32_t startingSequence) -{ - // check that database is actually empty - auto ledger = std::const_pointer_cast( - app_.getLedgerMaster().getValidatedLedger()); - if (ledger) - { - JLOG(journal_.fatal()) << __func__ << " : " - << "Database is not empty"; - assert(false); - return {}; - } - - // fetch the ledger from the network. This function will not return until - // either the fetch is successful, or the server is being shutdown. This - // only fetches the ledger header and the transactions+metadata - std::optional ledgerData{ - fetchLedgerData(startingSequence)}; - if (!ledgerData) - return {}; - - LedgerInfo lgrInfo = - deserializeHeader(makeSlice(ledgerData->ledger_header()), true); - - JLOG(journal_.debug()) << __func__ << " : " - << "Deserialized ledger header. " - << detail::toString(lgrInfo); - - ledger = - std::make_shared(lgrInfo, app_.config(), app_.getNodeFamily()); - ledger->stateMap().clearSynching(); - ledger->txMap().clearSynching(); - -#ifdef RIPPLED_REPORTING - std::vector accountTxData = - insertTransactions(ledger, *ledgerData); -#endif - - auto start = std::chrono::system_clock::now(); - - ThreadSafeQueue> writeQueue; - std::thread asyncWriter{[this, &ledger, &writeQueue]() { - consumeLedgerData(ledger, writeQueue); - }}; - - // download the full account state map. This function downloads full ledger - // data and pushes the downloaded data into the writeQueue. asyncWriter - // consumes from the queue and inserts the data into the Ledger object. - // Once the below call returns, all data has been pushed into the queue - loadBalancer_.loadInitialLedger(startingSequence, writeQueue); - - // null is used to represent the end of the queue - std::shared_ptr null; - writeQueue.push(null); - // wait for the writer to finish - asyncWriter.join(); - - if (!stopping_) - { - flushLedger(ledger); - if (app_.config().reporting()) - { -#ifdef RIPPLED_REPORTING - dynamic_cast(&app_.getRelationalDatabase()) - ->writeLedgerAndTransactions(ledger->info(), accountTxData); -#endif - } - } - auto end = std::chrono::system_clock::now(); - JLOG(journal_.debug()) << "Time to download and store ledger = " - << ((end - start).count()) / 1000000000.0; - return ledger; -} - -void -ReportingETL::flushLedger(std::shared_ptr& ledger) -{ - JLOG(journal_.debug()) << __func__ << " : " - << "Flushing ledger. " - << detail::toString(ledger->info()); - // These are recomputed in setImmutable - auto& accountHash = ledger->info().accountHash; - auto& txHash = ledger->info().txHash; - auto& ledgerHash = ledger->info().hash; - - assert( - ledger->info().seq < XRP_LEDGER_EARLIEST_FEES || - ledger->read(keylet::fees())); - ledger->setImmutable(false); - auto start = std::chrono::system_clock::now(); - - auto numFlushed = ledger->stateMap().flushDirty(hotACCOUNT_NODE); - - auto numTxFlushed = ledger->txMap().flushDirty(hotTRANSACTION_NODE); - - { - Serializer s(128); - s.add32(HashPrefix::ledgerMaster); - addRaw(ledger->info(), s); - app_.getNodeStore().store( - hotLEDGER, - std::move(s.modData()), - ledger->info().hash, - ledger->info().seq); - } - - app_.getNodeStore().sync(); - - auto end = std::chrono::system_clock::now(); - - JLOG(journal_.debug()) << __func__ << " : " - << "Flushed " << numFlushed - << " nodes to nodestore from stateMap"; - JLOG(journal_.debug()) << __func__ << " : " - << "Flushed " << numTxFlushed - << " nodes to nodestore from txMap"; - - JLOG(journal_.debug()) << __func__ << " : " - << "Flush took " - << (end - start).count() / 1000000000.0 - << " seconds"; - - if (numFlushed == 0) - { - JLOG(journal_.fatal()) << __func__ << " : " - << "Flushed 0 nodes from state map"; - assert(false); - } - if (numTxFlushed == 0) - { - JLOG(journal_.warn()) << __func__ << " : " - << "Flushed 0 nodes from tx map"; - } - - // Make sure calculated hashes are correct - if (ledger->stateMap().getHash().as_uint256() != accountHash) - { - JLOG(journal_.fatal()) - << __func__ << " : " - << "State map hash does not match. " - << "Expected hash = " << strHex(accountHash) << "Actual hash = " - << strHex(ledger->stateMap().getHash().as_uint256()); - Throw("state map hash mismatch"); - } - - if (ledger->txMap().getHash().as_uint256() != txHash) - { - JLOG(journal_.fatal()) - << __func__ << " : " - << "Tx map hash does not match. " - << "Expected hash = " << strHex(txHash) << "Actual hash = " - << strHex(ledger->txMap().getHash().as_uint256()); - Throw("tx map hash mismatch"); - } - - if (ledger->info().hash != ledgerHash) - { - JLOG(journal_.fatal()) - << __func__ << " : " - << "Ledger hash does not match. " - << "Expected hash = " << strHex(ledgerHash) - << "Actual hash = " << strHex(ledger->info().hash); - Throw("ledger hash mismatch"); - } - - JLOG(journal_.info()) << __func__ << " : " - << "Successfully flushed ledger! " - << detail::toString(ledger->info()); -} - -void -ReportingETL::publishLedger(std::shared_ptr& ledger) -{ - app_.getOPs().pubLedger(ledger); - - setLastPublish(); -} - -bool -ReportingETL::publishLedger(uint32_t ledgerSequence, uint32_t maxAttempts) -{ - JLOG(journal_.info()) << __func__ << " : " - << "Attempting to publish ledger = " - << ledgerSequence; - size_t numAttempts = 0; - while (!stopping_) - { - auto ledger = app_.getLedgerMaster().getLedgerBySeq(ledgerSequence); - - if (!ledger) - { - JLOG(journal_.warn()) - << __func__ << " : " - << "Trying to publish. Could not find ledger with sequence = " - << ledgerSequence; - // We try maxAttempts times to publish the ledger, waiting one - // second in between each attempt. - // If the ledger is not present in the database after maxAttempts, - // we attempt to take over as the writer. If the takeover fails, - // doContinuousETL will return, and this node will go back to - // publishing. - // If the node is in strict read only mode, we simply - // skip publishing this ledger and return false indicating the - // publish failed - if (numAttempts >= maxAttempts) - { - JLOG(journal_.error()) << __func__ << " : " - << "Failed to publish ledger after " - << numAttempts << " attempts."; - if (!readOnly_) - { - JLOG(journal_.info()) << __func__ << " : " - << "Attempting to become ETL writer"; - return false; - } - else - { - JLOG(journal_.debug()) - << __func__ << " : " - << "In strict read-only mode. " - << "Skipping publishing this ledger. " - << "Beginning fast forward."; - return false; - } - } - else - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - ++numAttempts; - } - continue; - } - - publishStrand_.post([this, ledger, fname = __func__]() { - app_.getOPs().pubLedger(ledger); - setLastPublish(); - JLOG(journal_.info()) - << fname << " : " - << "Published ledger. " << detail::toString(ledger->info()); - }); - return true; - } - return false; -} - -std::optional -ReportingETL::fetchLedgerData(uint32_t idx) -{ - JLOG(journal_.debug()) << __func__ << " : " - << "Attempting to fetch ledger with sequence = " - << idx; - - std::optional response = - loadBalancer_.fetchLedger(idx, false); - JLOG(journal_.trace()) << __func__ << " : " - << "GetLedger reply = " << response->DebugString(); - return response; -} - -std::optional -ReportingETL::fetchLedgerDataAndDiff(uint32_t idx) -{ - JLOG(journal_.debug()) << __func__ << " : " - << "Attempting to fetch ledger with sequence = " - << idx; - - std::optional response = - loadBalancer_.fetchLedger(idx, true); - JLOG(journal_.trace()) << __func__ << " : " - << "GetLedger reply = " << response->DebugString(); - return response; -} - -std::pair, std::vector> -ReportingETL::buildNextLedger( - std::shared_ptr& next, - org::xrpl::rpc::v1::GetLedgerResponse& rawData) -{ - JLOG(journal_.info()) << __func__ << " : " - << "Beginning ledger update"; - - LedgerInfo lgrInfo = - deserializeHeader(makeSlice(rawData.ledger_header()), true); - - JLOG(journal_.debug()) << __func__ << " : " - << "Deserialized ledger header. " - << detail::toString(lgrInfo); - - next->setLedgerInfo(lgrInfo); - - next->stateMap().clearSynching(); - next->txMap().clearSynching(); - - std::vector accountTxData{ - insertTransactions(next, rawData)}; - - JLOG(journal_.debug()) - << __func__ << " : " - << "Inserted all transactions. Number of transactions = " - << rawData.transactions_list().transactions_size(); - - for (auto& obj : rawData.ledger_objects().objects()) - { - auto key = uint256::fromVoidChecked(obj.key()); - if (!key) - throw std::runtime_error("Recevied malformed object ID"); - - auto& data = obj.data(); - - // indicates object was deleted - if (data.size() == 0) - { - JLOG(journal_.trace()) << __func__ << " : " - << "Erasing object = " << *key; - if (next->exists(*key)) - next->rawErase(*key); - } - else - { - SerialIter it{data.data(), data.size()}; - std::shared_ptr sle = std::make_shared(it, *key); - - if (next->exists(*key)) - { - JLOG(journal_.trace()) << __func__ << " : " - << "Replacing object = " << *key; - next->rawReplace(sle); - } - else - { - JLOG(journal_.trace()) << __func__ << " : " - << "Inserting object = " << *key; - next->rawInsert(sle); - } - } - } - JLOG(journal_.debug()) - << __func__ << " : " - << "Inserted/modified/deleted all objects. Number of objects = " - << rawData.ledger_objects().objects_size(); - - if (!rawData.skiplist_included()) - { - next->updateSkipList(); - JLOG(journal_.warn()) - << __func__ << " : " - << "tx process is not sending skiplist. This indicates that the tx " - "process is parsing metadata instead of doing a SHAMap diff. " - "Make sure tx process is running the same code as reporting to " - "use SHAMap diff instead of parsing metadata"; - } - - JLOG(journal_.debug()) << __func__ << " : " - << "Finished ledger update. " - << detail::toString(next->info()); - return {std::move(next), std::move(accountTxData)}; -} - -// Database must be populated when this starts -std::optional -ReportingETL::runETLPipeline(uint32_t startSequence) -{ - /* - * Behold, mortals! This function spawns three separate threads, which talk - * to each other via 2 different thread safe queues and 1 atomic variable. - * All threads and queues are function local. This function returns when all - * of the threads exit. There are two termination conditions: the first is - * if the load thread encounters a write conflict. In this case, the load - * thread sets writeConflict, an atomic bool, to true, which signals the - * other threads to stop. The second termination condition is when the - * entire server is shutting down, which is detected in one of three ways: - * 1. isStopping() returns true if the server is shutting down - * 2. networkValidatedLedgers_.waitUntilValidatedByNetwork returns - * false, signaling the wait was aborted. - * 3. fetchLedgerDataAndDiff returns an empty optional, signaling the fetch - * was aborted. - * In all cases, the extract thread detects this condition, - * and pushes an empty optional onto the transform queue. The transform - * thread, upon popping an empty optional, pushes an empty optional onto the - * load queue, and then returns. The load thread, upon popping an empty - * optional, returns. - */ - - JLOG(journal_.debug()) << __func__ << " : " - << "Starting etl pipeline"; - writing_ = true; - - std::shared_ptr parent = std::const_pointer_cast( - app_.getLedgerMaster().getLedgerBySeq(startSequence - 1)); - if (!parent) - { - assert(false); - Throw("runETLPipeline: parent ledger is null"); - } - - std::atomic_bool writeConflict = false; - std::optional lastPublishedSequence; - constexpr uint32_t maxQueueSize = 1000; - - ThreadSafeQueue> - transformQueue{maxQueueSize}; - - std::thread extracter{[this, - &startSequence, - &writeConflict, - &transformQueue]() { - beast::setCurrentThreadName("rippled: ReportingETL extract"); - uint32_t currentSequence = startSequence; - - // there are two stopping conditions here. - // First, if there is a write conflict in the load thread, the ETL - // mechanism should stop. - // The other stopping condition is if the entire server is shutting - // down. This can be detected in a variety of ways. See the comment - // at the top of the function - while (networkValidatedLedgers_.waitUntilValidatedByNetwork( - currentSequence) && - !writeConflict && !isStopping()) - { - auto start = std::chrono::system_clock::now(); - std::optional fetchResponse{ - fetchLedgerDataAndDiff(currentSequence)}; - // if the fetch is unsuccessful, stop. fetchLedger only returns - // false if the server is shutting down, or if the ledger was - // found in the database (which means another process already - // wrote the ledger that this process was trying to extract; - // this is a form of a write conflict). Otherwise, - // fetchLedgerDataAndDiff will keep trying to fetch the - // specified ledger until successful - if (!fetchResponse) - { - break; - } - auto end = std::chrono::system_clock::now(); - - auto time = ((end - start).count()) / 1000000000.0; - auto tps = - fetchResponse->transactions_list().transactions_size() / time; - - JLOG(journal_.debug()) << "Extract phase time = " << time - << " . Extract phase tps = " << tps; - - transformQueue.push(std::move(fetchResponse)); - ++currentSequence; - } - // empty optional tells the transformer to shut down - transformQueue.push({}); - }}; - - ThreadSafeQueue, - std::vector>>> - loadQueue{maxQueueSize}; - std::thread transformer{[this, - &parent, - &writeConflict, - &loadQueue, - &transformQueue]() { - beast::setCurrentThreadName("rippled: ReportingETL transform"); - - assert(parent); - parent = std::make_shared(*parent, NetClock::time_point{}); - while (!writeConflict) - { - std::optional fetchResponse{ - transformQueue.pop()}; - // if fetchResponse is an empty optional, the extracter thread has - // stopped and the transformer should stop as well - if (!fetchResponse) - { - break; - } - if (isStopping()) - continue; - - auto start = std::chrono::system_clock::now(); - auto [next, accountTxData] = - buildNextLedger(parent, *fetchResponse); - auto end = std::chrono::system_clock::now(); - - auto duration = ((end - start).count()) / 1000000000.0; - JLOG(journal_.debug()) << "transform time = " << duration; - // The below line needs to execute before pushing to the queue, in - // order to prevent this thread and the loader thread from accessing - // the same SHAMap concurrently - parent = std::make_shared(*next, NetClock::time_point{}); - loadQueue.push( - std::make_pair(std::move(next), std::move(accountTxData))); - } - // empty optional tells the loader to shutdown - loadQueue.push({}); - }}; - - std::thread loader{[this, - &lastPublishedSequence, - &loadQueue, - &writeConflict]() { - beast::setCurrentThreadName("rippled: ReportingETL load"); - size_t totalTransactions = 0; - double totalTime = 0; - while (!writeConflict) - { - std::optional, - std::vector>> - result{loadQueue.pop()}; - // if result is an empty optional, the transformer thread has - // stopped and the loader should stop as well - if (!result) - break; - if (isStopping()) - continue; - - auto& ledger = result->first; - auto& accountTxData = result->second; - - auto start = std::chrono::system_clock::now(); - // write to the key-value store - flushLedger(ledger); - - auto mid = std::chrono::system_clock::now(); - // write to RDBMS - // if there is a write conflict, some other process has already - // written this ledger and has taken over as the ETL writer -#ifdef RIPPLED_REPORTING - if (!dynamic_cast(&app_.getRelationalDatabase()) - ->writeLedgerAndTransactions( - ledger->info(), accountTxData)) - writeConflict = true; -#endif - auto end = std::chrono::system_clock::now(); - - if (!writeConflict) - { - publishLedger(ledger); - lastPublishedSequence = ledger->info().seq; - } - // print some performance numbers - auto kvTime = ((mid - start).count()) / 1000000000.0; - auto relationalTime = ((end - mid).count()) / 1000000000.0; - - size_t numTxns = accountTxData.size(); - totalTime += kvTime; - totalTransactions += numTxns; - JLOG(journal_.info()) - << "Load phase of etl : " - << "Successfully published ledger! Ledger info: " - << detail::toString(ledger->info()) - << ". txn count = " << numTxns - << ". key-value write time = " << kvTime - << ". relational write time = " << relationalTime - << ". key-value tps = " << numTxns / kvTime - << ". relational tps = " << numTxns / relationalTime - << ". total key-value tps = " << totalTransactions / totalTime; - } - }}; - - // wait for all of the threads to stop - loader.join(); - extracter.join(); - transformer.join(); - writing_ = false; - - JLOG(journal_.debug()) << __func__ << " : " - << "Stopping etl pipeline"; - - return lastPublishedSequence; -} - -// main loop. The software begins monitoring the ledgers that are validated -// by the nework. The member networkValidatedLedgers_ keeps track of the -// sequences of ledgers validated by the network. Whenever a ledger is validated -// by the network, the software looks for that ledger in the database. Once the -// ledger is found in the database, the software publishes that ledger to the -// ledgers stream. If a network validated ledger is not found in the database -// after a certain amount of time, then the software attempts to take over -// responsibility of the ETL process, where it writes new ledgers to the -// database. The software will relinquish control of the ETL process if it -// detects that another process has taken over ETL. -void -ReportingETL::monitor() -{ - auto ledger = std::const_pointer_cast( - app_.getLedgerMaster().getValidatedLedger()); - if (!ledger) - { - JLOG(journal_.info()) << __func__ << " : " - << "Database is empty. Will download a ledger " - "from the network."; - if (startSequence_) - { - JLOG(journal_.info()) - << __func__ << " : " - << "ledger sequence specified in config. " - << "Will begin ETL process starting with ledger " - << *startSequence_; - ledger = loadInitialLedger(*startSequence_); - } - else - { - JLOG(journal_.info()) - << __func__ << " : " - << "Waiting for next ledger to be validated by network..."; - std::optional mostRecentValidated = - networkValidatedLedgers_.getMostRecent(); - if (mostRecentValidated) - { - JLOG(journal_.info()) << __func__ << " : " - << "Ledger " << *mostRecentValidated - << " has been validated. " - << "Downloading..."; - ledger = loadInitialLedger(*mostRecentValidated); - } - else - { - JLOG(journal_.info()) << __func__ << " : " - << "The wait for the next validated " - << "ledger has been aborted. " - << "Exiting monitor loop"; - return; - } - } - } - else - { - if (startSequence_) - { - Throw( - "start sequence specified but db is already populated"); - } - JLOG(journal_.info()) - << __func__ << " : " - << "Database already populated. Picking up from the tip of history"; - } - if (!ledger) - { - JLOG(journal_.error()) - << __func__ << " : " - << "Failed to load initial ledger. Exiting monitor loop"; - return; - } - else - { - publishLedger(ledger); - } - uint32_t nextSequence = ledger->info().seq + 1; - - JLOG(journal_.debug()) << __func__ << " : " - << "Database is populated. " - << "Starting monitor loop. sequence = " - << nextSequence; - while (!stopping_ && - networkValidatedLedgers_.waitUntilValidatedByNetwork(nextSequence)) - { - JLOG(journal_.info()) << __func__ << " : " - << "Ledger with sequence = " << nextSequence - << " has been validated by the network. " - << "Attempting to find in database and publish"; - // Attempt to take over responsibility of ETL writer after 10 failed - // attempts to publish the ledger. publishLedger() fails if the - // ledger that has been validated by the network is not found in the - // database after the specified number of attempts. publishLedger() - // waits one second between each attempt to read the ledger from the - // database - // - // In strict read-only mode, when the software fails to find a - // ledger in the database that has been validated by the network, - // the software will only try to publish subsequent ledgers once, - // until one of those ledgers is found in the database. Once the - // software successfully publishes a ledger, the software will fall - // back to the normal behavior of trying several times to publish - // the ledger that has been validated by the network. In this - // manner, a reporting processing running in read-only mode does not - // need to restart if the database is wiped. - constexpr size_t timeoutSeconds = 10; - bool success = publishLedger(nextSequence, timeoutSeconds); - if (!success) - { - JLOG(journal_.warn()) - << __func__ << " : " - << "Failed to publish ledger with sequence = " << nextSequence - << " . Beginning ETL"; - // doContinousETLPipelined returns the most recent sequence - // published empty optional if no sequence was published - std::optional lastPublished = - runETLPipeline(nextSequence); - JLOG(journal_.info()) << __func__ << " : " - << "Aborting ETL. Falling back to publishing"; - // if no ledger was published, don't increment nextSequence - if (lastPublished) - nextSequence = *lastPublished + 1; - } - else - { - ++nextSequence; - } - } -} - -void -ReportingETL::monitorReadOnly() -{ - JLOG(journal_.debug()) << "Starting reporting in strict read only mode"; - std::optional mostRecent = - networkValidatedLedgers_.getMostRecent(); - if (!mostRecent) - return; - uint32_t sequence = *mostRecent; - bool success = true; - while (!stopping_ && - networkValidatedLedgers_.waitUntilValidatedByNetwork(sequence)) - { - success = publishLedger(sequence, success ? 30 : 1); - ++sequence; - } -} - -void -ReportingETL::doWork() -{ - worker_ = std::thread([this]() { - beast::setCurrentThreadName("rippled: ReportingETL worker"); - if (readOnly_) - monitorReadOnly(); - else - monitor(); - }); -} - -ReportingETL::ReportingETL(Application& app) - : app_(app) - , journal_(app.journal("ReportingETL")) - , publishStrand_(app_.getIOService()) - , loadBalancer_(*this) -{ - // if present, get endpoint from config - if (app_.config().exists("reporting")) - { -#ifndef RIPPLED_REPORTING - Throw( - "Config file specifies reporting, but software was not built with " - "-Dreporting=1. To use reporting, configure CMake with " - "-Dreporting=1"); -#endif - if (!app_.config().useTxTables()) - Throw( - "Reporting requires tx tables. Set use_tx_tables=1 in config " - "file, under [ledger_tx_tables] section"); - Section section = app_.config().section("reporting"); - - JLOG(journal_.debug()) << "Parsing config info"; - - auto& vals = section.values(); - for (auto& v : vals) - { - JLOG(journal_.debug()) << "val is " << v; - Section source = app_.config().section(v); - - auto optIp = source.get("source_ip"); - if (!optIp) - continue; - - auto optWsPort = source.get("source_ws_port"); - if (!optWsPort) - continue; - - auto optGrpcPort = source.get("source_grpc_port"); - if (!optGrpcPort) - { - // add source without grpc port - // used in read-only mode to detect when new ledgers have - // been validated. Used for publishing - if (app_.config().reportingReadOnly()) - loadBalancer_.add(*optIp, *optWsPort); - continue; - } - - loadBalancer_.add(*optIp, *optWsPort, *optGrpcPort); - } - - // this is true iff --reportingReadOnly was passed via command line - readOnly_ = app_.config().reportingReadOnly(); - - // if --reportingReadOnly was not passed via command line, check config - // file. Command line takes precedence - if (!readOnly_) - { - auto const optRO = section.get("read_only"); - if (optRO) - { - readOnly_ = (*optRO == "true" || *optRO == "1"); - app_.config().setReportingReadOnly(readOnly_); - } - } - - // lambda throws a useful message if string to integer conversion fails - auto asciiToIntThrows = - [](auto& dest, std::string const& src, char const* onError) { - char const* const srcEnd = src.data() + src.size(); - auto [ptr, err] = std::from_chars(src.data(), srcEnd, dest); - - if (err == std::errc()) - // skip whitespace at end of string - while (ptr != srcEnd && - std::isspace(static_cast(*ptr))) - ++ptr; - - // throw if - // o conversion error or - // o entire string is not consumed - if (err != std::errc() || ptr != srcEnd) - Throw(onError + src); - }; - - // handle command line arguments - if (app_.config().START_UP == Config::StartUpType::FRESH && !readOnly_) - { - asciiToIntThrows( - *startSequence_, - app_.config().START_LEDGER, - "Expected integral START_LEDGER command line argument. Got: "); - } - // if not passed via command line, check config for start sequence - if (!startSequence_) - { - auto const optStartSeq = section.get("start_sequence"); - if (optStartSeq) - { - // set a value so we can dereference - startSequence_ = 0; - asciiToIntThrows( - *startSequence_, - *optStartSeq, - "Expected integral start_sequence config entry. Got: "); - } - } - - auto const optFlushInterval = section.get("flush_interval"); - if (optFlushInterval) - asciiToIntThrows( - flushInterval_, - *optFlushInterval, - "Expected integral flush_interval config entry. Got: "); - - auto const optNumMarkers = section.get("num_markers"); - if (optNumMarkers) - asciiToIntThrows( - numMarkers_, - *optNumMarkers, - "Expected integral num_markers config entry. Got: "); - } -} - -} // namespace ripple diff --git a/src/ripple/app/reporting/ReportingETL.h b/src/ripple/app/reporting/ReportingETL.h deleted file mode 100644 index 71e08adf1f3..00000000000 --- a/src/ripple/app/reporting/ReportingETL.h +++ /dev/null @@ -1,367 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_REPORTING_REPORTINGETL_H_INCLUDED -#define RIPPLE_APP_REPORTING_REPORTINGETL_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h" -#include - -#include -#include -#include - -#include -namespace ripple { - -using AccountTransactionsData = RelationalDatabase::AccountTransactionsData; - -/** - * This class is responsible for continuously extracting data from a - * p2p node, and writing that data to the databases. Usually, multiple different - * processes share access to the same network accessible databases, in which - * case only one such process is performing ETL and writing to the database. The - * other processes simply monitor the database for new ledgers, and publish - * those ledgers to the various subscription streams. If a monitoring process - * determines that the ETL writer has failed (no new ledgers written for some - * time), the process will attempt to become the ETL writer. If there are - * multiple monitoring processes that try to become the ETL writer at the same - * time, one will win out, and the others will fall back to - * monitoring/publishing. In this sense, this class dynamically transitions from - * monitoring to writing and from writing to monitoring, based on the activity - * of other processes running on different machines. - */ -class ReportingETL -{ -private: - Application& app_; - - beast::Journal journal_; - - std::thread worker_; - - /// Strand to ensure that ledgers are published in order. - /// If ETL is started far behind the network, ledgers will be written and - /// published very rapidly. Monitoring processes will publish ledgers as - /// they are written. However, to publish a ledger, the monitoring process - /// needs to read all of the transactions for that ledger from the database. - /// Reading the transactions from the database requires network calls, which - /// can be slow. It is imperative however that the monitoring processes keep - /// up with the writer, else the monitoring processes will not be able to - /// detect if the writer failed. Therefore, publishing each ledger (which - /// includes reading all of the transactions from the database) is done from - /// the application wide asio io_service, and a strand is used to ensure - /// ledgers are published in order - boost::asio::io_context::strand publishStrand_; - - /// Mechanism for communicating with ETL sources. ETLLoadBalancer wraps an - /// arbitrary number of ETL sources and load balances ETL requests across - /// those sources. - ETLLoadBalancer loadBalancer_; - - /// Mechanism for detecting when the network has validated a new ledger. - /// This class provides a way to wait for a specific ledger to be validated - NetworkValidatedLedgers networkValidatedLedgers_; - - /// Whether the software is stopping - std::atomic_bool stopping_ = false; - - /// Used to determine when to write to the database during the initial - /// ledger download. By default, the software downloads an entire ledger and - /// then writes to the database. If flushInterval_ is non-zero, the software - /// will write to the database as new ledger data (SHAMap leaf nodes) - /// arrives. It is not neccesarily more effient to write the data as it - /// arrives, as different SHAMap leaf nodes share the same SHAMap inner - /// nodes; flushing prematurely can result in the same SHAMap inner node - /// being written to the database more than once. It is recommended to use - /// the default value of 0 for this variable; however, different values can - /// be experimented with if better performance is desired. - size_t flushInterval_ = 0; - - /// This variable controls the number of GetLedgerData calls that will be - /// executed in parallel during the initial ledger download. GetLedgerData - /// allows clients to page through a ledger over many RPC calls. - /// GetLedgerData returns a marker that is used as an offset in a subsequent - /// call. If numMarkers_ is greater than 1, there will be multiple chains of - /// GetLedgerData calls iterating over different parts of the same ledger in - /// parallel. This can dramatically speed up the time to download the - /// initial ledger. However, a higher value for this member variable puts - /// more load on the ETL source. - size_t numMarkers_ = 2; - - /// Whether the process is in strict read-only mode. In strict read-only - /// mode, the process will never attempt to become the ETL writer, and will - /// only publish ledgers as they are written to the database. - bool readOnly_ = false; - - /// Whether the process is writing to the database. Used by server_info - std::atomic_bool writing_ = false; - - /// Ledger sequence to start ETL from. If this is empty, ETL will start from - /// the next ledger validated by the network. If this is set, and the - /// database is already populated, an error is thrown. - std::optional startSequence_; - - /// The time that the most recently published ledger was published. Used by - /// server_info - std::chrono::time_point lastPublish_; - - std::mutex publishTimeMtx_; - - std::chrono::time_point - getLastPublish() - { - std::unique_lock lck(publishTimeMtx_); - return lastPublish_; - } - - void - setLastPublish() - { - std::unique_lock lck(publishTimeMtx_); - lastPublish_ = std::chrono::system_clock::now(); - } - - /// Download a ledger with specified sequence in full, via GetLedgerData, - /// and write the data to the databases. This takes several minutes or - /// longer. - /// @param sequence the sequence of the ledger to download - /// @return The ledger downloaded, with a full transaction and account state - /// map - std::shared_ptr - loadInitialLedger(uint32_t sequence); - - /// Run ETL. Extracts ledgers and writes them to the database, until a write - /// conflict occurs (or the server shuts down). - /// @note database must already be populated when this function is called - /// @param startSequence the first ledger to extract - /// @return the last ledger written to the database, if any - std::optional - runETLPipeline(uint32_t startSequence); - - /// Monitor the network for newly validated ledgers. Also monitor the - /// database to see if any process is writing those ledgers. This function - /// is called when the application starts, and will only return when the - /// application is shutting down. If the software detects the database is - /// empty, this function will call loadInitialLedger(). If the software - /// detects ledgers are not being written, this function calls - /// runETLPipeline(). Otherwise, this function publishes ledgers as they are - /// written to the database. - void - monitor(); - - /// Monitor the database for newly written ledgers. - /// Similar to the monitor(), except this function will never call - /// runETLPipeline() or loadInitialLedger(). This function only publishes - /// ledgers as they are written to the database. - void - monitorReadOnly(); - - /// Extract data for a particular ledger from an ETL source. This function - /// continously tries to extract the specified ledger (using all available - /// ETL sources) until the extraction succeeds, or the server shuts down. - /// @param sequence sequence of the ledger to extract - /// @return ledger header and transaction+metadata blobs. Empty optional - /// if the server is shutting down - std::optional - fetchLedgerData(uint32_t sequence); - - /// Extract data for a particular ledger from an ETL source. This function - /// continously tries to extract the specified ledger (using all available - /// ETL sources) until the extraction succeeds, or the server shuts down. - /// @param sequence sequence of the ledger to extract - /// @return ledger header, transaction+metadata blobs, and all ledger - /// objects created, modified or deleted between this ledger and the parent. - /// Empty optional if the server is shutting down - std::optional - fetchLedgerDataAndDiff(uint32_t sequence); - - /// Insert all of the extracted transactions into the ledger - /// @param ledger ledger to insert transactions into - /// @param data data extracted from an ETL source - /// @return struct that contains the neccessary info to write to the - /// transctions and account_transactions tables in Postgres (mostly - /// transaction hashes, corresponding nodestore hashes and affected - /// accounts) - std::vector - insertTransactions( - std::shared_ptr& ledger, - org::xrpl::rpc::v1::GetLedgerResponse& data); - - /// Build the next ledger using the previous ledger and the extracted data. - /// This function calls insertTransactions() - /// @note rawData should be data that corresponds to the ledger immediately - /// following parent - /// @param parent the previous ledger - /// @param rawData data extracted from an ETL source - /// @return the newly built ledger and data to write to Postgres - std::pair, std::vector> - buildNextLedger( - std::shared_ptr& parent, - org::xrpl::rpc::v1::GetLedgerResponse& rawData); - - /// Write all new data to the key-value store - /// @param ledger ledger with new data to write - void - flushLedger(std::shared_ptr& ledger); - - /// Attempt to read the specified ledger from the database, and then publish - /// that ledger to the ledgers stream. - /// @param ledgerSequence the sequence of the ledger to publish - /// @param maxAttempts the number of times to attempt to read the ledger - /// from the database. 1 attempt per second - /// @return whether the ledger was found in the database and published - bool - publishLedger(uint32_t ledgerSequence, uint32_t maxAttempts = 10); - - /// Publish the passed in ledger - /// @param ledger the ledger to publish - void - publishLedger(std::shared_ptr& ledger); - - /// Consume data from a queue and insert that data into the ledger - /// This function will continue to pull from the queue until the queue - /// returns nullptr. This is used during the initial ledger download - /// @param ledger the ledger to insert data into - /// @param writeQueue the queue with extracted data - void - consumeLedgerData( - std::shared_ptr& ledger, - ThreadSafeQueue>& writeQueue); - -public: - explicit ReportingETL(Application& app); - - ~ReportingETL() - { - } - - NetworkValidatedLedgers& - getNetworkValidatedLedgers() - { - return networkValidatedLedgers_; - } - - bool - isStopping() const - { - return stopping_; - } - - /// Get the number of markers to use during the initial ledger download. - /// This is equivelent to the degree of parallelism during the initial - /// ledger download - /// @return the number of markers - uint32_t - getNumMarkers() - { - return numMarkers_; - } - - Application& - getApplication() - { - return app_; - } - - beast::Journal& - getJournal() - { - return journal_; - } - - Json::Value - getInfo() - { - Json::Value result(Json::objectValue); - - result["etl_sources"] = loadBalancer_.toJson(); - result["is_writer"] = writing_.load(); - auto last = getLastPublish(); - if (last.time_since_epoch().count() != 0) - result["last_publish_time"] = - to_string(std::chrono::floor( - getLastPublish())); - return result; - } - - /// start all of the necessary components and begin ETL - void - start() - { - JLOG(journal_.info()) << "Starting reporting etl"; - assert(app_.config().reporting()); - assert(app_.config().standalone()); - assert(app_.config().reportingReadOnly() == readOnly_); - - stopping_ = false; - - loadBalancer_.start(); - doWork(); - } - - void - stop() - { - JLOG(journal_.info()) << "onStop called"; - JLOG(journal_.debug()) << "Stopping Reporting ETL"; - stopping_ = true; - networkValidatedLedgers_.stop(); - loadBalancer_.stop(); - - JLOG(journal_.debug()) << "Stopped loadBalancer"; - if (worker_.joinable()) - worker_.join(); - - JLOG(journal_.debug()) << "Joined worker thread"; - } - - ETLLoadBalancer& - getETLLoadBalancer() - { - return loadBalancer_; - } - -private: - void - doWork(); -}; - -} // namespace ripple -#endif diff --git a/src/ripple/app/tx/impl/Clawback.cpp b/src/ripple/app/tx/impl/Clawback.cpp deleted file mode 100644 index 58546db5ca7..00000000000 --- a/src/ripple/app/tx/impl/Clawback.cpp +++ /dev/null @@ -1,143 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2023 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { - -NotTEC -Clawback::preflight(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureClawback)) - return temDISABLED; - - if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) - return ret; - - if (ctx.tx.getFlags() & tfClawbackMask) - return temINVALID_FLAG; - - AccountID const issuer = ctx.tx[sfAccount]; - STAmount const clawAmount = ctx.tx[sfAmount]; - - // The issuer field is used for the token holder instead - AccountID const& holder = clawAmount.getIssuer(); - - if (issuer == holder || isXRP(clawAmount) || clawAmount <= beast::zero) - return temBAD_AMOUNT; - - return preflight2(ctx); -} - -TER -Clawback::preclaim(PreclaimContext const& ctx) -{ - AccountID const issuer = ctx.tx[sfAccount]; - STAmount const clawAmount = ctx.tx[sfAmount]; - AccountID const& holder = clawAmount.getIssuer(); - - auto const sleIssuer = ctx.view.read(keylet::account(issuer)); - auto const sleHolder = ctx.view.read(keylet::account(holder)); - if (!sleIssuer || !sleHolder) - return terNO_ACCOUNT; - - if (sleHolder->isFieldPresent(sfAMMID)) - return tecAMM_ACCOUNT; - - std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags); - - // If AllowTrustLineClawback is not set or NoFreeze is set, return no - // permission - if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || - (issuerFlagsIn & lsfNoFreeze)) - return tecNO_PERMISSION; - - auto const sleRippleState = - ctx.view.read(keylet::line(holder, issuer, clawAmount.getCurrency())); - if (!sleRippleState) - return tecNO_LINE; - - STAmount const balance = (*sleRippleState)[sfBalance]; - - // If balance is positive, issuer must have higher address than holder - if (balance > beast::zero && issuer < holder) - return tecNO_PERMISSION; - - // If balance is negative, issuer must have lower address than holder - if (balance < beast::zero && issuer > holder) - return tecNO_PERMISSION; - - // At this point, we know that issuer and holder accounts - // are correct and a trustline exists between them. - // - // Must now explicitly check the balance to make sure - // available balance is non-zero. - // - // We can't directly check the balance of trustline because - // the available balance of a trustline is prone to new changes (eg. - // XLS-34). So we must use `accountHolds`. - if (accountHolds( - ctx.view, - holder, - clawAmount.getCurrency(), - issuer, - fhIGNORE_FREEZE, - ctx.j) <= beast::zero) - return tecINSUFFICIENT_FUNDS; - - return tesSUCCESS; -} - -TER -Clawback::doApply() -{ - AccountID const& issuer = account_; - STAmount clawAmount = ctx_.tx[sfAmount]; - AccountID const holder = clawAmount.getIssuer(); // cannot be reference - - // Replace the `issuer` field with issuer's account - clawAmount.setIssuer(issuer); - if (holder == issuer) - return tecINTERNAL; - - // Get the spendable balance. Must use `accountHolds`. - STAmount const spendableAmount = accountHolds( - view(), - holder, - clawAmount.getCurrency(), - clawAmount.getIssuer(), - fhIGNORE_FREEZE, - j_); - - return rippleCredit( - view(), - holder, - issuer, - std::min(spendableAmount, clawAmount), - true, - j_); -} - -} // namespace ripple diff --git a/src/ripple/app/tx/impl/DepositPreauth.cpp b/src/ripple/app/tx/impl/DepositPreauth.cpp deleted file mode 100644 index 7d99e63017a..00000000000 --- a/src/ripple/app/tx/impl/DepositPreauth.cpp +++ /dev/null @@ -1,203 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2018 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { - -NotTEC -DepositPreauth::preflight(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureDepositPreauth)) - return temDISABLED; - - if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) - return ret; - - auto& tx = ctx.tx; - auto& j = ctx.j; - - if (tx.getFlags() & tfUniversalMask) - { - JLOG(j.trace()) << "Malformed transaction: Invalid flags set."; - return temINVALID_FLAG; - } - - auto const optAuth = ctx.tx[~sfAuthorize]; - auto const optUnauth = ctx.tx[~sfUnauthorize]; - if (static_cast(optAuth) == static_cast(optUnauth)) - { - // Either both fields are present or neither field is present. In - // either case the transaction is malformed. - JLOG(j.trace()) - << "Malformed transaction: " - "Invalid Authorize and Unauthorize field combination."; - return temMALFORMED; - } - - // Make sure that the passed account is valid. - AccountID const target{optAuth ? *optAuth : *optUnauth}; - if (target == beast::zero) - { - JLOG(j.trace()) << "Malformed transaction: Authorized or Unauthorized " - "field zeroed."; - return temINVALID_ACCOUNT_ID; - } - - // An account may not preauthorize itself. - if (optAuth && (target == ctx.tx[sfAccount])) - { - JLOG(j.trace()) - << "Malformed transaction: Attempting to DepositPreauth self."; - return temCANNOT_PREAUTH_SELF; - } - - return preflight2(ctx); -} - -TER -DepositPreauth::preclaim(PreclaimContext const& ctx) -{ - // Determine which operation we're performing: authorizing or unauthorizing. - if (ctx.tx.isFieldPresent(sfAuthorize)) - { - // Verify that the Authorize account is present in the ledger. - AccountID const auth{ctx.tx[sfAuthorize]}; - if (!ctx.view.exists(keylet::account(auth))) - return tecNO_TARGET; - - // Verify that the Preauth entry they asked to add is not already - // in the ledger. - if (ctx.view.exists(keylet::depositPreauth(ctx.tx[sfAccount], auth))) - return tecDUPLICATE; - } - else - { - // Verify that the Preauth entry they asked to remove is in the ledger. - AccountID const unauth{ctx.tx[sfUnauthorize]}; - if (!ctx.view.exists(keylet::depositPreauth(ctx.tx[sfAccount], unauth))) - return tecNO_ENTRY; - } - return tesSUCCESS; -} - -TER -DepositPreauth::doApply() -{ - if (ctx_.tx.isFieldPresent(sfAuthorize)) - { - auto const sleOwner = view().peek(keylet::account(account_)); - if (!sleOwner) - return {tefINTERNAL}; - - // A preauth counts against the reserve of the issuing account, but we - // check the starting balance because we want to allow dipping into the - // reserve to pay fees. - { - STAmount const reserve{view().fees().accountReserve( - sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } - - // Preclaim already verified that the Preauth entry does not yet exist. - // Create and populate the Preauth entry. - AccountID const auth{ctx_.tx[sfAuthorize]}; - Keylet const preauthKeylet = keylet::depositPreauth(account_, auth); - auto slePreauth = std::make_shared(preauthKeylet); - - slePreauth->setAccountID(sfAccount, account_); - slePreauth->setAccountID(sfAuthorize, auth); - view().insert(slePreauth); - - auto viewJ = ctx_.app.journal("View"); - auto const page = view().dirInsert( - keylet::ownerDir(account_), - preauthKeylet, - describeOwnerDir(account_)); - - JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " - << to_string(preauthKeylet.key) << ": " - << (page ? "success" : "failure"); - - if (!page) - return tecDIR_FULL; - - slePreauth->setFieldU64(sfOwnerNode, *page); - - // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, viewJ); - } - else - { - auto const preauth = - keylet::depositPreauth(account_, ctx_.tx[sfUnauthorize]); - - return DepositPreauth::removeFromLedger( - ctx_.app, view(), preauth.key, j_); - } - return tesSUCCESS; -} - -TER -DepositPreauth::removeFromLedger( - Application& app, - ApplyView& view, - uint256 const& preauthIndex, - beast::Journal j) -{ - // Verify that the Preauth entry they asked to remove is - // in the ledger. - std::shared_ptr const slePreauth{ - view.peek(keylet::depositPreauth(preauthIndex))}; - if (!slePreauth) - { - JLOG(j.warn()) << "Selected DepositPreauth does not exist."; - return tecNO_ENTRY; - } - - AccountID const account{(*slePreauth)[sfAccount]}; - std::uint64_t const page{(*slePreauth)[sfOwnerNode]}; - if (!view.dirRemove(keylet::ownerDir(account), page, preauthIndex, false)) - { - JLOG(j.fatal()) << "Unable to delete DepositPreauth from owner."; - return tefBAD_LEDGER; - } - - // If we succeeded, update the DepositPreauth owner's reserve. - auto const sleOwner = view.peek(keylet::account(account)); - if (!sleOwner) - return tefINTERNAL; - - adjustOwnerCount(view, sleOwner, -1, app.journal("View")); - - // Remove DepositPreauth from ledger. - view.erase(slePreauth); - - return tesSUCCESS; -} - -} // namespace ripple diff --git a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp b/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp deleted file mode 100644 index 22eca2dffdd..00000000000 --- a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp +++ /dev/null @@ -1,287 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { - -NotTEC -NFTokenCreateOffer::preflight(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureNonFungibleTokensV1)) - return temDISABLED; - - if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) - return ret; - - auto const txFlags = ctx.tx.getFlags(); - bool const isSellOffer = txFlags & tfSellNFToken; - - if (txFlags & tfNFTokenCreateOfferMask) - return temINVALID_FLAG; - - auto const account = ctx.tx[sfAccount]; - auto const nftFlags = nft::getFlags(ctx.tx[sfNFTokenID]); - - { - STAmount const amount = ctx.tx[sfAmount]; - - if (amount.negative() && ctx.rules.enabled(fixNFTokenNegOffer)) - // An offer for a negative amount makes no sense. - return temBAD_AMOUNT; - - if (!isXRP(amount)) - { - if (nftFlags & nft::flagOnlyXRP) - return temBAD_AMOUNT; - - if (!amount) - return temBAD_AMOUNT; - } - - // If this is an offer to buy, you must offer something; if it's an - // offer to sell, you can ask for nothing. - if (!isSellOffer && !amount) - return temBAD_AMOUNT; - } - - if (auto exp = ctx.tx[~sfExpiration]; exp == 0) - return temBAD_EXPIRATION; - - auto const owner = ctx.tx[~sfOwner]; - - // The 'Owner' field must be present when offering to buy, but can't - // be present when selling (it's implicit): - if (owner.has_value() == isSellOffer) - return temMALFORMED; - - if (owner && owner == account) - return temMALFORMED; - - if (auto dest = ctx.tx[~sfDestination]) - { - // Some folks think it makes sense for a buy offer to specify a - // specific broker using the Destination field. This change doesn't - // deserve it's own amendment, so we're piggy-backing on - // fixNFTokenNegOffer. - // - // Prior to fixNFTokenNegOffer any use of the Destination field on - // a buy offer was malformed. - if (!isSellOffer && !ctx.rules.enabled(fixNFTokenNegOffer)) - return temMALFORMED; - - // The destination can't be the account executing the transaction. - if (dest == account) - return temMALFORMED; - } - - return preflight2(ctx); -} - -TER -NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) -{ - if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) - return tecEXPIRED; - - auto const nftokenID = ctx.tx[sfNFTokenID]; - bool const isSellOffer = ctx.tx.isFlag(tfSellNFToken); - - if (!nft::findToken( - ctx.view, ctx.tx[isSellOffer ? sfAccount : sfOwner], nftokenID)) - return tecNO_ENTRY; - - auto const nftFlags = nft::getFlags(nftokenID); - auto const issuer = nft::getIssuer(nftokenID); - auto const amount = ctx.tx[sfAmount]; - - if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() && - nft::getTransferFee(nftokenID)) - { - if (!ctx.view.exists(keylet::account(issuer))) - return tecNO_ISSUER; - - if (!ctx.view.exists(keylet::line(issuer, amount.issue()))) - return tecNO_LINE; - - if (isFrozen( - ctx.view, issuer, amount.getCurrency(), amount.getIssuer())) - return tecFROZEN; - } - - if (issuer != ctx.tx[sfAccount] && !(nftFlags & nft::flagTransferable)) - { - auto const root = ctx.view.read(keylet::account(issuer)); - assert(root); - - if (auto minter = (*root)[~sfNFTokenMinter]; - minter != ctx.tx[sfAccount]) - return tefNFTOKEN_IS_NOT_TRANSFERABLE; - } - - if (isFrozen( - ctx.view, - ctx.tx[sfAccount], - amount.getCurrency(), - amount.getIssuer())) - return tecFROZEN; - - // If this is an offer to buy the token, the account must have the - // needed funds at hand; but note that funds aren't reserved and the - // offer may later become unfunded. - if (!isSellOffer) - { - // After this amendment, we allow an IOU issuer to make a buy offer - // using their own currency. - if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2)) - { - if (accountFunds( - ctx.view, - ctx.tx[sfAccount], - amount, - FreezeHandling::fhZERO_IF_FROZEN, - ctx.j) - .signum() <= 0) - return tecUNFUNDED_OFFER; - } - else if ( - accountHolds( - ctx.view, - ctx.tx[sfAccount], - amount.getCurrency(), - amount.getIssuer(), - FreezeHandling::fhZERO_IF_FROZEN, - ctx.j) - .signum() <= 0) - return tecUNFUNDED_OFFER; - } - - if (auto const destination = ctx.tx[~sfDestination]) - { - // If a destination is specified, the destination must already be in - // the ledger. - auto const sleDst = ctx.view.read(keylet::account(*destination)); - - if (!sleDst) - return tecNO_DST; - - // check if the destination has disallowed incoming offers - if (ctx.view.rules().enabled(featureDisallowIncoming)) - { - // flag cannot be set unless amendment is enabled but - // out of an abundance of caution check anyway - - if (sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer) - return tecNO_PERMISSION; - } - } - - if (auto const owner = ctx.tx[~sfOwner]) - { - // Check if the owner (buy offer) has disallowed incoming offers - if (ctx.view.rules().enabled(featureDisallowIncoming)) - { - auto const sleOwner = ctx.view.read(keylet::account(*owner)); - - // defensively check - // it should not be possible to specify owner that doesn't exist - if (!sleOwner) - return tecNO_TARGET; - - if (sleOwner->getFlags() & lsfDisallowIncomingNFTokenOffer) - return tecNO_PERMISSION; - } - } - - return tesSUCCESS; -} - -TER -NFTokenCreateOffer::doApply() -{ - if (auto const acct = view().read(keylet::account(ctx_.tx[sfAccount])); - mPriorBalance < view().fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return tecINSUFFICIENT_RESERVE; - - auto const nftokenID = ctx_.tx[sfNFTokenID]; - - auto const offerID = - keylet::nftoffer(account_, ctx_.tx.getSeqProxy().value()); - - // Create the offer: - { - // Token offers are always added to the owner's owner directory: - auto const ownerNode = view().dirInsert( - keylet::ownerDir(account_), offerID, describeOwnerDir(account_)); - - if (!ownerNode) - return tecDIR_FULL; - - bool const isSellOffer = ctx_.tx.isFlag(tfSellNFToken); - - // Token offers are also added to the token's buy or sell offer - // directory - auto const offerNode = view().dirInsert( - isSellOffer ? keylet::nft_sells(nftokenID) - : keylet::nft_buys(nftokenID), - offerID, - [&nftokenID, isSellOffer](std::shared_ptr const& sle) { - (*sle)[sfFlags] = - isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers; - (*sle)[sfNFTokenID] = nftokenID; - }); - - if (!offerNode) - return tecDIR_FULL; - - std::uint32_t sleFlags = 0; - - if (isSellOffer) - sleFlags |= lsfSellNFToken; - - auto offer = std::make_shared(offerID); - (*offer)[sfOwner] = account_; - (*offer)[sfNFTokenID] = nftokenID; - (*offer)[sfAmount] = ctx_.tx[sfAmount]; - (*offer)[sfFlags] = sleFlags; - (*offer)[sfOwnerNode] = *ownerNode; - (*offer)[sfNFTokenOfferNode] = *offerNode; - - if (auto const expiration = ctx_.tx[~sfExpiration]) - (*offer)[sfExpiration] = *expiration; - - if (auto const destination = ctx_.tx[~sfDestination]) - (*offer)[sfDestination] = *destination; - - view().insert(offer); - } - - // Update owner count. - adjustOwnerCount(view(), view().peek(keylet::account(account_)), 1, j_); - - return tesSUCCESS; -} - -} // namespace ripple diff --git a/src/ripple/basics/ThreadSafetyAnalysis.h b/src/ripple/basics/ThreadSafetyAnalysis.h deleted file mode 100644 index b1889d5b4c6..00000000000 --- a/src/ripple/basics/ThreadSafetyAnalysis.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef RIPPLE_BASICS_THREAD_SAFTY_ANALYSIS_H_INCLUDED -#define RIPPLE_BASICS_THREAD_SAFTY_ANALYSIS_H_INCLUDED - -#ifdef RIPPLE_ENABLE_THREAD_SAFETY_ANNOTATIONS -#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) -#else -#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op -#endif - -#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) - -#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) - -#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) - -#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) - -#define ACQUIRED_BEFORE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) - -#define ACQUIRED_AFTER(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) - -#define REQUIRES(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) - -#define REQUIRES_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) - -#define ACQUIRE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) - -#define ACQUIRE_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) - -#define RELEASE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) - -#define RELEASE_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) - -#define RELEASE_GENERIC(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) - -#define TRY_ACQUIRE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) - -#define TRY_ACQUIRE_SHARED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) - -#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) - -#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) - -#define ASSERT_SHARED_CAPABILITY(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) - -#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) - -#define NO_THREAD_SAFETY_ANALYSIS \ - THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) - -#endif diff --git a/src/ripple/beast/test/fail_counter.h b/src/ripple/beast/test/fail_counter.h deleted file mode 100644 index 8a11602e22d..00000000000 --- a/src/ripple/beast/test/fail_counter.h +++ /dev/null @@ -1,160 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_FAIL_COUNTER_HPP -#define BEAST_TEST_FAIL_COUNTER_HPP - -#include -#include - -namespace beast { -namespace test { - -enum class error { fail_error = 1 }; - -namespace detail { - -class fail_error_category : public boost::system::error_category -{ -public: - const char* - name() const noexcept override - { - return "test"; - } - - std::string - message(int ev) const override - { - switch (static_cast(ev)) - { - default: - case error::fail_error: - return "test error"; - } - } - - boost::system::error_condition - default_error_condition(int ev) const noexcept override - { - return boost::system::error_condition{ev, *this}; - } - - bool - equivalent(int ev, boost::system::error_condition const& condition) - const noexcept override - { - return condition.value() == ev && &condition.category() == this; - } - - bool - equivalent(error_code const& error, int ev) const noexcept override - { - return error.value() == ev && &error.category() == this; - } -}; - -inline boost::system::error_category const& -get_error_category() -{ - static fail_error_category const cat{}; - return cat; -} - -} // namespace detail - -inline error_code -make_error_code(error ev) -{ - return error_code{ - static_cast::type>(ev), - detail::get_error_category()}; -} - -/** An error code with an error set on default construction - - Default constructed versions of this object will have - an error code set right away. This helps tests find code - which forgets to clear the error code on success. -*/ -struct fail_error_code : error_code -{ - fail_error_code() : error_code(make_error_code(error::fail_error)) - { - } - - template - fail_error_code(Arg0&& arg0, ArgN&&... argn) - : error_code(arg0, std::forward(argn)...) - { - } -}; - -/** A countdown to simulated failure. - - On the Nth operation, the class will fail with the specified - error code, or the default error code of @ref error::fail_error. -*/ -class fail_counter -{ - std::size_t n_; - error_code ec_; - -public: - fail_counter(fail_counter&&) = default; - - /** Construct a counter. - - @param The 0-based index of the operation to fail on or after. - */ - explicit fail_counter( - std::size_t n, - error_code ev = make_error_code(error::fail_error)) - : n_(n), ec_(ev) - { - } - - /// Throw an exception on the Nth failure - void - fail() - { - if (n_ > 0) - --n_; - if (!n_) - BOOST_THROW_EXCEPTION(system_error{ec_}); - } - - /// Set an error code on the Nth failure - bool - fail(error_code& ec) - { - if (n_ > 0) - --n_; - if (!n_) - { - ec = ec_; - return true; - } - ec.assign(0, ec.category()); - return false; - } -}; - -} // namespace test -} // namespace beast - -namespace boost { -namespace system { -template <> -struct is_error_code_enum -{ - static bool const value = true; -}; -} // namespace system -} // namespace boost - -#endif diff --git a/src/ripple/beast/test/fail_stream.h b/src/ripple/beast/test/fail_stream.h deleted file mode 100644 index a1465f006ca..00000000000 --- a/src/ripple/beast/test/fail_stream.h +++ /dev/null @@ -1,182 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_FAIL_STREAM_HPP -#define BEAST_TEST_FAIL_STREAM_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace test { - -/** A stream wrapper that fails. - - On the Nth operation, the stream will fail with the specified - error code, or the default error code of invalid_argument. -*/ -template -class fail_stream -{ - boost::optional fc_; - fail_counter* pfc_; - NextLayer next_layer_; - -public: - using next_layer_type = typename std::remove_reference::type; - - using lowest_layer_type = typename get_lowest_layer::type; - - fail_stream(fail_stream&&) = delete; - fail_stream(fail_stream const&) = delete; - fail_stream& - operator=(fail_stream&&) = delete; - fail_stream& - operator=(fail_stream const&) = delete; - - template - explicit fail_stream(std::size_t n, Args&&... args) - : fc_(n), pfc_(&*fc_), next_layer_(std::forward(args)...) - { - } - - template - explicit fail_stream(fail_counter& fc, Args&&... args) - : pfc_(&fc), next_layer_(std::forward(args)...) - { - } - - next_layer_type& - next_layer() - { - return next_layer_; - } - - lowest_layer_type& - lowest_layer() - { - return next_layer_.lowest_layer(); - } - - lowest_layer_type const& - lowest_layer() const - { - return next_layer_.lowest_layer(); - } - - boost::asio::io_service& - get_io_service() - { - return next_layer_.get_io_service(); - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - pfc_->fail(); - return next_layer_.read_some(buffers); - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers, error_code& ec) - { - if (pfc_->fail(ec)) - return 0; - return next_layer_.read_some(buffers, ec); - } - - template - async_return_type - async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) - { - error_code ec; - if (pfc_->fail(ec)) - { - async_completion init{ - handler}; - next_layer_.get_io_service().post( - bind_handler(init.completion_handler, ec, 0)); - return init.result.get(); - } - return next_layer_.async_read_some( - buffers, std::forward(handler)); - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - pfc_->fail(); - return next_layer_.write_some(buffers); - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers, error_code& ec) - { - if (pfc_->fail(ec)) - return 0; - return next_layer_.write_some(buffers, ec); - } - - template - async_return_type - async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) - { - error_code ec; - if (pfc_->fail(ec)) - { - async_completion init{ - handler}; - next_layer_.get_io_service().post( - bind_handler(init.completion_handler, ec, 0)); - return init.result.get(); - } - return next_layer_.async_write_some( - buffers, std::forward(handler)); - } - - friend void - teardown( - websocket::teardown_tag, - fail_stream& stream, - boost::system::error_code& ec) - { - if (stream.pfc_->fail(ec)) - return; - beast::websocket_helpers::call_teardown(stream.next_layer(), ec); - } - - template - friend void - async_teardown( - websocket::teardown_tag, - fail_stream& stream, - TeardownHandler&& handler) - { - error_code ec; - if (stream.pfc_->fail(ec)) - { - stream.get_io_service().post(bind_handler(std::move(handler), ec)); - return; - } - beast::websocket_helpers::call_async_teardown( - stream.next_layer(), std::forward(handler)); - } -}; - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/pipe_stream.h b/src/ripple/beast/test/pipe_stream.h deleted file mode 100644 index 1aaaadfa4ff..00000000000 --- a/src/ripple/beast/test/pipe_stream.h +++ /dev/null @@ -1,482 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_PIPE_STREAM_HPP -#define BEAST_TEST_PIPE_STREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace test { - -/** A bidirectional in-memory communication channel - - An instance of this class provides a client and server - endpoint that are automatically connected to each other - similarly to a connected socket. - - Test pipes are used to facilitate writing unit tests - where the behavior of the transport is tightly controlled - to help illuminate all code paths (for code coverage) -*/ -class pipe -{ -public: - using buffer_type = flat_buffer; - -private: - struct read_op - { - virtual ~read_op() = default; - virtual void - operator()() = 0; - }; - - struct state - { - std::mutex m; - buffer_type b; - std::condition_variable cv; - std::unique_ptr op; - bool eof = false; - }; - - state s_[2]; - -public: - /** Represents an endpoint. - - Each pipe has a client stream and a server stream. - */ - class stream - { - friend class pipe; - - template - class read_op_impl; - - state& in_; - state& out_; - boost::asio::io_service& ios_; - fail_counter* fc_ = nullptr; - std::size_t read_max_ = (std::numeric_limits::max)(); - std::size_t write_max_ = (std::numeric_limits::max)(); - - stream(state& in, state& out, boost::asio::io_service& ios) - : in_(in), out_(out), ios_(ios), buffer(in_.b) - { - } - - public: - using buffer_type = pipe::buffer_type; - - /// Direct access to the underlying buffer - buffer_type& buffer; - - /// Counts the number of read calls - std::size_t nread = 0; - - /// Counts the number of write calls - std::size_t nwrite = 0; - - ~stream() = default; - stream(stream&&) = default; - - /// Set the fail counter on the object - void - fail(fail_counter& fc) - { - fc_ = &fc; - } - - /// Return the `io_service` associated with the object - boost::asio::io_service& - get_io_service() - { - return ios_; - } - - /// Set the maximum number of bytes returned by read_some - void - read_size(std::size_t n) - { - read_max_ = n; - } - - /// Set the maximum number of bytes returned by write_some - void - write_size(std::size_t n) - { - write_max_ = n; - } - - /// Returns a string representing the pending input data - string_view - str() const - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - return { - buffer_cast(*in_.b.data().begin()), - buffer_size(*in_.b.data().begin())}; - } - - /// Clear the buffer holding the input data - void - clear() - { - in_.b.consume((std::numeric_limits::max)()); - } - - /** Close the stream. - - The other end of the pipe will see - `boost::asio::error::eof` on read. - */ - template - void - close(); - - template - std::size_t - read_some(MutableBufferSequence const& buffers); - - template - std::size_t - read_some(MutableBufferSequence const& buffers, error_code& ec); - - template - async_return_type - async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler); - - template - std::size_t - write_some(ConstBufferSequence const& buffers); - - template - std::size_t - write_some(ConstBufferSequence const& buffers, error_code&); - - template - async_return_type - async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler); - - friend void - teardown( - websocket::teardown_tag, - stream&, - boost::system::error_code& ec) - { - ec.assign(0, ec.category()); - } - - template - friend void - async_teardown( - websocket::teardown_tag, - stream& s, - TeardownHandler&& handler) - { - s.get_io_service().post( - bind_handler(std::move(handler), error_code{})); - } - }; - - /** Constructor. - - The client and server endpoints will use the same `io_service`. - */ - explicit pipe(boost::asio::io_service& ios) - : client(s_[0], s_[1], ios), server(s_[1], s_[0], ios) - { - } - - /** Constructor. - - The client and server endpoints will different `io_service` objects. - */ - explicit pipe(boost::asio::io_service& ios1, boost::asio::io_service& ios2) - : client(s_[0], s_[1], ios1), server(s_[1], s_[0], ios2) - { - } - - /// Represents the client endpoint - stream client; - - /// Represents the server endpoint - stream server; -}; - -//------------------------------------------------------------------------------ - -template -class pipe::stream::read_op_impl : public pipe::read_op -{ - stream& s_; - Buffers b_; - Handler h_; - -public: - read_op_impl(stream& s, Buffers const& b, Handler&& h) - : s_(s), b_(b), h_(std::move(h)) - { - } - - read_op_impl(stream& s, Buffers const& b, Handler const& h) - : s_(s), b_(b), h_(h) - { - } - - void - operator()() override; -}; - -//------------------------------------------------------------------------------ - -template -void -pipe::stream::read_op_impl::operator()() -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - s_.ios_.post([&]() { - BOOST_ASSERT(s_.in_.op); - std::unique_lock lock{s_.in_.m}; - if (s_.in_.b.size() > 0) - { - auto const bytes_transferred = - buffer_copy(b_, s_.in_.b.data(), s_.read_max_); - s_.in_.b.consume(bytes_transferred); - auto& s = s_; - Handler h{std::move(h_)}; - lock.unlock(); - s.in_.op.reset(nullptr); - ++s.nread; - s.ios_.post( - bind_handler(std::move(h), error_code{}, bytes_transferred)); - } - else - { - BOOST_ASSERT(s_.in_.eof); - auto& s = s_; - Handler h{std::move(h_)}; - lock.unlock(); - s.in_.op.reset(nullptr); - ++s.nread; - s.ios_.post(bind_handler(std::move(h), boost::asio::error::eof, 0)); - } - }); -} - -//------------------------------------------------------------------------------ - -template -void -pipe::stream::close() -{ - std::lock_guard lock{out_.m}; - out_.eof = true; - if (out_.op) - out_.op.get()->operator()(); - else - out_.cv.notify_all(); -} - -template -std::size_t -pipe::stream::read_some(MutableBufferSequence const& buffers) -{ - static_assert( - is_mutable_buffer_sequence::value, - "MutableBufferSequence requirements not met"); - error_code ec; - auto const n = read_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; -} - -template -std::size_t -pipe::stream::read_some(MutableBufferSequence const& buffers, error_code& ec) -{ - static_assert( - is_mutable_buffer_sequence::value, - "MutableBufferSequence requirements not met"); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - BOOST_ASSERT(!in_.op); - BOOST_ASSERT(buffer_size(buffers) > 0); - if (fc_ && fc_->fail(ec)) - return 0; - std::unique_lock lock{in_.m}; - in_.cv.wait(lock, [&]() { return in_.b.size() > 0 || in_.eof; }); - std::size_t bytes_transferred; - if (in_.b.size() > 0) - { - ec.assign(0, ec.category()); - bytes_transferred = buffer_copy(buffers, in_.b.data(), read_max_); - in_.b.consume(bytes_transferred); - } - else - { - BOOST_ASSERT(in_.eof); - bytes_transferred = 0; - ec = boost::asio::error::eof; - } - ++nread; - return bytes_transferred; -} - -template -async_return_type -pipe::stream::async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler) -{ - static_assert( - is_mutable_buffer_sequence::value, - "MutableBufferSequence requirements not met"); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - BOOST_ASSERT(!in_.op); - BOOST_ASSERT(buffer_size(buffers) > 0); - async_completion init{handler}; - if (fc_) - { - error_code ec; - if (fc_->fail(ec)) - return ios_.post(bind_handler(init.completion_handler, ec, 0)); - } - { - std::unique_lock lock{in_.m}; - if (in_.eof) - { - lock.unlock(); - ++nread; - ios_.post(bind_handler( - init.completion_handler, boost::asio::error::eof, 0)); - } - else if (buffer_size(buffers) == 0 || buffer_size(in_.b.data()) > 0) - { - auto const bytes_transferred = - buffer_copy(buffers, in_.b.data(), read_max_); - in_.b.consume(bytes_transferred); - lock.unlock(); - ++nread; - ios_.post(bind_handler( - init.completion_handler, error_code{}, bytes_transferred)); - } - else - { - in_.op.reset( - new read_op_impl< - handler_type, - MutableBufferSequence>{ - *this, buffers, init.completion_handler}); - } - } - return init.result.get(); -} - -template -std::size_t -pipe::stream::write_some(ConstBufferSequence const& buffers) -{ - static_assert( - is_const_buffer_sequence::value, - "ConstBufferSequence requirements not met"); - BOOST_ASSERT(!out_.eof); - error_code ec; - auto const bytes_transferred = write_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return bytes_transferred; -} - -template -std::size_t -pipe::stream::write_some(ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert( - is_const_buffer_sequence::value, - "ConstBufferSequence requirements not met"); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - BOOST_ASSERT(!out_.eof); - if (fc_ && fc_->fail(ec)) - return 0; - auto const n = (std::min)(buffer_size(buffers), write_max_); - std::unique_lock lock{out_.m}; - auto const bytes_transferred = buffer_copy(out_.b.prepare(n), buffers); - out_.b.commit(bytes_transferred); - lock.unlock(); - if (out_.op) - out_.op.get()->operator()(); - else - out_.cv.notify_all(); - ++nwrite; - ec.assign(0, ec.category()); - return bytes_transferred; -} - -template -async_return_type -pipe::stream::async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler) -{ - static_assert( - is_const_buffer_sequence::value, - "ConstBufferSequence requirements not met"); - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - BOOST_ASSERT(!out_.eof); - async_completion init{handler}; - if (fc_) - { - error_code ec; - if (fc_->fail(ec)) - return ios_.post(bind_handler(init.completion_handler, ec, 0)); - } - auto const n = (std::min)(buffer_size(buffers), write_max_); - std::unique_lock lock{out_.m}; - auto const bytes_transferred = buffer_copy(out_.b.prepare(n), buffers); - out_.b.commit(bytes_transferred); - lock.unlock(); - if (out_.op) - out_.op.get()->operator()(); - else - out_.cv.notify_all(); - ++nwrite; - ios_.post( - bind_handler(init.completion_handler, error_code{}, bytes_transferred)); - return init.result.get(); -} - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/sig_wait.h b/src/ripple/beast/test/sig_wait.h deleted file mode 100644 index 92720561fa6..00000000000 --- a/src/ripple/beast/test/sig_wait.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_SIG_WAIT_HPP -#define BEAST_TEST_SIG_WAIT_HPP - -#include - -namespace beast { -namespace test { - -/// Block until SIGINT or SIGTERM is received. -inline void -sig_wait() -{ - boost::asio::io_service ios; - boost::asio::signal_set signals(ios, SIGINT, SIGTERM); - signals.async_wait([&](boost::system::error_code const&, int) {}); - ios.run(); -} - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/string_iostream.h b/src/ripple/beast/test/string_iostream.h deleted file mode 100644 index 56f741be068..00000000000 --- a/src/ripple/beast/test/string_iostream.h +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_STRING_IOSTREAM_HPP -#define BEAST_TEST_STRING_IOSTREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace test { - -/** A SyncStream and AsyncStream that reads from a string and writes to another - string. - - This class behaves like a socket, except that written data is - appended to a string exposed as a public data member, and when - data is read it comes from a string provided at construction. -*/ -class string_iostream -{ - std::string s_; - boost::asio::const_buffer cb_; - boost::asio::io_service& ios_; - std::size_t read_max_; - -public: - std::string str; - - string_iostream( - boost::asio::io_service& ios, - std::string s, - std::size_t read_max = (std::numeric_limits::max)()) - : s_(std::move(s)) - , cb_(boost::asio::buffer(s_)) - , ios_(ios) - , read_max_(read_max) - { - } - - boost::asio::io_service& - get_io_service() - { - return ios_; - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - error_code ec; - auto const n = read_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers, error_code& ec) - { - auto const n = - boost::asio::buffer_copy(buffers, buffer_prefix(read_max_, cb_)); - if (n > 0) - { - ec.assign(0, ec.category()); - cb_ = cb_ + n; - } - else - { - ec = boost::asio::error::eof; - } - return n; - } - - template - async_return_type - async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) - { - auto const n = - boost::asio::buffer_copy(buffers, boost::asio::buffer(s_)); - error_code ec; - if (n > 0) - s_.erase(0, n); - else - ec = boost::asio::error::eof; - async_completion init{ - handler}; - ios_.post(bind_handler(init.completion_handler, ec, n)); - return init.result.get(); - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - error_code ec; - auto const n = write_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers, error_code& ec) - { - ec.assign(0, ec.category()); - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto const n = buffer_size(buffers); - str.reserve(str.size() + n); - for (boost::asio::const_buffer buffer : buffers) - str.append(buffer_cast(buffer), buffer_size(buffer)); - return n; - } - - template - async_return_type - async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) - { - error_code ec; - auto const bytes_transferred = write_some(buffers, ec); - async_completion init{ - handler}; - get_io_service().post( - bind_handler(init.completion_handler, ec, bytes_transferred)); - return init.result.get(); - } - - friend void - teardown( - websocket::teardown_tag, - string_iostream&, - boost::system::error_code& ec) - { - ec.assign(0, ec.category()); - } - - template - friend void - async_teardown( - websocket::teardown_tag, - string_iostream& stream, - TeardownHandler&& handler) - { - stream.get_io_service().post( - bind_handler(std::move(handler), error_code{})); - } -}; - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/string_istream.h b/src/ripple/beast/test/string_istream.h deleted file mode 100644 index 81f35469905..00000000000 --- a/src/ripple/beast/test/string_istream.h +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_STRING_ISTREAM_HPP -#define BEAST_TEST_STRING_ISTREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace test { - -/** A SyncStream and AsyncStream that reads from a string. - - This class behaves like a socket, except that written data is simply - discarded, and when data is read it comes from a string provided - at construction. -*/ -class string_istream -{ - std::string s_; - boost::asio::const_buffer cb_; - boost::asio::io_service& ios_; - std::size_t read_max_; - -public: - string_istream( - boost::asio::io_service& ios, - std::string s, - std::size_t read_max = (std::numeric_limits::max)()) - : s_(std::move(s)) - , cb_(boost::asio::buffer(s_)) - , ios_(ios) - , read_max_(read_max) - { - } - - boost::asio::io_service& - get_io_service() - { - return ios_; - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - error_code ec; - auto const n = read_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers, error_code& ec) - { - auto const n = boost::asio::buffer_copy(buffers, cb_, read_max_); - if (n > 0) - { - ec.assign(0, ec.category()); - cb_ = cb_ + n; - } - else - { - ec = boost::asio::error::eof; - } - return n; - } - - template - async_return_type - async_read_some(MutableBufferSequence const& buffers, ReadHandler&& handler) - { - auto const n = - boost::asio::buffer_copy(buffers, boost::asio::buffer(s_)); - error_code ec; - if (n > 0) - s_.erase(0, n); - else - ec = boost::asio::error::eof; - async_completion init{ - handler}; - ios_.post(bind_handler(init.completion_handler, ec, n)); - return init.result.get(); - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - error_code ec; - auto const n = write_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers, error_code& ec) - { - ec.assign(0, ec.category()); - return boost::asio::buffer_size(buffers); - } - - template - async_return_type - async_write_some(ConstBuffeSequence const& buffers, WriteHandler&& handler) - { - async_completion init{ - handler}; - ios_.post(bind_handler( - init.completion_handler, - error_code{}, - boost::asio::buffer_size(buffers))); - return init.result.get(); - } - - friend void - teardown( - websocket::teardown_tag, - string_istream&, - boost::system::error_code& ec) - { - ec.assign(0, ec.category()); - } - - template - friend void - async_teardown( - websocket::teardown_tag, - string_istream& stream, - TeardownHandler&& handler) - { - stream.get_io_service().post( - bind_handler(std::move(handler), error_code{})); - } -}; - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/string_ostream.h b/src/ripple/beast/test/string_ostream.h deleted file mode 100644 index 4cca9e7fce0..00000000000 --- a/src/ripple/beast/test/string_ostream.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_STRING_OSTREAM_HPP -#define BEAST_TEST_STRING_OSTREAM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace test { - -class string_ostream -{ - boost::asio::io_service& ios_; - std::size_t write_max_; - -public: - std::string str; - - explicit string_ostream( - boost::asio::io_service& ios, - std::size_t write_max = (std::numeric_limits::max)()) - : ios_(ios), write_max_(write_max) - { - } - - boost::asio::io_service& - get_io_service() - { - return ios_; - } - - template - std::size_t - read_some(MutableBufferSequence const& buffers) - { - error_code ec; - auto const n = read_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - read_some(MutableBufferSequence const&, error_code& ec) - { - ec = boost::asio::error::eof; - return 0; - } - - template - async_return_type - async_read_some(MutableBufferSequence const&, ReadHandler&& handler) - { - async_completion init{ - handler}; - ios_.post( - bind_handler(init.completion_handler, boost::asio::error::eof, 0)); - return init.result.get(); - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers) - { - error_code ec; - auto const n = write_some(buffers, ec); - if (ec) - BOOST_THROW_EXCEPTION(system_error{ec}); - return n; - } - - template - std::size_t - write_some(ConstBufferSequence const& buffers, error_code& ec) - { - ec.assign(0, ec.category()); - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto const n = (std::min)(buffer_size(buffers), write_max_); - str.reserve(str.size() + n); - for (boost::asio::const_buffer buffer : buffer_prefix(n, buffers)) - str.append(buffer_cast(buffer), buffer_size(buffer)); - return n; - } - - template - async_return_type - async_write_some(ConstBufferSequence const& buffers, WriteHandler&& handler) - { - error_code ec; - auto const bytes_transferred = write_some(buffers, ec); - async_completion init{ - handler}; - get_io_service().post( - bind_handler(init.completion_handler, ec, bytes_transferred)); - return init.result.get(); - } - - friend void - teardown( - websocket::teardown_tag, - string_ostream&, - boost::system::error_code& ec) - { - ec.assign(0, ec.category()); - } - - template - friend void - async_teardown( - websocket::teardown_tag, - string_ostream& stream, - TeardownHandler&& handler) - { - stream.get_io_service().post( - bind_handler(std::move(handler), error_code{})); - } -}; - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/test/test_allocator.h b/src/ripple/beast/test/test_allocator.h deleted file mode 100644 index 598e22c2766..00000000000 --- a/src/ripple/beast/test/test_allocator.h +++ /dev/null @@ -1,159 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_TEST_TEST_ALLOCATOR_HPP -#define BEAST_TEST_TEST_ALLOCATOR_HPP - -#include -#include -#include - -namespace beast { -namespace test { - -struct test_allocator_info -{ - std::size_t id; - std::size_t ncopy = 0; - std::size_t nmove = 0; - std::size_t nmassign = 0; - std::size_t ncpassign = 0; - std::size_t nselect = 0; - - test_allocator_info() - : id([] { - static std::atomic sid(0); - return ++sid; - }()) - { - } -}; - -template -class test_allocator; - -template -struct test_allocator_base -{ -}; - -template -struct test_allocator_base -{ - static test_allocator - select_on_container_copy_construction( - test_allocator const& a) - { - return test_allocator{}; - } -}; - -template -class test_allocator - : public test_allocator_base -{ - std::shared_ptr info_; - - template - friend class test_allocator; - -public: - using value_type = T; - - using propagate_on_container_copy_assignment = - std::integral_constant; - - using propagate_on_container_move_assignment = - std::integral_constant; - - using propagate_on_container_swap = std::integral_constant; - - template - struct rebind - { - using other = test_allocator; - }; - - test_allocator() : info_(std::make_shared()) - { - } - - test_allocator(test_allocator const& u) noexcept : info_(u.info_) - { - ++info_->ncopy; - } - - template - test_allocator( - test_allocator const& u) noexcept - : info_(u.info_) - { - ++info_->ncopy; - } - - test_allocator(test_allocator&& t) : info_(t.info_) - { - ++info_->nmove; - } - - test_allocator& - operator=(test_allocator const& u) noexcept - { - info_ = u.info_; - ++info_->ncpassign; - return *this; - } - - test_allocator& - operator=(test_allocator&& u) noexcept - { - info_ = u.info_; - ++info_->nmassign; - return *this; - } - - value_type* - allocate(std::size_t n) - { - return static_cast(::operator new(n * sizeof(value_type))); - } - - void - deallocate(value_type* p, std::size_t) noexcept - { - ::operator delete(p); - } - - bool - operator==(test_allocator const& other) const - { - return id() == other.id() || Equal; - } - - bool - operator!=(test_allocator const& other) const - { - return !this->operator==(other); - } - - std::size_t - id() const - { - return info_->id; - } - - test_allocator_info const* - operator->() const - { - return info_.get(); - } -}; - -} // namespace test -} // namespace beast - -#endif diff --git a/src/ripple/beast/unit_test/dstream.h b/src/ripple/beast/unit_test/dstream.h deleted file mode 100644 index ff2036a12cb..00000000000 --- a/src/ripple/beast/unit_test/dstream.h +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_UNIT_TEST_DSTREAM_HPP -#define BEAST_UNIT_TEST_DSTREAM_HPP - -#include -#include -#include -#include -#include -#include - -#ifdef BOOST_WINDOWS -#include -//#include -#endif - -namespace beast { -namespace unit_test { - -#ifdef BOOST_WINDOWS - -namespace detail { - -template -class dstream_buf : public std::basic_stringbuf -{ - using ostream = std::basic_ostream; - - bool dbg_; - ostream& os_; - - template - void - write(T const*) = delete; - - void - write(char const* s) - { - if (dbg_) - /*boost::detail::winapi*/ ::OutputDebugStringA(s); - os_ << s; - } - - void - write(wchar_t const* s) - { - if (dbg_) - /*boost::detail::winapi*/ ::OutputDebugStringW(s); - os_ << s; - } - -public: - explicit dstream_buf(ostream& os) - : os_(os), dbg_(/*boost::detail::winapi*/ ::IsDebuggerPresent() != 0) - { - } - - ~dstream_buf() - { - sync(); - } - - int - sync() override - { - write(this->str().c_str()); - this->str(""); - return 0; - } -}; - -} // namespace detail - -/** std::ostream with Visual Studio IDE redirection. - - Instances of this stream wrap a specified `std::ostream` - (such as `std::cout` or `std::cerr`). If the IDE debugger - is attached when the stream is created, output will be - additionally copied to the Visual Studio Output window. -*/ -template < - class CharT, - class Traits = std::char_traits, - class Allocator = std::allocator> -class basic_dstream : public std::basic_ostream -{ - detail::dstream_buf buf_; - -public: - /** Construct a stream. - - @param os The output stream to wrap. - */ - explicit basic_dstream(std::ostream& os) - : std::basic_ostream(&buf_), buf_(os) - { - if (os.flags() & std::ios::unitbuf) - std::unitbuf(*this); - } -}; - -using dstream = basic_dstream; -using dwstream = basic_dstream; - -#else - -using dstream = std::ostream&; -using dwstream = std::wostream&; - -#endif - -} // namespace unit_test -} // namespace beast - -#endif diff --git a/src/ripple/beast/utility/hash_pair.h b/src/ripple/beast/utility/hash_pair.h deleted file mode 100644 index 08042b34778..00000000000 --- a/src/ripple/beast/utility/hash_pair.h +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef BEAST_UTILITY_HASH_PAIR_H_INCLUDED -#define BEAST_UTILITY_HASH_PAIR_H_INCLUDED - -#include -#include - -#include -#include - -namespace std { - -/** Specialization of std::hash for any std::pair type. */ -template -struct hash> - : private boost::base_from_member, 0>, - private boost::base_from_member, 1> -{ -private: - using first_hash = boost::base_from_member, 0>; - using second_hash = boost::base_from_member, 1>; - -public: - hash() - { - } - - hash( - std::hash const& first_hash_, - std::hash const& second_hash_) - : first_hash(first_hash_), second_hash(second_hash_) - { - } - - std::size_t - operator()(std::pair const& value) - { - std::size_t result(first_hash::member(value.first)); - boost::hash_combine(result, second_hash::member(value.second)); - return result; - } - - std::size_t - operator()(std::pair const& value) const - { - std::size_t result(first_hash::member(value.first)); - boost::hash_combine(result, second_hash::member(value.second)); - return result; - } -}; - -} // namespace std - -#endif diff --git a/src/ripple/core/Pg.cpp b/src/ripple/core/Pg.cpp deleted file mode 100644 index df0f6da5ef5..00000000000 --- a/src/ripple/core/Pg.cpp +++ /dev/null @@ -1,1415 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifdef RIPPLED_REPORTING -// Need raw socket manipulation to determine if postgres socket IPv4 or 6. -#if defined(_WIN32) -#include -#include -#else -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -static void -noticeReceiver(void* arg, PGresult const* res) -{ - beast::Journal& j = *static_cast(arg); - JLOG(j.info()) << "server message: " << PQresultErrorMessage(res); -} - -//----------------------------------------------------------------------------- - -std::string -PgResult::msg() const -{ - if (error_.has_value()) - { - std::stringstream ss; - ss << error_->first << ": " << error_->second; - return ss.str(); - } - if (result_) - return "ok"; - - // Must be stopping. - return "stopping"; -} - -//----------------------------------------------------------------------------- - -/* - Connecting described in: - https://www.postgresql.org/docs/10/libpq-connect.html - */ -void -Pg::connect() -{ - if (conn_) - { - // Nothing to do if we already have a good connection. - if (PQstatus(conn_.get()) == CONNECTION_OK) - return; - /* Try resetting connection. */ - PQreset(conn_.get()); - } - else // Make new connection. - { - conn_.reset(PQconnectdbParams( - reinterpret_cast(&config_.keywordsIdx[0]), - reinterpret_cast(&config_.valuesIdx[0]), - 0)); - if (!conn_) - Throw("No db connection struct"); - } - - /** Results from a synchronous connection attempt can only be either - * CONNECTION_OK or CONNECTION_BAD. */ - if (PQstatus(conn_.get()) == CONNECTION_BAD) - { - std::stringstream ss; - ss << "DB connection status " << PQstatus(conn_.get()) << ": " - << PQerrorMessage(conn_.get()); - Throw(ss.str()); - } - - // Log server session console messages. - PQsetNoticeReceiver( - conn_.get(), noticeReceiver, const_cast(&j_)); -} - -PgResult -Pg::query(char const* command, std::size_t nParams, char const* const* values) -{ - // The result object must be freed using the libpq API PQclear() call. - pg_result_type ret{nullptr, [](PGresult* result) { PQclear(result); }}; - // Connect then submit query. - while (true) - { - { - std::lock_guard lock(mutex_); - if (stop_) - return PgResult(); - } - try - { - connect(); - if (nParams) - { - // PQexecParams can process only a single command. - ret.reset(PQexecParams( - conn_.get(), - command, - nParams, - nullptr, - values, - nullptr, - nullptr, - 0)); - } - else - { - // PQexec can process multiple commands separated by - // semi-colons. Returns the response from the last - // command processed. - ret.reset(PQexec(conn_.get(), command)); - } - if (!ret) - Throw("no result structure returned"); - break; - } - catch (std::exception const& e) - { - // Sever connection and retry until successful. - disconnect(); - JLOG(j_.error()) << "database error, retrying: " << e.what(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - } - - // Ensure proper query execution. - switch (PQresultStatus(ret.get())) - { - case PGRES_TUPLES_OK: - case PGRES_COMMAND_OK: - case PGRES_COPY_IN: - case PGRES_COPY_OUT: - case PGRES_COPY_BOTH: - break; - default: { - std::stringstream ss; - ss << "bad query result: " << PQresStatus(PQresultStatus(ret.get())) - << " error message: " << PQerrorMessage(conn_.get()) - << ", number of tuples: " << PQntuples(ret.get()) - << ", number of fields: " << PQnfields(ret.get()); - JLOG(j_.error()) << ss.str(); - PgResult retRes(ret.get(), conn_.get()); - disconnect(); - return retRes; - } - } - - return PgResult(std::move(ret)); -} - -static pg_formatted_params -formatParams(pg_params const& dbParams, beast::Journal const& j) -{ - std::vector> const& values = dbParams.second; - /* Convert vector to C-style array of C-strings for postgres API. - std::nullopt is a proxy for NULL since an empty std::string is - 0 length but not NULL. */ - std::vector valuesIdx; - valuesIdx.reserve(values.size()); - std::stringstream ss; - bool first = true; - for (auto const& value : values) - { - if (value) - { - valuesIdx.push_back(value->c_str()); - ss << value->c_str(); - } - else - { - valuesIdx.push_back(nullptr); - ss << "(null)"; - } - if (first) - first = false; - else - ss << ','; - } - - JLOG(j.trace()) << "query: " << dbParams.first << ". params: " << ss.str(); - return valuesIdx; -} - -PgResult -Pg::query(pg_params const& dbParams) -{ - char const* const& command = dbParams.first; - auto const formattedParams = formatParams(dbParams, j_); - return query( - command, - formattedParams.size(), - formattedParams.size() - ? reinterpret_cast(&formattedParams[0]) - : nullptr); -} - -void -Pg::bulkInsert(char const* table, std::string const& records) -{ - // https://www.postgresql.org/docs/12/libpq-copy.html#LIBPQ-COPY-SEND - assert(conn_.get()); - static auto copyCmd = boost::format(R"(COPY %s FROM stdin)"); - auto res = query(boost::str(copyCmd % table).c_str()); - if (!res || res.status() != PGRES_COPY_IN) - { - std::stringstream ss; - ss << "bulkInsert to " << table - << ". Postgres insert error: " << res.msg(); - if (res) - ss << ". Query status not PGRES_COPY_IN: " << res.status(); - Throw(ss.str()); - } - - if (PQputCopyData(conn_.get(), records.c_str(), records.size()) == -1) - { - std::stringstream ss; - ss << "bulkInsert to " << table - << ". PQputCopyData error: " << PQerrorMessage(conn_.get()); - disconnect(); - Throw(ss.str()); - } - - if (PQputCopyEnd(conn_.get(), nullptr) == -1) - { - std::stringstream ss; - ss << "bulkInsert to " << table - << ". PQputCopyEnd error: " << PQerrorMessage(conn_.get()); - disconnect(); - Throw(ss.str()); - } - - // The result object must be freed using the libpq API PQclear() call. - pg_result_type copyEndResult{ - nullptr, [](PGresult* result) { PQclear(result); }}; - copyEndResult.reset(PQgetResult(conn_.get())); - ExecStatusType status = PQresultStatus(copyEndResult.get()); - if (status != PGRES_COMMAND_OK) - { - std::stringstream ss; - ss << "bulkInsert to " << table - << ". PQputCopyEnd status not PGRES_COMMAND_OK: " << status; - disconnect(); - Throw(ss.str()); - } -} - -bool -Pg::clear() -{ - if (!conn_) - return false; - - // The result object must be freed using the libpq API PQclear() call. - pg_result_type res{nullptr, [](PGresult* result) { PQclear(result); }}; - - // Consume results until no more, or until the connection is severed. - do - { - res.reset(PQgetResult(conn_.get())); - if (!res) - break; - - // Pending bulk copy operations may leave the connection in such a - // state that it must be disconnected. - switch (PQresultStatus(res.get())) - { - case PGRES_COPY_IN: - if (PQputCopyEnd(conn_.get(), nullptr) != -1) - break; - [[fallthrough]]; // avoids compiler warning - case PGRES_COPY_OUT: - case PGRES_COPY_BOTH: - conn_.reset(); - default:; - } - } while (res && conn_); - - return conn_ != nullptr; -} - -//----------------------------------------------------------------------------- - -PgPool::PgPool(Section const& pgConfig, beast::Journal j) : j_(j) -{ - // Make sure that boost::asio initializes the SSL library. - { - static boost::asio::ssl::detail::openssl_init initSsl; - } - // Don't have postgres client initialize SSL. - PQinitOpenSSL(0, 0); - - /* - Connect to postgres to create low level connection parameters - with optional caching of network address info for subsequent connections. - See https://www.postgresql.org/docs/10/libpq-connect.html - - For bounds checking of postgres connection data received from - the network: the largest size for any connection field in - PG source code is 64 bytes as of 5/2019. There are 29 fields. - */ - constexpr std::size_t maxFieldSize = 1024; - constexpr std::size_t maxFields = 1000; - - // The connection object must be freed using the libpq API PQfinish() call. - pg_connection_type conn( - PQconnectdb(get(pgConfig, "conninfo").c_str()), - [](PGconn* conn) { PQfinish(conn); }); - if (!conn) - Throw("Can't create DB connection."); - if (PQstatus(conn.get()) != CONNECTION_OK) - { - std::stringstream ss; - ss << "Initial DB connection failed: " << PQerrorMessage(conn.get()); - Throw(ss.str()); - } - - int const sockfd = PQsocket(conn.get()); - if (sockfd == -1) - Throw("No DB socket is open."); - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - if (getpeername(sockfd, reinterpret_cast(&addr), &len) == - -1) - { - Throw( - errno, std::generic_category(), "Can't get server address info."); - } - - // Set "port" and "hostaddr" if we're caching it. - bool const remember_ip = get(pgConfig, "remember_ip", true); - - if (remember_ip) - { - config_.keywords.push_back("port"); - config_.keywords.push_back("hostaddr"); - std::string port; - std::string hostaddr; - - if (addr.ss_family == AF_INET) - { - hostaddr.assign(INET_ADDRSTRLEN, '\0'); - struct sockaddr_in const& ainfo = - reinterpret_cast(addr); - port = std::to_string(ntohs(ainfo.sin_port)); - if (!inet_ntop( - AF_INET, &ainfo.sin_addr, &hostaddr[0], hostaddr.size())) - { - Throw( - errno, - std::generic_category(), - "Can't get IPv4 address string."); - } - } - else if (addr.ss_family == AF_INET6) - { - hostaddr.assign(INET6_ADDRSTRLEN, '\0'); - struct sockaddr_in6 const& ainfo = - reinterpret_cast(addr); - port = std::to_string(ntohs(ainfo.sin6_port)); - if (!inet_ntop( - AF_INET6, &ainfo.sin6_addr, &hostaddr[0], hostaddr.size())) - { - Throw( - errno, - std::generic_category(), - "Can't get IPv6 address string."); - } - } - - config_.values.push_back(port.c_str()); - config_.values.push_back(hostaddr.c_str()); - } - std::unique_ptr connOptions( - PQconninfo(conn.get()), - [](PQconninfoOption* opts) { PQconninfoFree(opts); }); - if (!connOptions) - Throw("Can't get DB connection options."); - - std::size_t nfields = 0; - for (PQconninfoOption* option = connOptions.get(); - option->keyword != nullptr; - ++option) - { - if (++nfields > maxFields) - { - std::stringstream ss; - ss << "DB returned connection options with > " << maxFields - << " fields."; - Throw(ss.str()); - } - - if (!option->val || - (remember_ip && - (!strcmp(option->keyword, "hostaddr") || - !strcmp(option->keyword, "port")))) - { - continue; - } - - if (strlen(option->keyword) > maxFieldSize || - strlen(option->val) > maxFieldSize) - { - std::stringstream ss; - ss << "DB returned a connection option name or value with\n"; - ss << "excessive size (>" << maxFieldSize << " bytes).\n"; - ss << "option (possibly truncated): " - << std::string_view( - option->keyword, - std::min(strlen(option->keyword), maxFieldSize)) - << '\n'; - ss << " value (possibly truncated): " - << std::string_view( - option->val, std::min(strlen(option->val), maxFieldSize)); - Throw(ss.str()); - } - config_.keywords.push_back(option->keyword); - config_.values.push_back(option->val); - } - - config_.keywordsIdx.reserve(config_.keywords.size() + 1); - config_.valuesIdx.reserve(config_.values.size() + 1); - for (std::size_t n = 0; n < config_.keywords.size(); ++n) - { - config_.keywordsIdx.push_back(config_.keywords[n].c_str()); - config_.valuesIdx.push_back(config_.values[n].c_str()); - } - config_.keywordsIdx.push_back(nullptr); - config_.valuesIdx.push_back(nullptr); - - get_if_exists(pgConfig, "max_connections", config_.max_connections); - std::size_t timeout; - if (get_if_exists(pgConfig, "timeout", timeout)) - config_.timeout = std::chrono::seconds(timeout); -} - -void -PgPool::setup() -{ - { - std::stringstream ss; - ss << "max_connections: " << config_.max_connections << ", " - << "timeout: " << config_.timeout.count() << ", " - << "connection params: "; - bool first = true; - for (std::size_t i = 0; i < config_.keywords.size(); ++i) - { - if (first) - first = false; - else - ss << ", "; - ss << config_.keywords[i] << ": " - << (config_.keywords[i] == "password" ? "*" : config_.values[i]); - } - JLOG(j_.debug()) << ss.str(); - } -} - -void -PgPool::stop() -{ - std::lock_guard lock(mutex_); - stop_ = true; - cond_.notify_all(); - idle_.clear(); - JLOG(j_.info()) << "stopped"; -} - -void -PgPool::idleSweeper() -{ - std::size_t before, after; - { - std::lock_guard lock(mutex_); - before = idle_.size(); - if (config_.timeout != std::chrono::seconds(0)) - { - auto const found = - idle_.upper_bound(clock_type::now() - config_.timeout); - for (auto it = idle_.begin(); it != found;) - { - it = idle_.erase(it); - --connections_; - } - } - after = idle_.size(); - } - - JLOG(j_.info()) << "Idle sweeper. connections: " << connections_ - << ". checked out: " << connections_ - after - << ". idle before, after sweep: " << before << ", " - << after; -} - -std::unique_ptr -PgPool::checkout() -{ - std::unique_ptr ret; - std::unique_lock lock(mutex_); - do - { - if (stop_) - return {}; - - // If there is a connection in the pool, return the most recent. - if (idle_.size()) - { - auto entry = idle_.rbegin(); - ret = std::move(entry->second); - idle_.erase(std::next(entry).base()); - } - // Otherwise, return a new connection unless over threshold. - else if (connections_ < config_.max_connections) - { - ++connections_; - ret = std::make_unique(config_, j_, stop_, mutex_); - } - // Otherwise, wait until a connection becomes available or we stop. - else - { - JLOG(j_.error()) << "No database connections available."; - cond_.wait(lock); - } - } while (!ret && !stop_); - lock.unlock(); - - return ret; -} - -void -PgPool::checkin(std::unique_ptr& pg) -{ - if (pg) - { - std::lock_guard lock(mutex_); - if (!stop_ && pg->clear()) - { - idle_.emplace(clock_type::now(), std::move(pg)); - } - else - { - --connections_; - pg.reset(); - } - } - - cond_.notify_all(); -} - -//----------------------------------------------------------------------------- - -std::shared_ptr -make_PgPool(Section const& pgConfig, beast::Journal j) -{ - auto ret = std::make_shared(pgConfig, j); - ret->setup(); - return ret; -} - -//----------------------------------------------------------------------------- - -/** Postgres Schema Management - * - * The postgres schema has several properties to facilitate - * consistent deployments, including upgrades. It is not recommended to - * upgrade the schema concurrently. - * - * Initial deployment should be against a completely fresh database. The - * postgres user must have the CREATE TABLE privilege. - * - * With postgres configured, the first step is to apply the version_query - * schema and consume the results. This script returns the currently - * installed schema version, if configured, or 0 if not. It is idempotent. - * - * If the version installed on the database is equal to the - * LATEST_SCHEMA_VERSION, then no action should take place. - * - * If the version on the database is 0, then the entire latest schema - * should be deployed with the applySchema() function. - * Each version that is developed is fully - * represented in the full_schemata array with each version equal to the - * text in the array's index position. For example, index position 1 - * contains the full schema version 1. Position 2 contains schema version 2. - * Index 0 should never be referenced and its value only a placeholder. - * If a fresh installation is aborted, then subsequent fresh installations - * should install the same version previously attempted, even if there - * exists a newer version. The initSchema() function performs this task. - * Therefore, previous schema versions should remain in the array - * without modification as new versions are developed and placed after them. - * Once the schema is succesffuly deployed, applySchema() persists the - * schema version to the database. - * - * If the current version of the database is greater than 0, then it means - * that a previous schema version is already present. In this case, the database - * schema needs to be updated incrementally for each subsequent version. - * Again, applySchema() is used to upgrade the schema. Schema upgrades are - * in the upgrade_schemata array. Each entry by index position represents - * the database schema version from which the upgrade begins. Each upgrade - * sets the database to the next version. Schema upgrades can only safely - * happen from one version to the next. To upgrade several versions of schema, - * upgrade incrementally for each version that separates the current from the - * latest. For example, to upgrade from version 5 to version 6 of the schema, - * use upgrade_schemata[5]. To upgrade from version 1 to version 4, use - * upgrade_schemata[1], upgrade_schemata[2], and upgrade_schemata[3] in - * sequence. - * - * To upgrade the schema past version 1, the following variables must be - * updated: - * 1) LATEST_SCHEMA_VERSION must be set to the new version. - * 2) A new entry must be placed at the end of the full_schemata array. This - * entry should have the entire schema so that fresh installations can - * be performed with it. The index position must be equal to the - * LATEST_SCHEMA_VERSION. - * 3) A new entry must be placed at the end of the upgrade_schemata array. - * This entry should only contain commands to upgrade the schema from - * the immediately previous version to the new version. - * - * It is up to the developer to ensure that all schema commands are idempotent. - * This protects against 2 things: - * 1) Resuming schema installation after a problem. - * 2) Concurrent schema updates from multiple processes. - * - * There are several things that must considered for upgrading existing - * schemata to avoid stability and performance problems. Some examples and - * suggestions follow. - * - Schema changes such as creating new columns and indices can consume - * a lot of time. Therefore, before such changes, a separate script should - * be executed by the user to perform the schema upgrade prior to restarting - * rippled. - * - Stored functions cannot be dropped while being accessed. Also, - * dropping stored functions can be ambiguous if multiple functions with - * the same name but different signatures exist. Further, stored function - * behavior from one schema version to the other would likely be handled - * differently by rippled. In this case, it is likely that the functions - * themselves should be versioned such as by appending a number to the - * end of the name (abcf becomes abcf_2, abcf_3, etc.) - * - * Essentially, each schema upgrade will have its own factors to impact - * service availability and function. - */ - -#define LATEST_SCHEMA_VERSION 1 - -char const* version_query = R"( -CREATE TABLE IF NOT EXISTS version (version int NOT NULL, - fresh_pending int NOT NULL); - --- Version 0 means that no schema has been fully deployed. -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM version) THEN - INSERT INTO version VALUES (0, 0); -END IF; -END $$; - --- Function to set the schema version. _in_pending should only be set to --- non-zero prior to an attempt to initialize the schema from scratch. --- After successful initialization, this should set to 0. --- _in_version should be set to the version of schema that has been applied --- once successful application has occurred. -CREATE OR REPLACE FUNCTION set_schema_version ( - _in_version int, - _in_pending int -) RETURNS void AS $$ -DECLARE - _current_version int; -BEGIN - IF _in_version IS NULL OR _in_pending IS NULL THEN RETURN; END IF; - IF EXISTS (SELECT 1 FROM version) THEN DELETE FROM version; END IF; - INSERT INTO version VALUES (_in_version, _in_pending); - RETURN; -END; -$$ LANGUAGE plpgsql; - --- PQexec() returns the output of the last statement in its response. -SELECT * FROM version; -)"; - -std::array full_schemata = { - // version 0: - "There is no such thing as schema version 0." - - // version 1: - , - R"( --- Table to store ledger headers. -CREATE TABLE IF NOT EXISTS ledgers ( - ledger_seq bigint PRIMARY KEY, - ledger_hash bytea NOT NULL, - prev_hash bytea NOT NULL, - total_coins bigint NOT NULL, - closing_time bigint NOT NULL, - prev_closing_time bigint NOT NULL, - close_time_res bigint NOT NULL, - close_flags bigint NOT NULL, - account_set_hash bytea NOT NULL, - trans_set_hash bytea NOT NULL -); - --- Index for lookups by ledger hash. -CREATE INDEX IF NOT EXISTS ledgers_ledger_hash_idx ON ledgers - USING hash (ledger_hash); - --- Transactions table. Deletes from the ledger table --- cascade here based on ledger_seq. -CREATE TABLE IF NOT EXISTS transactions ( - ledger_seq bigint NOT NULL, - transaction_index bigint NOT NULL, - trans_id bytea NOT NULL, - nodestore_hash bytea NOT NULL, - constraint transactions_pkey PRIMARY KEY (ledger_seq, transaction_index), - constraint transactions_fkey FOREIGN KEY (ledger_seq) - REFERENCES ledgers (ledger_seq) ON DELETE CASCADE -); - --- Index for lookups by transaction hash. -CREATE INDEX IF NOT EXISTS transactions_trans_id_idx ON transactions - USING hash (trans_id); - --- Table that maps accounts to transactions affecting them. Deletes from the --- ledger table by way of transactions table cascade here based on ledger_seq. -CREATE TABLE IF NOT EXISTS account_transactions ( - account bytea NOT NULL, - ledger_seq bigint NOT NULL, - transaction_index bigint NOT NULL, - constraint account_transactions_pkey PRIMARY KEY (account, ledger_seq, - transaction_index), - constraint account_transactions_fkey FOREIGN KEY (ledger_seq, - transaction_index) REFERENCES transactions ( - ledger_seq, transaction_index) ON DELETE CASCADE -); - --- Index to allow for fast cascading deletions and referential integrity. -CREATE INDEX IF NOT EXISTS fki_account_transactions_idx ON - account_transactions USING btree (ledger_seq, transaction_index); - --- Avoid inadvertent administrative tampering with committed data. -CREATE OR REPLACE RULE ledgers_update_protect AS ON UPDATE TO - ledgers DO INSTEAD NOTHING; -CREATE OR REPLACE RULE transactions_update_protect AS ON UPDATE TO - transactions DO INSTEAD NOTHING; -CREATE OR REPLACE RULE account_transactions_update_protect AS ON UPDATE TO - account_transactions DO INSTEAD NOTHING; - --- Stored procedure to assist with the tx() RPC call. Takes transaction hash --- as input. If found, returns the ledger sequence in which it was applied. --- If not, returns the range of ledgers searched. -CREATE OR REPLACE FUNCTION tx ( - _in_trans_id bytea -) RETURNS jsonb AS $$ -DECLARE - _min_seq bigint := min_ledger(); - _max_seq bigint := max_ledger(); - _ledger_seq bigint; - _nodestore_hash bytea; -BEGIN - - IF _min_seq IS NULL THEN - RETURN jsonb_build_object('error', 'empty database'); - END IF; - IF length(_in_trans_id) != 32 THEN - RETURN jsonb_build_object('error', '_in_trans_id size: ' - || to_char(length(_in_trans_id), '999')); - END IF; - - EXECUTE 'SELECT nodestore_hash, ledger_seq - FROM transactions - WHERE trans_id = $1 - AND ledger_seq BETWEEN $2 AND $3 - ' INTO _nodestore_hash, _ledger_seq USING _in_trans_id, _min_seq, _max_seq; - IF _nodestore_hash IS NULL THEN - RETURN jsonb_build_object('min_seq', _min_seq, 'max_seq', _max_seq); - END IF; - RETURN jsonb_build_object('nodestore_hash', _nodestore_hash, 'ledger_seq', - _ledger_seq); -END; -$$ LANGUAGE plpgsql; - --- Return the earliest ledger sequence intended for range operations --- that protect the bottom of the range from deletion. Return NULL if empty. -CREATE OR REPLACE FUNCTION min_ledger () RETURNS bigint AS $$ -DECLARE - _min_seq bigint := (SELECT ledger_seq from min_seq); -BEGIN - IF _min_seq IS NULL THEN - RETURN (SELECT ledger_seq FROM ledgers ORDER BY ledger_seq ASC LIMIT 1); - ELSE - RETURN _min_seq; - END IF; -END; -$$ LANGUAGE plpgsql; - --- Return the latest ledger sequence in the database, or NULL if empty. -CREATE OR REPLACE FUNCTION max_ledger () RETURNS bigint AS $$ -BEGIN - RETURN (SELECT ledger_seq FROM ledgers ORDER BY ledger_seq DESC LIMIT 1); -END; -$$ LANGUAGE plpgsql; - --- account_tx() RPC helper. From the rippled reporting process, only the --- parameters without defaults are required. For the parameters with --- defaults, validation should be done by rippled, such as: --- _in_account_id should be a valid xrp base58 address. --- _in_forward either true or false according to the published api --- _in_limit should be validated and not simply passed through from --- client. --- --- For _in_ledger_index_min and _in_ledger_index_max, if passed in the --- request, verify that their type is int and pass through as is. --- For _ledger_hash, verify and convert from hex length 32 bytes and --- prepend with \x (\\x C++). --- --- For _in_ledger_index, if the input type is integer, then pass through --- as is. If the type is string and contents = validated, then do not --- set _in_ledger_index. Instead set _in_invalidated to TRUE. --- --- There is no need for rippled to do any type of lookup on max/min --- ledger range, lookup of hash, or the like. This functions does those --- things, including error responses if bad input. Only the above must --- be done to set the correct search range. --- --- If a marker is present in the request, verify the members 'ledger' --- and 'seq' are integers and they correspond to _in_marker_seq --- _in_marker_index. --- To reiterate: --- JSON input field 'ledger' corresponds to _in_marker_seq --- JSON input field 'seq' corresponds to _in_marker_index -CREATE OR REPLACE FUNCTION account_tx ( - _in_account_id bytea, - _in_forward bool, - _in_limit bigint, - _in_ledger_index_min bigint = NULL, - _in_ledger_index_max bigint = NULL, - _in_ledger_hash bytea = NULL, - _in_ledger_index bigint = NULL, - _in_validated bool = NULL, - _in_marker_seq bigint = NULL, - _in_marker_index bigint = NULL -) RETURNS jsonb AS $$ -DECLARE - _min bigint; - _max bigint; - _sort_order text := (SELECT CASE WHEN _in_forward IS TRUE THEN - 'ASC' ELSE 'DESC' END); - _marker bool; - _between_min bigint; - _between_max bigint; - _sql text; - _cursor refcursor; - _result jsonb; - _record record; - _tally bigint := 0; - _ret_marker jsonb; - _transactions jsonb[] := '{}'; -BEGIN - IF _in_ledger_index_min IS NOT NULL OR - _in_ledger_index_max IS NOT NULL THEN - _min := (SELECT CASE WHEN _in_ledger_index_min IS NULL - THEN min_ledger() ELSE greatest( - _in_ledger_index_min, min_ledger()) END); - _max := (SELECT CASE WHEN _in_ledger_index_max IS NULL OR - _in_ledger_index_max = -1 THEN max_ledger() ELSE - least(_in_ledger_index_max, max_ledger()) END); - - IF _max < _min THEN - RETURN jsonb_build_object('error', 'max is less than min ledger'); - END IF; - - ELSIF _in_ledger_hash IS NOT NULL OR _in_ledger_index IS NOT NULL - OR _in_validated IS TRUE THEN - IF _in_ledger_hash IS NOT NULL THEN - IF length(_in_ledger_hash) != 32 THEN - RETURN jsonb_build_object('error', '_in_ledger_hash size: ' - || to_char(length(_in_ledger_hash), '999')); - END IF; - EXECUTE 'SELECT ledger_seq - FROM ledgers - WHERE ledger_hash = $1' - INTO _min USING _in_ledger_hash::bytea; - ELSE - IF _in_ledger_index IS NOT NULL AND _in_validated IS TRUE THEN - RETURN jsonb_build_object('error', - '_in_ledger_index cannot be set and _in_validated true'); - END IF; - IF _in_validated IS TRUE THEN - _in_ledger_index := max_ledger(); - END IF; - _min := (SELECT ledger_seq - FROM ledgers - WHERE ledger_seq = _in_ledger_index); - END IF; - IF _min IS NULL THEN - RETURN jsonb_build_object('error', 'ledger not found'); - END IF; - _max := _min; - ELSE - _min := min_ledger(); - _max := max_ledger(); - END IF; - - IF _in_marker_seq IS NOT NULL OR _in_marker_index IS NOT NULL THEN - _marker := TRUE; - IF _in_marker_seq IS NULL OR _in_marker_index IS NULL THEN - -- The rippled implementation returns no transaction results - -- if either of these values are missing. - _between_min := 0; - _between_max := 0; - ELSE - IF _in_forward IS TRUE THEN - _between_min := _in_marker_seq; - _between_max := _max; - ELSE - _between_min := _min; - _between_max := _in_marker_seq; - END IF; - END IF; - ELSE - _marker := FALSE; - _between_min := _min; - _between_max := _max; - END IF; - IF _between_max < _between_min THEN - RETURN jsonb_build_object('error', 'ledger search range is ' - || to_char(_between_min, '999') || '-' - || to_char(_between_max, '999')); - END IF; - - _sql := format(' - SELECT transactions.ledger_seq, transactions.transaction_index, - transactions.trans_id, transactions.nodestore_hash - FROM transactions - INNER JOIN account_transactions - ON transactions.ledger_seq = - account_transactions.ledger_seq - AND transactions.transaction_index = - account_transactions.transaction_index - WHERE account_transactions.account = $1 - AND account_transactions.ledger_seq BETWEEN $2 AND $3 - ORDER BY transactions.ledger_seq %s, transactions.transaction_index %s - ', _sort_order, _sort_order); - - OPEN _cursor FOR EXECUTE _sql USING _in_account_id, _between_min, - _between_max; - LOOP - FETCH _cursor INTO _record; - IF _record IS NULL THEN EXIT; END IF; - IF _marker IS TRUE THEN - IF _in_marker_seq = _record.ledger_seq THEN - IF _in_forward IS TRUE THEN - IF _in_marker_index > _record.transaction_index THEN - CONTINUE; - END IF; - ELSE - IF _in_marker_index < _record.transaction_index THEN - CONTINUE; - END IF; - END IF; - END IF; - _marker := FALSE; - END IF; - - _tally := _tally + 1; - IF _tally > _in_limit THEN - _ret_marker := jsonb_build_object( - 'ledger', _record.ledger_seq, - 'seq', _record.transaction_index); - EXIT; - END IF; - - -- Is the transaction index in the tx object? - _transactions := _transactions || jsonb_build_object( - 'ledger_seq', _record.ledger_seq, - 'transaction_index', _record.transaction_index, - 'trans_id', _record.trans_id, - 'nodestore_hash', _record.nodestore_hash); - - END LOOP; - CLOSE _cursor; - - _result := jsonb_build_object('ledger_index_min', _min, - 'ledger_index_max', _max, - 'transactions', _transactions); - IF _ret_marker IS NOT NULL THEN - _result := _result || jsonb_build_object('marker', _ret_marker); - END IF; - RETURN _result; -END; -$$ LANGUAGE plpgsql; - --- Trigger prior to insert on ledgers table. Validates length of hash fields. --- Verifies ancestry based on ledger_hash & prev_hash as follows: --- 1) If ledgers is empty, allows insert. --- 2) For each new row, check for previous and later ledgers by a single --- sequence. For each that exist, confirm ancestry based on hashes. --- 3) Disallow inserts with no prior or next ledger by sequence if any --- ledgers currently exist. This disallows gaps to be introduced by --- way of inserting. -CREATE OR REPLACE FUNCTION insert_ancestry() RETURNS TRIGGER AS $$ -DECLARE - _parent bytea; - _child bytea; -BEGIN - IF length(NEW.ledger_hash) != 32 OR length(NEW.prev_hash) != 32 THEN - RAISE 'ledger_hash and prev_hash must each be 32 bytes: %', NEW; - END IF; - - IF (SELECT ledger_hash - FROM ledgers - ORDER BY ledger_seq DESC - LIMIT 1) = NEW.prev_hash THEN RETURN NEW; END IF; - - IF NOT EXISTS (SELECT 1 FROM LEDGERS) THEN RETURN NEW; END IF; - - _parent := (SELECT ledger_hash - FROM ledgers - WHERE ledger_seq = NEW.ledger_seq - 1); - _child := (SELECT prev_hash - FROM ledgers - WHERE ledger_seq = NEW.ledger_seq + 1); - IF _parent IS NULL AND _child IS NULL THEN - RAISE 'Ledger Ancestry error: orphan.'; - END IF; - IF _parent != NEW.prev_hash THEN - RAISE 'Ledger Ancestry error: bad parent.'; - END IF; - IF _child != NEW.ledger_hash THEN - RAISE 'Ledger Ancestry error: bad child.'; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Trigger function prior to delete on ledgers table. Disallow gaps from --- forming. Do not allow deletions if both the previous and next ledgers --- are present. In other words, only allow either the least or greatest --- to be deleted. -CREATE OR REPLACE FUNCTION delete_ancestry () RETURNS TRIGGER AS $$ -BEGIN - IF EXISTS (SELECT 1 - FROM ledgers - WHERE ledger_seq = OLD.ledger_seq + 1) - AND EXISTS (SELECT 1 - FROM ledgers - WHERE ledger_seq = OLD.ledger_seq - 1) THEN - RAISE 'Ledger Ancestry error: Can only delete the least or greatest ' - 'ledger.'; - END IF; - RETURN OLD; -END; -$$ LANGUAGE plpgsql; - --- Track the minimum sequence that should be used for ranged queries --- with protection against deletion during the query. This should --- be updated before calling online_delete() to not block deleting that --- range. -CREATE TABLE IF NOT EXISTS min_seq ( - ledger_seq bigint NOT NULL -); - --- Set the minimum sequence for use in ranged queries with protection --- against deletion greater than or equal to the input parameter. This --- should be called prior to online_delete() with the same parameter --- value so that online_delete() is not blocked by range queries --- that are protected against concurrent deletion of the ledger at --- the bottom of the range. This function needs to be called from a --- separate transaction from that which executes online_delete(). -CREATE OR REPLACE FUNCTION prepare_delete ( - _in_last_rotated bigint -) RETURNS void AS $$ -BEGIN - IF EXISTS (SELECT 1 FROM min_seq) THEN - DELETE FROM min_seq; - END IF; - INSERT INTO min_seq VALUES (_in_last_rotated + 1); -END; -$$ LANGUAGE plpgsql; - --- Function to delete old data. All data belonging to ledgers prior to and --- equal to the _in_seq parameter will be deleted. This should be --- called with the input parameter equivalent to the value of lastRotated --- in rippled's online_delete routine. -CREATE OR REPLACE FUNCTION online_delete ( - _in_seq bigint -) RETURNS void AS $$ -BEGIN - DELETE FROM LEDGERS WHERE ledger_seq <= _in_seq; -END; -$$ LANGUAGE plpgsql; - --- Function to delete data from the top of the ledger range. Delete --- everything greater than the input parameter. --- It doesn't do a normal range delete because of the trigger protecting --- deletions causing gaps. Instead, it walks back from the greatest ledger. -CREATE OR REPLACE FUNCTION delete_above ( - _in_seq bigint -) RETURNS void AS $$ -DECLARE - _max_seq bigint := max_ledger(); - _i bigint := _max_seq; -BEGIN - IF _max_seq IS NULL THEN RETURN; END IF; - LOOP - IF _i <= _in_seq THEN RETURN; END IF; - EXECUTE 'DELETE FROM ledgers WHERE ledger_seq = $1' USING _i; - _i := _i - 1; - END LOOP; -END; -$$ LANGUAGE plpgsql; - --- Verify correct ancestry of ledgers in database: --- Table to persist last-confirmed latest ledger with proper ancestry. -CREATE TABLE IF NOT EXISTS ancestry_verified ( - ledger_seq bigint NOT NULL -); - --- Function to verify ancestry of ledgers based on ledger_hash and prev_hash. --- Upon failure, returns ledger sequence failing ancestry check. --- Otherwise, returns NULL. --- _in_full: If TRUE, verify entire table. Else verify starting from --- value in ancestry_verfied table. If no value, then start --- from lowest ledger. --- _in_persist: If TRUE, persist the latest ledger with correct ancestry. --- If an exception was raised because of failure, persist --- the latest ledger prior to that which failed. --- _in_min: If set and _in_full is not true, the starting ledger from which --- to verify. --- _in_max: If set and _in_full is not true, the latest ledger to verify. -CREATE OR REPLACE FUNCTION check_ancestry ( - _in_full bool = FALSE, - _in_persist bool = TRUE, - _in_min bigint = NULL, - _in_max bigint = NULL -) RETURNS bigint AS $$ -DECLARE - _min bigint; - _max bigint; - _last_verified bigint; - _parent ledgers; - _current ledgers; - _cursor refcursor; -BEGIN - IF _in_full IS TRUE AND - (_in_min IS NOT NULL) OR (_in_max IS NOT NULL) THEN - RAISE 'Cannot specify manual range and do full check.'; - END IF; - - IF _in_min IS NOT NULL THEN - _min := _in_min; - ELSIF _in_full IS NOT TRUE THEN - _last_verified := (SELECT ledger_seq FROM ancestry_verified); - IF _last_verified IS NULL THEN - _min := min_ledger(); - ELSE - _min := _last_verified + 1; - END IF; - ELSE - _min := min_ledger(); - END IF; - EXECUTE 'SELECT * FROM ledgers WHERE ledger_seq = $1' - INTO _parent USING _min - 1; - IF _last_verified IS NOT NULL AND _parent IS NULL THEN - RAISE 'Verified ledger % doesn''t exist.', _last_verified; - END IF; - - IF _in_max IS NOT NULL THEN - _max := _in_max; - ELSE - _max := max_ledger(); - END IF; - - OPEN _cursor FOR EXECUTE 'SELECT * - FROM ledgers - WHERE ledger_seq BETWEEN $1 AND $2 - ORDER BY ledger_seq ASC' - USING _min, _max; - LOOP - FETCH _cursor INTO _current; - IF _current IS NULL THEN EXIT; END IF; - IF _parent IS NOT NULL THEN - IF _current.prev_hash != _parent.ledger_hash THEN - CLOSE _cursor; - RETURN _current.ledger_seq; - RAISE 'Ledger ancestry failure current, parent:% %', - _current, _parent; - END IF; - END IF; - _parent := _current; - END LOOP; - CLOSE _cursor; - - IF _in_persist IS TRUE AND _parent IS NOT NULL THEN - DELETE FROM ancestry_verified; - INSERT INTO ancestry_verified VALUES (_parent.ledger_seq); - END IF; - - RETURN NULL; -END; -$$ LANGUAGE plpgsql; - --- Return number of whole seconds since the latest ledger was inserted, based --- on ledger close time (not wall clock) of the insert. --- Note that ledgers.closing_time is number of seconds since the XRP --- epoch, which is 01/01/2000 00:00:00. This in turn is 946684800 seconds --- after the UNIX epoch. This conforms to the "age" field in the --- server_info RPC call. -CREATE OR REPLACE FUNCTION age () RETURNS bigint AS $$ -BEGIN - RETURN (EXTRACT(EPOCH FROM (now())) - - (946684800 + (SELECT closing_time - FROM ledgers - ORDER BY ledger_seq DESC - LIMIT 1)))::bigint; -END; -$$ LANGUAGE plpgsql; - --- Return range of ledgers, or empty if none. This conforms to the --- "complete_ledgers" field of the server_info RPC call. Note --- that ledger gaps are prevented for reporting mode so the range --- is simply the set between the least and greatest ledgers. -CREATE OR REPLACE FUNCTION complete_ledgers () RETURNS text AS $$ -DECLARE - _min bigint := min_ledger(); - _max bigint := max_ledger(); -BEGIN - IF _min IS NULL THEN RETURN 'empty'; END IF; - IF _min = _max THEN RETURN _min; END IF; - RETURN _min || '-' || _max; -END; -$$ LANGUAGE plpgsql; - -)" - - // version 2: - // , R"(Full idempotent text of schema version 2)" - - // version 3: - // , R"(Full idempotent text of schema version 3)" - - // version 4: - // , R"(Full idempotent text of schema version 4)" - - // ... - - // version n: - // , R"(Full idempotent text of schema version n)" -}; - -std::array upgrade_schemata = { - // upgrade from version 0: - "There is no upgrade path from version 0. Instead, install " - "from full_schemata." - // upgrade from version 1 to 2: - //, R"(Text to idempotently upgrade from version 1 to 2)" - // upgrade from version 2 to 3: - //, R"(Text to idempotently upgrade from version 2 to 3)" - // upgrade from version 3 to 4: - //, R"(Text to idempotently upgrade from version 3 to 4)" - // ... - // upgrade from version n-1 to n: - //, R"(Text to idempotently upgrade from version n-1 to n)" -}; - -/** Apply schema to postgres. - * - * The schema text should contain idempotent SQL & plpgSQL statements. - * Once completed, the version of the schema will be persisted. - * - * Throws upon error. - * - * @param pool Postgres connection pool manager. - * @param schema SQL commands separated by semi-colon. - * @param currentVersion The current version of the schema on the database. - * @param schemaVersion The version that will be in place once the schema - * has been applied. - */ -void -applySchema( - std::shared_ptr const& pool, - char const* schema, - std::uint32_t currentVersion, - std::uint32_t schemaVersion) -{ - if (currentVersion != 0 && schemaVersion != currentVersion + 1) - { - assert(false); - std::stringstream ss; - ss << "Schema upgrade versions past initial deployment must increase " - "monotonically. Versions: current, target: " - << currentVersion << ", " << schemaVersion; - Throw(ss.str()); - } - - auto res = PgQuery(pool)({schema, {}}); - if (!res) - { - std::stringstream ss; - ss << "Error applying schema from version " << currentVersion << "to " - << schemaVersion << ": " << res.msg(); - Throw(ss.str()); - } - - auto cmd = boost::format(R"(SELECT set_schema_version(%u, 0))"); - res = PgQuery(pool)({boost::str(cmd % schemaVersion).c_str(), {}}); - if (!res) - { - std::stringstream ss; - ss << "Error setting schema version from " << currentVersion << " to " - << schemaVersion << ": " << res.msg(); - Throw(ss.str()); - } -} - -void -initSchema(std::shared_ptr const& pool) -{ - // Figure out what schema version, if any, is already installed. - auto res = PgQuery(pool)({version_query, {}}); - if (!res) - { - std::stringstream ss; - ss << "Error getting database schema version: " << res.msg(); - Throw(ss.str()); - } - std::uint32_t currentSchemaVersion = res.asInt(); - std::uint32_t const pendingSchemaVersion = res.asInt(0, 1); - - // Nothing to do if we are on the latest schema; - if (currentSchemaVersion == LATEST_SCHEMA_VERSION) - return; - - if (currentSchemaVersion == 0) - { - // If a fresh install has not been completed, then re-attempt - // the install of the same schema version. - std::uint32_t const freshVersion = - pendingSchemaVersion ? pendingSchemaVersion : LATEST_SCHEMA_VERSION; - // Persist that we are attempting a fresh install to the latest version. - // This protects against corruption in an aborted install that is - // followed by a fresh installation attempt with a new schema. - auto cmd = boost::format(R"(SELECT set_schema_version(0, %u))"); - res = PgQuery(pool)({boost::str(cmd % freshVersion).c_str(), {}}); - if (!res) - { - std::stringstream ss; - ss << "Error setting schema version from " << currentSchemaVersion - << " to " << freshVersion << ": " << res.msg(); - Throw(ss.str()); - } - - // Install the full latest schema. - applySchema( - pool, - full_schemata[freshVersion], - currentSchemaVersion, - freshVersion); - currentSchemaVersion = freshVersion; - } - - // Incrementally upgrade one version at a time until latest. - for (; currentSchemaVersion < LATEST_SCHEMA_VERSION; ++currentSchemaVersion) - { - applySchema( - pool, - upgrade_schemata[currentSchemaVersion], - currentSchemaVersion, - currentSchemaVersion + 1); - } -} - -} // namespace ripple -#endif diff --git a/src/ripple/core/Pg.h b/src/ripple/core/Pg.h deleted file mode 100644 index b2e8f465c30..00000000000 --- a/src/ripple/core/Pg.h +++ /dev/null @@ -1,520 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifdef RIPPLED_REPORTING -#ifndef RIPPLE_CORE_PG_H_INCLUDED -#define RIPPLE_CORE_PG_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -// These postgres structs must be freed only by the postgres API. -using pg_result_type = std::unique_ptr; -using pg_connection_type = std::unique_ptr; - -/** first: command - * second: parameter values - * - * The 2nd member takes an optional string to - * distinguish between NULL parameters and empty strings. An empty - * item corresponds to a NULL parameter. - * - * Postgres reads each parameter as a c-string, regardless of actual type. - * Binary types (bytea) need to be converted to hex and prepended with - * \x ("\\x"). - */ -using pg_params = - std::pair>>; - -/** Parameter values for pg API. */ -using pg_formatted_params = std::vector; - -/** Parameters for managing postgres connections. */ -struct PgConfig -{ - /** Maximum connections allowed to db. */ - std::size_t max_connections{std::numeric_limits::max()}; - /** Close idle connections past this duration. */ - std::chrono::seconds timeout{600}; - - /** Index of DB connection parameter names. */ - std::vector keywordsIdx; - /** DB connection parameter names. */ - std::vector keywords; - /** Index of DB connection parameter values. */ - std::vector valuesIdx; - /** DB connection parameter values. */ - std::vector values; -}; - -//----------------------------------------------------------------------------- - -/** Class that operates on postgres query results. - * - * The functions that return results do not check first whether the - * expected results are actually there. Therefore, the caller first needs - * to check whether or not a valid response was returned using the operator - * bool() overload. If number of tuples or fields are unknown, then check - * those. Each result field should be checked for null before attempting - * to return results. Finally, the caller must know the type of the field - * before calling the corresponding function to return a field. Postgres - * internally stores each result field as null-terminated strings. - */ -class PgResult -{ - // The result object must be freed using the libpq API PQclear() call. - pg_result_type result_{nullptr, [](PGresult* result) { PQclear(result); }}; - std::optional> error_; - -public: - /** Constructor for when the process is stopping. - * - */ - PgResult() - { - } - - /** Constructor for successful query results. - * - * @param result Query result. - */ - explicit PgResult(pg_result_type&& result) : result_(std::move(result)) - { - } - - /** Constructor for failed query results. - * - * @param result Query result that contains error information. - * @param conn Postgres connection that contains error information. - */ - PgResult(PGresult* result, PGconn* conn) - : error_({PQresultStatus(result), PQerrorMessage(conn)}) - { - } - - /** Return field as a null-terminated string pointer. - * - * Note that this function does not guarantee that the result struct - * exists, or that the row and fields exist, or that the field is - * not null. - * - * @param ntuple Row number. - * @param nfield Field number. - * @return Field contents. - */ - char const* - c_str(int ntuple = 0, int nfield = 0) const - { - return PQgetvalue(result_.get(), ntuple, nfield); - } - - /** Return field as equivalent to Postgres' INT type (32 bit signed). - * - * Note that this function does not guarantee that the result struct - * exists, or that the row and fields exist, or that the field is - * not null, or that the type is that requested. - - * @param ntuple Row number. - * @param nfield Field number. - * @return Field contents. - */ - std::int32_t - asInt(int ntuple = 0, int nfield = 0) const - { - return boost::lexical_cast( - PQgetvalue(result_.get(), ntuple, nfield)); - } - - /** Return field as equivalent to Postgres' BIGINT type (64 bit signed). - * - * Note that this function does not guarantee that the result struct - * exists, or that the row and fields exist, or that the field is - * not null, or that the type is that requested. - - * @param ntuple Row number. - * @param nfield Field number. - * @return Field contents. - */ - std::int64_t - asBigInt(int ntuple = 0, int nfield = 0) const - { - return boost::lexical_cast( - PQgetvalue(result_.get(), ntuple, nfield)); - } - - /** Returns whether the field is NULL or not. - * - * Note that this function does not guarantee that the result struct - * exists, or that the row and fields exist. - * - * @param ntuple Row number. - * @param nfield Field number. - * @return Whether field is NULL. - */ - bool - isNull(int ntuple = 0, int nfield = 0) const - { - return PQgetisnull(result_.get(), ntuple, nfield); - } - - /** Check whether a valid response occurred. - * - * @return Whether or not the query returned a valid response. - */ - operator bool() const - { - return result_ != nullptr; - } - - /** Message describing the query results suitable for diagnostics. - * - * If error, then the postgres error type and message are returned. - * Otherwise, "ok" - * - * @return Query result message. - */ - std::string - msg() const; - - /** Get number of rows in result. - * - * Note that this function does not guarantee that the result struct - * exists. - * - * @return Number of result rows. - */ - int - ntuples() const - { - return PQntuples(result_.get()); - } - - /** Get number of fields in result. - * - * Note that this function does not guarantee that the result struct - * exists. - * - * @return Number of result fields. - */ - int - nfields() const - { - return PQnfields(result_.get()); - } - - /** Return result status of the command. - * - * Note that this function does not guarantee that the result struct - * exists. - * - * @return - */ - ExecStatusType - status() const - { - return PQresultStatus(result_.get()); - } -}; - -/* Class that contains and operates upon a postgres connection. */ -class Pg -{ - friend class PgPool; - friend class PgQuery; - - PgConfig const& config_; - beast::Journal const j_; - bool& stop_; - std::mutex& mutex_; - - // The connection object must be freed using the libpq API PQfinish() call. - pg_connection_type conn_{nullptr, [](PGconn* conn) { PQfinish(conn); }}; - - /** Clear results from the connection. - * - * Results from previous commands must be cleared before new commands - * can be processed. This function should be called on connections - * that weren't processed completely before being reused, such as - * when being checked-in. - * - * @return whether or not connection still exists. - */ - bool - clear(); - - /** Connect to postgres. - * - * Idempotently connects to postgres by first checking whether an - * existing connection is already present. If connection is not present - * or in an errored state, reconnects to the database. - */ - void - connect(); - - /** Disconnect from postgres. */ - void - disconnect() - { - conn_.reset(); - } - - /** Execute postgres query. - * - * If parameters are included, then the command should contain only a - * single SQL statement. If no parameters, then multiple SQL statements - * delimited by semi-colons can be processed. The response is from - * the last command executed. - * - * @param command postgres API command string. - * @param nParams postgres API number of parameters. - * @param values postgres API array of parameter. - * @return Query result object. - */ - PgResult - query(char const* command, std::size_t nParams, char const* const* values); - - /** Execute postgres query with no parameters. - * - * @param command Query string. - * @return Query result object; - */ - PgResult - query(char const* command) - { - return query(command, 0, nullptr); - } - - /** Execute postgres query with parameters. - * - * @param dbParams Database command and parameter values. - * @return Query result object. - */ - PgResult - query(pg_params const& dbParams); - - /** Insert multiple records into a table using Postgres' bulk COPY. - * - * Throws upon error. - * - * @param table Name of table for import. - * @param records Records in the COPY IN format. - */ - void - bulkInsert(char const* table, std::string const& records); - -public: - /** Constructor for Pg class. - * - * @param config Config parameters. - * @param j Logger object. - * @param stop Reference to connection pool's stop flag. - * @param mutex Reference to connection pool's mutex. - */ - Pg(PgConfig const& config, - beast::Journal const j, - bool& stop, - std::mutex& mutex) - : config_(config), j_(j), stop_(stop), mutex_(mutex) - { - } -}; - -//----------------------------------------------------------------------------- - -/** Database connection pool. - * - * Allow re-use of postgres connections. Postgres connections are created - * as needed until configurable limit is reached. After use, each connection - * is placed in a container ordered by time of use. Each request for - * a connection grabs the most recently used connection from the container. - * If none are available, a new connection is used (up to configured limit). - * Idle connections are destroyed periodically after configurable - * timeout duration. - * - * This should be stored as a shared pointer so PgQuery objects can safely - * outlive it. - */ -class PgPool -{ - friend class PgQuery; - - using clock_type = std::chrono::steady_clock; - - PgConfig config_; - beast::Journal const j_; - std::mutex mutex_; - std::condition_variable cond_; - std::size_t connections_{}; - bool stop_{false}; - - /** Idle database connections ordered by timestamp to allow timing out. */ - std::multimap, std::unique_ptr> - idle_; - - /** Get a postgres connection object. - * - * Return the most recent idle connection in the pool, if available. - * Otherwise, return a new connection unless we're at the threshold. - * If so, then wait until a connection becomes available. - * - * @return Postgres object. - */ - std::unique_ptr - checkout(); - - /** Return a postgres object to the pool for reuse. - * - * If connection is healthy, place in pool for reuse. After calling this, - * the container no longer have a connection unless checkout() is called. - * - * @param pg Pg object. - */ - void - checkin(std::unique_ptr& pg); - -public: - /** Connection pool constructor. - * - * @param pgConfig Postgres config. - * @param j Logger object. - */ - PgPool(Section const& pgConfig, beast::Journal j); - - /** Initiate idle connection timer. - * - * The PgPool object needs to be fully constructed to support asynchronous - * operations. - */ - void - setup(); - - /** Prepare for process shutdown. */ - void - stop(); - - /** Disconnect idle postgres connections. */ - void - idleSweeper(); -}; - -//----------------------------------------------------------------------------- - -/** Class to query postgres. - * - * This class should be used by functions outside of this - * compilation unit for querying postgres. It automatically acquires and - * relinquishes a database connection to handle each query. - */ -class PgQuery -{ -private: - std::shared_ptr pool_; - std::unique_ptr pg_; - -public: - PgQuery() = delete; - - PgQuery(std::shared_ptr const& pool) - : pool_(pool), pg_(pool->checkout()) - { - } - - ~PgQuery() - { - pool_->checkin(pg_); - } - - /** Execute postgres query with parameters. - * - * @param dbParams Database command with parameters. - * @return Result of query, including errors. - */ - PgResult - operator()(pg_params const& dbParams) - { - if (!pg_) // It means we're stopping. Return empty result. - return PgResult(); - return pg_->query(dbParams); - } - - /** Execute postgres query with only command statement. - * - * @param command Command statement. - * @return Result of query, including errors. - */ - PgResult - operator()(char const* command) - { - return operator()(pg_params{command, {}}); - } - - /** Insert multiple records into a table using Postgres' bulk COPY. - * - * Throws upon error. - * - * @param table Name of table for import. - * @param records Records in the COPY IN format. - */ - void - bulkInsert(char const* table, std::string const& records) - { - pg_->bulkInsert(table, records); - } -}; - -//----------------------------------------------------------------------------- - -/** Create Postgres connection pool manager. - * - * @param pgConfig Configuration for Postgres. - * @param j Logger object. - * @return Postgres connection pool manager - */ -std::shared_ptr -make_PgPool(Section const& pgConfig, beast::Journal j); - -/** Initialize the Postgres schema. - * - * This function ensures that the database is running the latest version - * of the schema. - * - * @param pool Postgres connection pool manager. - */ -void -initSchema(std::shared_ptr const& pool); - -} // namespace ripple - -#endif // RIPPLE_CORE_PG_H_INCLUDED -#endif // RIPPLED_REPORTING diff --git a/src/ripple/net/DatabaseBody.h b/src/ripple/net/DatabaseBody.h deleted file mode 100644 index 4c74b879a59..00000000000 --- a/src/ripple/net/DatabaseBody.h +++ /dev/null @@ -1,182 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NET_DATABASEBODY_H -#define RIPPLE_NET_DATABASEBODY_H - -#include -#include -#include -#include -#include - -namespace ripple { - -// DatabaseBody needs to meet requirements -// from asio which is why some conventions -// used elsewhere in this code base are not -// followed. -struct DatabaseBody -{ - // Algorithm for storing buffers when parsing. - class reader; - - // The type of the @ref message::body member. - class value_type; - - /** Returns the size of the body - - @param body The database body to use - */ - static std::uint64_t - size(value_type const& body); -}; - -class DatabaseBody::value_type -{ - // This body container holds a connection to the - // database, and also caches the size when set. - - friend class reader; - friend struct DatabaseBody; - - // The cached file size - std::uint64_t fileSize_ = 0; - boost::filesystem::path path_; - std::unique_ptr conn_; - std::string batch_; - std::shared_ptr strand_; - std::mutex m_; - std::condition_variable c_; - std::uint64_t handlerCount_ = 0; - std::uint64_t part_ = 0; - bool closing_ = false; - -public: - /// Destructor - ~value_type() = default; - - /// Constructor - value_type() = default; - - /// Returns `true` if the file is open - bool - is_open() const - { - return static_cast(conn_); - } - - /// Returns the size of the file if open - std::uint64_t - size() const - { - return fileSize_; - } - - /// Close the file if open - void - close(); - - /** Open a file at the given path with the specified mode - - @param path The utf-8 encoded path to the file - - @param config The configuration settings - - @param io_service The asio context for running a strand. - - @param ec Set to the error, if any occurred - - @param j Journal. - */ - void - open( - boost::filesystem::path const& path, - Config const& config, - boost::asio::io_service& io_service, - boost::system::error_code& ec, - beast::Journal j); -}; - -/** Algorithm for storing buffers when parsing. - - Objects of this type are created during parsing - to store incoming buffers representing the body. -*/ -class DatabaseBody::reader -{ - value_type& body_; // The body we are writing to - - static constexpr std::uint32_t FLUSH_SIZE = 50000000; - static constexpr std::uint8_t MAX_HANDLERS = 3; - static constexpr std::uint16_t MAX_ROW_SIZE_PAD = 500; - -public: - // Constructor. - // - // This is called after the header is parsed and - // indicates that a non-zero sized body may be present. - // `h` holds the received message headers. - // `b` is an instance of `DatabaseBody`. - // - template - explicit reader( - boost::beast::http::header& h, - value_type& b); - - // Initializer - // - // This is called before the body is parsed and - // gives the reader a chance to do something that might - // need to return an error code. It informs us of - // the payload size (`content_length`) which we can - // optionally use for optimization. - // - // Note: boost::Beast calls init() and requires a - // boost::optional (not a std::optional) as the - // parameter. - void - init(boost::optional const&, boost::system::error_code& ec); - - // This function is called one or more times to store - // buffer sequences corresponding to the incoming body. - // - template - std::size_t - put(ConstBufferSequence const& buffers, boost::system::error_code& ec); - - void - do_put(std::string const& data); - - // This function is called when writing is complete. - // It is an opportunity to perform any final actions - // which might fail, in order to return an error code. - // Operations that might fail should not be attempted in - // destructors, since an exception thrown from there - // would terminate the program. - // - void - finish(boost::system::error_code& ec); -}; - -} // namespace ripple - -#include - -#endif // RIPPLE_NET_DATABASEBODY_H diff --git a/src/ripple/net/DatabaseDownloader.h b/src/ripple/net/DatabaseDownloader.h deleted file mode 100644 index 476939e1ff4..00000000000 --- a/src/ripple/net/DatabaseDownloader.h +++ /dev/null @@ -1,77 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NET_DATABASEDOWNLOADER_H -#define RIPPLE_NET_DATABASEDOWNLOADER_H - -#include -#include - -namespace ripple { - -class DatabaseDownloader : public HTTPDownloader -{ -public: - virtual ~DatabaseDownloader() = default; - -private: - DatabaseDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j); - - static const std::uint8_t MAX_PATH_LEN = - std::numeric_limits::max(); - - std::shared_ptr - getParser( - boost::filesystem::path dstPath, - std::function complete, - boost::system::error_code& ec, - beast::Journal j) override; - - bool - checkPath(boost::filesystem::path const& dstPath) override; - - void - closeBody(std::shared_ptr p) override; - - std::uint64_t - size(std::shared_ptr p) override; - - Config const& config_; - boost::asio::io_service& io_service_; - - friend std::shared_ptr - make_DatabaseDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j); -}; - -// DatabaseDownloader must be a shared_ptr because it uses shared_from_this -std::shared_ptr -make_DatabaseDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j); - -} // namespace ripple - -#endif // RIPPLE_NET_DATABASEDOWNLOADER_H diff --git a/src/ripple/net/HTTPDownloader.h b/src/ripple/net/HTTPDownloader.h deleted file mode 100644 index 34e81ea1f06..00000000000 --- a/src/ripple/net/HTTPDownloader.h +++ /dev/null @@ -1,131 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2018 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NET_HTTPDOWNLOADER_H_INCLUDED -#define RIPPLE_NET_HTTPDOWNLOADER_H_INCLUDED - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace ripple { - -/** Provides an asynchronous HTTP[S] file downloader - */ -class HTTPDownloader : public std::enable_shared_from_this -{ -public: - using error_code = boost::system::error_code; - - bool - download( - std::string const& host, - std::string const& port, - std::string const& target, - int version, - boost::filesystem::path const& dstPath, - std::function complete, - bool ssl = true); - - void - stop(); - - virtual ~HTTPDownloader() = default; - - bool - sessionIsActive() const; - - bool - isStopping() const; - -protected: - // must be accessed through a shared_ptr - // use make_XXX functions to create - HTTPDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j); - - using parser = boost::beast::http::basic_parser; - - beast::Journal const j_; - - void - fail( - boost::filesystem::path dstPath, - boost::system::error_code const& ec, - std::string const& errMsg, - std::shared_ptr parser); - -private: - Config const& config_; - boost::asio::io_service::strand strand_; - std::unique_ptr stream_; - boost::beast::flat_buffer read_buf_; - std::atomic stop_; - - // Used to protect sessionActive_ - mutable std::mutex m_; - bool sessionActive_; - std::condition_variable c_; - - void - do_session( - std::string host, - std::string port, - std::string target, - int version, - boost::filesystem::path dstPath, - std::function complete, - bool ssl, - boost::asio::yield_context yield); - - virtual std::shared_ptr - getParser( - boost::filesystem::path dstPath, - std::function complete, - boost::system::error_code& ec, - beast::Journal j) = 0; - - virtual bool - checkPath(boost::filesystem::path const& dstPath) = 0; - - virtual void - closeBody(std::shared_ptr p) = 0; - - virtual uint64_t - size(std::shared_ptr p) = 0; -}; - -} // namespace ripple - -#endif diff --git a/src/ripple/net/HTTPStream.h b/src/ripple/net/HTTPStream.h deleted file mode 100644 index f5aaaa48a81..00000000000 --- a/src/ripple/net/HTTPStream.h +++ /dev/null @@ -1,165 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NET_HTTPSTREAM_H_INCLUDED -#define RIPPLE_NET_HTTPSTREAM_H_INCLUDED - -#include -#include - -#include -#include -#include -#include - -#include - -namespace ripple { - -class HTTPStream -{ -public: - using request = boost::beast::http::request; - using parser = boost::beast::http::basic_parser; - - virtual ~HTTPStream() = default; - - [[nodiscard]] virtual boost::asio::ip::tcp::socket& - getStream() = 0; - - [[nodiscard]] virtual bool - connect( - std::string& errorOut, - std::string const& host, - std::string const& port, - boost::asio::yield_context& yield) = 0; - - virtual void - asyncWrite( - request& req, - boost::asio::yield_context& yield, - boost::system::error_code& ec) = 0; - - virtual void - asyncRead( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) = 0; - - virtual void - asyncReadSome( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) = 0; -}; - -class SSLStream : public HTTPStream -{ -public: - SSLStream( - Config const& config, - boost::asio::io_service::strand& strand, - beast::Journal j); - - virtual ~SSLStream() = default; - - boost::asio::ip::tcp::socket& - getStream() override; - - bool - connect( - std::string& errorOut, - std::string const& host, - std::string const& port, - boost::asio::yield_context& yield) override; - - void - asyncWrite( - request& req, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - - void - asyncRead( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - - void - asyncReadSome( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - -private: - HTTPClientSSLContext ssl_ctx_; - std::optional> - stream_; - boost::asio::io_service::strand& strand_; -}; - -class RawStream : public HTTPStream -{ -public: - RawStream(boost::asio::io_service::strand& strand); - - virtual ~RawStream() = default; - - boost::asio::ip::tcp::socket& - getStream() override; - - bool - connect( - std::string& errorOut, - std::string const& host, - std::string const& port, - boost::asio::yield_context& yield) override; - - void - asyncWrite( - request& req, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - - void - asyncRead( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - - void - asyncReadSome( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) override; - -private: - std::optional stream_; - boost::asio::io_service::strand& strand_; -}; - -} // namespace ripple - -#endif // RIPPLE_NET_HTTPSTREAM_H diff --git a/src/ripple/net/ShardDownloader.md b/src/ripple/net/ShardDownloader.md deleted file mode 100644 index d961df61c65..00000000000 --- a/src/ripple/net/ShardDownloader.md +++ /dev/null @@ -1,311 +0,0 @@ -# Shard Downloader - -## Overview - -This document describes mechanics of the `HTTPDownloader`, a class that performs -the task of downloading shards from remote web servers via HTTP. The downloader -utilizes a strand (`boost::asio::io_service::strand`) to ensure that downloads -are never executed concurrently. Hence, if a download is in progress when -another download is initiated, the second download will be queued and invoked -only when the first download is completed. - -## Motivation - -In March 2020 the downloader was modified to include some key features: - -- The ability to stop downloads during a graceful shutdown. -- The ability to resume partial downloads after a crash or shutdown. - -This document was created to document the changes introduced by this change. - -## Classes - -Much of the shard downloading process concerns the following classes: - -- `HTTPDownloader` - - This is a generic class designed for serially executing downloads via HTTP. - -- `ShardArchiveHandler` - - This class uses the `HTTPDownloader` to fetch shards from remote web servers. - Additionally, the archive handler performs validity checks on the downloaded - files and imports the validated files into the local shard store. - - The `ShardArchiveHandler` exposes a simple public interface: - - ```C++ - /** Add an archive to be downloaded and imported. - @param shardIndex the index of the shard to be imported. - @param url the location of the archive. - @return `true` if successfully added. - @note Returns false if called while downloading. - */ - bool - add(std::uint32_t shardIndex, std::pair&& url); - - /** Starts downloading and importing archives. */ - bool - start(); - ``` - - When a client submits a `download_shard` command via the RPC interface, each - of the requested files is registered with the handler via the `add` method. - After all the files have been registered, the handler's `start` method is - invoked, which in turn creates an instance of the `HTTPDownloader` and begins - the first download. When the download is completed, the downloader invokes - the handler's `complete` method, which will initiate the download of the next - file, or simply return if there are no more downloads to process. When - `complete` is invoked with no remaining files to be downloaded, the handler - and downloader are not destroyed automatically, but persist for the duration - of the application to assist with graceful shutdowns. - -- `DatabaseBody` - - This class defines a custom message body type, allowing an - `http::response_parser` to write to an SQLite database rather than to a flat - file. This class is discussed in further detail in the Recovery section. - -## Graceful Shutdowns & Recovery - -This section describes in greater detail how the shutdown and recovery features -of the downloader are implemented in C++ using the `boost::asio` framework. - -##### Member Variables: - -The variables shown here are members of the `HTTPDownloader` class and -will be used in the following code examples. - -```c++ -std::unique_ptr stream_; -std::condition_variable c_; -std::atomic stop_; -``` - -### Graceful Shutdowns - -##### Thread 1: - -A graceful shutdown begins when the `stop()` method of the -`ShardArchiveHandler` is invoked: - -```c++ -void -ShardArchiveHandler::stop() -{ - std::lock_guard lock(m_); - - if (downloader_) - { - downloader_->stop(); - downloader_.reset(); - } - - stopped(); -} -``` - -Inside of `HTTPDownloader::stop()`, if a download is currently in progress, -the `stop_` member variable is set and the thread waits for the -download to stop: - -```c++ -void -HTTPDownloader::stop() -{ - std::unique_lock lock(m_); - - stop_ = true; - - if(sessionActive_) - { - // Wait for the handler to exit. - c_.wait(lock, - [this]() - { - return !sessionActive_; - }); - } -} -``` - -##### Thread 2: - -The graceful shutdown is realized when the thread executing the download polls -`stop_` after this variable has been set to `true`. Polling occurs -while the file is being downloaded, in between calls to `async_read_some()`. The -stop takes effect when the socket is closed and the handler function ( -`do_session()` ) is exited. - -```c++ -void HTTPDownloader::do_session() -{ - - // (Connection initialization logic) . . . - - - // (In between calls to async_read_some): - if(stop_.load()) - { - close(p); - return exit(); - } - - // . . . - - break; -} -``` - -### Recovery - -Persisting the current state of both the archive handler and the downloader is -achieved by leveraging an SQLite database rather than flat files, as the -database protects against data corruption that could result from a system crash. - -##### ShardArchiveHandler - -Although `HTTPDownloader` is a generic class that could be used to download a -variety of file types, currently it is used exclusively by the -`ShardArchiveHandler` to download shards. In order to provide resilience, the -`ShardArchiveHandler` will use an SQLite database to preserve its current state -whenever there are active, paused, or queued downloads. The `shard_db` section -in the configuration file allows users to specify the location of the database -to use for this purpose. - -###### SQLite Table Format - -| Index | URL | -|:-----:|:-----------------------------------:| -| 1 | https://example.com/1.tar.lz4 | -| 2 | https://example.com/2.tar.lz4 | -| 5 | https://example.com/5.tar.lz4 | - -##### HTTPDownloader - -While the archive handler maintains a list of all partial and queued downloads, -the `HTTPDownloader` stores the raw bytes of the file currently being -downloaded. The partially downloaded file will be represented as one or more -`BLOB` entries in an SQLite database. As the maximum size of a `BLOB` entry is -currently limited to roughly 2.1 GB, a 5 GB shard file for instance will occupy -three database entries upon completion. - -###### SQLite Table Format - -Since downloads execute serially by design, the entries in this table always -correspond to the contents of a single file. - -| Bytes | size | Part | -|:------:|:----------:|:----:| -| 0x... | 2147483647 | 0 | -| 0x... | 2147483647 | 1 | -| 0x... | 705032706 | 2 | - -##### Config File Entry -The `download_path` field of the `shard_db` entry is used to determine where to -store the recovery database. If this field is omitted, the `path` field will be -used instead. - -```dosini -# This is the persistent datastore for shards. It is important for the health -# of the network that rippled operators shard as much as practical. -# NuDB requires SSD storage. Helpful information can be found on -# https://xrpl.org/history-sharding.html -[shard_db] -type=NuDB -path=/var/lib/rippled/db/shards/nudb -download_path=/var/lib/rippled/db/shards/ -max_historical_shards=50 -``` - -##### Resuming Partial Downloads -When resuming downloads after a shutdown, crash, or other interruption, the -`HTTPDownloader` will utilize the `range` field of the HTTP header to download -only the remainder of the partially downloaded file. - -```C++ -auto downloaded = getPartialFileSize(); -auto total = getTotalFileSize(); - -http::request req {http::verb::head, - target, - version}; - -if (downloaded < total) -{ - // If we already downloaded 1000 bytes to the database, - // the range header will look like: - // Range: "bytes=1000-" - req.set(http::field::range, "bytes=" + to_string(downloaded) + "-"); -} -else if(downloaded == total) -{ - // Download is already complete. (Interruption must - // have occurred after file was downloaded but before - // the state file was updated.) -} -else -{ - // The size of the partially downloaded file exceeds - // the total download size. Error condition. Handle - // appropriately. -} -``` - -##### DatabaseBody - -Previously, the `HTTPDownloader` leveraged an `http::response_parser` -instantiated with an `http::file_body`. The `file_body` class declares a nested -type, `reader`, which does the task of writing HTTP message payloads -(constituting a requested file) to the filesystem. In order for the -`http::response_parser` to interface with the database, we implement a custom -body type that declares a nested `reader` type which has been outfitted to -persist octects received from the remote host to a local SQLite database. The -code snippet below illustrates the customization points available to -user-defined body types: - -```C++ -/// Defines a Body type -struct body -{ - /// This determines the return type of the `message::body` member function - using value_type = ...; - - /// An optional function, returns the body's payload size (which may be - /// zero) - static - std::uint64_t - size(value_type const& v); - - /// The algorithm used for extracting buffers - class reader; - - /// The algorithm used for inserting buffers - class writer; -} - -``` -Note that the `DatabaseBody` class is specifically designed to work with `asio` -and follows `asio` conventions. - -The method invoked to write data to the filesystem (or SQLite database in our -case) has the following signature: - -```C++ -std::size_t -body::reader::put(ConstBufferSequence const& buffers, error_code& ec); -``` - -## Sequence Diagram - -This sequence diagram demonstrates a scenario wherein the `ShardArchiveHandler` -leverages the state persisted in the database to recover from a crash and resume -the requested downloads. - -![alt_text](./images/interrupt_sequence.png "Resuming downloads post abort") - -## State Diagram - -This diagram illustrates the various states of the Shard Downloader module. - -![alt_text](./images/states.png "Shard Downloader states") diff --git a/src/ripple/net/impl/DatabaseBody.ipp b/src/ripple/net/impl/DatabaseBody.ipp deleted file mode 100644 index 875a883527f..00000000000 --- a/src/ripple/net/impl/DatabaseBody.ipp +++ /dev/null @@ -1,232 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -namespace ripple { - -inline void -DatabaseBody::value_type::close() -{ - { - std::unique_lock lock(m_); - - // Stop all scheduled and currently - // executing handlers before closing. - if (handlerCount_) - { - closing_ = true; - - auto predicate = [&] { return !handlerCount_; }; - c_.wait(lock, predicate); - } - - conn_.reset(); - } -} - -inline void -DatabaseBody::value_type::open( - boost::filesystem::path const& path, - Config const& config, - boost::asio::io_service& io_service, - boost::system::error_code& ec, - beast::Journal j) -{ - strand_.reset(new boost::asio::io_service::strand(io_service)); - path_ = path; - - auto setup = setup_DatabaseCon(config); - setup.dataDir = path.parent_path(); - setup.useGlobalPragma = false; - - auto [conn, size] = openDatabaseBodyDb(setup, path, j); - conn_ = std::move(conn); - if (size) - fileSize_ = *size; -} - -// This is called from message::payload_size -inline std::uint64_t -DatabaseBody::size(value_type const& body) -{ - // Forward the call to the body - return body.size(); -} - -// We don't do much in the reader constructor since the -// database is already open. -// -template -DatabaseBody::reader::reader( - boost::beast::http::header&, - value_type& body) - : body_(body) -{ -} - -// We don't do anything with content_length but a sophisticated -// application might check available space on the device -// to see if there is enough room to store the body. -inline void -DatabaseBody::reader::init( - boost::optional const& /*content_length*/, - boost::system::error_code& ec) -{ - // The connection must already be available for writing - assert(body_.conn_); - - // The error_code specification requires that we - // either set the error to some value, or set it - // to indicate no error. - // - // We don't do anything fancy so set "no error" - ec = {}; -} - -// This will get called one or more times with body buffers -// -template -std::size_t -DatabaseBody::reader::put( - ConstBufferSequence const& buffers, - boost::system::error_code& ec) -{ - // This function must return the total number of - // bytes transferred from the input buffers. - std::size_t nwritten = 0; - - // Loop over all the buffers in the sequence, - // and write each one to the database. - for (auto it = buffer_sequence_begin(buffers); - it != buffer_sequence_end(buffers); - ++it) - { - boost::asio::const_buffer buffer = *it; - - body_.batch_.append( - static_cast(buffer.data()), buffer.size()); - - // Write this buffer to the database - if (body_.batch_.size() > FLUSH_SIZE) - { - bool post = true; - - { - std::lock_guard lock(body_.m_); - - if (body_.handlerCount_ >= MAX_HANDLERS) - post = false; - else - ++body_.handlerCount_; - } - - if (post) - { - body_.strand_->post( - [data = body_.batch_, this] { this->do_put(data); }); - - body_.batch_.clear(); - } - } - - nwritten += it->size(); - } - - // Indicate success - // This is required by the error_code specification - ec = {}; - - return nwritten; -} - -inline void -DatabaseBody::reader::do_put(std::string const& data) -{ - using namespace boost::asio; - - { - std::unique_lock lock(body_.m_); - - // The download is being halted. - if (body_.closing_) - { - if (--body_.handlerCount_ == 0) - { - lock.unlock(); - body_.c_.notify_one(); - } - - return; - } - } - - auto path = body_.path_.string(); - - { - auto db = body_.conn_->checkoutDb(); - body_.part_ = databaseBodyDoPut( - *db, data, path, body_.fileSize_, body_.part_, MAX_ROW_SIZE_PAD); - } - - bool const notify = [this] { - std::lock_guard lock(body_.m_); - return --body_.handlerCount_ == 0; - }(); - - if (notify) - body_.c_.notify_one(); -} - -// Called after writing is done when there's no error. -inline void -DatabaseBody::reader::finish(boost::system::error_code& ec) -{ - { - std::unique_lock lock(body_.m_); - - // Wait for scheduled DB writes - // to complete. - if (body_.handlerCount_) - { - auto predicate = [&] { return !body_.handlerCount_; }; - body_.c_.wait(lock, predicate); - } - } - - std::ofstream fout; - fout.open(body_.path_.string(), std::ios::binary | std::ios::out); - - { - auto db = body_.conn_->checkoutDb(); - databaseBodyFinish(*db, fout); - } - - // Flush any pending data that hasn't - // been been written to the DB. - if (body_.batch_.size()) - { - fout.write(body_.batch_.data(), body_.batch_.size()); - body_.batch_.clear(); - } - - fout.close(); -} - -} // namespace ripple diff --git a/src/ripple/net/impl/DatabaseDownloader.cpp b/src/ripple/net/impl/DatabaseDownloader.cpp deleted file mode 100644 index 52388cf0d1d..00000000000 --- a/src/ripple/net/impl/DatabaseDownloader.cpp +++ /dev/null @@ -1,93 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -namespace ripple { - -std::shared_ptr -make_DatabaseDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j) -{ - return std::shared_ptr( - new DatabaseDownloader(io_service, config, j)); -} - -DatabaseDownloader::DatabaseDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j) - : HTTPDownloader(io_service, config, j) - , config_(config) - , io_service_(io_service) -{ -} - -auto -DatabaseDownloader::getParser( - boost::filesystem::path dstPath, - std::function complete, - boost::system::error_code& ec, - beast::Journal j) -> std::shared_ptr -{ - using namespace boost::beast; - - auto p = std::make_shared>(); - p->body_limit(std::numeric_limits::max()); - p->get().body().open(dstPath, config_, io_service_, ec, j); - - if (ec) - p->get().body().close(); - - return p; -} - -bool -DatabaseDownloader::checkPath(boost::filesystem::path const& dstPath) -{ - return dstPath.string().size() <= MAX_PATH_LEN; -} - -void -DatabaseDownloader::closeBody(std::shared_ptr p) -{ - using namespace boost::beast; - - auto databaseBodyParser = - std::dynamic_pointer_cast>(p); - assert(databaseBodyParser); - - databaseBodyParser->get().body().close(); -} - -std::uint64_t -DatabaseDownloader::size(std::shared_ptr p) -{ - using namespace boost::beast; - - auto databaseBodyParser = - std::dynamic_pointer_cast>(p); - assert(databaseBodyParser); - - return databaseBodyParser->get().body().size(); -} - -} // namespace ripple diff --git a/src/ripple/net/impl/HTTPDownloader.cpp b/src/ripple/net/impl/HTTPDownloader.cpp deleted file mode 100644 index 43bf8d92589..00000000000 --- a/src/ripple/net/impl/HTTPDownloader.cpp +++ /dev/null @@ -1,340 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include - -namespace ripple { - -HTTPDownloader::HTTPDownloader( - boost::asio::io_service& io_service, - Config const& config, - beast::Journal j) - : j_(j) - , config_(config) - , strand_(io_service) - , stop_(false) - , sessionActive_(false) -{ -} - -bool -HTTPDownloader::download( - std::string const& host, - std::string const& port, - std::string const& target, - int version, - boost::filesystem::path const& dstPath, - std::function complete, - bool ssl) -{ - if (!checkPath(dstPath)) - return false; - - if (stop_) - return true; - - { - std::lock_guard lock(m_); - sessionActive_ = true; - } - - if (!strand_.running_in_this_thread()) - strand_.post(std::bind( - &HTTPDownloader::download, - shared_from_this(), - host, - port, - target, - version, - dstPath, - complete, - ssl)); - else - boost::asio::spawn( - strand_, - std::bind( - &HTTPDownloader::do_session, - shared_from_this(), - host, - port, - target, - version, - dstPath, - complete, - ssl, - std::placeholders::_1)); - return true; -} - -void -HTTPDownloader::do_session( - std::string const host, - std::string const port, - std::string const target, - int version, - boost::filesystem::path dstPath, - std::function complete, - bool ssl, - boost::asio::yield_context yield) -{ - using namespace boost::asio; - using namespace boost::beast; - - boost::system::error_code ec; - bool skip = false; - - ////////////////////////////////////////////// - // Define lambdas for encapsulating download - // operations: - auto close = [&](auto p) { - closeBody(p); - - // Gracefully close the stream - stream_->getStream().shutdown(socket_base::shutdown_both, ec); - if (ec == boost::asio::error::eof) - ec.assign(0, ec.category()); - if (ec) - { - // Most web servers don't bother with performing - // the SSL shutdown handshake, for speed. - JLOG(j_.trace()) << "shutdown: " << ec.message(); - } - - // The stream cannot be reused - stream_.reset(); - }; - - // When the downloader is being stopped - // because the server is shutting down, - // this method notifies a caller of `onStop` - // (`RPC::ShardArchiveHandler` to be specific) - // that the session has ended. - auto exit = [this, &dstPath, complete] { - if (!stop_) - complete(std::move(dstPath)); - - std::lock_guard lock(m_); - sessionActive_ = false; - c_.notify_one(); - }; - - auto failAndExit = [&exit, &dstPath, complete, &ec, this]( - std::string const& errMsg, auto p) { - fail(dstPath, ec, errMsg, p); - exit(); - }; - // end lambdas - //////////////////////////////////////////////////////////// - - if (stop_.load()) - return exit(); - - auto p = this->getParser(dstPath, complete, ec, j_); - if (ec) - return failAndExit("getParser", p); - - ////////////////////////////////////////////// - // Prepare for download and establish the - // connection: - if (ssl) - stream_ = std::make_unique(config_, strand_, j_); - else - stream_ = std::make_unique(strand_); - - std::string error; - if (!stream_->connect(error, host, port, yield)) - return failAndExit(error, p); - - // Set up an HTTP HEAD request message to find the file size - http::request req{http::verb::head, target, version}; - req.set(http::field::host, host); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - - std::uint64_t const rangeStart = size(p); - - // Requesting a portion of the file - if (rangeStart) - { - req.set( - http::field::range, - (boost::format("bytes=%llu-") % rangeStart).str()); - } - - stream_->asyncWrite(req, yield, ec); - if (ec) - return failAndExit("async_write", p); - - { - // Read the response - http::response_parser connectParser; - connectParser.skip(true); - stream_->asyncRead(read_buf_, connectParser, yield, ec); - if (ec) - return failAndExit("async_read", p); - - // Range request was rejected - if (connectParser.get().result() == http::status::range_not_satisfiable) - { - req.erase(http::field::range); - - stream_->asyncWrite(req, yield, ec); - if (ec) - return failAndExit("async_write_range_verify", p); - - http::response_parser rangeParser; - rangeParser.skip(true); - - stream_->asyncRead(read_buf_, rangeParser, yield, ec); - if (ec) - return failAndExit("async_read_range_verify", p); - - // The entire file is downloaded already. - if (rangeParser.content_length() == rangeStart) - skip = true; - else - return failAndExit("range_not_satisfiable", p); - } - else if ( - rangeStart && - connectParser.get().result() != http::status::partial_content) - { - ec.assign( - boost::system::errc::not_supported, - boost::system::generic_category()); - - return failAndExit("Range request ignored", p); - } - else if (auto len = connectParser.content_length()) - { - try - { - // Ensure sufficient space is available - if (*len > space(dstPath.parent_path()).available) - { - return failAndExit( - "Insufficient disk space for download", p); - } - } - catch (std::exception const& e) - { - return failAndExit(std::string("exception: ") + e.what(), p); - } - } - } - - if (!skip) - { - // Set up an HTTP GET request message to download the file - req.method(http::verb::get); - - if (rangeStart) - { - req.set( - http::field::range, - (boost::format("bytes=%llu-") % rangeStart).str()); - } - } - - stream_->asyncWrite(req, yield, ec); - if (ec) - return failAndExit("async_write", p); - - // end prepare and connect - //////////////////////////////////////////////////////////// - - if (skip) - p->skip(true); - - // Download the file - while (!p->is_done()) - { - if (stop_.load()) - { - close(p); - return exit(); - } - - stream_->asyncReadSome(read_buf_, *p, yield, ec); - } - - JLOG(j_.trace()) << "download completed: " << dstPath.string(); - - close(p); - exit(); -} - -void -HTTPDownloader::stop() -{ - stop_ = true; - - std::unique_lock lock(m_); - if (sessionActive_) - { - // Wait for the handler to exit. - c_.wait(lock, [this]() { return !sessionActive_; }); - } -} - -bool -HTTPDownloader::sessionIsActive() const -{ - std::lock_guard lock(m_); - return sessionActive_; -} - -bool -HTTPDownloader::isStopping() const -{ - std::lock_guard lock(m_); - return stop_; -} - -void -HTTPDownloader::fail( - boost::filesystem::path dstPath, - boost::system::error_code const& ec, - std::string const& errMsg, - std::shared_ptr parser) -{ - if (!ec) - { - JLOG(j_.error()) << errMsg; - } - else if (ec != boost::asio::error::operation_aborted) - { - JLOG(j_.error()) << errMsg << ": " << ec.message(); - } - - if (parser) - closeBody(parser); - - try - { - remove(dstPath); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what() - << " in function: " << __func__; - } -} - -} // namespace ripple diff --git a/src/ripple/net/impl/HTTPStream.cpp b/src/ripple/net/impl/HTTPStream.cpp deleted file mode 100644 index d235767739d..00000000000 --- a/src/ripple/net/impl/HTTPStream.cpp +++ /dev/null @@ -1,203 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -namespace ripple { - -SSLStream::SSLStream( - Config const& config, - boost::asio::io_service::strand& strand, - beast::Journal j) - : ssl_ctx_(config, j, boost::asio::ssl::context::tlsv12_client) - , strand_(strand) -{ -} - -boost::asio::ip::tcp::socket& -SSLStream::getStream() -{ - assert(stream_); - return stream_->next_layer(); -} - -bool -SSLStream::connect( - std::string& errorOut, - std::string const& host, - std::string const& port, - boost::asio::yield_context& yield) -{ - using namespace boost::asio; - using namespace boost::beast; - - boost::system::error_code ec; - - auto fail = [&errorOut, &ec]( - std::string const& errorIn, - std::string const& message = "") { - errorOut = errorIn + ": " + (message.empty() ? ec.message() : message); - return false; - }; - - ip::tcp::resolver resolver{strand_.context()}; - auto const endpoints = resolver.async_resolve(host, port, yield[ec]); - if (ec) - return fail("async_resolve"); - - try - { - stream_.emplace(strand_.context(), ssl_ctx_.context()); - } - catch (std::exception const& e) - { - return fail("exception", e.what()); - } - - ec = ssl_ctx_.preConnectVerify(*stream_, host); - if (ec) - return fail("preConnectVerify"); - - boost::asio::async_connect( - stream_->next_layer(), endpoints.begin(), endpoints.end(), yield[ec]); - if (ec) - return fail("async_connect"); - - ec = ssl_ctx_.postConnectVerify(*stream_, host); - if (ec) - return fail("postConnectVerify"); - - stream_->async_handshake(ssl::stream_base::client, yield[ec]); - if (ec) - return fail("async_handshake"); - - return true; -} - -void -SSLStream::asyncWrite( - request& req, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_write(*stream_, req, yield[ec]); -} - -void -SSLStream::asyncRead( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_read(*stream_, buf, p, yield[ec]); -} - -void -SSLStream::asyncReadSome( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_read_some(*stream_, buf, p, yield[ec]); -} - -RawStream::RawStream(boost::asio::io_service::strand& strand) : strand_(strand) -{ -} - -boost::asio::ip::tcp::socket& -RawStream::getStream() -{ - assert(stream_); - return *stream_; -} - -bool -RawStream::connect( - std::string& errorOut, - std::string const& host, - std::string const& port, - boost::asio::yield_context& yield) -{ - using namespace boost::asio; - using namespace boost::beast; - - boost::system::error_code ec; - - auto fail = [&errorOut, &ec]( - std::string const& errorIn, - std::string const& message = "") { - errorOut = errorIn + ": " + (message.empty() ? ec.message() : message); - return false; - }; - - ip::tcp::resolver resolver{strand_.context()}; - auto const endpoints = resolver.async_resolve(host, port, yield[ec]); - if (ec) - return fail("async_resolve"); - - try - { - stream_.emplace(strand_.context()); - } - catch (std::exception const& e) - { - return fail("exception", e.what()); - } - - boost::asio::async_connect( - *stream_, endpoints.begin(), endpoints.end(), yield[ec]); - if (ec) - return fail("async_connect"); - - return true; -} - -void -RawStream::asyncWrite( - request& req, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_write(*stream_, req, yield[ec]); -} - -void -RawStream::asyncRead( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_read(*stream_, buf, p, yield[ec]); -} - -void -RawStream::asyncReadSome( - boost::beast::flat_buffer& buf, - parser& p, - boost::asio::yield_context& yield, - boost::system::error_code& ec) -{ - boost::beast::http::async_read_some(*stream_, buf, p, yield[ec]); -} - -} // namespace ripple diff --git a/src/ripple/net/uml/interrupt_sequence.pu b/src/ripple/net/uml/interrupt_sequence.pu deleted file mode 100644 index ba046d084f8..00000000000 --- a/src/ripple/net/uml/interrupt_sequence.pu +++ /dev/null @@ -1,233 +0,0 @@ -@startuml - - -skinparam shadowing false - -/' -skinparam sequence { - ArrowColor #e1e4e8 - ActorBorderColor #e1e4e8 - DatabaseBorderColor #e1e4e8 - LifeLineBorderColor Black - LifeLineBackgroundColor #d3d6d9 - - ParticipantBorderColor DeepSkyBlue - ParticipantBackgroundColor DodgerBlue - ParticipantFontName Impact - ParticipantFontSize 17 - ParticipantFontColor #A9DCDF - - NoteBackgroundColor #6a737d - - ActorBackgroundColor #f6f8fa - ActorFontColor #6a737d - ActorFontSize 17 - ActorFontName Aapex - - EntityBackgroundColor #f6f8fa - EntityFontColor #6a737d - EntityFontSize 17 - EntityFontName Aapex - - DatabaseBackgroundColor #f6f8fa - DatabaseFontColor #6a737d - DatabaseFontSize 17 - DatabaseFontName Aapex - - CollectionsBackgroundColor #f6f8fa - ActorFontColor #6a737d - ActorFontSize 17 - ActorFontName Aapex -} - -skinparam note { - BackgroundColor #fafbfc - BorderColor #e1e4e8 -} -'/ - -'skinparam monochrome true - -actor Client as c -entity RippleNode as rn -entity ShardArchiveHandler as sa -entity SSLHTTPDownloader as d -database Database as db -collections Fileserver as s - -c -> rn: Launch RippleNode -activate rn - -c -> rn: Issue download request - -note right of c - **Download Request:** - - { - "method": "download_shard", - "params": - [ - { - "shards": - [ - {"index": 1, "url": "https://example.com/1.tar.lz4"}, - {"index": 2, "url": "https://example.com/2.tar.lz4"}, - {"index": 5, "url": "https://example.com/5.tar.lz4"} - ] - } - ] - } -end note - -rn -> sa: Create instance of Handler -activate sa - -rn -> sa: Add three downloads -sa -> sa: Validate requested downloads - -rn -> sa: Initiate Downloads -sa -> rn: ACK: Initiating -rn -> c: Initiating requested downloads - -sa -> db: Save state to the database\n(Processing three downloads) - -note right of db - - **ArchiveHandler State (SQLite Table):** - - | Index | URL | - | 1 | https://example.com/1.tar.lz4 | - | 2 | https://example.com/2.tar.lz4 | - | 5 | https://example.com/5.tar.lz4 | - -end note - -sa -> d: Create instance of Downloader -activate d - -group Download 1 - - note over sa - **Download 1:** - - This encapsulates the download of the first file - at URL "https://example.com/1.tar.lz4". - - end note - - sa -> d: Start download - - d -> s: Connect and request file - s -> d: Send file - d -> sa: Invoke completion handler - -end - -sa -> sa: Import and validate shard - -sa -> db: Update persisted state\n(Remove download) - -note right of db - **ArchiveHandler State:** - - | Index | URL | - | 2 | https://example.com/2.tar.lz4 | - | 5 | https://example.com/5.tar.lz4 | - -end note - -group Download 2 - - sa -> d: Start download - - d -> s: Connect and request file - -end - -rn -> rn: **RippleNode crashes** - -deactivate sa -deactivate rn -deactivate d - -c -> rn: Restart RippleNode -activate rn - -rn -> db: Detect non-empty state database - -rn -> sa: Create instance of Handler -activate sa - -sa -> db: Load state - -note right of db - **ArchiveHandler State:** - - | Index | URL | - | 2 | https://example.com/2.tar.lz4 | - | 5 | https://example.com/5.tar.lz4 | - -end note - -sa -> d: Create instance of Downloader -activate d - -sa -> sa: Resume Download 2 - -group Download 2 - - sa -> d: Start download - - d -> s: Connect and request file - s -> d: Send file - d -> sa: Invoke completion handler - -end - -sa -> sa: Import and validate shard - -sa -> db: Update persisted state \n(Remove download) - -note right of db - **ArchiveHandler State:** - - | Index | URL | - | 5 | https://example.com/5.tar.lz4 | - -end note - -group Download 3 - - sa -> d: Start download - - d -> s: Connect and request file - s -> d: Send file - d -> sa: Invoke completion handler - -end - -sa -> sa: Import and validate shard - -sa -> db: Update persisted state \n(Remove download) - -note right of db - **ArchiveHandler State:** - - ***empty*** - -end note - -sa -> db: Remove empty database - -sa -> sa: Automatically destroyed -deactivate sa - -d -> d: Destroyed via reference\ncounting -deactivate d - -c -> rn: Poll RippleNode to verify successfull\nimport of all requested shards. -c -> rn: Shutdown RippleNode - -deactivate rn - -@enduml diff --git a/src/ripple/net/uml/states.pu b/src/ripple/net/uml/states.pu deleted file mode 100644 index b5db8ee48f4..00000000000 --- a/src/ripple/net/uml/states.pu +++ /dev/null @@ -1,69 +0,0 @@ -@startuml - -state "Updating Database" as UD4 { - UD4: Update the database to reflect - UD4: the current state. -} -state "Initiating Download" as ID { - ID: Omit the range header to download - ID: the entire file. -} - -state "Evaluate Database" as ED { - ED: Determine the current state - ED: based on the contents of the - ED: database from a previous run. -} - -state "Remove Database" as RD { - RD: The database is destroyed when - RD: empty. -} - -state "Download in Progress" as DP - -state "Download Completed" as DC { - - state "Updating Database" as UD { - UD: Update the database to reflect - UD: the current state. - } - - state "Queue Check" as QC { - QC: Check the queue for any reamining - QC: downloads. - } - - [*] --> UD - UD --> QC -} - -state "Check Resume" as CR { - CR: Determine whether we're resuming - CR: a previous download or starting a - CR: new one. -} - -state "Resuming Download" as IPD { - IPD: Set the range header in the - IPD: HTTP request as needed. -} - -[*] --> ED : State DB is present at\nnode launch -ED --> RD : State DB is empty -ED --> CR : There are downloads queued -RD --> [*] - -[*] --> UD4 : Client invokes <>\ncommand -UD4 --> ID : Database updated -ID --> DP : Download started -DP --> DC : Download completed -DC --> ID : There **are** additional downloads\nqueued -DP --> [*] : A graceful shutdown is\nin progress -DC --> RD : There **are no** additional\ndownloads queued - -CR --> IPD : Resuming an interrupted\ndownload -IPD --> DP: Download started -CR --> ID : Initiating a new\ndownload - -@enduml diff --git a/src/ripple/nodestore/DatabaseShard.h b/src/ripple/nodestore/DatabaseShard.h deleted file mode 100644 index 55f864f41b7..00000000000 --- a/src/ripple/nodestore/DatabaseShard.h +++ /dev/null @@ -1,298 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NODESTORE_DATABASESHARD_H_INCLUDED -#define RIPPLE_NODESTORE_DATABASESHARD_H_INCLUDED - -#include -#include -#include -#include -#include - -#include -#include - -namespace ripple { -namespace NodeStore { - -/** A collection of historical shards - */ -class DatabaseShard : public Database -{ -public: - /** Construct a shard store - - @param scheduler The scheduler to use for performing asynchronous tasks - @param readThreads The number of asynchronous read threads to create - @param config The shard configuration section for the database - @param journal Destination for logging output - */ - DatabaseShard( - Scheduler& scheduler, - int readThreads, - Section const& config, - beast::Journal journal) - : Database(scheduler, readThreads, config, journal) - { - } - - /** Initialize the database - - @return `true` if the database initialized without error - */ - [[nodiscard]] virtual bool - init() = 0; - - /** Prepare to store a new ledger in the shard being acquired - - @param validLedgerSeq The sequence of the maximum valid ledgers - @return If a ledger should be fetched and stored, then returns the - ledger sequence of the ledger to request. Otherwise returns - std::nullopt. - Some reasons this may return std::nullopt are: all shards are - stored and full, max allowed disk space would be exceeded, or a - ledger was recently requested and not enough time has passed - between requests. - @implNote adds a new writable shard if necessary - */ - [[nodiscard]] virtual std::optional - prepareLedger(std::uint32_t validLedgerSeq) = 0; - - /** Prepare one or more shard indexes to be imported into the database - - @param shardIndexes Shard indexes to be prepared for import - @return true if all shard indexes successfully prepared for import - */ - [[nodiscard]] virtual bool - prepareShards(std::vector const& shardIndexes) = 0; - - /** Remove a previously prepared shard index for import - - @param shardIndex Shard index to be removed from import - */ - virtual void - removePreShard(std::uint32_t shardIndex) = 0; - - /** Get shard indexes being imported - - @return a string representing the shards prepared for import - */ - [[nodiscard]] virtual std::string - getPreShards() = 0; - - /** Import a shard from the shard archive handler into the - shard database. This differs from 'importDatabase' which - imports the contents of the NodeStore - - @param shardIndex Shard index to import - @param srcDir The directory to import from - @return true If the shard was successfully imported - @implNote if successful, srcDir is moved to the database directory - */ - [[nodiscard]] virtual bool - importShard( - std::uint32_t shardIndex, - boost::filesystem::path const& srcDir) = 0; - - /** Fetch a ledger from the shard store - - @param hash The key of the ledger to retrieve - @param seq The sequence of the ledger - @return The ledger if found, nullptr otherwise - */ - [[nodiscard]] virtual std::shared_ptr - fetchLedger(uint256 const& hash, std::uint32_t seq) = 0; - - /** Notifies the database that the given ledger has been - fully acquired and stored. - - @param ledger The stored ledger to be marked as complete - */ - virtual void - setStored(std::shared_ptr const& ledger) = 0; - - /** Invoke a callback on the SQLite db holding the - corresponding ledger - - @return Value returned by callback function. - */ - virtual bool - callForLedgerSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) = 0; - - /** Invoke a callback on the ledger SQLite db for the - corresponding shard - - @return Value returned by callback function. - */ - virtual bool - callForLedgerSQLByShardIndex( - std::uint32_t shardIndex, - std::function const& callback) = 0; - - /** Invoke a callback on the transaction SQLite db - for the corresponding ledger - - @return Value returned by callback function. - */ - virtual bool - callForTransactionSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) = 0; - - /** Invoke a callback on the transaction SQLite db - for the corresponding shard - - @return Value returned by callback function. - */ - virtual bool - callForTransactionSQLByShardIndex( - std::uint32_t shardIndex, - std::function const& callback) = 0; - - /** - * @brief iterateLedgerSQLsForward Checks out ledger databases for all - * shards in ascending order starting from given shard index until - * shard with the largest index visited or callback returned false. - * For each visited shard calls given callback function passing - * shard index and session with the database to it. - * @param minShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returns true, false otherwise. - */ - virtual bool - iterateLedgerSQLsForward( - std::optional minShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) = 0; - - /** - * @brief iterateTransactionSQLsForward Checks out transaction databases for - * all shards in ascending order starting from given shard index - * until shard with the largest index visited or callback returned - * false. For each visited shard calls given callback function - * passing shard index and session with the database to it. - * @param minShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returns true, false otherwise. - */ - virtual bool - iterateTransactionSQLsForward( - std::optional minShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) = 0; - - /** - * @brief iterateLedgerSQLsBack Checks out ledger databases for - * all shards in descending order starting from given shard index - * until shard with the smallest index visited or callback returned - * false. For each visited shard calls given callback function - * passing shard index and session with the database to it. - * @param maxShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returns true, false otherwise. - */ - virtual bool - iterateLedgerSQLsBack( - std::optional maxShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) = 0; - - /** - * @brief iterateTransactionSQLsBack Checks out transaction databases for - * all shards in descending order starting from given shard index - * until shard with the smallest index visited or callback returned - * false. For each visited shard calls given callback function - * passing shard index and session with the database to it. - * @param maxShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param callback Callback function to call. - * @return True if each callback function returns true, false otherwise. - */ - virtual bool - iterateTransactionSQLsBack( - std::optional maxShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) = 0; - - /** Query information about shards held - - @return Information about shards held by this node - */ - [[nodiscard]] virtual std::unique_ptr - getShardInfo() const = 0; - - /** Returns the root database directory - */ - [[nodiscard]] virtual boost::filesystem::path const& - getRootDir() const = 0; - - /** Returns a JSON object detailing the status of an ongoing - database import if one is running, otherwise an error - object. - */ - virtual Json::Value - getDatabaseImportStatus() const = 0; - - /** Initiates a NodeStore to ShardStore import and returns - the result in a JSON object. - */ - virtual Json::Value - startNodeToShard() = 0; - - /** Terminates a NodeStore to ShardStore import and returns - the result in a JSON object. - */ - virtual Json::Value - stopNodeToShard() = 0; - - /** Returns the first ledger sequence of the shard currently being imported - from the NodeStore - - @return The ledger sequence or an unseated value if no import is running - */ - virtual std::optional - getDatabaseImportSequence() const = 0; - - /** Returns the number of queued tasks - */ - [[nodiscard]] virtual size_t - getNumTasks() const = 0; -}; - -extern std::unique_ptr -make_ShardStore( - Application& app, - Scheduler& scheduler, - int readThreads, - beast::Journal j); - -} // namespace NodeStore -} // namespace ripple - -#endif diff --git a/src/ripple/nodestore/DeterministicShard.md b/src/ripple/nodestore/DeterministicShard.md deleted file mode 100644 index 70d0584567b..00000000000 --- a/src/ripple/nodestore/DeterministicShard.md +++ /dev/null @@ -1,162 +0,0 @@ -# Deterministic Database Shards - -This doc describes the standard way to assemble the database shard. -A shard assembled using this approach becomes deterministic i.e. -if two independent sides assemble a shard consisting of the same ledgers, -accounts and transactions, then they will obtain the same shard files -`nudb.dat` and `nudb.key`. The approach deals with the `NuDB` database -format only, refer to `https://github.com/vinniefalco/NuDB`. - - -## Headers - -Due to NuDB database definition, the following headers are used for -database files: - -nudb.key: -``` -char[8] Type The characters "nudb.key" -uint16 Version Holds the version number -uint64 UID Unique ID generated on creation -uint64 Appnum Application defined constant -uint16 KeySize Key size in bytes -uint64 Salt A random seed -uint64 Pepper The salt hashed -uint16 BlockSize size of a file block in bytes -uint16 LoadFactor Target fraction in 65536ths -uint8[56] Reserved Zeroes -uint8[] Reserved Zero-pad to block size -``` - -nudb.dat: -``` -char[8] Type The characters "nudb.dat" -uint16 Version Holds the version number -uint64 UID Unique ID generated on creation -uint64 Appnum Application defined constant -uint16 KeySize Key size in bytes -uint8[64] (reserved) Zeroes -``` -All of these fields are saved using network byte order -(bigendian: most significant byte first). - -To make the shard deterministic the following parameters are used -as values of header field both for `nudb.key` and `nudb.dat` files. -``` -Version 2 -UID digest(0) -Appnum digest(2) | 0x5348524400000000 /* 'SHRD' */ -KeySize 32 -Salt digest(1) -Pepper XXH64(Salt) -BlockSize 0x1000 (4096 bytes) -LoadFactor 0.5 (numeric 0x8000) -``` -Note: XXH64() is well-known hash algorithm. - -The `digest(i)` mentioned above defined as the follows: - -First, RIPEMD160 hash `H` calculated of the following structure -(the same as final Key of the shard): -``` -uint32 version Version of shard, 2 at the present -uint32 firstSeq Sequence number of first ledger in the shard -uint32 lastSeq Sequence number of last ledger in the shard -uint256 lastHash Hash of last ledger in shard -``` -there all 32-bit integers are hashed in network byte order -(bigendian: most significant byte first). - -Then, `digest(i)` is defined as the following part of the above hash `H`: -``` -digest(0) = H[0] << 56 | H[1] << 48 | ... | H[7] << 0, -digest(1) = H[8] << 56 | H[9] << 48 | ... | H[15] << 0, -digest(2) = H[16] << 24 | H[17] << 16 | ... | H[19] << 0, -``` -where `H[i]` denotes `i`-th byte of hash `H`. - - -## Contents - -After deterministic shard is created using the above mentioned headers, -it filled with objects using the following steps. - -1. All objects within the shard are visited in the order described in the -next section. Here the objects are: ledger headers, SHAmap tree nodes -including state and transaction nodes, final key. - -2. Set of all visited objects is divided into groups. Each group except of -the last contains 16384 objects in the order of their visiting. Last group -may contain less than 16384 objects. - -3. All objects within each group are sorted in according to their hashes. -Objects are sorted by increasing of their hashes, precisely, by increasing -of hex representations of hashes in lexicographic order. For example, -the following is an example of sorted hashes in their hex representation: -``` -0000000000000000000000000000000000000000000000000000000000000000 -154F29A919B30F50443A241C466691B046677C923EE7905AB97A4DBE8A5C2429 -2231553FC01D37A66C61BBEEACBB8C460994493E5659D118E19A8DDBB1444273 -272DCBFD8E4D5D786CF11A5444B30FB35435933B5DE6C660AA46E68CF0F5C441 -3C062FD9F0BCDCA31ACEBCD8E530D0BDAD1F1D1257B89C435616506A3EE6CB9E -58A0E5AE427CDDC1C7C06448E8C3E4BF718DE036D827881624B20465C3E1336F -... -``` - -4. Finally, objects added to the deterministic shard group by group in the -sorted order within each group from low to high hashes. - - -## Order of visiting objects - -The shard consists of 16384 ledgers and the final key with the hash 0. -Each ledger has the header object and two SMAmaps: state and transaction. -SHAmap is a rooted tree in which each node has maximum of 16 descendants -enumerating by indexes 0..15. Visiting each node in the SHAmap -is performing by functions visitNodes and visitDifferences implemented -in the file `ripple/shamap/impl/ShaMapSync.cpp`. - -Here is how the function visitNodes works: it visit the root at first. -Then it visit all nodes in the 1st layer, i. e. the nodes which are -immediately descendants of the root sequentially from index 0 to 15. -Then it visit all nodes in 2nd layer i.e. the nodes which are immediately -descendants the nodes from 1st layer. The order of visiting 2nd layer nodes -is the following. First, descendants of the 1st layer node with index 0 -are visited sequintially from index 0 to 15. Then descendents of 1st layer -node with index 1 are visited etc. After visiting all nodes of 2nd layer -the nodes from 3rd layer are visited etc. - -The function visitDifferences works similar to visitNodes with the following -exceptions. The first exception is that visitDifferences get 2 arguments: -current SHAmap and previous SHAmap and visit only the nodes from current -SHAmap which and not present in previous SHAmap. The second exception is -that visitDifferences visits all non-leaf nodes in the order of visitNodes -function, but all leaf nodes are visited immedeately after visiting of their -parent node sequentially from index 0 to 15. - -Finally, all objects within the shard are visited in the following order. -All ledgers are visited from the ledger with high index to the ledger with -low index in descending order. For each ledger the state SHAmap is visited -first using visitNode function for the ledger with highest index and -visitDifferences function for other ledgers. Then transaction SHAmap is visited -using visitNodes function. At last, the ledger header object is visited. -Final key of the shard is visited at the end. - - -## Tests - -To perform test to deterministic shards implementation one can enter -the following command: -``` -rippled --unittest ripple.NodeStore.DatabaseShard -``` - -The following is the right output of deterministic shards test: -``` -ripple.NodeStore.DatabaseShard DatabaseShard deterministic_shard -with backend nudb -Iteration 0: RIPEMD160[nudb.key] = F96BF2722AB2EE009FFAE4A36AAFC4F220E21951 -Iteration 0: RIPEMD160[nudb.dat] = FAE6AE84C15968B0419FDFC014931EA12A396C71 -Iteration 1: RIPEMD160[nudb.key] = F96BF2722AB2EE009FFAE4A36AAFC4F220E21951 -Iteration 1: RIPEMD160[nudb.dat] = FAE6AE84C15968B0419FDFC014931EA12A396C71 -``` diff --git a/src/ripple/nodestore/README.md b/src/ripple/nodestore/README.md deleted file mode 100644 index 7b004f67a42..00000000000 --- a/src/ripple/nodestore/README.md +++ /dev/null @@ -1,370 +0,0 @@ -# Database Documentation -* [NodeStore](#nodestore) -* [Benchmarks](#benchmarks) -* [Downloaded Shard Validation](#downloaded-shard-validation) -* [Shard Storage Paths](#shard-storage-paths) - -# NodeStore - -## Introduction - -A `NodeObject` is a simple object that the Ledger uses to store entries. It is -comprised of a type, a hash and a blob. It can be uniquely -identified by the hash, which is a 256 bit hash of the blob. The blob is a -variable length block of serialized data. The type identifies what the blob -contains. The fields are as follows: - -* `mType` - - An enumeration that determines what the blob holds. There are four - different types of objects stored. - - * **ledger** - - A ledger header. - - * **transaction** - - A signed transaction. - - * **account node** - - A node in a ledger's account state tree. - - * **transaction node** - - A node in a ledger's transaction tree. - -* `mHash` - - A 256-bit hash of the blob. - -* `mData` - - A blob containing the payload. Stored in the following format. - -|Byte | | | -|:------|:--------------------|:-------------------------| -|0...7 |unused | | -|8 |type |NodeObjectType enumeration| -|9...end|data |body of the object data | ---- -The `NodeStore` provides an interface that stores, in a persistent database, a -collection of NodeObjects that rippled uses as its primary representation of -ledger entries. All ledger entries are stored as NodeObjects and as such, need -to be persisted between launches. If a NodeObject is accessed and is not in -memory, it will be retrieved from the database. - -## Backend - -The `NodeStore` implementation provides the `Backend` abstract interface, -which lets different key/value databases to be chosen at run-time. This allows -experimentation with different engines. Improvements in the performance of the -NodeStore are a constant area of research. The database can be specified in -the configuration file [node_db] section as follows. - -One or more lines of key / value pairs - -Example: -``` -type=RocksDB -path=rocksdb -compression=1 -``` -Choices for 'type' (not case-sensitive) - -* **HyperLevelDB** - - An improved version of LevelDB (preferred). - -* **LevelDB** - - Google's LevelDB database (deprecated). - -* **none** - - Use no backend. - -* **RocksDB** - - Facebook's RocksDB database, builds on LevelDB. - -* **SQLite** - - Use SQLite. - -'path' speficies where the backend will store its data files. - -Choices for 'compression' - -* **0** off - -* **1** on (default) - - -# Benchmarks - -The `NodeStore.Timing` test is used to execute a set of read/write workloads to -compare current available nodestore backends. It can be executed with: - -``` -$rippled --unittest=NodeStoreTiming -``` - -It is also possible to use alternate DB config params by passing config strings -as `--unittest-arg`. - -## Addendum - -The discussion below refers to a `RocksDBQuick` backend that has since been -removed from the code as it was not working and not maintained. That backend -primarily used one of the several rocks `Optimize*` methods to setup the -majority of the DB options/params, whereas the primary RocksDB backend exposes -many of the available config options directly. The code for RocksDBQuick can be -found in versions of this repo 1.2 and earlier if you need to refer back to it. -The conclusions below date from about 2014 and may need revisiting based on -newer versions of RocksDB (TBD). - -## Discussion - -RocksDBQuickFactory is intended to provide a testbed for comparing potential -rocksdb performance with the existing recommended configuration in rippled.cfg. -Through various executions and profiling some conclusions are presented below. - -* If the write ahead log is enabled, insert speed soon clogs up under load. The -BatchWriter class intends to stop this from blocking the main threads by queuing -up writes and running them in a separate thread. However, rocksdb already has -separate threads dedicated to flushing the memtable to disk and the memtable is -itself an in-memory queue. The result is two queues with a guarantee of -durability in between. However if the memtable was used as the sole queue and -the rocksdb::Flush() call was manually triggered at opportune moments, possibly -just after ledger close, then that would provide similar, but more predictable -guarantees. It would also remove an unneeded thread and unnecessary memory -usage. An alternative point of view is that because there will always be many -other rippled instances running there is no need for such guarantees. The nodes -will always be available from another peer. - -* Lookup in a block was previously using binary search. With rippled's use case -it is highly unlikely that two adjacent key/values will ever be requested one -after the other. Therefore hash indexing of blocks makes much more sense. -Rocksdb has a number of options for hash indexing both memtables and blocks and -these need more testing to find the best choice. - -* The current Database implementation has two forms of caching, so the LRU cache -of blocks at Factory level does not make any sense. However, if the hash -indexing and potentially the new [bloom -filter](http://rocksdb.org/blog/1427/new-bloom-filter-format/) can provide -faster lookup for non-existent keys, then potentially the caching could exist at -Factory level. - -* Multiple runs of the benchmarks can yield surprisingly different results. This -can perhaps be attributed to the asynchronous nature of rocksdb's compaction -process. The benchmarks are artifical and create highly unlikely write load to -create the dataset to measure different read access patterns. Therefore multiple -runs of the benchmarks are required to get a feel for the effectiveness of the -changes. This contrasts sharply with the keyvadb benchmarking were highly -repeatable timings were discovered. Also realistically sized datasets are -required to get a correct insight. The number of 2,000,000 key/values (actually -4,000,000 after the two insert benchmarks complete) is too low to get a full -picture. - -* An interesting side effect of running the benchmarks in a profiler was that a -clear pattern of what RocksDB does under the hood was observable. This led to -the decision to trial hash indexing and also the discovery of the native CRC32 -instruction not being used. - -* Important point to note that is if this factory is tested with an existing set -of sst files none of the old sst files will benefit from indexing changes until -they are compacted at a future point in time. - -# Downloaded Shard Validation - -## Overview - -In order to validate shards that have been downloaded from file servers (as -opposed to shards acquired from peers), the application must confirm the -validity of the downloaded shard's last ledger. So before initiating the -download, we first confirm that we are able to retrieve the shard's last ledger -hash. The following sections describe this confirmation process in greater -detail. - -## Hash Verification - -### Flag Ledger - -Since the number of ledgers contained in each shard is always a multiple of 256, -a shard's last ledger is always a flag ledger. Conveniently, the skip list -stored within a ledger will provide us with a series of flag ledger hashes, -enabling the software to corroborate a shard's last ledger hash. We access the -skip list by calling `LedgerMaster::walkHashBySeq` and providing the sequence of -a shard's last ledger: - -```C++ -std::optional expectedHash; -expectedHash = - app_.getLedgerMaster().walkHashBySeq(lastLedgerSeq(shardIndex)); -``` - -When a user requests a shard download, the `ShardArchiveHandler` will first use -this function to retrieve the hash of the shard's last ledger. If the function -returns a hash, downloading the shard can proceed. Once the download completes, -the server can reliably retrieve this last ledger hash to complete validation of -the shard. - -### Caveats - -#### Later Ledger - -The `walkHashBySeq` function will provide the hash of a flag ledger only if the -application has stored a later ledger. When verifying the last ledger hash of a -pending shard download, if there is no later ledger stored, the download will be -deferred until a later ledger has been stored. - -We use the presence (or absence) of a validated ledger with a sequence number -later than the sequence of the shard's last ledger as a heuristic for -determining whether or not we should have the shard's last ledger hash. A later -ledger must be present in order to reliably retrieve the hash of the shard's -last ledger. The hash will only be retrieved when a later ledger is present. -Otherwise verification of the shard will be deferred. - -### Retries - -#### Retry Limit - -If the server must defer hash verification, the software will initiate a timer -that upon expiration, will re-attempt verifying the last ledger hash. We place -an upper limit on the number of attempts the server makes to achieve this -verification. When the maximum number of attempts has been reached, the download -request will fail, and the `ShardArchiveHandler` will proceed with any remaining -downloads. An attempt counts toward the limit only when we are able to get a -later validated ledger (implying a current view of the network), but are unable -to retrieve the last ledger hash. Retries that occur because no validated ledger -was available are not counted. - -# Shard Storage Paths - -## Overview - -The shard database stores validated ledgers in logical groups called shards. As -of June 2020, a shard stores 16384 ledgers by default. In order to allow users -to store shards on multiple devices, the shard database can be configured with -several file system paths. Each path provided should refer to a directory on a -distinct filesystem, and no two paths should ever correspond to the same -filesystem. Violating this restriction will cause the server to inaccurately -estimate the amount of space available for storing shards. In the absence of a -suitable platform agnostic solution, this requirement is enforced only on -Linux. However, on other platforms we employ a heuristic that issues a warning -if we suspect that this restriction is violated. - -## Configuration - -The `shard_db` and `historical_shard_paths` sections of the server's -configuration file will be used to determine where the server stores shards. -Minimally, the `shard_db` section must contain a single `path` key. -If this is the only storage path provided, all shards will be stored at this -location. If the configuration also lists one or more lines in the -`historical_shard_paths` section, all older shards will be stored at these -locations, and the `path` will be used only to store the current -and previous shards. The goal is to allow users to provide an efficient SSD for -storing recent shards, as these will be accessed more frequently, while using -large mechanical drives for storing older shards that will be accessed less -frequently. - -Below is a sample configuration snippet that provides a path for main storage -and several paths for historical storage: - -```dosini -# This is the persistent datastore for shards. It is important for the health -# of the network that server operators shard as much as practical. -# NuDB requires SSD storage. Helpful information can be found on -# https://xrpl.org/history-sharding.html -[shard_db] -type=NuDB - -# A single path for storing -# the current and previous -# shards: -# ------------------------- -path=/var/lib/rippled/db/shards/nudb - -# Path where shards are stored -# while being downloaded: -# ---------------------------- -download_path=/var/lib/rippled/db/shards/ - -# The number of historical shards to store. -# The default value is 0, which means that -# the server won't store any historical -# shards - only the current and previous -# shards will be stored. -# ------------------------------------ -max_historical_shards=100 - -# List of paths for storing older shards. -[historical_shard_paths] -/mnt/disk1 -/mnt/disk2 -/mnt/disk3 - -``` -## Shard Migration - -When a new shard (*current shard*) is confirmed by the network, the recent -shards will shift. The *previous shard* will become a *historical shard*, the -*current shard* will become the *previous shard*, and the new shard will become -the *current shard*. These are just logical labels, and the shards themselves -don't change to reflect being current, previous, or historical. However, if the -server's configuration specifies one or more paths for historical storage, -during this shift the formerly *previous shard* will be migrated to one of the -historical paths. If multiple paths are provided, the server dynamically -chooses one with sufficient space for storing the shard. - -**Note:** As of June 2020, the shard database does not store the partial shard -currently being built by live network transactions, but this is planned to -change. When this feature is implemented, the *current shard* will refer to this -partial shard, and the *previous shard* will refer to the most recently -validated shard. - -### Selecting a Historical Storage Path - -When storing historical shards, if multiple historical paths are provided, the -path to use for each shard will be selected in a random fashion. By using all -available storage devices, we create a uniform distribution of disk utilization -for disks of equivalent size, (provided that the disks are used only to store -shards). In theory, selecting devices in this manner will also increase our -chances for concurrent access to stored shards, however as of June 2020 -concurrent shard access is not implemented. Lastly, a storage path is included -in the random distribution only if it has enough storage capacity to hold the -next shard. - -## Shard Acquisition - -When the server is acquiring shard history, these acquired shards will be stored -at a path designated for historical storage (`historical_storage_path`). If no -such path is provided, acquired shards will be stored at the -`path`. - -## Storage capacity - -### Filesystem Capacity - -When the shard database updates its record of disk utilization, it trusts that -the provided historical paths refer to distinct devices, or at least distinct -filesystems. If this requirement is violated, the database will operate with an -inaccurate view of how many shards it can store. Violation of this requirement -won't necessarily impede database operations, but the database will fail to -identify scenarios wherein storing the maximum number of historical shards (as -per the 'historical_shard_count' parameter in the configuration file) would -exceed the amount of storage space available. - -### Shard Migration - -During a "recent shard shift", if the server has already reached the configured -limit of stored historical shards, instead of moving the formerly *previous -shard* to a historical drive (or keeping it at the 'path') the -shard will be dropped and removed from the filesystem. - -### Shard Acquisition - -Once the configured limit of stored historical shards has been reached, shard -acquisition halts, and no additional shards will be acquired. diff --git a/src/ripple/nodestore/ShardInfo.h b/src/ripple/nodestore/ShardInfo.h deleted file mode 100644 index 90400276f85..00000000000 --- a/src/ripple/nodestore/ShardInfo.h +++ /dev/null @@ -1,122 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NODESTORE_SHARDINFO_H_INCLUDED -#define RIPPLE_NODESTORE_SHARDINFO_H_INCLUDED - -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -/* Contains information on the status of shards for a node - */ -class ShardInfo -{ -private: - class Incomplete - { - public: - Incomplete() = delete; - Incomplete(ShardState state, std::uint32_t percentProgress) - : state_(state), percentProgress_(percentProgress) - { - } - - [[nodiscard]] ShardState - state() const noexcept - { - return state_; - } - - [[nodiscard]] std::uint32_t - percentProgress() const noexcept - { - return percentProgress_; - } - - private: - ShardState state_; - std::uint32_t percentProgress_; - }; - -public: - [[nodiscard]] NetClock::time_point const& - msgTimestamp() const - { - return msgTimestamp_; - } - - void - setMsgTimestamp(NetClock::time_point const& timestamp) - { - msgTimestamp_ = timestamp; - } - - [[nodiscard]] std::string - finalizedToString() const; - - [[nodiscard]] bool - setFinalizedFromString(std::string const& str) - { - return from_string(finalized_, str); - } - - [[nodiscard]] RangeSet const& - finalized() const - { - return finalized_; - } - - [[nodiscard]] std::string - incompleteToString() const; - - [[nodiscard]] std::map const& - incomplete() const - { - return incomplete_; - } - - // Returns true if successful or false because of a duplicate index - bool - update( - std::uint32_t shardIndex, - ShardState state, - std::uint32_t percentProgress); - - [[nodiscard]] protocol::TMPeerShardInfoV2 - makeMessage(Application& app); - -private: - // Finalized immutable shards - RangeSet finalized_; - - // Incomplete shards being acquired or finalized - std::map incomplete_; - - // Message creation time - NetClock::time_point msgTimestamp_; -}; - -} // namespace NodeStore -} // namespace ripple - -#endif diff --git a/src/ripple/nodestore/ShardPool.md b/src/ripple/nodestore/ShardPool.md deleted file mode 100644 index 2079feabb4e..00000000000 --- a/src/ripple/nodestore/ShardPool.md +++ /dev/null @@ -1,43 +0,0 @@ -# Open Shard Management - -## Overview - -Shard NuDB and SQLite databases consume server resources. This can be unnecessarily taxing on servers with many shards. The open shard management feature aims to improve the situation by managing a limited number of open shard database connections. The feature, which is integrated into the existing DatabaseShardImp and Shard classes, maintains a limited pool of open databases prioritized by their last use time stamp. The following sections describe the feature in greater detail. - -### Open Shard Management - -The open shard management feature is integrated into the DatabaseShardImp and Shard classes. As the DatabaseShardImp sweep function is periodically called, the number of finalized open shards, which constitutes the open pool, are examined. Upon the pool exceeding a pool limit, an attempt is made to close enough open shards to remain within the limit. Shards to be closed are selected based on their last use time stamp, which is automatically updated on database access. If necessary, shards will automatically open their databases when accessed. - -```C++ - if (openFinals.size() > openFinalLimit_) - { - // Try to close enough shards to be within the limit. - // Sort on largest elapsed time since last use. - std::sort( - openFinals.begin(), - openFinals.end(), - [&](std::shared_ptr const& lhsShard, - std::shared_ptr const& rhsShard) { - return lhsShard->getLastUse() > rhsShard->getLastUse(); - }); - - for (auto it{openFinals.cbegin()}; - it != openFinals.cend() && openFinals.size() > openFinalLimit_;) - { - if ((*it)->tryClose()) - it = openFinals.erase(it); - else - ++it; - } - } -``` - -### Shard - -When closing an open shard, DatabaseShardImp will call the Shard 'tryClose' function. This function will only close the shard databases if there are no outstanding references. - -DatabaseShardImp will use the Shard 'isOpen' function to determine the state of a shard's database. - -### Caveats - -The Shard class must check the state of its databases before use. Prior use assumed databases were always open, that is no longer the case with the open shard management feature. diff --git a/src/ripple/nodestore/ShardSizeTuning.md b/src/ripple/nodestore/ShardSizeTuning.md deleted file mode 100644 index bded73c43c5..00000000000 --- a/src/ripple/nodestore/ShardSizeTuning.md +++ /dev/null @@ -1,213 +0,0 @@ -# Shard size Tuning - -The purpose of this document is to compare the sizes of shards containing -varying amounts of ledgers. - -## Methodology - -One can see visually from a block explorer that a typical mainnet ledger -consists of about 30 offer transactions issued by about 8 different accounts, -and several transactions of other types. To simulate this situation and -similar situations we have constructed deterministic shards of differenet -sizes, with varying amounts of offers per ledger and varying amounts of -accounts issuing these offers. - -In the following results table, the number of ledgers per shard ranges from 256 -to 16K with the size doubling the size at each step. We considered the -following numbers of offers per ledger: 0, 1, 5, 10 and 30. Also we considered -case of 1 and 8 accounts issuing offers. For each constructed deterministic -shard we counted its size. Finally we compared doubled size of the shard with -N ledgers and the size of a shard with 2*N ledgers where othere parameters such -as number of offers and accounts are the same. This comparison is sufficient to -determine which number of ledgers per shard leads to less storage size on the -disk. - -Note that we minimize total storage size on the disk, but not the size of each -shard because data below shows that the size of a typical shard is not larger -than 10G, but sizes of modern disks, even SSDs, start from 250G. So there is -no problem to fit a single shard to a disk, even small. - - -## Raw results table - -All sizes of constructed shards are shown in the following table. -Rows corresponds to shard sizes (S) counted in ledgers, columns corresponds -to numbers of offers (O) per ledger. In each cell there are two numbers: -first number corresponds to the case of 1 account issuing offers, the second -number corresponds to 8 accounts. Each number is a size of the shard with -given parameters measured in megabytes. - -|S\O|0|1|5|10|30| -|---|---|---|---|---|---| -|256|2.2/2.2|3.4/3.3|5.3/7.3|7.7/10.9|17.1/21.9| -|512|4.4/4.5|7.0/7.0|11.2/15.6|16.4/23.7|36.9/47.9| -|1K|8.9/9.0|14.7/14.6|23.7/33.6|35.0/51.0|78.2/ 102.9| -|2K|17.8/18.0|30.5/30.7|50.4/72.2|74.3/ 111.9|166.2/ 221.0| -|4K|35.5/35.9|63.6/64.2|106.2/ 154.8|156.1/ 238.7|354.7/ 476.0| -|8K|71.1/71.9|133.4/ 134.5|222.2/ 328.1|329.1/ 511.2|754.6/ 1021.0| -|16K|142.3/ 143.9|279/9 280.8|465.7/ 698.1|696.4/ 1094.2|1590.5/ 2166.6| - -## Preliminary conclusion - -If one compares a doubled size of shard with N ledgers and a size of shard -with 2*N ledgers anywhere in the above table than the conlusion will be that -the second number is greater. For example, the following table shows the -percentage by which the second number is greater for the most interesting case -of 30 offers per ledger. The first row corresponds to the case of 1 account -issuing offers, and the second row corresponds to the case of 8 issuing -accounts. - -|A\N|256|512|1K|2K|4K|8K| -|---|---|---|---|---|---|---| -|1|8%|6%|6%|6%|7%|6%|5%| -|8|9%|7%|7%|8%|6%|7%|6%| - -The common conclusion in this model is that if one doubled the number of -the ledgers in a shard then the total disk space utilized will raise by 5-9%. - -## Adding accounts into consideration - -Previous model does not take into account that there are large number of -XRP accounts in the mainnet, and each shard should contain information -about each of these accounts. As of January 2020, there were about 1.9 million -XRP accounts, and stored information about each of them is not less than 133 -bytes. The constant 133 was obtained from debug print of rippled program when -it saves account object to the database. So the actual size of each shard from -raw table should be increased by at least 1.9M * 133 = 252.7M. Thus we obtained -the following table of shard sizes for the most interesting case (30 offers -per ledger and 8 issuing accounts) where S is shard size in ledgers and M is -shard size in megabytes - -|S|256|512|1K|2K|4K|8K|16K| -|---|---|---|---|---|---|---|---| -|M|274.6|300.6|355.6|473.7|728.7|1273.7|2419.3| - -Now we can see from the last table that even considering minimum assumption -about number of accounts and corresponding additional size of a shard, -doubled size of shard with N ledgers is larger than size of a shard with -2*N ledgers. If number of accounts increase then this inequality will be -even stronger. - -## Using mainnet data - -Next idea to improve model is to count real shard sizes from mainnet. -We used real 16k-ledgers shards with indexes from 2600 to 3600 with step 100, -and corresponding real 8k-ledgers shards. Each 16k-ledgers shard consists -of two 8k-ledgers shards which are called "corresponding". For example, -16k-ledgers shard with index 2600 consists of two 8k-ledgers shards with -indexes 5200 and 5201. - -In the following table we compare size of a 16k-ledgers shard with sum of sizes -of two corresponding 8k-ledgers shards. There we only count size of nudb.dat -file, sizes are in GB. Ratio is the size of two 8k-ledgers shards divided -to the size of 16k-ledgers shard. - -|Index|16k-ledgers|8k-ledgers sum|Ratio| -|---|---|---|---| -|2600|2.39|1.49 + 1.63 = 3.12|1.31| -|2700|2.95|1.77 + 1.94 = 3.71|1.26| -|2800|2.53|1.54 + 1.75 = 3.29|1.30| -|2900|3.83|2.26 + 2.35 = 4.61|1.20| -|3000|4.49|2.70 + 2.59 = 5.29|1.18| -|3100|3.79|2.35 + 2.25 = 4.60|1.21| -|3200|4.15|2.54 + 2.43 = 4.97|1.20| -|3300|5.19|3.23 + 2.80 = 6.03|1.16| -|3400|4.18|2.53 + 2.51 = 5.04|1.21| -|3500|5.06|2.90 + 3.04 = 5.94|1.17| -|3600|4.18|2.56 + 2.51 = 5.07|1.21| -|Average|3.89|2.35 + 2.35 = 4.70|1.21| - -Note that shard on the disk consists of 4 files each of which can be large too. -These files are nudb.dat, nudb.key, ledger.db, transaction.db. Next table is -similar to previous with the following exception: each number is total size -of these 2 files: nudb.dat and nudb.key. We decided not to count sizes of -ledger.db and transaction.db since these sizes are not permanent instead of -sizes of nudb.* which are permanent for deterministic shards. - -|Index|16k-ledgers|8k-ledgers sum|Ratio| -|---|---|---|---| -|2600|2.76|1.73 + 1.89 = 3.62|1.31| -|2700|3.40|2.05 + 2.25 = 4.30|1.26| -|2800|2.91|1.79 + 2.02 = 3.81|1.31| -|2900|4.40|2.62 + 2.71 = 5.33|1.21| -|3000|5.09|3.09 + 2.96 = 6.05|1.19| -|3100|4.29|2.69 + 2.57 = 5.26|1.23| -|3200|4.69|2.90 + 2.78 = 5.68|1.21| -|3300|5.92|3.72 + 3.21 = 6.93|1.17| -|3400|4.77|2.91 + 2.89 = 5.80|1.22| -|3500|5.73|3.31 + 3.47 = 6.78|1.18| -|3600|4.77|2.95 + 2.90 = 5.85|1.23| -|Average|4.43|2.70 + 2.70 = 5.40|1.22| - -We can see that in all tables ratio is greater then 1, so using shards with -16 ledgers is preferred. - -## Compare 16K shards and 32K shards - -To claim that shards with 16K ledgers are the best choice, we also assembled -shards with 32k ledgers per shard with indexes from 1300 to 1800 with step 50 -and corresponding shards with 16k ledgers per shard. For example, 32k-ledgers -shard 1800 correnspond to 16k-ledgers shards with indexes 3600 and 3601 etc. - -Here are result tables for these shards similar to tables from previous part. -In the first table we only take into consideration sizes of nudb.dat files. - -|Index|32k-ledgers|16k-ledgers sum|Ratio| -|---|---|---|---| -|1300|4.00|2.39 + 2.32 = 4.71|1.18| -|1350|5.23|2.95 + 3.02 = 5.97|1.14| -|1400|4.37|2.53 + 2.59 = 5.12|1.17| -|1450|7.02|3.83 + 3.98 = 7.81|1.11| -|1500|7.53|4.49 + 3.86 = 8.35|1.11| -|1550|6.85|3.79 + 3.89 = 7.68|1.12| -|1600|7.28|4.15 + 3.99 = 8.14|1.12| -|1650|8.10|5.19 + 3.76 = 8.95|1.10| -|1700|7.58|4.18 + 4.27 = 8.45|1.11| -|1750|8.95|5.06 + 4.77 = 9.83|1.10| -|1800|7.29|4.18 + 4.02 = 8.20|1.12| -|Average|6.75|3.88 + 3.68 = 7.56|1.12| - -In the second table we take into consideration total sizes of files nudb.dat -and nudb.key. - -|Index|32k-ledgers|16k-ledgers sum|Ratio| -|---|---|---|---| -|1300|4.59|2.76 + 2.68 = 5.44|1.19| -|1350|5.98|3.40 + 3.47 = 6.87|1.15| -|1400|4.99|2.91 + 2.98 = 5.89|1.18| -|1450|8.02|4.40 + 4.56 = 8.96|1.12| -|1500|8.51|5.09 + 4.39 = 9.48|1.11| -|1550|7.73|4.29 + 4.42 = 8.71|1.13| -|1600|8.20|4.69 + 4.52 = 9.21|1.12| -|1650|9.20|5.92 + 4.29 = 10.21|1.11| -|1700|8.61|4.77 + 4.87 = 9.64|1.12| -|1750|10.09|5.73 + 5.41 = 11.14|1.10| -|1800|8.27|4.77 + 4.59 = 9.36|1.13| -|Average|7.69|4.43 + 4.20 = 8.63|1.12| - -## Conclusion - -We showed that using shards with 8k ledgers leads to raising required disk size -by 22% in comparison with using shards with 16k ledgers. In the same way, -using shards with 16k ledgers leads to raising required disk space by 12% -in comparison with using shards with 32k ledgers. Note that increase ratio 12% -is much less than 22% so using 32k-ledgers shards will bring us not so much -economy in disk space. - -At the same time, size is one thing to compare but there are other aspects. -Smaller shards have an advantage that they take less time to acquire and -finalize. They also make for smaller archived shards which take less time to -download and import. Having more/smaller shards might also lead to better -database concurrency/performance. - -It is hard to maintain both size and time parameters by a single optimization -formulae because different choices for weights of size and time may lead to -different results. But using "common sense" arguments we can compare -16k-ledgers shards and 32k-ledgers as follows: using 32k-ledgers shards give us -12% advantage in size, and about 44% disadvantage in time, because average size -of 16k-ledgers shard is about 56% of average 32k-ledgers shard. At the same, -if we compare 16k-ledgers shards with 8k-ledgers, then the first has 22% -advantage in size and 39% disadvantage in time. So the balance of -advantages/disadvantages is better when we use 16k-ledgers shards. - -Thus we recommend use shards with 16K ledgers. diff --git a/src/ripple/nodestore/backend/CassandraFactory.cpp b/src/ripple/nodestore/backend/CassandraFactory.cpp deleted file mode 100644 index d13cd71827b..00000000000 --- a/src/ripple/nodestore/backend/CassandraFactory.cpp +++ /dev/null @@ -1,983 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifdef RIPPLED_REPORTING - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -void -writeCallback(CassFuture* fut, void* cbData); -void -readCallback(CassFuture* fut, void* cbData); - -class CassandraBackend : public Backend -{ -private: - // convenience function for one-off queries. For normal reads and writes, - // use the prepared statements insert_ and select_ - CassStatement* - makeStatement(char const* query, std::size_t params) - { - CassStatement* ret = cass_statement_new(query, params); - CassError rc = - cass_statement_set_consistency(ret, CASS_CONSISTENCY_QUORUM); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting query consistency: " << query - << ", result: " << rc << ", " << cass_error_desc(rc); - Throw(ss.str()); - } - return ret; - } - - beast::Journal const j_; - // size of a key - size_t const keyBytes_; - - Section const config_; - - std::atomic open_{false}; - - // mutex used for open() and close() - std::mutex mutex_; - - std::unique_ptr session_{ - nullptr, - [](CassSession* session) { - // Try to disconnect gracefully. - CassFuture* fut = cass_session_close(session); - cass_future_wait(fut); - cass_future_free(fut); - cass_session_free(session); - }}; - - // Database statements cached server side. Using these is more efficient - // than making a new statement - const CassPrepared* insert_ = nullptr; - const CassPrepared* select_ = nullptr; - - // io_context used for exponential backoff for write retries - boost::asio::io_context ioContext_; - std::optional work_; - std::thread ioThread_; - - // maximum number of concurrent in flight requests. New requests will wait - // for earlier requests to finish if this limit is exceeded - uint32_t maxRequestsOutstanding = 10000000; - std::atomic_uint32_t numRequestsOutstanding_ = 0; - - // mutex and condition_variable to limit the number of concurrent in flight - // requests - std::mutex throttleMutex_; - std::condition_variable throttleCv_; - - // writes are asynchronous. This mutex and condition_variable is used to - // wait for all writes to finish - std::mutex syncMutex_; - std::condition_variable syncCv_; - - Counters> counters_; - -public: - CassandraBackend( - size_t keyBytes, - Section const& keyValues, - beast::Journal journal) - : j_(journal), keyBytes_(keyBytes), config_(keyValues) - { - } - - ~CassandraBackend() override - { - close(); - } - - std::string - getName() override - { - return "cassandra"; - } - - bool - isOpen() override - { - return open_; - } - - // Setup all of the necessary components for talking to the database. - // Create the table if it doesn't exist already - // @param createIfMissing ignored - void - open(bool createIfMissing) override - { - if (open_) - { - assert(false); - JLOG(j_.error()) << "database is already open"; - return; - } - - std::lock_guard lock(mutex_); - CassCluster* cluster = cass_cluster_new(); - if (!cluster) - Throw( - "nodestore:: Failed to create CassCluster"); - - std::string secureConnectBundle = get(config_, "secure_connect_bundle"); - - if (!secureConnectBundle.empty()) - { - /* Setup driver to connect to the cloud using the secure connection - * bundle */ - if (cass_cluster_set_cloud_secure_connection_bundle( - cluster, secureConnectBundle.c_str()) != CASS_OK) - { - JLOG(j_.error()) << "Unable to configure cloud using the " - "secure connection bundle: " - << secureConnectBundle; - Throw( - "nodestore: Failed to connect using secure connection " - "bundle"); - return; - } - } - else - { - std::string contact_points = get(config_, "contact_points"); - if (contact_points.empty()) - { - Throw( - "nodestore: Missing contact_points in Cassandra config"); - } - CassError rc = cass_cluster_set_contact_points( - cluster, contact_points.c_str()); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting Cassandra contact_points: " - << contact_points << ", result: " << rc << ", " - << cass_error_desc(rc); - - Throw(ss.str()); - } - - int port = get(config_, "port"); - if (port) - { - rc = cass_cluster_set_port(cluster, port); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting Cassandra port: " << port - << ", result: " << rc << ", " << cass_error_desc(rc); - - Throw(ss.str()); - } - } - } - cass_cluster_set_token_aware_routing(cluster, cass_true); - CassError rc = cass_cluster_set_protocol_version( - cluster, CASS_PROTOCOL_VERSION_V4); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting cassandra protocol version: " - << ", result: " << rc << ", " << cass_error_desc(rc); - - Throw(ss.str()); - } - - std::string username = get(config_, "username"); - if (username.size()) - { - std::cout << "user = " << username - << " password = " << get(config_, "password") - << std::endl; - cass_cluster_set_credentials( - cluster, username.c_str(), get(config_, "password").c_str()); - } - - unsigned int const ioThreads = get(config_, "io_threads", 4); - maxRequestsOutstanding = - get(config_, "max_requests_outstanding", 10000000); - JLOG(j_.info()) << "Configuring Cassandra driver to use " << ioThreads - << " IO threads. Capping maximum pending requests at " - << maxRequestsOutstanding; - rc = cass_cluster_set_num_threads_io(cluster, ioThreads); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting Cassandra io threads to " - << ioThreads << ", result: " << rc << ", " - << cass_error_desc(rc); - Throw(ss.str()); - } - - rc = cass_cluster_set_queue_size_io( - cluster, - maxRequestsOutstanding); // This number needs to scale w/ the - // number of request per sec - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting Cassandra max core connections per " - "host" - << ", result: " << rc << ", " << cass_error_desc(rc); - std::cout << ss.str() << std::endl; - return; - ; - } - cass_cluster_set_request_timeout(cluster, 2000); - - std::string certfile = get(config_, "certfile"); - if (certfile.size()) - { - std::ifstream fileStream( - boost::filesystem::path(certfile).string(), std::ios::in); - if (!fileStream) - { - std::stringstream ss; - ss << "opening config file " << certfile; - Throw( - errno, std::generic_category(), ss.str()); - } - std::string cert( - std::istreambuf_iterator{fileStream}, - std::istreambuf_iterator{}); - if (fileStream.bad()) - { - std::stringstream ss; - ss << "reading config file " << certfile; - Throw( - errno, std::generic_category(), ss.str()); - } - - CassSsl* context = cass_ssl_new(); - cass_ssl_set_verify_flags(context, CASS_SSL_VERIFY_NONE); - rc = cass_ssl_add_trusted_cert(context, cert.c_str()); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error setting Cassandra ssl context: " << rc - << ", " << cass_error_desc(rc); - Throw(ss.str()); - } - - cass_cluster_set_ssl(cluster, context); - cass_ssl_free(context); - } - - std::string keyspace = get(config_, "keyspace"); - if (keyspace.empty()) - { - Throw( - "nodestore: Missing keyspace in Cassandra config"); - } - - std::string tableName = get(config_, "table_name"); - if (tableName.empty()) - { - Throw( - "nodestore: Missing table name in Cassandra config"); - } - - cass_cluster_set_connect_timeout(cluster, 10000); - - CassStatement* statement; - CassFuture* fut; - bool setupSessionAndTable = false; - while (!setupSessionAndTable) - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - session_.reset(cass_session_new()); - assert(session_); - - fut = cass_session_connect_keyspace( - session_.get(), cluster, keyspace.c_str()); - rc = cass_future_error_code(fut); - cass_future_free(fut); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "nodestore: Error connecting Cassandra session keyspace: " - << rc << ", " << cass_error_desc(rc); - JLOG(j_.error()) << ss.str(); - continue; - } - - std::stringstream query; - query << "CREATE TABLE IF NOT EXISTS " << tableName - << " ( hash blob PRIMARY KEY, object blob)"; - - statement = makeStatement(query.str().c_str(), 0); - fut = cass_session_execute(session_.get(), statement); - rc = cass_future_error_code(fut); - cass_future_free(fut); - cass_statement_free(statement); - if (rc != CASS_OK && rc != CASS_ERROR_SERVER_INVALID_QUERY) - { - std::stringstream ss; - ss << "nodestore: Error creating Cassandra table: " << rc - << ", " << cass_error_desc(rc); - JLOG(j_.error()) << ss.str(); - continue; - } - - query.str(""); - query << "SELECT * FROM " << tableName << " LIMIT 1"; - statement = makeStatement(query.str().c_str(), 0); - fut = cass_session_execute(session_.get(), statement); - rc = cass_future_error_code(fut); - cass_future_free(fut); - cass_statement_free(statement); - if (rc != CASS_OK) - { - if (rc == CASS_ERROR_SERVER_INVALID_QUERY) - { - JLOG(j_.warn()) << "table not here yet, sleeping 1s to " - "see if table creation propagates"; - continue; - } - else - { - std::stringstream ss; - ss << "nodestore: Error checking for table: " << rc << ", " - << cass_error_desc(rc); - JLOG(j_.error()) << ss.str(); - continue; - } - } - - setupSessionAndTable = true; - } - - cass_cluster_free(cluster); - - bool setupPreparedStatements = false; - while (!setupPreparedStatements) - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - std::stringstream query; - query << "INSERT INTO " << tableName - << " (hash, object) VALUES (?, ?)"; - CassFuture* prepare_future = - cass_session_prepare(session_.get(), query.str().c_str()); - - /* Wait for the statement to prepare and get the result */ - rc = cass_future_error_code(prepare_future); - - if (rc != CASS_OK) - { - /* Handle error */ - cass_future_free(prepare_future); - - std::stringstream ss; - ss << "nodestore: Error preparing insert : " << rc << ", " - << cass_error_desc(rc); - JLOG(j_.error()) << ss.str(); - continue; - } - - /* Get the prepared object from the future */ - insert_ = cass_future_get_prepared(prepare_future); - - /* The future can be freed immediately after getting the prepared - * object - */ - cass_future_free(prepare_future); - - query.str(""); - query << "SELECT object FROM " << tableName << " WHERE hash = ?"; - prepare_future = - cass_session_prepare(session_.get(), query.str().c_str()); - - /* Wait for the statement to prepare and get the result */ - rc = cass_future_error_code(prepare_future); - - if (rc != CASS_OK) - { - /* Handle error */ - cass_future_free(prepare_future); - - std::stringstream ss; - ss << "nodestore: Error preparing select : " << rc << ", " - << cass_error_desc(rc); - JLOG(j_.error()) << ss.str(); - continue; - } - - /* Get the prepared object from the future */ - select_ = cass_future_get_prepared(prepare_future); - - /* The future can be freed immediately after getting the prepared - * object - */ - cass_future_free(prepare_future); - setupPreparedStatements = true; - } - - work_.emplace(ioContext_); - ioThread_ = std::thread{[this]() { ioContext_.run(); }}; - open_ = true; - } - - // Close the connection to the database - void - close() override - { - { - std::lock_guard lock(mutex_); - if (insert_) - { - cass_prepared_free(insert_); - insert_ = nullptr; - } - if (select_) - { - cass_prepared_free(select_); - select_ = nullptr; - } - work_.reset(); - ioThread_.join(); - } - open_ = false; - } - - // Synchronously fetch the object with key key and store the result in pno - // @param key the key of the object - // @param pno object in which to store the result - // @return result status of query - Status - fetch(void const* key, std::shared_ptr* pno) override - { - JLOG(j_.trace()) << "Fetching from cassandra"; - CassStatement* statement = cass_prepared_bind(select_); - cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM); - CassError rc = cass_statement_bind_bytes( - statement, 0, static_cast(key), keyBytes_); - if (rc != CASS_OK) - { - cass_statement_free(statement); - JLOG(j_.error()) << "Binding Cassandra fetch query: " << rc << ", " - << cass_error_desc(rc); - pno->reset(); - return backendError; - } - CassFuture* fut; - do - { - fut = cass_session_execute(session_.get(), statement); - rc = cass_future_error_code(fut); - if (rc != CASS_OK) - { - std::stringstream ss; - ss << "Cassandra fetch error"; - ss << ", retrying"; - ++counters_.readRetries; - ss << ": " << cass_error_desc(rc); - JLOG(j_.warn()) << ss.str(); - } - } while (rc != CASS_OK); - - CassResult const* res = cass_future_get_result(fut); - cass_statement_free(statement); - cass_future_free(fut); - - CassRow const* row = cass_result_first_row(res); - if (!row) - { - cass_result_free(res); - pno->reset(); - return notFound; - } - cass_byte_t const* buf; - std::size_t bufSize; - rc = cass_value_get_bytes(cass_row_get_column(row, 0), &buf, &bufSize); - if (rc != CASS_OK) - { - cass_result_free(res); - pno->reset(); - JLOG(j_.error()) << "Cassandra fetch result error: " << rc << ", " - << cass_error_desc(rc); - ++counters_.readErrors; - return backendError; - } - - nudb::detail::buffer bf; - std::pair uncompressed = - nodeobject_decompress(buf, bufSize, bf); - DecodedBlob decoded(key, uncompressed.first, uncompressed.second); - cass_result_free(res); - - if (!decoded.wasOk()) - { - pno->reset(); - JLOG(j_.error()) << "Cassandra error decoding result: " << rc - << ", " << cass_error_desc(rc); - ++counters_.readErrors; - return dataCorrupt; - } - *pno = decoded.createObject(); - return ok; - } - - struct ReadCallbackData - { - CassandraBackend& backend; - const void* const key; - std::shared_ptr& result; - std::condition_variable& cv; - - std::atomic_uint32_t& numFinished; - size_t batchSize; - - ReadCallbackData( - CassandraBackend& backend, - const void* const key, - std::shared_ptr& result, - std::condition_variable& cv, - std::atomic_uint32_t& numFinished, - size_t batchSize) - : backend(backend) - , key(key) - , result(result) - , cv(cv) - , numFinished(numFinished) - , batchSize(batchSize) - { - } - - ReadCallbackData(ReadCallbackData const& other) = default; - }; - - std::pair>, Status> - fetchBatch(std::vector const& hashes) override - { - std::size_t const numHashes = hashes.size(); - JLOG(j_.trace()) << "Fetching " << numHashes - << " records from Cassandra"; - std::atomic_uint32_t numFinished = 0; - std::condition_variable cv; - std::mutex mtx; - std::vector> results{numHashes}; - std::vector> cbs; - cbs.reserve(numHashes); - for (std::size_t i = 0; i < hashes.size(); ++i) - { - cbs.push_back(std::make_shared( - *this, - static_cast(hashes[i]), - results[i], - cv, - numFinished, - numHashes)); - read(*cbs[i]); - } - assert(results.size() == cbs.size()); - - std::unique_lock lck(mtx); - cv.wait(lck, [&numFinished, &numHashes]() { - return numFinished == numHashes; - }); - - JLOG(j_.trace()) << "Fetched " << numHashes - << " records from Cassandra"; - return {results, ok}; - } - - void - read(ReadCallbackData& data) - { - CassStatement* statement = cass_prepared_bind(select_); - cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM); - CassError rc = cass_statement_bind_bytes( - statement, 0, static_cast(data.key), keyBytes_); - if (rc != CASS_OK) - { - size_t batchSize = data.batchSize; - if (++(data.numFinished) == batchSize) - data.cv.notify_all(); - cass_statement_free(statement); - JLOG(j_.error()) << "Binding Cassandra fetch query: " << rc << ", " - << cass_error_desc(rc); - return; - } - - CassFuture* fut = cass_session_execute(session_.get(), statement); - - cass_statement_free(statement); - - cass_future_set_callback(fut, readCallback, static_cast(&data)); - cass_future_free(fut); - } - - struct WriteCallbackData - { - CassandraBackend* backend; - // The shared pointer to the node object must exist until it's - // confirmed persisted. Otherwise, it can become deleted - // prematurely if other copies are removed from caches. - std::shared_ptr no; - std::optional e; - std::pair compressed; - std::chrono::steady_clock::time_point begin; - // The data is stored in this buffer. The void* in the above member - // is a pointer into the below buffer - nudb::detail::buffer bf; - std::atomic& totalWriteRetries; - - uint32_t currentRetries = 0; - - WriteCallbackData( - CassandraBackend* f, - std::shared_ptr const& nobj, - std::atomic& retries) - : backend(f), no(nobj), totalWriteRetries(retries) - { - e.emplace(no); - - compressed = - NodeStore::nodeobject_compress(e->getData(), e->getSize(), bf); - } - }; - - void - write(WriteCallbackData& data, bool isRetry) - { - { - // We limit the total number of concurrent inflight writes. This is - // a client side throttling to prevent overloading the database. - // This is mostly useful when the very first ledger is being written - // in full, which is several millions records. On sufficiently large - // Cassandra clusters, this throttling is not needed; the default - // value of maxRequestsOutstanding is 10 million, which is more - // records than are present in any single ledger - std::unique_lock lck(throttleMutex_); - if (!isRetry && numRequestsOutstanding_ > maxRequestsOutstanding) - { - JLOG(j_.trace()) << __func__ << " : " - << "Max outstanding requests reached. " - << "Waiting for other requests to finish"; - ++counters_.writesDelayed; - throttleCv_.wait(lck, [this]() { - return numRequestsOutstanding_ < maxRequestsOutstanding; - }); - } - } - - CassStatement* statement = cass_prepared_bind(insert_); - cass_statement_set_consistency(statement, CASS_CONSISTENCY_QUORUM); - CassError rc = cass_statement_bind_bytes( - statement, - 0, - static_cast(data.e->getKey()), - keyBytes_); - if (rc != CASS_OK) - { - cass_statement_free(statement); - std::stringstream ss; - ss << "Binding cassandra insert hash: " << rc << ", " - << cass_error_desc(rc); - JLOG(j_.error()) << __func__ << " : " << ss.str(); - Throw(ss.str()); - } - rc = cass_statement_bind_bytes( - statement, - 1, - static_cast(data.compressed.first), - data.compressed.second); - if (rc != CASS_OK) - { - cass_statement_free(statement); - std::stringstream ss; - ss << "Binding cassandra insert object: " << rc << ", " - << cass_error_desc(rc); - JLOG(j_.error()) << __func__ << " : " << ss.str(); - Throw(ss.str()); - } - data.begin = std::chrono::steady_clock::now(); - CassFuture* fut = cass_session_execute(session_.get(), statement); - cass_statement_free(statement); - - cass_future_set_callback(fut, writeCallback, static_cast(&data)); - cass_future_free(fut); - } - - void - store(std::shared_ptr const& no) override - { - JLOG(j_.trace()) << "Writing to cassandra"; - WriteCallbackData* data = - new WriteCallbackData(this, no, counters_.writeRetries); - - ++numRequestsOutstanding_; - write(*data, false); - } - - void - storeBatch(Batch const& batch) override - { - for (auto const& no : batch) - { - store(no); - } - } - - void - sync() override - { - std::unique_lock lck(syncMutex_); - - syncCv_.wait(lck, [this]() { return numRequestsOutstanding_ == 0; }); - } - - // Iterate through entire table and execute f(). Used for import only, - // with database not being written to, so safe to paginate through - // objects table with LIMIT x OFFSET y. - void - for_each(std::function)> f) override - { - assert(false); - Throw("not implemented"); - } - - int - getWriteLoad() override - { - return 0; - } - - void - setDeletePath() override - { - } - - int - fdRequired() const override - { - return 0; - } - - std::optional> - counters() const override - { - return counters_; - } - - friend void - writeCallback(CassFuture* fut, void* cbData); - - friend void - readCallback(CassFuture* fut, void* cbData); -}; - -// Process the result of an asynchronous read. Retry on error -// @param fut cassandra future associated with the read -// @param cbData struct that holds the request parameters -void -readCallback(CassFuture* fut, void* cbData) -{ - CassandraBackend::ReadCallbackData& requestParams = - *static_cast(cbData); - - CassError rc = cass_future_error_code(fut); - - if (rc != CASS_OK) - { - ++(requestParams.backend.counters_.readRetries); - JLOG(requestParams.backend.j_.warn()) - << "Cassandra fetch error : " << rc << " : " << cass_error_desc(rc) - << " - retrying"; - // Retry right away. The only time the cluster should ever be overloaded - // is when the very first ledger is being written in full (millions of - // writes at once), during which no reads should be occurring. If reads - // are timing out, the code/architecture should be modified to handle - // greater read load, as opposed to just exponential backoff - requestParams.backend.read(requestParams); - } - else - { - auto finish = [&requestParams]() { - size_t batchSize = requestParams.batchSize; - if (++(requestParams.numFinished) == batchSize) - requestParams.cv.notify_all(); - }; - CassResult const* res = cass_future_get_result(fut); - - CassRow const* row = cass_result_first_row(res); - if (!row) - { - cass_result_free(res); - JLOG(requestParams.backend.j_.error()) - << "Cassandra fetch get row error : " << rc << ", " - << cass_error_desc(rc); - finish(); - return; - } - cass_byte_t const* buf; - std::size_t bufSize; - rc = cass_value_get_bytes(cass_row_get_column(row, 0), &buf, &bufSize); - if (rc != CASS_OK) - { - cass_result_free(res); - JLOG(requestParams.backend.j_.error()) - << "Cassandra fetch get bytes error : " << rc << ", " - << cass_error_desc(rc); - ++requestParams.backend.counters_.readErrors; - finish(); - return; - } - nudb::detail::buffer bf; - std::pair uncompressed = - nodeobject_decompress(buf, bufSize, bf); - DecodedBlob decoded( - requestParams.key, uncompressed.first, uncompressed.second); - cass_result_free(res); - - if (!decoded.wasOk()) - { - JLOG(requestParams.backend.j_.fatal()) - << "Cassandra fetch error - data corruption : " << rc << ", " - << cass_error_desc(rc); - ++requestParams.backend.counters_.readErrors; - finish(); - return; - } - requestParams.result = decoded.createObject(); - finish(); - } -} - -// Process the result of an asynchronous write. Retry on error -// @param fut cassandra future associated with the write -// @param cbData struct that holds the request parameters -void -writeCallback(CassFuture* fut, void* cbData) -{ - CassandraBackend::WriteCallbackData& requestParams = - *static_cast(cbData); - CassandraBackend& backend = *requestParams.backend; - auto rc = cass_future_error_code(fut); - if (rc != CASS_OK) - { - JLOG(backend.j_.error()) - << "ERROR!!! Cassandra insert error: " << rc << ", " - << cass_error_desc(rc) << ", retrying "; - ++requestParams.totalWriteRetries; - // exponential backoff with a max wait of 2^10 ms (about 1 second) - auto wait = std::chrono::milliseconds( - lround(std::pow(2, std::min(10u, requestParams.currentRetries)))); - ++requestParams.currentRetries; - std::shared_ptr timer = - std::make_shared( - backend.ioContext_, std::chrono::steady_clock::now() + wait); - timer->async_wait([timer, &requestParams, &backend]( - const boost::system::error_code& error) { - backend.write(requestParams, true); - }); - } - else - { - backend.counters_.writeDurationUs += - std::chrono::duration_cast( - std::chrono::steady_clock::now() - requestParams.begin) - .count(); - --(backend.numRequestsOutstanding_); - - backend.throttleCv_.notify_all(); - if (backend.numRequestsOutstanding_ == 0) - backend.syncCv_.notify_all(); - delete &requestParams; - } -} - -//------------------------------------------------------------------------------ - -class CassandraFactory : public Factory -{ -public: - CassandraFactory() - { - Manager::instance().insert(*this); - } - - ~CassandraFactory() override - { - Manager::instance().erase(*this); - } - - std::string - getName() const override - { - return "cassandra"; - } - - std::unique_ptr - createInstance( - size_t keyBytes, - Section const& keyValues, - std::size_t burstSize, - Scheduler& scheduler, - beast::Journal journal) override - { - return std::make_unique(keyBytes, keyValues, journal); - } -}; - -static CassandraFactory cassandraFactory; - -} // namespace NodeStore -} // namespace ripple -#endif diff --git a/src/ripple/nodestore/impl/DatabaseShardImp.cpp b/src/ripple/nodestore/impl/DatabaseShardImp.cpp deleted file mode 100644 index 33000b5d24c..00000000000 --- a/src/ripple/nodestore/impl/DatabaseShardImp.cpp +++ /dev/null @@ -1,2253 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#if BOOST_OS_LINUX -#include -#endif - -namespace ripple { - -namespace NodeStore { - -DatabaseShardImp::DatabaseShardImp( - Application& app, - Scheduler& scheduler, - int readThreads, - beast::Journal j) - : DatabaseShard( - scheduler, - readThreads, - app.config().section(ConfigSection::shardDatabase()), - j) - , app_(app) - , avgShardFileSz_(ledgersPerShard_ * kilobytes(192ull)) - , openFinalLimit_( - app.config().getValueFor(SizedItem::openFinalLimit, std::nullopt)) -{ - if (app.config().reporting()) - { - Throw( - "Attempted to create DatabaseShardImp in reporting mode. Reporting " - "does not support shards. Remove shards info from config"); - } -} - -bool -DatabaseShardImp::init() -{ - { - std::lock_guard lock(mutex_); - if (init_) - { - JLOG(j_.error()) << "already initialized"; - return false; - } - - if (!initConfig(lock)) - { - JLOG(j_.error()) << "invalid configuration file settings"; - return false; - } - - try - { - using namespace boost::filesystem; - - // Consolidate the main storage path and all historical paths - std::vector paths{dir_}; - paths.insert( - paths.end(), historicalPaths_.begin(), historicalPaths_.end()); - - for (auto const& path : paths) - { - if (exists(path)) - { - if (!is_directory(path)) - { - JLOG(j_.error()) << path << " must be a directory"; - return false; - } - } - else if (!create_directories(path)) - { - JLOG(j_.error()) - << "failed to create path: " + path.string(); - return false; - } - } - - if (!app_.config().standalone() && !historicalPaths_.empty()) - { - // Check historical paths for duplicated file systems - if (!checkHistoricalPaths(lock)) - return false; - } - - ctx_ = std::make_unique(); - ctx_->start(); - - // Find shards - std::uint32_t openFinals{0}; - for (auto const& path : paths) - { - for (auto const& it : directory_iterator(path)) - { - // Ignore files - if (!is_directory(it)) - continue; - - // Ignore nonnumerical directory names - auto const shardDir{it.path()}; - auto dirName{shardDir.stem().string()}; - if (!std::all_of( - dirName.begin(), dirName.end(), [](auto c) { - return ::isdigit(static_cast(c)); - })) - { - continue; - } - - // Ignore values below the earliest shard index - auto const shardIndex{std::stoul(dirName)}; - if (shardIndex < earliestShardIndex_) - { - JLOG(j_.debug()) - << "shard " << shardIndex - << " ignored, comes before earliest shard index " - << earliestShardIndex_; - continue; - } - - // Check if a previous database import failed - if (is_regular_file(shardDir / databaseImportMarker_)) - { - JLOG(j_.warn()) - << "shard " << shardIndex - << " previously failed database import, removing"; - remove_all(shardDir); - continue; - } - - auto shard{std::make_shared( - app_, *this, shardIndex, shardDir.parent_path(), j_)}; - if (!shard->init(scheduler_, *ctx_)) - { - // Remove corrupted or legacy shard - shard->removeOnDestroy(); - JLOG(j_.warn()) - << "shard " << shardIndex << " removed, " - << (shard->isLegacy() ? "legacy" : "corrupted") - << " shard"; - continue; - } - - switch (shard->getState()) - { - case ShardState::finalized: - if (++openFinals > openFinalLimit_) - shard->tryClose(); - shards_.emplace(shardIndex, std::move(shard)); - break; - - case ShardState::complete: - finalizeShard( - shards_.emplace(shardIndex, std::move(shard)) - .first->second, - true, - std::nullopt); - break; - - case ShardState::acquire: - if (acquireIndex_ != 0) - { - JLOG(j_.error()) - << "more than one shard being acquired"; - return false; - } - - shards_.emplace(shardIndex, std::move(shard)); - acquireIndex_ = shardIndex; - break; - - default: - JLOG(j_.error()) - << "shard " << shardIndex << " invalid state"; - return false; - } - } - } - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - init_ = true; - } - - updateFileStats(); - return true; -} - -std::optional -DatabaseShardImp::prepareLedger(std::uint32_t validLedgerSeq) -{ - std::optional shardIndex; - - { - std::lock_guard lock(mutex_); - assert(init_); - - if (acquireIndex_ != 0) - { - if (auto const it{shards_.find(acquireIndex_)}; it != shards_.end()) - return it->second->prepare(); - - // Should never get here - assert(false); - return std::nullopt; - } - - if (!canAdd_) - return std::nullopt; - - shardIndex = findAcquireIndex(validLedgerSeq, lock); - } - - if (!shardIndex) - { - JLOG(j_.debug()) << "no new shards to add"; - { - std::lock_guard lock(mutex_); - canAdd_ = false; - } - return std::nullopt; - } - - auto const pathDesignation = [this, shardIndex = *shardIndex]() { - std::lock_guard lock(mutex_); - return prepareForNewShard(shardIndex, numHistoricalShards(lock), lock); - }(); - - if (!pathDesignation) - return std::nullopt; - - auto const needsHistoricalPath = - *pathDesignation == PathDesignation::historical; - - auto shard = [this, shardIndex, needsHistoricalPath] { - std::lock_guard lock(mutex_); - return std::make_unique( - app_, - *this, - *shardIndex, - (needsHistoricalPath ? chooseHistoricalPath(lock) : ""), - j_); - }(); - - if (!shard->init(scheduler_, *ctx_)) - return std::nullopt; - - auto const ledgerSeq{shard->prepare()}; - { - std::lock_guard lock(mutex_); - shards_.emplace(*shardIndex, std::move(shard)); - acquireIndex_ = *shardIndex; - updatePeers(lock); - } - - return ledgerSeq; -} - -bool -DatabaseShardImp::prepareShards(std::vector const& shardIndexes) -{ - auto fail = [j = j_, &shardIndexes]( - std::string const& msg, - std::optional shardIndex = std::nullopt) { - auto multipleIndexPrequel = [&shardIndexes] { - std::vector indexesAsString(shardIndexes.size()); - std::transform( - shardIndexes.begin(), - shardIndexes.end(), - indexesAsString.begin(), - [](uint32_t const index) { return std::to_string(index); }); - - return std::string("shard") + - (shardIndexes.size() > 1 ? "s " : " ") + - boost::algorithm::join(indexesAsString, ", "); - }; - - JLOG(j.error()) << (shardIndex ? "shard " + std::to_string(*shardIndex) - : multipleIndexPrequel()) - << " " << msg; - return false; - }; - - if (shardIndexes.empty()) - return fail("invalid shard indexes"); - - std::lock_guard lock(mutex_); - assert(init_); - - if (!canAdd_) - return fail("cannot be stored at this time"); - - auto historicalShardsToPrepare = 0; - - for (auto const shardIndex : shardIndexes) - { - if (shardIndex < earliestShardIndex_) - { - return fail( - "comes before earliest shard index " + - std::to_string(earliestShardIndex_), - shardIndex); - } - - // If we are synced to the network, check if the shard index is - // greater or equal to the current or validated shard index. - auto seqCheck = [&](std::uint32_t ledgerSeq) { - if (ledgerSeq >= earliestLedgerSeq_ && - shardIndex >= seqToShardIndex(ledgerSeq)) - { - return fail("invalid index", shardIndex); - } - return true; - }; - if (!seqCheck(app_.getLedgerMaster().getValidLedgerIndex() + 1) || - !seqCheck(app_.getLedgerMaster().getCurrentLedgerIndex())) - { - return fail("invalid index", shardIndex); - } - - if (shards_.find(shardIndex) != shards_.end()) - return fail("is already stored", shardIndex); - - if (preparedIndexes_.find(shardIndex) != preparedIndexes_.end()) - return fail( - "is already queued for import from the shard archive handler", - shardIndex); - - if (databaseImportStatus_) - { - if (auto shard = databaseImportStatus_->currentShard.lock(); shard) - { - if (shard->index() == shardIndex) - return fail( - "is being imported from the nodestore", shardIndex); - } - } - - // Any shard earlier than the two most recent shards - // is a historical shard - if (shardIndex < shardBoundaryIndex()) - ++historicalShardsToPrepare; - } - - auto const numHistShards = numHistoricalShards(lock); - - // Check shard count and available storage space - if (numHistShards + historicalShardsToPrepare > maxHistoricalShards_) - return fail("maximum number of historical shards reached"); - - if (historicalShardsToPrepare) - { - // Check available storage space for historical shards - if (!sufficientStorage( - historicalShardsToPrepare, PathDesignation::historical, lock)) - return fail("insufficient storage space available"); - } - - if (auto const recentShardsToPrepare = - shardIndexes.size() - historicalShardsToPrepare; - recentShardsToPrepare) - { - // Check available storage space for recent shards - if (!sufficientStorage( - recentShardsToPrepare, PathDesignation::none, lock)) - return fail("insufficient storage space available"); - } - - for (auto const shardIndex : shardIndexes) - preparedIndexes_.emplace(shardIndex); - - updatePeers(lock); - return true; -} - -void -DatabaseShardImp::removePreShard(std::uint32_t shardIndex) -{ - std::lock_guard lock(mutex_); - assert(init_); - - if (preparedIndexes_.erase(shardIndex)) - updatePeers(lock); -} - -std::string -DatabaseShardImp::getPreShards() -{ - RangeSet rs; - { - std::lock_guard lock(mutex_); - assert(init_); - - for (auto const& shardIndex : preparedIndexes_) - rs.insert(shardIndex); - } - - if (rs.empty()) - return {}; - - return ripple::to_string(rs); -}; - -bool -DatabaseShardImp::importShard( - std::uint32_t shardIndex, - boost::filesystem::path const& srcDir) -{ - auto fail = [&](std::string const& msg, - std::lock_guard const& lock) { - JLOG(j_.error()) << "shard " << shardIndex << " " << msg; - - // Remove the failed import shard index so it can be retried - preparedIndexes_.erase(shardIndex); - updatePeers(lock); - return false; - }; - - using namespace boost::filesystem; - try - { - if (!is_directory(srcDir) || is_empty(srcDir)) - { - return fail( - "invalid source directory " + srcDir.string(), - std::lock_guard(mutex_)); - } - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what(), - std::lock_guard(mutex_)); - } - - auto const expectedHash{app_.getLedgerMaster().walkHashBySeq( - lastLedgerSeq(shardIndex), InboundLedger::Reason::GENERIC)}; - if (!expectedHash) - return fail("expected hash not found", std::lock_guard(mutex_)); - - path dstDir; - { - std::lock_guard lock(mutex_); - if (shards_.find(shardIndex) != shards_.end()) - return fail("already exists", lock); - - // Check shard was prepared for import - if (preparedIndexes_.find(shardIndex) == preparedIndexes_.end()) - return fail("was not prepared for import", lock); - - auto const pathDesignation{ - prepareForNewShard(shardIndex, numHistoricalShards(lock), lock)}; - if (!pathDesignation) - return fail("failed to import", lock); - - if (*pathDesignation == PathDesignation::historical) - dstDir = chooseHistoricalPath(lock); - else - dstDir = dir_; - } - dstDir /= std::to_string(shardIndex); - - auto renameDir = [&, fname = __func__](path const& src, path const& dst) { - try - { - rename(src, dst); - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + fname + - ". Error: " + e.what(), - std::lock_guard(mutex_)); - } - return true; - }; - - // Rename source directory to the shard database directory - if (!renameDir(srcDir, dstDir)) - return false; - - // Create the new shard - auto shard{std::make_unique( - app_, *this, shardIndex, dstDir.parent_path(), j_)}; - - if (!shard->init(scheduler_, *ctx_) || - shard->getState() != ShardState::complete) - { - shard.reset(); - renameDir(dstDir, srcDir); - return fail("failed to import", std::lock_guard(mutex_)); - } - - auto const [it, inserted] = [&]() { - std::lock_guard lock(mutex_); - preparedIndexes_.erase(shardIndex); - return shards_.emplace(shardIndex, std::move(shard)); - }(); - - if (!inserted) - { - shard.reset(); - renameDir(dstDir, srcDir); - return fail("failed to import", std::lock_guard(mutex_)); - } - - finalizeShard(it->second, true, expectedHash); - return true; -} - -std::shared_ptr -DatabaseShardImp::fetchLedger(uint256 const& hash, std::uint32_t ledgerSeq) -{ - auto const shardIndex{seqToShardIndex(ledgerSeq)}; - { - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - assert(init_); - - auto const it{shards_.find(shardIndex)}; - if (it == shards_.end()) - return nullptr; - shard = it->second; - } - - // Ledger must be stored in a final or acquiring shard - switch (shard->getState()) - { - case ShardState::finalized: - break; - case ShardState::acquire: - if (shard->containsLedger(ledgerSeq)) - break; - [[fallthrough]]; - default: - return nullptr; - } - } - - auto const nodeObject{Database::fetchNodeObject(hash, ledgerSeq)}; - if (!nodeObject) - return nullptr; - - auto fail = [&](std::string const& msg) -> std::shared_ptr { - JLOG(j_.error()) << "shard " << shardIndex << " " << msg; - return nullptr; - }; - - auto ledger{std::make_shared( - deserializePrefixedHeader(makeSlice(nodeObject->getData())), - app_.config(), - *app_.getShardFamily())}; - - if (ledger->info().seq != ledgerSeq) - { - return fail( - "encountered invalid ledger sequence " + std::to_string(ledgerSeq)); - } - if (ledger->info().hash != hash) - { - return fail( - "encountered invalid ledger hash " + to_string(hash) + - " on sequence " + std::to_string(ledgerSeq)); - } - - ledger->setFull(); - if (!ledger->stateMap().fetchRoot( - SHAMapHash{ledger->info().accountHash}, nullptr)) - { - return fail( - "is missing root STATE node on hash " + to_string(hash) + - " on sequence " + std::to_string(ledgerSeq)); - } - - if (ledger->info().txHash.isNonZero()) - { - if (!ledger->txMap().fetchRoot( - SHAMapHash{ledger->info().txHash}, nullptr)) - { - return fail( - "is missing root TXN node on hash " + to_string(hash) + - " on sequence " + std::to_string(ledgerSeq)); - } - } - return ledger; -} - -void -DatabaseShardImp::setStored(std::shared_ptr const& ledger) -{ - auto const ledgerSeq{ledger->info().seq}; - if (ledger->info().hash.isZero()) - { - JLOG(j_.error()) << "zero ledger hash for ledger sequence " - << ledgerSeq; - return; - } - if (ledger->info().accountHash.isZero()) - { - JLOG(j_.error()) << "zero account hash for ledger sequence " - << ledgerSeq; - return; - } - if (ledger->stateMap().getHash().isNonZero() && - !ledger->stateMap().isValid()) - { - JLOG(j_.error()) << "invalid state map for ledger sequence " - << ledgerSeq; - return; - } - if (ledger->info().txHash.isNonZero() && !ledger->txMap().isValid()) - { - JLOG(j_.error()) << "invalid transaction map for ledger sequence " - << ledgerSeq; - return; - } - - auto const shardIndex{seqToShardIndex(ledgerSeq)}; - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - assert(init_); - - if (shardIndex != acquireIndex_) - { - JLOG(j_.trace()) - << "shard " << shardIndex << " is not being acquired"; - return; - } - - auto const it{shards_.find(shardIndex)}; - if (it == shards_.end()) - { - JLOG(j_.error()) - << "shard " << shardIndex << " is not being acquired"; - return; - } - shard = it->second; - } - - if (shard->containsLedger(ledgerSeq)) - { - JLOG(j_.trace()) << "shard " << shardIndex << " ledger already stored"; - return; - } - - setStoredInShard(shard, ledger); -} - -std::unique_ptr -DatabaseShardImp::getShardInfo() const -{ - std::lock_guard lock(mutex_); - return getShardInfo(lock); -} - -void -DatabaseShardImp::stop() -{ - // Stop read threads in base before data members are destroyed - Database::stop(); - std::vector> shards; - { - std::lock_guard lock(mutex_); - shards.reserve(shards_.size()); - for (auto const& [_, shard] : shards_) - { - shards.push_back(shard); - shard->stop(); - } - shards_.clear(); - } - taskQueue_.stop(); - - // All shards should be expired at this point - for (auto const& wptr : shards) - { - if (auto const shard{wptr.lock()}) - { - JLOG(j_.warn()) << " shard " << shard->index() << " unexpired"; - } - } - - std::unique_lock lock(mutex_); - - // Notify the shard being imported - // from the node store to stop - if (databaseImportStatus_) - { - // A node store import is in progress - if (auto importShard = databaseImportStatus_->currentShard.lock(); - importShard) - importShard->stop(); - } - - // Wait for the node store import thread - // if necessary - if (databaseImporter_.joinable()) - { - // Tells the import function to halt - haltDatabaseImport_ = true; - - // Wait for the function to exit - while (databaseImportStatus_) - { - // Unlock just in case the import - // function is waiting on the mutex - lock.unlock(); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - lock.lock(); - } - - // Calling join while holding the mutex_ without - // first making sure that doImportDatabase has - // exited could lead to deadlock via the mutex - // acquisition that occurs in that function - if (databaseImporter_.joinable()) - databaseImporter_.join(); - } -} - -void -DatabaseShardImp::importDatabase(Database& source) -{ - std::lock_guard lock(mutex_); - assert(init_); - - // Only the application local node store can be imported - assert(&source == &app_.getNodeStore()); - - if (databaseImporter_.joinable()) - { - assert(false); - JLOG(j_.error()) << "database import already in progress"; - return; - } - - startDatabaseImportThread(lock); -} - -void -DatabaseShardImp::doImportDatabase() -{ - auto shouldHalt = [this] { - bool expected = true; - return haltDatabaseImport_.compare_exchange_strong(expected, false) || - isStopping(); - }; - - if (shouldHalt()) - return; - - auto loadLedger = - [this](char const* const sortOrder) -> std::optional { - std::shared_ptr ledger; - std::uint32_t ledgerSeq{0}; - std::optional info; - if (sortOrder == std::string("asc")) - { - info = dynamic_cast(&app_.getRelationalDatabase()) - ->getLimitedOldestLedgerInfo(earliestLedgerSeq()); - } - else - { - info = dynamic_cast(&app_.getRelationalDatabase()) - ->getLimitedNewestLedgerInfo(earliestLedgerSeq()); - } - if (info) - { - ledger = loadLedgerHelper(*info, app_, false); - ledgerSeq = info->seq; - } - if (!ledger || ledgerSeq == 0) - { - JLOG(j_.error()) << "no suitable ledgers were found in" - " the SQLite database to import"; - return std::nullopt; - } - return ledgerSeq; - }; - - // Find earliest ledger sequence stored - auto const earliestLedgerSeq{loadLedger("asc")}; - if (!earliestLedgerSeq) - return; - - auto const earliestIndex = [&] { - auto earliestIndex = seqToShardIndex(*earliestLedgerSeq); - - // Consider only complete shards - if (earliestLedgerSeq != firstLedgerSeq(earliestIndex)) - ++earliestIndex; - - return earliestIndex; - }(); - - // Find last ledger sequence stored - auto const latestLedgerSeq = loadLedger("desc"); - if (!latestLedgerSeq) - return; - - auto const latestIndex = [&] { - auto latestIndex = seqToShardIndex(*latestLedgerSeq); - - // Consider only complete shards - if (latestLedgerSeq != lastLedgerSeq(latestIndex)) - --latestIndex; - - return latestIndex; - }(); - - if (latestIndex < earliestIndex) - { - JLOG(j_.error()) << "no suitable ledgers were found in" - " the SQLite database to import"; - return; - } - - JLOG(j_.debug()) << "Importing ledgers for shards " << earliestIndex - << " through " << latestIndex; - - { - std::lock_guard lock(mutex_); - - assert(!databaseImportStatus_); - databaseImportStatus_ = std::make_unique( - earliestIndex, latestIndex, 0); - } - - // Import the shards - for (std::uint32_t shardIndex = earliestIndex; shardIndex <= latestIndex; - ++shardIndex) - { - if (shouldHalt()) - return; - - auto const pathDesignation = [this, shardIndex] { - std::lock_guard lock(mutex_); - - auto const numHistShards = numHistoricalShards(lock); - auto const pathDesignation = - prepareForNewShard(shardIndex, numHistShards, lock); - - return pathDesignation; - }(); - - if (!pathDesignation) - break; - - { - std::lock_guard lock(mutex_); - - // Skip if being acquired - if (shardIndex == acquireIndex_) - { - JLOG(j_.debug()) - << "shard " << shardIndex << " already being acquired"; - continue; - } - - // Skip if being imported from the shard archive handler - if (preparedIndexes_.find(shardIndex) != preparedIndexes_.end()) - { - JLOG(j_.debug()) - << "shard " << shardIndex << " already being imported"; - continue; - } - - // Skip if stored - if (shards_.find(shardIndex) != shards_.end()) - { - JLOG(j_.debug()) << "shard " << shardIndex << " already stored"; - continue; - } - } - - std::uint32_t const firstSeq = firstLedgerSeq(shardIndex); - std::uint32_t const lastSeq = - std::max(firstSeq, lastLedgerSeq(shardIndex)); - - // Verify SQLite ledgers are in the node store - { - auto const ledgerHashes{ - app_.getRelationalDatabase().getHashesByIndex( - firstSeq, lastSeq)}; - if (ledgerHashes.size() != maxLedgers(shardIndex)) - continue; - - auto& source = app_.getNodeStore(); - bool valid{true}; - - for (std::uint32_t n = firstSeq; n <= lastSeq; ++n) - { - if (!source.fetchNodeObject(ledgerHashes.at(n).ledgerHash, n)) - { - JLOG(j_.warn()) << "SQLite ledger sequence " << n - << " mismatches node store"; - valid = false; - break; - } - } - if (!valid) - continue; - } - - if (shouldHalt()) - return; - - bool const needsHistoricalPath = - *pathDesignation == PathDesignation::historical; - - auto const path = needsHistoricalPath - ? chooseHistoricalPath(std::lock_guard(mutex_)) - : dir_; - - // Create the new shard - auto shard{std::make_shared(app_, *this, shardIndex, path, j_)}; - if (!shard->init(scheduler_, *ctx_)) - continue; - - { - std::lock_guard lock(mutex_); - - if (shouldHalt()) - return; - - databaseImportStatus_->currentIndex = shardIndex; - databaseImportStatus_->currentShard = shard; - databaseImportStatus_->firstSeq = firstSeq; - databaseImportStatus_->lastSeq = lastSeq; - } - - // Create a marker file to signify a database import in progress - auto const shardDir{path / std::to_string(shardIndex)}; - auto const markerFile{shardDir / databaseImportMarker_}; - { - std::ofstream ofs{markerFile.string()}; - if (!ofs.is_open()) - { - JLOG(j_.error()) << "shard " << shardIndex - << " failed to create temp marker file"; - shard->removeOnDestroy(); - continue; - } - } - - // Copy the ledgers from node store - std::shared_ptr recentStored; - std::optional lastLedgerHash; - - while (auto const ledgerSeq = shard->prepare()) - { - if (shouldHalt()) - return; - - // Not const so it may be moved later - auto ledger{loadByIndex(*ledgerSeq, app_, false)}; - if (!ledger || ledger->info().seq != ledgerSeq) - break; - - auto const result{shard->storeLedger(ledger, recentStored)}; - storeStats(result.count, result.size); - if (result.error) - break; - - if (!shard->setLedgerStored(ledger)) - break; - - if (!lastLedgerHash && ledgerSeq == lastSeq) - lastLedgerHash = ledger->info().hash; - - recentStored = std::move(ledger); - } - - if (shouldHalt()) - return; - - using namespace boost::filesystem; - bool success{false}; - if (lastLedgerHash && shard->getState() == ShardState::complete) - { - // Store shard final key - Serializer s; - s.add32(Shard::version); - s.add32(firstLedgerSeq(shardIndex)); - s.add32(lastLedgerSeq(shardIndex)); - s.addBitString(*lastLedgerHash); - auto const nodeObject{NodeObject::createObject( - hotUNKNOWN, std::move(s.modData()), Shard::finalKey)}; - - if (shard->storeNodeObject(nodeObject)) - { - try - { - std::lock_guard lock(mutex_); - - // The database import process is complete and the - // marker file is no longer required - remove_all(markerFile); - - JLOG(j_.debug()) << "shard " << shardIndex - << " was successfully imported" - " from the NodeStore"; - finalizeShard( - shards_.emplace(shardIndex, std::move(shard)) - .first->second, - true, - std::nullopt); - - // This variable is meant to capture the success - // of everything up to the point of shard finalization. - // If the shard fails to finalize, this condition will - // be handled by the finalization function itself, and - // not here. - success = true; - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard index " << shardIndex - << ". Exception caught in function " - << __func__ << ". Error: " << e.what(); - } - } - } - - if (!success) - { - JLOG(j_.error()) << "shard " << shardIndex - << " failed to import from the NodeStore"; - - if (shard) - shard->removeOnDestroy(); - } - } - - if (shouldHalt()) - return; - - updateFileStats(); -} - -std::int32_t -DatabaseShardImp::getWriteLoad() const -{ - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - assert(init_); - - auto const it{shards_.find(acquireIndex_)}; - if (it == shards_.end()) - return 0; - shard = it->second; - } - - return shard->getWriteLoad(); -} - -void -DatabaseShardImp::store( - NodeObjectType type, - Blob&& data, - uint256 const& hash, - std::uint32_t ledgerSeq) -{ - auto const shardIndex{seqToShardIndex(ledgerSeq)}; - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - if (shardIndex != acquireIndex_) - { - JLOG(j_.trace()) - << "shard " << shardIndex << " is not being acquired"; - return; - } - - auto const it{shards_.find(shardIndex)}; - if (it == shards_.end()) - { - JLOG(j_.error()) - << "shard " << shardIndex << " is not being acquired"; - return; - } - shard = it->second; - } - - auto const nodeObject{ - NodeObject::createObject(type, std::move(data), hash)}; - if (shard->storeNodeObject(nodeObject)) - storeStats(1, nodeObject->getData().size()); -} - -bool -DatabaseShardImp::storeLedger(std::shared_ptr const& srcLedger) -{ - auto const ledgerSeq{srcLedger->info().seq}; - auto const shardIndex{seqToShardIndex(ledgerSeq)}; - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - assert(init_); - - if (shardIndex != acquireIndex_) - { - JLOG(j_.trace()) - << "shard " << shardIndex << " is not being acquired"; - return false; - } - - auto const it{shards_.find(shardIndex)}; - if (it == shards_.end()) - { - JLOG(j_.error()) - << "shard " << shardIndex << " is not being acquired"; - return false; - } - shard = it->second; - } - - auto const result{shard->storeLedger(srcLedger, nullptr)}; - storeStats(result.count, result.size); - if (result.error || result.count == 0 || result.size == 0) - return false; - - return setStoredInShard(shard, srcLedger); -} - -void -DatabaseShardImp::sweep() -{ - std::vector> shards; - { - std::lock_guard lock(mutex_); - assert(init_); - - shards.reserve(shards_.size()); - for (auto const& e : shards_) - shards.push_back(e.second); - } - - std::vector> openFinals; - openFinals.reserve(openFinalLimit_); - - for (auto const& weak : shards) - { - if (auto const shard{weak.lock()}; shard && shard->isOpen()) - { - if (shard->getState() == ShardState::finalized) - openFinals.emplace_back(std::move(shard)); - } - } - - if (openFinals.size() > openFinalLimit_) - { - JLOG(j_.trace()) << "Open shards exceed configured limit of " - << openFinalLimit_ << " by " - << (openFinals.size() - openFinalLimit_); - - // Try to close enough shards to be within the limit. - // Sort ascending on last use so the oldest are removed first. - std::sort( - openFinals.begin(), - openFinals.end(), - [&](std::shared_ptr const& lhsShard, - std::shared_ptr const& rhsShard) { - return lhsShard->getLastUse() < rhsShard->getLastUse(); - }); - - for (auto it{openFinals.cbegin()}; - it != openFinals.cend() && openFinals.size() > openFinalLimit_;) - { - if ((*it)->tryClose()) - it = openFinals.erase(it); - else - ++it; - } - } -} - -Json::Value -DatabaseShardImp::getDatabaseImportStatus() const -{ - if (std::lock_guard lock(mutex_); databaseImportStatus_) - { - Json::Value ret(Json::objectValue); - - ret[jss::firstShardIndex] = databaseImportStatus_->earliestIndex; - ret[jss::lastShardIndex] = databaseImportStatus_->latestIndex; - ret[jss::currentShardIndex] = databaseImportStatus_->currentIndex; - - Json::Value currentShard(Json::objectValue); - currentShard[jss::firstSequence] = databaseImportStatus_->firstSeq; - currentShard[jss::lastSequence] = databaseImportStatus_->lastSeq; - - if (auto shard = databaseImportStatus_->currentShard.lock(); shard) - currentShard[jss::storedSeqs] = shard->getStoredSeqs(); - - ret[jss::currentShard] = currentShard; - - if (haltDatabaseImport_) - ret[jss::message] = "Database import halt initiated..."; - - return ret; - } - - return RPC::make_error(rpcINTERNAL, "Database import not running"); -} - -Json::Value -DatabaseShardImp::startNodeToShard() -{ - std::lock_guard lock(mutex_); - - if (!init_) - return RPC::make_error(rpcINTERNAL, "Shard store not initialized"); - - if (databaseImporter_.joinable()) - return RPC::make_error( - rpcINTERNAL, "Database import already in progress"); - - if (isStopping()) - return RPC::make_error(rpcINTERNAL, "Node is shutting down"); - - startDatabaseImportThread(lock); - - Json::Value result(Json::objectValue); - result[jss::message] = "Database import initiated..."; - - return result; -} - -Json::Value -DatabaseShardImp::stopNodeToShard() -{ - std::lock_guard lock(mutex_); - - if (!init_) - return RPC::make_error(rpcINTERNAL, "Shard store not initialized"); - - if (!databaseImporter_.joinable()) - return RPC::make_error(rpcINTERNAL, "Database import not running"); - - if (isStopping()) - return RPC::make_error(rpcINTERNAL, "Node is shutting down"); - - haltDatabaseImport_ = true; - - Json::Value result(Json::objectValue); - result[jss::message] = "Database import halt initiated..."; - - return result; -} - -std::optional -DatabaseShardImp::getDatabaseImportSequence() const -{ - std::lock_guard lock(mutex_); - - if (!databaseImportStatus_) - return {}; - - return databaseImportStatus_->firstSeq; -} - -bool -DatabaseShardImp::initConfig(std::lock_guard const&) -{ - auto fail = [j = j_](std::string const& msg) { - JLOG(j.error()) << "[" << ConfigSection::shardDatabase() << "] " << msg; - return false; - }; - - Config const& config{app_.config()}; - Section const& section{config.section(ConfigSection::shardDatabase())}; - - auto compare = [&](std::string const& name, std::uint32_t defaultValue) { - std::uint32_t shardDBValue{defaultValue}; - get_if_exists(section, name, shardDBValue); - - std::uint32_t nodeDBValue{defaultValue}; - get_if_exists( - config.section(ConfigSection::nodeDatabase()), name, nodeDBValue); - - return shardDBValue == nodeDBValue; - }; - - // If ledgers_per_shard or earliest_seq are specified, - // they must be equally assigned in 'node_db' - if (!compare("ledgers_per_shard", DEFAULT_LEDGERS_PER_SHARD)) - { - return fail( - "and [" + ConfigSection::nodeDatabase() + "] define different '" + - "ledgers_per_shard" + "' values"); - } - if (!compare("earliest_seq", XRP_LEDGER_EARLIEST_SEQ)) - { - return fail( - "and [" + ConfigSection::nodeDatabase() + "] define different '" + - "earliest_seq" + "' values"); - } - - using namespace boost::filesystem; - if (!get_if_exists(section, "path", dir_)) - return fail("'path' missing"); - - { - get_if_exists(section, "max_historical_shards", maxHistoricalShards_); - - Section const& historicalShardPaths = - config.section(SECTION_HISTORICAL_SHARD_PATHS); - - auto values = historicalShardPaths.values(); - - std::sort(values.begin(), values.end()); - values.erase(std::unique(values.begin(), values.end()), values.end()); - - for (auto const& s : values) - { - auto const dir = path(s); - if (dir_ == dir) - { - return fail( - "the 'path' cannot also be in the " - "'historical_shard_path' section"); - } - - historicalPaths_.push_back(s); - } - } - - // NuDB is the default and only supported permanent storage backend - backendName_ = get(section, "type", "nudb"); - if (!boost::iequals(backendName_, "NuDB")) - return fail("'type' value unsupported"); - - return true; -} - -std::shared_ptr -DatabaseShardImp::fetchNodeObject( - uint256 const& hash, - std::uint32_t ledgerSeq, - FetchReport& fetchReport, - bool duplicate) -{ - auto const shardIndex{seqToShardIndex(ledgerSeq)}; - std::shared_ptr shard; - { - std::lock_guard lock(mutex_); - auto const it{shards_.find(shardIndex)}; - if (it == shards_.end()) - return nullptr; - shard = it->second; - } - - return shard->fetchNodeObject(hash, fetchReport); -} - -std::optional -DatabaseShardImp::findAcquireIndex( - std::uint32_t validLedgerSeq, - std::lock_guard const&) -{ - if (validLedgerSeq < earliestLedgerSeq_) - return std::nullopt; - - auto const maxShardIndex{[this, validLedgerSeq]() { - auto shardIndex{seqToShardIndex(validLedgerSeq)}; - if (validLedgerSeq != lastLedgerSeq(shardIndex)) - --shardIndex; - return shardIndex; - }()}; - auto const maxNumShards{maxShardIndex - earliestShardIndex_ + 1}; - - // Check if the shard store has all shards - if (shards_.size() >= maxNumShards) - return std::nullopt; - - if (maxShardIndex < 1024 || - static_cast(shards_.size()) / maxNumShards > 0.5f) - { - // Small or mostly full index space to sample - // Find the available indexes and select one at random - std::vector available; - available.reserve(maxNumShards - shards_.size()); - - for (auto shardIndex = earliestShardIndex_; shardIndex <= maxShardIndex; - ++shardIndex) - { - if (shards_.find(shardIndex) == shards_.end() && - preparedIndexes_.find(shardIndex) == preparedIndexes_.end()) - { - available.push_back(shardIndex); - } - } - - if (available.empty()) - return std::nullopt; - - if (available.size() == 1) - return available.front(); - - return available[rand_int( - 0u, static_cast(available.size() - 1))]; - } - - // Large, sparse index space to sample - // Keep choosing indexes at random until an available one is found - // chances of running more than 30 times is less than 1 in a billion - for (int i = 0; i < 40; ++i) - { - auto const shardIndex{rand_int(earliestShardIndex_, maxShardIndex)}; - if (shards_.find(shardIndex) == shards_.end() && - preparedIndexes_.find(shardIndex) == preparedIndexes_.end()) - { - return shardIndex; - } - } - - assert(false); - return std::nullopt; -} - -void -DatabaseShardImp::finalizeShard( - std::shared_ptr& shard, - bool const writeSQLite, - std::optional const& expectedHash) -{ - taskQueue_.addTask([this, - wptr = std::weak_ptr(shard), - writeSQLite, - expectedHash]() { - if (isStopping()) - return; - - auto shard{wptr.lock()}; - if (!shard) - { - JLOG(j_.debug()) << "Shard removed before being finalized"; - return; - } - - if (!shard->finalize(writeSQLite, expectedHash)) - { - if (isStopping()) - return; - - // Invalid or corrupt shard, remove it - removeFailedShard(shard); - return; - } - - if (isStopping()) - return; - - { - auto const boundaryIndex{shardBoundaryIndex()}; - std::lock_guard lock(mutex_); - - if (shard->index() < boundaryIndex) - { - // This is a historical shard - if (!historicalPaths_.empty() && - shard->getDir().parent_path() == dir_) - { - // Shard wasn't placed at a separate historical path - JLOG(j_.warn()) << "shard " << shard->index() - << " is not stored at a historical path"; - } - } - else - { - // Not a historical shard. Shift recent shards if necessary - assert(!boundaryIndex || shard->index() - boundaryIndex <= 1); - relocateOutdatedShards(lock); - - // Set the appropriate recent shard index - if (shard->index() == boundaryIndex) - secondLatestShardIndex_ = shard->index(); - else - latestShardIndex_ = shard->index(); - - if (shard->getDir().parent_path() != dir_) - { - JLOG(j_.warn()) << "shard " << shard->index() - << " is not stored at the path"; - } - } - - updatePeers(lock); - } - - updateFileStats(); - }); -} - -void -DatabaseShardImp::updateFileStats() -{ - std::vector> shards; - { - std::lock_guard lock(mutex_); - if (shards_.empty()) - return; - - shards.reserve(shards_.size()); - for (auto const& e : shards_) - shards.push_back(e.second); - } - - std::uint64_t sumSz{0}; - std::uint32_t sumFd{0}; - std::uint32_t numShards{0}; - for (auto const& weak : shards) - { - if (auto const shard{weak.lock()}; shard) - { - auto const [sz, fd] = shard->getFileInfo(); - sumSz += sz; - sumFd += fd; - ++numShards; - } - } - - std::lock_guard lock(mutex_); - fileSz_ = sumSz; - fdRequired_ = sumFd; - avgShardFileSz_ = (numShards == 0 ? fileSz_ : fileSz_ / numShards); - - if (!canAdd_) - return; - - if (auto const count = numHistoricalShards(lock); - count >= maxHistoricalShards_) - { - if (maxHistoricalShards_) - { - // In order to avoid excessive output, don't produce - // this warning if the server isn't configured to - // store historical shards. - JLOG(j_.warn()) << "maximum number of historical shards reached"; - } - - canAdd_ = false; - } - else if (!sufficientStorage( - maxHistoricalShards_ - count, - PathDesignation::historical, - lock)) - { - JLOG(j_.warn()) - << "maximum shard store size exceeds available storage space"; - - canAdd_ = false; - } -} - -bool -DatabaseShardImp::sufficientStorage( - std::uint32_t numShards, - PathDesignation pathDesignation, - std::lock_guard const&) const -{ - try - { - std::vector capacities; - - if (pathDesignation == PathDesignation::historical && - !historicalPaths_.empty()) - { - capacities.reserve(historicalPaths_.size()); - - for (auto const& path : historicalPaths_) - { - // Get the available storage for each historical path - auto const availableSpace = - boost::filesystem::space(path).available; - - capacities.push_back(availableSpace); - } - } - else - { - // Get the available storage for the main shard path - capacities.push_back(boost::filesystem::space(dir_).available); - } - - for (std::uint64_t const capacity : capacities) - { - // Leverage all the historical shard paths to - // see if collectively they can fit the specified - // number of shards. For this to work properly, - // each historical path must correspond to a separate - // physical device or filesystem. - - auto const shardCap = capacity / avgShardFileSz_; - if (numShards <= shardCap) - return true; - - numShards -= shardCap; - } - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - return false; -} - -bool -DatabaseShardImp::setStoredInShard( - std::shared_ptr& shard, - std::shared_ptr const& ledger) -{ - if (!shard->setLedgerStored(ledger)) - { - // Invalid or corrupt shard, remove it - removeFailedShard(shard); - return false; - } - - if (shard->getState() == ShardState::complete) - { - std::lock_guard lock(mutex_); - if (auto const it{shards_.find(shard->index())}; it != shards_.end()) - { - if (shard->index() == acquireIndex_) - acquireIndex_ = 0; - - finalizeShard(it->second, false, std::nullopt); - } - else - { - JLOG(j_.debug()) - << "shard " << shard->index() << " is no longer being acquired"; - } - } - - updateFileStats(); - return true; -} - -void -DatabaseShardImp::removeFailedShard(std::shared_ptr& shard) -{ - { - std::lock_guard lock(mutex_); - - if (shard->index() == acquireIndex_) - acquireIndex_ = 0; - - if (shard->index() == latestShardIndex_) - latestShardIndex_ = std::nullopt; - - if (shard->index() == secondLatestShardIndex_) - secondLatestShardIndex_ = std::nullopt; - } - - shard->removeOnDestroy(); - - // Reset the shared_ptr to invoke the shard's - // destructor and remove it from the server - shard.reset(); - updateFileStats(); -} - -std::uint32_t -DatabaseShardImp::shardBoundaryIndex() const -{ - auto const validIndex = app_.getLedgerMaster().getValidLedgerIndex(); - - if (validIndex < earliestLedgerSeq_) - return 0; - - // Shards with an index earlier than the recent shard boundary index - // are considered historical. The three shards at or later than - // this index consist of the two most recently validated shards - // and the shard still in the process of being built by live - // transactions. - return seqToShardIndex(validIndex) - 1; -} - -std::uint32_t -DatabaseShardImp::numHistoricalShards( - std::lock_guard const& lock) const -{ - auto const boundaryIndex{shardBoundaryIndex()}; - return std::count_if( - shards_.begin(), shards_.end(), [boundaryIndex](auto const& entry) { - return entry.first < boundaryIndex; - }); -} - -void -DatabaseShardImp::relocateOutdatedShards( - std::lock_guard const& lock) -{ - auto& cur{latestShardIndex_}; - auto& prev{secondLatestShardIndex_}; - if (!cur && !prev) - return; - - auto const latestShardIndex = - seqToShardIndex(app_.getLedgerMaster().getValidLedgerIndex()); - auto const separateHistoricalPath = !historicalPaths_.empty(); - - auto const removeShard = [this](std::uint32_t const shardIndex) -> void { - canAdd_ = false; - - if (auto it = shards_.find(shardIndex); it != shards_.end()) - { - if (it->second) - removeFailedShard(it->second); - else - { - JLOG(j_.warn()) << "can't find shard to remove"; - } - } - else - { - JLOG(j_.warn()) << "can't find shard to remove"; - } - }; - - auto const keepShard = [this, &lock, removeShard, separateHistoricalPath]( - std::uint32_t const shardIndex) -> bool { - if (numHistoricalShards(lock) >= maxHistoricalShards_) - { - JLOG(j_.error()) << "maximum number of historical shards reached"; - removeShard(shardIndex); - return false; - } - if (separateHistoricalPath && - !sufficientStorage(1, PathDesignation::historical, lock)) - { - JLOG(j_.error()) << "insufficient storage space available"; - removeShard(shardIndex); - return false; - } - - return true; - }; - - // Move a shard from the main shard path to a historical shard - // path by copying the contents, and creating a new shard. - auto const moveShard = [this, - &lock](std::uint32_t const shardIndex) -> void { - auto it{shards_.find(shardIndex)}; - if (it == shards_.end()) - { - JLOG(j_.warn()) << "can't find shard to move to historical path"; - return; - } - - auto& shard{it->second}; - - // Close any open file descriptors before moving the shard - // directory. Don't call removeOnDestroy since that would - // attempt to close the fds after the directory has been moved. - if (!shard->tryClose()) - { - JLOG(j_.warn()) << "can't close shard to move to historical path"; - return; - } - - auto const dst{chooseHistoricalPath(lock)}; - try - { - // Move the shard directory to the new path - boost::filesystem::rename( - shard->getDir().string(), dst / std::to_string(shardIndex)); - } - catch (...) - { - JLOG(j_.error()) << "shard " << shardIndex - << " failed to move to historical storage"; - return; - } - - // Create a shard instance at the new location - shard = std::make_shared(app_, *this, shardIndex, dst, j_); - - // Open the new shard - if (!shard->init(scheduler_, *ctx_)) - { - JLOG(j_.error()) << "shard " << shardIndex - << " failed to open in historical storage"; - shard->removeOnDestroy(); - shard.reset(); - } - }; - - // See if either of the recent shards needs to be updated - bool const curNotSynched = - latestShardIndex_ && *latestShardIndex_ != latestShardIndex; - bool const prevNotSynched = secondLatestShardIndex_ && - *secondLatestShardIndex_ != latestShardIndex - 1; - - // A new shard has been published. Move outdated - // shards to historical storage as needed - if (curNotSynched || prevNotSynched) - { - if (prev) - { - // Move the formerly second latest shard to historical storage - if (keepShard(*prev) && separateHistoricalPath) - moveShard(*prev); - - prev = std::nullopt; - } - - if (cur) - { - // The formerly latest shard is now the second latest - if (cur == latestShardIndex - 1) - prev = cur; - - // The formerly latest shard is no longer a 'recent' shard - else - { - // Move the formerly latest shard to historical storage - if (keepShard(*cur) && separateHistoricalPath) - moveShard(*cur); - } - - cur = std::nullopt; - } - } -} - -auto -DatabaseShardImp::prepareForNewShard( - std::uint32_t shardIndex, - std::uint32_t numHistoricalShards, - std::lock_guard const& lock) -> std::optional -{ - // Any shard earlier than the two most recent shards is a historical shard - auto const boundaryIndex{shardBoundaryIndex()}; - auto const isHistoricalShard = shardIndex < boundaryIndex; - - auto const designation = isHistoricalShard && !historicalPaths_.empty() - ? PathDesignation::historical - : PathDesignation::none; - - // Check shard count and available storage space - if (isHistoricalShard && numHistoricalShards >= maxHistoricalShards_) - { - JLOG(j_.error()) << "maximum number of historical shards reached"; - canAdd_ = false; - return std::nullopt; - } - if (!sufficientStorage(1, designation, lock)) - { - JLOG(j_.error()) << "insufficient storage space available"; - canAdd_ = false; - return std::nullopt; - } - - return designation; -} - -boost::filesystem::path -DatabaseShardImp::chooseHistoricalPath(std::lock_guard const&) const -{ - // If not configured with separate historical paths, - // use the main path (dir_) by default. - if (historicalPaths_.empty()) - return dir_; - - boost::filesystem::path historicalShardPath; - std::vector potentialPaths; - - for (boost::filesystem::path const& path : historicalPaths_) - { - if (boost::filesystem::space(path).available >= avgShardFileSz_) - potentialPaths.push_back(path); - } - - if (potentialPaths.empty()) - { - JLOG(j_.error()) << "failed to select a historical shard path"; - return ""; - } - - std::sample( - potentialPaths.begin(), - potentialPaths.end(), - &historicalShardPath, - 1, - default_prng()); - - return historicalShardPath; -} - -bool -DatabaseShardImp::checkHistoricalPaths(std::lock_guard const&) const -{ -#if BOOST_OS_LINUX - // Each historical shard path must correspond - // to a directory on a distinct device or file system. - // Currently, this constraint is enforced only on Linux. - std::unordered_map> filesystemIDs( - historicalPaths_.size()); - - for (auto const& path : historicalPaths_) - { - struct statvfs buffer; - if (statvfs(path.c_str(), &buffer)) - { - JLOG(j_.error()) - << "failed to acquire stats for 'historical_shard_path': " - << path; - return false; - } - - filesystemIDs[buffer.f_fsid].push_back(path.string()); - } - - bool ret = true; - for (auto const& entry : filesystemIDs) - { - // Check to see if any of the paths are stored on the same file system - if (entry.second.size() > 1) - { - // Two or more historical storage paths - // correspond to the same file system. - JLOG(j_.error()) - << "The following paths correspond to the same filesystem: " - << boost::algorithm::join(entry.second, ", ") - << ". Each configured historical storage path should" - " be on a unique device or filesystem."; - - ret = false; - } - } - - return ret; - -#else - // The requirement that each historical storage path - // corresponds to a distinct device or file system is - // enforced only on Linux, so on other platforms - // keep track of the available capacities for each - // path. Issue a warning if we suspect any of the paths - // may violate this requirement. - - // Map byte counts to each path that shares that byte count. - std::unordered_map> - uniqueCapacities(historicalPaths_.size()); - - for (auto const& path : historicalPaths_) - uniqueCapacities[boost::filesystem::space(path).available].push_back( - path.string()); - - for (auto const& entry : uniqueCapacities) - { - // Check to see if any paths have the same amount of available bytes. - if (entry.second.size() > 1) - { - // Two or more historical storage paths may - // correspond to the same device or file system. - JLOG(j_.warn()) - << "Each of the following paths have " << entry.first - << " bytes free, and may be located on the same device" - " or file system: " - << boost::algorithm::join(entry.second, ", ") - << ". Each configured historical storage path should" - " be on a unique device or file system."; - } - } -#endif - - return true; -} - -bool -DatabaseShardImp::callForLedgerSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) -{ - if (ledgerSeq < earliestLedgerSeq_) - { - JLOG(j_.warn()) << "callForLedgerSQLByLedgerSeq ledger seq too early: " - << ledgerSeq; - return false; - } - - return callForLedgerSQLByShardIndex(seqToShardIndex(ledgerSeq), callback); -} - -bool -DatabaseShardImp::callForLedgerSQLByShardIndex( - const uint32_t shardIndex, - std::function const& callback) -{ - std::lock_guard lock(mutex_); - - auto const it{shards_.find(shardIndex)}; - - return it != shards_.end() && - it->second->getState() == ShardState::finalized && - it->second->callForLedgerSQL(callback); -} - -bool -DatabaseShardImp::callForTransactionSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) -{ - return callForTransactionSQLByShardIndex( - seqToShardIndex(ledgerSeq), callback); -} - -bool -DatabaseShardImp::callForTransactionSQLByShardIndex( - std::uint32_t const shardIndex, - std::function const& callback) -{ - std::lock_guard lock(mutex_); - - auto const it{shards_.find(shardIndex)}; - - return it != shards_.end() && - it->second->getState() == ShardState::finalized && - it->second->callForTransactionSQL(callback); -} - -bool -DatabaseShardImp::iterateShardsForward( - std::optional minShardIndex, - std::function const& visit) -{ - std::lock_guard lock(mutex_); - - std::map>::iterator it, eit; - - if (!minShardIndex) - it = shards_.begin(); - else - it = shards_.lower_bound(*minShardIndex); - - eit = shards_.end(); - - for (; it != eit; it++) - { - if (it->second->getState() == ShardState::finalized) - { - if (!visit(*it->second)) - return false; - } - } - - return true; -} - -bool -DatabaseShardImp::iterateLedgerSQLsForward( - std::optional minShardIndex, - std::function const& - callback) -{ - return iterateShardsForward( - minShardIndex, [&callback](Shard& shard) -> bool { - return shard.callForLedgerSQL(callback); - }); -} - -bool -DatabaseShardImp::iterateTransactionSQLsForward( - std::optional minShardIndex, - std::function const& - callback) -{ - return iterateShardsForward( - minShardIndex, [&callback](Shard& shard) -> bool { - return shard.callForTransactionSQL(callback); - }); -} - -bool -DatabaseShardImp::iterateShardsBack( - std::optional maxShardIndex, - std::function const& visit) -{ - std::lock_guard lock(mutex_); - - std::map>::reverse_iterator it, eit; - - if (!maxShardIndex) - it = shards_.rbegin(); - else - it = std::make_reverse_iterator(shards_.upper_bound(*maxShardIndex)); - - eit = shards_.rend(); - - for (; it != eit; it++) - { - if (it->second->getState() == ShardState::finalized && - (!maxShardIndex || it->first <= *maxShardIndex)) - { - if (!visit(*it->second)) - return false; - } - } - - return true; -} - -bool -DatabaseShardImp::iterateLedgerSQLsBack( - std::optional maxShardIndex, - std::function const& - callback) -{ - return iterateShardsBack(maxShardIndex, [&callback](Shard& shard) -> bool { - return shard.callForLedgerSQL(callback); - }); -} - -bool -DatabaseShardImp::iterateTransactionSQLsBack( - std::optional maxShardIndex, - std::function const& - callback) -{ - return iterateShardsBack(maxShardIndex, [&callback](Shard& shard) -> bool { - return shard.callForTransactionSQL(callback); - }); -} - -std::unique_ptr -DatabaseShardImp::getShardInfo(std::lock_guard const&) const -{ - auto shardInfo{std::make_unique()}; - for (auto const& [_, shard] : shards_) - { - shardInfo->update( - shard->index(), shard->getState(), shard->getPercentProgress()); - } - - for (auto const shardIndex : preparedIndexes_) - shardInfo->update(shardIndex, ShardState::queued, 0); - - return shardInfo; -} - -size_t -DatabaseShardImp::getNumTasks() const -{ - std::lock_guard lock(mutex_); - return taskQueue_.size(); -} - -void -DatabaseShardImp::updatePeers(std::lock_guard const& lock) const -{ - if (!app_.config().standalone() && - app_.getOPs().getOperatingMode() != OperatingMode::DISCONNECTED) - { - auto const message{getShardInfo(lock)->makeMessage(app_)}; - app_.overlay().foreach(send_always(std::make_shared( - message, protocol::mtPEER_SHARD_INFO_V2))); - } -} - -void -DatabaseShardImp::startDatabaseImportThread(std::lock_guard const&) -{ - // Run the lengthy node store import process in the background - // on a dedicated thread. - databaseImporter_ = std::thread([this] { - doImportDatabase(); - - std::lock_guard lock(mutex_); - - // Make sure to clear this in case the import - // exited early. - databaseImportStatus_.reset(); - - // Detach the thread so subsequent attempts - // to start the import won't get held up by - // the old thread of execution - databaseImporter_.detach(); - }); -} - -//------------------------------------------------------------------------------ - -std::unique_ptr -make_ShardStore( - Application& app, - Scheduler& scheduler, - int readThreads, - beast::Journal j) -{ - // The shard store is optional. Future changes will require it. - Section const& section{ - app.config().section(ConfigSection::shardDatabase())}; - if (section.empty()) - return nullptr; - - return std::make_unique(app, scheduler, readThreads, j); -} - -} // namespace NodeStore -} // namespace ripple diff --git a/src/ripple/nodestore/impl/DatabaseShardImp.h b/src/ripple/nodestore/impl/DatabaseShardImp.h deleted file mode 100644 index 5cf1f3701c4..00000000000 --- a/src/ripple/nodestore/impl/DatabaseShardImp.h +++ /dev/null @@ -1,429 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NODESTORE_DATABASESHARDIMP_H_INCLUDED -#define RIPPLE_NODESTORE_DATABASESHARDIMP_H_INCLUDED - -#include -#include -#include - -#include - -namespace ripple { -namespace NodeStore { - -class DatabaseShardImp : public DatabaseShard -{ -public: - DatabaseShardImp() = delete; - DatabaseShardImp(DatabaseShardImp const&) = delete; - DatabaseShardImp(DatabaseShardImp&&) = delete; - DatabaseShardImp& - operator=(DatabaseShardImp const&) = delete; - DatabaseShardImp& - operator=(DatabaseShardImp&&) = delete; - - DatabaseShardImp( - Application& app, - Scheduler& scheduler, - int readThreads, - beast::Journal j); - - ~DatabaseShardImp() - { - stop(); - } - - [[nodiscard]] bool - init() override; - - std::optional - prepareLedger(std::uint32_t validLedgerSeq) override; - - bool - prepareShards(std::vector const& shardIndexes) override; - - void - removePreShard(std::uint32_t shardIndex) override; - - std::string - getPreShards() override; - - bool - importShard(std::uint32_t shardIndex, boost::filesystem::path const& srcDir) - override; - - std::shared_ptr - fetchLedger(uint256 const& hash, std::uint32_t ledgerSeq) override; - - void - setStored(std::shared_ptr const& ledger) override; - - std::unique_ptr - getShardInfo() const override; - - size_t - getNumTasks() const override; - - boost::filesystem::path const& - getRootDir() const override - { - return dir_; - } - - std::string - getName() const override - { - return backendName_; - } - - void - stop() override; - - /** Import the application local node store - - @param source The application node store. - */ - void - importDatabase(Database& source) override; - - void - doImportDatabase(); - - std::int32_t - getWriteLoad() const override; - - bool - isSameDB(std::uint32_t s1, std::uint32_t s2) override - { - return seqToShardIndex(s1) == seqToShardIndex(s2); - } - - void - store( - NodeObjectType type, - Blob&& data, - uint256 const& hash, - std::uint32_t ledgerSeq) override; - - void - sync() override{}; - - bool - storeLedger(std::shared_ptr const& srcLedger) override; - - void - sweep() override; - - Json::Value - getDatabaseImportStatus() const override; - - Json::Value - startNodeToShard() override; - - Json::Value - stopNodeToShard() override; - - std::optional - getDatabaseImportSequence() const override; - - bool - callForLedgerSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) override; - - bool - callForLedgerSQLByShardIndex( - std::uint32_t const shardIndex, - std::function const& callback) override; - - bool - callForTransactionSQLByLedgerSeq( - LedgerIndex ledgerSeq, - std::function const& callback) override; - - bool - callForTransactionSQLByShardIndex( - std::uint32_t const shardIndex, - std::function const& callback) override; - - bool - iterateLedgerSQLsForward( - std::optional minShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) override; - - bool - iterateTransactionSQLsForward( - std::optional minShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) override; - - bool - iterateLedgerSQLsBack( - std::optional maxShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) override; - - bool - iterateTransactionSQLsBack( - std::optional maxShardIndex, - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback) override; - -private: - enum class PathDesignation : uint8_t { - none, // No path specified - historical // Needs a historical path - }; - - struct DatabaseImportStatus - { - DatabaseImportStatus( - std::uint32_t const earliestIndex, - std::uint32_t const latestIndex, - std::uint32_t const currentIndex) - : earliestIndex(earliestIndex) - , latestIndex(latestIndex) - , currentIndex(currentIndex) - { - } - - // Index of the first shard to be imported - std::uint32_t earliestIndex{0}; - - // Index of the last shard to be imported - std::uint32_t latestIndex{0}; - - // Index of the shard currently being imported - std::uint32_t currentIndex{0}; - - // First ledger sequence of the current shard - std::uint32_t firstSeq{0}; - - // Last ledger sequence of the current shard - std::uint32_t lastSeq{0}; - - // The shard currently being imported - std::weak_ptr currentShard; - }; - - Application& app_; - mutable std::mutex mutex_; - bool init_{false}; - - // The context shared with all shard backend databases - std::unique_ptr ctx_; - - // Queue of background tasks to be performed - TaskQueue taskQueue_; - - // Shards held by this server - std::map> shards_; - - // Shard indexes being imported from the shard archive handler - std::set preparedIndexes_; - - // Shard index being acquired from the peer network - std::uint32_t acquireIndex_{0}; - - // The shard store root directory - boost::filesystem::path dir_; - - // If new shards can be stored - bool canAdd_{true}; - - // The name associated with the backend used with the shard store - std::string backendName_; - - // Maximum number of historical shards to store. - std::uint32_t maxHistoricalShards_{0}; - - // Contains historical shard paths - std::vector historicalPaths_; - - // Storage space utilized by the shard store (in bytes) - std::uint64_t fileSz_{0}; - - // Average storage space required by a shard (in bytes) - std::uint64_t avgShardFileSz_; - - // The limit of final shards with open databases at any time - std::uint32_t const openFinalLimit_; - - // File name used to mark shards being imported from node store - static constexpr auto databaseImportMarker_ = "database_import"; - - // latestShardIndex_ and secondLatestShardIndex hold the indexes - // of the shards most recently confirmed by the network. These - // values are not updated in real time and are modified only - // when adding shards to the database, in order to determine where - // pending shards will be stored on the filesystem. A value of - // std::nullopt indicates that the corresponding shard is not held - // by the database. - std::optional latestShardIndex_; - std::optional secondLatestShardIndex_; - - // Struct used for node store import progress - std::unique_ptr databaseImportStatus_; - - // Thread for running node store import - std::thread databaseImporter_; - - // Indicates whether the import should stop - std::atomic_bool haltDatabaseImport_{false}; - - // Initialize settings from the configuration file - // Lock must be held - bool - initConfig(std::lock_guard const&); - - std::shared_ptr - fetchNodeObject( - uint256 const& hash, - std::uint32_t ledgerSeq, - FetchReport& fetchReport, - bool duplicate) override; - - void - for_each(std::function)> f) override - { - Throw("Import from shard store not supported"); - } - - // Randomly select a shard index not stored - // Lock must be held - std::optional - findAcquireIndex( - std::uint32_t validLedgerSeq, - std::lock_guard const&); - - // Queue a task to finalize a shard by verifying its databases - // Lock must be held - void - finalizeShard( - std::shared_ptr& shard, - bool writeSQLite, - std::optional const& expectedHash); - - // Update storage and file descriptor usage stats - void - updateFileStats(); - - // Returns true if the file system has enough storage - // available to hold the specified number of shards. - // The value of pathDesignation determines whether - // the shard(s) in question are historical and thus - // meant to be stored at a path designated for historical - // shards. - bool - sufficientStorage( - std::uint32_t numShards, - PathDesignation pathDesignation, - std::lock_guard const&) const; - - bool - setStoredInShard( - std::shared_ptr& shard, - std::shared_ptr const& ledger); - - void - removeFailedShard(std::shared_ptr& shard); - - // Returns the index that represents the logical - // partition between historical and recent shards - std::uint32_t - shardBoundaryIndex() const; - - std::uint32_t - numHistoricalShards(std::lock_guard const& lock) const; - - // Shifts the recent and second most recent (by index) - // shards as new shards become available on the network. - // Older shards are moved to a historical shard path. - void - relocateOutdatedShards(std::lock_guard const& lock); - - // Checks whether the shard can be stored. If - // the new shard can't be stored, returns - // std::nullopt. Otherwise returns an enum - // indicating whether the new shard should be - // placed in a separate directory for historical - // shards. - std::optional - prepareForNewShard( - std::uint32_t shardIndex, - std::uint32_t numHistoricalShards, - std::lock_guard const& lock); - - boost::filesystem::path - chooseHistoricalPath(std::lock_guard const&) const; - - /** - * @brief iterateShardsForward Visits all shards starting from given - * in ascending order and calls given callback function to each - * of them passing shard as parameter. - * @param minShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param visit Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateShardsForward( - std::optional minShardIndex, - std::function const& visit); - - /** - * @brief iterateShardsBack Visits all shards starting from given - * in descending order and calls given callback function to each - * of them passing shard as parameter. - * @param maxShardIndex Start shard index to visit or none if all shards - * should be visited. - * @param visit Callback function to call. - * @return True if each callback function returned true, false otherwise. - */ - bool - iterateShardsBack( - std::optional maxShardIndex, - std::function const& visit); - - bool - checkHistoricalPaths(std::lock_guard const&) const; - - std::unique_ptr - getShardInfo(std::lock_guard const&) const; - - // Update peers with the status of every complete and incomplete shard - void - updatePeers(std::lock_guard const& lock) const; - - // Start the node store import process - void - startDatabaseImportThread(std::lock_guard const&); -}; - -} // namespace NodeStore -} // namespace ripple - -#endif diff --git a/src/ripple/nodestore/impl/DeterministicShard.cpp b/src/ripple/nodestore/impl/DeterministicShard.cpp deleted file mode 100644 index 5dd6bfb4cb3..00000000000 --- a/src/ripple/nodestore/impl/DeterministicShard.cpp +++ /dev/null @@ -1,216 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -DeterministicShard::DeterministicShard( - Application& app, - boost::filesystem::path const& dir, - std::uint32_t index, - beast::Journal j) - : app_(app) - , index_(index) - , dir_(dir / "tmp") - , ctx_(std::make_unique()) - , j_(j) - , curMemObjs_(0) - , maxMemObjs_( - app_.getShardStore()->ledgersPerShard() <= 256 ? maxMemObjsTest - : maxMemObjsDefault) -{ -} - -DeterministicShard::~DeterministicShard() -{ - close(true); -} - -bool -DeterministicShard::init(Serializer const& finalKey) -{ - auto db = app_.getShardStore(); - - auto fail = [&](std::string const& msg) { - JLOG(j_.error()) << "deterministic shard " << index_ - << " not created: " << msg; - backend_.reset(); - try - { - remove_all(dir_); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "deterministic shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - } - return false; - }; - - if (!db) - return fail("shard store not exists"); - - if (index_ < db->earliestShardIndex()) - return fail("Invalid shard index"); - - Config const& config{app_.config()}; - Section section{config.section(ConfigSection::shardDatabase())}; - auto const type{get(section, "type", "nudb")}; - auto const factory{Manager::instance().find(type)}; - if (!factory) - return fail("failed to find factory for " + type); - - section.set("path", dir_.string()); - backend_ = factory->createInstance( - NodeObject::keyBytes, section, 1, scheduler_, *ctx_, j_); - - if (!backend_) - return fail("failed to create database"); - - ripemd160_hasher h; - h(finalKey.data(), finalKey.size()); - auto const result{static_cast(h)}; - auto const hash{uint160::fromVoid(result.data())}; - - auto digest = [&](int n) { - auto const data{hash.data()}; - std::uint64_t result{0}; - - switch (n) - { - case 0: - case 1: - // Construct 64 bits from sequential eight bytes - for (int i = 0; i < 8; i++) - result = (result << 8) + data[n * 8 + i]; - break; - - case 2: - // Construct 64 bits using the last four bytes of data - result = (static_cast(data[16]) << 24) + - (static_cast(data[17]) << 16) + - (static_cast(data[18]) << 8) + - (static_cast(data[19])); - break; - } - - return result; - }; - auto const uid{digest(0)}; - auto const salt{digest(1)}; - auto const appType{digest(2) | deterministicType}; - - // Open or create the NuDB key/value store - try - { - if (exists(dir_)) - remove_all(dir_); - - backend_->open(true, appType, uid, salt); - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - return true; -} - -std::shared_ptr -make_DeterministicShard( - Application& app, - boost::filesystem::path const& shardDir, - std::uint32_t shardIndex, - Serializer const& finalKey, - beast::Journal j) -{ - std::shared_ptr dShard( - new DeterministicShard(app, shardDir, shardIndex, j)); - if (!dShard->init(finalKey)) - return {}; - return dShard; -} - -void -DeterministicShard::close(bool cancel) -{ - try - { - if (cancel) - { - backend_.reset(); - remove_all(dir_); - } - else - { - ctx_->flush(); - curMemObjs_ = 0; - backend_.reset(); - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "deterministic shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - } -} - -bool -DeterministicShard::store(std::shared_ptr const& nodeObject) -{ - try - { - backend_->store(nodeObject); - - // Flush to the backend if at threshold - if (++curMemObjs_ >= maxMemObjs_) - { - ctx_->flush(); - curMemObjs_ = 0; - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "deterministic shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - return true; -} - -} // namespace NodeStore -} // namespace ripple diff --git a/src/ripple/nodestore/impl/DeterministicShard.h b/src/ripple/nodestore/impl/DeterministicShard.h deleted file mode 100644 index c32a45127ef..00000000000 --- a/src/ripple/nodestore/impl/DeterministicShard.h +++ /dev/null @@ -1,174 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NODESTORE_DETERMINISTICSHARD_H_INCLUDED -#define RIPPLE_NODESTORE_DETERMINISTICSHARD_H_INCLUDED - -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -/** DeterministicShard class. - * - * 1. The init() method creates temporary folder dir_, - * and the deterministic shard is initialized in that folder. - * 2. The store() method adds object to memory pool. - * 3. The flush() method stores all objects from memory pool to the shard - * located in dir_ in sorted order. - * 4. The close(true) method closes the backend and removes the directory. - */ -class DeterministicShard -{ - constexpr static std::uint32_t maxMemObjsDefault = 16384u; - constexpr static std::uint32_t maxMemObjsTest = 16u; - - /* "SHRD" in ASCII */ - constexpr static std::uint64_t deterministicType = 0x5348524400000000ll; - -private: - DeterministicShard(DeterministicShard const&) = delete; - DeterministicShard& - operator=(DeterministicShard const&) = delete; - - /** Creates the object for shard database - * - * @param app Application object - * @param dir Directory where shard is located - * @param index Index of the shard - * @param j Journal to logging - */ - DeterministicShard( - Application& app, - boost::filesystem::path const& dir, - std::uint32_t index, - beast::Journal j); - - /** Initializes the deterministic shard. - * - * @param finalKey Serializer of shard's final key which consists of: - * shard version (32 bit) - * first ledger sequence in the shard (32 bit) - * last ledger sequence in the shard (32 bit) - * hash of last ledger (256 bits) - * @return true if no error, false if error - */ - bool - init(Serializer const& finalKey); - -public: - ~DeterministicShard(); - - /** Finalizes and closes the shard. - */ - void - close() - { - close(false); - } - - [[nodiscard]] boost::filesystem::path const& - getDir() const - { - return dir_; - } - - /** Store a node object in memory. - * - * @param nodeObject The node object to store - * @return true on success. - * @note Flushes all objects in memory to the backend when the number - * of node objects held in memory exceed a threshold - */ - [[nodiscard]] bool - store(std::shared_ptr const& nodeObject); - -private: - /** Finalizes and closes the shard. - * - * @param cancel True if reject the shard and delete all files, - * false if finalize the shard and store them - */ - void - close(bool cancel); - - // Application reference - Application& app_; - - // Shard Index - std::uint32_t const index_; - - // Path to temporary database files - boost::filesystem::path const dir_; - - // Dummy scheduler for deterministic write - DummyScheduler scheduler_; - - // NuDB context - std::unique_ptr ctx_; - - // NuDB key/value store for node objects - std::shared_ptr backend_; - - // Journal - beast::Journal const j_; - - // Current number of in-cache objects - std::uint32_t curMemObjs_; - - // Maximum number of in-cache objects - std::uint32_t const maxMemObjs_; - - friend std::shared_ptr - make_DeterministicShard( - Application& app, - boost::filesystem::path const& shardDir, - std::uint32_t shardIndex, - Serializer const& finalKey, - beast::Journal j); -}; - -/** Creates shared pointer to deterministic shard and initializes it. - * - * @param app Application object - * @param shardDir Directory where shard is located - * @param shardIndex Index of the shard - * @param finalKey Serializer of shard's ginal key which consists of: - * shard version (32 bit) - * first ledger sequence in the shard (32 bit) - * last ledger sequence in the shard (32 bit) - * hash of last ledger (256 bits) - * @param j Journal to logging - * @return Shared pointer to deterministic shard or {} in case of error. - */ -std::shared_ptr -make_DeterministicShard( - Application& app, - boost::filesystem::path const& shardDir, - std::uint32_t shardIndex, - Serializer const& finalKey, - beast::Journal j); - -} // namespace NodeStore -} // namespace ripple - -#endif diff --git a/src/ripple/nodestore/impl/Shard.cpp b/src/ripple/nodestore/impl/Shard.cpp deleted file mode 100644 index f05f56903d2..00000000000 --- a/src/ripple/nodestore/impl/Shard.cpp +++ /dev/null @@ -1,1274 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -uint256 const Shard::finalKey{0}; - -Shard::Shard( - Application& app, - DatabaseShard const& db, - std::uint32_t index, - beast::Journal j) - : Shard(app, db, index, "", j) -{ -} - -Shard::Shard( - Application& app, - DatabaseShard const& db, - std::uint32_t index, - boost::filesystem::path const& dir, - beast::Journal j) - : app_(app) - , j_(j) - , index_(index) - , firstSeq_(db.firstLedgerSeq(index)) - , lastSeq_(std::max(firstSeq_, db.lastLedgerSeq(index))) - , maxLedgers_(db.maxLedgers(index)) - , dir_((dir.empty() ? db.getRootDir() : dir) / std::to_string(index_)) -{ -} - -Shard::~Shard() -{ - if (!removeOnDestroy_) - return; - - if (backend_) - { - // Abort removal if the backend is in use - if (backendCount_ > 0) - { - JLOG(j_.error()) << "shard " << index_ - << " backend in use, unable to remove directory"; - return; - } - - // Release database files first otherwise remove_all may fail - backend_.reset(); - lgrSQLiteDB_.reset(); - txSQLiteDB_.reset(); - acquireInfo_.reset(); - } - - try - { - boost::filesystem::remove_all(dir_); - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - } -} - -bool -Shard::init(Scheduler& scheduler, nudb::context& context) -{ - Section section{app_.config().section(ConfigSection::shardDatabase())}; - std::string const type{get(section, "type", "nudb")}; - auto const factory{Manager::instance().find(type)}; - if (!factory) - { - JLOG(j_.error()) << "shard " << index_ << " failed to find factory for " - << type; - return false; - } - section.set("path", dir_.string()); - - std::lock_guard lock{mutex_}; - if (backend_) - { - JLOG(j_.error()) << "shard " << index_ << " already initialized"; - return false; - } - backend_ = factory->createInstance( - NodeObject::keyBytes, - section, - megabytes( - app_.config().getValueFor(SizedItem::burstSize, std::nullopt)), - scheduler, - context, - j_); - - return open(lock); -} - -bool -Shard::isOpen() const -{ - std::lock_guard lock(mutex_); - if (!backend_) - { - JLOG(j_.error()) << "shard " << index_ << " not initialized"; - return false; - } - - return backend_->isOpen(); -} - -bool -Shard::tryClose() -{ - // Keep database open if being acquired or finalized - if (state_ != ShardState::finalized) - return false; - - std::lock_guard lock(mutex_); - - // Keep database open if in use - if (backendCount_ > 0) - return false; - - if (!backend_) - { - JLOG(j_.error()) << "shard " << index_ << " not initialized"; - return false; - } - if (!backend_->isOpen()) - return false; - - try - { - backend_->close(); - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - lgrSQLiteDB_.reset(); - txSQLiteDB_.reset(); - acquireInfo_.reset(); - - // Reset caches to reduce memory use - app_.getShardFamily()->getFullBelowCache(lastSeq_)->reset(); - app_.getShardFamily()->getTreeNodeCache(lastSeq_)->reset(); - - return true; -} - -std::optional -Shard::prepare() -{ - if (state_ != ShardState::acquire) - { - JLOG(j_.warn()) << "shard " << index_ - << " prepare called when not acquiring"; - return std::nullopt; - } - - std::lock_guard lock(mutex_); - if (!acquireInfo_) - { - JLOG(j_.error()) << "shard " << index_ - << " missing acquire SQLite database"; - return std::nullopt; - } - - if (acquireInfo_->storedSeqs.empty()) - return lastSeq_; - return prevMissing(acquireInfo_->storedSeqs, 1 + lastSeq_, firstSeq_); -} - -bool -Shard::storeNodeObject(std::shared_ptr const& nodeObject) -{ - if (state_ != ShardState::acquire) - { - // The import node store case is an exception - if (nodeObject->getHash() != finalKey) - { - // Ignore residual calls from InboundLedgers - JLOG(j_.trace()) << "shard " << index_ << " not acquiring"; - return false; - } - } - - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return false; - - try - { - std::lock_guard lock(mutex_); - backend_->store(nodeObject); - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - return true; -} - -std::shared_ptr -Shard::fetchNodeObject(uint256 const& hash, FetchReport& fetchReport) -{ - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return nullptr; - - std::shared_ptr nodeObject; - - // Try the backend - Status status; - try - { - std::lock_guard lock(mutex_); - status = backend_->fetch(hash.data(), &nodeObject); - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return nullptr; - } - - switch (status) - { - case ok: - case notFound: - break; - case dataCorrupt: { - JLOG(j_.fatal()) - << "shard " << index_ << ". Corrupt node object at hash " - << to_string(hash); - break; - } - default: { - JLOG(j_.warn()) - << "shard " << index_ << ". Unknown status=" << status - << " fetching node object at hash " << to_string(hash); - break; - } - } - - if (nodeObject) - fetchReport.wasFound = true; - - return nodeObject; -} - -Shard::StoreLedgerResult -Shard::storeLedger( - std::shared_ptr const& srcLedger, - std::shared_ptr const& next) -{ - StoreLedgerResult result; - if (state_ != ShardState::acquire) - { - // Ignore residual calls from InboundLedgers - JLOG(j_.trace()) << "shard " << index_ << ". Not acquiring"; - return result; - } - if (containsLedger(srcLedger->info().seq)) - { - JLOG(j_.trace()) << "shard " << index_ << ". Ledger already stored"; - return result; - } - - auto fail = [&](std::string const& msg) { - JLOG(j_.error()) << "shard " << index_ << ". Source ledger sequence " - << srcLedger->info().seq << ". " << msg; - result.error = true; - return result; - }; - - if (srcLedger->info().hash.isZero()) - return fail("Invalid hash"); - if (srcLedger->info().accountHash.isZero()) - return fail("Invalid account hash"); - - auto& srcDB{const_cast(srcLedger->stateMap().family().db())}; - if (&srcDB == &(app_.getShardFamily()->db())) - return fail("Source and destination databases are the same"); - - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return fail("Failed to lock backend"); - - Batch batch; - batch.reserve(batchWritePreallocationSize); - auto storeBatch = [&]() { - std::uint64_t sz{0}; - for (auto const& nodeObject : batch) - sz += nodeObject->getData().size(); - - try - { - std::lock_guard lock(mutex_); - backend_->storeBatch(batch); - } - catch (std::exception const& e) - { - fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - return false; - } - - result.count += batch.size(); - result.size += sz; - batch.clear(); - return true; - }; - - // Store ledger header - { - Serializer s(sizeof(std::uint32_t) + sizeof(LedgerInfo)); - s.add32(HashPrefix::ledgerMaster); - addRaw(srcLedger->info(), s); - auto nodeObject = NodeObject::createObject( - hotLEDGER, std::move(s.modData()), srcLedger->info().hash); - batch.emplace_back(std::move(nodeObject)); - } - - bool error = false; - auto visit = [&](SHAMapTreeNode const& node) { - if (!stop_) - { - if (auto nodeObject = srcDB.fetchNodeObject( - node.getHash().as_uint256(), srcLedger->info().seq)) - { - batch.emplace_back(std::move(nodeObject)); - if (batch.size() < batchWritePreallocationSize || storeBatch()) - return true; - } - } - - error = true; - return false; - }; - - // Store the state map - if (srcLedger->stateMap().getHash().isNonZero()) - { - if (!srcLedger->stateMap().isValid()) - return fail("Invalid state map"); - - if (next && next->info().parentHash == srcLedger->info().hash) - { - auto have = next->stateMap().snapShot(false); - srcLedger->stateMap().snapShot(false)->visitDifferences( - &(*have), visit); - } - else - srcLedger->stateMap().snapShot(false)->visitNodes(visit); - if (error) - return fail("Failed to store state map"); - } - - // Store the transaction map - if (srcLedger->info().txHash.isNonZero()) - { - if (!srcLedger->txMap().isValid()) - return fail("Invalid transaction map"); - - srcLedger->txMap().snapShot(false)->visitNodes(visit); - if (error) - return fail("Failed to store transaction map"); - } - - if (!batch.empty() && !storeBatch()) - return fail("Failed to store"); - - return result; -} - -bool -Shard::setLedgerStored(std::shared_ptr const& ledger) -{ - if (state_ != ShardState::acquire) - { - // Ignore residual calls from InboundLedgers - JLOG(j_.trace()) << "shard " << index_ << " not acquiring"; - return false; - } - - auto fail = [&](std::string const& msg) { - JLOG(j_.error()) << "shard " << index_ << ". " << msg; - return false; - }; - - auto const ledgerSeq{ledger->info().seq}; - if (ledgerSeq < firstSeq_ || ledgerSeq > lastSeq_) - return fail("Invalid ledger sequence " + std::to_string(ledgerSeq)); - - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return false; - - // This lock is used as an optimization to prevent unneeded - // calls to storeSQLite before acquireInfo_ is updated - std::lock_guard storedLock(storedMutex_); - - { - std::lock_guard lock(mutex_); - if (!acquireInfo_) - return fail("Missing acquire SQLite database"); - - if (boost::icl::contains(acquireInfo_->storedSeqs, ledgerSeq)) - { - // Ignore redundant calls - JLOG(j_.debug()) << "shard " << index_ << " ledger sequence " - << ledgerSeq << " already stored"; - return true; - } - } - - if (!storeSQLite(ledger)) - return fail("Failed to store ledger"); - - std::lock_guard lock(mutex_); - - // Update the acquire database - acquireInfo_->storedSeqs.insert(ledgerSeq); - - try - { - auto session{acquireInfo_->SQLiteDB->checkoutDb()}; - soci::blob sociBlob(*session); - convert(to_string(acquireInfo_->storedSeqs), sociBlob); - if (ledgerSeq == lastSeq_) - { - // Store shard's last ledger hash - auto const sHash{to_string(ledger->info().hash)}; - *session << "UPDATE Shard " - "SET LastLedgerHash = :lastLedgerHash," - "StoredLedgerSeqs = :storedLedgerSeqs " - "WHERE ShardIndex = :shardIndex;", - soci::use(sHash), soci::use(sociBlob), soci::use(index_); - } - else - { - *session << "UPDATE Shard " - "SET StoredLedgerSeqs = :storedLedgerSeqs " - "WHERE ShardIndex = :shardIndex;", - soci::use(sociBlob), soci::use(index_); - } - } - catch (std::exception const& e) - { - acquireInfo_->storedSeqs.erase(ledgerSeq); - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - // Update progress - progress_ = boost::icl::length(acquireInfo_->storedSeqs); - if (progress_ == maxLedgers_) - state_ = ShardState::complete; - - setFileStats(lock); - JLOG(j_.trace()) << "shard " << index_ << " stored ledger sequence " - << ledgerSeq; - return true; -} - -bool -Shard::containsLedger(std::uint32_t ledgerSeq) const -{ - if (ledgerSeq < firstSeq_ || ledgerSeq > lastSeq_) - return false; - if (state_ != ShardState::acquire) - return true; - - std::lock_guard lock(mutex_); - if (!acquireInfo_) - { - JLOG(j_.error()) << "shard " << index_ - << " missing acquire SQLite database"; - return false; - } - return boost::icl::contains(acquireInfo_->storedSeqs, ledgerSeq); -} - -std::chrono::steady_clock::time_point -Shard::getLastUse() const -{ - std::lock_guard lock(mutex_); - return lastAccess_; -} - -std::pair -Shard::getFileInfo() const -{ - std::lock_guard lock(mutex_); - return {fileSz_, fdRequired_}; -} - -std::int32_t -Shard::getWriteLoad() -{ - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return 0; - std::lock_guard lock(mutex_); - return backend_->getWriteLoad(); -} - -bool -Shard::isLegacy() const -{ - std::lock_guard lock(mutex_); - return legacy_; -} - -bool -Shard::finalize(bool writeSQLite, std::optional const& referenceHash) -{ - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return false; - - uint256 hash{0}; - std::uint32_t ledgerSeq{0}; - auto fail = [&](std::string const& msg) { - JLOG(j_.fatal()) << "shard " << index_ << ". " << msg - << (hash.isZero() ? "" - : ". Ledger hash " + to_string(hash)) - << (ledgerSeq == 0 ? "" - : ". Ledger sequence " + - std::to_string(ledgerSeq)); - state_ = ShardState::finalizing; - progress_ = 0; - busy_ = false; - return false; - }; - - try - { - std::lock_guard lock(mutex_); - - state_ = ShardState::finalizing; - progress_ = 0; - - // Check if a final key has been stored - if (std::shared_ptr nodeObject; - backend_->fetch(finalKey.data(), &nodeObject) == Status::ok) - { - // Check final key's value - SerialIter sIt( - nodeObject->getData().data(), nodeObject->getData().size()); - if (sIt.get32() != version) - return fail("invalid version"); - - if (sIt.get32() != firstSeq_ || sIt.get32() != lastSeq_) - return fail("out of range ledger sequences"); - - if (hash = sIt.get256(); hash.isZero()) - return fail("invalid last ledger hash"); - } - else - { - // In the absence of a final key, an acquire SQLite database - // must be present in order to verify the shard - if (!acquireInfo_) - return fail("missing acquire SQLite database"); - - auto [res, seqshash] = selectAcquireDBLedgerSeqsHash( - *acquireInfo_->SQLiteDB->checkoutDb(), index_); - - if (!res) - return fail("missing or invalid ShardIndex"); - - if (!seqshash.hash) - return fail("missing LastLedgerHash"); - - if (!hash.parseHex(*seqshash.hash) || hash.isZero()) - return fail("invalid LastLedgerHash"); - - if (!seqshash.sequences) - return fail("missing StoredLedgerSeqs"); - - auto& storedSeqs{acquireInfo_->storedSeqs}; - if (!from_string(storedSeqs, *seqshash.sequences) || - boost::icl::first(storedSeqs) != firstSeq_ || - boost::icl::last(storedSeqs) != lastSeq_ || - storedSeqs.size() != maxLedgers_) - { - return fail("invalid StoredLedgerSeqs"); - } - } - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - // Verify the last ledger hash of a downloaded shard - // using a ledger hash obtained from the peer network - if (referenceHash && *referenceHash != hash) - return fail("invalid last ledger hash"); - - // Verify every ledger stored in the backend - Config const& config{app_.config()}; - std::shared_ptr ledger; - std::shared_ptr next; - auto const lastLedgerHash{hash}; - auto& shardFamily{*app_.getShardFamily()}; - auto const fullBelowCache{shardFamily.getFullBelowCache(lastSeq_)}; - auto const treeNodeCache{shardFamily.getTreeNodeCache(lastSeq_)}; - - // Reset caches to reduce memory usage - fullBelowCache->reset(); - treeNodeCache->reset(); - - Serializer s; - s.add32(version); - s.add32(firstSeq_); - s.add32(lastSeq_); - s.addBitString(lastLedgerHash); - - std::shared_ptr dShard{ - make_DeterministicShard(app_, dir_, index_, s, j_)}; - if (!dShard) - return fail("Failed to create deterministic shard"); - - // Start with the last ledger in the shard and walk backwards from - // child to parent until we reach the first ledger - ledgerSeq = lastSeq_; - while (ledgerSeq >= firstSeq_) - { - if (stop_) - return false; - - auto nodeObject{verifyFetch(hash)}; - if (!nodeObject) - return fail("invalid ledger"); - - ledger = std::make_shared( - deserializePrefixedHeader(makeSlice(nodeObject->getData())), - config, - shardFamily); - if (ledger->info().seq != ledgerSeq) - return fail("invalid ledger sequence"); - if (ledger->info().hash != hash) - return fail("invalid ledger hash"); - - ledger->stateMap().setLedgerSeq(ledgerSeq); - ledger->txMap().setLedgerSeq(ledgerSeq); - ledger->setImmutable(); - if (!ledger->stateMap().fetchRoot( - SHAMapHash{ledger->info().accountHash}, nullptr)) - { - return fail("missing root STATE node"); - } - if (ledger->info().txHash.isNonZero() && - !ledger->txMap().fetchRoot( - SHAMapHash{ledger->info().txHash}, nullptr)) - { - return fail("missing root TXN node"); - } - - if (!verifyLedger(ledger, next, dShard)) - return fail("failed to verify ledger"); - - if (!dShard->store(nodeObject)) - return fail("failed to store node object"); - - if (writeSQLite && !storeSQLite(ledger)) - return fail("failed storing to SQLite databases"); - - assert( - ledger->info().seq == ledgerSeq && - (ledger->info().seq < XRP_LEDGER_EARLIEST_FEES || - ledger->read(keylet::fees()))); - - hash = ledger->info().parentHash; - next = std::move(ledger); - - // Update progress - progress_ = maxLedgers_ - (ledgerSeq - firstSeq_); - - --ledgerSeq; - - fullBelowCache->reset(); - treeNodeCache->reset(); - } - - JLOG(j_.debug()) << "shard " << index_ << " is valid"; - - /* - TODO MP - SQLite VACUUM blocks all database access while processing. - Depending on the file size, that can take a while. Until we find - a non-blocking way of doing this, we cannot enable vacuum as - it can desync a server. - - try - { - // VACUUM the SQLite databases - auto const tmpDir {dir_ / "tmp_vacuum"}; - create_directory(tmpDir); - - auto vacuum = [&tmpDir](std::unique_ptr& sqliteDB) - { - auto session {sqliteDB->checkoutDb()}; - *session << "PRAGMA synchronous=OFF;"; - *session << "PRAGMA journal_mode=OFF;"; - *session << "PRAGMA temp_store_directory='" << - tmpDir.string() << "';"; - *session << "VACUUM;"; - }; - vacuum(lgrSQLiteDB_); - vacuum(txSQLiteDB_); - remove_all(tmpDir); - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - */ - - auto const nodeObject{ - NodeObject::createObject(hotUNKNOWN, std::move(s.modData()), finalKey)}; - if (!dShard->store(nodeObject)) - return fail("failed to store node object"); - - try - { - { - // Store final key's value, may already be stored - std::lock_guard lock(mutex_); - backend_->store(nodeObject); - } - - // Do not allow all other threads work with the shard - busy_ = true; - - // Wait until all other threads leave the shard - while (backendCount_ > 1) - std::this_thread::yield(); - - std::lock_guard lock(mutex_); - - // Close original backend - backend_->close(); - - // Close SQL databases - lgrSQLiteDB_.reset(); - txSQLiteDB_.reset(); - - // Remove the acquire SQLite database - if (acquireInfo_) - { - acquireInfo_.reset(); - remove_all(dir_ / AcquireShardDBName); - } - - // Close deterministic backend - dShard->close(); - - // Replace original backend with deterministic backend - remove(dir_ / "nudb.key"); - remove(dir_ / "nudb.dat"); - rename(dShard->getDir() / "nudb.key", dir_ / "nudb.key"); - rename(dShard->getDir() / "nudb.dat", dir_ / "nudb.dat"); - - // Re-open deterministic shard - if (!open(lock)) - return fail("failed to open"); - - assert(state_ == ShardState::finalized); - - // Allow all other threads work with the shard - busy_ = false; - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - return true; -} - -bool -Shard::open(std::lock_guard const& lock) -{ - using namespace boost::filesystem; - Config const& config{app_.config()}; - auto preexist{false}; - auto fail = [this, &preexist](std::string const& msg) REQUIRES(mutex_) { - backend_->close(); - lgrSQLiteDB_.reset(); - txSQLiteDB_.reset(); - acquireInfo_.reset(); - - state_ = ShardState::acquire; - progress_ = 0; - - if (!preexist) - remove_all(dir_); - - if (!msg.empty()) - { - JLOG(j_.fatal()) << "shard " << index_ << " " << msg; - } - return false; - }; - auto createAcquireInfo = [this, &config]() REQUIRES(mutex_) { - DatabaseCon::Setup setup; - setup.startUp = config.standalone() ? config.LOAD : config.START_UP; - setup.standAlone = config.standalone(); - setup.dataDir = dir_; - setup.useGlobalPragma = true; - - acquireInfo_ = std::make_unique(); - acquireInfo_->SQLiteDB = makeAcquireDB( - setup, - DatabaseCon::CheckpointerSetup{&app_.getJobQueue(), &app_.logs()}, - j_); - - state_ = ShardState::acquire; - progress_ = 0; - }; - - try - { - // Open or create the NuDB key/value store - preexist = exists(dir_); - backend_->open(!preexist); - - if (!preexist) - { - // A new shard - createAcquireInfo(); - insertAcquireDBIndex(acquireInfo_->SQLiteDB->getSession(), index_); - } - else if (exists(dir_ / AcquireShardDBName)) - { - // A shard being acquired, backend is likely incomplete - createAcquireInfo(); - auto [res, s] = selectAcquireDBLedgerSeqs( - acquireInfo_->SQLiteDB->getSession(), index_); - - if (!res) - return fail("invalid acquire SQLite database"); - - if (s) - { - auto& storedSeqs{acquireInfo_->storedSeqs}; - if (!from_string(storedSeqs, *s)) - return fail("invalid StoredLedgerSeqs"); - - if (boost::icl::first(storedSeqs) < firstSeq_ || - boost::icl::last(storedSeqs) > lastSeq_) - { - return fail("invalid StoredLedgerSeqs"); - } - - // Check if backend is complete - progress_ = boost::icl::length(storedSeqs); - if (progress_ == maxLedgers_) - state_ = ShardState::complete; - } - } - else - { - // A shard with a finalized or complete state - std::shared_ptr nodeObject; - if (backend_->fetch(finalKey.data(), &nodeObject) != Status::ok) - { - legacy_ = true; - return fail("incompatible, missing backend final key"); - } - - // Check final key's value - SerialIter sIt( - nodeObject->getData().data(), nodeObject->getData().size()); - if (sIt.get32() != version) - return fail("invalid version"); - - if (sIt.get32() != firstSeq_ || sIt.get32() != lastSeq_) - return fail("out of range ledger sequences"); - - if (sIt.get256().isZero()) - return fail("invalid last ledger hash"); - - if (exists(dir_ / LgrDBName) && exists(dir_ / TxDBName)) - { - lastAccess_ = std::chrono::steady_clock::now(); - state_ = ShardState::finalized; - } - else - state_ = ShardState::complete; - - progress_ = maxLedgers_; - } - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - if (!initSQLite(lock)) - return fail({}); - - setFileStats(lock); - return true; -} - -bool -Shard::initSQLite(std::lock_guard const&) -{ - Config const& config{app_.config()}; - DatabaseCon::Setup const setup = [&]() { - DatabaseCon::Setup setup; - setup.startUp = config.standalone() ? config.LOAD : config.START_UP; - setup.standAlone = config.standalone(); - setup.dataDir = dir_; - setup.useGlobalPragma = (state_ != ShardState::complete); - return setup; - }(); - - try - { - if (lgrSQLiteDB_) - lgrSQLiteDB_.reset(); - - if (txSQLiteDB_) - txSQLiteDB_.reset(); - - switch (state_) - { - case ShardState::complete: - case ShardState::finalizing: - case ShardState::finalized: { - auto [lgr, tx] = makeShardCompleteLedgerDBs(config, setup, j_); - - lgrSQLiteDB_ = std::move(lgr); - lgrSQLiteDB_->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor( - SizedItem::lgrDBCache, std::nullopt))); - - txSQLiteDB_ = std::move(tx); - txSQLiteDB_->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor( - SizedItem::txnDBCache, std::nullopt))); - break; - } - - // case ShardState::acquire: - // case ShardState::queued: - default: { - // Incomplete shards use a Write Ahead Log for performance - auto [lgr, tx] = makeShardIncompleteLedgerDBs( - config, - setup, - DatabaseCon::CheckpointerSetup{ - &app_.getJobQueue(), &app_.logs()}, - j_); - - lgrSQLiteDB_ = std::move(lgr); - lgrSQLiteDB_->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::lgrDBCache))); - - txSQLiteDB_ = std::move(tx); - txSQLiteDB_->getSession() << boost::str( - boost::format("PRAGMA cache_size=-%d;") % - kilobytes(config.getValueFor(SizedItem::txnDBCache))); - break; - } - } - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - return true; -} - -bool -Shard::storeSQLite(std::shared_ptr const& ledger) -{ - if (stop_) - return false; - - try - { - std::lock_guard lock(mutex_); - - auto res = updateLedgerDBs( - *txSQLiteDB_->checkoutDb(), - *lgrSQLiteDB_->checkoutDb(), - ledger, - index_, - stop_, - j_); - - if (!res) - return false; - - // Update the acquire database if present - if (acquireInfo_) - { - std::optional s; - if (!acquireInfo_->storedSeqs.empty()) - s = to_string(acquireInfo_->storedSeqs); - - updateAcquireDB( - acquireInfo_->SQLiteDB->getSession(), - ledger, - index_, - lastSeq_, - s); - } - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - return false; - } - - return true; -} - -void -Shard::setFileStats(std::lock_guard const&) -{ - fileSz_ = 0; - fdRequired_ = 0; - try - { - using namespace boost::filesystem; - for (auto const& d : directory_iterator(dir_)) - { - if (is_regular_file(d)) - { - fileSz_ += file_size(d); - ++fdRequired_; - } - } - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "shard " << index_ - << ". Exception caught in function " << __func__ - << ". Error: " << e.what(); - } -} - -bool -Shard::verifyLedger( - std::shared_ptr const& ledger, - std::shared_ptr const& next, - std::shared_ptr const& dShard) const -{ - auto fail = [j = j_, index = index_, &ledger](std::string const& msg) { - JLOG(j.error()) << "shard " << index << ". " << msg - << (ledger->info().hash.isZero() ? "" - : ". Ledger hash " + - to_string(ledger->info().hash)) - << (ledger->info().seq == 0 ? "" - : ". Ledger sequence " + - std::to_string(ledger->info().seq)); - return false; - }; - - if (ledger->info().hash.isZero()) - return fail("Invalid ledger hash"); - if (ledger->info().accountHash.isZero()) - return fail("Invalid ledger account hash"); - - bool error{false}; - auto visit = [this, &error, &dShard](SHAMapTreeNode const& node) { - if (stop_) - return false; - - auto nodeObject{verifyFetch(node.getHash().as_uint256())}; - if (!nodeObject || !dShard->store(nodeObject)) - error = true; - - return !error; - }; - - // Verify the state map - if (ledger->stateMap().getHash().isNonZero()) - { - if (!ledger->stateMap().isValid()) - return fail("Invalid state map"); - - try - { - if (next && next->info().parentHash == ledger->info().hash) - ledger->stateMap().visitDifferences(&next->stateMap(), visit); - else - ledger->stateMap().visitNodes(visit); - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - if (stop_) - return false; - if (error) - return fail("Invalid state map"); - } - - // Verify the transaction map - if (ledger->info().txHash.isNonZero()) - { - if (!ledger->txMap().isValid()) - return fail("Invalid transaction map"); - - try - { - ledger->txMap().visitNodes(visit); - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } - - if (stop_) - return false; - if (error) - return fail("Invalid transaction map"); - } - - return true; -} - -std::shared_ptr -Shard::verifyFetch(uint256 const& hash) const -{ - std::shared_ptr nodeObject; - auto fail = - [j = j_, index = index_, &hash, &nodeObject](std::string const& msg) { - JLOG(j.error()) << "shard " << index << ". " << msg - << ". Node object hash " << to_string(hash); - nodeObject.reset(); - return nodeObject; - }; - - try - { - std::lock_guard lock(mutex_); - switch (backend_->fetch(hash.data(), &nodeObject)) - { - case ok: - // Verify that the hash of node object matches the payload - if (nodeObject->getHash() != - sha512Half(makeSlice(nodeObject->getData()))) - return fail("Node object hash does not match payload"); - return nodeObject; - case notFound: - return fail("Missing node object"); - case dataCorrupt: - return fail("Corrupt node object"); - default: - return fail("Unknown error"); - } - } - catch (std::exception const& e) - { - return fail( - std::string(". Exception caught in function ") + __func__ + - ". Error: " + e.what()); - } -} - -Shard::Count -Shard::makeBackendCount() -{ - if (stop_ || busy_) - return Shard::Count{nullptr}; - - std::lock_guard lock(mutex_); - if (!backend_) - { - JLOG(j_.error()) << "shard " << index_ << " not initialized"; - return Shard::Count{nullptr}; - } - if (!backend_->isOpen()) - { - if (!open(lock)) - return Shard::Count{nullptr}; - } - else if (state_ == ShardState::finalized) - lastAccess_ = std::chrono::steady_clock::now(); - - return Shard::Count(&backendCount_); -} - -bool -Shard::doCallForSQL( - std::function const& callback, - LockedSociSession&& db) -{ - return callback(*db); -} - -bool -Shard::doCallForSQL( - std::function const& - callback, - LockedSociSession&& db) -{ - return callback(*db, index_); -} - -} // namespace NodeStore -} // namespace ripple diff --git a/src/ripple/nodestore/impl/Shard.h b/src/ripple/nodestore/impl/Shard.h deleted file mode 100644 index 210bdd54a60..00000000000 --- a/src/ripple/nodestore/impl/Shard.h +++ /dev/null @@ -1,432 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2017 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_NODESTORE_SHARD_H_INCLUDED -#define RIPPLE_NODESTORE_SHARD_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace ripple { -namespace NodeStore { - -using PCache = TaggedCache; -using NCache = KeyCache; -class DatabaseShard; - -/* A range of historical ledgers backed by a node store. - Shards are indexed and store `ledgersPerShard`. - Shard `i` stores ledgers starting with sequence: `1 + (i * ledgersPerShard)` - and ending with sequence: `(i + 1) * ledgersPerShard`. - Once a shard has all its ledgers, it is never written to again. - - Public functions can be called concurrently from any thread. -*/ -class Shard final -{ -public: - /// Copy constructor (disallowed) - Shard(Shard const&) = delete; - - /// Move constructor (disallowed) - Shard(Shard&&) = delete; - - // Copy assignment (disallowed) - Shard& - operator=(Shard const&) = delete; - - // Move assignment (disallowed) - Shard& - operator=(Shard&&) = delete; - - Shard( - Application& app, - DatabaseShard const& db, - std::uint32_t index, - boost::filesystem::path const& dir, - beast::Journal j); - - Shard( - Application& app, - DatabaseShard const& db, - std::uint32_t index, - beast::Journal j); - - ~Shard(); - - /** Initialize shard. - - @param scheduler The scheduler to use for performing asynchronous tasks. - @param context The context to use for the backend. - */ - [[nodiscard]] bool - init(Scheduler& scheduler, nudb::context& context); - - /** Returns true if the database are open. - */ - [[nodiscard]] bool - isOpen() const; - - /** Try to close databases if not in use. - - @return true if databases were closed. - */ - bool - tryClose(); - - /** Notify shard to prepare for shutdown. - */ - void - stop() noexcept - { - stop_ = true; - } - - [[nodiscard]] std::optional - prepare(); - - [[nodiscard]] bool - storeNodeObject(std::shared_ptr const& nodeObject); - - [[nodiscard]] std::shared_ptr - fetchNodeObject(uint256 const& hash, FetchReport& fetchReport); - - /** Store a ledger. - - @param srcLedger The ledger to store. - @param next The ledger that immediately follows srcLedger, can be null. - @return StoreLedgerResult containing data about the store. - */ - struct StoreLedgerResult - { - std::uint64_t count{0}; // Number of storage calls - std::uint64_t size{0}; // Number of bytes stored - bool error{false}; - }; - - [[nodiscard]] StoreLedgerResult - storeLedger( - std::shared_ptr const& srcLedger, - std::shared_ptr const& next); - - [[nodiscard]] bool - setLedgerStored(std::shared_ptr const& ledger); - - [[nodiscard]] bool - containsLedger(std::uint32_t ledgerSeq) const; - - [[nodiscard]] std::uint32_t - index() const noexcept - { - return index_; - } - - [[nodiscard]] boost::filesystem::path const& - getDir() const noexcept - { - return dir_; - } - - [[nodiscard]] std::chrono::steady_clock::time_point - getLastUse() const; - - /** Returns a pair where the first item describes the storage space - utilized and the second item is the number of file descriptors required. - */ - [[nodiscard]] std::pair - getFileInfo() const; - - [[nodiscard]] ShardState - getState() const noexcept - { - return state_; - } - - /** Returns a percent signifying how complete - the current state of the shard is. - */ - [[nodiscard]] std::uint32_t - getPercentProgress() const noexcept - { - return calculatePercent(progress_, maxLedgers_); - } - - [[nodiscard]] std::int32_t - getWriteLoad(); - - /** Returns `true` if shard is older, without final key data - */ - [[nodiscard]] bool - isLegacy() const; - - /** Finalize shard by walking its ledgers, verifying each Merkle tree and - creating a deterministic backend. - - @param writeSQLite If true, SQLite entries will be rewritten using - verified backend data. - @param referenceHash If present, this hash must match the hash - of the last ledger in the shard. - */ - [[nodiscard]] bool - finalize(bool writeSQLite, std::optional const& referenceHash); - - /** Enables removal of the shard directory on destruction. - */ - void - removeOnDestroy() noexcept - { - removeOnDestroy_ = true; - } - - std::string - getStoredSeqs() - { - std::lock_guard lock(mutex_); - if (!acquireInfo_) - return ""; - - return to_string(acquireInfo_->storedSeqs); - } - - /** Invoke a callback on the ledger SQLite db - - @param callback Callback function to call. - @return Value returned by callback function. - */ - template - bool - callForLedgerSQL(std::function const& callback) - { - return callForSQL(callback, lgrSQLiteDB_->checkoutDb()); - } - - /** Invoke a callback on the transaction SQLite db - - @param callback Callback function to call. - @return Value returned by callback function. - */ - template - bool - callForTransactionSQL(std::function const& callback) - { - return callForSQL(callback, txSQLiteDB_->checkoutDb()); - } - - // Current shard version - static constexpr std::uint32_t version{2}; - - // The finalKey is a hard coded value of zero. It is used to store - // finalizing shard data to the backend. The data contains a version, - // last ledger's hash, and the first and last ledger sequences. - static uint256 const finalKey; - -private: - class Count final - { - public: - Count(Count const&) = delete; - Count& - operator=(Count const&) = delete; - Count& - operator=(Count&&) = delete; - - Count(Count&& other) noexcept : counter_(other.counter_) - { - other.counter_ = nullptr; - } - - explicit Count(std::atomic* counter) noexcept - : counter_(counter) - { - if (counter_) - ++(*counter_); - } - - ~Count() noexcept - { - if (counter_) - --(*counter_); - } - - explicit operator bool() const noexcept - { - return counter_ != nullptr; - } - - private: - std::atomic* counter_; - }; - - struct AcquireInfo - { - // SQLite database to track information about what has been acquired - std::unique_ptr SQLiteDB; - - // Tracks the sequences of ledgers acquired and stored in the backend - RangeSet storedSeqs; - }; - - Application& app_; - beast::Journal const j_; - mutable std::mutex mutex_; - mutable std::mutex storedMutex_; - - // Shard Index - std::uint32_t const index_; - - // First ledger sequence in the shard - std::uint32_t const firstSeq_; - - // Last ledger sequence in the shard - std::uint32_t const lastSeq_; - - // The maximum number of ledgers the shard can store - // The earliest shard may store fewer ledgers than subsequent shards - std::uint32_t const maxLedgers_; - - // Path to database files - boost::filesystem::path const dir_; - - // Storage space utilized by the shard - GUARDED_BY(mutex_) std::uint64_t fileSz_{0}; - - // Number of file descriptors required by the shard - GUARDED_BY(mutex_) std::uint32_t fdRequired_{0}; - - // NuDB key/value store for node objects - std::unique_ptr backend_ GUARDED_BY(mutex_); - - std::atomic backendCount_{0}; - - // Ledger SQLite database used for indexes - std::unique_ptr lgrSQLiteDB_ GUARDED_BY(mutex_); - - // Transaction SQLite database used for indexes - std::unique_ptr txSQLiteDB_ GUARDED_BY(mutex_); - - // Tracking information used only when acquiring a shard from the network. - // If the shard is finalized, this member will be null. - std::unique_ptr acquireInfo_ GUARDED_BY(mutex_); - ; - - // Older shard without an acquire database or final key - // Eventually there will be no need for this and should be removed - GUARDED_BY(mutex_) bool legacy_{false}; - - // Determines if the shard needs to stop processing for shutdown - std::atomic stop_{false}; - - // Determines if the shard busy with replacing by deterministic one - std::atomic busy_{false}; - - // State of the shard - std::atomic state_{ShardState::acquire}; - - // Number of ledgers processed for the current shard state - std::atomic progress_{0}; - - // Determines if the shard directory should be removed in the destructor - std::atomic removeOnDestroy_{false}; - - // The time of the last access of a shard with a finalized state - std::chrono::steady_clock::time_point lastAccess_ GUARDED_BY(mutex_); - ; - - // Open shard databases - [[nodiscard]] bool - open(std::lock_guard const& lock) REQUIRES(mutex_); - - // Open/Create SQLite databases - // Lock over mutex_ required - [[nodiscard]] bool - initSQLite(std::lock_guard const&) REQUIRES(mutex_); - - // Write SQLite entries for this ledger - [[nodiscard]] bool - storeSQLite(std::shared_ptr const& ledger); - - // Set storage and file descriptor usage stats - // Lock over mutex_ required - void - setFileStats(std::lock_guard const&) REQUIRES(mutex_); - - // Verify this ledger by walking its SHAMaps and verifying its Merkle trees - // Every node object verified will be stored in the deterministic shard - [[nodiscard]] bool - verifyLedger( - std::shared_ptr const& ledger, - std::shared_ptr const& next, - std::shared_ptr const& dShard) const; - - // Fetches from backend and log errors based on status codes - [[nodiscard]] std::shared_ptr - verifyFetch(uint256 const& hash) const; - - // Open databases if they are closed - [[nodiscard]] Shard::Count - makeBackendCount(); - - // Invoke a callback on the supplied session parameter - template - bool - callForSQL( - std::function const& callback, - LockedSociSession&& db) - { - auto const scopedCount{makeBackendCount()}; - if (!scopedCount) - return false; - - return doCallForSQL(callback, std::move(db)); - } - - // Invoke a callback that accepts a SQLite session parameter - bool - doCallForSQL( - std::function const& callback, - LockedSociSession&& db); - - // Invoke a callback that accepts a SQLite session and the - // shard index as parameters - bool - doCallForSQL( - std::function< - bool(soci::session& session, std::uint32_t shardIndex)> const& - callback, - LockedSociSession&& db); -}; - -} // namespace NodeStore -} // namespace ripple - -#endif diff --git a/src/ripple/nodestore/impl/ShardInfo.cpp b/src/ripple/nodestore/impl/ShardInfo.cpp deleted file mode 100644 index fca828ab447..00000000000 --- a/src/ripple/nodestore/impl/ShardInfo.cpp +++ /dev/null @@ -1,136 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -std::string -ShardInfo::finalizedToString() const -{ - if (!finalized_.empty()) - return ripple::to_string(finalized_); - return {}; -} - -std::string -ShardInfo::incompleteToString() const -{ - std::string result; - if (!incomplete_.empty()) - { - for (auto const& [shardIndex, incomplete] : incomplete_) - { - result += std::to_string(shardIndex) + ":" + - std::to_string(incomplete.percentProgress()) + ","; - } - result.pop_back(); - } - - return result; -} - -bool -ShardInfo::update( - std::uint32_t shardIndex, - ShardState state, - std::uint32_t percentProgress) -{ - if (state == ShardState::finalized) - { - if (boost::icl::contains(finalized_, shardIndex)) - return false; - - finalized_.insert(shardIndex); - return true; - } - - return incomplete_.emplace(shardIndex, Incomplete(state, percentProgress)) - .second; -} - -protocol::TMPeerShardInfoV2 -ShardInfo::makeMessage(Application& app) -{ - protocol::TMPeerShardInfoV2 message; - Serializer s; - s.add32(HashPrefix::shardInfo); - - // Set the message creation time - msgTimestamp_ = app.timeKeeper().now(); - { - auto const timestamp{msgTimestamp_.time_since_epoch().count()}; - message.set_timestamp(timestamp); - s.add32(timestamp); - } - - if (!incomplete_.empty()) - { - message.mutable_incomplete()->Reserve(incomplete_.size()); - for (auto const& [shardIndex, incomplete] : incomplete_) - { - auto tmIncomplete{message.add_incomplete()}; - - tmIncomplete->set_shardindex(shardIndex); - s.add32(shardIndex); - - static_assert(std::is_same_v< - std::underlying_type_t, - std::uint32_t>); - auto const state{static_cast(incomplete.state())}; - tmIncomplete->set_state(state); - s.add32(state); - - // Set progress if greater than zero - auto const percentProgress{incomplete.percentProgress()}; - if (percentProgress > 0) - { - tmIncomplete->set_progress(percentProgress); - s.add32(percentProgress); - } - } - } - - if (!finalized_.empty()) - { - auto const str{ripple::to_string(finalized_)}; - message.set_finalized(str); - s.addRaw(str.data(), str.size()); - } - - // Set the public key - auto const& publicKey{app.nodeIdentity().first}; - message.set_publickey(publicKey.data(), publicKey.size()); - - // Create a digital signature using the node private key - auto const signature{sign(publicKey, app.nodeIdentity().second, s.slice())}; - - // Set the digital signature - message.set_signature(signature.data(), signature.size()); - - return message; -} - -} // namespace NodeStore -} // namespace ripple diff --git a/src/ripple/nodestore/impl/TaskQueue.cpp b/src/ripple/nodestore/impl/TaskQueue.cpp deleted file mode 100644 index 90b6ba3451a..00000000000 --- a/src/ripple/nodestore/impl/TaskQueue.cpp +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2019 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -#include - -namespace ripple { -namespace NodeStore { - -TaskQueue::TaskQueue() : workers_(*this, nullptr, "Shard store taskQueue", 1) -{ -} - -void -TaskQueue::stop() -{ - workers_.stop(); -} - -void -TaskQueue::addTask(std::function task) -{ - { - std::lock_guard lock{mutex_}; - tasks_.emplace(std::move(task)); - } - workers_.addTask(); -} - -size_t -TaskQueue::size() const -{ - std::lock_guard lock{mutex_}; - return tasks_.size() + processing_; -} - -void -TaskQueue::processTask(int instance) -{ - std::function task; - - { - std::lock_guard lock{mutex_}; - - assert(!tasks_.empty()); - task = std::move(tasks_.front()); - tasks_.pop(); - - ++processing_; - } - - task(); - - std::lock_guard lock{mutex_}; - --processing_; -} - -} // namespace NodeStore -} // namespace ripple diff --git a/src/ripple/peerfinder/sim/FunctionQueue.h b/src/ripple/peerfinder/sim/FunctionQueue.h deleted file mode 100644 index d2fbb6dc26b..00000000000 --- a/src/ripple/peerfinder/sim/FunctionQueue.h +++ /dev/null @@ -1,100 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PEERFINDER_SIM_FUNCTIONQUEUE_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_FUNCTIONQUEUE_H_INCLUDED - -namespace ripple { -namespace PeerFinder { -namespace Sim { - -/** Maintains a queue of functors that can be called later. */ -class FunctionQueue -{ -public: - explicit FunctionQueue() = default; - -private: - class BasicWork - { - public: - virtual ~BasicWork() - { - } - virtual void - operator()() = 0; - }; - - template - class Work : public BasicWork - { - public: - explicit Work(Function f) : m_f(f) - { - } - void - operator()() - { - (m_f)(); - } - - private: - Function m_f; - }; - - std::list> m_work; - -public: - /** Returns `true` if there is no remaining work */ - bool - empty() - { - return m_work.empty(); - } - - /** Queue a function. - Function must be callable with this signature: - void (void) - */ - template - void - post(Function f) - { - m_work.emplace_back(std::make_unique>(f)); - } - - /** Run all pending functions. - The functions will be invoked in the order they were queued. - */ - void - run() - { - while (!m_work.empty()) - { - (*m_work.front())(); - m_work.pop_front(); - } - } -}; - -} // namespace Sim -} // namespace PeerFinder -} // namespace ripple - -#endif diff --git a/src/ripple/peerfinder/sim/GraphAlgorithms.h b/src/ripple/peerfinder/sim/GraphAlgorithms.h deleted file mode 100644 index b11ba42a7a7..00000000000 --- a/src/ripple/peerfinder/sim/GraphAlgorithms.h +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PEERFINDER_SIM_GRAPHALGORITHMS_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_GRAPHALGORITHMS_H_INCLUDED - -namespace ripple { -namespace PeerFinder { -namespace Sim { - -template -struct VertexTraits; - -/** Call a function for each vertex in a connected graph. - Function will be called with this signature: - void (Vertex&, std::size_t diameter); -*/ - -template -void -breadth_first_traverse(Vertex& start, Function f) -{ - using Traits = VertexTraits; - using Edges = typename Traits::Edges; - using Edge = typename Traits::Edge; - - using Probe = std::pair; - using Work = std::deque; - using Visited = std::set; - Work work; - Visited visited; - work.emplace_back(&start, 0); - int diameter(0); - while (!work.empty()) - { - Probe const p(work.front()); - work.pop_front(); - if (visited.find(p.first) != visited.end()) - continue; - diameter = std::max(p.second, diameter); - visited.insert(p.first); - for (typename Edges::iterator iter(Traits::edges(*p.first).begin()); - iter != Traits::edges(*p.first).end(); - ++iter) - { - Vertex* v(Traits::vertex(*iter)); - if (visited.find(v) != visited.end()) - continue; - if (!iter->closed()) - work.emplace_back(v, p.second + 1); - } - f(*p.first, diameter); - } -} - -} // namespace Sim -} // namespace PeerFinder -} // namespace ripple - -#endif diff --git a/src/ripple/peerfinder/sim/NodeSnapshot.h b/src/ripple/peerfinder/sim/NodeSnapshot.h deleted file mode 100644 index fbb08ece9a2..00000000000 --- a/src/ripple/peerfinder/sim/NodeSnapshot.h +++ /dev/null @@ -1,37 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PEERFINDER_SIM_NODESNAPSHOT_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_NODESNAPSHOT_H_INCLUDED - -namespace ripple { -namespace PeerFinder { -namespace Sim { - -/** A snapshot of a Node in the network simulator. */ -struct NodeSnapshot -{ - explicit NodeSnapshot() = default; -}; - -} // namespace Sim -} // namespace PeerFinder -} // namespace ripple - -#endif diff --git a/src/ripple/peerfinder/sim/Predicates.h b/src/ripple/peerfinder/sim/Predicates.h deleted file mode 100644 index 7bf125b383a..00000000000 --- a/src/ripple/peerfinder/sim/Predicates.h +++ /dev/null @@ -1,87 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PEERFINDER_SIM_PREDICATES_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_PREDICATES_H_INCLUDED - -namespace ripple { -namespace PeerFinder { -namespace Sim { - -/** UnaryPredicate, returns `true` if the 'to' node on a Link matches. */ -/** @{ */ -template -class is_remote_node_pred -{ -public: - is_remote_node_pred(Node const& n) : node(n) - { - } - template - bool - operator()(Link const& l) const - { - return &node == &l.remote_node(); - } - -private: - Node const& node; -}; - -template -is_remote_node_pred -is_remote_node(Node const& node) -{ - return is_remote_node_pred(node); -} - -template -is_remote_node_pred -is_remote_node(Node const* node) -{ - return is_remote_node_pred(*node); -} -/** @} */ - -//------------------------------------------------------------------------------ - -/** UnaryPredicate, `true` if the remote address matches. */ -class is_remote_endpoint -{ -public: - explicit is_remote_endpoint(beast::IP::Endpoint const& address) - : m_endpoint(address) - { - } - template - bool - operator()(Link const& link) const - { - return link.remote_endpoint() == m_endpoint; - } - -private: - beast::IP::Endpoint const m_endpoint; -}; - -} // namespace Sim -} // namespace PeerFinder -} // namespace ripple - -#endif diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h deleted file mode 100644 index 727d531ff40..00000000000 --- a/src/ripple/protocol/SField.h +++ /dev/null @@ -1,683 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PROTOCOL_SFIELD_H_INCLUDED -#define RIPPLE_PROTOCOL_SFIELD_H_INCLUDED - -#include -#include - -#include -#include -#include - -namespace ripple { - -/* - -Some fields have a different meaning for their - default value versus not present. - Example: - QualityIn on a TrustLine - -*/ - -//------------------------------------------------------------------------------ - -// Forwards -class STAccount; -class STAmount; -class STIssue; -class STBlob; -template -class STBitString; -template -class STInteger; -class STXChainBridge; -class STVector256; -class STCurrency; - -#pragma push_macro("XMACRO") -#undef XMACRO - -#define XMACRO(STYPE) \ - /* special types */ \ - STYPE(STI_UNKNOWN, -2) \ - STYPE(STI_NOTPRESENT, 0) \ - STYPE(STI_UINT16, 1) \ - \ - /* types (common) */ \ - STYPE(STI_UINT32, 2) \ - STYPE(STI_UINT64, 3) \ - STYPE(STI_UINT128, 4) \ - STYPE(STI_UINT256, 5) \ - STYPE(STI_AMOUNT, 6) \ - STYPE(STI_VL, 7) \ - STYPE(STI_ACCOUNT, 8) \ - \ - /* 9-13 are reserved */ \ - STYPE(STI_OBJECT, 14) \ - STYPE(STI_ARRAY, 15) \ - \ - /* types (uncommon) */ \ - STYPE(STI_UINT8, 16) \ - STYPE(STI_UINT160, 17) \ - STYPE(STI_PATHSET, 18) \ - STYPE(STI_VECTOR256, 19) \ - STYPE(STI_UINT96, 20) \ - STYPE(STI_UINT192, 21) \ - STYPE(STI_UINT384, 22) \ - STYPE(STI_UINT512, 23) \ - STYPE(STI_ISSUE, 24) \ - STYPE(STI_XCHAIN_BRIDGE, 25) \ - STYPE(STI_CURRENCY, 26) \ - \ - /* high-level types */ \ - /* cannot be serialized inside other types */ \ - STYPE(STI_TRANSACTION, 10001) \ - STYPE(STI_LEDGERENTRY, 10002) \ - STYPE(STI_VALIDATION, 10003) \ - STYPE(STI_METADATA, 10004) - -#pragma push_macro("TO_ENUM") -#undef TO_ENUM -#pragma push_macro("TO_MAP") -#undef TO_MAP - -#define TO_ENUM(name, value) name = value, -#define TO_MAP(name, value) {#name, value}, - -enum SerializedTypeID { XMACRO(TO_ENUM) }; - -static std::map const sTypeMap = {XMACRO(TO_MAP)}; - -#undef XMACRO -#undef TO_ENUM - -#pragma pop_macro("XMACRO") -#pragma pop_macro("TO_ENUM") -#pragma pop_macro("TO_MAP") - -// constexpr -inline int -field_code(SerializedTypeID id, int index) -{ - return (safe_cast(id) << 16) | index; -} - -// constexpr -inline int -field_code(int id, int index) -{ - return (id << 16) | index; -} - -/** Identifies fields. - - Fields are necessary to tag data in signed transactions so that - the binary format of the transaction can be canonicalized. All - SFields are created at compile time. - - Each SField, once constructed, lives until program termination, and there - is only one instance per fieldType/fieldValue pair which serves the entire - application. -*/ -class SField -{ -public: - enum { - sMD_Never = 0x00, - sMD_ChangeOrig = 0x01, // original value when it changes - sMD_ChangeNew = 0x02, // new value when it changes - sMD_DeleteFinal = 0x04, // final value when it is deleted - sMD_Create = 0x08, // value when it's created - sMD_Always = 0x10, // value when node containing it is affected at all - sMD_Default = - sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create - }; - - enum class IsSigning : unsigned char { no, yes }; - static IsSigning const notSigning = IsSigning::no; - - int const fieldCode; // (type<<16)|index - SerializedTypeID const fieldType; // STI_* - int const fieldValue; // Code number for protocol - std::string const fieldName; - int const fieldMeta; - int const fieldNum; - IsSigning const signingField; - Json::StaticString const jsonName; - - SField(SField const&) = delete; - SField& - operator=(SField const&) = delete; - SField(SField&&) = delete; - SField& - operator=(SField&&) = delete; - -public: - struct private_access_tag_t; // public, but still an implementation detail - - // These constructors can only be called from SField.cpp - SField( - private_access_tag_t, - SerializedTypeID tid, - int fv, - const char* fn, - int meta = sMD_Default, - IsSigning signing = IsSigning::yes); - explicit SField(private_access_tag_t, int fc); - - static const SField& - getField(int fieldCode); - static const SField& - getField(std::string const& fieldName); - static const SField& - getField(int type, int value) - { - return getField(field_code(type, value)); - } - - static const SField& - getField(SerializedTypeID type, int value) - { - return getField(field_code(type, value)); - } - - std::string const& - getName() const - { - return fieldName; - } - - bool - hasName() const - { - return fieldCode > 0; - } - - Json::StaticString const& - getJsonName() const - { - return jsonName; - } - - bool - isInvalid() const - { - return fieldCode == -1; - } - - bool - isUseful() const - { - return fieldCode > 0; - } - - bool - isBinary() const - { - return fieldValue < 256; - } - - // A discardable field is one that cannot be serialized, and - // should be discarded during serialization,like 'hash'. - // You cannot serialize an object's hash inside that object, - // but you can have it in the JSON representation. - bool - isDiscardable() const - { - return fieldValue > 256; - } - - int - getCode() const - { - return fieldCode; - } - int - getNum() const - { - return fieldNum; - } - static int - getNumFields() - { - return num; - } - - bool - shouldMeta(int c) const - { - return (fieldMeta & c) != 0; - } - - bool - shouldInclude(bool withSigningField) const - { - return (fieldValue < 256) && - (withSigningField || (signingField == IsSigning::yes)); - } - - bool - operator==(const SField& f) const - { - return fieldCode == f.fieldCode; - } - - bool - operator!=(const SField& f) const - { - return fieldCode != f.fieldCode; - } - - static int - compare(const SField& f1, const SField& f2); - - static std::map const& - getKnownCodeToField() - { - return knownCodeToField; - } - -private: - static int num; - static std::map knownCodeToField; -}; - -/** A field with a type known at compile time. */ -template -struct TypedField : SField -{ - using type = T; - - template - explicit TypedField(private_access_tag_t pat, Args&&... args); -}; - -/** Indicate std::optional field semantics. */ -template -struct OptionaledField -{ - TypedField const* f; - - explicit OptionaledField(TypedField const& f_) : f(&f_) - { - } -}; - -template -inline OptionaledField -operator~(TypedField const& f) -{ - return OptionaledField(f); -} - -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ - -using SF_UINT8 = TypedField>; -using SF_UINT16 = TypedField>; -using SF_UINT32 = TypedField>; -using SF_UINT64 = TypedField>; -using SF_UINT96 = TypedField>; -using SF_UINT128 = TypedField>; -using SF_UINT160 = TypedField>; -using SF_UINT192 = TypedField>; -using SF_UINT256 = TypedField>; -using SF_UINT384 = TypedField>; -using SF_UINT512 = TypedField>; - -using SF_ACCOUNT = TypedField; -using SF_AMOUNT = TypedField; -using SF_ISSUE = TypedField; -using SF_CURRENCY = TypedField; -using SF_VL = TypedField; -using SF_VECTOR256 = TypedField; -using SF_XCHAIN_BRIDGE = TypedField; - -//------------------------------------------------------------------------------ - -extern SField const sfInvalid; -extern SField const sfGeneric; -extern SField const sfLedgerEntry; -extern SField const sfTransaction; -extern SField const sfValidation; -extern SField const sfMetadata; - -// 8-bit integers (common) -extern SF_UINT8 const sfCloseResolution; -extern SF_UINT8 const sfMethod; -extern SF_UINT8 const sfTransactionResult; -extern SF_UINT8 const sfWasLockingChainSend; -extern SF_UINT8 const sfScale; - -// 8-bit integers (uncommon) -extern SF_UINT8 const sfTickSize; -extern SF_UINT8 const sfUNLModifyDisabling; -extern SF_UINT8 const sfHookResult; - -// 16-bit integers (common) -extern SF_UINT16 const sfLedgerEntryType; -extern SF_UINT16 const sfTransactionType; -extern SF_UINT16 const sfSignerWeight; -extern SF_UINT16 const sfTransferFee; -extern SF_UINT16 const sfTradingFee; - -// 16-bit integers (uncommon) -extern SF_UINT16 const sfVersion; -extern SF_UINT16 const sfHookStateChangeCount; -extern SF_UINT16 const sfHookEmitCount; -extern SF_UINT16 const sfHookExecutionIndex; -extern SF_UINT16 const sfHookApiVersion; -extern SF_UINT16 const sfDiscountedFee; - -// 32-bit integers (common) -extern SF_UINT32 const sfNetworkID; -extern SF_UINT32 const sfFlags; -extern SF_UINT32 const sfSourceTag; -extern SF_UINT32 const sfSequence; -extern SF_UINT32 const sfPreviousTxnLgrSeq; -extern SF_UINT32 const sfLedgerSequence; -extern SF_UINT32 const sfCloseTime; -extern SF_UINT32 const sfParentCloseTime; -extern SF_UINT32 const sfSigningTime; -extern SF_UINT32 const sfExpiration; -extern SF_UINT32 const sfTransferRate; -extern SF_UINT32 const sfWalletSize; -extern SF_UINT32 const sfOwnerCount; -extern SF_UINT32 const sfDestinationTag; -extern SF_UINT32 const sfLastUpdateTime; - -// 32-bit integers (uncommon) -extern SF_UINT32 const sfHighQualityIn; -extern SF_UINT32 const sfHighQualityOut; -extern SF_UINT32 const sfLowQualityIn; -extern SF_UINT32 const sfLowQualityOut; -extern SF_UINT32 const sfQualityIn; -extern SF_UINT32 const sfQualityOut; -extern SF_UINT32 const sfStampEscrow; -extern SF_UINT32 const sfBondAmount; -extern SF_UINT32 const sfLoadFee; -extern SF_UINT32 const sfOfferSequence; -extern SF_UINT32 const sfFirstLedgerSequence; -extern SF_UINT32 const sfLastLedgerSequence; -extern SF_UINT32 const sfTransactionIndex; -extern SF_UINT32 const sfOperationLimit; -extern SF_UINT32 const sfReferenceFeeUnits; -extern SF_UINT32 const sfReserveBase; -extern SF_UINT32 const sfReserveIncrement; -extern SF_UINT32 const sfSetFlag; -extern SF_UINT32 const sfClearFlag; -extern SF_UINT32 const sfSignerQuorum; -extern SF_UINT32 const sfCancelAfter; -extern SF_UINT32 const sfFinishAfter; -extern SF_UINT32 const sfSignerListID; -extern SF_UINT32 const sfSettleDelay; -extern SF_UINT32 const sfTicketCount; -extern SF_UINT32 const sfTicketSequence; -extern SF_UINT32 const sfNFTokenTaxon; -extern SF_UINT32 const sfMintedNFTokens; -extern SF_UINT32 const sfBurnedNFTokens; -extern SF_UINT32 const sfHookStateCount; -extern SF_UINT32 const sfEmitGeneration; -extern SF_UINT32 const sfVoteWeight; -extern SF_UINT32 const sfFirstNFTokenSequence; -extern SF_UINT32 const sfOracleDocumentID; - -// 64-bit integers (common) -extern SF_UINT64 const sfIndexNext; -extern SF_UINT64 const sfIndexPrevious; -extern SF_UINT64 const sfBookNode; -extern SF_UINT64 const sfOwnerNode; -extern SF_UINT64 const sfBaseFee; -extern SF_UINT64 const sfExchangeRate; -extern SF_UINT64 const sfLowNode; -extern SF_UINT64 const sfHighNode; -extern SF_UINT64 const sfDestinationNode; -extern SF_UINT64 const sfCookie; -extern SF_UINT64 const sfServerVersion; -extern SF_UINT64 const sfNFTokenOfferNode; -extern SF_UINT64 const sfEmitBurden; - -// 64-bit integers (uncommon) -extern SF_UINT64 const sfHookOn; -extern SF_UINT64 const sfHookInstructionCount; -extern SF_UINT64 const sfHookReturnCode; -extern SF_UINT64 const sfReferenceCount; -extern SF_UINT64 const sfXChainClaimID; -extern SF_UINT64 const sfXChainAccountCreateCount; -extern SF_UINT64 const sfXChainAccountClaimCount; -extern SF_UINT64 const sfAssetPrice; - -// 128-bit -extern SF_UINT128 const sfEmailHash; - -// 160-bit (common) -extern SF_UINT160 const sfTakerPaysCurrency; -extern SF_UINT160 const sfTakerPaysIssuer; -extern SF_UINT160 const sfTakerGetsCurrency; -extern SF_UINT160 const sfTakerGetsIssuer; - -// 256-bit (common) -extern SF_UINT256 const sfLedgerHash; -extern SF_UINT256 const sfParentHash; -extern SF_UINT256 const sfTransactionHash; -extern SF_UINT256 const sfAccountHash; -extern SF_UINT256 const sfPreviousTxnID; -extern SF_UINT256 const sfLedgerIndex; -extern SF_UINT256 const sfWalletLocator; -extern SF_UINT256 const sfRootIndex; -extern SF_UINT256 const sfAccountTxnID; -extern SF_UINT256 const sfNFTokenID; -extern SF_UINT256 const sfEmitParentTxnID; -extern SF_UINT256 const sfEmitNonce; -extern SF_UINT256 const sfEmitHookHash; -extern SF_UINT256 const sfAMMID; - -// 256-bit (uncommon) -extern SF_UINT256 const sfBookDirectory; -extern SF_UINT256 const sfInvoiceID; -extern SF_UINT256 const sfNickname; -extern SF_UINT256 const sfAmendment; -extern SF_UINT256 const sfDigest; -extern SF_UINT256 const sfChannel; -extern SF_UINT256 const sfConsensusHash; -extern SF_UINT256 const sfCheckID; -extern SF_UINT256 const sfValidatedHash; -extern SF_UINT256 const sfPreviousPageMin; -extern SF_UINT256 const sfNextPageMin; -extern SF_UINT256 const sfNFTokenBuyOffer; -extern SF_UINT256 const sfNFTokenSellOffer; -extern SF_UINT256 const sfHookStateKey; -extern SF_UINT256 const sfHookHash; -extern SF_UINT256 const sfHookNamespace; -extern SF_UINT256 const sfHookSetTxnID; - -// currency amount (common) -extern SF_AMOUNT const sfAmount; -extern SF_AMOUNT const sfBalance; -extern SF_AMOUNT const sfLimitAmount; -extern SF_AMOUNT const sfTakerPays; -extern SF_AMOUNT const sfTakerGets; -extern SF_AMOUNT const sfLowLimit; -extern SF_AMOUNT const sfHighLimit; -extern SF_AMOUNT const sfFee; -extern SF_AMOUNT const sfSendMax; -extern SF_AMOUNT const sfDeliverMin; -extern SF_AMOUNT const sfAmount2; -extern SF_AMOUNT const sfEPrice; -extern SF_AMOUNT const sfBidMin; -extern SF_AMOUNT const sfBidMax; -extern SF_AMOUNT const sfPrice; -extern SF_AMOUNT const sfLPTokenBalance; - -// currency amount (uncommon) -extern SF_AMOUNT const sfMinimumOffer; -extern SF_AMOUNT const sfRippleEscrow; -extern SF_AMOUNT const sfDeliveredAmount; -extern SF_AMOUNT const sfNFTokenBrokerFee; -extern SF_AMOUNT const sfLPTokenOut; -extern SF_AMOUNT const sfLPTokenIn; - -// currency amount (fees) -extern SF_AMOUNT const sfBaseFeeDrops; -extern SF_AMOUNT const sfReserveBaseDrops; -extern SF_AMOUNT const sfReserveIncrementDrops; -extern SF_AMOUNT const sfSignatureReward; -extern SF_AMOUNT const sfMinAccountCreateAmount; - -// variable length (common) -extern SF_VL const sfPublicKey; -extern SF_VL const sfMessageKey; -extern SF_VL const sfSigningPubKey; -extern SF_VL const sfTxnSignature; -extern SF_VL const sfURI; -extern SF_VL const sfSignature; -extern SF_VL const sfDomain; -extern SF_VL const sfFundCode; -extern SF_VL const sfRemoveCode; -extern SF_VL const sfExpireCode; -extern SF_VL const sfCreateCode; -extern SF_VL const sfMemoType; -extern SF_VL const sfMemoData; -extern SF_VL const sfMemoFormat; -extern SF_VL const sfDIDDocument; -extern SF_VL const sfData; -extern SF_VL const sfAssetClass; -extern SF_VL const sfProvider; - -// variable length (uncommon) -extern SF_VL const sfFulfillment; -extern SF_VL const sfCondition; -extern SF_VL const sfMasterSignature; -extern SF_VL const sfUNLModifyValidator; -extern SF_VL const sfValidatorToDisable; -extern SF_VL const sfValidatorToReEnable; -extern SF_VL const sfHookStateData; -extern SF_VL const sfHookReturnString; -extern SF_VL const sfHookParameterName; -extern SF_VL const sfHookParameterValue; - -// account -extern SF_ACCOUNT const sfAccount; -extern SF_ACCOUNT const sfOwner; -extern SF_ACCOUNT const sfDestination; -extern SF_ACCOUNT const sfIssuer; -extern SF_ACCOUNT const sfAuthorize; -extern SF_ACCOUNT const sfUnauthorize; -extern SF_ACCOUNT const sfRegularKey; -extern SF_ACCOUNT const sfNFTokenMinter; -extern SF_ACCOUNT const sfEmitCallback; - -// account (uncommon) -extern SF_ACCOUNT const sfHookAccount; -extern SF_ACCOUNT const sfOtherChainSource; -extern SF_ACCOUNT const sfOtherChainDestination; -extern SF_ACCOUNT const sfAttestationSignerAccount; -extern SF_ACCOUNT const sfAttestationRewardAccount; -extern SF_ACCOUNT const sfLockingChainDoor; -extern SF_ACCOUNT const sfIssuingChainDoor; - -// path set -extern SField const sfPaths; - -// currency -extern SF_CURRENCY const sfBaseAsset; -extern SF_CURRENCY const sfQuoteAsset; - -// issue -extern SF_ISSUE const sfAsset; -extern SF_ISSUE const sfAsset2; -extern SF_ISSUE const sfLockingChainIssue; -extern SF_ISSUE const sfIssuingChainIssue; - -// bridge -extern SF_XCHAIN_BRIDGE const sfXChainBridge; - -// vector of 256-bit -extern SF_VECTOR256 const sfIndexes; -extern SF_VECTOR256 const sfHashes; -extern SF_VECTOR256 const sfAmendments; -extern SF_VECTOR256 const sfNFTokenOffers; - -// inner object -// OBJECT/1 is reserved for end of object -extern SField const sfTransactionMetaData; -extern SField const sfCreatedNode; -extern SField const sfDeletedNode; -extern SField const sfModifiedNode; -extern SField const sfPreviousFields; -extern SField const sfFinalFields; -extern SField const sfNewFields; -extern SField const sfTemplateEntry; -extern SField const sfMemo; -extern SField const sfSignerEntry; -extern SField const sfNFToken; -extern SField const sfEmitDetails; -extern SField const sfHook; -extern SField const sfVoteEntry; -extern SField const sfAuctionSlot; -extern SField const sfAuthAccount; -extern SField const sfPriceData; - -extern SField const sfSigner; -extern SField const sfMajority; -extern SField const sfDisabledValidator; -extern SField const sfEmittedTxn; -extern SField const sfHookExecution; -extern SField const sfHookDefinition; -extern SField const sfHookParameter; -extern SField const sfHookGrant; -extern SField const sfXChainClaimProofSig; -extern SField const sfXChainCreateAccountProofSig; -extern SField const sfXChainClaimAttestationCollectionElement; -extern SField const sfXChainCreateAccountAttestationCollectionElement; - -// array of objects (common) -// ARRAY/1 is reserved for end of array -// extern SField const sfSigningAccounts; // Never been used. -extern SField const sfSigners; -extern SField const sfSignerEntries; -extern SField const sfTemplate; -extern SField const sfNecessary; -extern SField const sfSufficient; -extern SField const sfAffectedNodes; -extern SField const sfMemos; -extern SField const sfNFTokens; -extern SField const sfHooks; -extern SField const sfVoteSlots; -extern SField const sfAuthAccounts; -extern SField const sfPriceDataSeries; - -// array of objects (uncommon) -extern SField const sfMajorities; -extern SField const sfDisabledValidators; -extern SField const sfHookExecutions; -extern SField const sfHookParameters; -extern SField const sfHookGrants; -extern SField const sfXChainClaimAttestations; -extern SField const sfXChainCreateAccountAttestations; - -//------------------------------------------------------------------------------ - -} // namespace ripple - -#endif diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h deleted file mode 100644 index b5afa470f38..00000000000 --- a/src/ripple/protocol/TxFormats.h +++ /dev/null @@ -1,237 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PROTOCOL_TXFORMATS_H_INCLUDED -#define RIPPLE_PROTOCOL_TXFORMATS_H_INCLUDED - -#include - -namespace ripple { - -/** Transaction type identifiers. - - These are part of the binary message format. - - @ingroup protocol -*/ -/** Transaction type identifieers - - Each ledger object requires a unique type identifier, which is stored - within the object itself; this makes it possible to iterate the entire - ledger and determine each object's type and verify that the object you - retrieved from a given hash matches the expected type. - - @warning Since these values are included in transactions, which are signed - objects, and used by the code to determine the type of transaction - being invoked, they are part of the protocol. **Changing them - should be avoided because without special handling, this will - result in a hard fork.** - - @note When retiring types, the specific values should not be removed but - should be marked as [[deprecated]]. This is to avoid accidental - reuse of identifiers. - - @todo The C++ language does not enable checking for duplicate values - here. If it becomes possible then we should do this. - - @ingroup protocol -*/ -// clang-format off -enum TxType : std::uint16_t -{ - /** This transaction type executes a payment. */ - ttPAYMENT = 0, - - /** This transaction type creates an escrow object. */ - ttESCROW_CREATE = 1, - - /** This transaction type completes an existing escrow. */ - ttESCROW_FINISH = 2, - - /** This transaction type adjusts various account settings. */ - ttACCOUNT_SET = 3, - - /** This transaction type cancels an existing escrow. */ - ttESCROW_CANCEL = 4, - - /** This transaction type sets or clears an account's "regular key". */ - ttREGULAR_KEY_SET = 5, - - /** This transaction type is deprecated; it is retained for historical purposes. */ - ttNICKNAME_SET [[deprecated("This transaction type is not supported and should not be used.")]] = 6, - - /** This transaction type creates an offer to trade one asset for another. */ - ttOFFER_CREATE = 7, - - /** This transaction type cancels existing offers to trade one asset for another. */ - ttOFFER_CANCEL = 8, - - /** This transaction type is deprecated; it is retained for historical purposes. */ - ttCONTRACT [[deprecated("This transaction type is not supported and should not be used.")]] = 9, - - /** This transaction type creates a new set of tickets. */ - ttTICKET_CREATE = 10, - - /** This identifier was never used, but the slot is reserved for historical purposes. */ - ttSPINAL_TAP [[deprecated("This transaction type is not supported and should not be used.")]] = 11, - - /** This transaction type modifies the signer list associated with an account. */ - ttSIGNER_LIST_SET = 12, - - /** This transaction type creates a new unidirectional XRP payment channel. */ - ttPAYCHAN_CREATE = 13, - - /** This transaction type funds an existing unidirectional XRP payment channel. */ - ttPAYCHAN_FUND = 14, - - /** This transaction type submits a claim against an existing unidirectional payment channel. */ - ttPAYCHAN_CLAIM = 15, - - /** This transaction type creates a new check. */ - ttCHECK_CREATE = 16, - - /** This transaction type cashes an existing check. */ - ttCHECK_CASH = 17, - - /** This transaction type cancels an existing check. */ - ttCHECK_CANCEL = 18, - - /** This transaction type grants or revokes authorization to transfer funds. */ - ttDEPOSIT_PREAUTH = 19, - - /** This transaction type modifies a trustline between two accounts. */ - ttTRUST_SET = 20, - - /** This transaction type deletes an existing account. */ - ttACCOUNT_DELETE = 21, - - /** This transaction type installs a hook. */ - ttHOOK_SET [[maybe_unused]] = 22, - - /** This transaction mints a new NFT. */ - ttNFTOKEN_MINT = 25, - - /** This transaction burns (i.e. destroys) an existing NFT. */ - ttNFTOKEN_BURN = 26, - - /** This transaction creates a new offer to buy or sell an NFT. */ - ttNFTOKEN_CREATE_OFFER = 27, - - /** This transaction cancels an existing offer to buy or sell an existing NFT. */ - ttNFTOKEN_CANCEL_OFFER = 28, - - /** This transaction accepts an existing offer to buy or sell an existing NFT. */ - ttNFTOKEN_ACCEPT_OFFER = 29, - - /** This transaction claws back issued tokens. */ - ttCLAWBACK = 30, - - /** This transaction type creates an AMM instance */ - ttAMM_CREATE = 35, - - /** This transaction type deposits into an AMM instance */ - ttAMM_DEPOSIT = 36, - - /** This transaction type withdraws from an AMM instance */ - ttAMM_WITHDRAW = 37, - - /** This transaction type votes for the trading fee */ - ttAMM_VOTE = 38, - - /** This transaction type bids for the auction slot */ - ttAMM_BID = 39, - - /** This transaction type deletes AMM in the empty state */ - ttAMM_DELETE = 40, - - /** This transactions creates a crosschain sequence number */ - ttXCHAIN_CREATE_CLAIM_ID = 41, - - /** This transactions initiates a crosschain transaction */ - ttXCHAIN_COMMIT = 42, - - /** This transaction completes a crosschain transaction */ - ttXCHAIN_CLAIM = 43, - - /** This transaction initiates a crosschain account create transaction */ - ttXCHAIN_ACCOUNT_CREATE_COMMIT = 44, - - /** This transaction adds an attestation to a claimid*/ - ttXCHAIN_ADD_CLAIM_ATTESTATION = 45, - - /** This transaction adds an attestation to a claimid*/ - ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION = 46, - - /** This transaction modifies a sidechain */ - ttXCHAIN_MODIFY_BRIDGE = 47, - - /** This transactions creates a sidechain */ - ttXCHAIN_CREATE_BRIDGE = 48, - - /** This transaction type creates or updates a DID */ - ttDID_SET = 49, - - /** This transaction type deletes a DID */ - ttDID_DELETE = 50, - - - /** This transaction type creates an Oracle instance */ - ttORACLE_SET = 51, - - /** This transaction type deletes an Oracle instance */ - ttORACLE_DELETE = 52, - - /** This system-generated transaction type is used to update the status of the various amendments. - - For details, see: https://xrpl.org/amendments.html - */ - ttAMENDMENT = 100, - - /** This system-generated transaction type is used to update the network's fee settings. - - For details, see: https://xrpl.org/fee-voting.html - */ - ttFEE = 101, - - /** This system-generated transaction type is used to update the network's negative UNL - - For details, see: https://xrpl.org/negative-unl.html - */ - ttUNL_MODIFY = 102, -}; -// clang-format on - -/** Manages the list of known transaction formats. - */ -class TxFormats : public KnownFormats -{ -private: - /** Create the object. - This will load the object with all the known transaction formats. - */ - TxFormats(); - -public: - static TxFormats const& - getInstance(); -}; - -} // namespace ripple - -#endif diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp deleted file mode 100644 index 4f117a5d60d..00000000000 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ /dev/null @@ -1,378 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include - -namespace ripple { - -LedgerFormats::LedgerFormats() -{ - // clang-format off - // Fields shared by all ledger formats: - static const std::initializer_list commonFields{ - {sfLedgerIndex, soeOPTIONAL}, - {sfLedgerEntryType, soeREQUIRED}, - {sfFlags, soeREQUIRED}, - }; - - add(jss::AccountRoot, - ltACCOUNT_ROOT, - { - {sfAccount, soeREQUIRED}, - {sfSequence, soeREQUIRED}, - {sfBalance, soeREQUIRED}, - {sfOwnerCount, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfAccountTxnID, soeOPTIONAL}, - {sfRegularKey, soeOPTIONAL}, - {sfEmailHash, soeOPTIONAL}, - {sfWalletLocator, soeOPTIONAL}, - {sfWalletSize, soeOPTIONAL}, - {sfMessageKey, soeOPTIONAL}, - {sfTransferRate, soeOPTIONAL}, - {sfDomain, soeOPTIONAL}, - {sfTickSize, soeOPTIONAL}, - {sfTicketCount, soeOPTIONAL}, - {sfNFTokenMinter, soeOPTIONAL}, - {sfMintedNFTokens, soeDEFAULT}, - {sfBurnedNFTokens, soeDEFAULT}, - {sfFirstNFTokenSequence, soeOPTIONAL}, - {sfAMMID, soeOPTIONAL}, - }, - commonFields); - - add(jss::DirectoryNode, - ltDIR_NODE, - { - {sfOwner, soeOPTIONAL}, // for owner directories - {sfTakerPaysCurrency, soeOPTIONAL}, // order book directories - {sfTakerPaysIssuer, soeOPTIONAL}, // order book directories - {sfTakerGetsCurrency, soeOPTIONAL}, // order book directories - {sfTakerGetsIssuer, soeOPTIONAL}, // order book directories - {sfExchangeRate, soeOPTIONAL}, // order book directories - {sfIndexes, soeREQUIRED}, - {sfRootIndex, soeREQUIRED}, - {sfIndexNext, soeOPTIONAL}, - {sfIndexPrevious, soeOPTIONAL}, - {sfNFTokenID, soeOPTIONAL}, - {sfPreviousTxnID, soeOPTIONAL}, - {sfPreviousTxnLgrSeq, soeOPTIONAL}, - }, - commonFields); - - add(jss::Offer, - ltOFFER, - { - {sfAccount, soeREQUIRED}, - {sfSequence, soeREQUIRED}, - {sfTakerPays, soeREQUIRED}, - {sfTakerGets, soeREQUIRED}, - {sfBookDirectory, soeREQUIRED}, - {sfBookNode, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - }, - commonFields); - - add(jss::RippleState, - ltRIPPLE_STATE, - { - {sfBalance, soeREQUIRED}, - {sfLowLimit, soeREQUIRED}, - {sfHighLimit, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfLowNode, soeOPTIONAL}, - {sfLowQualityIn, soeOPTIONAL}, - {sfLowQualityOut, soeOPTIONAL}, - {sfHighNode, soeOPTIONAL}, - {sfHighQualityIn, soeOPTIONAL}, - {sfHighQualityOut, soeOPTIONAL}, - }, - commonFields); - - add(jss::Escrow, - ltESCROW, - { - {sfAccount, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfCondition, soeOPTIONAL}, - {sfCancelAfter, soeOPTIONAL}, - {sfFinishAfter, soeOPTIONAL}, - {sfSourceTag, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfDestinationNode, soeOPTIONAL}, - }, - commonFields); - - add(jss::LedgerHashes, - ltLEDGER_HASHES, - { - {sfFirstLedgerSequence, soeOPTIONAL}, - {sfLastLedgerSequence, soeOPTIONAL}, - {sfHashes, soeREQUIRED}, - }, - commonFields); - - add(jss::Amendments, - ltAMENDMENTS, - { - {sfAmendments, soeOPTIONAL}, // Enabled - {sfMajorities, soeOPTIONAL}, - {sfPreviousTxnID, soeOPTIONAL}, - {sfPreviousTxnLgrSeq, soeOPTIONAL}, - }, - commonFields); - - add(jss::FeeSettings, - ltFEE_SETTINGS, - { - // Old version uses raw numbers - {sfBaseFee, soeOPTIONAL}, - {sfReferenceFeeUnits, soeOPTIONAL}, - {sfReserveBase, soeOPTIONAL}, - {sfReserveIncrement, soeOPTIONAL}, - // New version uses Amounts - {sfBaseFeeDrops, soeOPTIONAL}, - {sfReserveBaseDrops, soeOPTIONAL}, - {sfReserveIncrementDrops, soeOPTIONAL}, - {sfPreviousTxnID, soeOPTIONAL}, - {sfPreviousTxnLgrSeq, soeOPTIONAL}, - }, - commonFields); - - add(jss::Ticket, - ltTICKET, - { - {sfAccount, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfTicketSequence, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - }, - commonFields); - - // All fields are soeREQUIRED because there is always a - // SignerEntries. If there are no SignerEntries the node is deleted. - add(jss::SignerList, - ltSIGNER_LIST, - { - {sfOwnerNode, soeREQUIRED}, - {sfSignerQuorum, soeREQUIRED}, - {sfSignerEntries, soeREQUIRED}, - {sfSignerListID, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - }, - commonFields); - - add(jss::PayChannel, - ltPAYCHAN, - { - {sfAccount, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfBalance, soeREQUIRED}, - {sfPublicKey, soeREQUIRED}, - {sfSettleDelay, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - {sfCancelAfter, soeOPTIONAL}, - {sfSourceTag, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfDestinationNode, soeOPTIONAL}, - }, - commonFields); - - add(jss::Check, - ltCHECK, - { - {sfAccount, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfSendMax, soeREQUIRED}, - {sfSequence, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfDestinationNode, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - {sfInvoiceID, soeOPTIONAL}, - {sfSourceTag, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - }, - commonFields); - - add(jss::DepositPreauth, - ltDEPOSIT_PREAUTH, - { - {sfAccount, soeREQUIRED}, - {sfAuthorize, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED}, - }, - commonFields); - - add(jss::NegativeUNL, - ltNEGATIVE_UNL, - { - {sfDisabledValidators, soeOPTIONAL}, - {sfValidatorToDisable, soeOPTIONAL}, - {sfValidatorToReEnable, soeOPTIONAL}, - {sfPreviousTxnID, soeOPTIONAL}, - {sfPreviousTxnLgrSeq, soeOPTIONAL}, - }, - commonFields); - - add(jss::NFTokenPage, - ltNFTOKEN_PAGE, - { - {sfPreviousPageMin, soeOPTIONAL}, - {sfNextPageMin, soeOPTIONAL}, - {sfNFTokens, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::NFTokenOffer, - ltNFTOKEN_OFFER, - { - {sfOwner, soeREQUIRED}, - {sfNFTokenID, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfNFTokenOfferNode, soeREQUIRED}, - {sfDestination, soeOPTIONAL}, - {sfExpiration, soeOPTIONAL}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::AMM, - ltAMM, - { - {sfAccount, soeREQUIRED}, - {sfTradingFee, soeDEFAULT}, - {sfVoteSlots, soeOPTIONAL}, - {sfAuctionSlot, soeOPTIONAL}, - {sfLPTokenBalance, soeREQUIRED}, - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeOPTIONAL}, - {sfPreviousTxnLgrSeq, soeOPTIONAL}, - }, - commonFields); - - add(jss::Bridge, - ltBRIDGE, - { - {sfAccount, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - {sfMinAccountCreateAmount, soeOPTIONAL}, - {sfXChainBridge, soeREQUIRED}, - {sfXChainClaimID, soeREQUIRED}, - {sfXChainAccountCreateCount, soeREQUIRED}, - {sfXChainAccountClaimCount, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::XChainOwnedClaimID, - ltXCHAIN_OWNED_CLAIM_ID, - { - {sfAccount, soeREQUIRED}, - {sfXChainBridge, soeREQUIRED}, - {sfXChainClaimID, soeREQUIRED}, - {sfOtherChainSource, soeREQUIRED}, - {sfXChainClaimAttestations, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::XChainOwnedCreateAccountClaimID, - ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, - { - {sfAccount, soeREQUIRED}, - {sfXChainBridge, soeREQUIRED}, - {sfXChainAccountCreateCount, soeREQUIRED}, - {sfXChainCreateAccountAttestations, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::DID, - ltDID, - { - {sfAccount, soeREQUIRED}, - {sfDIDDocument, soeOPTIONAL}, - {sfURI, soeOPTIONAL}, - {sfData, soeOPTIONAL}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - add(jss::Oracle, - ltORACLE, - { - {sfOwner, soeREQUIRED}, - {sfProvider, soeREQUIRED}, - {sfPriceDataSeries, soeREQUIRED}, - {sfAssetClass, soeREQUIRED}, - {sfLastUpdateTime, soeREQUIRED}, - {sfURI, soeOPTIONAL}, - {sfOwnerNode, soeREQUIRED}, - {sfPreviousTxnID, soeREQUIRED}, - {sfPreviousTxnLgrSeq, soeREQUIRED} - }, - commonFields); - - // clang-format on -} - -LedgerFormats const& -LedgerFormats::getInstance() -{ - static LedgerFormats instance; - return instance; -} - -} // namespace ripple diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp deleted file mode 100644 index 6d034db75ef..00000000000 --- a/src/ripple/protocol/impl/SField.cpp +++ /dev/null @@ -1,503 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include - -namespace ripple { - -// Storage for static const members. -SField::IsSigning const SField::notSigning; -int SField::num = 0; -std::map SField::knownCodeToField; - -// Give only this translation unit permission to construct SFields -struct SField::private_access_tag_t -{ - explicit private_access_tag_t() = default; -}; - -static SField::private_access_tag_t access; - -template -template -TypedField::TypedField(private_access_tag_t pat, Args&&... args) - : SField(pat, std::forward(args)...) -{ -} - -// Construct all compile-time SFields, and register them in the knownCodeToField -// database: - -// Use macros for most SField construction to enforce naming conventions. -#pragma push_macro("CONSTRUCT_UNTYPED_SFIELD") -#undef CONSTRUCT_UNTYPED_SFIELD - -// It would be possible to design the macros so that sfName and txtName would -// be constructed from a single macro parameter. We chose not to take that -// path because then you cannot grep for the exact SField name and find -// where it is constructed. These macros allow that grep to succeed. -#define CONSTRUCT_UNTYPED_SFIELD(sfName, txtName, stiSuffix, fieldValue, ...) \ - SField const sfName( \ - access, STI_##stiSuffix, fieldValue, txtName, ##__VA_ARGS__); \ - static_assert( \ - std::string_view(#sfName) == "sf" txtName, \ - "Declaration of SField does not match its text name") - -#pragma push_macro("CONSTRUCT_TYPED_SFIELD") -#undef CONSTRUCT_TYPED_SFIELD - -#define CONSTRUCT_TYPED_SFIELD(sfName, txtName, stiSuffix, fieldValue, ...) \ - SF_##stiSuffix const sfName( \ - access, STI_##stiSuffix, fieldValue, txtName, ##__VA_ARGS__); \ - static_assert( \ - std::string_view(#sfName) == "sf" txtName, \ - "Declaration of SField does not match its text name") - -// clang-format off - -// SFields which, for historical reasons, do not follow naming conventions. -SField const sfInvalid(access, -1); -SField const sfGeneric(access, 0); -SField const sfHash(access, STI_UINT256, 257, "hash"); -SField const sfIndex(access, STI_UINT256, 258, "index"); - -// Untyped SFields -CONSTRUCT_UNTYPED_SFIELD(sfLedgerEntry, "LedgerEntry", LEDGERENTRY, 257); -CONSTRUCT_UNTYPED_SFIELD(sfTransaction, "Transaction", TRANSACTION, 257); -CONSTRUCT_UNTYPED_SFIELD(sfValidation, "Validation", VALIDATION, 257); -CONSTRUCT_UNTYPED_SFIELD(sfMetadata, "Metadata", METADATA, 257); - -// 8-bit integers -CONSTRUCT_TYPED_SFIELD(sfCloseResolution, "CloseResolution", UINT8, 1); -CONSTRUCT_TYPED_SFIELD(sfMethod, "Method", UINT8, 2); -CONSTRUCT_TYPED_SFIELD(sfTransactionResult, "TransactionResult", UINT8, 3); -CONSTRUCT_TYPED_SFIELD(sfScale, "Scale", UINT8, 4); - -// 8-bit integers (uncommon) -CONSTRUCT_TYPED_SFIELD(sfTickSize, "TickSize", UINT8, 16); -CONSTRUCT_TYPED_SFIELD(sfUNLModifyDisabling, "UNLModifyDisabling", UINT8, 17); -CONSTRUCT_TYPED_SFIELD(sfHookResult, "HookResult", UINT8, 18); -CONSTRUCT_TYPED_SFIELD(sfWasLockingChainSend, "WasLockingChainSend", UINT8, 19); - -// 16-bit integers -CONSTRUCT_TYPED_SFIELD(sfLedgerEntryType, "LedgerEntryType", UINT16, 1, SField::sMD_Never); -CONSTRUCT_TYPED_SFIELD(sfTransactionType, "TransactionType", UINT16, 2); -CONSTRUCT_TYPED_SFIELD(sfSignerWeight, "SignerWeight", UINT16, 3); -CONSTRUCT_TYPED_SFIELD(sfTransferFee, "TransferFee", UINT16, 4); -CONSTRUCT_TYPED_SFIELD(sfTradingFee, "TradingFee", UINT16, 5); -CONSTRUCT_TYPED_SFIELD(sfDiscountedFee, "DiscountedFee", UINT16, 6); - -// 16-bit integers (uncommon) -CONSTRUCT_TYPED_SFIELD(sfVersion, "Version", UINT16, 16); -CONSTRUCT_TYPED_SFIELD(sfHookStateChangeCount, "HookStateChangeCount", UINT16, 17); -CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, 18); -CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19); -CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20); - -// 32-bit integers (common) -CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1); -CONSTRUCT_TYPED_SFIELD(sfFlags, "Flags", UINT32, 2); -CONSTRUCT_TYPED_SFIELD(sfSourceTag, "SourceTag", UINT32, 3); -CONSTRUCT_TYPED_SFIELD(sfSequence, "Sequence", UINT32, 4); -CONSTRUCT_TYPED_SFIELD(sfPreviousTxnLgrSeq, "PreviousTxnLgrSeq", UINT32, 5, SField::sMD_DeleteFinal); -CONSTRUCT_TYPED_SFIELD(sfLedgerSequence, "LedgerSequence", UINT32, 6); -CONSTRUCT_TYPED_SFIELD(sfCloseTime, "CloseTime", UINT32, 7); -CONSTRUCT_TYPED_SFIELD(sfParentCloseTime, "ParentCloseTime", UINT32, 8); -CONSTRUCT_TYPED_SFIELD(sfSigningTime, "SigningTime", UINT32, 9); -CONSTRUCT_TYPED_SFIELD(sfExpiration, "Expiration", UINT32, 10); -CONSTRUCT_TYPED_SFIELD(sfTransferRate, "TransferRate", UINT32, 11); -CONSTRUCT_TYPED_SFIELD(sfWalletSize, "WalletSize", UINT32, 12); -CONSTRUCT_TYPED_SFIELD(sfOwnerCount, "OwnerCount", UINT32, 13); -CONSTRUCT_TYPED_SFIELD(sfDestinationTag, "DestinationTag", UINT32, 14); -CONSTRUCT_TYPED_SFIELD(sfLastUpdateTime, "LastUpdateTime", UINT32, 15); - -// 32-bit integers (uncommon) -CONSTRUCT_TYPED_SFIELD(sfHighQualityIn, "HighQualityIn", UINT32, 16); -CONSTRUCT_TYPED_SFIELD(sfHighQualityOut, "HighQualityOut", UINT32, 17); -CONSTRUCT_TYPED_SFIELD(sfLowQualityIn, "LowQualityIn", UINT32, 18); -CONSTRUCT_TYPED_SFIELD(sfLowQualityOut, "LowQualityOut", UINT32, 19); -CONSTRUCT_TYPED_SFIELD(sfQualityIn, "QualityIn", UINT32, 20); -CONSTRUCT_TYPED_SFIELD(sfQualityOut, "QualityOut", UINT32, 21); -CONSTRUCT_TYPED_SFIELD(sfStampEscrow, "StampEscrow", UINT32, 22); -CONSTRUCT_TYPED_SFIELD(sfBondAmount, "BondAmount", UINT32, 23); -CONSTRUCT_TYPED_SFIELD(sfLoadFee, "LoadFee", UINT32, 24); -CONSTRUCT_TYPED_SFIELD(sfOfferSequence, "OfferSequence", UINT32, 25); -CONSTRUCT_TYPED_SFIELD(sfFirstLedgerSequence, "FirstLedgerSequence", UINT32, 26); -CONSTRUCT_TYPED_SFIELD(sfLastLedgerSequence, "LastLedgerSequence", UINT32, 27); -CONSTRUCT_TYPED_SFIELD(sfTransactionIndex, "TransactionIndex", UINT32, 28); -CONSTRUCT_TYPED_SFIELD(sfOperationLimit, "OperationLimit", UINT32, 29); -CONSTRUCT_TYPED_SFIELD(sfReferenceFeeUnits, "ReferenceFeeUnits", UINT32, 30); -CONSTRUCT_TYPED_SFIELD(sfReserveBase, "ReserveBase", UINT32, 31); -CONSTRUCT_TYPED_SFIELD(sfReserveIncrement, "ReserveIncrement", UINT32, 32); -CONSTRUCT_TYPED_SFIELD(sfSetFlag, "SetFlag", UINT32, 33); -CONSTRUCT_TYPED_SFIELD(sfClearFlag, "ClearFlag", UINT32, 34); -CONSTRUCT_TYPED_SFIELD(sfSignerQuorum, "SignerQuorum", UINT32, 35); -CONSTRUCT_TYPED_SFIELD(sfCancelAfter, "CancelAfter", UINT32, 36); -CONSTRUCT_TYPED_SFIELD(sfFinishAfter, "FinishAfter", UINT32, 37); -CONSTRUCT_TYPED_SFIELD(sfSignerListID, "SignerListID", UINT32, 38); -CONSTRUCT_TYPED_SFIELD(sfSettleDelay, "SettleDelay", UINT32, 39); -CONSTRUCT_TYPED_SFIELD(sfTicketCount, "TicketCount", UINT32, 40); -CONSTRUCT_TYPED_SFIELD(sfTicketSequence, "TicketSequence", UINT32, 41); -CONSTRUCT_TYPED_SFIELD(sfNFTokenTaxon, "NFTokenTaxon", UINT32, 42); -CONSTRUCT_TYPED_SFIELD(sfMintedNFTokens, "MintedNFTokens", UINT32, 43); -CONSTRUCT_TYPED_SFIELD(sfBurnedNFTokens, "BurnedNFTokens", UINT32, 44); -CONSTRUCT_TYPED_SFIELD(sfHookStateCount, "HookStateCount", UINT32, 45); -CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, 46); -// 47 is reserved for LockCount(Hooks) -CONSTRUCT_TYPED_SFIELD(sfVoteWeight, "VoteWeight", UINT32, 48); -CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); -CONSTRUCT_TYPED_SFIELD(sfOracleDocumentID, "OracleDocumentID", UINT32, 51); - -// 64-bit integers (common) -CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1); -CONSTRUCT_TYPED_SFIELD(sfIndexPrevious, "IndexPrevious", UINT64, 2); -CONSTRUCT_TYPED_SFIELD(sfBookNode, "BookNode", UINT64, 3); -CONSTRUCT_TYPED_SFIELD(sfOwnerNode, "OwnerNode", UINT64, 4); -CONSTRUCT_TYPED_SFIELD(sfBaseFee, "BaseFee", UINT64, 5); -CONSTRUCT_TYPED_SFIELD(sfExchangeRate, "ExchangeRate", UINT64, 6); -CONSTRUCT_TYPED_SFIELD(sfLowNode, "LowNode", UINT64, 7); -CONSTRUCT_TYPED_SFIELD(sfHighNode, "HighNode", UINT64, 8); -CONSTRUCT_TYPED_SFIELD(sfDestinationNode, "DestinationNode", UINT64, 9); -CONSTRUCT_TYPED_SFIELD(sfCookie, "Cookie", UINT64, 10); -CONSTRUCT_TYPED_SFIELD(sfServerVersion, "ServerVersion", UINT64, 11); -CONSTRUCT_TYPED_SFIELD(sfNFTokenOfferNode, "NFTokenOfferNode", UINT64, 12); -CONSTRUCT_TYPED_SFIELD(sfEmitBurden, "EmitBurden", UINT64, 13); - -// 64-bit integers (uncommon) -CONSTRUCT_TYPED_SFIELD(sfHookOn, "HookOn", UINT64, 16); -CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17); -CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18); -CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19); -CONSTRUCT_TYPED_SFIELD(sfXChainClaimID, "XChainClaimID", UINT64, 20); -CONSTRUCT_TYPED_SFIELD(sfXChainAccountCreateCount, "XChainAccountCreateCount", UINT64, 21); -CONSTRUCT_TYPED_SFIELD(sfXChainAccountClaimCount, "XChainAccountClaimCount", UINT64, 22); -CONSTRUCT_TYPED_SFIELD(sfAssetPrice, "AssetPrice", UINT64, 23); - -// 128-bit -CONSTRUCT_TYPED_SFIELD(sfEmailHash, "EmailHash", UINT128, 1); - -// 160-bit (common) -CONSTRUCT_TYPED_SFIELD(sfTakerPaysCurrency, "TakerPaysCurrency", UINT160, 1); -CONSTRUCT_TYPED_SFIELD(sfTakerPaysIssuer, "TakerPaysIssuer", UINT160, 2); -CONSTRUCT_TYPED_SFIELD(sfTakerGetsCurrency, "TakerGetsCurrency", UINT160, 3); -CONSTRUCT_TYPED_SFIELD(sfTakerGetsIssuer, "TakerGetsIssuer", UINT160, 4); - -// 256-bit (common) -CONSTRUCT_TYPED_SFIELD(sfLedgerHash, "LedgerHash", UINT256, 1); -CONSTRUCT_TYPED_SFIELD(sfParentHash, "ParentHash", UINT256, 2); -CONSTRUCT_TYPED_SFIELD(sfTransactionHash, "TransactionHash", UINT256, 3); -CONSTRUCT_TYPED_SFIELD(sfAccountHash, "AccountHash", UINT256, 4); -CONSTRUCT_TYPED_SFIELD(sfPreviousTxnID, "PreviousTxnID", UINT256, 5, SField::sMD_DeleteFinal); -CONSTRUCT_TYPED_SFIELD(sfLedgerIndex, "LedgerIndex", UINT256, 6); -CONSTRUCT_TYPED_SFIELD(sfWalletLocator, "WalletLocator", UINT256, 7); -CONSTRUCT_TYPED_SFIELD(sfRootIndex, "RootIndex", UINT256, 8, SField::sMD_Always); -CONSTRUCT_TYPED_SFIELD(sfAccountTxnID, "AccountTxnID", UINT256, 9); -CONSTRUCT_TYPED_SFIELD(sfNFTokenID, "NFTokenID", UINT256, 10); -CONSTRUCT_TYPED_SFIELD(sfEmitParentTxnID, "EmitParentTxnID", UINT256, 11); -CONSTRUCT_TYPED_SFIELD(sfEmitNonce, "EmitNonce", UINT256, 12); -CONSTRUCT_TYPED_SFIELD(sfEmitHookHash, "EmitHookHash", UINT256, 13); -CONSTRUCT_TYPED_SFIELD(sfAMMID, "AMMID", UINT256, 14); - -// 256-bit (uncommon) -CONSTRUCT_TYPED_SFIELD(sfBookDirectory, "BookDirectory", UINT256, 16); -CONSTRUCT_TYPED_SFIELD(sfInvoiceID, "InvoiceID", UINT256, 17); -CONSTRUCT_TYPED_SFIELD(sfNickname, "Nickname", UINT256, 18); -CONSTRUCT_TYPED_SFIELD(sfAmendment, "Amendment", UINT256, 19); -// 20 is currently unused -CONSTRUCT_TYPED_SFIELD(sfDigest, "Digest", UINT256, 21); -CONSTRUCT_TYPED_SFIELD(sfChannel, "Channel", UINT256, 22); -CONSTRUCT_TYPED_SFIELD(sfConsensusHash, "ConsensusHash", UINT256, 23); -CONSTRUCT_TYPED_SFIELD(sfCheckID, "CheckID", UINT256, 24); -CONSTRUCT_TYPED_SFIELD(sfValidatedHash, "ValidatedHash", UINT256, 25); -CONSTRUCT_TYPED_SFIELD(sfPreviousPageMin, "PreviousPageMin", UINT256, 26); -CONSTRUCT_TYPED_SFIELD(sfNextPageMin, "NextPageMin", UINT256, 27); -CONSTRUCT_TYPED_SFIELD(sfNFTokenBuyOffer, "NFTokenBuyOffer", UINT256, 28); -CONSTRUCT_TYPED_SFIELD(sfNFTokenSellOffer, "NFTokenSellOffer", UINT256, 29); -CONSTRUCT_TYPED_SFIELD(sfHookStateKey, "HookStateKey", UINT256, 30); -CONSTRUCT_TYPED_SFIELD(sfHookHash, "HookHash", UINT256, 31); -CONSTRUCT_TYPED_SFIELD(sfHookNamespace, "HookNamespace", UINT256, 32); -CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256, 33); - -// currency amount (common) -CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1); -CONSTRUCT_TYPED_SFIELD(sfBalance, "Balance", AMOUNT, 2); -CONSTRUCT_TYPED_SFIELD(sfLimitAmount, "LimitAmount", AMOUNT, 3); -CONSTRUCT_TYPED_SFIELD(sfTakerPays, "TakerPays", AMOUNT, 4); -CONSTRUCT_TYPED_SFIELD(sfTakerGets, "TakerGets", AMOUNT, 5); -CONSTRUCT_TYPED_SFIELD(sfLowLimit, "LowLimit", AMOUNT, 6); -CONSTRUCT_TYPED_SFIELD(sfHighLimit, "HighLimit", AMOUNT, 7); -CONSTRUCT_TYPED_SFIELD(sfFee, "Fee", AMOUNT, 8); -CONSTRUCT_TYPED_SFIELD(sfSendMax, "SendMax", AMOUNT, 9); -CONSTRUCT_TYPED_SFIELD(sfDeliverMin, "DeliverMin", AMOUNT, 10); -CONSTRUCT_TYPED_SFIELD(sfAmount2, "Amount2", AMOUNT, 11); -CONSTRUCT_TYPED_SFIELD(sfBidMin, "BidMin", AMOUNT, 12); -CONSTRUCT_TYPED_SFIELD(sfBidMax, "BidMax", AMOUNT, 13); - -// currency amount (uncommon) -CONSTRUCT_TYPED_SFIELD(sfMinimumOffer, "MinimumOffer", AMOUNT, 16); -CONSTRUCT_TYPED_SFIELD(sfRippleEscrow, "RippleEscrow", AMOUNT, 17); -CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18); -CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19); - -// Reserve 20 & 21 for Hooks - -// currency amount (fees) -CONSTRUCT_TYPED_SFIELD(sfBaseFeeDrops, "BaseFeeDrops", AMOUNT, 22); -CONSTRUCT_TYPED_SFIELD(sfReserveBaseDrops, "ReserveBaseDrops", AMOUNT, 23); -CONSTRUCT_TYPED_SFIELD(sfReserveIncrementDrops, "ReserveIncrementDrops", AMOUNT, 24); - -// currency amount (AMM) -CONSTRUCT_TYPED_SFIELD(sfLPTokenOut, "LPTokenOut", AMOUNT, 25); -CONSTRUCT_TYPED_SFIELD(sfLPTokenIn, "LPTokenIn", AMOUNT, 26); -CONSTRUCT_TYPED_SFIELD(sfEPrice, "EPrice", AMOUNT, 27); -CONSTRUCT_TYPED_SFIELD(sfPrice, "Price", AMOUNT, 28); -CONSTRUCT_TYPED_SFIELD(sfSignatureReward, "SignatureReward", AMOUNT, 29); -CONSTRUCT_TYPED_SFIELD(sfMinAccountCreateAmount, "MinAccountCreateAmount", AMOUNT, 30); -CONSTRUCT_TYPED_SFIELD(sfLPTokenBalance, "LPTokenBalance", AMOUNT, 31); - -// variable length (common) -CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1); -CONSTRUCT_TYPED_SFIELD(sfMessageKey, "MessageKey", VL, 2); -CONSTRUCT_TYPED_SFIELD(sfSigningPubKey, "SigningPubKey", VL, 3); -CONSTRUCT_TYPED_SFIELD(sfTxnSignature, "TxnSignature", VL, 4, SField::sMD_Default, SField::notSigning); -CONSTRUCT_TYPED_SFIELD(sfURI, "URI", VL, 5); -CONSTRUCT_TYPED_SFIELD(sfSignature, "Signature", VL, 6, SField::sMD_Default, SField::notSigning); -CONSTRUCT_TYPED_SFIELD(sfDomain, "Domain", VL, 7); -CONSTRUCT_TYPED_SFIELD(sfFundCode, "FundCode", VL, 8); -CONSTRUCT_TYPED_SFIELD(sfRemoveCode, "RemoveCode", VL, 9); -CONSTRUCT_TYPED_SFIELD(sfExpireCode, "ExpireCode", VL, 10); -CONSTRUCT_TYPED_SFIELD(sfCreateCode, "CreateCode", VL, 11); -CONSTRUCT_TYPED_SFIELD(sfMemoType, "MemoType", VL, 12); -CONSTRUCT_TYPED_SFIELD(sfMemoData, "MemoData", VL, 13); -CONSTRUCT_TYPED_SFIELD(sfMemoFormat, "MemoFormat", VL, 14); - -// variable length (uncommon) -CONSTRUCT_TYPED_SFIELD(sfFulfillment, "Fulfillment", VL, 16); -CONSTRUCT_TYPED_SFIELD(sfCondition, "Condition", VL, 17); -CONSTRUCT_TYPED_SFIELD(sfMasterSignature, "MasterSignature", VL, 18, SField::sMD_Default, SField::notSigning); -CONSTRUCT_TYPED_SFIELD(sfUNLModifyValidator, "UNLModifyValidator", VL, 19); -CONSTRUCT_TYPED_SFIELD(sfValidatorToDisable, "ValidatorToDisable", VL, 20); -CONSTRUCT_TYPED_SFIELD(sfValidatorToReEnable, "ValidatorToReEnable", VL, 21); -CONSTRUCT_TYPED_SFIELD(sfHookStateData, "HookStateData", VL, 22); -CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL, 23); -CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24); -CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25); -CONSTRUCT_TYPED_SFIELD(sfDIDDocument, "DIDDocument", VL, 26); -CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 27); -CONSTRUCT_TYPED_SFIELD(sfAssetClass, "AssetClass", VL, 28); -CONSTRUCT_TYPED_SFIELD(sfProvider, "Provider", VL, 29); - -// account -CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1); -CONSTRUCT_TYPED_SFIELD(sfOwner, "Owner", ACCOUNT, 2); -CONSTRUCT_TYPED_SFIELD(sfDestination, "Destination", ACCOUNT, 3); -CONSTRUCT_TYPED_SFIELD(sfIssuer, "Issuer", ACCOUNT, 4); -CONSTRUCT_TYPED_SFIELD(sfAuthorize, "Authorize", ACCOUNT, 5); -CONSTRUCT_TYPED_SFIELD(sfUnauthorize, "Unauthorize", ACCOUNT, 6); -// 7 is currently unused -CONSTRUCT_TYPED_SFIELD(sfRegularKey, "RegularKey", ACCOUNT, 8); -CONSTRUCT_TYPED_SFIELD(sfNFTokenMinter, "NFTokenMinter", ACCOUNT, 9); -CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT, 10); - -// account (uncommon) -CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16); -CONSTRUCT_TYPED_SFIELD(sfOtherChainSource, "OtherChainSource", ACCOUNT, 18); -CONSTRUCT_TYPED_SFIELD(sfOtherChainDestination, "OtherChainDestination",ACCOUNT, 19); -CONSTRUCT_TYPED_SFIELD(sfAttestationSignerAccount, "AttestationSignerAccount", ACCOUNT, 20); -CONSTRUCT_TYPED_SFIELD(sfAttestationRewardAccount, "AttestationRewardAccount", ACCOUNT, 21); -CONSTRUCT_TYPED_SFIELD(sfLockingChainDoor, "LockingChainDoor", ACCOUNT, 22); -CONSTRUCT_TYPED_SFIELD(sfIssuingChainDoor, "IssuingChainDoor", ACCOUNT, 23); - -// vector of 256-bit -CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never); -CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR256, 2); -CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3); -CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4); - -// path set -CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1); - -// currency -CONSTRUCT_TYPED_SFIELD(sfBaseAsset, "BaseAsset", CURRENCY, 1); -CONSTRUCT_TYPED_SFIELD(sfQuoteAsset, "QuoteAsset", CURRENCY, 2); - -// issue -CONSTRUCT_TYPED_SFIELD(sfLockingChainIssue, "LockingChainIssue", ISSUE, 1); -CONSTRUCT_TYPED_SFIELD(sfIssuingChainIssue, "IssuingChainIssue", ISSUE, 2); -CONSTRUCT_TYPED_SFIELD(sfAsset, "Asset", ISSUE, 3); -CONSTRUCT_TYPED_SFIELD(sfAsset2, "Asset2", ISSUE, 4); - -// Bridge -CONSTRUCT_TYPED_SFIELD(sfXChainBridge, "XChainBridge", XCHAIN_BRIDGE, - 1); -// inner object -// OBJECT/1 is reserved for end of object -CONSTRUCT_UNTYPED_SFIELD(sfTransactionMetaData, "TransactionMetaData", OBJECT, 2); -CONSTRUCT_UNTYPED_SFIELD(sfCreatedNode, "CreatedNode", OBJECT, 3); -CONSTRUCT_UNTYPED_SFIELD(sfDeletedNode, "DeletedNode", OBJECT, 4); -CONSTRUCT_UNTYPED_SFIELD(sfModifiedNode, "ModifiedNode", OBJECT, 5); -CONSTRUCT_UNTYPED_SFIELD(sfPreviousFields, "PreviousFields", OBJECT, 6); -CONSTRUCT_UNTYPED_SFIELD(sfFinalFields, "FinalFields", OBJECT, 7); -CONSTRUCT_UNTYPED_SFIELD(sfNewFields, "NewFields", OBJECT, 8); -CONSTRUCT_UNTYPED_SFIELD(sfTemplateEntry, "TemplateEntry", OBJECT, 9); -CONSTRUCT_UNTYPED_SFIELD(sfMemo, "Memo", OBJECT, 10); -CONSTRUCT_UNTYPED_SFIELD(sfSignerEntry, "SignerEntry", OBJECT, 11); -CONSTRUCT_UNTYPED_SFIELD(sfNFToken, "NFToken", OBJECT, 12); -CONSTRUCT_UNTYPED_SFIELD(sfEmitDetails, "EmitDetails", OBJECT, 13); -CONSTRUCT_UNTYPED_SFIELD(sfHook, "Hook", OBJECT, 14); - -// inner object (uncommon) -CONSTRUCT_UNTYPED_SFIELD(sfSigner, "Signer", OBJECT, 16); -// 17 has not been used yet -CONSTRUCT_UNTYPED_SFIELD(sfMajority, "Majority", OBJECT, 18); -CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidator, "DisabledValidator", OBJECT, 19); -CONSTRUCT_UNTYPED_SFIELD(sfEmittedTxn, "EmittedTxn", OBJECT, 20); -CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT, 21); -CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22); -CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23); -CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24); -CONSTRUCT_UNTYPED_SFIELD(sfVoteEntry, "VoteEntry", OBJECT, 25); -CONSTRUCT_UNTYPED_SFIELD(sfAuctionSlot, "AuctionSlot", OBJECT, 26); -CONSTRUCT_UNTYPED_SFIELD(sfAuthAccount, "AuthAccount", OBJECT, 27); -CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimProofSig, "XChainClaimProofSig", OBJECT, 28); -CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountProofSig, - "XChainCreateAccountProofSig", - OBJECT, 29); -CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, - "XChainClaimAttestationCollectionElement", - OBJECT, 30); -CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, - "XChainCreateAccountAttestationCollectionElement", - OBJECT, 31); -CONSTRUCT_UNTYPED_SFIELD(sfPriceData, "PriceData", OBJECT, 32); - -// array of objects -// ARRAY/1 is reserved for end of array -// 2 has never been used -CONSTRUCT_UNTYPED_SFIELD(sfSigners, "Signers", ARRAY, 3, SField::sMD_Default, SField::notSigning); -CONSTRUCT_UNTYPED_SFIELD(sfSignerEntries, "SignerEntries", ARRAY, 4); -CONSTRUCT_UNTYPED_SFIELD(sfTemplate, "Template", ARRAY, 5); -CONSTRUCT_UNTYPED_SFIELD(sfNecessary, "Necessary", ARRAY, 6); -CONSTRUCT_UNTYPED_SFIELD(sfSufficient, "Sufficient", ARRAY, 7); -CONSTRUCT_UNTYPED_SFIELD(sfAffectedNodes, "AffectedNodes", ARRAY, 8); -CONSTRUCT_UNTYPED_SFIELD(sfMemos, "Memos", ARRAY, 9); -CONSTRUCT_UNTYPED_SFIELD(sfNFTokens, "NFTokens", ARRAY, 10); -CONSTRUCT_UNTYPED_SFIELD(sfHooks, "Hooks", ARRAY, 11); -CONSTRUCT_UNTYPED_SFIELD(sfVoteSlots, "VoteSlots", ARRAY, 12); - -// array of objects (uncommon) -CONSTRUCT_UNTYPED_SFIELD(sfMajorities, "Majorities", ARRAY, 16); -CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, 17); -CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18); -CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19); -CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20); -CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimAttestations, - "XChainClaimAttestations", - ARRAY, 21); -CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestations, - "XChainCreateAccountAttestations", - ARRAY, 22); -// 23 is unused and available for use -CONSTRUCT_UNTYPED_SFIELD(sfPriceDataSeries, "PriceDataSeries", ARRAY, 24); -CONSTRUCT_UNTYPED_SFIELD(sfAuthAccounts, "AuthAccounts", ARRAY, 25); - -// clang-format on - -#undef CONSTRUCT_TYPED_SFIELD -#undef CONSTRUCT_UNTYPED_SFIELD - -#pragma pop_macro("CONSTRUCT_TYPED_SFIELD") -#pragma pop_macro("CONSTRUCT_UNTYPED_SFIELD") - -SField::SField( - private_access_tag_t, - SerializedTypeID tid, - int fv, - const char* fn, - int meta, - IsSigning signing) - : fieldCode(field_code(tid, fv)) - , fieldType(tid) - , fieldValue(fv) - , fieldName(fn) - , fieldMeta(meta) - , fieldNum(++num) - , signingField(signing) - , jsonName(fieldName.c_str()) -{ - knownCodeToField[fieldCode] = this; -} - -SField::SField(private_access_tag_t, int fc) - : fieldCode(fc) - , fieldType(STI_UNKNOWN) - , fieldValue(0) - , fieldMeta(sMD_Never) - , fieldNum(++num) - , signingField(IsSigning::yes) - , jsonName(fieldName.c_str()) -{ - knownCodeToField[fieldCode] = this; -} - -SField const& -SField::getField(int code) -{ - auto it = knownCodeToField.find(code); - - if (it != knownCodeToField.end()) - { - return *(it->second); - } - return sfInvalid; -} - -int -SField::compare(SField const& f1, SField const& f2) -{ - // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2 - if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0)) - return 0; - - if (f1.fieldCode < f2.fieldCode) - return -1; - - if (f2.fieldCode < f1.fieldCode) - return 1; - - return 0; -} - -SField const& -SField::getField(std::string const& fieldName) -{ - for (auto const& [_, f] : knownCodeToField) - { - (void)_; - if (f->fieldName == fieldName) - return *f; - } - return sfInvalid; -} - -} // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp deleted file mode 100644 index d2bdd4f8aa7..00000000000 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ /dev/null @@ -1,514 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 - -#include -#include -#include - -namespace ripple { - -TxFormats::TxFormats() -{ - // Fields shared by all txFormats: - static const std::initializer_list commonFields{ - {sfTransactionType, soeREQUIRED}, - {sfFlags, soeOPTIONAL}, - {sfSourceTag, soeOPTIONAL}, - {sfAccount, soeREQUIRED}, - {sfSequence, soeREQUIRED}, - {sfPreviousTxnID, soeOPTIONAL}, // emulate027 - {sfLastLedgerSequence, soeOPTIONAL}, - {sfAccountTxnID, soeOPTIONAL}, - {sfFee, soeREQUIRED}, - {sfOperationLimit, soeOPTIONAL}, - {sfMemos, soeOPTIONAL}, - {sfSigningPubKey, soeREQUIRED}, - {sfTicketSequence, soeOPTIONAL}, - {sfTxnSignature, soeOPTIONAL}, - {sfSigners, soeOPTIONAL}, // submit_multisigned - {sfNetworkID, soeOPTIONAL}, - }; - - add(jss::AccountSet, - ttACCOUNT_SET, - { - {sfEmailHash, soeOPTIONAL}, - {sfWalletLocator, soeOPTIONAL}, - {sfWalletSize, soeOPTIONAL}, - {sfMessageKey, soeOPTIONAL}, - {sfDomain, soeOPTIONAL}, - {sfTransferRate, soeOPTIONAL}, - {sfSetFlag, soeOPTIONAL}, - {sfClearFlag, soeOPTIONAL}, - {sfTickSize, soeOPTIONAL}, - {sfNFTokenMinter, soeOPTIONAL}, - }, - commonFields); - - add(jss::TrustSet, - ttTRUST_SET, - { - {sfLimitAmount, soeOPTIONAL}, - {sfQualityIn, soeOPTIONAL}, - {sfQualityOut, soeOPTIONAL}, - }, - commonFields); - - add(jss::OfferCreate, - ttOFFER_CREATE, - { - {sfTakerPays, soeREQUIRED}, - {sfTakerGets, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - {sfOfferSequence, soeOPTIONAL}, - }, - commonFields); - - add(jss::AMMCreate, - ttAMM_CREATE, - { - {sfAmount, soeREQUIRED}, - {sfAmount2, soeREQUIRED}, - {sfTradingFee, soeREQUIRED}, - }, - commonFields); - - add(jss::AMMDeposit, - ttAMM_DEPOSIT, - { - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfAmount2, soeOPTIONAL}, - {sfEPrice, soeOPTIONAL}, - {sfLPTokenOut, soeOPTIONAL}, - {sfTradingFee, soeOPTIONAL}, - }, - commonFields); - - add(jss::AMMWithdraw, - ttAMM_WITHDRAW, - { - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfAmount2, soeOPTIONAL}, - {sfEPrice, soeOPTIONAL}, - {sfLPTokenIn, soeOPTIONAL}, - }, - commonFields); - - add(jss::AMMVote, - ttAMM_VOTE, - { - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfTradingFee, soeREQUIRED}, - }, - commonFields); - - add(jss::AMMBid, - ttAMM_BID, - { - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfBidMin, soeOPTIONAL}, - {sfBidMax, soeOPTIONAL}, - {sfAuthAccounts, soeOPTIONAL}, - }, - commonFields); - - add(jss::AMMDelete, - ttAMM_DELETE, - { - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - }, - commonFields); - - add(jss::OfferCancel, - ttOFFER_CANCEL, - { - {sfOfferSequence, soeREQUIRED}, - }, - commonFields); - - add(jss::SetRegularKey, - ttREGULAR_KEY_SET, - { - {sfRegularKey, soeOPTIONAL}, - }, - commonFields); - - add(jss::Payment, - ttPAYMENT, - { - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfSendMax, soeOPTIONAL}, - {sfPaths, soeDEFAULT}, - {sfInvoiceID, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - {sfDeliverMin, soeOPTIONAL}, - }, - commonFields); - - add(jss::EscrowCreate, - ttESCROW_CREATE, - { - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfCondition, soeOPTIONAL}, - {sfCancelAfter, soeOPTIONAL}, - {sfFinishAfter, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - }, - commonFields); - - add(jss::EscrowFinish, - ttESCROW_FINISH, - { - {sfOwner, soeREQUIRED}, - {sfOfferSequence, soeREQUIRED}, - {sfFulfillment, soeOPTIONAL}, - {sfCondition, soeOPTIONAL}, - }, - commonFields); - - add(jss::EscrowCancel, - ttESCROW_CANCEL, - { - {sfOwner, soeREQUIRED}, - {sfOfferSequence, soeREQUIRED}, - }, - commonFields); - - add(jss::EnableAmendment, - ttAMENDMENT, - { - {sfLedgerSequence, soeREQUIRED}, - {sfAmendment, soeREQUIRED}, - }, - commonFields); - - add(jss::SetFee, - ttFEE, - { - {sfLedgerSequence, soeOPTIONAL}, - // Old version uses raw numbers - {sfBaseFee, soeOPTIONAL}, - {sfReferenceFeeUnits, soeOPTIONAL}, - {sfReserveBase, soeOPTIONAL}, - {sfReserveIncrement, soeOPTIONAL}, - // New version uses Amounts - {sfBaseFeeDrops, soeOPTIONAL}, - {sfReserveBaseDrops, soeOPTIONAL}, - {sfReserveIncrementDrops, soeOPTIONAL}, - }, - commonFields); - - add(jss::UNLModify, - ttUNL_MODIFY, - { - {sfUNLModifyDisabling, soeREQUIRED}, - {sfLedgerSequence, soeREQUIRED}, - {sfUNLModifyValidator, soeREQUIRED}, - }, - commonFields); - - add(jss::TicketCreate, - ttTICKET_CREATE, - { - {sfTicketCount, soeREQUIRED}, - }, - commonFields); - - // The SignerEntries are optional because a SignerList is deleted by - // setting the SignerQuorum to zero and omitting SignerEntries. - add(jss::SignerListSet, - ttSIGNER_LIST_SET, - { - {sfSignerQuorum, soeREQUIRED}, - {sfSignerEntries, soeOPTIONAL}, - }, - commonFields); - - add(jss::PaymentChannelCreate, - ttPAYCHAN_CREATE, - { - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfSettleDelay, soeREQUIRED}, - {sfPublicKey, soeREQUIRED}, - {sfCancelAfter, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - }, - commonFields); - - add(jss::PaymentChannelFund, - ttPAYCHAN_FUND, - { - {sfChannel, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - }, - commonFields); - - add(jss::PaymentChannelClaim, - ttPAYCHAN_CLAIM, - { - {sfChannel, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfBalance, soeOPTIONAL}, - {sfSignature, soeOPTIONAL}, - {sfPublicKey, soeOPTIONAL}, - }, - commonFields); - - add(jss::CheckCreate, - ttCHECK_CREATE, - { - {sfDestination, soeREQUIRED}, - {sfSendMax, soeREQUIRED}, - {sfExpiration, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, - {sfInvoiceID, soeOPTIONAL}, - }, - commonFields); - - add(jss::CheckCash, - ttCHECK_CASH, - { - {sfCheckID, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfDeliverMin, soeOPTIONAL}, - }, - commonFields); - - add(jss::CheckCancel, - ttCHECK_CANCEL, - { - {sfCheckID, soeREQUIRED}, - }, - commonFields); - - add(jss::AccountDelete, - ttACCOUNT_DELETE, - { - {sfDestination, soeREQUIRED}, - {sfDestinationTag, soeOPTIONAL}, - }, - commonFields); - - add(jss::DepositPreauth, - ttDEPOSIT_PREAUTH, - { - {sfAuthorize, soeOPTIONAL}, - {sfUnauthorize, soeOPTIONAL}, - }, - commonFields); - - add(jss::NFTokenMint, - ttNFTOKEN_MINT, - { - {sfNFTokenTaxon, soeREQUIRED}, - {sfTransferFee, soeOPTIONAL}, - {sfIssuer, soeOPTIONAL}, - {sfURI, soeOPTIONAL}, - }, - commonFields); - - add(jss::NFTokenBurn, - ttNFTOKEN_BURN, - { - {sfNFTokenID, soeREQUIRED}, - {sfOwner, soeOPTIONAL}, - }, - commonFields); - - add(jss::NFTokenCreateOffer, - ttNFTOKEN_CREATE_OFFER, - { - {sfNFTokenID, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfDestination, soeOPTIONAL}, - {sfOwner, soeOPTIONAL}, - {sfExpiration, soeOPTIONAL}, - }, - commonFields); - - add(jss::NFTokenCancelOffer, - ttNFTOKEN_CANCEL_OFFER, - { - {sfNFTokenOffers, soeREQUIRED}, - }, - commonFields); - - add(jss::NFTokenAcceptOffer, - ttNFTOKEN_ACCEPT_OFFER, - { - {sfNFTokenBuyOffer, soeOPTIONAL}, - {sfNFTokenSellOffer, soeOPTIONAL}, - {sfNFTokenBrokerFee, soeOPTIONAL}, - }, - commonFields); - - add(jss::Clawback, - ttCLAWBACK, - { - {sfAmount, soeREQUIRED}, - }, - commonFields); - - add(jss::XChainCreateBridge, - ttXCHAIN_CREATE_BRIDGE, - { - {sfXChainBridge, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - {sfMinAccountCreateAmount, soeOPTIONAL}, - }, - commonFields); - - add(jss::XChainModifyBridge, - ttXCHAIN_MODIFY_BRIDGE, - { - {sfXChainBridge, soeREQUIRED}, - {sfSignatureReward, soeOPTIONAL}, - {sfMinAccountCreateAmount, soeOPTIONAL}, - }, - commonFields); - - add(jss::XChainCreateClaimID, - ttXCHAIN_CREATE_CLAIM_ID, - { - {sfXChainBridge, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - {sfOtherChainSource, soeREQUIRED}, - }, - commonFields); - - add(jss::XChainCommit, - ttXCHAIN_COMMIT, - { - {sfXChainBridge, soeREQUIRED}, - {sfXChainClaimID, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfOtherChainDestination, soeOPTIONAL}, - }, - commonFields); - - add(jss::XChainClaim, - ttXCHAIN_CLAIM, - { - {sfXChainBridge, soeREQUIRED}, - {sfXChainClaimID, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfDestinationTag, soeOPTIONAL}, - {sfAmount, soeREQUIRED}, - }, - commonFields); - - add(jss::XChainAddClaimAttestation, - ttXCHAIN_ADD_CLAIM_ATTESTATION, - { - {sfXChainBridge, soeREQUIRED}, - - {sfAttestationSignerAccount, soeREQUIRED}, - {sfPublicKey, soeREQUIRED}, - {sfSignature, soeREQUIRED}, - {sfOtherChainSource, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfAttestationRewardAccount, soeREQUIRED}, - {sfWasLockingChainSend, soeREQUIRED}, - - {sfXChainClaimID, soeREQUIRED}, - {sfDestination, soeOPTIONAL}, - }, - commonFields); - - add(jss::XChainAddAccountCreateAttestation, - ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, - { - {sfXChainBridge, soeREQUIRED}, - - {sfAttestationSignerAccount, soeREQUIRED}, - {sfPublicKey, soeREQUIRED}, - {sfSignature, soeREQUIRED}, - {sfOtherChainSource, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfAttestationRewardAccount, soeREQUIRED}, - {sfWasLockingChainSend, soeREQUIRED}, - - {sfXChainAccountCreateCount, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - }, - commonFields); - - add(jss::XChainAccountCreateCommit, - ttXCHAIN_ACCOUNT_CREATE_COMMIT, - { - {sfXChainBridge, soeREQUIRED}, - {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, - {sfSignatureReward, soeREQUIRED}, - }, - commonFields); - - add(jss::DIDSet, - ttDID_SET, - { - {sfDIDDocument, soeOPTIONAL}, - {sfURI, soeOPTIONAL}, - {sfData, soeOPTIONAL}, - }, - commonFields); - - add(jss::DIDDelete, ttDID_DELETE, {}, commonFields); - - add(jss::OracleSet, - ttORACLE_SET, - { - {sfOracleDocumentID, soeREQUIRED}, - {sfProvider, soeOPTIONAL}, - {sfURI, soeOPTIONAL}, - {sfAssetClass, soeOPTIONAL}, - {sfLastUpdateTime, soeREQUIRED}, - {sfPriceDataSeries, soeREQUIRED}, - }, - commonFields); - - add(jss::OracleDelete, - ttORACLE_DELETE, - { - {sfOracleDocumentID, soeREQUIRED}, - }, - commonFields); -} - -TxFormats const& -TxFormats::getInstance() -{ - static TxFormats const instance; - return instance; -} - -} // namespace ripple diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h deleted file mode 100644 index b5f3d1fcb5d..00000000000 --- a/src/ripple/protocol/jss.h +++ /dev/null @@ -1,781 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PROTOCOL_JSONFIELDS_H_INCLUDED -#define RIPPLE_PROTOCOL_JSONFIELDS_H_INCLUDED - -#include - -namespace ripple { -namespace jss { - -// JSON static strings - -#define JSS(x) constexpr ::Json::StaticString x(#x) - -/* These "StaticString" field names are used instead of string literals to - optimize the performance of accessing properties of Json::Value objects. - - Most strings have a trailing comment. Here is the legend: - - in: Read by the given RPC handler from its `Json::Value` parameter. - out: Assigned by the given RPC handler in the `Json::Value` it returns. - field: A field of at least one type of transaction. - RPC: Common properties of RPC requests and responses. - error: Common properties of RPC error responses. -*/ - -JSS(AL_size); // out: GetCounts -JSS(AL_hit_rate); // out: GetCounts -JSS(Account); // in: TransactionSign; field. -JSS(AccountDelete); // transaction type. -JSS(AccountRoot); // ledger type. -JSS(AccountSet); // transaction type. -JSS(AMM); // ledger type -JSS(AMMBid); // transaction type -JSS(AMMID); // field -JSS(AMMCreate); // transaction type -JSS(AMMDeposit); // transaction type -JSS(AMMDelete); // transaction type -JSS(AMMVote); // transaction type -JSS(AMMWithdraw); // transaction type -JSS(Amendments); // ledger type. -JSS(Amount); // in: TransactionSign; field. -JSS(Amount2); // in/out: AMM IOU/XRP pool, deposit, withdraw amount -JSS(Asset); // in: AMM Asset1 -JSS(Asset2); // in: AMM Asset2 -JSS(AssetClass); // in: Oracle -JSS(AssetPrice); // in: Oracle -JSS(AuthAccount); // in: AMM Auction Slot -JSS(AuthAccounts); // in: AMM Auction Slot -JSS(BaseAsset); // in: Oracle -JSS(Bridge); // ledger type. -JSS(Check); // ledger type. -JSS(CheckCancel); // transaction type. -JSS(CheckCash); // transaction type. -JSS(CheckCreate); // transaction type. -JSS(Clawback); // transaction type. -JSS(ClearFlag); // field. -JSS(DID); // ledger type. -JSS(DIDDelete); // transaction type. -JSS(DIDSet); // transaction type. -JSS(DeliverMax); // out: alias to Amount -JSS(DeliverMin); // in: TransactionSign -JSS(DepositPreauth); // transaction and ledger type. -JSS(Destination); // in: TransactionSign; field. -JSS(DirectoryNode); // ledger type. -JSS(EnableAmendment); // transaction type. -JSS(EPrice); // in: AMM Deposit option -JSS(Escrow); // ledger type. -JSS(EscrowCancel); // transaction type. -JSS(EscrowCreate); // transaction type. -JSS(EscrowFinish); // transaction type. -JSS(Fee); // in/out: TransactionSign; field. -JSS(FeeSettings); // ledger type. -JSS(Flags); // in/out: TransactionSign; field. -JSS(incomplete_shards); // out: OverlayImpl, PeerImp -JSS(Invalid); // -JSS(LastLedgerSequence); // in: TransactionSign; field -JSS(LastUpdateTime); // field. -JSS(LedgerHashes); // ledger type. -JSS(LimitAmount); // field. -JSS(BidMax); // in: AMM Bid -JSS(BidMin); // in: AMM Bid -JSS(NetworkID); // field. -JSS(NFTokenBurn); // transaction type. -JSS(NFTokenMint); // transaction type. -JSS(NFTokenOffer); // ledger type. -JSS(NFTokenAcceptOffer); // transaction type. -JSS(NFTokenCancelOffer); // transaction type. -JSS(NFTokenCreateOffer); // transaction type. -JSS(NFTokenPage); // ledger type. -JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens -JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens -JSS(LPToken); // out: AMM Liquidity Provider tokens info -JSS(Offer); // ledger type. -JSS(OfferCancel); // transaction type. -JSS(OfferCreate); // transaction type. -JSS(OfferSequence); // field. -JSS(Oracle); // ledger type. -JSS(OracleDelete); // transaction type. -JSS(OracleDocumentID); // field -JSS(OracleSet); // transaction type. -JSS(Owner); // field -JSS(Paths); // in/out: TransactionSign -JSS(PayChannel); // ledger type. -JSS(Payment); // transaction type. -JSS(PaymentChannelClaim); // transaction type. -JSS(PaymentChannelCreate); // transaction type. -JSS(PaymentChannelFund); // transaction type. -JSS(PriceDataSeries); // field. -JSS(PriceData); // field. -JSS(Provider); // field. -JSS(QuoteAsset); // in: Oracle. -JSS(RippleState); // ledger type. -JSS(SLE_hit_rate); // out: GetCounts. -JSS(SetFee); // transaction type. -JSS(UNLModify); // transaction type. -JSS(Scale); // field. -JSS(SettleDelay); // in: TransactionSign -JSS(SendMax); // in: TransactionSign -JSS(Sequence); // in/out: TransactionSign; field. -JSS(SetFlag); // field. -JSS(SetRegularKey); // transaction type. -JSS(SignerList); // ledger type. -JSS(SignerListSet); // transaction type. -JSS(SigningPubKey); // field. -JSS(TakerGets); // field. -JSS(TakerPays); // field. -JSS(Ticket); // ledger type. -JSS(TicketCreate); // transaction type. -JSS(TxnSignature); // field. -JSS(TradingFee); // in/out: AMM trading fee -JSS(TransactionType); // in: TransactionSign. -JSS(TransferRate); // in: TransferRate. -JSS(TrustSet); // transaction type. -JSS(URI); // field. -JSS(VoteSlots); // out: AMM Vote -JSS(XChainAddAccountCreateAttestation); // transaction type. -JSS(XChainAddClaimAttestation); // transaction type. -JSS(XChainAccountCreateCommit); // transaction type. -JSS(XChainClaim); // transaction type. -JSS(XChainCommit); // transaction type. -JSS(XChainCreateBridge); // transaction type. -JSS(XChainCreateClaimID); // transaction type. -JSS(XChainModifyBridge); // transaction type. -JSS(XChainOwnedClaimID); // ledger type. -JSS(XChainOwnedCreateAccountClaimID); // ledger type. -JSS(aborted); // out: InboundLedger -JSS(accepted); // out: LedgerToJson, OwnerInfo, SubmitTransaction -JSS(account); // in/out: many -JSS(accountState); // out: LedgerToJson -JSS(accountTreeHash); // out: ledger/Ledger.cpp -JSS(account_data); // out: AccountInfo -JSS(account_flags); // out: AccountInfo -JSS(account_hash); // out: LedgerToJson -JSS(account_id); // out: WalletPropose -JSS(account_nfts); // out: AccountNFTs -JSS(account_objects); // out: AccountObjects -JSS(account_root); // in: LedgerEntry -JSS(account_sequence_next); // out: SubmitTransaction -JSS(account_sequence_available); // out: SubmitTransaction -JSS(account_history_tx_stream); // in: Subscribe, Unsubscribe -JSS(account_history_tx_index); // out: Account txn history subscribe - -JSS(account_history_tx_first); // out: Account txn history subscribe -JSS(account_history_boundary); // out: Account txn history subscribe -JSS(accounts); // in: LedgerEntry, Subscribe, - // handlers/Ledger, Unsubscribe -JSS(accounts_proposed); // in: Subscribe, Unsubscribe -JSS(action); -JSS(acquiring); // out: LedgerRequest -JSS(address); // out: PeerImp -JSS(affected); // out: AcceptedLedgerTx -JSS(age); // out: NetworkOPs, Peers -JSS(alternatives); // out: PathRequest, RipplePathFind -JSS(amendment_blocked); // out: NetworkOPs -JSS(amendments); // in: AccountObjects, out: NetworkOPs -JSS(amm); // out: amm_info -JSS(amm_account); // in: amm_info -JSS(amount); // out: AccountChannels, amm_info -JSS(amount2); // out: amm_info -JSS(api_version); // in: many, out: Version -JSS(api_version_low); // out: Version -JSS(applied); // out: SubmitTransaction -JSS(asks); // out: Subscribe -JSS(asset); // in: amm_info -JSS(asset2); // in: amm_info -JSS(assets); // out: GatewayBalances -JSS(asset_frozen); // out: amm_info -JSS(asset2_frozen); // out: amm_info -JSS(attestations); // -JSS(attestation_reward_account); // -JSS(auction_slot); // out: amm_info -JSS(authorized); // out: AccountLines -JSS(auth_accounts); // out: amm_info -JSS(auth_change); // out: AccountInfo -JSS(auth_change_queued); // out: AccountInfo -JSS(available); // out: ValidatorList -JSS(avg_bps_recv); // out: Peers -JSS(avg_bps_sent); // out: Peers -JSS(balance); // out: AccountLines -JSS(balances); // out: GatewayBalances -JSS(base); // out: LogLevel -JSS(base_asset); // in: get_aggregate_price -JSS(base_fee); // out: NetworkOPs -JSS(base_fee_xrp); // out: NetworkOPs -JSS(bids); // out: Subscribe -JSS(binary); // in: AccountTX, LedgerEntry, - // AccountTxOld, Tx LedgerData -JSS(blob); // out: ValidatorList -JSS(blobs_v2); // out: ValidatorList - // in: UNL -JSS(books); // in: Subscribe, Unsubscribe -JSS(both); // in: Subscribe, Unsubscribe -JSS(both_sides); // in: Subscribe, Unsubscribe -JSS(broadcast); // out: SubmitTransaction -JSS(bridge); // in: LedgerEntry -JSS(bridge_account); // in: LedgerEntry -JSS(build_path); // in: TransactionSign -JSS(build_version); // out: NetworkOPs -JSS(cancel_after); // out: AccountChannels -JSS(can_delete); // out: CanDelete -JSS(changes); // out: BookChanges -JSS(channel_id); // out: AccountChannels -JSS(channels); // out: AccountChannels -JSS(check); // in: AccountObjects -JSS(check_nodes); // in: LedgerCleaner -JSS(clear); // in/out: FetchInfo -JSS(close); // out: BookChanges -JSS(close_flags); // out: LedgerToJson -JSS(close_time); // in: Application, out: NetworkOPs, - // RCLCxPeerPos, LedgerToJson -JSS(close_time_iso); // out: Tx, NetworkOPs, TransactionEntry - // AccountTx, LedgerToJson -JSS(close_time_estimated); // in: Application, out: LedgerToJson -JSS(close_time_human); // out: LedgerToJson -JSS(close_time_offset); // out: NetworkOPs -JSS(close_time_resolution); // in: Application; out: LedgerToJson -JSS(closed); // out: NetworkOPs, LedgerToJson, - // handlers/Ledger -JSS(closed_ledger); // out: NetworkOPs -JSS(cluster); // out: PeerImp -JSS(code); // out: errors -JSS(command); // in: RPCHandler -JSS(complete); // out: NetworkOPs, InboundLedger -JSS(complete_ledgers); // out: NetworkOPs, PeerImp -JSS(complete_shards); // out: OverlayImpl, PeerImp -JSS(consensus); // out: NetworkOPs, LedgerConsensus -JSS(converge_time); // out: NetworkOPs -JSS(converge_time_s); // out: NetworkOPs -JSS(cookie); // out: NetworkOPs -JSS(count); // in: AccountTx*, ValidatorList -JSS(counters); // in/out: retrieve counters -JSS(ctid); // in/out: Tx RPC -JSS(currency_a); // out: BookChanges -JSS(currency_b); // out: BookChanges -JSS(currentShard); // out: NodeToShardStatus -JSS(currentShardIndex); // out: NodeToShardStatus -JSS(currency); // in: paths/PathRequest, STAmount - // out: STPathSet, STAmount, - // AccountLines -JSS(current); // out: OwnerInfo -JSS(current_activities); -JSS(current_ledger_size); // out: TxQ -JSS(current_queue_size); // out: TxQ -JSS(data); // out: LedgerData -JSS(date); // out: tx/Transaction, NetworkOPs -JSS(dbKBLedger); // out: getCounts -JSS(dbKBTotal); // out: getCounts -JSS(dbKBTransaction); // out: getCounts -JSS(debug_signing); // in: TransactionSign -JSS(deletion_blockers_only); // in: AccountObjects -JSS(delivered_amount); // out: insertDeliveredAmount -JSS(deposit_authorized); // out: deposit_authorized -JSS(deposit_preauth); // in: AccountObjects, LedgerData -JSS(deprecated); // out -JSS(descending); // in: AccountTx* -JSS(description); // in/out: Reservations -JSS(destination); // in: nft_buy_offers, nft_sell_offers -JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines - // out: AccountChannels -JSS(destination_amount); // in: PathRequest, RipplePathFind -JSS(destination_currencies); // in: PathRequest, RipplePathFind -JSS(destination_tag); // in: PathRequest - // out: AccountChannels -JSS(details); // out: Manifest, server_info -JSS(did); // in: LedgerEntry -JSS(dir_entry); // out: DirectoryEntryIterator -JSS(dir_index); // out: DirectoryEntryIterator -JSS(dir_root); // out: DirectoryEntryIterator -JSS(directory); // in: LedgerEntry -JSS(discounted_fee); // out: amm_info -JSS(domain); // out: ValidatorInfo, Manifest -JSS(drops); // out: TxQ -JSS(duration_us); // out: NetworkOPs -JSS(effective); // out: ValidatorList - // in: UNL -JSS(enabled); // out: AmendmentTable -JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit -JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit -JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit -JSS(entire_set); // out: get_aggregate_price -JSS(ephemeral_key); // out: ValidatorInfo - // in/out: Manifest -JSS(error); // out: error -JSS(errored); -JSS(error_code); // out: error -JSS(error_exception); // out: Submit -JSS(error_message); // out: error -JSS(escrow); // in: LedgerEntry -JSS(expand); // in: handler/Ledger -JSS(expected_date); // out: any (warnings) -JSS(expected_date_UTC); // out: any (warnings) -JSS(expected_ledger_size); // out: TxQ -JSS(expiration); // out: AccountOffers, AccountChannels, - // ValidatorList, amm_info -JSS(fail_hard); // in: Sign, Submit -JSS(failed); // out: InboundLedger -JSS(feature); // in: Feature -JSS(features); // out: Feature -JSS(fee); // out: NetworkOPs, Peers -JSS(fee_base); // out: NetworkOPs -JSS(fee_div_max); // in: TransactionSign -JSS(fee_level); // out: AccountInfo -JSS(fee_mult_max); // in: TransactionSign -JSS(fee_ref); // out: NetworkOPs, DEPRECATED -JSS(fetch_pack); // out: NetworkOPs -JSS(FIELDS); // out: RPC server_definitions - // matches definitions.json format -JSS(first); // out: rpc/Version -JSS(firstSequence); // out: NodeToShardStatus -JSS(firstShardIndex); // out: NodeToShardStatus -JSS(finished); -JSS(fix_txns); // in: LedgerCleaner -JSS(flags); // out: AccountOffers, - // NetworkOPs -JSS(forward); // in: AccountTx -JSS(freeze); // out: AccountLines -JSS(freeze_peer); // out: AccountLines -JSS(frozen_balances); // out: GatewayBalances -JSS(full); // in: LedgerClearer, handlers/Ledger -JSS(full_reply); // out: PathFind -JSS(fullbelow_size); // out: GetCounts -JSS(good); // out: RPCVersion -JSS(hash); // out: NetworkOPs, InboundLedger, - // LedgerToJson, STTx; field -JSS(hashes); // in: AccountObjects -JSS(have_header); // out: InboundLedger -JSS(have_state); // out: InboundLedger -JSS(have_transactions); // out: InboundLedger -JSS(high); // out: BookChanges -JSS(highest_sequence); // out: AccountInfo -JSS(highest_ticket); // out: AccountInfo -JSS(historical_perminute); // historical_perminute. -JSS(hostid); // out: NetworkOPs -JSS(hotwallet); // in: GatewayBalances -JSS(id); // websocket. -JSS(ident); // in: AccountCurrencies, AccountInfo, - // OwnerInfo -JSS(ignore_default); // in: AccountLines -JSS(inLedger); // out: tx/Transaction -JSS(inbound); // out: PeerImp -JSS(index); // in: LedgerEntry, DownloadShard - // out: STLedgerEntry, - // LedgerEntry, TxHistory, LedgerData -JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo -JSS(initial_sync_duration_us); -JSS(internal_command); // in: Internal -JSS(invalid_API_version); // out: Many, when a request has an invalid - // version -JSS(io_latency_ms); // out: NetworkOPs -JSS(ip); // in: Connect, out: OverlayImpl -JSS(is_burned); // out: nft_info (clio) -JSS(isSerialized); // out: RPC server_definitions - // matches definitions.json format -JSS(isSigningField); // out: RPC server_definitions - // matches definitions.json format -JSS(isVLEncoded); // out: RPC server_definitions - // matches definitions.json format -JSS(issuer); // in: RipplePathFind, Subscribe, - // Unsubscribe, BookOffers - // out: STPathSet, STAmount -JSS(job); -JSS(job_queue); -JSS(jobs); -JSS(jsonrpc); // json version -JSS(jq_trans_overflow); // JobQueue transaction limit overflow. -JSS(kept); // out: SubmitTransaction -JSS(key); // out -JSS(key_type); // in/out: WalletPropose, TransactionSign -JSS(latency); // out: PeerImp -JSS(last); // out: RPCVersion -JSS(lastSequence); // out: NodeToShardStatus -JSS(lastShardIndex); // out: NodeToShardStatus -JSS(last_close); // out: NetworkOPs -JSS(last_refresh_time); // out: ValidatorSite -JSS(last_refresh_status); // out: ValidatorSite -JSS(last_refresh_message); // out: ValidatorSite -JSS(ledger); // in: NetworkOPs, LedgerCleaner, - // RPCHelpers - // out: NetworkOPs, PeerImp -JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, - // LedgerCurrent, LedgerAccept, - // AccountLines -JSS(ledger_data); // out: LedgerHeader -JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, - // RipplePathFind, TransactionEntry, - // handlers/Ledger - // out: NetworkOPs, RPCHelpers, - // LedgerClosed, LedgerData, - // AccountLines -JSS(ledger_hit_rate); // out: GetCounts -JSS(ledger_index); // in/out: many -JSS(ledger_index_max); // in, out: AccountTx* -JSS(ledger_index_min); // in, out: AccountTx* -JSS(ledger_max); // in, out: AccountTx* -JSS(ledger_min); // in, out: AccountTx* -JSS(ledger_time); // out: NetworkOPs -JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions - // matches definitions.json format -JSS(levels); // LogLevels -JSS(limit); // in/out: AccountTx*, AccountOffers, - // AccountLines, AccountObjects - // in: LedgerData, BookOffers -JSS(limit_peer); // out: AccountLines -JSS(lines); // out: AccountLines -JSS(list); // out: ValidatorList -JSS(load); // out: NetworkOPs, PeerImp -JSS(load_base); // out: NetworkOPs -JSS(load_factor); // out: NetworkOPs -JSS(load_factor_cluster); // out: NetworkOPs -JSS(load_factor_fee_escalation); // out: NetworkOPs -JSS(load_factor_fee_queue); // out: NetworkOPs -JSS(load_factor_fee_reference); // out: NetworkOPs -JSS(load_factor_local); // out: NetworkOPs -JSS(load_factor_net); // out: NetworkOPs -JSS(load_factor_server); // out: NetworkOPs -JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs -JSS(local); // out: resource/Logic.h -JSS(local_txs); // out: GetCounts -JSS(local_static_keys); // out: ValidatorList -JSS(low); // out: BookChanges -JSS(lowest_sequence); // out: AccountInfo -JSS(lowest_ticket); // out: AccountInfo -JSS(lp_token); // out: amm_info -JSS(majority); // out: RPC feature -JSS(manifest); // out: ValidatorInfo, Manifest -JSS(marker); // in/out: AccountTx, AccountOffers, - // AccountLines, AccountObjects, - // LedgerData - // in: BookOffers -JSS(master_key); // out: WalletPropose, NetworkOPs, - // ValidatorInfo - // in/out: Manifest -JSS(master_seed); // out: WalletPropose -JSS(master_seed_hex); // out: WalletPropose -JSS(master_signature); // out: pubManifest -JSS(max_ledger); // in/out: LedgerCleaner -JSS(max_queue_size); // out: TxQ -JSS(max_spend_drops); // out: AccountInfo -JSS(max_spend_drops_total); // out: AccountInfo -JSS(mean); // out: get_aggregate_price -JSS(median); // out: get_aggregate_price -JSS(median_fee); // out: TxQ -JSS(median_level); // out: TxQ -JSS(message); // error. -JSS(meta); // out: NetworkOPs, AccountTx*, Tx -JSS(meta_blob); // out: NetworkOPs, AccountTx*, Tx -JSS(metaData); -JSS(metadata); // out: TransactionEntry -JSS(method); // RPC -JSS(methods); -JSS(metrics); // out: Peers -JSS(min_count); // in: GetCounts -JSS(min_ledger); // in: LedgerCleaner -JSS(minimum_fee); // out: TxQ -JSS(minimum_level); // out: TxQ -JSS(missingCommand); // error -JSS(name); // out: AmendmentTableImpl, PeerImp -JSS(needed_state_hashes); // out: InboundLedger -JSS(needed_transaction_hashes); // out: InboundLedger -JSS(network_id); // out: NetworkOPs -JSS(network_ledger); // out: NetworkOPs -JSS(next_refresh_time); // out: ValidatorSite -JSS(nft_id); // in: nft_sell_offers, nft_buy_offers -JSS(nft_offer); // in: LedgerEntry -JSS(nft_offer_index); // out nft_buy_offers, nft_sell_offers -JSS(nft_page); // in: LedgerEntry -JSS(nft_serial); // out: account_nfts -JSS(nft_taxon); // out: nft_info (clio) -JSS(nftoken_id); // out: insertNFTokenID -JSS(nftoken_ids); // out: insertNFTokenID -JSS(no_ripple); // out: AccountLines -JSS(no_ripple_peer); // out: AccountLines -JSS(node); // out: LedgerEntry -JSS(node_binary); // out: LedgerEntry -JSS(node_read_bytes); // out: GetCounts -JSS(node_read_errors); // out: GetCounts -JSS(node_read_retries); // out: GetCounts -JSS(node_reads_hit); // out: GetCounts -JSS(node_reads_total); // out: GetCounts -JSS(node_reads_duration_us); // out: GetCounts -JSS(node_size); // out: server_info -JSS(nodestore); // out: GetCounts -JSS(node_writes); // out: GetCounts -JSS(node_written_bytes); // out: GetCounts -JSS(node_writes_duration_us); // out: GetCounts -JSS(node_write_retries); // out: GetCounts -JSS(node_writes_delayed); // out::GetCounts -JSS(nth); // out: RPC server_definitions -JSS(nunl); // in: AccountObjects -JSS(obligations); // out: GatewayBalances -JSS(offer); // in: LedgerEntry -JSS(offers); // out: NetworkOPs, AccountOffers, Subscribe -JSS(offer_id); // out: insertNFTokenOfferID -JSS(offline); // in: TransactionSign -JSS(offset); // in/out: AccountTxOld -JSS(open); // out: handlers/Ledger -JSS(open_ledger_cost); // out: SubmitTransaction -JSS(open_ledger_fee); // out: TxQ -JSS(open_ledger_level); // out: TxQ -JSS(oracle); // in: LedgerEntry -JSS(oracles); // in: get_aggregate_price -JSS(oracle_document_id); // in: get_aggregate_price -JSS(owner); // in: LedgerEntry, out: NetworkOPs -JSS(owner_funds); // in/out: Ledger, NetworkOPs, AcceptedLedgerTx -JSS(page_index); -JSS(params); // RPC -JSS(parent_close_time); // out: LedgerToJson -JSS(parent_hash); // out: LedgerToJson -JSS(partition); // in: LogLevel -JSS(passphrase); // in: WalletPropose -JSS(password); // in: Subscribe -JSS(paths); // in: RipplePathFind -JSS(paths_canonical); // out: RipplePathFind -JSS(paths_computed); // out: PathRequest, RipplePathFind -JSS(payment_channel); // in: LedgerEntry -JSS(peer); // in: AccountLines -JSS(peer_authorized); // out: AccountLines -JSS(peer_id); // out: RCLCxPeerPos -JSS(peers); // out: InboundLedger, handlers/Peers, Overlay -JSS(peer_disconnects); // Severed peer connection counter. -JSS(peer_disconnects_resources); // Severed peer connections because of - // excess resource consumption. -JSS(port); // in: Connect, out: NetworkOPs -JSS(ports); // out: NetworkOPs -JSS(previous); // out: Reservations -JSS(previous_ledger); // out: LedgerPropose -JSS(price); // out: amm_info, AuctionSlot -JSS(proof); // in: BookOffers -JSS(propose_seq); // out: LedgerPropose -JSS(proposers); // out: NetworkOPs, LedgerConsensus -JSS(protocol); // out: NetworkOPs, PeerImp -JSS(proxied); // out: RPC ping -JSS(pubkey_node); // out: NetworkOPs -JSS(pubkey_publisher); // out: ValidatorList -JSS(pubkey_validator); // out: NetworkOPs, ValidatorList -JSS(public_key); // out: OverlayImpl, PeerImp, WalletPropose, - // ValidatorInfo - // in/out: Manifest -JSS(public_key_hex); // out: WalletPropose -JSS(published_ledger); // out: NetworkOPs -JSS(publisher_lists); // out: ValidatorList -JSS(quality); // out: NetworkOPs -JSS(quality_in); // out: AccountLines -JSS(quality_out); // out: AccountLines -JSS(queue); // in: AccountInfo -JSS(queue_data); // out: AccountInfo -JSS(queued); // out: SubmitTransaction -JSS(queued_duration_us); -JSS(quote_asset); // in: get_aggregate_price -JSS(random); // out: Random -JSS(raw_meta); // out: AcceptedLedgerTx -JSS(receive_currencies); // out: AccountCurrencies -JSS(reference_level); // out: TxQ -JSS(refresh_interval); // in: UNL -JSS(refresh_interval_min); // out: ValidatorSites -JSS(regular_seed); // in/out: LedgerEntry -JSS(remaining); // out: ValidatorList -JSS(remote); // out: Logic.h -JSS(request); // RPC -JSS(requested); // out: Manifest -JSS(reservations); // out: Reservations -JSS(reserve_base); // out: NetworkOPs -JSS(reserve_base_xrp); // out: NetworkOPs -JSS(reserve_inc); // out: NetworkOPs -JSS(reserve_inc_xrp); // out: NetworkOPs -JSS(response); // websocket -JSS(result); // RPC -JSS(ripple_lines); // out: NetworkOPs -JSS(ripple_state); // in: LedgerEntr -JSS(ripplerpc); // ripple RPC version -JSS(role); // out: Ping.cpp -JSS(rpc); -JSS(rt_accounts); // in: Subscribe, Unsubscribe -JSS(running_duration_us); -JSS(search_depth); // in: RipplePathFind -JSS(searched_all); // out: Tx -JSS(secret); // in: TransactionSign, - // ValidationCreate, ValidationSeed, - // channel_authorize -JSS(seed); // -JSS(seed_hex); // in: WalletPropose, TransactionSign -JSS(send_currencies); // out: AccountCurrencies -JSS(send_max); // in: PathRequest, RipplePathFind -JSS(seq); // in: LedgerEntry; - // out: NetworkOPs, RPCSub, AccountOffers, - // ValidatorList, ValidatorInfo, Manifest -JSS(sequence); // in: UNL -JSS(sequence_count); // out: AccountInfo -JSS(server_domain); // out: NetworkOPs -JSS(server_state); // out: NetworkOPs -JSS(server_state_duration_us); // out: NetworkOPs -JSS(server_status); // out: NetworkOPs -JSS(server_version); // out: NetworkOPs -JSS(settle_delay); // out: AccountChannels -JSS(severity); // in: LogLevel -JSS(shards); // in/out: GetCounts, DownloadShard -JSS(signature); // out: NetworkOPs, ChannelAuthorize -JSS(signature_verified); // out: ChannelVerify -JSS(signing_key); // out: NetworkOPs -JSS(signing_keys); // out: ValidatorList -JSS(signing_time); // out: NetworkOPs -JSS(signer_list); // in: AccountObjects -JSS(signer_lists); // in/out: AccountInfo -JSS(size); // out: get_aggregate_price -JSS(snapshot); // in: Subscribe -JSS(source_account); // in: PathRequest, RipplePathFind -JSS(source_amount); // in: PathRequest, RipplePathFind -JSS(source_currencies); // in: PathRequest, RipplePathFind -JSS(source_tag); // out: AccountChannels -JSS(stand_alone); // out: NetworkOPs -JSS(standard_deviation); // out: get_aggregate_price -JSS(start); // in: TxHistory -JSS(started); -JSS(state); // out: Logic.h, ServerState, LedgerData -JSS(state_accounting); // out: NetworkOPs -JSS(state_now); // in: Subscribe -JSS(status); // error -JSS(stop); // in: LedgerCleaner -JSS(stop_history_tx_only); // in: Unsubscribe, stop history tx stream -JSS(storedSeqs); // out: NodeToShardStatus -JSS(streams); // in: Subscribe, Unsubscribe -JSS(strict); // in: AccountCurrencies, AccountInfo -JSS(sub_index); // in: LedgerEntry -JSS(subcommand); // in: PathFind -JSS(success); // rpc -JSS(supported); // out: AmendmentTableImpl -JSS(sync_mode); // in: Submit -JSS(system_time_offset); // out: NetworkOPs -JSS(tag); // out: Peers -JSS(taker); // in: Subscribe, BookOffers -JSS(taker_gets); // in: Subscribe, Unsubscribe, BookOffers -JSS(taker_gets_funded); // out: NetworkOPs -JSS(taker_pays); // in: Subscribe, Unsubscribe, BookOffers -JSS(taker_pays_funded); // out: NetworkOPs -JSS(threshold); // in: Blacklist -JSS(ticket); // in: AccountObjects -JSS(ticket_count); // out: AccountInfo -JSS(ticket_seq); // in: LedgerEntry -JSS(time); -JSS(timeouts); // out: InboundLedger -JSS(time_threshold); // in/out: Oracle aggregate -JSS(time_interval); // out: AMM Auction Slot -JSS(track); // out: PeerImp -JSS(traffic); // out: Overlay -JSS(trim); // in: get_aggregate_price -JSS(trimmed_set); // out: get_aggregate_price -JSS(total); // out: counters -JSS(total_bytes_recv); // out: Peers -JSS(total_bytes_sent); // out: Peers -JSS(total_coins); // out: LedgerToJson -JSS(trading_fee); // out: amm_info -JSS(transTreeHash); // out: ledger/Ledger.cpp -JSS(transaction); // in: Tx - // out: NetworkOPs, AcceptedLedgerTx, -JSS(transaction_hash); // out: RCLCxPeerPos, LedgerToJson -JSS(transactions); // out: LedgerToJson, - // in: AccountTx*, Unsubscribe -JSS(TRANSACTION_RESULTS); // out: RPC server_definitions - // matches definitions.json format -JSS(TRANSACTION_TYPES); // out: RPC server_definitions - // matches definitions.json format -JSS(TYPES); // out: RPC server_definitions - // matches definitions.json format -JSS(transfer_rate); // out: nft_info (clio) -JSS(transitions); // out: NetworkOPs -JSS(treenode_cache_size); // out: GetCounts -JSS(treenode_track_size); // out: GetCounts -JSS(trusted); // out: UnlList -JSS(trusted_validator_keys); // out: ValidatorList -JSS(tx); // out: STTx, AccountTx* -JSS(tx_blob); // in/out: Submit, - // in: TransactionSign, AccountTx* -JSS(tx_hash); // in: TransactionEntry -JSS(tx_json); // in/out: TransactionSign - // out: TransactionEntry -JSS(tx_signing_hash); // out: TransactionSign -JSS(tx_unsigned); // out: TransactionSign -JSS(txn_count); // out: NetworkOPs -JSS(txr_tx_cnt); // out: protocol message tx's count -JSS(txr_tx_sz); // out: protocol message tx's size -JSS(txr_have_txs_cnt); // out: protocol message have tx count -JSS(txr_have_txs_sz); // out: protocol message have tx size -JSS(txr_get_ledger_cnt); // out: protocol message get ledger count -JSS(txr_get_ledger_sz); // out: protocol message get ledger size -JSS(txr_ledger_data_cnt); // out: protocol message ledger data count -JSS(txr_ledger_data_sz); // out: protocol message ledger data size -JSS(txr_transactions_cnt); // out: protocol message get object count -JSS(txr_transactions_sz); // out: protocol message get object size -JSS(txr_selected_cnt); // out: selected peers count -JSS(txr_suppressed_cnt); // out: suppressed peers count -JSS(txr_not_enabled_cnt); // out: peers with tx reduce-relay disabled count -JSS(txr_missing_tx_freq); // out: missing tx frequency average -JSS(txs); // out: TxHistory -JSS(type); // in: AccountObjects - // out: NetworkOPs, RPC server_definitions - // OverlayImpl, Logic -JSS(type_hex); // out: STPathSet -JSS(unl); // out: UnlList -JSS(unlimited); // out: Connection.h -JSS(uptime); // out: GetCounts -JSS(uri); // out: ValidatorSites -JSS(url); // in/out: Subscribe, Unsubscribe -JSS(url_password); // in: Subscribe -JSS(url_username); // in: Subscribe -JSS(urlgravatar); // -JSS(username); // in: Subscribe -JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx* - // Tx -JSS(validator_list_expires); // out: NetworkOps, ValidatorList -JSS(validator_list); // out: NetworkOps, ValidatorList -JSS(validators); -JSS(validated_hash); // out: NetworkOPs -JSS(validated_ledger); // out: NetworkOPs -JSS(validated_ledger_index); // out: SubmitTransaction -JSS(validated_ledgers); // out: NetworkOPs -JSS(validation_key); // out: ValidationCreate, ValidationSeed -JSS(validation_private_key); // out: ValidationCreate -JSS(validation_public_key); // out: ValidationCreate, ValidationSeed -JSS(validation_quorum); // out: NetworkOPs -JSS(validation_seed); // out: ValidationCreate, ValidationSeed -JSS(validations); // out: AmendmentTableImpl -JSS(validator_sites); // out: ValidatorSites -JSS(value); // out: STAmount -JSS(version); // out: RPCVersion -JSS(vetoed); // out: AmendmentTableImpl -JSS(volume_a); // out: BookChanges -JSS(volume_b); // out: BookChanges -JSS(vote); // in: Feature -JSS(vote_slots); // out: amm_info -JSS(vote_weight); // out: amm_info -JSS(warning); // rpc: -JSS(warnings); // out: server_info, server_state -JSS(workers); -JSS(write_load); // out: GetCounts -JSS(xchain_owned_claim_id); // in: LedgerEntry, AccountObjects -JSS(xchain_owned_create_account_claim_id); // in: LedgerEntry -JSS(NegativeUNL); // out: ValidatorList; ledger type -#undef JSS - -} // namespace jss -} // namespace ripple - -#endif diff --git a/src/ripple/rpc/ShardArchiveHandler.h b/src/ripple/rpc/ShardArchiveHandler.h deleted file mode 100644 index b9e9b3a60a9..00000000000 --- a/src/ripple/rpc/ShardArchiveHandler.h +++ /dev/null @@ -1,176 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_RPC_SHARDARCHIVEHANDLER_H_INCLUDED -#define RIPPLE_RPC_SHARDARCHIVEHANDLER_H_INCLUDED - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace ripple { -#ifdef ENABLE_TESTS -namespace test { -class ShardArchiveHandler_test; -} -#endif // ENABLE_TESTS -namespace RPC { - -/** Handles the download and import of one or more shard archives. */ -class ShardArchiveHandler -{ -public: - using TimerOpCounter = - ClosureCounter; -#ifdef ENABLE_TESTS - friend class test::ShardArchiveHandler_test; -#endif // ENABLE_TESTS - - static boost::filesystem::path - getDownloadDirectory(Config const& config); - - static std::unique_ptr - makeShardArchiveHandler(Application& app); - - // Create a ShardArchiveHandler only if - // the state database is present, indicating - // that recovery is needed. - static std::unique_ptr - tryMakeRecoveryHandler(Application& app); - - explicit ShardArchiveHandler(Application& app); - - virtual ~ShardArchiveHandler() = default; - - [[nodiscard]] bool - init(); - - bool - add(std::uint32_t shardIndex, std::pair&& url); - - /** Starts downloading and importing archives. */ - bool - start(); - - void - stop(); - - void - release(); - -private: - ShardArchiveHandler() = delete; - ShardArchiveHandler(ShardArchiveHandler const&) = delete; - ShardArchiveHandler& - operator=(ShardArchiveHandler&&) = delete; - ShardArchiveHandler& - operator=(ShardArchiveHandler const&) = delete; - - [[nodiscard]] bool - initFromDB(std::lock_guard const&); - - /** Add an archive to be downloaded and imported. - @param shardIndex the index of the shard to be imported. - @param url the location of the archive. - @return `true` if successfully added. - @note Returns false if called while downloading. - */ - bool - add(std::uint32_t shardIndex, - parsedURL&& url, - std::lock_guard const&); - - // Begins the download and import of the next archive. - bool - next(std::lock_guard const& l); - - // Callback used by the downloader to notify completion of a download. - void - complete(boost::filesystem::path dstPath); - - // Extract a downloaded archive and import it into the shard store. - void - process(boost::filesystem::path const& dstPath); - - // Remove the archive being processed. - void - remove(std::lock_guard const&); - - void - doRelease(std::lock_guard const&); - - bool - onClosureFailed( - std::string const& errorMsg, - std::lock_guard const& lock); - - bool - removeAndProceed(std::lock_guard const& lock); - - ///////////////////////////////////////////////// - // m_ is used to protect access to downloader_, - // archives_, process_ and to protect setting and - // destroying sqlDB_. - ///////////////////////////////////////////////// - std::mutex mutable m_; - std::atomic_bool stopping_{false}; - std::shared_ptr downloader_; - std::map archives_; - bool process_; - std::unique_ptr sqlDB_; - ///////////////////////////////////////////////// - - Application& app_; - beast::Journal const j_; - boost::filesystem::path const downloadDir_; - boost::asio::basic_waitable_timer timer_; - JobCounter jobCounter_; - TimerOpCounter timerCounter_; - ShardVerificationScheduler verificationScheduler_; -}; - -//////////////////////////////////////////////////////////////////// -// The RecoveryHandler is an empty class that is constructed by -// the application when the ShardArchiveHandler's state database -// is present at application start, indicating that the handler -// needs to perform recovery. However, if recovery isn't needed -// at application start, and the user subsequently submits a request -// to download shards, we construct a ShardArchiveHandler rather -// than a RecoveryHandler to process the request. With this approach, -// type verification can be employed to determine whether the -// ShardArchiveHandler was constructed in recovery mode by the -// application, or as a response to a user submitting a request to -// download shards. -//////////////////////////////////////////////////////////////////// -class RecoveryHandler : public ShardArchiveHandler -{ -public: - explicit RecoveryHandler(Application& app); -}; - -} // namespace RPC -} // namespace ripple - -#endif diff --git a/src/ripple/rpc/ShardVerificationScheduler.h b/src/ripple/rpc/ShardVerificationScheduler.h deleted file mode 100644 index 659b3e90491..00000000000 --- a/src/ripple/rpc/ShardVerificationScheduler.h +++ /dev/null @@ -1,84 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_RPC_SHARDVERIFICATIONSCHEDULER_H_INCLUDED -#define RIPPLE_RPC_SHARDVERIFICATIONSCHEDULER_H_INCLUDED - -#include -#include - -namespace ripple { -namespace RPC { - -class ShardVerificationScheduler -{ -public: - // This is the signature of the function that the client - // wants to have invoked upon timer expiration. The function - // should check the error code 'ec' and abort the function - // if the timer was cancelled: - // (ec == boost::asio::error::operation_aborted). - // In the body of the function, the client should perform - // the necessary verification. - using retryFunction = - std::function; - - ShardVerificationScheduler() = default; - - ShardVerificationScheduler( - std::chrono::seconds retryInterval, - std::uint32_t maxAttempts); - - bool - retry(Application& app, bool shouldHaveHash, retryFunction f); - - void - reset(); - -private: - using waitable_timer = - boost::asio::basic_waitable_timer; - - ///////////////////////////////////////////////////// - // NOTE: retryInterval_ and maxAttempts_ were chosen - // semi-arbitrarily and experimenting with other - // values might prove useful. - ///////////////////////////////////////////////////// - - static constexpr std::chrono::seconds defaultRetryInterval_{60}; - - static constexpr std::uint32_t defaultmaxAttempts_{5}; - - // The number of seconds to wait before retrying - // retrieval of a shard's last ledger hash - const std::chrono::seconds retryInterval_{defaultRetryInterval_}; - - // Maximum attempts to retrieve a shard's last ledger hash - const std::uint32_t maxAttempts_{defaultmaxAttempts_}; - - std::unique_ptr timer_; - - // Number of attempts to retrieve a shard's last ledger hash - std::uint32_t numAttempts_{0}; -}; - -} // namespace RPC -} // namespace ripple - -#endif // RIPPLE_RPC_SHARDVERIFICATIONSCHEDULER_H_INCLUDED diff --git a/src/ripple/rpc/handlers/CrawlShards.cpp b/src/ripple/rpc/handlers/CrawlShards.cpp deleted file mode 100644 index 41b74860c3a..00000000000 --- a/src/ripple/rpc/handlers/CrawlShards.cpp +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2018 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -/** RPC command that reports stored shards by nodes. - { - // Determines if the result includes node public key. - // optional, default is false - public_key: - - // The maximum number of peer hops to attempt. - // optional, default is zero, maximum is 3 - limit: - } -*/ -Json::Value -doCrawlShards(RPC::JsonContext& context) -{ - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - - if (context.role != Role::ADMIN) - return rpcError(rpcNO_PERMISSION); - - std::uint32_t relays{0}; - if (auto const& jv = context.params[jss::limit]) - { - if (!(jv.isUInt() || (jv.isInt() && jv.asInt() >= 0))) - return RPC::expected_field_error(jss::limit, "unsigned integer"); - relays = std::min(jv.asUInt(), relayLimit); - context.loadType = Resource::feeHighBurdenRPC; - } - else - context.loadType = Resource::feeMediumBurdenRPC; - - // Collect shard info from server and peers - bool const includePublicKey{ - context.params.isMember(jss::public_key) && - context.params[jss::public_key].asBool()}; - Json::Value jvResult{ - context.app.overlay().crawlShards(includePublicKey, relays)}; - - return jvResult; -} - -} // namespace ripple diff --git a/src/ripple/rpc/handlers/DepositAuthorized.cpp b/src/ripple/rpc/handlers/DepositAuthorized.cpp deleted file mode 100644 index bb6e3b07a9a..00000000000 --- a/src/ripple/rpc/handlers/DepositAuthorized.cpp +++ /dev/null @@ -1,113 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2018 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { - -// { -// source_account : -// destination_account : -// ledger_hash : -// ledger_index : -// } - -Json::Value -doDepositAuthorized(RPC::JsonContext& context) -{ - Json::Value const& params = context.params; - - // Validate source_account. - if (!params.isMember(jss::source_account)) - return RPC::missing_field_error(jss::source_account); - if (!params[jss::source_account].isString()) - return RPC::make_error( - rpcINVALID_PARAMS, - RPC::expected_field_message(jss::source_account, "a string")); - - auto srcID = parseBase58(params[jss::source_account].asString()); - if (!srcID) - return rpcError(rpcACT_MALFORMED); - auto const srcAcct{std::move(srcID.value())}; - - // Validate destination_account. - if (!params.isMember(jss::destination_account)) - return RPC::missing_field_error(jss::destination_account); - if (!params[jss::destination_account].isString()) - return RPC::make_error( - rpcINVALID_PARAMS, - RPC::expected_field_message(jss::destination_account, "a string")); - - auto dstID = - parseBase58(params[jss::destination_account].asString()); - if (!dstID) - return rpcError(rpcACT_MALFORMED); - auto const dstAcct{std::move(dstID.value())}; - - // Validate ledger. - std::shared_ptr ledger; - Json::Value result = RPC::lookupLedger(ledger, context); - - if (!ledger) - return result; - - // If source account is not in the ledger it can't be authorized. - if (!ledger->exists(keylet::account(srcAcct))) - { - RPC::inject_error(rpcSRC_ACT_NOT_FOUND, result); - return result; - } - - // If destination account is not in the ledger you can't deposit to it, eh? - auto const sleDest = ledger->read(keylet::account(dstAcct)); - if (!sleDest) - { - RPC::inject_error(rpcDST_ACT_NOT_FOUND, result); - return result; - } - - // If the two accounts are the same, then the deposit should be fine. - bool depositAuthorized{true}; - if (srcAcct != dstAcct) - { - // Check destination for the DepositAuth flag. If that flag is - // not set then a deposit should be just fine. - if (sleDest->getFlags() & lsfDepositAuth) - { - // See if a preauthorization entry is in the ledger. - auto const sleDepositAuth = - ledger->read(keylet::depositPreauth(dstAcct, srcAcct)); - depositAuthorized = static_cast(sleDepositAuth); - } - } - result[jss::source_account] = params[jss::source_account].asString(); - result[jss::destination_account] = - params[jss::destination_account].asString(); - - result[jss::deposit_authorized] = depositAuthorized; - return result; -} - -} // namespace ripple diff --git a/src/ripple/rpc/handlers/DownloadShard.cpp b/src/ripple/rpc/handlers/DownloadShard.cpp deleted file mode 100644 index eacf499df04..00000000000 --- a/src/ripple/rpc/handlers/DownloadShard.cpp +++ /dev/null @@ -1,176 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2014 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace ripple { - -/** RPC command that downloads and import shard archives. - { - shards: [{index: , url: }] - } - - example: - { - "command": "download_shard", - "shards": [ - {"index": 1, "url": "https://domain.com/1.tar.lz4"}, - {"index": 5, "url": "https://domain.com/5.tar.lz4"} - ] - } -*/ -Json::Value -doDownloadShard(RPC::JsonContext& context) -{ - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - - if (context.role != Role::ADMIN) - return rpcError(rpcNO_PERMISSION); - - // The shard store must be configured - auto shardStore{context.app.getShardStore()}; - if (!shardStore) - return rpcError(rpcNOT_ENABLED); - - // Return status update if already downloading - auto preShards{shardStore->getPreShards()}; - if (!preShards.empty()) - { - std::string s{"Download already in progress. Shard"}; - if (!std::all_of(preShards.begin(), preShards.end(), ::isdigit)) - s += "s"; - return RPC::makeObjectValue(s + " " + preShards); - } - - if (!context.params.isMember(jss::shards)) - return RPC::missing_field_error(jss::shards); - if (!context.params[jss::shards].isArray() || - context.params[jss::shards].size() == 0) - { - return RPC::expected_field_error(std::string(jss::shards), "an array"); - } - - // Validate shards - static const std::string ext{".tar.lz4"}; - std::map> archives; - for (auto& it : context.params[jss::shards]) - { - // Validate the index - if (!it.isMember(jss::index)) - return RPC::missing_field_error(jss::index); - auto& jv{it[jss::index]}; - if (!(jv.isUInt() || (jv.isInt() && jv.asInt() >= 0))) - { - return RPC::expected_field_error( - std::string(jss::index), "an unsigned integer"); - } - - // Validate the URL - if (!it.isMember(jss::url)) - return RPC::missing_field_error(jss::url); - parsedURL url; - auto unparsedURL = it[jss::url].asString(); - if (!parseUrl(url, unparsedURL) || url.domain.empty() || - url.path.empty()) - { - return RPC::invalid_field_error(jss::url); - } - if (url.scheme != "https" && url.scheme != "http") - return RPC::expected_field_error( - std::string(jss::url), "HTTPS or HTTP"); - - // URL must point to an lz4 compressed tar archive '.tar.lz4' - auto archiveName{url.path.substr(url.path.find_last_of("/\\") + 1)}; - if (archiveName.empty() || archiveName.size() <= ext.size()) - { - return RPC::make_param_error( - "Invalid field '" + std::string(jss::url) + - "', invalid archive name"); - } - if (!boost::iends_with(archiveName, ext)) - { - return RPC::make_param_error( - "Invalid field '" + std::string(jss::url) + - "', invalid archive extension"); - } - - // Check for duplicate indexes - if (!archives - .emplace( - jv.asUInt(), std::make_pair(std::move(url), unparsedURL)) - .second) - { - return RPC::make_param_error( - "Invalid field '" + std::string(jss::index) + - "', duplicate shard ids."); - } - } - - RPC::ShardArchiveHandler* handler = nullptr; - - try - { - handler = context.app.getShardArchiveHandler(); - - if (!handler) - return RPC::make_error( - rpcINTERNAL, "Failed to create ShardArchiveHandler."); - } - catch (std::exception const& e) - { - return RPC::make_error( - rpcINTERNAL, std::string("Failed to start download: ") + e.what()); - } - - for (auto& [index, url] : archives) - { - if (!handler->add(index, std::move(url))) - { - return RPC::make_param_error( - "Invalid field '" + std::string(jss::index) + "', shard id " + - std::to_string(index) + " exists or being acquired"); - } - } - - // Begin downloading. - if (!handler->start()) - { - handler->release(); - return rpcError(rpcINTERNAL); - } - - std::string s{"Downloading shard"}; - preShards = shardStore->getPreShards(); - if (!std::all_of(preShards.begin(), preShards.end(), ::isdigit)) - s += "s"; - return RPC::makeObjectValue(s + " " + preShards); -} - -} // namespace ripple diff --git a/src/ripple/rpc/handlers/NodeToShard.cpp b/src/ripple/rpc/handlers/NodeToShard.cpp deleted file mode 100644 index 552900d1548..00000000000 --- a/src/ripple/rpc/handlers/NodeToShard.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -// node_to_shard [status|start|stop] -Json::Value -doNodeToShard(RPC::JsonContext& context) -{ - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - - // Shard store must be enabled - auto const shardStore = context.app.getShardStore(); - if (!shardStore) - return RPC::make_error(rpcNOT_ENABLED); - - if (!context.params.isMember(jss::action)) - return RPC::missing_field_error(jss::action); - - // Obtain and normalize the action to perform - auto const action = [&context] { - auto value = context.params[jss::action].asString(); - boost::to_lower(value); - - return value; - }(); - - // Vector of allowed actions - std::vector const allowedActions = {"status", "start", "stop"}; - - // Validate the action - if (std::find(allowedActions.begin(), allowedActions.end(), action) == - allowedActions.end()) - return RPC::invalid_field_error(jss::action); - - // Perform the action - if (action == "status") - { - // Get the status of the database import - return shardStore->getDatabaseImportStatus(); - } - else if (action == "start") - { - // Kick off an import - return shardStore->startNodeToShard(); - } - else if (action == "stop") - { - // Halt an import - return shardStore->stopNodeToShard(); - } - else - { - // Shouldn't happen - assert(false); - return rpcError(rpcINTERNAL); - } -} - -} // namespace ripple diff --git a/src/ripple/rpc/impl/ShardArchiveHandler.cpp b/src/ripple/rpc/impl/ShardArchiveHandler.cpp deleted file mode 100644 index 90bd5edc361..00000000000 --- a/src/ripple/rpc/impl/ShardArchiveHandler.cpp +++ /dev/null @@ -1,585 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2014 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace ripple { -namespace RPC { - -using namespace boost::filesystem; -using namespace std::chrono_literals; - -boost::filesystem::path -ShardArchiveHandler::getDownloadDirectory(Config const& config) -{ - return boost::filesystem::path{ - get(config.section(ConfigSection::shardDatabase()), - "download_path", - get(config.section(ConfigSection::shardDatabase()), - "path", - ""))} / - "download"; -} - -std::unique_ptr -ShardArchiveHandler::makeShardArchiveHandler(Application& app) -{ - return std::make_unique(app); -} - -std::unique_ptr -ShardArchiveHandler::tryMakeRecoveryHandler(Application& app) -{ - auto const downloadDir(getDownloadDirectory(app.config())); - - // Create the handler iff the database - // is present. - if (exists(downloadDir / stateDBName) && - is_regular_file(downloadDir / stateDBName)) - { - return std::make_unique(app); - } - - return nullptr; -} - -ShardArchiveHandler::ShardArchiveHandler(Application& app) - : process_(false) - , app_(app) - , j_(app.journal("ShardArchiveHandler")) - , downloadDir_(getDownloadDirectory(app.config())) - , timer_(app_.getIOService()) - , verificationScheduler_( - std::chrono::seconds(get( - app.config().section(ConfigSection::shardDatabase()), - "shard_verification_retry_interval")), - - get( - app.config().section(ConfigSection::shardDatabase()), - "shard_verification_max_attempts")) -{ - assert(app_.getShardStore()); -} - -bool -ShardArchiveHandler::init() -{ - std::lock_guard lock(m_); - - if (process_ || downloader_ != nullptr || sqlDB_ != nullptr) - { - JLOG(j_.warn()) << "Archives already being processed"; - return false; - } - - // Initialize from pre-existing database - if (exists(downloadDir_ / stateDBName) && - is_regular_file(downloadDir_ / stateDBName)) - { - downloader_ = - make_DatabaseDownloader(app_.getIOService(), app_.config(), j_); - - return initFromDB(lock); - } - - // Fresh initialization - else - { - try - { - create_directories(downloadDir_); - - sqlDB_ = makeArchiveDB(downloadDir_, stateDBName, j_); - } - catch (std::exception const& e) - { - JLOG(j_.error()) - << "exception: " << e.what() << " in function: " << __func__; - - return false; - } - } - - return true; -} - -bool -ShardArchiveHandler::initFromDB(std::lock_guard const& lock) -{ - try - { - using namespace boost::filesystem; - - assert( - exists(downloadDir_ / stateDBName) && - is_regular_file(downloadDir_ / stateDBName)); - - sqlDB_ = makeArchiveDB(downloadDir_, stateDBName, j_); - - readArchiveDB(*sqlDB_, [&](std::string const& url_, int state) { - parsedURL url; - - if (!parseUrl(url, url_)) - { - JLOG(j_.error()) << "Failed to parse url: " << url_; - - return; - } - - add(state, std::move(url), lock); - }); - - // Failed to load anything - // from the state database. - if (archives_.empty()) - { - release(); - return false; - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what() - << " in function: " << __func__; - - return false; - } - - return true; -} - -void -ShardArchiveHandler::stop() -{ - stopping_ = true; - { - std::lock_guard lock(m_); - - if (downloader_) - { - downloader_->stop(); - downloader_.reset(); - } - - timer_.cancel(); - } - - jobCounter_.join( - "ShardArchiveHandler", std::chrono::milliseconds(2000), j_); - - timerCounter_.join( - "ShardArchiveHandler", std::chrono::milliseconds(2000), j_); -} - -bool -ShardArchiveHandler::add( - std::uint32_t shardIndex, - std::pair&& url) -{ - std::lock_guard lock(m_); - - if (!add(shardIndex, std::forward(url.first), lock)) - return false; - - insertArchiveDB(*sqlDB_, shardIndex, url.second); - - return true; -} - -bool -ShardArchiveHandler::add( - std::uint32_t shardIndex, - parsedURL&& url, - std::lock_guard const&) -{ - if (process_) - { - JLOG(j_.error()) << "Download and import already in progress"; - return false; - } - - auto const it{archives_.find(shardIndex)}; - if (it != archives_.end()) - return url == it->second; - - archives_.emplace(shardIndex, std::move(url)); - - return true; -} - -bool -ShardArchiveHandler::start() -{ - std::lock_guard lock(m_); - if (!app_.getShardStore()) - { - JLOG(j_.error()) << "No shard store available"; - return false; - } - if (process_) - { - JLOG(j_.warn()) << "Archives already being processed"; - return false; - } - if (archives_.empty()) - { - JLOG(j_.warn()) << "No archives to process"; - return false; - } - - std::vector shardIndexes(archives_.size()); - std::transform( - archives_.begin(), - archives_.end(), - shardIndexes.begin(), - [](auto const& entry) { return entry.first; }); - - if (!app_.getShardStore()->prepareShards(shardIndexes)) - return false; - - try - { - // Create temp root download directory - create_directories(downloadDir_); - - if (!downloader_) - { - // will throw if can't initialize ssl context - downloader_ = - make_DatabaseDownloader(app_.getIOService(), app_.config(), j_); - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what(); - return false; - } - - process_ = true; - return next(lock); -} - -void -ShardArchiveHandler::release() -{ - std::lock_guard lock(m_); - doRelease(lock); -} - -bool -ShardArchiveHandler::next(std::lock_guard const& l) -{ - if (stopping_) - return false; - - if (archives_.empty()) - { - doRelease(l); - return false; - } - - auto const shardIndex{archives_.begin()->first}; - - // We use the sequence of the last validated ledger - // to determine whether or not we have stored a ledger - // that comes after the last ledger in this shard. A - // later ledger must be present in order to reliably - // retrieve the hash of the shard's last ledger. - std::optional expectedHash; - bool shouldHaveHash = false; - if (auto const seq = app_.getShardStore()->lastLedgerSeq(shardIndex); - (shouldHaveHash = app_.getLedgerMaster().getValidLedgerIndex() > seq)) - { - expectedHash = app_.getLedgerMaster().walkHashBySeq( - seq, InboundLedger::Reason::GENERIC); - } - - if (!expectedHash) - { - auto wrapper = - timerCounter_.wrap([this](boost::system::error_code const& ec) { - if (ec != boost::asio::error::operation_aborted) - { - std::lock_guard lock(m_); - this->next(lock); - } - }); - - if (!wrapper) - return onClosureFailed( - "failed to wrap closure for last ledger confirmation timer", l); - - if (!verificationScheduler_.retry(app_, shouldHaveHash, *wrapper)) - { - JLOG(j_.error()) << "failed to find last ledger hash for shard " - << shardIndex << ", maximum attempts reached"; - - return removeAndProceed(l); - } - - return true; - } - - // Create a temp archive directory at the root - auto const dstDir{downloadDir_ / std::to_string(shardIndex)}; - try - { - create_directory(dstDir); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what(); - return removeAndProceed(l); - } - - // Download the archive. Process in another thread - // to prevent holding up the lock if the downloader - // sleeps. - auto const& url{archives_.begin()->second}; - auto wrapper = jobCounter_.wrap([this, url, dstDir]() { - auto const ssl = (url.scheme == "https"); - auto const defaultPort = ssl ? 443 : 80; - - if (!downloader_->download( - url.domain, - std::to_string(url.port.value_or(defaultPort)), - url.path, - 11, - dstDir / "archive.tar.lz4", - [this](path dstPath) { complete(dstPath); }, - ssl)) - { - std::lock_guard l(m_); - removeAndProceed(l); - } - }); - - if (!wrapper) - return onClosureFailed( - "failed to wrap closure for starting download", l); - - app_.getJobQueue().addJob(jtCLIENT_SHARD, "ShardArchiveHandler", *wrapper); - - return true; -} - -void -ShardArchiveHandler::complete(path dstPath) -{ - if (stopping_) - return; - - { - std::lock_guard lock(m_); - try - { - if (!is_regular_file(dstPath)) - { - auto ar{archives_.begin()}; - JLOG(j_.error()) - << "Downloading shard id " << ar->first << " from URL " - << ar->second.domain << ar->second.path; - removeAndProceed(lock); - return; - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what(); - removeAndProceed(lock); - return; - } - } - - // Make lambdas mutable captured vars can be moved from - auto wrapper = - jobCounter_.wrap([=, this, dstPath = std::move(dstPath)]() mutable { - if (stopping_) - return; - - // If not synced then defer and retry - auto const mode{app_.getOPs().getOperatingMode()}; - if (mode != OperatingMode::FULL) - { - std::lock_guard lock(m_); - timer_.expires_from_now(static_cast( - (static_cast(OperatingMode::FULL) - - static_cast(mode)) * - 10)); - - auto wrapper = timerCounter_.wrap( - [=, this, dstPath = std::move(dstPath)]( - boost::system::error_code const& ec) mutable { - if (ec != boost::asio::error::operation_aborted) - complete(std::move(dstPath)); - }); - - if (!wrapper) - onClosureFailed( - "failed to wrap closure for operating mode timer", - lock); - else - timer_.async_wait(*wrapper); - } - else - { - process(dstPath); - std::lock_guard lock(m_); - removeAndProceed(lock); - } - }); - - if (!wrapper) - { - if (stopping_) - return; - - JLOG(j_.error()) << "failed to wrap closure for process()"; - - std::lock_guard lock(m_); - removeAndProceed(lock); - } - - // Process in another thread to not hold up the IO service - app_.getJobQueue().addJob(jtCLIENT_SHARD, "ShardArchiveHandler", *wrapper); -} - -void -ShardArchiveHandler::process(path const& dstPath) -{ - std::uint32_t shardIndex; - { - std::lock_guard lock(m_); - shardIndex = archives_.begin()->first; - } - - auto const shardDir{dstPath.parent_path() / std::to_string(shardIndex)}; - try - { - // Extract the downloaded archive - extractTarLz4(dstPath, dstPath.parent_path()); - - // The extracted root directory name must match the shard index - if (!is_directory(shardDir)) - { - JLOG(j_.error()) << "Shard " << shardIndex - << " mismatches archive shard directory"; - return; - } - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what(); - return; - } - - // Import the shard into the shard store - if (!app_.getShardStore()->importShard(shardIndex, shardDir)) - { - JLOG(j_.error()) << "Importing shard " << shardIndex; - return; - } - - JLOG(j_.debug()) << "Shard " << shardIndex << " downloaded and imported"; -} - -void -ShardArchiveHandler::remove(std::lock_guard const&) -{ - verificationScheduler_.reset(); - - auto const shardIndex{archives_.begin()->first}; - app_.getShardStore()->removePreShard(shardIndex); - archives_.erase(shardIndex); - - deleteFromArchiveDB(*sqlDB_, shardIndex); - - auto const dstDir{downloadDir_ / std::to_string(shardIndex)}; - try - { - remove_all(dstDir); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what(); - } -} - -void -ShardArchiveHandler::doRelease(std::lock_guard const&) -{ - timer_.cancel(); - for (auto const& ar : archives_) - app_.getShardStore()->removePreShard(ar.first); - archives_.clear(); - - dropArchiveDB(*sqlDB_); - - sqlDB_.reset(); - - // Remove temp root download directory - try - { - remove_all(downloadDir_); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "exception: " << e.what() - << " in function: " << __func__; - } - - downloader_.reset(); - process_ = false; -} - -bool -ShardArchiveHandler::onClosureFailed( - std::string const& errorMsg, - std::lock_guard const& lock) -{ - if (stopping_) - return false; - - JLOG(j_.error()) << errorMsg; - - return removeAndProceed(lock); -} - -bool -ShardArchiveHandler::removeAndProceed(std::lock_guard const& lock) -{ - remove(lock); - return next(lock); -} - -RecoveryHandler::RecoveryHandler(Application& app) : ShardArchiveHandler(app) -{ -} - -} // namespace RPC -} // namespace ripple diff --git a/src/ripple/rpc/impl/ShardVerificationScheduler.cpp b/src/ripple/rpc/impl/ShardVerificationScheduler.cpp deleted file mode 100644 index ad6b6df7b5c..00000000000 --- a/src/ripple/rpc/impl/ShardVerificationScheduler.cpp +++ /dev/null @@ -1,68 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include - -namespace ripple { -namespace RPC { - -ShardVerificationScheduler::ShardVerificationScheduler( - std::chrono::seconds retryInterval, - std::uint32_t maxAttempts) - : retryInterval_( - (retryInterval == std::chrono::seconds(0) ? defaultRetryInterval_ - : retryInterval)) - , maxAttempts_(maxAttempts == 0 ? defaultmaxAttempts_ : maxAttempts) -{ -} - -bool -ShardVerificationScheduler::retry( - Application& app, - bool shouldHaveHash, - retryFunction f) -{ - if (numAttempts_ >= maxAttempts_) - return false; - - // Retry attempts only count when we - // have a validated ledger with a - // sequence later than the shard's - // last ledger. - if (shouldHaveHash) - ++numAttempts_; - - if (!timer_) - timer_ = std::make_unique(app.getIOService()); - - timer_->expires_from_now(retryInterval_); - timer_->async_wait(f); - - return true; -} - -void -ShardVerificationScheduler::reset() -{ - numAttempts_ = 0; -} - -} // namespace RPC -} // namespace ripple diff --git a/src/ripple/shamap/ShardFamily.h b/src/ripple/shamap/ShardFamily.h deleted file mode 100644 index de809cf589c..00000000000 --- a/src/ripple/shamap/ShardFamily.h +++ /dev/null @@ -1,125 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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. -*/ -//============================================================================== - -#ifndef RIPPLE_SHAMAP_SHARDFAMILY_H_INCLUDED -#define RIPPLE_SHAMAP_SHARDFAMILY_H_INCLUDED - -#include -#include - -namespace ripple { - -class Application; - -class ShardFamily : public Family -{ -public: - ShardFamily() = delete; - ShardFamily(ShardFamily const&) = delete; - ShardFamily(ShardFamily&&) = delete; - - ShardFamily& - operator=(ShardFamily const&) = delete; - - ShardFamily& - operator=(ShardFamily&&) = delete; - - ShardFamily(Application& app, CollectorManager& cm); - - NodeStore::Database& - db() override - { - return db_; - } - - NodeStore::Database const& - db() const override - { - return db_; - } - - beast::Journal const& - journal() override - { - return j_; - } - - bool - isShardBacked() const override - { - return true; - } - - std::shared_ptr - getFullBelowCache(std::uint32_t ledgerSeq) override; - - /** Return the number of entries in the cache */ - int - getFullBelowCacheSize(); - - std::shared_ptr - getTreeNodeCache(std::uint32_t ledgerSeq) override; - - /** Return a pair where the first item is the number of items cached - and the second item is the number of entries in the cached - */ - std::pair - getTreeNodeCacheSize(); - - void - sweep() override; - - void - reset() override; - - void - missingNodeAcquireBySeq(std::uint32_t seq, uint256 const& nodeHash) - override; - - void - missingNodeAcquireByHash(uint256 const& hash, std::uint32_t seq) override - { - acquire(hash, seq); - } - -private: - Application& app_; - NodeStore::Database& db_; - CollectorManager& cm_; - beast::Journal const j_; - - std::unordered_map> fbCache_; - std::mutex fbCacheMutex_; - - std::unordered_map> tnCache_; - std::mutex tnCacheMutex_; - int const tnTargetSize_; - std::chrono::seconds const tnTargetAge_; - - // Missing node handler - LedgerIndex maxSeq_{0}; - std::mutex maxSeqMutex_; - - void - acquire(uint256 const& hash, std::uint32_t seq); -}; - -} // namespace ripple - -#endif diff --git a/src/ripple/shamap/impl/ShardFamily.cpp b/src/ripple/shamap/impl/ShardFamily.cpp deleted file mode 100644 index f22d4152e2b..00000000000 --- a/src/ripple/shamap/impl/ShardFamily.cpp +++ /dev/null @@ -1,198 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include - -namespace ripple { - -static NodeStore::Database& -getShardStore(Application& app) -{ - auto const dbPtr = app.getShardStore(); - assert(dbPtr); - return *dbPtr; -} - -ShardFamily::ShardFamily(Application& app, CollectorManager& cm) - : app_(app) - , db_(getShardStore(app)) - , cm_(cm) - , j_(app.journal("ShardFamily")) - , tnTargetSize_(app.config().getValueFor(SizedItem::treeCacheSize, 0)) - , tnTargetAge_(app.config().getValueFor(SizedItem::treeCacheAge, 0)) -{ -} - -std::shared_ptr -ShardFamily::getFullBelowCache(std::uint32_t ledgerSeq) -{ - auto const shardIndex{app_.getShardStore()->seqToShardIndex(ledgerSeq)}; - std::lock_guard lock(fbCacheMutex_); - if (auto const it{fbCache_.find(shardIndex)}; it != fbCache_.end()) - return it->second; - - // Create a cache for the corresponding shard - auto fbCache{std::make_shared( - "Shard family full below cache shard " + std::to_string(shardIndex), - stopwatch(), - j_, - cm_.collector(), - fullBelowTargetSize, - fullBelowExpiration)}; - return fbCache_.emplace(shardIndex, std::move(fbCache)).first->second; -} - -int -ShardFamily::getFullBelowCacheSize() -{ - size_t sz{0}; - std::lock_guard lock(fbCacheMutex_); - for (auto const& e : fbCache_) - sz += e.second->size(); - return sz; -} - -std::shared_ptr -ShardFamily::getTreeNodeCache(std::uint32_t ledgerSeq) -{ - auto const shardIndex{app_.getShardStore()->seqToShardIndex(ledgerSeq)}; - std::lock_guard lock(tnCacheMutex_); - if (auto const it{tnCache_.find(shardIndex)}; it != tnCache_.end()) - return it->second; - - // Create a cache for the corresponding shard - auto tnCache{std::make_shared( - "Shard family tree node cache shard " + std::to_string(shardIndex), - tnTargetSize_, - tnTargetAge_, - stopwatch(), - j_)}; - return tnCache_.emplace(shardIndex, std::move(tnCache)).first->second; -} - -std::pair -ShardFamily::getTreeNodeCacheSize() -{ - int cacheSz{0}; - int trackSz{0}; - std::lock_guard lock(tnCacheMutex_); - for (auto const& e : tnCache_) - { - cacheSz += e.second->getCacheSize(); - trackSz += e.second->getTrackSize(); - } - return {cacheSz, trackSz}; -} - -void -ShardFamily::sweep() -{ - { - std::lock_guard lock(fbCacheMutex_); - for (auto it = fbCache_.cbegin(); it != fbCache_.cend();) - { - it->second->sweep(); - - // Remove cache if empty - if (it->second->size() == 0) - it = fbCache_.erase(it); - else - ++it; - } - } - - std::lock_guard lock(tnCacheMutex_); - for (auto it = tnCache_.cbegin(); it != tnCache_.cend();) - { - it->second->sweep(); - - // Remove cache if empty - if (it->second->getTrackSize() == 0) - it = tnCache_.erase(it); - else - ++it; - } -} - -void -ShardFamily::reset() -{ - { - std::lock_guard lock(maxSeqMutex_); - maxSeq_ = 0; - } - - { - std::lock_guard lock(fbCacheMutex_); - fbCache_.clear(); - } - - std::lock_guard lock(tnCacheMutex_); - tnCache_.clear(); -} - -void -ShardFamily::missingNodeAcquireBySeq(std::uint32_t seq, uint256 const& nodeHash) -{ - std::ignore = nodeHash; - JLOG(j_.error()) << "Missing node in ledger sequence " << seq; - - std::unique_lock lock(maxSeqMutex_); - if (maxSeq_ == 0) - { - maxSeq_ = seq; - - do - { - // Try to acquire the most recent missing ledger - seq = maxSeq_; - - lock.unlock(); - - // This can invoke the missing node handler - acquire(app_.getLedgerMaster().getHashBySeq(seq), seq); - - lock.lock(); - } while (maxSeq_ != seq); - } - else if (maxSeq_ < seq) - { - // We found a more recent ledger with a missing node - maxSeq_ = seq; - } -} - -void -ShardFamily::acquire(uint256 const& hash, std::uint32_t seq) -{ - if (hash.isNonZero()) - { - JLOG(j_.error()) << "Missing node in " << to_string(hash); - - app_.getInboundLedgers().acquire( - hash, seq, InboundLedger::Reason::SHARD); - } -} - -} // namespace ripple diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index e230ed4d3c5..058cdfd1d2d 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp new file mode 100644 index 00000000000..c547a537bfb --- /dev/null +++ b/src/test/app/AMMClawback_test.cpp @@ -0,0 +1,1794 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace ripple { +namespace test { +class AMMClawback_test : public jtx::AMMTest +{ + void + testInvalidRequest(FeatureBitset features) + { + testcase("test invalid request"); + using namespace jtx; + + // Test if holder does not exist. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(100000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + env.trust(USD(10000), alice); + env(pay(gw, alice, gw["USD"](100))); + + AMM amm(env, alice, XRP(100), USD(100)); + env.close(); + + env(amm::ammClawback( + gw, Account("unknown"), USD, XRP, std::nullopt), + ter(terNO_ACCOUNT)); + } + + // Test if asset pair provided does not exist. This should + // return terNO_AMM error. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(100000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(10000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + // Withdraw all the tokens from the AMMAccount. + // The AMMAccount will be auto deleted. + AMM amm(env, gw, XRP(100), USD(100)); + amm.withdrawAll(gw); + BEAST_EXPECT(!amm.ammExists()); + env.close(); + + // The AMM account does not exist at all now. + // It should return terNO_AMM error. + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + ter(terNO_AMM)); + } + + // Test if the issuer field and holder field is the same. This should + // return temMALFORMED error. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // Issuer can not clawback from himself. + env(amm::ammClawback(gw, gw, USD, XRP, std::nullopt), + ter(temMALFORMED)); + + // Holder can not clawback from himself. + env(amm::ammClawback(alice, alice, USD, XRP, std::nullopt), + ter(temMALFORMED)); + } + + // Test if the Asset field matches the Account field. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // The Asset's issuer field is alice, while the Account field is gw. + // This should return temMALFORMED because they do not match. + env(amm::ammClawback( + gw, + alice, + Issue{gw["USD"].currency, alice.id()}, + XRP, + std::nullopt), + ter(temMALFORMED)); + } + + // Test if the Amount field matches the Asset field. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // The Asset's issuer subfield is gw account and Amount's issuer + // subfield is alice account. Return temBAD_AMOUNT because + // they do not match. + env(amm::ammClawback( + gw, + alice, + USD, + XRP, + STAmount{Issue{gw["USD"].currency, alice.id()}, 1}), + ter(temBAD_AMOUNT)); + } + + // Test if the Amount is invalid, which is less than zero. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // Return temBAD_AMOUNT if the Amount value is less than 0. + env(amm::ammClawback( + gw, + alice, + USD, + XRP, + STAmount{Issue{gw["USD"].currency, gw.id()}, -1}), + ter(temBAD_AMOUNT)); + + // Return temBAD_AMOUNT if the Amount value is 0. + env(amm::ammClawback( + gw, + alice, + USD, + XRP, + STAmount{Issue{gw["USD"].currency, gw.id()}, 0}), + ter(temBAD_AMOUNT)); + } + + // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback + // transaction is prohibited. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + env.require(balance(alice, gw["USD"](100))); + env.require(balance(gw, alice["USD"](-100))); + + // gw creates AMM pool of XRP/USD. + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // If asfAllowTrustLineClawback is not set, the issuer is not + // allowed to send the AMMClawback transaction. + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + ter(tecNO_PERMISSION)); + } + + // Test invalid flag. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // Return temINVALID_FLAG when providing invalid flag. + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + txflags(tfTwoAssetIfEmpty), + ter(temINVALID_FLAG)); + } + + // Test if tfClawTwoAssets is set when the two assets in the AMM pool + // are not issued by the same issuer. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 100 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + // gw creates AMM pool of XRP/USD. + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + + // Return temINVALID_FLAG because the issuer set tfClawTwoAssets, + // but the issuer only issues USD in the pool. The issuer is not + // allowed to set tfClawTwoAssets flag if he did not issue both + // assets in the pool. + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + txflags(tfClawTwoAssets), + ter(temINVALID_FLAG)); + } + + // Test clawing back XRP is being prohibited. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + + // Alice creates AMM pool of XRP/USD. + AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + // Clawback XRP is prohibited. + env(amm::ammClawback(gw, alice, XRP, USD, std::nullopt), + ter(temMALFORMED)); + } + } + + void + testFeatureDisabled(FeatureBitset features) + { + testcase("test featureAMMClawback is not enabled."); + using namespace jtx; + if (!features[featureAMMClawback]) + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + + // When featureAMMClawback is not enabled, AMMClawback is disabled. + // Because when featureAMMClawback is disabled, we can not create + // amm account, call amm::ammClawback directly for testing purpose. + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + ter(temDISABLED)); + } + } + + void + testAMMClawbackSpecificAmount(FeatureBitset features) + { + testcase("test AMMClawback specific amount"); + using namespace jtx; + + // Test AMMClawback for USD/EUR pool. The assets are issued by different + // issuer. Claw back USD, and EUR goes back to the holder. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + env.require(balance(gw, alice["USD"](-3000))); + env.require(balance(alice, gw["USD"](3000))); + + // gw2 issues 3000 EUR to Alice. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(3000))); + env.close(); + env.require(balance(gw2, alice["EUR"](-3000))); + env.require(balance(alice, gw2["EUR"](3000))); + + // Alice creates AMM pool of EUR/USD. + AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(2000), EUR(1000), IOUAmount{1414213562373095, -12})); + + // gw clawback 1000 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice's initial balance for USD is 3000 USD. Alice deposited 2000 + // USD into the pool, then she has 1000 USD. And 1000 USD was clawed + // back from the AMM pool, so she still has 1000 USD. + env.require(balance(gw, alice["USD"](-1000))); + env.require(balance(alice, gw["USD"](1000))); + + // Alice's initial balance for EUR is 3000 EUR. Alice deposited 1000 + // EUR into the pool, 500 EUR was withdrawn proportionally. So she + // has 2500 EUR now. + env.require(balance(gw2, alice["EUR"](-2500))); + env.require(balance(alice, gw2["EUR"](2500))); + + // 1000 USD and 500 EUR was withdrawn from the AMM pool, so the + // current balance is 1000 USD and 500 EUR. + BEAST_EXPECT(amm.expectBalances( + USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); + + // Alice has half of its initial lptokens Left. + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13})); + + // gw clawback another 1000 USD from the AMM pool. The AMM pool will + // be empty and get deleted. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice should still has 1000 USD because gw clawed back from the + // AMM pool. + env.require(balance(gw, alice["USD"](-1000))); + env.require(balance(alice, gw["USD"](1000))); + + // Alice should has 3000 EUR now because another 500 EUR was + // withdrawn. + env.require(balance(gw2, alice["EUR"](-3000))); + env.require(balance(alice, gw2["EUR"](3000))); + + // amm is automatically deleted. + BEAST_EXPECT(!amm.ammExists()); + } + + // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back + // to the holder. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + env.require(balance(gw, alice["USD"](-3000))); + env.require(balance(alice, gw["USD"](3000))); + + // Alice creates AMM pool of XRP/USD. + AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(2000), XRP(1000), IOUAmount{1414213562373095, -9})); + + auto aliceXrpBalance = env.balance(alice, XRP); + + // gw clawback 1000 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice's initial balance for USD is 3000 USD. Alice deposited 2000 + // USD into the pool, then she has 1000 USD. And 1000 USD was clawed + // back from the AMM pool, so she still has 1000 USD. + env.require(balance(gw, alice["USD"](-1000))); + env.require(balance(alice, gw["USD"](1000))); + + // Alice will get 500 XRP back. + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500))); + + // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the + // current balance is 1000 USD and 500 XRP. + BEAST_EXPECT(amm.expectBalances( + USD(1000), XRP(500), IOUAmount{7071067811865475, -10})); + + // Alice has half of its initial lptokens Left. + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865475, -10})); + + // gw clawback another 1000 USD from the AMM pool. The AMM pool will + // be empty and get deleted. + env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice should still has 1000 USD because gw clawed back from the + // AMM pool. + env.require(balance(gw, alice["USD"](-1000))); + env.require(balance(alice, gw["USD"](1000))); + + // Alice will get another 1000 XRP back. + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + + // amm is automatically deleted. + BEAST_EXPECT(!amm.ammExists()); + } + } + + void + testAMMClawbackExceedBalance(FeatureBitset features) + { + testcase( + "test AMMClawback specific amount which exceeds the current " + "balance"); + using namespace jtx; + + // Test AMMClawback for USD/EUR pool. The assets are issued by different + // issuer. Claw back USD for multiple times, and EUR goes back to the + // holder. The last AMMClawback transaction exceeds the holder's USD + // balance in AMM pool. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 6000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(6000))); + env.close(); + env.require(balance(alice, gw["USD"](6000))); + + // gw2 issues 6000 EUR to Alice. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(6000))); + env.close(); + env.require(balance(alice, gw2["EUR"](6000))); + + // Alice creates AMM pool of EUR/USD + AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + + // gw clawback 1000 USD from the AMM pool + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice's initial balance for USD is 6000 USD. Alice deposited 4000 + // USD into the pool, then she has 2000 USD. And 1000 USD was clawed + // back from the AMM pool, so she still has 2000 USD. + env.require(balance(alice, gw["USD"](2000))); + + // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000 + // EUR into the pool, 1250 EUR was withdrawn proportionally. So she + // has 2500 EUR now. + env.require(balance(alice, gw2["EUR"](2250))); + + // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the + // current balance is 3000 USD and 3750 EUR. + BEAST_EXPECT(amm.expectBalances( + USD(3000), EUR(3750), IOUAmount{3354101966249685, -12})); + + // Alice has 3/4 of its initial lptokens Left. + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12})); + + // gw clawback another 500 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, EUR, USD(500)), + ter(tesSUCCESS)); + env.close(); + + // Alice should still has 2000 USD because gw clawed back from the + // AMM pool. + env.require(balance(alice, gw["USD"](2000))); + + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2500000000000001), -12}, + STAmount{EUR, UINT64_C(3125000000000001), -12}, + IOUAmount{2795084971874738, -12})); + + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2874999999999999), -12)); + + // gw clawback small amount, 1 USD. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS)); + env.close(); + + // Another 1 USD / 1.25 EUR was withdrawn. + env.require(balance(alice, gw["USD"](2000))); + + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2499000000000002), -12}, + STAmount{EUR, UINT64_C(3123750000000002), -12}, + IOUAmount{2793966937885989, -12})); + + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2876249999999998), -12)); + + // gw clawback 4000 USD, exceeding the current balance. We + // will clawback all. + env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](2000))); + + // All alice's EUR in the pool goes back to alice. + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(6000000000000000), -12)); + + // amm is automatically deleted. + BEAST_EXPECT(!amm.ammExists()); + } + + // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times, + // and XRP goes back to the holder. The last AMMClawback transaction + // exceeds the holder's USD balance in AMM pool. In this case, gw + // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2 + // creates the AMM pool EUR/XRP. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + Account bob{"bob"}; + env.fund(XRP(1000000), gw, gw2, alice, bob); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw2 sets asfAllowTrustLineClawback. + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw2, asfAllowTrustLineClawback)); + + // gw issues 6000 USD to Alice and 5000 USD to Bob. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(6000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(5000))); + env.close(); + + // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(5000))); + env.trust(EUR(100000), bob); + env(pay(gw2, bob, EUR(4000))); + env.close(); + + // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD. + AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS)); + BEAST_EXPECT(amm.expectBalances( + USD(1000), XRP(2000), IOUAmount{1414213562373095, -9})); + amm.deposit(alice, USD(1000), XRP(2000)); + BEAST_EXPECT(amm.expectBalances( + USD(2000), XRP(4000), IOUAmount{2828427124746190, -9})); + amm.deposit(bob, USD(1000), XRP(2000)); + BEAST_EXPECT(amm.expectBalances( + USD(3000), XRP(6000), IOUAmount{4242640687119285, -9})); + env.close(); + + // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR. + AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS)); + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + amm2.deposit(alice, EUR(1000), XRP(3000)); + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + amm2.deposit(bob, EUR(1000), XRP(3000)); + BEAST_EXPECT(amm2.expectBalances( + EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9})); + env.close(); + + auto aliceXrpBalance = env.balance(alice, XRP); + auto bobXrpBalance = env.balance(bob, XRP); + + // gw clawback 500 USD from alice in amm + env(amm::ammClawback(gw, alice, USD, XRP, USD(500)), + ter(tesSUCCESS)); + env.close(); + + // Alice's initial balance for USD is 6000 USD. Alice deposited 1000 + // USD into the pool, then she has 5000 USD. And 500 USD was clawed + // back from the AMM pool, so she still has 5000 USD. + env.require(balance(alice, gw["USD"](5000))); + + // Bob's balance is not changed. + env.require(balance(bob, gw["USD"](4000))); + + // Alice gets 1000 XRP back. + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + + BEAST_EXPECT(amm.expectBalances( + USD(2500), XRP(5000), IOUAmount{3535533905932738, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); + + // gw clawback 10 USD from bob in amm. + env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](5000))); + env.require(balance(bob, gw["USD"](4000))); + + // Bob gets 20 XRP back. + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2490000000000001), -12}, + XRP(4980), + IOUAmount{3521391770309008, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + + // gw2 clawback 200 EUR from amm2. + env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw2["EUR"](4000))); + env.require(balance(bob, gw2["EUR"](3000))); + + // Alice gets 600 XRP back. + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000) + XRP(600))); + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9})); + BEAST_EXPECT( + amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9})); + BEAST_EXPECT( + amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9})); + + // gw claw back 1000 USD from alice in amm, which exceeds alice's + // balance. This will clawback all the remaining LP tokens of alice + // (corresponding 500 USD / 1000 XRP). + env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](5000))); + env.require(balance(bob, gw["USD"](4000))); + + // Alice gets 1000 XRP back. + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(1990000000000001), -12}, + XRP(3980), + IOUAmount{2814284989122460, -9})); + + // gw clawback 1000 USD from bob in amm, which also exceeds bob's + // balance in amm. All bob's lptoken in amm will be consumed, which + // corresponds to 990 USD / 1980 XRP + env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](5000))); + env.require(balance(bob, gw["USD"](4000))); + + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + + // Now neither alice nor bob has any lptoken in amm. + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + + // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's + // balance. All alice's lptokens will be consumed, which corresponds + // to 800EUR / 2400 XRP. + env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw2["EUR"](4000))); + env.require(balance(bob, gw2["EUR"](3000))); + + // Alice gets another 2400 XRP back, bob's XRP balance remains the + // same. + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + + // Alice now does not have any lptoken in amm2 + BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); + + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + + // gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's + // balance. All bob's lptokens will be consumed, which corresponds + // to 1000EUR / 3000 XRP. + env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw2["EUR"](4000))); + env.require(balance(bob, gw2["EUR"](3000))); + + // Bob gets another 3000 XRP back. Alice's XRP balance remains the + // same. + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000))); + + // Neither alice nor bob has any lptoken in amm2 + BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); + + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + } + } + + void + testAMMClawbackAll(FeatureBitset features) + { + testcase("test AMMClawback all the tokens in the AMM pool"); + using namespace jtx; + + // Test AMMClawback for USD/EUR pool. The assets are issued by different + // issuer. Claw back all the USD for different users. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + Account bob{"bob"}; + Account carol{"carol"}; + env.fund(XRP(1000000), gw, gw2, alice, bob, carol); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw2 sets asfAllowTrustLineClawback. + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw2, asfAllowTrustLineClawback)); + + // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD + // to Carol. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(6000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(5000))); + env.trust(USD(100000), carol); + env(pay(gw, carol, USD(4000))); + env.close(); + + // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000 + // EUR to Carol. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(6000))); + env.trust(EUR(100000), bob); + env(pay(gw2, bob, EUR(5000))); + env.trust(EUR(100000), carol); + env(pay(gw2, carol, EUR(4000))); + env.close(); + + // Alice creates AMM pool of EUR/USD + AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + amm.deposit(bob, USD(2000), EUR(2500)); + BEAST_EXPECT(amm.expectBalances( + USD(6000), EUR(7500), IOUAmount{6708203932499370, -12})); + amm.deposit(carol, USD(1000), EUR(1250)); + BEAST_EXPECT(amm.expectBalances( + USD(7000), EUR(8750), IOUAmount{7826237921249265, -12})); + + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12})); + BEAST_EXPECT( + amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12})); + + env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, gw2["EUR"](1000))); + env.require(balance(bob, gw["USD"](3000))); + env.require(balance(bob, gw2["EUR"](2500))); + env.require(balance(carol, gw["USD"](3000))); + env.require(balance(carol, gw2["EUR"](2750))); + + // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR) + env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(4999999999999999), -12}, + STAmount{EUR, UINT64_C(6249999999999999), -12}, + IOUAmount{5590169943749475, -12})); + + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + BEAST_EXPECT( + amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12})); + + // Bob will get 2500 EUR back. + env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, gw2["EUR"](1000))); + BEAST_EXPECT( + env.balance(bob, USD) == + STAmount(USD, UINT64_C(3000000000000000), -12)); + + BEAST_EXPECT( + env.balance(bob, EUR) == + STAmount(EUR, UINT64_C(5000000000000001), -12)); + env.require(balance(carol, gw["USD"](3000))); + env.require(balance(carol, gw2["EUR"](2750))); + + // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR) + env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(3999999999999999), -12}, + STAmount{EUR, UINT64_C(4999999999999999), -12}, + IOUAmount{4472135954999580, -12})); + + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0))); + + // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR) + env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(carol, gw2["EUR"](2750))); + env.require(balance(carol, gw["USD"](4000))); + BEAST_EXPECT(!amm.ammExists()); + } + + // Test AMMClawback for USD/XRP pool. Claw back all the USD for + // different users. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + Account bob{"bob"}; + env.fund(XRP(1000000), gw, alice, bob); + env.close(); + + // gw sets asfAllowTrustLineClawback + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 600000 USD to Alice and 500000 USD to Bob. + auto const USD = gw["USD"]; + env.trust(USD(1000000), alice); + env(pay(gw, alice, USD(600000))); + env.trust(USD(1000000), bob); + env(pay(gw, bob, USD(500000))); + env.close(); + + // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD. + AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS)); + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + amm.deposit(alice, USD(1000), XRP(200)); + BEAST_EXPECT(amm.expectBalances( + USD(11000), XRP(2200), IOUAmount{4919349550499538, -9})); + amm.deposit(bob, USD(2000), XRP(400)); + BEAST_EXPECT(amm.expectBalances( + USD(13000), XRP(2600), IOUAmount{5813776741499453, -9})); + env.close(); + + auto aliceXrpBalance = env.balance(alice, XRP); + auto bobXrpBalance = env.balance(bob, XRP); + + // gw clawback all alice's USD in amm. (1000 USD / 200 XRP) + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(12000), XRP(2400), IOUAmount{5366563145999495, -9})); + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200))); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + + // gw clawback all bob's USD in amm. (2000 USD / 400 XRP) + env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400))); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + } + } + + void + testAMMClawbackSameIssuerAssets(FeatureBitset features) + { + testcase( + "test AMMClawback from AMM pool with assets having the same " + "issuer"); + using namespace jtx; + + // Test AMMClawback for USD/EUR pool. The assets are issued by different + // issuer. Claw back all the USD for different users. + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + Account bob{"bob"}; + Account carol{"carol"}; + env.fund(XRP(1000000), gw, alice, bob, carol); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(10000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(9000))); + env.trust(USD(100000), carol); + env(pay(gw, carol, USD(8000))); + env.close(); + + auto const EUR = gw["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw, alice, EUR(10000))); + env.trust(EUR(100000), bob); + env(pay(gw, bob, EUR(9000))); + env.trust(EUR(100000), carol); + env(pay(gw, carol, EUR(8000))); + env.close(); + + AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000))); + amm.deposit(bob, USD(4000), EUR(1000)); + BEAST_EXPECT( + amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); + amm.deposit(carol, USD(2000), EUR(500)); + BEAST_EXPECT( + amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); + + // gw clawback 1000 USD from carol. + env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + // 250 EUR goes back to carol. + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + + // gw clawback 1000 USD from bob with tfClawTwoAssets flag. + // then the corresponding EUR will also be clawed back + // by gw. + env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)), + txflags(tfClawTwoAssets), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + // 250 EUR did not go back to bob because tfClawTwoAssets is set. + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + + // gw clawback all USD from alice and set tfClawTwoAssets. + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + } + + void + testAMMClawbackSameCurrency(FeatureBitset features) + { + testcase( + "test AMMClawback from AMM pool with assets having the same " + "currency, but from different issuer"); + using namespace jtx; + + // Test AMMClawback for USD/EUR pool. The assets are issued by different + // issuer. Claw back all the USD for different users. + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + Account bob{"bob"}; + env.fund(XRP(1000000), gw, gw2, alice, bob); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw2 sets asfAllowTrustLineClawback. + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw2, asfAllowTrustLineClawback)); + + env.trust(gw["USD"](100000), alice); + env(pay(gw, alice, gw["USD"](8000))); + env.trust(gw["USD"](100000), bob); + env(pay(gw, bob, gw["USD"](7000))); + + env.trust(gw2["USD"](100000), alice); + env(pay(gw2, alice, gw2["USD"](6000))); + env.trust(gw2["USD"](100000), bob); + env(pay(gw2, bob, gw2["USD"](5000))); + env.close(); + + AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + gw["USD"](1000), + gw2["USD"](1500), + IOUAmount{1224744871391589, -12})); + amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000)); + BEAST_EXPECT(amm.expectBalances( + gw["USD"](3000), + gw2["USD"](4500), + IOUAmount{3674234614174767, -12})); + + // Issuer does not match with asset. + env(amm::ammClawback( + gw, + alice, + gw2["USD"], + gw["USD"], + STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}), + ter(temMALFORMED)); + + // gw2 clawback 500 gw2[USD] from alice. + env(amm::ammClawback( + gw2, + alice, + gw2["USD"], + gw["USD"], + STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + STAmount{gw["USD"], UINT64_C(2666666666666667), -12}, + gw2["USD"](4000), + IOUAmount{3265986323710904, -12})); + + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12})); + BEAST_EXPECT( + env.balance(alice, gw["USD"]) == + STAmount(gw["USD"], UINT64_C(7333333333333333), -12)); + BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500)); + BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000)); + BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000)); + + // gw clawback all gw["USD"] from bob. + env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + STAmount{gw["USD"], UINT64_C(6666666666666670), -13}, + gw2["USD"](1000), + IOUAmount{8164965809277260, -13})); + + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + BEAST_EXPECT( + env.balance(alice, gw["USD"]) == + STAmount(gw["USD"], UINT64_C(7333333333333333), -12)); + BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500)); + BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000)); + // Bob gets 3000 gw2["USD"] back and now his balance is 5000. + BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000)); + } + + void + testAMMClawbackIssuesEachOther(FeatureBitset features) + { + testcase("test AMMClawback when issuing token for each other"); + using namespace jtx; + + // gw and gw2 issues token for each other. Test AMMClawback from + // each other. + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw2 sets asfAllowTrustLineClawback. + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw2, asfAllowTrustLineClawback)); + + auto const USD = gw["USD"]; + env.trust(USD(100000), gw2); + env(pay(gw, gw2, USD(5000))); + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(5000))); + + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), gw); + env(pay(gw2, gw, EUR(6000))); + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(6000))); + env.close(); + + AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(1000), EUR(2000), IOUAmount{1414213562373095, -12})); + + amm.deposit(gw2, USD(2000), EUR(4000)); + BEAST_EXPECT(amm.expectBalances( + USD(3000), EUR(6000), IOUAmount{4242640687119285, -12})); + + amm.deposit(alice, USD(3000), EUR(6000)); + BEAST_EXPECT(amm.expectBalances( + USD(6000), EUR(12000), IOUAmount{8485281374238570, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); + + // gw claws back 1000 USD from gw2. + env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); + + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000)); + BEAST_EXPECT(env.balance(gw2, USD) == USD(3000)); + + // gw2 claws back 1000 EUR from gw. + env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(4500), + STAmount(EUR, UINT64_C(9000000000000001), -12), + IOUAmount{6363961030678928, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); + + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000)); + BEAST_EXPECT(env.balance(gw2, USD) == USD(3000)); + + // gw2 claws back 4000 EUR from alice. + env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + USD(2500), + STAmount(EUR, UINT64_C(5000000000000001), -12), + IOUAmount{3535533905932738, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); + + BEAST_EXPECT(env.balance(alice, USD) == USD(4000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000)); + BEAST_EXPECT(env.balance(gw2, USD) == USD(3000)); + } + + void + testNotHoldingLptoken(FeatureBitset features) + { + testcase( + "test AMMClawback from account which does not own any lptoken in " + "the pool"); + using namespace jtx; + + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(5000))); + + AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS)); + env.close(); + + // Alice did not deposit in the amm pool. So AMMClawback from Alice + // will fail. + env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)), + ter(tecAMM_BALANCE)); + } + + void + testAssetFrozen(FeatureBitset features) + { + testcase("test assets frozen"); + using namespace jtx; + + // test individually frozen trustline. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + env.require(balance(alice, gw["USD"](3000))); + + // gw2 issues 3000 EUR to Alice. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(3000))); + env.close(); + env.require(balance(alice, gw2["EUR"](3000))); + + // Alice creates AMM pool of EUR/USD. + AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(2000), EUR(1000), IOUAmount{1414213562373095, -12})); + + // freeze trustline + env(trust(gw, alice["USD"](0), tfSetFreeze)); + env.close(); + + // gw clawback 1000 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, gw2["EUR"](2500))); + BEAST_EXPECT(amm.expectBalances( + USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); + + // Alice has half of its initial lptokens Left. + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13})); + + // gw clawback another 1000 USD from the AMM pool. The AMM pool will + // be empty and get deleted. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + // Alice should still has 1000 USD because gw clawed back from the + // AMM pool. + env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, gw2["EUR"](3000))); + + // amm is automatically deleted. + BEAST_EXPECT(!amm.ammExists()); + } + + // test individually frozen trustline of both USD and EUR currency. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + env.require(balance(alice, gw["USD"](3000))); + + // gw2 issues 3000 EUR to Alice. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(3000))); + env.close(); + env.require(balance(alice, gw2["EUR"](3000))); + + // Alice creates AMM pool of EUR/USD. + AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(2000), EUR(1000), IOUAmount{1414213562373095, -12})); + + // freeze trustlines + env(trust(gw, alice["USD"](0), tfSetFreeze)); + env(trust(gw2, alice["EUR"](0), tfSetFreeze)); + env.close(); + + // gw clawback 1000 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, gw2["EUR"](2500))); + BEAST_EXPECT(amm.expectBalances( + USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13})); + } + + // test gw global freeze. + { + Env env(*this, features); + Account gw{"gateway"}; + Account gw2{"gateway2"}; + Account alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 3000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(3000))); + env.close(); + env.require(balance(alice, gw["USD"](3000))); + + // gw2 issues 3000 EUR to Alice. + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(3000))); + env.close(); + env.require(balance(alice, gw2["EUR"](3000))); + + // Alice creates AMM pool of EUR/USD. + AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(2000), EUR(1000), IOUAmount{1414213562373095, -12})); + + // global freeze + env(fset(gw, asfGlobalFreeze)); + env.close(); + + // gw clawback 1000 USD from the AMM pool. + env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + + env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, gw2["EUR"](2500))); + BEAST_EXPECT(amm.expectBalances( + USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13})); + } + + // Test both assets are issued by the same issuer. And issuer sets + // global freeze. + { + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + Account bob{"bob"}; + Account carol{"carol"}; + env.fund(XRP(1000000), gw, alice, bob, carol); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(10000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(9000))); + env.trust(USD(100000), carol); + env(pay(gw, carol, USD(8000))); + env.close(); + + auto const EUR = gw["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw, alice, EUR(10000))); + env.trust(EUR(100000), bob); + env(pay(gw, bob, EUR(9000))); + env.trust(EUR(100000), carol); + env(pay(gw, carol, EUR(8000))); + env.close(); + + AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT( + amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000))); + amm.deposit(bob, USD(4000), EUR(1000)); + BEAST_EXPECT( + amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); + amm.deposit(carol, USD(2000), EUR(500)); + BEAST_EXPECT( + amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); + + // global freeze + env(fset(gw, asfGlobalFreeze)); + env.close(); + + // gw clawback 1000 USD from carol. + env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + // 250 EUR goes back to carol. + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + + // gw clawback 1000 USD from bob with tfClawTwoAssets flag. + // then the corresponding EUR will also be clawed back + // by gw. + env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)), + txflags(tfClawTwoAssets), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + // 250 EUR did not go back to bob because tfClawTwoAssets is set. + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + + // gw clawback all USD from alice and set tfClawTwoAssets. + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets), + ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000))); + + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500))); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500))); + BEAST_EXPECT(env.balance(alice, USD) == USD(2000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); + } + } + + void + testSingleDepositAndClawback(FeatureBitset features) + { + testcase("test single depoit and clawback"); + using namespace jtx; + + // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back + // to the holder. + Env env(*this, features); + Account gw{"gateway"}; + Account alice{"alice"}; + env.fund(XRP(1000000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 1000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + env.require(balance(alice, gw["USD"](1000))); + + // gw creates AMM pool of XRP/USD. + AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000))); + + amm.deposit(alice, USD(400)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + USD(800), XRP(100), IOUAmount{2828427124746190, -10})); + + auto aliceXrpBalance = env.balance(alice, XRP); + + env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5656854249492380), -13), + XRP(70.710678), + IOUAmount(200000))); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(29.289322))); + } + + void + run() override + { + FeatureBitset const all{jtx::supported_amendments()}; + testInvalidRequest(all); + testFeatureDisabled(all - featureAMMClawback); + testAMMClawbackSpecificAmount(all); + testAMMClawbackExceedBalance(all); + testAMMClawbackAll(all); + testAMMClawbackSameIssuerAssets(all); + testAMMClawbackSameCurrency(all); + testAMMClawbackIssuesEachOther(all); + testNotHoldingLptoken(all); + testAssetFrozen(all); + testSingleDepositAndClawback(all); + } +}; +BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 27fb2ce14f5..96053b93b44 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -16,25 +16,25 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index b4abd385257..f1e81132c5e 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -16,23 +16,23 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -416,25 +416,10 @@ struct AMM_test : public jtx::AMMTest AMM ammAlice1( env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE)); } - - // Issuer has clawback enabled - { - Env env(*this); - env.fund(XRP(1'000), gw); - env(fset(gw, asfAllowTrustLineClawback)); - fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct); - env.close(); - AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION)); - AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION)); - env(fclear(gw, asfAllowTrustLineClawback)); - env.close(); - // Can't be cleared - AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION)); - } } void - testInvalidDeposit() + testInvalidDeposit(FeatureBitset features) { testcase("Invalid Deposit"); @@ -869,62 +854,112 @@ struct AMM_test : public jtx::AMMTest }); // Globally frozen asset - testAMM([&](AMM& ammAlice, Env& env) { - env(fset(gw, asfGlobalFreeze)); - // Can deposit non-frozen token - ammAlice.deposit(carol, XRP(100)); - ammAlice.deposit( - carol, - USD(100), - std::nullopt, - std::nullopt, - std::nullopt, - ter(tecFROZEN)); - ammAlice.deposit( - carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.deposit( - carol, - XRP(100), - USD(100), - std::nullopt, - std::nullopt, - ter(tecFROZEN)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + env(fset(gw, asfGlobalFreeze)); + if (!features[featureAMMClawback]) + // If the issuer set global freeze, the holder still can + // deposit the other non-frozen token when AMMClawback is + // not enabled. + ammAlice.deposit(carol, XRP(100)); + else + // If the issuer set global freeze, the holder cannot + // deposit the other non-frozen token when AMMClawback is + // enabled. + ammAlice.deposit( + carol, + XRP(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + carol, + USD(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + carol, + 1'000'000, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + carol, + XRP(100), + USD(100), + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + }, + std::nullopt, + 0, + std::nullopt, + {features}); // Individually frozen (AMM) account - testAMM([&](AMM& ammAlice, Env& env) { - env(trust(gw, carol["USD"](0), tfSetFreeze)); - env.close(); - // Can deposit non-frozen token - ammAlice.deposit(carol, XRP(100)); - ammAlice.deposit( - carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.deposit( - carol, - USD(100), - std::nullopt, - std::nullopt, - std::nullopt, - ter(tecFROZEN)); - env(trust(gw, carol["USD"](0), tfClearFreeze)); - // Individually frozen AMM - env(trust( - gw, - STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, - tfSetFreeze)); - env.close(); - // Can deposit non-frozen token - ammAlice.deposit(carol, XRP(100)); - ammAlice.deposit( - carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.deposit( - carol, - USD(100), - std::nullopt, - std::nullopt, - std::nullopt, - ter(tecFROZEN)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + env(trust(gw, carol["USD"](0), tfSetFreeze)); + env.close(); + if (!features[featureAMMClawback]) + // Can deposit non-frozen token if AMMClawback is not + // enabled + ammAlice.deposit(carol, XRP(100)); + else + // Cannot deposit non-frozen token if the other token is + // frozen when AMMClawback is enabled + ammAlice.deposit( + carol, + XRP(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + + ammAlice.deposit( + carol, + 1'000'000, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + carol, + USD(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + env(trust(gw, carol["USD"](0), tfClearFreeze)); + // Individually frozen AMM + env(trust( + gw, + STAmount{ + Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, + tfSetFreeze)); + env.close(); + // Can deposit non-frozen token + ammAlice.deposit(carol, XRP(100)); + ammAlice.deposit( + carol, + 1'000'000, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + carol, + USD(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + }, + std::nullopt, + 0, + std::nullopt, + {features}); // Individually frozen (AMM) account with IOU/IOU AMM testAMM( @@ -970,6 +1005,44 @@ struct AMM_test : public jtx::AMMTest }, {{USD(20'000), BTC(0.5)}}); + // Deposit unauthorized token. + { + Env env(*this, features); + env.fund(XRP(1000), gw, alice, bob); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth)); + env(trust(alice, gw["USD"](20))); + env.close(); + env(pay(gw, alice, gw["USD"](10))); + env.close(); + env(trust(gw, bob["USD"](100))); + env.close(); + + AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS)); + env.close(); + + if (features[featureAMMClawback]) + // if featureAMMClawback is enabled, bob can not deposit XRP + // because he's not authorized to hold the paired token + // gw["USD"]. + amm.deposit( + bob, + XRP(10), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecNO_AUTH)); + else + amm.deposit( + bob, + XRP(10), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tesSUCCESS)); + } + // Insufficient XRP balance testAMM([&](AMM& ammAlice, Env& env) { env.fund(XRP(1'000), bob); @@ -3618,13 +3691,16 @@ struct AMM_test : public jtx::AMMTest STAmount(USD, UINT64_C(9'970'097277662122), -12), STAmount(EUR, UINT64_C(10'029'99250187452), -11), ammUSD_EUR.tokens())); - BEAST_EXPECT(expectOffers( - env, - alice, - 1, - {{Amounts{ - XRPAmount(30'201'749), - STAmount(USD, UINT64_C(29'90272233787818), -14)}}})); + + // fixReducedOffersV2 changes the expected results slightly. + Amounts const expectedAmounts = + env.closed()->rules().enabled(fixReducedOffersV2) + ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233787816), -14)} + : Amounts{ + XRPAmount(30'201'749), + STAmount(USD, UINT64_C(29'90272233787818), -14)}; + + BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}})); } else { @@ -3632,13 +3708,16 @@ struct AMM_test : public jtx::AMMTest STAmount(USD, UINT64_C(9'970'097277662172), -12), STAmount(EUR, UINT64_C(10'029'99250187452), -11), ammUSD_EUR.tokens())); - BEAST_EXPECT(expectOffers( - env, - alice, - 1, - {{Amounts{ - XRPAmount(30'201'749), - STAmount(USD, UINT64_C(29'9027223378284), -13)}}})); + + // fixReducedOffersV2 changes the expected results slightly. + Amounts const expectedAmounts = + env.closed()->rules().enabled(fixReducedOffersV2) + ? Amounts{XRPAmount(30'201'749), STAmount(USD, UINT64_C(29'90272233782839), -14)} + : Amounts{ + XRPAmount(30'201'749), + STAmount(USD, UINT64_C(29'90272233782840), -14)}; + + BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}})); } // Initial 30,000 + 100 BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100})); @@ -6856,13 +6935,192 @@ struct AMM_test : public jtx::AMMTest } } + void + testAMMClawback(FeatureBitset features) + { + testcase("test clawback from AMM account"); + using namespace jtx; + + // Issuer has clawback enabled + Env env(*this, features); + env.fund(XRP(1'000), gw); + env(fset(gw, asfAllowTrustLineClawback)); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct); + env.close(); + + // If featureAMMClawback is not enabled, AMMCreate is not allowed for + // clawback-enabled issuer + if (!features[featureAMMClawback]) + { + AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION)); + AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION)); + env(fclear(gw, asfAllowTrustLineClawback)); + env.close(); + // Can't be cleared + AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION)); + } + // If featureAMMClawback is enabled, AMMCreate is allowed for + // clawback-enabled issuer. Clawback from the AMM Account is not + // allowed, which will return tecAMM_ACCOUNT. We can only use + // AMMClawback transaction to claw back from AMM Account. + else + { + AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); + AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE)); + + // Construct the amount being clawed back using AMM account. + // By doing this, we make the clawback transaction's Amount field's + // subfield `issuer` to be the AMM account, which means + // we are clawing back from an AMM account. This should return an + // tecAMM_ACCOUNT error because regular Clawback transaction is not + // allowed for clawing back from an AMM account. Please notice the + // `issuer` subfield represents the account being clawed back, which + // is confusing. + Issue usd(USD.issue().currency, amm.ammAccount()); + auto amount = amountFromString(usd, "10"); + env(claw(gw, amount), ter(tecAMM_ACCOUNT)); + } + } + + void + testAMMDepositWithFrozenAssets(FeatureBitset features) + { + testcase("test AMMDeposit with frozen assets"); + using namespace jtx; + + // This lambda function is used to create trustlines + // between gw and alice, and create an AMM account. + // And also test the callback function. + auto testAMMDeposit = [&](Env& env, std::function cb) { + env.fund(XRP(1'000), gw); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct); + env.close(); + AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS)); + env(trust(gw, alice["USD"](0), tfSetFreeze)); + cb(amm); + }; + + // Deposit two assets, one of which is frozen, + // then we should get tecFROZEN error. + { + Env env(*this, features); + testAMMDeposit(env, [&](AMM& amm) { + amm.deposit( + alice, + USD(100), + XRP(100), + std::nullopt, + tfTwoAsset, + ter(tecFROZEN)); + }); + } + + // Deposit one asset, which is the frozen token, + // then we should get tecFROZEN error. + { + Env env(*this, features); + testAMMDeposit(env, [&](AMM& amm) { + amm.deposit( + alice, + USD(100), + std::nullopt, + std::nullopt, + tfSingleAsset, + ter(tecFROZEN)); + }); + } + + if (features[featureAMMClawback]) + { + // Deposit one asset which is not the frozen token, + // but the other asset is frozen. We should get tecFROZEN error + // when feature AMMClawback is enabled. + Env env(*this, features); + testAMMDeposit(env, [&](AMM& amm) { + amm.deposit( + alice, + XRP(100), + std::nullopt, + std::nullopt, + tfSingleAsset, + ter(tecFROZEN)); + }); + } + else + { + // Deposit one asset which is not the frozen token, + // but the other asset is frozen. We will get tecSUCCESS + // when feature AMMClawback is not enabled. + Env env(*this, features); + testAMMDeposit(env, [&](AMM& amm) { + amm.deposit( + alice, + XRP(100), + std::nullopt, + std::nullopt, + tfSingleAsset, + ter(tesSUCCESS)); + }); + } + } + + void + testFixReserveCheckOnWithdrawal(FeatureBitset features) + { + testcase("Fix Reserve Check On Withdrawal"); + using namespace jtx; + + auto const err = features[fixAMMv1_2] ? ter(tecINSUFFICIENT_RESERVE) + : ter(tesSUCCESS); + + auto test = [&](auto&& cb) { + Env env(*this, features); + auto const starting_xrp = + reserve(env, 2) + env.current()->fees().base * 5; + env.fund(starting_xrp, gw); + env.fund(starting_xrp, alice); + env.trust(USD(2'000), alice); + env.close(); + env(pay(gw, alice, USD(2'000))); + env.close(); + AMM amm(env, gw, EUR(1'000), USD(1'000)); + amm.deposit(alice, USD(1)); + cb(amm); + }; + + // Equal withdraw + test([&](AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); }); + + // Equal withdraw with a limit + test([&](AMM& amm) { + amm.withdraw(WithdrawArg{ + .account = alice, + .asset1Out = EUR(0.1), + .asset2Out = USD(0.1), + .err = err}); + amm.withdraw(WithdrawArg{ + .account = alice, + .asset1Out = USD(0.1), + .asset2Out = EUR(0.1), + .err = err}); + }); + + // Single withdraw + test([&](AMM& amm) { + amm.withdraw(WithdrawArg{ + .account = alice, .asset1Out = EUR(0.1), .err = err}); + amm.withdraw(WithdrawArg{.account = alice, .asset1Out = USD(0.1)}); + }); + } + void run() override { FeatureBitset const all{jtx::supported_amendments()}; testInvalidInstance(); testInstanceCreate(); - testInvalidDeposit(); + testInvalidDeposit(all); + testInvalidDeposit(all - featureAMMClawback); testDeposit(); testInvalidWithdraw(); testWithdraw(); @@ -6874,6 +7132,8 @@ struct AMM_test : public jtx::AMMTest testInvalidAMMPayment(); testBasicPaymentEngine(all); testBasicPaymentEngine(all - fixAMMv1_1); + testBasicPaymentEngine(all - fixReducedOffersV2); + testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2); testAMMTokens(); testAmendment(); testFlags(); @@ -6900,6 +7160,14 @@ struct AMM_test : public jtx::AMMTest testFixAMMOfferBlockedByLOB(all - fixAMMv1_1); testLPTokenBalance(all); testLPTokenBalance(all - fixAMMv1_1); + testAMMClawback(all); + testAMMClawback(all - featureAMMClawback); + testAMMClawback(all - fixAMMv1_1 - featureAMMClawback); + testAMMDepositWithFrozenAssets(all); + testAMMDepositWithFrozenAssets(all - featureAMMClawback); + testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback); + testFixReserveCheckOnWithdrawal(all); + testFixReserveCheckOnWithdrawal(all - fixAMMv1_2); } }; diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index fbd631f444a..f8d3cf4692a 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { @@ -912,6 +912,366 @@ class AccountDelete_test : public beast::unit_test::suite env.close(); } + void + testDestinationDepositAuthCredentials() + { + { + testcase( + "Destination Constraints with DepositPreauth and Credentials"); + + using namespace test::jtx; + + Account const alice{"alice"}; + Account const becky{"becky"}; + Account const carol{"carol"}; + Account const daria{"daria"}; + + const char credType[] = "abcd"; + + Env env{*this}; + env.fund(XRP(100000), alice, becky, carol, daria); + env.close(); + + // carol issue credentials for becky + env(credentials::create(becky, carol, credType)); + env.close(); + + // get credentials index + auto const jv = + credentials::ledgerEntry(env, becky, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Close enough ledgers to be able to delete becky's account. + incLgrSeqForAccDel(env, becky); + + auto const acctDelFee{drops(env.current()->fees().increment)}; + + // becky use credentials but they aren't accepted + env(acctdelete(becky, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(tecBAD_CREDENTIALS)); + env.close(); + + { + // alice sets the lsfDepositAuth flag on her account. This + // should prevent becky from deleting her account while using + // alice as the destination. + env(fset(alice, asfDepositAuth)); + env.close(); + } + + // Fail, credentials still not accepted + env(acctdelete(becky, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(tecBAD_CREDENTIALS)); + env.close(); + + // becky accept the credentials + env(credentials::accept(becky, carol, credType)); + env.close(); + + // Fail, credentials doesn’t belong to carol + env(acctdelete(carol, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(tecBAD_CREDENTIALS)); + + // Fail, no depositPreauth for provided credentials + env(acctdelete(becky, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(tecNO_PERMISSION)); + env.close(); + + // alice create DepositPreauth Object + env(deposit::authCredentials(alice, {{carol, credType}})); + env.close(); + + // becky attempts to delete her account, but alice won't take her + // XRP, so the delete is blocked. + env(acctdelete(becky, alice), + fee(acctDelFee), + ter(tecNO_PERMISSION)); + + // becky use empty credentials and can't delete account + env(acctdelete(becky, alice), + fee(acctDelFee), + credentials::ids({}), + ter(temMALFORMED)); + + // becky use bad credentials and can't delete account + env(acctdelete(becky, alice), + credentials::ids( + {"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E" + "A288BE4"}), + fee(acctDelFee), + ter(tecBAD_CREDENTIALS)); + env.close(); + + // becky use credentials and can delete account + env(acctdelete(becky, alice), + credentials::ids({credIdx}), + fee(acctDelFee)); + env.close(); + + { + // check that credential object deleted too + auto const jNoCred = + credentials::ledgerEntry(env, becky, carol, credType); + BEAST_EXPECT( + jNoCred.isObject() && jNoCred.isMember(jss::result) && + jNoCred[jss::result].isMember(jss::error) && + jNoCred[jss::result][jss::error] == "entryNotFound"); + } + + testcase("Credentials that aren't required"); + { // carol issue credentials for daria + env(credentials::create(daria, carol, credType)); + env.close(); + env(credentials::accept(daria, carol, credType)); + env.close(); + std::string const credDaria = + credentials::ledgerEntry( + env, daria, carol, credType)[jss::result][jss::index] + .asString(); + + // daria use valid credentials, which aren't required and can + // delete her account + env(acctdelete(daria, carol), + credentials::ids({credDaria}), + fee(acctDelFee)); + env.close(); + + // check that credential object deleted too + auto const jNoCred = + credentials::ledgerEntry(env, daria, carol, credType); + + BEAST_EXPECT( + jNoCred.isObject() && jNoCred.isMember(jss::result) && + jNoCred[jss::result].isMember(jss::error) && + jNoCred[jss::result][jss::error] == "entryNotFound"); + } + + { + Account const eaton{"eaton"}; + Account const fred{"fred"}; + + env.fund(XRP(5000), eaton, fred); + + // carol issue credentials for eaton + env(credentials::create(eaton, carol, credType)); + env.close(); + env(credentials::accept(eaton, carol, credType)); + env.close(); + std::string const credEaton = + credentials::ledgerEntry( + env, eaton, carol, credType)[jss::result][jss::index] + .asString(); + + // fred make preauthorization through authorized account + env(fset(fred, asfDepositAuth)); + env.close(); + env(deposit::auth(fred, eaton)); + env.close(); + + // Close enough ledgers to be able to delete becky's account. + incLgrSeqForAccDel(env, eaton); + auto const acctDelFee{drops(env.current()->fees().increment)}; + + // eaton use valid credentials, but he already authorized + // through "Authorized" field. + env(acctdelete(eaton, fred), + credentials::ids({credEaton}), + fee(acctDelFee)); + env.close(); + + // check that credential object deleted too + auto const jNoCred = + credentials::ledgerEntry(env, eaton, carol, credType); + + BEAST_EXPECT( + jNoCred.isObject() && jNoCred.isMember(jss::result) && + jNoCred[jss::result].isMember(jss::error) && + jNoCred[jss::result][jss::error] == "entryNotFound"); + } + + testcase("Expired credentials"); + { + Account const john{"john"}; + + env.fund(XRP(10000), john); + env.close(); + + auto jv = credentials::create(john, carol, credType); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 20; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + env(credentials::accept(john, carol, credType)); + env.close(); + jv = credentials::ledgerEntry(env, john, carol, credType); + std::string const credIdx = + jv[jss::result][jss::index].asString(); + + incLgrSeqForAccDel(env, john); + + // credentials are expired + // john use credentials but can't delete account + env(acctdelete(john, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(tecEXPIRED)); + env.close(); + + { + // check that expired credential object deleted + auto jv = + credentials::ledgerEntry(env, john, carol, credType); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + jv[jss::result].isMember(jss::error) && + jv[jss::result][jss::error] == "entryNotFound"); + } + } + } + + { + testcase("Credentials feature disabled"); + using namespace test::jtx; + + Account const alice{"alice"}; + Account const becky{"becky"}; + Account const carol{"carol"}; + + Env env{*this, supported_amendments() - featureCredentials}; + env.fund(XRP(100000), alice, becky, carol); + env.close(); + + // alice sets the lsfDepositAuth flag on her account. This should + // prevent becky from deleting her account while using alice as the + // destination. + env(fset(alice, asfDepositAuth)); + env.close(); + + // Close enough ledgers to be able to delete becky's account. + incLgrSeqForAccDel(env, becky); + + auto const acctDelFee{drops(env.current()->fees().increment)}; + + std::string const credIdx = + "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE" + "CB"; + + // and can't delete even with old DepositPreauth + env(deposit::auth(alice, becky)); + env.close(); + + env(acctdelete(becky, alice), + credentials::ids({credIdx}), + fee(acctDelFee), + ter(temDISABLED)); + env.close(); + } + } + + void + testDeleteCredentialsOwner() + { + { + testcase("Deleting Issuer deletes issued credentials"); + + using namespace test::jtx; + + Account const alice{"alice"}; + Account const becky{"becky"}; + Account const carol{"carol"}; + + const char credType[] = "abcd"; + + Env env{*this}; + env.fund(XRP(100000), alice, becky, carol); + env.close(); + + // carol issue credentials for becky + env(credentials::create(becky, carol, credType)); + env.close(); + env(credentials::accept(becky, carol, credType)); + env.close(); + + // get credentials index + auto const jv = + credentials::ledgerEntry(env, becky, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Close enough ledgers to be able to delete carol's account. + incLgrSeqForAccDel(env, carol); + + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(carol, alice), fee(acctDelFee)); + env.close(); + + { // check that credential object deleted too + BEAST_EXPECT(!env.le(credIdx)); + auto const jv = + credentials::ledgerEntry(env, becky, carol, credType); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + jv[jss::result].isMember(jss::error) && + jv[jss::result][jss::error] == "entryNotFound"); + } + } + + { + testcase("Deleting Subject deletes issued credentials"); + + using namespace test::jtx; + + Account const alice{"alice"}; + Account const becky{"becky"}; + Account const carol{"carol"}; + + const char credType[] = "abcd"; + + Env env{*this}; + env.fund(XRP(100000), alice, becky, carol); + env.close(); + + // carol issue credentials for becky + env(credentials::create(becky, carol, credType)); + env.close(); + env(credentials::accept(becky, carol, credType)); + env.close(); + + // get credentials index + auto const jv = + credentials::ledgerEntry(env, becky, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Close enough ledgers to be able to delete carol's account. + incLgrSeqForAccDel(env, becky); + + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(becky, alice), fee(acctDelFee)); + env.close(); + + { // check that credential object deleted too + BEAST_EXPECT(!env.le(credIdx)); + auto const jv = + credentials::ledgerEntry(env, becky, carol, credType); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + jv[jss::result].isMember(jss::error) && + jv[jss::result][jss::error] == "entryNotFound"); + } + } + } + void run() override { @@ -925,6 +1285,8 @@ class AccountDelete_test : public beast::unit_test::suite testBalanceTooSmallForFee(); testWithTickets(); testDest(); + testDestinationDepositAuthCredentials(); + testDeleteCredentialsOwner(); } }; diff --git a/src/test/app/AccountTxPaging_test.cpp b/src/test/app/AccountTxPaging_test.cpp index d3969e279b7..680e006a74f 100644 --- a/src/test/app/AccountTxPaging_test.cpp +++ b/src/test/app/AccountTxPaging_test.cpp @@ -16,15 +16,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 238e05ba523..2f95fc0280b 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -17,21 +17,21 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 8f0c0ec46b8..2c4f44ce79f 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { @@ -108,16 +108,6 @@ class Check_test : public beast::unit_test::suite return result; } - // Helper function that returns the owner count on an account. - static std::uint32_t - ownerCount(test::jtx::Env const& env, test::jtx::Account const& account) - { - std::uint32_t ret{0}; - if (auto const sleAccount = env.le(account)) - ret = sleAccount->getFieldU32(sfOwnerCount); - return ret; - } - // Helper function that verifies the expected DeliveredAmount is present. // // NOTE: the function _infers_ the transaction to operate on by calling diff --git a/src/test/app/Clawback_test.cpp b/src/test/app/Clawback_test.cpp index 630e4836d8e..c000433d2af 100644 --- a/src/test/app/Clawback_test.cpp +++ b/src/test/app/Clawback_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -37,16 +37,6 @@ class Clawback_test : public beast::unit_test::suite return boost::lexical_cast(t); } - // Helper function that returns the owner count of an account root. - static std::uint32_t - ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) - { - std::uint32_t ret{0}; - if (auto const sleAcct = env.le(acct)) - ret = sleAcct->at(sfOwnerCount); - return ret; - } - // Helper function that returns the number of tickets held by an account. static std::uint32_t ticketCount(test::jtx::Env const& env, test::jtx::Account const& acct) @@ -965,6 +955,7 @@ class Clawback_test : public beast::unit_test::suite using namespace test::jtx; FeatureBitset const all{supported_amendments()}; + testWithFeats(all - featureMPTokensV1); testWithFeats(all); } }; diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp new file mode 100644 index 00000000000..e5d90d9766c --- /dev/null +++ b/src/test/app/Credentials_test.cpp @@ -0,0 +1,1079 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { +namespace test { + +static inline bool +checkVL( + std::shared_ptr const& sle, + SField const& field, + std::string const& expected) +{ + return strHex(expected) == strHex(sle->getFieldVL(field)); +} + +static inline Keylet +credentialKeylet( + test::jtx::Account const& subject, + test::jtx::Account const& issuer, + std::string_view credType) +{ + return keylet::credential( + subject.id(), issuer.id(), Slice(credType.data(), credType.size())); +} + +struct Credentials_test : public beast::unit_test::suite +{ + void + testSuccessful(FeatureBitset features) + { + using namespace test::jtx; + + const char credType[] = "abcde"; + const char uri[] = "uri"; + + Account const issuer{"issuer"}; + Account const subject{"subject"}; + Account const other{"other"}; + + Env env{*this, features}; + + { + testcase("Create for subject."); + + auto const credKey = credentialKeylet(subject, issuer, credType); + + env.fund(XRP(5000), subject, issuer, other); + env.close(); + + // Test Create credentials + env(credentials::create(subject, issuer, credType), + credentials::uri(uri)); + env.close(); + { + auto const sleCred = env.le(credKey); + BEAST_EXPECT(static_cast(sleCred)); + if (!sleCred) + return; + + BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id()); + BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id()); + BEAST_EXPECT(!sleCred->getFieldU32(sfFlags)); + BEAST_EXPECT(ownerCount(env, issuer) == 1); + BEAST_EXPECT(!ownerCount(env, subject)); + BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType)); + BEAST_EXPECT(checkVL(sleCred, sfURI, uri)); + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + + env(credentials::accept(subject, issuer, credType)); + env.close(); + { + // check switching owner of the credentials from issuer to + // subject + auto const sleCred = env.le(credKey); + BEAST_EXPECT(static_cast(sleCred)); + if (!sleCred) + return; + + BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id()); + BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id()); + BEAST_EXPECT(!ownerCount(env, issuer)); + BEAST_EXPECT(ownerCount(env, subject) == 1); + BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType)); + BEAST_EXPECT(checkVL(sleCred, sfURI, uri)); + BEAST_EXPECT(sleCred->getFieldU32(sfFlags) == lsfAccepted); + } + + env(credentials::deleteCred(subject, subject, issuer, credType)); + env.close(); + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, issuer)); + BEAST_EXPECT(!ownerCount(env, subject)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + } + + { + testcase("Create for themself."); + + auto const credKey = credentialKeylet(issuer, issuer, credType); + + env(credentials::create(issuer, issuer, credType), + credentials::uri(uri)); + env.close(); + { + auto const sleCred = env.le(credKey); + BEAST_EXPECT(static_cast(sleCred)); + if (!sleCred) + return; + + BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id()); + BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id()); + BEAST_EXPECT((sleCred->getFieldU32(sfFlags) & lsfAccepted)); + BEAST_EXPECT( + sleCred->getFieldU64(sfIssuerNode) == + sleCred->getFieldU64(sfSubjectNode)); + BEAST_EXPECT(ownerCount(env, issuer) == 1); + BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType)); + BEAST_EXPECT(checkVL(sleCred, sfURI, uri)); + auto const jle = + credentials::ledgerEntry(env, issuer, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + issuer.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + + env(credentials::deleteCred(issuer, issuer, issuer, credType)); + env.close(); + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, issuer)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, issuer, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + } + } + + void + testCredentialsDelete(FeatureBitset features) + { + using namespace test::jtx; + + const char credType[] = "abcde"; + + Account const issuer{"issuer"}; + Account const subject{"subject"}; + Account const other{"other"}; + + Env env{*this, features}; + + // fund subject and issuer + env.fund(XRP(5000), issuer, subject, other); + env.close(); + + { + testcase("Delete issuer before accept"); + + auto const credKey = credentialKeylet(subject, issuer, credType); + env(credentials::create(subject, issuer, credType)); + env.close(); + + // delete issuer + { + int const delta = env.seq(issuer) + 255; + for (int i = 0; i < delta; ++i) + env.close(); + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(issuer, other), fee(acctDelFee)); + env.close(); + } + + // check credentials deleted too + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, subject)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + + // resurrection + env.fund(XRP(5000), issuer); + env.close(); + } + + { + testcase("Delete issuer after accept"); + + auto const credKey = credentialKeylet(subject, issuer, credType); + env(credentials::create(subject, issuer, credType)); + env.close(); + env(credentials::accept(subject, issuer, credType)); + env.close(); + + // delete issuer + { + int const delta = env.seq(issuer) + 255; + for (int i = 0; i < delta; ++i) + env.close(); + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(issuer, other), fee(acctDelFee)); + env.close(); + } + + // check credentials deleted too + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, subject)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + + // resurrection + env.fund(XRP(5000), issuer); + env.close(); + } + + { + testcase("Delete subject before accept"); + + auto const credKey = credentialKeylet(subject, issuer, credType); + env(credentials::create(subject, issuer, credType)); + env.close(); + + // delete subject + { + int const delta = env.seq(subject) + 255; + for (int i = 0; i < delta; ++i) + env.close(); + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(subject, other), fee(acctDelFee)); + env.close(); + } + + // check credentials deleted too + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, issuer)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + + // resurrection + env.fund(XRP(5000), subject); + env.close(); + } + + { + testcase("Delete subject after accept"); + + auto const credKey = credentialKeylet(subject, issuer, credType); + env(credentials::create(subject, issuer, credType)); + env.close(); + env(credentials::accept(subject, issuer, credType)); + env.close(); + + // delete subject + { + int const delta = env.seq(subject) + 255; + for (int i = 0; i < delta; ++i) + env.close(); + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(subject, other), fee(acctDelFee)); + env.close(); + } + + // check credentials deleted too + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, issuer)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + + // resurrection + env.fund(XRP(5000), subject); + env.close(); + } + + { + testcase("Delete by other"); + + auto const credKey = credentialKeylet(subject, issuer, credType); + auto jv = credentials::create(subject, issuer, credType); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count(); + jv[sfExpiration.jsonName] = t + 20; + env(jv); + + // time advance + env.close(); + env.close(); + env.close(); + + // Other account delete credentials + env(credentials::deleteCred(other, subject, issuer, credType)); + env.close(); + + // check credentials object + { + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, issuer)); + BEAST_EXPECT(!ownerCount(env, subject)); + + // check no credential exists anymore + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + } + + { + testcase("Delete by subject"); + + env(credentials::create(subject, issuer, credType)); + env.close(); + + // Subject can delete + env(credentials::deleteCred(subject, subject, issuer, credType)); + env.close(); + { + auto const credKey = + credentialKeylet(subject, issuer, credType); + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, subject)); + BEAST_EXPECT(!ownerCount(env, issuer)); + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + } + + { + testcase("Delete by issuer"); + env(credentials::create(subject, issuer, credType)); + env.close(); + + env(credentials::deleteCred(issuer, subject, issuer, credType)); + env.close(); + { + auto const credKey = + credentialKeylet(subject, issuer, credType); + BEAST_EXPECT(!env.le(credKey)); + BEAST_EXPECT(!ownerCount(env, subject)); + BEAST_EXPECT(!ownerCount(env, issuer)); + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + jle[jss::result].isMember(jss::error) && + jle[jss::result][jss::error] == "entryNotFound"); + } + } + } + + void + testCreateFailed(FeatureBitset features) + { + using namespace test::jtx; + + const char credType[] = "abcde"; + + Account const issuer{"issuer"}; + Account const subject{"subject"}; + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), subject, issuer); + env.close(); + + { + testcase("Credentials fail, no subject param."); + auto jv = credentials::create(subject, issuer, credType); + jv.removeMember(jss::Subject); + env(jv, ter(temMALFORMED)); + } + + { + auto jv = credentials::create(subject, issuer, credType); + jv[jss::Subject] = to_string(xrpAccount()); + env(jv, ter(temMALFORMED)); + } + + { + testcase("Credentials fail, no credentialType param."); + auto jv = credentials::create(subject, issuer, credType); + jv.removeMember(sfCredentialType.jsonName); + env(jv, ter(temMALFORMED)); + } + + { + testcase("Credentials fail, empty credentialType param."); + auto jv = credentials::create(subject, issuer, ""); + env(jv, ter(temMALFORMED)); + } + + { + testcase( + "Credentials fail, credentialType length > " + "maxCredentialTypeLength."); + constexpr std::string_view longCredType = + "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" + "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p"; + static_assert(longCredType.size() > maxCredentialTypeLength); + auto jv = credentials::create(subject, issuer, longCredType); + env(jv, ter(temMALFORMED)); + } + + { + testcase("Credentials fail, URI length > 256."); + constexpr std::string_view longURI = + "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" + "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p " + "9hfup;wDJFBVSD8f72 " + "pfhiusdovnbs;" + "djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd " + "vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB" + "WULE" + "fv28o37gfwEFB3872TFO8GSDSDVD"; + static_assert(longURI.size() > maxCredentialURILength); + env(credentials::create(subject, issuer, credType), + credentials::uri(longURI), + ter(temMALFORMED)); + } + + { + testcase("Credentials fail, URI empty."); + env(credentials::create(subject, issuer, credType), + credentials::uri(""), + ter(temMALFORMED)); + } + + { + testcase("Credentials fail, expiration in the past."); + auto jv = credentials::create(subject, issuer, credType); + // current time in ripple epoch - 1s + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() - + 1; + jv[sfExpiration.jsonName] = t; + env(jv, ter(tecEXPIRED)); + } + + { + testcase("Credentials fail, invalid fee."); + + auto jv = credentials::create(subject, issuer, credType); + jv[jss::Fee] = -1; + env(jv, ter(temBAD_FEE)); + } + + { + testcase("Credentials fail, duplicate."); + auto const jv = credentials::create(subject, issuer, credType); + env(jv); + env.close(); + env(jv, ter(tecDUPLICATE)); + env.close(); + + // check credential still present + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + } + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), issuer); + env.close(); + + { + testcase("Credentials fail, subject doesn't exist."); + auto const jv = credentials::create(subject, issuer, credType); + env(jv, ter(tecNO_TARGET)); + } + } + + { + using namespace jtx; + Env env{*this, features}; + + auto const reserve = drops(env.current()->fees().accountReserve(0)); + env.fund(reserve, subject, issuer); + env.close(); + + testcase("Credentials fail, not enough reserve."); + { + auto const jv = credentials::create(subject, issuer, credType); + env(jv, ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + } + } + + void + testAcceptFailed(FeatureBitset features) + { + using namespace jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const subject{"subject"}; + Account const other{"other"}; + + { + Env env{*this, features}; + + env.fund(XRP(5000), subject, issuer); + + { + testcase("CredentialsAccept fail, Credential doesn't exist."); + env(credentials::accept(subject, issuer, credType), + ter(tecNO_ENTRY)); + env.close(); + } + + { + testcase("CredentialsAccept fail, invalid Issuer account."); + auto jv = credentials::accept(subject, issuer, credType); + jv[jss::Issuer] = to_string(xrpAccount()); + env(jv, ter(temINVALID_ACCOUNT_ID)); + env.close(); + } + + { + testcase( + "CredentialsAccept fail, invalid credentialType param."); + auto jv = credentials::accept(subject, issuer, ""); + env(jv, ter(temMALFORMED)); + } + } + + { + Env env{*this, features}; + + env.fund(drops(env.current()->fees().accountReserve(1)), issuer); + env.fund(drops(env.current()->fees().accountReserve(0)), subject); + env.close(); + + { + testcase("CredentialsAccept fail, not enough reserve."); + env(credentials::create(subject, issuer, credType)); + env.close(); + + env(credentials::accept(subject, issuer, credType), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + // check credential still present + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + } + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), subject, issuer); + env.close(); + + { + env(credentials::create(subject, issuer, credType)); + env.close(); + + testcase("CredentialsAccept fail, invalid fee."); + auto jv = credentials::accept(subject, issuer, credType); + jv[jss::Fee] = -1; + env(jv, ter(temBAD_FEE)); + + testcase("CredentialsAccept fail, lsfAccepted already set."); + env(credentials::accept(subject, issuer, credType)); + env.close(); + env(credentials::accept(subject, issuer, credType), + ter(tecDUPLICATE)); + env.close(); + + // check credential still present + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + + { + const char credType2[] = "efghi"; + + testcase("CredentialsAccept fail, expired credentials."); + auto jv = credentials::create(subject, issuer, credType2); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count(); + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + + // credentials are expired now + env(credentials::accept(subject, issuer, credType2), + ter(tecEXPIRED)); + env.close(); + + // check that expired credentials were deleted + auto const jDelCred = + credentials::ledgerEntry(env, subject, issuer, credType2); + BEAST_EXPECT( + jDelCred.isObject() && jDelCred.isMember(jss::result) && + jDelCred[jss::result].isMember(jss::error) && + jDelCred[jss::result][jss::error] == "entryNotFound"); + + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, subject) == 1); + } + } + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), issuer, subject, other); + env.close(); + + { + testcase("CredentialsAccept fail, issuer doesn't exist."); + auto jv = credentials::create(subject, issuer, credType); + env(jv); + env.close(); + + // delete issuer + int const delta = env.seq(issuer) + 255; + for (int i = 0; i < delta; ++i) + env.close(); + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(issuer, other), fee(acctDelFee)); + + // can't accept - no issuer account + jv = credentials::accept(subject, issuer, credType); + env(jv, ter(tecNO_ISSUER)); + env.close(); + + // check that expired credentials were deleted + auto const jDelCred = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jDelCred.isObject() && jDelCred.isMember(jss::result) && + jDelCred[jss::result].isMember(jss::error) && + jDelCred[jss::result][jss::error] == "entryNotFound"); + } + } + } + + void + testDeleteFailed(FeatureBitset features) + { + using namespace test::jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const subject{"subject"}; + Account const other{"other"}; + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), subject, issuer, other); + env.close(); + + { + testcase("CredentialsDelete fail, no Credentials."); + env(credentials::deleteCred(subject, subject, issuer, credType), + ter(tecNO_ENTRY)); + env.close(); + } + + { + testcase("CredentialsDelete fail, invalid Subject account."); + auto jv = + credentials::deleteCred(subject, subject, issuer, credType); + jv[jss::Subject] = to_string(xrpAccount()); + env(jv, ter(temINVALID_ACCOUNT_ID)); + env.close(); + } + + { + testcase("CredentialsDelete fail, invalid Issuer account."); + auto jv = + credentials::deleteCred(subject, subject, issuer, credType); + jv[jss::Issuer] = to_string(xrpAccount()); + env(jv, ter(temINVALID_ACCOUNT_ID)); + env.close(); + } + + { + testcase( + "CredentialsDelete fail, invalid credentialType param."); + auto jv = credentials::deleteCred(subject, subject, issuer, ""); + env(jv, ter(temMALFORMED)); + } + + { + const char credType2[] = "fghij"; + + env(credentials::create(subject, issuer, credType2)); + env.close(); + + // Other account can't delete credentials without expiration + env(credentials::deleteCred(other, subject, issuer, credType2), + ter(tecNO_PERMISSION)); + env.close(); + + // check credential still present + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType2); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType2))); + } + + { + testcase("CredentialsDelete fail, time not expired yet."); + + auto jv = credentials::create(subject, issuer, credType); + // current time in ripple epoch + 1000s + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 1000; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + + // Other account can't delete credentials that not expired + env(credentials::deleteCred(other, subject, issuer, credType), + ter(tecNO_PERMISSION)); + env.close(); + + // check credential still present + auto const jle = + credentials::ledgerEntry(env, subject, issuer, credType); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember("LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + subject.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType))); + } + + { + testcase("CredentialsDelete fail, no Issuer and Subject."); + + auto jv = + credentials::deleteCred(subject, subject, issuer, credType); + jv.removeMember(jss::Subject); + jv.removeMember(jss::Issuer); + env(jv, ter(temMALFORMED)); + env.close(); + } + + { + testcase("CredentialsDelete fail, invalid fee."); + + auto jv = + credentials::deleteCred(subject, subject, issuer, credType); + jv[jss::Fee] = -1; + env(jv, ter(temBAD_FEE)); + env.close(); + } + + { + testcase("deleteSLE fail, bad SLE."); + auto view = std::make_shared( + env.current().get(), ApplyFlags::tapNONE); + auto ter = + ripple::credentials::deleteSLE(*view, {}, env.journal); + BEAST_EXPECT(ter == tecNO_ENTRY); + } + } + } + + void + testFeatureFailed(FeatureBitset features) + { + using namespace test::jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const subject{"subject"}; + + { + using namespace jtx; + Env env{*this, features}; + + env.fund(XRP(5000), subject, issuer); + env.close(); + + { + testcase("Credentials fail, Feature is not enabled."); + env(credentials::create(subject, issuer, credType), + ter(temDISABLED)); + env(credentials::accept(subject, issuer, credType), + ter(temDISABLED)); + env(credentials::deleteCred(subject, subject, issuer, credType), + ter(temDISABLED)); + } + } + } + + void + testRPC() + { + using namespace test::jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const subject{"subject"}; + + { + using namespace jtx; + Env env{*this}; + + env.fund(XRP(5000), subject, issuer); + env.close(); + + env(credentials::create(subject, issuer, credType)); + env.close(); + + env(credentials::accept(subject, issuer, credType)); + env.close(); + + testcase("account_tx"); + + std::string txHash0, txHash1; + { + Json::Value params; + params[jss::account] = subject.human(); + auto const jv = env.rpc( + "json", "account_tx", to_string(params))[jss::result]; + + BEAST_EXPECT(jv[jss::transactions].size() == 4); + auto const& tx0(jv[jss::transactions][0u][jss::tx]); + BEAST_EXPECT( + tx0[jss::TransactionType] == jss::CredentialAccept); + auto const& tx1(jv[jss::transactions][1u][jss::tx]); + BEAST_EXPECT( + tx1[jss::TransactionType] == jss::CredentialCreate); + txHash0 = tx0[jss::hash].asString(); + txHash1 = tx1[jss::hash].asString(); + } + + { + Json::Value params; + params[jss::account] = issuer.human(); + auto const jv = env.rpc( + "json", "account_tx", to_string(params))[jss::result]; + + BEAST_EXPECT(jv[jss::transactions].size() == 4); + auto const& tx0(jv[jss::transactions][0u][jss::tx]); + BEAST_EXPECT( + tx0[jss::TransactionType] == jss::CredentialAccept); + auto const& tx1(jv[jss::transactions][1u][jss::tx]); + BEAST_EXPECT( + tx1[jss::TransactionType] == jss::CredentialCreate); + + BEAST_EXPECT(txHash0 == tx0[jss::hash].asString()); + BEAST_EXPECT(txHash1 == tx1[jss::hash].asString()); + } + + testcase("account_objects"); + std::string objectIdx; + { + Json::Value params; + params[jss::account] = subject.human(); + auto jv = env.rpc( + "json", "account_objects", to_string(params))[jss::result]; + + BEAST_EXPECT(jv[jss::account_objects].size() == 1); + auto const& object(jv[jss::account_objects][0u]); + + BEAST_EXPECT( + object["LedgerEntryType"].asString() == jss::Credential); + objectIdx = object[jss::index].asString(); + } + + { + Json::Value params; + params[jss::account] = issuer.human(); + auto jv = env.rpc( + "json", "account_objects", to_string(params))[jss::result]; + + BEAST_EXPECT(jv[jss::account_objects].size() == 1); + auto const& object(jv[jss::account_objects][0u]); + + BEAST_EXPECT( + object["LedgerEntryType"].asString() == jss::Credential); + BEAST_EXPECT(objectIdx == object[jss::index].asString()); + } + } + } + + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + testSuccessful(all); + testCredentialsDelete(all); + testCreateFailed(all); + testAcceptFailed(all); + testDeleteFailed(all); + testFeatureFailed(all - featureCredentials); + testRPC(); + } +}; + +BEAST_DEFINE_TESTSUITE(Credentials, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp index 09cca1e82af..6f6a7eb3e7f 100644 --- a/src/test/app/CrossingLimits_test.cpp +++ b/src/test/app/CrossingLimits_test.cpp @@ -15,9 +15,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 82e77a20264..3f9cce1d33e 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include #include @@ -30,16 +30,6 @@ namespace ripple { namespace test { -// Helper function that returns the owner count of an account root. -static std::uint32_t -ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) -{ - std::uint32_t ret{0}; - if (auto const sleAcct = env.le(acct)) - ret = sleAcct->at(sfOwnerCount); - return ret; -} - bool checkVL(Slice const& result, std::string expected) { diff --git a/src/test/app/DNS_test.cpp b/src/test/app/DNS_test.cpp index 6ae4bc8d64a..7a39c5f0790 100644 --- a/src/test/app/DNS_test.cpp +++ b/src/test/app/DNS_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include #include diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp index 316d95ba740..3c62a47a4a4 100644 --- a/src/test/app/DeliverMin_test.cpp +++ b/src/test/app/DeliverMin_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index f10633c5e46..0f2481a7c9e 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -17,8 +17,10 @@ */ //============================================================================== -#include #include +#include + +#include namespace ripple { namespace test { @@ -381,6 +383,25 @@ struct DepositAuth_test : public beast::unit_test::suite } }; +static Json::Value +ledgerEntryDepositPreauth( + jtx::Env& env, + jtx::Account const& acc, + std::vector const& auth) +{ + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = acc.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]); + for (auto const& o : auth) + { + arr.append(o.toLEJson()); + } + return env.rpc("json", "ledger_entry", to_string(jvParams)); +} + struct DepositPreauth_test : public beast::unit_test::suite { void @@ -634,6 +655,69 @@ struct DepositPreauth_test : public beast::unit_test::suite sendmax(XRP(10)), ter(expect)); env.close(); + + { + // becky setup depositpreauth with credentials + const char credType[] = "abcde"; + Account const carol{"carol"}; + env.fund(XRP(5000), carol); + + bool const supportsCredentials = features[featureCredentials]; + + TER const expectCredentials( + supportsCredentials ? TER(tesSUCCESS) : TER(temDISABLED)); + TER const expectPayment( + !supportsCredentials + ? TER(temDISABLED) + : (!supportsPreauth ? TER(tecNO_PERMISSION) + : TER(tesSUCCESS))); + TER const expectDP( + !supportsPreauth + ? TER(temDISABLED) + : (!supportsCredentials ? TER(temDISABLED) + : TER(tesSUCCESS))); + + env(deposit::authCredentials(becky, {{carol, credType}}), + ter(expectDP)); + env.close(); + + // gw accept credentials + env(credentials::create(gw, carol, credType), + ter(expectCredentials)); + env.close(); + env(credentials::accept(gw, carol, credType), + ter(expectCredentials)); + env.close(); + + auto jv = credentials::ledgerEntry(env, gw, carol, credType); + std::string const credIdx = supportsCredentials + ? jv[jss::result][jss::index].asString() + : "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6" + "EA288BE4"; + + env(pay(gw, becky, USD(100)), + credentials::ids({credIdx}), + ter(expectPayment)); + env.close(); + } + + { + using namespace std::chrono; + + if (!supportsPreauth) + { + auto const seq1 = env.seq(alice); + env(escrow(alice, becky, XRP(100)), + finish_time(env.now() + 1s)); + env.close(); + + // Failed as rule is disabled + env(finish(gw, alice, seq1), + fee(1500), + ter(tecNO_PERMISSION)); + env.close(); + } + } } if (supportsPreauth) @@ -724,14 +808,759 @@ struct DepositPreauth_test : public beast::unit_test::suite } } + void + testCredentialsPayment() + { + using namespace jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const maria{"maria"}; + Account const john{"john"}; + + { + testcase("Payment failed with disabled credentials rule."); + + Env env(*this, supported_amendments() - featureCredentials); + + env.fund(XRP(5000), issuer, bob, alice); + env.close(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // Setup DepositPreauth object failed - amendent is not supported + env(deposit::authCredentials(bob, {{issuer, credType}}), + ter(temDISABLED)); + env.close(); + + // But can create old DepositPreauth + env(deposit::auth(bob, alice)); + env.close(); + + // And alice can't pay with any credentials, amendement is not + // enabled + std::string const invalidIdx = + "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E" + "01E034"; + env(pay(alice, bob, XRP(10)), + credentials::ids({invalidIdx}), + ter(temDISABLED)); + env.close(); + } + + { + testcase("Payment with credentials."); + + Env env(*this); + + env.fund(XRP(5000), issuer, alice, bob, john); + env.close(); + + // Issuer create credentials, but Alice didn't accept them yet + env(credentials::create(alice, issuer, credType)); + env.close(); + + // Get the index of the credentials + auto const jv = + credentials::ledgerEntry(env, alice, issuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // Bob will accept payements from accounts with credentials signed + // by 'issuer' + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + BEAST_EXPECT( + jDP.isObject() && jDP.isMember(jss::result) && + !jDP[jss::result].isMember(jss::error) && + jDP[jss::result].isMember(jss::node) && + jDP[jss::result][jss::node].isMember("LedgerEntryType") && + jDP[jss::result][jss::node]["LedgerEntryType"] == + jss::DepositPreauth); + + // Alice can't pay - empty credentials array + { + auto jv = pay(alice, bob, XRP(100)); + jv[sfCredentialIDs.jsonName] = Json::arrayValue; + env(jv, ter(temMALFORMED)); + env.close(); + } + + // Alice can't pay - not accepted credentials + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + env.close(); + + // Alice accept the credentials + env(credentials::accept(alice, issuer, credType)); + env.close(); + + // Now Alice can pay + env(pay(alice, bob, XRP(100)), credentials::ids({credIdx})); + env.close(); + + // Alice can pay Maria without depositPreauth enabled + env(pay(alice, maria, XRP(250)), credentials::ids({credIdx})); + env.close(); + + // john can accept payment with old depositPreauth and valid + // credentials + env(fset(john, asfDepositAuth)); + env(deposit::auth(john, alice)); + env(pay(alice, john, XRP(100)), credentials::ids({credIdx})); + env.close(); + } + + { + testcase("Payment failed with invalid credentials."); + + Env env(*this); + + env.fund(XRP(10000), issuer, alice, bob, maria); + env.close(); + + // Issuer create credentials, but Alice didn't accept them yet + env(credentials::create(alice, issuer, credType)); + env.close(); + // Alice accept the credentials + env(credentials::accept(alice, issuer, credType)); + env.close(); + // Get the index of the credentials + auto const jv = + credentials::ledgerEntry(env, alice, issuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + { + // Success as destination didn't enable preauthorization so + // valid credentials will not fail + env(pay(alice, bob, XRP(100)), credentials::ids({credIdx})); + } + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + { + // Fail as destination didn't setup DepositPreauth object + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx}), + ter(tecNO_PERMISSION)); + } + + // Bob setup DepositPreauth object, duplicates is not allowed + env(deposit::authCredentials( + bob, {{issuer, credType}, {issuer, credType}}), + ter(temMALFORMED)); + + // Bob setup DepositPreauth object + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + + { + std::string const invalidIdx = + "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E" + "01E034"; + // Alice can't pay with non-existing credentials + env(pay(alice, bob, XRP(100)), + credentials::ids({invalidIdx}), + ter(tecBAD_CREDENTIALS)); + } + + { // maria can't pay using valid credentials but issued for + // different account + env(pay(maria, bob, XRP(100)), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + } + + { + // create another valid credential + const char credType2[] = "fghij"; + env(credentials::create(alice, issuer, credType2)); + env.close(); + env(credentials::accept(alice, issuer, credType2)); + env.close(); + auto const jv = + credentials::ledgerEntry(env, alice, issuer, credType2); + std::string const credIdx2 = + jv[jss::result][jss::index].asString(); + + // Alice can't pay with invalid set of valid credentials + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx, credIdx2}), + ter(tecNO_PERMISSION)); + } + + // Error, duplicate credentials + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx, credIdx}), + ter(temMALFORMED)); + + // Alice can pay + env(pay(alice, bob, XRP(100)), credentials::ids({credIdx})); + env.close(); + } + } + + void + testCredentialsCreation() + { + using namespace jtx; + + const char credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const maria{"maria"}; + + { + testcase("Creating / deleting with credentials."); + + Env env(*this); + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + { + // both included [AuthorizeCredentials UnauthorizeCredentials] + auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + jv[sfUnauthorizeCredentials.jsonName] = Json::arrayValue; + env(jv, ter(temMALFORMED)); + } + + { + // both included [Unauthorize, AuthorizeCredentials] + auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + jv[sfUnauthorize.jsonName] = issuer.human(); + env(jv, ter(temMALFORMED)); + } + + { + // both included [Authorize, AuthorizeCredentials] + auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + jv[sfAuthorize.jsonName] = issuer.human(); + env(jv, ter(temMALFORMED)); + } + + { + // both included [Unauthorize, UnauthorizeCredentials] + auto jv = deposit::unauthCredentials(bob, {{issuer, credType}}); + jv[sfUnauthorize.jsonName] = issuer.human(); + env(jv, ter(temMALFORMED)); + } + + { + // both included [Authorize, UnauthorizeCredentials] + auto jv = deposit::unauthCredentials(bob, {{issuer, credType}}); + jv[sfAuthorize.jsonName] = issuer.human(); + env(jv, ter(temMALFORMED)); + } + + { + // AuthorizeCredentials is empty + auto jv = deposit::authCredentials(bob, {}); + env(jv, ter(temMALFORMED)); + } + + { + // invalid issuer + auto jv = deposit::authCredentials(bob, {}); + auto& arr(jv[sfAuthorizeCredentials.jsonName]); + Json::Value cred = Json::objectValue; + cred[jss::Issuer] = to_string(xrpAccount()); + cred[sfCredentialType.jsonName] = + strHex(std::string_view(credType)); + Json::Value credParent; + credParent[jss::Credential] = cred; + arr.append(std::move(credParent)); + + env(jv, ter(temINVALID_ACCOUNT_ID)); + } + + { + // empty credential type + auto jv = deposit::authCredentials(bob, {{issuer, {}}}); + env(jv, ter(temMALFORMED)); + } + + { + // AuthorizeCredentials is larger than 8 elements + Account const a("a"), b("b"), c("c"), d("d"), e("e"), f("f"), + g("g"), h("h"), i("i"); + auto const& z = credType; + auto jv = deposit::authCredentials( + bob, + {{a, z}, + {b, z}, + {c, z}, + {d, z}, + {e, z}, + {f, z}, + {g, z}, + {h, z}, + {i, z}}); + env(jv, ter(temMALFORMED)); + } + + { + // Can't create with non-existing issuer + Account const rick{"rick"}; + auto jv = deposit::authCredentials(bob, {{rick, credType}}); + env(jv, ter(tecNO_ISSUER)); + env.close(); + } + + { + // not enough reserve + Account const john{"john"}; + env.fund(env.current()->fees().accountReserve(0), john); + auto jv = deposit::authCredentials(john, {{issuer, credType}}); + env(jv, ter(tecINSUFFICIENT_RESERVE)); + } + + { + // NO deposit object exists + env(deposit::unauthCredentials(bob, {{issuer, credType}}), + ter(tecNO_ENTRY)); + } + + // Create DepositPreauth object + { + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + BEAST_EXPECT( + jDP.isObject() && jDP.isMember(jss::result) && + !jDP[jss::result].isMember(jss::error) && + jDP[jss::result].isMember(jss::node) && + jDP[jss::result][jss::node].isMember("LedgerEntryType") && + jDP[jss::result][jss::node]["LedgerEntryType"] == + jss::DepositPreauth); + + // Check object fields + BEAST_EXPECT( + jDP[jss::result][jss::node][jss::Account] == bob.human()); + auto const& credentials( + jDP[jss::result][jss::node]["AuthorizeCredentials"]); + BEAST_EXPECT(credentials.isArray() && credentials.size() == 1); + for (auto const& o : credentials) + { + auto const& c(o[jss::Credential]); + BEAST_EXPECT(c[jss::Issuer].asString() == issuer.human()); + BEAST_EXPECT( + c["CredentialType"].asString() == + strHex(std::string_view(credType))); + } + + // can't create duplicate + env(deposit::authCredentials(bob, {{issuer, credType}}), + ter(tecDUPLICATE)); + } + + // Delete DepositPreauth object + { + env(deposit::unauthCredentials(bob, {{issuer, credType}})); + env.close(); + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + BEAST_EXPECT( + jDP.isObject() && jDP.isMember(jss::result) && + jDP[jss::result].isMember(jss::error) && + jDP[jss::result][jss::error] == "entryNotFound"); + } + } + } + + void + testExpiredCreds() + { + using namespace jtx; + const char credType[] = "abcde"; + const char credType2[] = "fghijkl"; + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + IOU const USD = gw["USD"]; + Account const zelda{"zelda"}; + + { + testcase("Payment failed with expired credentials."); + + Env env(*this); + + env.fund(XRP(10000), issuer, alice, bob, gw); + env.close(); + + // Create credentials + auto jv = credentials::create(alice, issuer, credType); + // Current time in ripple epoch. + // Every time ledger close, unittest timer increase by 10s + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 60; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + + // Alice accept the credentials + env(credentials::accept(alice, issuer, credType)); + env.close(); + + // Create credential which not expired + jv = credentials::create(alice, issuer, credType2); + uint32_t const t2 = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 1000; + jv[sfExpiration.jsonName] = t2; + env(jv); + env.close(); + env(credentials::accept(alice, issuer, credType2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // Get the index of the credentials + jv = credentials::ledgerEntry(env, alice, issuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + jv = credentials::ledgerEntry(env, alice, issuer, credType2); + std::string const credIdx2 = jv[jss::result][jss::index].asString(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + // Bob setup DepositPreauth object + env(deposit::authCredentials( + bob, {{issuer, credType}, {issuer, credType2}})); + env.close(); + + { + // Alice can pay + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx, credIdx2})); + env.close(); + env.close(); + + // Ledger closed, time increased, alice can't pay anymore + env(pay(alice, bob, XRP(100)), + credentials::ids({credIdx, credIdx2}), + ter(tecEXPIRED)); + env.close(); + + { + // check that expired credentials were deleted + auto const jDelCred = + credentials::ledgerEntry(env, alice, issuer, credType); + BEAST_EXPECT( + jDelCred.isObject() && jDelCred.isMember(jss::result) && + jDelCred[jss::result].isMember(jss::error) && + jDelCred[jss::result][jss::error] == "entryNotFound"); + } + + { + // check that non-expired credential still present + auto const jle = + credentials::ledgerEntry(env, alice, issuer, credType2); + BEAST_EXPECT( + jle.isObject() && jle.isMember(jss::result) && + !jle[jss::result].isMember(jss::error) && + jle[jss::result].isMember(jss::node) && + jle[jss::result][jss::node].isMember( + "LedgerEntryType") && + jle[jss::result][jss::node]["LedgerEntryType"] == + jss::Credential && + jle[jss::result][jss::node][jss::Issuer] == + issuer.human() && + jle[jss::result][jss::node][jss::Subject] == + alice.human() && + jle[jss::result][jss::node]["CredentialType"] == + strHex(std::string_view(credType2))); + } + + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 1); + } + + { + auto jv = credentials::create(gw, issuer, credType); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 40; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + env(credentials::accept(gw, issuer, credType)); + env.close(); + + jv = credentials::ledgerEntry(env, gw, issuer, credType); + std::string const credIdx = + jv[jss::result][jss::index].asString(); + + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, gw) == 1); + + env.close(); + env.close(); + env.close(); + + // credentials are expired + env(pay(gw, bob, USD(150)), + credentials::ids({credIdx}), + ter(tecEXPIRED)); + env.close(); + + // check that expired credentials were deleted + auto const jDelCred = + credentials::ledgerEntry(env, gw, issuer, credType); + BEAST_EXPECT( + jDelCred.isObject() && jDelCred.isMember(jss::result) && + jDelCred[jss::result].isMember(jss::error) && + jDelCred[jss::result][jss::error] == "entryNotFound"); + + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, gw) == 0); + } + } + + { + using namespace std::chrono; + + testcase("Escrow failed with expired credentials."); + + Env env(*this); + + env.fund(XRP(5000), issuer, alice, bob, zelda); + env.close(); + + // Create credentials + auto jv = credentials::create(zelda, issuer, credType); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 50; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + + // Zelda accept the credentials + env(credentials::accept(zelda, issuer, credType)); + env.close(); + + // Get the index of the credentials + jv = credentials::ledgerEntry(env, zelda, issuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + // Bob setup DepositPreauth object + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + + auto const seq = env.seq(alice); + env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s)); + env.close(); + + // zelda can't finish escrow with invalid credentials + { + env(finish(zelda, alice, seq), + credentials::ids({}), + ter(temMALFORMED)); + env.close(); + } + + { + // zelda can't finish escrow with invalid credentials + std::string const invalidIdx = + "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E" + "01E034"; + + env(finish(zelda, alice, seq), + credentials::ids({invalidIdx}), + ter(tecBAD_CREDENTIALS)); + env.close(); + } + + { // Ledger closed, time increased, zelda can't finish escrow + env(finish(zelda, alice, seq), + credentials::ids({credIdx}), + fee(1500), + ter(tecEXPIRED)); + env.close(); + } + + // check that expired credentials were deleted + auto const jDelCred = + credentials::ledgerEntry(env, zelda, issuer, credType); + BEAST_EXPECT( + jDelCred.isObject() && jDelCred.isMember(jss::result) && + jDelCred[jss::result].isMember(jss::error) && + jDelCred[jss::result][jss::error] == "entryNotFound"); + } + } + + void + testSortingCredentials() + { + using namespace jtx; + + Account const stock{"stock"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env(*this); + + testcase("Sorting credentials."); + + env.fund(XRP(5000), stock, alice, bob); + + std::vector credentials = { + {"a", "a"}, + {"b", "b"}, + {"c", "c"}, + {"d", "d"}, + {"e", "e"}, + {"f", "f"}, + {"g", "g"}, + {"h", "h"}}; + + for (auto const& c : credentials) + env.fund(XRP(5000), c.issuer); + env.close(); + + std::random_device rd; + std::mt19937 gen(rd()); + + { + std::unordered_map pubKey2Acc; + for (auto const& c : credentials) + pubKey2Acc.emplace(c.issuer.human(), c.issuer); + + // check sorting in object + for (int i = 0; i < 10; ++i) + { + std::ranges::shuffle(credentials, gen); + env(deposit::authCredentials(stock, credentials)); + env.close(); + + auto const dp = + ledgerEntryDepositPreauth(env, stock, credentials); + auto const& authCred( + dp[jss::result][jss::node]["AuthorizeCredentials"]); + BEAST_EXPECT( + authCred.isArray() && + authCred.size() == credentials.size()); + std::vector> readedCreds; + for (auto const& o : authCred) + { + auto const& c(o[jss::Credential]); + auto issuer = c[jss::Issuer].asString(); + + if (BEAST_EXPECT(pubKey2Acc.contains(issuer))) + readedCreds.emplace_back( + pubKey2Acc.at(issuer), + c["CredentialType"].asString()); + } + + BEAST_EXPECT(std::ranges::is_sorted(readedCreds)); + + env(deposit::unauthCredentials(stock, credentials)); + env.close(); + } + } + + { + std::ranges::shuffle(credentials, gen); + env(deposit::authCredentials(stock, credentials)); + env.close(); + + // check sorting in params + for (int i = 0; i < 10; ++i) + { + std::ranges::shuffle(credentials, gen); + env(deposit::authCredentials(stock, credentials), + ter(tecDUPLICATE)); + } + } + + testcase("Check duplicate credentials."); + { + // check duplicates in depositPreauth params + std::ranges::shuffle(credentials, gen); + for (auto const& c : credentials) + { + auto credentials2 = credentials; + credentials2.push_back(c); + + env(deposit::authCredentials(stock, credentials2), + ter(temMALFORMED)); + } + + // create batch of credentials and save their hashes + std::vector credentialIDs; + for (auto const& c : credentials) + { + env(credentials::create(alice, c.issuer, c.credType)); + env.close(); + env(credentials::accept(alice, c.issuer, c.credType)); + env.close(); + + credentialIDs.push_back(credentials::ledgerEntry( + env, + alice, + c.issuer, + c.credType)[jss::result][jss::index] + .asString()); + } + + // check duplicates in payment params + for (auto const& h : credentialIDs) + { + auto credentialIDs2 = credentialIDs; + credentialIDs2.push_back(h); + + env(pay(alice, bob, XRP(100)), + credentials::ids(credentialIDs2), + ter(temMALFORMED)); + } + } + } + void run() override { testEnable(); testInvalid(); auto const supported{jtx::supported_amendments()}; + testPayment(supported - featureDepositPreauth - featureCredentials); testPayment(supported - featureDepositPreauth); + testPayment(supported - featureCredentials); testPayment(supported); + testCredentialsPayment(); + testCredentialsCreation(); + testExpiredCreds(); + testSortingCredentials(); } }; diff --git a/src/test/app/Discrepancy_test.cpp b/src/test/app/Discrepancy_test.cpp index c89432f9115..1eaa1ad86dd 100644 --- a/src/test/app/Discrepancy_test.cpp +++ b/src/test/app/Discrepancy_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 083764fd371..714fc7734d9 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include @@ -1508,6 +1508,154 @@ struct Escrow_test : public beast::unit_test::suite } } + void + testCredentials() + { + testcase("Test with credentials"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + Account const dillon{"dillon "}; + Account const zelda{"zelda"}; + + const char credType[] = "abcde"; + + { + // Credentials amendment not enabled + Env env(*this, supported_amendments() - featureCredentials); + env.fund(XRP(5000), alice, bob); + env.close(); + + auto const seq = env.seq(alice); + env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s)); + env.close(); + + env(fset(bob, asfDepositAuth)); + env.close(); + env(deposit::auth(bob, alice)); + env.close(); + + std::string const credIdx = + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4"; + env(finish(bob, alice, seq), + credentials::ids({credIdx}), + ter(temDISABLED)); + } + + { + Env env(*this); + + env.fund(XRP(5000), alice, bob, carol, dillon, zelda); + env.close(); + + env(credentials::create(carol, zelda, credType)); + env.close(); + auto const jv = + credentials::ledgerEntry(env, carol, zelda, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + auto const seq = env.seq(alice); + env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 50s)); + env.close(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // Fail, credentials not accepted + env(finish(carol, alice, seq), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + + env.close(); + + env(credentials::accept(carol, zelda, credType)); + env.close(); + + // Fail, credentials doesn’t belong to root account + env(finish(dillon, alice, seq), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + + // Fail, no depositPreauth + env(finish(carol, alice, seq), + credentials::ids({credIdx}), + ter(tecNO_PERMISSION)); + + env(deposit::authCredentials(bob, {{zelda, credType}})); + env.close(); + + // Success + env.close(); + env(finish(carol, alice, seq), credentials::ids({credIdx})); + env.close(); + } + + { + testcase("Escrow with credentials without depositPreauth"); + using namespace std::chrono; + + Env env(*this); + + env.fund(XRP(5000), alice, bob, carol, dillon, zelda); + env.close(); + + env(credentials::create(carol, zelda, credType)); + env.close(); + env(credentials::accept(carol, zelda, credType)); + env.close(); + auto const jv = + credentials::ledgerEntry(env, carol, zelda, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + auto const seq = env.seq(alice); + env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 50s)); + // time advance + env.close(); + env.close(); + env.close(); + env.close(); + env.close(); + env.close(); + + // Succeed, Bob doesn't require preauthorization + env(finish(carol, alice, seq), credentials::ids({credIdx})); + env.close(); + + { + const char credType2[] = "fghijk"; + + env(credentials::create(bob, zelda, credType2)); + env.close(); + env(credentials::accept(bob, zelda, credType2)); + env.close(); + auto const credIdxBob = + credentials::ledgerEntry( + env, bob, zelda, credType2)[jss::result][jss::index] + .asString(); + + auto const seq = env.seq(alice); + env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s)); + env.close(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + env(deposit::authCredentials(bob, {{zelda, credType}})); + env.close(); + + // Use any valid credentials if account == dst + env(finish(bob, alice, seq), credentials::ids({credIdxBob})); + env.close(); + } + } + } + void run() override { @@ -1522,6 +1670,7 @@ struct Escrow_test : public beast::unit_test::suite testMetaAndOwnership(); testConsequences(); testEscrowWithTickets(); + testCredentials(); } }; diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index ad38aefb20a..289ce2a713e 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp new file mode 100644 index 00000000000..f8db4df4f92 --- /dev/null +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -0,0 +1,666 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { + +class FixNFTokenPageLinks_test : public beast::unit_test::suite +{ + // Helper function that returns the number of nfts owned by an account. + static std::uint32_t + nftCount(test::jtx::Env& env, test::jtx::Account const& acct) + { + Json::Value params; + params[jss::account] = acct.human(); + params[jss::type] = "state"; + Json::Value nfts = env.rpc("json", "account_nfts", to_string(params)); + return nfts[jss::result][jss::account_nfts].size(); + }; + + // A helper function that generates 96 nfts packed into three pages + // of 32 each. Returns a sorted vector of the NFTokenIDs packed into + // the pages. + std::vector + genPackedTokens(test::jtx::Env& env, test::jtx::Account const& owner) + { + using namespace test::jtx; + + std::vector nfts; + nfts.reserve(96); + + // We want to create fully packed NFT pages. This is a little + // tricky since the system currently in place is inclined to + // assign consecutive tokens to only 16 entries per page. + // + // By manipulating the internal form of the taxon we can force + // creation of NFT pages that are completely full. This lambda + // tells us the taxon value we should pass in in order for the + // internal representation to match the passed in value. + auto internalTaxon = [this, &env]( + Account const& acct, + std::uint32_t taxon) -> std::uint32_t { + std::uint32_t tokenSeq = [this, &env, &acct]() { + auto const le = env.le(acct); + if (BEAST_EXPECT(le)) + return le->at(~sfMintedNFTokens).value_or(0u); + return 0u; + }(); + + // If fixNFTokenRemint amendment is on, we must + // add FirstNFTokenSequence. + if (env.current()->rules().enabled(fixNFTokenRemint)) + tokenSeq += env.le(acct) + ->at(~sfFirstNFTokenSequence) + .value_or(env.seq(acct)); + + return toUInt32(nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon))); + }; + + for (std::uint32_t i = 0; i < 96; ++i) + { + // In order to fill the pages we use the taxon to break them + // into groups of 16 entries. By having the internal + // representation of the taxon go... + // 0, 3, 2, 5, 4, 7... + // in sets of 16 NFTs we can get each page to be fully + // populated. + std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0); + uint32_t const extTaxon = internalTaxon(owner, intTaxon); + nfts.push_back( + token::getNextID(env, owner, extTaxon, tfTransferable)); + env(token::mint(owner, extTaxon), txflags(tfTransferable)); + env.close(); + } + + // Sort the NFTs so they are listed in storage order, not + // creation order. + std::sort(nfts.begin(), nfts.end()); + + // Verify that the owner does indeed have exactly three pages + // of NFTs with 32 entries in each page. + { + Json::Value params; + params[jss::account] = owner.human(); + auto resp = env.rpc("json", "account_objects", to_string(params)); + + Json::Value const& acctObjs = + resp[jss::result][jss::account_objects]; + + int pageCount = 0; + for (Json::UInt i = 0; i < acctObjs.size(); ++i) + { + if (BEAST_EXPECT( + acctObjs[i].isMember(sfNFTokens.jsonName) && + acctObjs[i][sfNFTokens.jsonName].isArray())) + { + BEAST_EXPECT(acctObjs[i][sfNFTokens.jsonName].size() == 32); + ++pageCount; + } + } + // If this check fails then the internal NFT directory logic + // has changed. + BEAST_EXPECT(pageCount == 3); + } + return nfts; + }; + + void + testLedgerStateFixErrors() + { + testcase("LedgerStateFix error cases"); + + using namespace test::jtx; + + Account const alice("alice"); + + { + // Verify that the LedgerStateFix transaction is disabled + // without the fixNFTokenPageLinks amendment. + Env env{*this, supported_amendments() - fixNFTokenPageLinks}; + env.fund(XRP(1000), alice); + + auto const linkFixFee = drops(env.current()->fees().increment); + env(ledgerStateFix::nftPageLinks(alice, alice), + fee(linkFixFee), + ter(temDISABLED)); + } + + Env env{*this, supported_amendments()}; + env.fund(XRP(1000), alice); + std::uint32_t const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + + // Preflight + + { + // Fail preflight1. Can't combine AcccountTxnID and ticket. + Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + tx[sfAccountTxnID.jsonName] = + "00000000000000000000000000000000" + "00000000000000000000000000000000"; + env(tx, ticket::use(ticketSeq), ter(temINVALID)); + } + // Fee too low. + env(ledgerStateFix::nftPageLinks(alice, alice), ter(telINSUF_FEE_P)); + + // Invalid flags. + auto const linkFixFee = drops(env.current()->fees().increment); + env(ledgerStateFix::nftPageLinks(alice, alice), + fee(linkFixFee), + txflags(tfPassive), + ter(temINVALID_FLAG)); + + { + // ledgerStateFix::nftPageLinks requires an Owner field. + Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + tx.removeMember(sfOwner.jsonName); + env(tx, fee(linkFixFee), ter(temINVALID)); + } + { + // Invalid LedgerFixType codes. + Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + tx[sfLedgerFixType.jsonName] = 0; + env(tx, fee(linkFixFee), ter(tefINVALID_LEDGER_FIX_TYPE)); + + tx[sfLedgerFixType.jsonName] = 200; + env(tx, fee(linkFixFee), ter(tefINVALID_LEDGER_FIX_TYPE)); + } + + // Preclaim + Account const carol("carol"); + env.memoize(carol); + env(ledgerStateFix::nftPageLinks(alice, carol), + fee(linkFixFee), + ter(tecOBJECT_NOT_FOUND)); + } + + void + testTokenPageLinkErrors() + { + testcase("NFTokenPageLinkFix error cases"); + + using namespace test::jtx; + + Account const alice("alice"); + + Env env{*this, supported_amendments()}; + env.fund(XRP(1000), alice); + + // These cases all return the same TER code, but they exercise + // different cases where there is nothing to fix in an owner's + // NFToken pages. So they increase test coverage. + + // Owner has no pages to fix. + auto const linkFixFee = drops(env.current()->fees().increment); + env(ledgerStateFix::nftPageLinks(alice, alice), + fee(linkFixFee), + ter(tecFAILED_PROCESSING)); + + // Alice has only one page. + env(token::mint(alice), txflags(tfTransferable)); + env.close(); + + env(ledgerStateFix::nftPageLinks(alice, alice), + fee(linkFixFee), + ter(tecFAILED_PROCESSING)); + + // Alice has at least three pages. + for (std::uint32_t i = 0; i < 64; ++i) + { + env(token::mint(alice), txflags(tfTransferable)); + env.close(); + } + + env(ledgerStateFix::nftPageLinks(alice, alice), + fee(linkFixFee), + ter(tecFAILED_PROCESSING)); + } + + void + testFixNFTokenPageLinks() + { + // Steps: + // 1. Before the fixNFTokenPageLinks amendment is enabled, build the + // three kinds of damaged NFToken directories we know about: + // A. One where there is only one page, but without the final index. + // B. One with multiple pages and a missing final page. + // C. One with links missing in the middle of the chain. + // 2. Enable the fixNFTokenPageLinks amendment. + // 3. Invoke the LedgerStateFix transactor and repair the directories. + testcase("Fix links"); + + using namespace test::jtx; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const daria("daria"); + + Env env{*this, supported_amendments() - fixNFTokenPageLinks}; + env.fund(XRP(1000), alice, bob, carol, daria); + + //********************************************************************** + // Step 1A: Create damaged NFToken directories: + // o One where there is only one page, but without the final index. + //********************************************************************** + + // alice generates three packed pages. + std::vector aliceNFTs = genPackedTokens(env, alice); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Get the index of the middle page. + uint256 const aliceMiddleNFTokenPageIndex = [&env, &alice]() { + auto lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + return lastNFTokenPage->at(sfPreviousPageMin); + }(); + + // alice burns all the tokens in the first and last pages. + for (int i = 0; i < 32; ++i) + { + env(token::burn(alice, {aliceNFTs[i]})); + env.close(); + } + aliceNFTs.erase(aliceNFTs.begin(), aliceNFTs.begin() + 32); + for (int i = 0; i < 32; ++i) + { + env(token::burn(alice, {aliceNFTs.back()})); + aliceNFTs.pop_back(); + env.close(); + } + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(nftCount(env, alice) == 32); + + // Removing the last token from the last page deletes the last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + BEAST_EXPECT(!env.le(keylet::nftpage_max(alice))); + + // alice's "middle" page is still present, but has no links. + { + auto aliceMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(aliceMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + !aliceMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + !aliceMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + //********************************************************************** + // Step 1B: Create damaged NFToken directories: + // o One with multiple pages and a missing final page. + //********************************************************************** + + // bob generates three packed pages. + std::vector bobNFTs = genPackedTokens(env, bob); + BEAST_EXPECT(nftCount(env, bob) == 96); + BEAST_EXPECT(ownerCount(env, bob) == 3); + + // Get the index of the middle page. + uint256 const bobMiddleNFTokenPageIndex = [&env, &bob]() { + auto lastNFTokenPage = env.le(keylet::nftpage_max(bob)); + return lastNFTokenPage->at(sfPreviousPageMin); + }(); + + // bob burns all the tokens in the very last page. + for (int i = 0; i < 32; ++i) + { + env(token::burn(bob, {bobNFTs.back()})); + bobNFTs.pop_back(); + env.close(); + } + BEAST_EXPECT(nftCount(env, bob) == 64); + BEAST_EXPECT(ownerCount(env, bob) == 2); + + // Removing the last token from the last page deletes the last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + BEAST_EXPECT(!env.le(keylet::nftpage_max(bob))); + + // bob's "middle" page is still present, but has lost the + // NextPageMin field. + { + auto bobMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(bobMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + bobMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!bobMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + //********************************************************************** + // Step 1C: Create damaged NFToken directories: + // o One with links missing in the middle of the chain. + //********************************************************************** + + // carol generates three packed pages. + std::vector carolNFTs = genPackedTokens(env, carol); + BEAST_EXPECT(nftCount(env, carol) == 96); + BEAST_EXPECT(ownerCount(env, carol) == 3); + + // Get the index of the middle page. + uint256 const carolMiddleNFTokenPageIndex = [&env, &carol]() { + auto lastNFTokenPage = env.le(keylet::nftpage_max(carol)); + return lastNFTokenPage->at(sfPreviousPageMin); + }(); + + // carol sells all of the tokens in the very last page to daria. + std::vector dariaNFTs; + dariaNFTs.reserve(32); + for (int i = 0; i < 32; ++i) + { + uint256 const offerIndex = + keylet::nftoffer(carol, env.seq(carol)).key; + env(token::createOffer(carol, carolNFTs.back(), XRP(0)), + txflags(tfSellNFToken)); + env.close(); + + env(token::acceptSellOffer(daria, offerIndex)); + env.close(); + + dariaNFTs.push_back(carolNFTs.back()); + carolNFTs.pop_back(); + } + BEAST_EXPECT(nftCount(env, carol) == 64); + BEAST_EXPECT(ownerCount(env, carol) == 2); + + // Removing the last token from the last page deletes the last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + BEAST_EXPECT(!env.le(keylet::nftpage_max(carol))); + + // carol's "middle" page is still present, but has lost the + // NextPageMin field. + auto carolMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(carolMiddleNFTokenPage)) + return; + + BEAST_EXPECT(carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + + // At this point carol's NFT directory has the same problem that + // bob's has: the last page is missing. Now we make things more + // complicated by putting the last page back. carol buys their NFTs + // back from daria. + for (uint256 const& nft : dariaNFTs) + { + uint256 const offerIndex = + keylet::nftoffer(carol, env.seq(carol)).key; + env(token::createOffer(carol, nft, drops(1)), token::owner(daria)); + env.close(); + + env(token::acceptBuyOffer(daria, offerIndex)); + env.close(); + + carolNFTs.push_back(nft); + } + + // Note that carol actually owns 96 NFTs, but only 64 are reported + // because the links are damaged. + BEAST_EXPECT(nftCount(env, carol) == 64); + BEAST_EXPECT(ownerCount(env, carol) == 3); + + // carol's "middle" page is present and still has no NextPageMin field. + { + auto carolMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(carolMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + !carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + // carol has a "last" page again, but it has no PreviousPageMin field. + { + auto carolLastNFTokenPage = env.le(keylet::nftpage_max(carol)); + + BEAST_EXPECT( + !carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + //********************************************************************** + // Step 2: Enable the fixNFTokenPageLinks amendment. + //********************************************************************** + // Verify that the LedgerStateFix transaction is not enabled. + auto const linkFixFee = drops(env.current()->fees().increment); + env(ledgerStateFix::nftPageLinks(daria, alice), + fee(linkFixFee), + ter(temDISABLED)); + + // Wait 15 ledgers so the LedgerStateFix transaction is no longer + // retried. + for (int i = 0; i < 15; ++i) + env.close(); + + env.enableFeature(fixNFTokenPageLinks); + env.close(); + + //********************************************************************** + // Step 3A: Repair the one-page directory (alice's) + //********************************************************************** + + // Verify that alice's NFToken directory is still damaged. + + // alice's last page should still be missing. + BEAST_EXPECT(!env.le(keylet::nftpage_max(alice))); + + // alice's "middle" page is still present and has no links. + { + auto aliceMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(aliceMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + !aliceMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + !aliceMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // The server "remembers" daria's failed nftPageLinks transaction + // signature. So we need to advance daria's sequence number before + // daria can submit a similar transaction. + env(noop(daria)); + + // daria fixes the links in alice's NFToken directory. + env(ledgerStateFix::nftPageLinks(daria, alice), fee(linkFixFee)); + env.close(); + + // alices's last page should now be present and include no links. + { + auto aliceLastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(aliceLastNFTokenPage)) + return; + + BEAST_EXPECT( + !aliceLastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!aliceLastNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // alice's middle page should be gone. + BEAST_EXPECT(!env.le(keylet::nftpage( + keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex))); + + BEAST_EXPECT(nftCount(env, alice) == 32); + BEAST_EXPECT(ownerCount(env, alice) == 1); + + //********************************************************************** + // Step 3B: Repair the two-page directory (bob's) + //********************************************************************** + + // Verify that bob's NFToken directory is still damaged. + + // bob's last page should still be missing. + BEAST_EXPECT(!env.le(keylet::nftpage_max(bob))); + + // bob's "middle" page is still present and missing NextPageMin. + { + auto bobMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(bobMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + bobMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!bobMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // daria fixes the links in bob's NFToken directory. + env(ledgerStateFix::nftPageLinks(daria, bob), fee(linkFixFee)); + env.close(); + + // bob's last page should now be present and include a previous + // link but no next link. + { + auto const lastPageKeylet = keylet::nftpage_max(bob); + auto const bobLastNFTokenPage = env.le(lastPageKeylet); + if (!BEAST_EXPECT(bobLastNFTokenPage)) + return; + + BEAST_EXPECT(bobLastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + bobLastNFTokenPage->at(sfPreviousPageMin) != + bobMiddleNFTokenPageIndex); + BEAST_EXPECT(!bobLastNFTokenPage->isFieldPresent(sfNextPageMin)); + + auto const bobNewFirstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(bob), + bobLastNFTokenPage->at(sfPreviousPageMin))); + if (!BEAST_EXPECT(bobNewFirstNFTokenPage)) + return; + + BEAST_EXPECT( + bobNewFirstNFTokenPage->isFieldPresent(sfNextPageMin) && + bobNewFirstNFTokenPage->at(sfNextPageMin) == + lastPageKeylet.key); + BEAST_EXPECT( + !bobNewFirstNFTokenPage->isFieldPresent(sfPreviousPageMin)); + } + + // bob's middle page should be gone. + BEAST_EXPECT(!env.le(keylet::nftpage( + keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex))); + + BEAST_EXPECT(nftCount(env, bob) == 64); + BEAST_EXPECT(ownerCount(env, bob) == 2); + + //********************************************************************** + // Step 3C: Repair the three-page directory (carol's) + //********************************************************************** + + // Verify that carol's NFToken directory is still damaged. + + // carol's "middle" page is present and has no NextPageMin field. + { + auto carolMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(carolMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + !carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + // carol has a "last" page, but it has no PreviousPageMin field. + { + auto carolLastNFTokenPage = env.le(keylet::nftpage_max(carol)); + + BEAST_EXPECT( + !carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // carol fixes the links in their own NFToken directory. + env(ledgerStateFix::nftPageLinks(carol, carol), fee(linkFixFee)); + env.close(); + + { + // carol's "middle" page is present and now has a NextPageMin field. + auto const lastPageKeylet = keylet::nftpage_max(carol); + auto carolMiddleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex)); + if (!BEAST_EXPECT(carolMiddleNFTokenPage)) + return; + + BEAST_EXPECT( + carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin) && + carolMiddleNFTokenPage->at(sfNextPageMin) == + lastPageKeylet.key); + + // carol has a "last" page that includes a PreviousPageMin field. + auto carolLastNFTokenPage = env.le(lastPageKeylet); + if (!BEAST_EXPECT(carolLastNFTokenPage)) + return; + + BEAST_EXPECT( + carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin) && + carolLastNFTokenPage->at(sfPreviousPageMin) == + carolMiddleNFTokenPageIndex); + BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin)); + + // carol also has a "first" page that includes a NextPageMin field. + auto carolFirstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(carol), + carolMiddleNFTokenPage->at(sfPreviousPageMin))); + if (!BEAST_EXPECT(carolFirstNFTokenPage)) + return; + + BEAST_EXPECT( + carolFirstNFTokenPage->isFieldPresent(sfNextPageMin) && + carolFirstNFTokenPage->at(sfNextPageMin) == + carolMiddleNFTokenPageIndex); + BEAST_EXPECT( + !carolFirstNFTokenPage->isFieldPresent(sfPreviousPageMin)); + } + + // With the link repair, the server knows that carol has 96 NFTs. + BEAST_EXPECT(nftCount(env, carol) == 96); + BEAST_EXPECT(ownerCount(env, carol) == 3); + } + +public: + void + run() override + { + testLedgerStateFixErrors(); + testTokenPageLinkErrors(); + testFixNFTokenPageLinks(); + } +}; + +BEAST_DEFINE_TESTSUITE(FixNFTokenPageLinks, tx, ripple); + +} // namespace ripple diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 920f7a6e058..4d1397eab83 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -506,7 +506,6 @@ struct Flow_test : public beast::unit_test::suite // Without limits, the 0.4 USD would produce 1000 EUR in the forward // pass. This test checks that the payment produces 1 EUR, as // expected. - Env env(*this, features); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(USD(1000), alice, bob, carol); @@ -515,17 +514,52 @@ struct Flow_test : public beast::unit_test::suite env(pay(gw, alice, USD(1000))); env(pay(gw, bob, EUR(1000))); + Keylet const bobUsdOffer = keylet::offer(bob, env.seq(bob)); env(offer(bob, USD(1), drops(2)), txflags(tfPassive)); env(offer(bob, drops(1), EUR(1000)), txflags(tfPassive)); + bool const reducedOffersV2 = features[fixReducedOffersV2]; + + // With reducedOffersV2, it is not allowed to accept less than + // USD(0.5) of bob's USD offer. If we provide 1 drop for less + // than USD(0.5), then the remaining fractional offer would + // block the order book. + TER const expectedTER = + reducedOffersV2 ? TER(tecPATH_DRY) : TER(tesSUCCESS); env(pay(alice, carol, EUR(1)), path(~XRP, ~EUR), sendmax(USD(0.4)), - txflags(tfNoRippleDirect | tfPartialPayment)); + txflags(tfNoRippleDirect | tfPartialPayment), + ter(expectedTER)); + + if (!reducedOffersV2) + { + env.require(balance(carol, EUR(1))); + env.require(balance(bob, USD(0.4))); + env.require(balance(bob, EUR(999))); - env.require(balance(carol, EUR(1))); - env.require(balance(bob, USD(0.4))); - env.require(balance(bob, EUR(999))); + // Show that bob's USD offer is now a blocker. + std::shared_ptr const usdOffer = env.le(bobUsdOffer); + if (BEAST_EXPECT(usdOffer)) + { + std::uint64_t const bookRate = [&usdOffer]() { + // Extract the least significant 64 bits from the + // book page. That's where the quality is stored. + std::string bookDirStr = + to_string(usdOffer->at(sfBookDirectory)); + bookDirStr.erase(0, 48); + return std::stoull(bookDirStr, nullptr, 16); + }(); + std::uint64_t const actualRate = getRate( + usdOffer->at(sfTakerGets), usdOffer->at(sfTakerPays)); + + // We expect the actual rate of the offer to be worse + // (larger) than the rate of the book page holding the + // offer. This is a defect which is corrected by + // fixReducedOffersV2. + BEAST_EXPECT(actualRate > bookRate); + } + } } } @@ -989,14 +1023,12 @@ struct Flow_test : public beast::unit_test::suite 9000000000000000ll, -17, false, - false, STAmount::unchecked{}}; STAmount tinyAmt3{ USD.issue(), 9000000000000003ll, -17, false, - false, STAmount::unchecked{}}; env(offer(gw, drops(9000000000), tinyAmt3)); @@ -1024,14 +1056,12 @@ struct Flow_test : public beast::unit_test::suite 9000000000000000ll, -17, false, - false, STAmount::unchecked{}}; STAmount tinyAmt3{ USD.issue(), 9000000000000003ll, -17, false, - false, STAmount::unchecked{}}; env(pay(gw, alice, tinyAmt1)); @@ -1375,9 +1405,11 @@ struct Flow_test : public beast::unit_test::suite { using namespace jtx; FeatureBitset const ownerPaysFee{featureOwnerPaysFee}; + FeatureBitset const reducedOffersV2(fixReducedOffersV2); testLineQuality(features); testFalseDry(features); + testBookStep(features - reducedOffersV2); testDirectStep(features); testBookStep(features); testDirectStep(features | ownerPaysFee); diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index cb4653c086d..0c54f0e1f39 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -16,12 +16,12 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index 96d14e824cf..1234bc5b9cb 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 880cbea5980..e1a837a9cb2 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include namespace ripple { namespace test { diff --git a/src/test/app/LedgerLoad_test.cpp b/src/test/app/LedgerLoad_test.cpp index f06e7d0bf01..5b6df353d81 100644 --- a/src/test/app/LedgerLoad_test.cpp +++ b/src/test/app/LedgerLoad_test.cpp @@ -17,12 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -36,10 +38,12 @@ class LedgerLoad_test : public beast::unit_test::suite std::unique_ptr cfg, std::string const& dbPath, std::string const& ledger, - Config::StartUpType type) + Config::StartUpType type, + std::optional trapTxHash) { cfg->START_LEDGER = ledger; cfg->START_UP = type; + cfg->TRAP_TX_HASH = trapTxHash; assert(!dbPath.empty()); cfg->legacy("database_path", dbPath); return cfg; @@ -52,6 +56,7 @@ class LedgerLoad_test : public beast::unit_test::suite std::string ledgerFile{}; Json::Value ledger{}; Json::Value hashes{}; + uint256 trapTxHash{}; }; SetupData @@ -94,6 +99,16 @@ class LedgerLoad_test : public beast::unit_test::suite }(); BEAST_EXPECT(retval.hashes.size() == 41); + retval.trapTxHash = [&]() { + auto const txs = env.rpc( + "ledger", + std::to_string(41), + "tx")[jss::result][jss::ledger][jss::transactions]; + BEAST_EXPECT(txs.isArray() && txs.size() > 0); + uint256 tmp; + BEAST_EXPECT(tmp.parseHex(txs[0u][jss::hash].asString())); + return tmp; + }(); // write this ledger data to a file. std::ofstream o(retval.ledgerFile, std::ios::out | std::ios::trunc); @@ -112,7 +127,11 @@ class LedgerLoad_test : public beast::unit_test::suite Env env( *this, envconfig( - ledgerConfig, sd.dbPath, sd.ledgerFile, Config::LOAD_FILE), + ledgerConfig, + sd.dbPath, + sd.ledgerFile, + Config::LOAD_FILE, + std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; @@ -132,7 +151,12 @@ class LedgerLoad_test : public beast::unit_test::suite except([&] { Env env( *this, - envconfig(ledgerConfig, sd.dbPath, "", Config::LOAD_FILE), + envconfig( + ledgerConfig, + sd.dbPath, + "", + Config::LOAD_FILE, + std::nullopt), nullptr, beast::severities::kDisabled); }); @@ -142,7 +166,11 @@ class LedgerLoad_test : public beast::unit_test::suite Env env( *this, envconfig( - ledgerConfig, sd.dbPath, "badfile.json", Config::LOAD_FILE), + ledgerConfig, + sd.dbPath, + "badfile.json", + Config::LOAD_FILE, + std::nullopt), nullptr, beast::severities::kDisabled); }); @@ -172,7 +200,8 @@ class LedgerLoad_test : public beast::unit_test::suite ledgerConfig, sd.dbPath, ledgerFileCorrupt.string(), - Config::LOAD_FILE), + Config::LOAD_FILE, + std::nullopt), nullptr, beast::severities::kDisabled); }); @@ -189,7 +218,12 @@ class LedgerLoad_test : public beast::unit_test::suite boost::erase_all(ledgerHash, "\""); Env env( *this, - envconfig(ledgerConfig, sd.dbPath, ledgerHash, Config::LOAD), + envconfig( + ledgerConfig, + sd.dbPath, + ledgerHash, + Config::LOAD, + std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; @@ -199,6 +233,103 @@ class LedgerLoad_test : public beast::unit_test::suite sd.ledger[jss::ledger][jss::accountState].size()); } + void + testReplay(SetupData const& sd) + { + testcase("Load and replay by hash"); + using namespace test::jtx; + + // create a new env with the ledger hash specified for startup + auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); + boost::erase_all(ledgerHash, "\""); + Env env( + *this, + envconfig( + ledgerConfig, + sd.dbPath, + ledgerHash, + Config::REPLAY, + std::nullopt), + nullptr, + beast::severities::kDisabled); + auto const jrb = env.rpc("ledger", "current", "full")[jss::result]; + BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97); + // in replace mode do not automatically accept the ledger being replayed + + env.close(); + auto const closed = env.rpc("ledger", "current", "full")[jss::result]; + BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98); + BEAST_EXPECT( + closed[jss::ledger][jss::accountState].size() <= + sd.ledger[jss::ledger][jss::accountState].size()); + } + + void + testReplayTx(SetupData const& sd) + { + testcase("Load and replay transaction by hash"); + using namespace test::jtx; + + // create a new env with the ledger hash specified for startup + auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); + boost::erase_all(ledgerHash, "\""); + Env env( + *this, + envconfig( + ledgerConfig, + sd.dbPath, + ledgerHash, + Config::REPLAY, + sd.trapTxHash), + nullptr, + beast::severities::kDisabled); + auto const jrb = env.rpc("ledger", "current", "full")[jss::result]; + BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97); + // in replace mode do not automatically accept the ledger being replayed + + env.close(); + auto const closed = env.rpc("ledger", "current", "full")[jss::result]; + BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98); + BEAST_EXPECT( + closed[jss::ledger][jss::accountState].size() <= + sd.ledger[jss::ledger][jss::accountState].size()); + } + + void + testReplayTxFail(SetupData const& sd) + { + testcase("Load and replay transaction by hash failure"); + using namespace test::jtx; + + // create a new env with the ledger hash specified for startup + auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]); + boost::erase_all(ledgerHash, "\""); + try + { + // will throw an exception, because we cannot load a ledger for + // replay when trapTxHash is set to an invalid transaction + Env env( + *this, + envconfig( + ledgerConfig, + sd.dbPath, + ledgerHash, + Config::REPLAY, + ~sd.trapTxHash), + nullptr, + beast::severities::kDisabled); + BEAST_EXPECT(false); + } + catch (std::runtime_error const&) + { + BEAST_EXPECT(true); + } + catch (...) + { + BEAST_EXPECT(false); + } + } + void testLoadLatest(SetupData const& sd) { @@ -208,7 +339,8 @@ class LedgerLoad_test : public beast::unit_test::suite // create a new env with the ledger "latest" specified for startup Env env( *this, - envconfig(ledgerConfig, sd.dbPath, "latest", Config::LOAD), + envconfig( + ledgerConfig, sd.dbPath, "latest", Config::LOAD, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; @@ -226,7 +358,8 @@ class LedgerLoad_test : public beast::unit_test::suite // create a new env with specific ledger index at startup Env env( *this, - envconfig(ledgerConfig, sd.dbPath, "43", Config::LOAD), + envconfig( + ledgerConfig, sd.dbPath, "43", Config::LOAD, std::nullopt), nullptr, beast::severities::kDisabled); auto jrb = env.rpc("ledger", "current", "full")[jss::result]; @@ -246,6 +379,9 @@ class LedgerLoad_test : public beast::unit_test::suite testLoad(sd); testBadFiles(sd); testLoadByHash(sd); + testReplay(sd); + testReplayTx(sd); + testReplayTxFail(sd); testLoadLatest(sd); testLoadIndex(sd); } diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index 87639c42fcd..d53d27b3185 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include #include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index b312fb69fe0..72db7443110 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/app/LoadFeeTrack_test.cpp b/src/test/app/LoadFeeTrack_test.cpp index f8e73cebd16..fa7489bf1bb 100644 --- a/src/test/app/LoadFeeTrack_test.cpp +++ b/src/test/app/LoadFeeTrack_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp new file mode 100644 index 00000000000..796a3f14c88 --- /dev/null +++ b/src/test/app/MPToken_test.cpp @@ -0,0 +1,2278 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class MPToken_test : public beast::unit_test::suite +{ + void + testCreateValidation(FeatureBitset features) + { + testcase("Create Validate"); + using namespace test::jtx; + Account const alice("alice"); + + // test preflight of MPTokenIssuanceCreate + { + // If the MPT amendment is not enabled, you should not be able to + // create MPTokenIssuances + Env env{*this, features - featureMPTokensV1}; + MPTTester mptAlice(env, alice); + + mptAlice.create({.ownerCount = 0, .err = temDISABLED}); + } + + // test preflight of MPTokenIssuanceCreate + { + Env env{*this, features}; + MPTTester mptAlice(env, alice); + + mptAlice.create({.flags = 0x00000001, .err = temINVALID_FLAG}); + + // tries to set a txfee while not enabling in the flag + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .transferFee = 1, + .metadata = "test", + .err = temMALFORMED}); + + // tries to set a txfee greater than max + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .transferFee = maxTransferFee + 1, + .metadata = "test", + .flags = tfMPTCanTransfer, + .err = temBAD_TRANSFER_FEE}); + + // tries to set a txfee while not enabling transfer + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .transferFee = maxTransferFee, + .metadata = "test", + .err = temMALFORMED}); + + // empty metadata returns error + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .transferFee = 0, + .metadata = "", + .err = temMALFORMED}); + + // MaximumAmout of 0 returns error + mptAlice.create( + {.maxAmt = 0, + .assetScale = 1, + .transferFee = 1, + .metadata = "test", + .err = temMALFORMED}); + + // MaximumAmount larger than 63 bit returns error + mptAlice.create( + {.maxAmt = 0xFFFF'FFFF'FFFF'FFF0, // 18'446'744'073'709'551'600 + .assetScale = 0, + .transferFee = 0, + .metadata = "test", + .err = temMALFORMED}); + mptAlice.create( + {.maxAmt = maxMPTokenAmount + 1, // 9'223'372'036'854'775'808 + .assetScale = 0, + .transferFee = 0, + .metadata = "test", + .err = temMALFORMED}); + } + } + + void + testCreateEnabled(FeatureBitset features) + { + testcase("Create Enabled"); + + using namespace test::jtx; + Account const alice("alice"); + + { + // If the MPT amendment IS enabled, you should be able to create + // MPTokenIssuances + Env env{*this, features}; + MPTTester mptAlice(env, alice); + mptAlice.create( + {.maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807 + .assetScale = 1, + .transferFee = 10, + .metadata = "123", + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | + tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback}); + + // Get the hash for the most recent transaction. + std::string const txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + + Json::Value const result = env.rpc("tx", txHash)[jss::result]; + BEAST_EXPECT( + result[sfMaximumAmount.getJsonName()] == "9223372036854775807"); + } + } + + void + testDestroyValidation(FeatureBitset features) + { + testcase("Destroy Validate"); + + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + // MPTokenIssuanceDestroy (preflight) + { + Env env{*this, features - featureMPTokensV1}; + MPTTester mptAlice(env, alice); + auto const id = makeMptID(env.seq(alice), alice); + mptAlice.destroy({.id = id, .ownerCount = 0, .err = temDISABLED}); + + env.enableFeature(featureMPTokensV1); + + mptAlice.destroy( + {.id = id, .flags = 0x00000001, .err = temINVALID_FLAG}); + } + + // MPTokenIssuanceDestroy (preclaim) + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.destroy( + {.id = makeMptID(env.seq(alice), alice), + .ownerCount = 0, + .err = tecOBJECT_NOT_FOUND}); + + mptAlice.create({.ownerCount = 1}); + + // a non-issuer tries to destroy a mptissuance they didn't issue + mptAlice.destroy({.issuer = bob, .err = tecNO_PERMISSION}); + + // Make sure that issuer can't delete issuance when it still has + // outstanding balance + { + // bob now holds a mptoken object + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + mptAlice.destroy({.err = tecHAS_OBLIGATIONS}); + } + } + } + + void + testDestroyEnabled(FeatureBitset features) + { + testcase("Destroy Enabled"); + + using namespace test::jtx; + Account const alice("alice"); + + // If the MPT amendment IS enabled, you should be able to destroy + // MPTokenIssuances + Env env{*this, features}; + MPTTester mptAlice(env, alice); + + mptAlice.create({.ownerCount = 1}); + + mptAlice.destroy({.ownerCount = 0}); + } + + void + testAuthorizeValidation(FeatureBitset features) + { + testcase("Validate authorize transaction"); + + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const cindy("cindy"); + // Validate amendment enable in MPTokenAuthorize (preflight) + { + Env env{*this, features - featureMPTokensV1}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.authorize( + {.account = bob, + .id = makeMptID(env.seq(alice), alice), + .err = temDISABLED}); + } + + // Validate fields in MPTokenAuthorize (preflight) + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + // The only valid MPTokenAuthorize flag is tfMPTUnauthorize, which + // has a value of 1 + mptAlice.authorize( + {.account = bob, .flags = 0x00000002, .err = temINVALID_FLAG}); + + mptAlice.authorize( + {.account = bob, .holder = bob, .err = temMALFORMED}); + + mptAlice.authorize({.holder = alice, .err = temMALFORMED}); + } + + // Try authorizing when MPTokenIssuance doesn't exist in + // MPTokenAuthorize (preclaim) + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const id = makeMptID(env.seq(alice), alice); + + mptAlice.authorize( + {.holder = bob, .id = id, .err = tecOBJECT_NOT_FOUND}); + + mptAlice.authorize( + {.account = bob, .id = id, .err = tecOBJECT_NOT_FOUND}); + } + + // Test bad scenarios without allowlisting in MPTokenAuthorize + // (preclaim) + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + // bob submits a tx with a holder field + mptAlice.authorize( + {.account = bob, .holder = alice, .err = tecNO_PERMISSION}); + + // alice tries to hold onto her own token + mptAlice.authorize({.account = alice, .err = tecNO_PERMISSION}); + + // the mpt does not enable allowlisting + mptAlice.authorize({.holder = bob, .err = tecNO_AUTH}); + + // bob now holds a mptoken object + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // bob cannot create the mptoken the second time + mptAlice.authorize({.account = bob, .err = tecDUPLICATE}); + + // Check that bob cannot delete MPToken when his balance is + // non-zero + { + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + // bob tries to delete his MPToken, but fails since he still + // holds tokens + mptAlice.authorize( + {.account = bob, + .flags = tfMPTUnauthorize, + .err = tecHAS_OBLIGATIONS}); + + // bob pays back alice 100 tokens + mptAlice.pay(bob, alice, 100); + } + + // bob deletes/unauthorizes his MPToken + mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize}); + + // bob receives error when he tries to delete his MPToken that has + // already been deleted + mptAlice.authorize( + {.account = bob, + .holderCount = 0, + .flags = tfMPTUnauthorize, + .err = tecOBJECT_NOT_FOUND}); + } + + // Test bad scenarios with allow-listing in MPTokenAuthorize (preclaim) + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth}); + + // alice submits a tx without specifying a holder's account + mptAlice.authorize({.err = tecNO_PERMISSION}); + + // alice submits a tx to authorize a holder that hasn't created + // a mptoken yet + mptAlice.authorize({.holder = bob, .err = tecOBJECT_NOT_FOUND}); + + // alice specifys a holder acct that doesn't exist + mptAlice.authorize({.holder = cindy, .err = tecNO_DST}); + + // bob now holds a mptoken object + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // alice tries to unauthorize bob. + // although tx is successful, + // but nothing happens because bob hasn't been authorized yet + mptAlice.authorize({.holder = bob, .flags = tfMPTUnauthorize}); + + // alice authorizes bob + // make sure bob's mptoken has set lsfMPTAuthorized + mptAlice.authorize({.holder = bob}); + + // alice tries authorizes bob again. + // tx is successful, but bob is already authorized, + // so no changes + mptAlice.authorize({.holder = bob}); + + // bob deletes his mptoken + mptAlice.authorize( + {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize}); + } + + // Test mptoken reserve requirement - first two mpts free (doApply) + { + Env env{*this, features}; + auto const acctReserve = env.current()->fees().accountReserve(0); + auto const incReserve = env.current()->fees().increment; + + // 1 drop + BEAST_EXPECT(incReserve > XRPAmount(1)); + MPTTester mptAlice1( + env, + alice, + {.holders = {bob}, + .xrpHolders = acctReserve + (incReserve - 1)}); + mptAlice1.create(); + + MPTTester mptAlice2(env, alice, {.fund = false}); + mptAlice2.create(); + + MPTTester mptAlice3(env, alice, {.fund = false}); + mptAlice3.create({.ownerCount = 3}); + + // first mpt for free + mptAlice1.authorize({.account = bob, .holderCount = 1}); + + // second mpt free + mptAlice2.authorize({.account = bob, .holderCount = 2}); + + mptAlice3.authorize( + {.account = bob, .err = tecINSUFFICIENT_RESERVE}); + + env(pay( + env.master, bob, drops(incReserve + incReserve + incReserve))); + env.close(); + + mptAlice3.authorize({.account = bob, .holderCount = 3}); + } + } + + void + testAuthorizeEnabled(FeatureBitset features) + { + testcase("Authorize Enabled"); + + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + // Basic authorization without allowlisting + { + Env env{*this, features}; + + // alice create mptissuance without allowisting + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + // bob creates a mptoken + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // bob deletes his mptoken + mptAlice.authorize( + {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize}); + } + + // With allowlisting + { + Env env{*this, features}; + + // alice creates a mptokenissuance that requires authorization + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth}); + + // bob creates a mptoken + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // alice authorizes bob + mptAlice.authorize({.account = alice, .holder = bob}); + + // Unauthorize bob's mptoken + mptAlice.authorize( + {.account = alice, + .holder = bob, + .holderCount = 1, + .flags = tfMPTUnauthorize}); + + mptAlice.authorize( + {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize}); + } + + // Holder can have dangling MPToken even if issuance has been destroyed. + // Make sure they can still delete/unauthorize the MPToken + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + // bob creates a mptoken + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // alice deletes her issuance + mptAlice.destroy({.ownerCount = 0}); + + // bob can delete his mptoken even though issuance is no longer + // existent + mptAlice.authorize( + {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize}); + } + } + + void + testSetValidation(FeatureBitset features) + { + testcase("Validate set transaction"); + + using namespace test::jtx; + Account const alice("alice"); // issuer + Account const bob("bob"); // holder + Account const cindy("cindy"); + // Validate fields in MPTokenIssuanceSet (preflight) + { + Env env{*this, features - featureMPTokensV1}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.set( + {.account = bob, + .id = makeMptID(env.seq(alice), alice), + .err = temDISABLED}); + + env.enableFeature(featureMPTokensV1); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // test invalid flag - only valid flags are tfMPTLock (1) and Unlock + // (2) + mptAlice.set( + {.account = alice, + .flags = 0x00000008, + .err = temINVALID_FLAG}); + + // set both lock and unlock flags at the same time will fail + mptAlice.set( + {.account = alice, + .flags = tfMPTLock | tfMPTUnlock, + .err = temINVALID_FLAG}); + + // if the holder is the same as the acct that submitted the tx, + // tx fails + mptAlice.set( + {.account = alice, + .holder = alice, + .flags = tfMPTLock, + .err = temMALFORMED}); + } + + // Validate fields in MPTokenIssuanceSet (preclaim) + // test when a mptokenissuance has disabled locking + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + // alice tries to lock a mptissuance that has disabled locking + mptAlice.set( + {.account = alice, + .flags = tfMPTLock, + .err = tecNO_PERMISSION}); + + // alice tries to unlock mptissuance that has disabled locking + mptAlice.set( + {.account = alice, + .flags = tfMPTUnlock, + .err = tecNO_PERMISSION}); + + // issuer tries to lock a bob's mptoken that has disabled + // locking + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTLock, + .err = tecNO_PERMISSION}); + + // issuer tries to unlock a bob's mptoken that has disabled + // locking + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTUnlock, + .err = tecNO_PERMISSION}); + } + + // Validate fields in MPTokenIssuanceSet (preclaim) + // test when mptokenissuance has enabled locking + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // alice trying to set when the mptissuance doesn't exist yet + mptAlice.set( + {.id = makeMptID(env.seq(alice), alice), + .flags = tfMPTLock, + .err = tecOBJECT_NOT_FOUND}); + + // create a mptokenissuance with locking + mptAlice.create({.ownerCount = 1, .flags = tfMPTCanLock}); + + // a non-issuer acct tries to set the mptissuance + mptAlice.set( + {.account = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION}); + + // trying to set a holder who doesn't have a mptoken + mptAlice.set( + {.holder = bob, + .flags = tfMPTLock, + .err = tecOBJECT_NOT_FOUND}); + + // trying to set a holder who doesn't exist + mptAlice.set( + {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST}); + } + } + + void + testSetEnabled(FeatureBitset features) + { + testcase("Enabled set transaction"); + + using namespace test::jtx; + + // Test locking and unlocking + Env env{*this, features}; + Account const alice("alice"); // issuer + Account const bob("bob"); // holder + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // create a mptokenissuance with locking + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); + + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // locks bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // trying to lock bob's mptoken again will still succeed + // but no changes to the objects + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice locks the mptissuance + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + // alice tries to lock up both mptissuance and mptoken again + // it will not change the flags and both will remain locked. + mptAlice.set({.account = alice, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice unlocks bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); + + // locks up bob's mptoken again + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice unlocks mptissuance + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + + // alice unlocks bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); + + // alice unlocks mptissuance and bob's mptoken again despite that + // they are already unlocked. Make sure this will not change the + // flags + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + } + + void + testPayment(FeatureBitset features) + { + testcase("Payment"); + + using namespace test::jtx; + Account const alice("alice"); // issuer + Account const bob("bob"); // holder + Account const carol("carol"); // holder + + // preflight validation + + // MPT is disabled + { + Env env{*this, features - featureMPTokensV1}; + Account const alice("alice"); + Account const bob("bob"); + + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), bob); + STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)}; + + env(pay(alice, bob, mpt), ter(temDISABLED)); + } + + // MPT is disabled, unsigned request + { + Env env{*this, features - featureMPTokensV1}; + Account const alice("alice"); // issuer + Account const carol("carol"); + auto const USD = alice["USD"]; + + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), carol); + STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)}; + + Json::Value jv; + jv[jss::secret] = alice.name(); + jv[jss::tx_json] = pay(alice, carol, mpt); + jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); + auto const jrr = env.rpc("json", "submit", to_string(jv)); + BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "temDISABLED"); + } + + // Invalid flag + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = bob}); + + for (auto flags : {tfNoRippleDirect, tfLimitQuality}) + env(pay(alice, bob, MPT(10)), + txflags(flags), + ter(temINVALID_FLAG)); + } + + // Invalid combination of send, sendMax, deliverMin, paths + { + Env env{*this, features}; + Account const alice("alice"); + Account const carol("carol"); + + MPTTester mptAlice(env, alice, {.holders = {carol}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = carol}); + + // sendMax and DeliverMin are valid XRP amount, + // but is invalid combination with MPT amount + auto const MPT = mptAlice["MPT"]; + env(pay(alice, carol, MPT(100)), + sendmax(XRP(100)), + ter(temMALFORMED)); + env(pay(alice, carol, MPT(100)), + delivermin(XRP(100)), + ter(temBAD_AMOUNT)); + // sendMax MPT is invalid with IOU or XRP + auto const USD = alice["USD"]; + env(pay(alice, carol, USD(100)), + sendmax(MPT(100)), + ter(temMALFORMED)); + env(pay(alice, carol, XRP(100)), + sendmax(MPT(100)), + ter(temMALFORMED)); + env(pay(alice, carol, USD(100)), + delivermin(MPT(100)), + ter(temBAD_AMOUNT)); + env(pay(alice, carol, XRP(100)), + delivermin(MPT(100)), + ter(temBAD_AMOUNT)); + // sendmax and amount are different MPT issue + test::jtx::MPT const MPT1( + "MPT", makeMptID(env.seq(alice) + 10, alice)); + env(pay(alice, carol, MPT1(100)), + sendmax(MPT(100)), + ter(temMALFORMED)); + // paths is invalid + env(pay(alice, carol, MPT(100)), path(~USD), ter(temMALFORMED)); + } + + // build_path is invalid if MPT + { + Env env{*this, features}; + Account const alice("alice"); + Account const carol("carol"); + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = carol}); + + Json::Value payment; + payment[jss::secret] = alice.name(); + payment[jss::tx_json] = pay(alice, carol, MPT(100)); + + payment[jss::build_path] = true; + auto jrr = env.rpc("json", "submit", to_string(payment)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + BEAST_EXPECT( + jrr[jss::result][jss::error_message] == + "Field 'build_path' not allowed in this context."); + } + + // Can't pay negative amount + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + mptAlice.pay(alice, bob, -1, temBAD_AMOUNT); + + mptAlice.pay(bob, carol, -1, temBAD_AMOUNT); + + mptAlice.pay(bob, alice, -1, temBAD_AMOUNT); + + env(pay(alice, bob, MPT(10)), sendmax(MPT(-1)), ter(temBAD_AMOUNT)); + } + + // Pay to self + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob}); + + mptAlice.pay(bob, bob, 10, temREDUNDANT); + } + + // preclaim validation + + // Destination doesn't exist + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob}); + + Account const bad{"bad"}; + env.memoize(bad); + + mptAlice.pay(bob, bad, 10, tecNO_DST); + } + + // apply validation + + // If RequireAuth is enabled, Payment fails if the receiver is not + // authorized + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + + mptAlice.pay(alice, bob, 100, tecNO_AUTH); + } + + // If RequireAuth is enabled, Payment fails if the sender is not + // authorized + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + + // alice UNAUTHORIZES bob + mptAlice.authorize( + {.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + + // bob fails to send back to alice because he is no longer + // authorize to move his funds! + mptAlice.pay(bob, alice, 100, tecNO_AUTH); + } + + // Non-issuer cannot send to each other if MPTCanTransfer isn't set + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const cindy{"cindy"}; + + MPTTester mptAlice(env, alice, {.holders = {bob, cindy}}); + + // alice creates issuance without MPTCanTransfer + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // cindy creates a MPToken + mptAlice.authorize({.account = cindy}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + // bob tries to send cindy 10 tokens, but fails because canTransfer + // is off + mptAlice.pay(bob, cindy, 10, tecNO_AUTH); + + // bob can send back to alice(issuer) just fine + mptAlice.pay(bob, alice, 10); + } + + // Holder is not authorized + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + // issuer to holder + mptAlice.pay(alice, bob, 100, tecNO_AUTH); + + // holder to issuer + mptAlice.pay(bob, alice, 100, tecNO_AUTH); + + // holder to holder + mptAlice.pay(bob, carol, 50, tecNO_AUTH); + } + + // Payer doesn't have enough funds + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + mptAlice.pay(alice, bob, 100); + + // Pay to another holder + mptAlice.pay(bob, carol, 101, tecPATH_PARTIAL); + + // Pay to the issuer + mptAlice.pay(bob, alice, 101, tecPATH_PARTIAL); + } + + // MPT is locked + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, .flags = tfMPTCanLock | tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + mptAlice.pay(alice, bob, 100); + mptAlice.pay(alice, carol, 100); + + // Global lock + mptAlice.set({.account = alice, .flags = tfMPTLock}); + // Can't send between holders + mptAlice.pay(bob, carol, 1, tecLOCKED); + mptAlice.pay(carol, bob, 2, tecLOCKED); + // Issuer can send + mptAlice.pay(alice, bob, 3); + // Holder can send back to issuer + mptAlice.pay(bob, alice, 4); + + // Global unlock + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + // Individual lock + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + // Can't send between holders + mptAlice.pay(bob, carol, 5, tecLOCKED); + mptAlice.pay(carol, bob, 6, tecLOCKED); + // Issuer can send + mptAlice.pay(alice, bob, 7); + // Holder can send back to issuer + mptAlice.pay(bob, alice, 8); + } + + // Transfer fee + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + // Transfer fee is 10% + mptAlice.create( + {.transferFee = 10'000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + + // Holders create MPToken + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // Payment between the issuer and the holder, no transfer fee. + mptAlice.pay(alice, bob, 2'000); + + // Payment between the holder and the issuer, no transfer fee. + mptAlice.pay(bob, alice, 1'000); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1'000)); + + // Payment between the holders. The sender doesn't have + // enough funds to cover the transfer fee. + mptAlice.pay(bob, carol, 1'000, tecPATH_PARTIAL); + + // Payment between the holders. The sender has enough funds + // but SendMax is not included. + mptAlice.pay(bob, carol, 100, tecPATH_PARTIAL); + + auto const MPT = mptAlice["MPT"]; + // SendMax doesn't cover the fee + env(pay(bob, carol, MPT(100)), + sendmax(MPT(109)), + ter(tecPATH_PARTIAL)); + + // Payment succeeds if sufficient SendMax is included. + // 100 to carol, 10 to issuer + env(pay(bob, carol, MPT(100)), sendmax(MPT(110))); + // 100 to carol, 10 to issuer + env(pay(bob, carol, MPT(100)), sendmax(MPT(115))); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 780)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 200)); + // Payment succeeds if partial payment even if + // SendMax is less than deliver amount + env(pay(bob, carol, MPT(100)), + sendmax(MPT(90)), + txflags(tfPartialPayment)); + // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) = + // 82) + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 282)); + } + + // Insufficient SendMax with no transfer fee + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + // Holders create MPToken + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + mptAlice.pay(alice, bob, 1'000); + + auto const MPT = mptAlice["MPT"]; + // SendMax is less than the amount + env(pay(bob, carol, MPT(100)), + sendmax(MPT(99)), + ter(tecPATH_PARTIAL)); + env(pay(bob, alice, MPT(100)), + sendmax(MPT(99)), + ter(tecPATH_PARTIAL)); + + // Payment succeeds if sufficient SendMax is included. + env(pay(bob, carol, MPT(100)), sendmax(MPT(100))); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 100)); + // Payment succeeds if partial payment + env(pay(bob, carol, MPT(100)), + sendmax(MPT(99)), + txflags(tfPartialPayment)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 199)); + } + + // DeliverMin + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + // Holders create MPToken + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + mptAlice.pay(alice, bob, 1'000); + + auto const MPT = mptAlice["MPT"]; + // Fails even with the partial payment because + // deliver amount < deliverMin + env(pay(bob, alice, MPT(100)), + sendmax(MPT(99)), + delivermin(MPT(100)), + txflags(tfPartialPayment), + ter(tecPATH_PARTIAL)); + // Payment succeeds if deliver amount >= deliverMin + env(pay(bob, alice, MPT(100)), + sendmax(MPT(99)), + delivermin(MPT(99)), + txflags(tfPartialPayment)); + } + + // Issuer fails trying to send more than the maximum amount allowed + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.maxAmt = 100, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + + // issuer sends holder the max amount allowed + mptAlice.pay(alice, bob, 100); + + // issuer tries to exceed max amount + mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL); + } + + // Issuer fails trying to send more than the default maximum + // amount allowed + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob}); + + // issuer sends holder the default max amount allowed + mptAlice.pay(alice, bob, maxMPTokenAmount); + + // issuer tries to exceed max amount + mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL); + } + + // Pay more than max amount fails in the json parser before + // transactor is called + { + Env env{*this, features}; + env.fund(XRP(1'000), alice, bob); + STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)}; + Json::Value jv; + jv[jss::secret] = alice.name(); + jv[jss::tx_json] = pay(alice, bob, mpt); + jv[jss::tx_json][jss::Amount][jss::value] = + to_string(maxMPTokenAmount + 1); + auto const jrr = env.rpc("json", "submit", to_string(jv)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + } + + // Pay maximum amount with the transfer fee, SendMax, and + // partial payment + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.maxAmt = 10'000, + .transferFee = 100, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // issuer sends holder the max amount allowed + mptAlice.pay(alice, bob, 10'000); + + // payment between the holders + env(pay(bob, carol, MPT(10'000)), + sendmax(MPT(10'000)), + txflags(tfPartialPayment)); + // Verify the metadata + auto const meta = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + // Issuer got 10 in the transfer fees + BEAST_EXPECT( + meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName] + [sfOutstandingAmount.fieldName] == "9990"); + // Destination account got 9'990 + BEAST_EXPECT( + meta[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName] + [sfMPTAmount.fieldName] == "9990"); + // Source account spent 10'000 + BEAST_EXPECT( + meta[2u][sfModifiedNode.fieldName][sfPreviousFields.fieldName] + [sfMPTAmount.fieldName] == "10000"); + BEAST_EXPECT( + !meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName] + .isMember(sfMPTAmount.fieldName)); + + // payment between the holders fails without + // partial payment + env(pay(bob, carol, MPT(10'000)), + sendmax(MPT(10'000)), + ter(tecPATH_PARTIAL)); + } + + // Pay maximum allowed amount + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.maxAmt = maxMPTokenAmount, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // issuer sends holder the max amount allowed + mptAlice.pay(alice, bob, maxMPTokenAmount); + BEAST_EXPECT( + mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount)); + + // payment between the holders + mptAlice.pay(bob, carol, maxMPTokenAmount); + BEAST_EXPECT( + mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount)); + // holder pays back to the issuer + mptAlice.pay(carol, alice, maxMPTokenAmount); + BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0)); + } + + // Issuer fails trying to send fund after issuance was destroyed + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob}); + + // alice destroys issuance + mptAlice.destroy({.ownerCount = 0}); + + // alice tries to send bob fund after issuance is destroyed, should + // fail. + mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND); + } + + // Non-existent issuance + { + Env env{*this, features}; + + env.fund(XRP(1'000), alice, bob); + + STAmount const mpt{MPTID{0}, 100}; + env(pay(alice, bob, mpt), ter(tecOBJECT_NOT_FOUND)); + } + + // Issuer fails trying to send to an account, which doesn't own MPT for + // an issuance that was destroyed + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + // alice destroys issuance + mptAlice.destroy({.ownerCount = 0}); + + // alice tries to send bob who doesn't own the MPT after issuance is + // destroyed, it should fail + mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND); + } + + // Issuers issues maximum amount of MPT to a holder, the holder should + // be able to transfer the max amount to someone else + { + Env env{*this, features}; + Account const alice("alice"); + Account const carol("bob"); + Account const bob("carol"); + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.maxAmt = 100, .ownerCount = 1, .flags = tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + mptAlice.pay(alice, bob, 100); + + // transfer max amount to another holder + mptAlice.pay(bob, carol, 100); + } + + // Simple payment + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // issuer to holder + mptAlice.pay(alice, bob, 100); + + // holder to issuer + mptAlice.pay(bob, alice, 100); + + // holder to holder + mptAlice.pay(alice, bob, 100); + mptAlice.pay(bob, carol, 50); + } + } + + void + testDepositPreauth() + { + testcase("DepositPreauth"); + + using namespace test::jtx; + Account const alice("alice"); // issuer + Account const bob("bob"); // holder + Account const diana("diana"); + Account const dpIssuer("dpIssuer"); // holder + + const char credType[] = "abcde"; + + { + Env env(*this); + + env.fund(XRP(50000), diana, dpIssuer); + env.close(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + env(pay(diana, bob, XRP(500))); + env.close(); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // Bob authorize alice + env(deposit::auth(bob, alice)); + env.close(); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + env.close(); + + // Create credentials + env(credentials::create(alice, dpIssuer, credType)); + env.close(); + env(credentials::accept(alice, dpIssuer, credType)); + env.close(); + auto const jv = + credentials::ledgerEntry(env, alice, dpIssuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // alice sends 100 MPT to bob with credentials which aren't required + mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}}); + env.close(); + + // Bob revoke authorization + env(deposit::unauth(bob, alice)); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice sends 100 MPT to bob with credentials, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION, {{credIdx}}); + env.close(); + + // Bob authorize credentials + env(deposit::authCredentials(bob, {{dpIssuer, credType}})); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice sends 100 MPT to bob with credentials + mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}}); + env.close(); + } + + testcase("DepositPreauth disabled featureCredentials"); + { + Env env(*this, supported_amendments() - featureCredentials); + + std::string const credIdx = + "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6" + "E2"; + + env.fund(XRP(50000), diana, dpIssuer); + env.close(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + env(pay(diana, bob, XRP(500))); + env.close(); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice try to send 100 MPT to bob with credentials, amendment + // disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + + // Bob authorize alice + env(deposit::auth(bob, alice)); + env.close(); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + env.close(); + + // alice sends 100 MPT to bob with credentials, amendment disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + + // Bob revoke authorization + env(deposit::unauth(bob, alice)); + env.close(); + + // alice try to send 100 MPT to bob + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice sends 100 MPT to bob with credentials, amendment disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + } + } + + void + testMPTInvalidInTx(FeatureBitset features) + { + testcase("MPT Amount Invalid in Transaction"); + using namespace test::jtx; + + // Validate that every transaction with an amount field, + // which doesn't support MPT, fails. + + // keyed by transaction + amount field + std::set txWithAmounts; + for (auto const& format : TxFormats::getInstance()) + { + for (auto const& e : format.getSOTemplate()) + { + // Transaction has amount fields. + // Exclude pseudo-transaction SetFee. Don't consider + // the Fee field since it's included in every transaction. + if (e.supportMPT() == soeMPTNotSupported && + e.sField().getName() != jss::Fee && + format.getName() != jss::SetFee) + { + txWithAmounts.insert( + format.getName() + e.sField().fieldName); + break; + } + } + } + + Account const alice("alice"); + auto const USD = alice["USD"]; + Account const carol("carol"); + MPTIssue issue(makeMptID(1, alice)); + STAmount mpt{issue, UINT64_C(100)}; + auto const jvb = bridge(alice, USD, alice, USD); + for (auto const& feature : {features, features - featureMPTokensV1}) + { + Env env{*this, feature}; + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), carol); + auto test = [&](Json::Value const& jv, + std::string const& amtField) { + txWithAmounts.erase( + jv[jss::TransactionType].asString() + amtField); + + // tx is signed + auto jtx = env.jt(jv); + Serializer s; + jtx.stx->add(s); + auto jrr = env.rpc("submit", strHex(s.slice())); + BEAST_EXPECT( + jrr[jss::result][jss::error] == "invalidTransaction"); + + // tx is unsigned + Json::Value jv1; + jv1[jss::secret] = alice.name(); + jv1[jss::tx_json] = jv; + jrr = env.rpc("json", "submit", to_string(jv1)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + + jrr = env.rpc("json", "sign", to_string(jv1)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + }; + // All transactions with sfAmount, which don't support MPT + // and transactions with amount fields, which can't be MPT + + // AMMCreate + auto ammCreate = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMCreate; + jv[jss::Account] = alice.human(); + jv[jss::Amount] = (field.fieldName == sfAmount.fieldName) + ? mpt.getJson(JsonOptions::none) + : "100000000"; + jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName) + ? mpt.getJson(JsonOptions::none) + : "100000000"; + jv[jss::TradingFee] = 0; + test(jv, field.fieldName); + }; + ammCreate(sfAmount); + ammCreate(sfAmount2); + // AMMDeposit + auto ammDeposit = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMDeposit; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + jv[jss::Flags] = tfSingleAsset; + test(jv, field.fieldName); + }; + for (SField const& field : + {std::ref(sfAmount), + std::ref(sfAmount2), + std::ref(sfEPrice), + std::ref(sfLPTokenOut)}) + ammDeposit(field); + // AMMWithdraw + auto ammWithdraw = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMWithdraw; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[jss::Flags] = tfSingleAsset; + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv, field.fieldName); + }; + ammWithdraw(sfAmount); + for (SField const& field : + {std::ref(sfAmount2), + std::ref(sfEPrice), + std::ref(sfLPTokenIn)}) + ammWithdraw(field); + // AMMBid + auto ammBid = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMBid; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv, field.fieldName); + }; + ammBid(sfBidMin); + ammBid(sfBidMax); + // AMMClawback + { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMClawback; + jv[jss::Account] = alice.human(); + jv[jss::Holder] = carol.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // CheckCash + auto checkCash = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::CheckCash; + jv[jss::Account] = alice.human(); + jv[sfCheckID.fieldName] = to_string(uint256{1}); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv, field.fieldName); + }; + checkCash(sfAmount); + checkCash(sfDeliverMin); + // CheckCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::CheckCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::SendMax] = mpt.getJson(JsonOptions::none); + test(jv, jss::SendMax.c_str()); + } + // EscrowCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::EscrowCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // OfferCreate + { + Json::Value jv = offer(alice, USD(100), mpt); + test(jv, jss::TakerPays.c_str()); + jv = offer(alice, mpt, USD(100)); + test(jv, jss::TakerGets.c_str()); + } + // PaymentChannelCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::SettleDelay] = 1; + jv[sfPublicKey.fieldName] = strHex(alice.pk().slice()); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // PaymentChannelFund + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelFund; + jv[jss::Account] = alice.human(); + jv[sfChannel.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // PaymentChannelClaim + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelClaim; + jv[jss::Account] = alice.human(); + jv[sfChannel.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // NFTokenCreateOffer + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenCreateOffer; + jv[jss::Account] = alice.human(); + jv[sfNFTokenID.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // NFTokenAcceptOffer + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenAcceptOffer; + jv[jss::Account] = alice.human(); + jv[sfNFTokenBrokerFee.fieldName] = + mpt.getJson(JsonOptions::none); + test(jv, sfNFTokenBrokerFee.fieldName); + } + // NFTokenMint + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenMint; + jv[jss::Account] = alice.human(); + jv[sfNFTokenTaxon.fieldName] = 1; + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv, jss::Amount.c_str()); + } + // TrustSet + auto trustSet = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::TrustSet; + jv[jss::Account] = alice.human(); + jv[jss::Flags] = 0; + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv, field.fieldName); + }; + trustSet(sfLimitAmount); + trustSet(sfFee); + // XChainCommit + { + Json::Value const jv = xchain_commit(alice, jvb, 1, mpt); + test(jv, jss::Amount.c_str()); + } + // XChainClaim + { + Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice); + test(jv, jss::Amount.c_str()); + } + // XChainCreateClaimID + { + Json::Value const jv = + xchain_create_claim_id(alice, jvb, mpt, alice); + test(jv, sfSignatureReward.fieldName); + } + // XChainAddClaimAttestation + { + Json::Value const jv = claim_attestation( + alice, + jvb, + alice, + mpt, + alice, + true, + 1, + alice, + signer(alice)); + test(jv, jss::Amount.c_str()); + } + // XChainAddAccountCreateAttestation + { + Json::Value jv = create_account_attestation( + alice, + jvb, + alice, + mpt, + XRP(10), + alice, + false, + 1, + alice, + signer(alice)); + for (auto const& field : + {sfAmount.fieldName, sfSignatureReward.fieldName}) + { + jv[field] = mpt.getJson(JsonOptions::none); + test(jv, field); + } + } + // XChainAccountCreateCommit + { + Json::Value jv = sidechain_xchain_account_create( + alice, jvb, alice, mpt, XRP(10)); + for (auto const& field : + {sfAmount.fieldName, sfSignatureReward.fieldName}) + { + jv[field] = mpt.getJson(JsonOptions::none); + test(jv, field); + } + } + // XChain[Create|Modify]Bridge + auto bridgeTx = [&](Json::StaticString const& tt, + STAmount const& rewardAmount, + STAmount const& minAccountAmount, + std::string const& field) { + Json::Value jv; + jv[jss::TransactionType] = tt; + jv[jss::Account] = alice.human(); + jv[sfXChainBridge.fieldName] = jvb; + jv[sfSignatureReward.fieldName] = + rewardAmount.getJson(JsonOptions::none); + jv[sfMinAccountCreateAmount.fieldName] = + minAccountAmount.getJson(JsonOptions::none); + test(jv, field); + }; + auto reward = STAmount{sfSignatureReward, mpt}; + auto minAmount = STAmount{sfMinAccountCreateAmount, USD(10)}; + for (SField const& field : + {std::ref(sfSignatureReward), + std::ref(sfMinAccountCreateAmount)}) + { + bridgeTx( + jss::XChainCreateBridge, + reward, + minAmount, + field.fieldName); + bridgeTx( + jss::XChainModifyBridge, + reward, + minAmount, + field.fieldName); + reward = STAmount{sfSignatureReward, USD(10)}; + minAmount = STAmount{sfMinAccountCreateAmount, mpt}; + } + } + BEAST_EXPECT(txWithAmounts.empty()); + } + + void + testTxJsonMetaFields(FeatureBitset features) + { + // checks synthetically injected mptissuanceid from `tx` response + testcase("Test synthetic fields from tx response"); + + using namespace test::jtx; + + Account const alice{"alice"}; + + Env env{*this, features}; + MPTTester mptAlice(env, alice); + + mptAlice.create(); + + std::string const txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + BEAST_EXPECTS( + txHash == + "E11F0E0CA14219922B7881F060B9CEE67CFBC87E4049A441ED2AE348FF8FAC" + "0E", + txHash); + Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta]; + auto const id = meta[jss::mpt_issuance_id].asString(); + // Expect mpt_issuance_id field + BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id)); + BEAST_EXPECT(id == to_string(mptAlice.issuanceID())); + BEAST_EXPECTS( + id == "00000004AE123A8556F3CF91154711376AFB0F894F832B3D", id); + } + + void + testClawbackValidation(FeatureBitset features) + { + testcase("MPT clawback validations"); + using namespace test::jtx; + + // Make sure clawback cannot work when featureMPTokensV1 is disabled + { + Env env(*this, features - featureMPTokensV1); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const USD = alice["USD"]; + auto const mpt = ripple::test::jtx::MPT( + alice.name(), makeMptID(env.seq(alice), alice)); + + env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED)); + env.close(); + + env(claw(alice, mpt(5)), ter(temDISABLED)); + env.close(); + + env(claw(alice, mpt(5), bob), ter(temDISABLED)); + env.close(); + } + + // Test preflight + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const USD = alice["USD"]; + auto const mpt = ripple::test::jtx::MPT( + alice.name(), makeMptID(env.seq(alice), alice)); + + // clawing back IOU from a MPT holder fails + env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED)); + env.close(); + + // clawing back MPT without specifying a holder fails + env(claw(alice, mpt(5)), ter(temMALFORMED)); + env.close(); + + // clawing back zero amount fails + env(claw(alice, mpt(0), bob), ter(temBAD_AMOUNT)); + env.close(); + + // alice can't claw back from herself + env(claw(alice, mpt(5), alice), ter(temMALFORMED)); + env.close(); + + // can't clawback negative amount + env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT)); + env.close(); + } + + // Preclaim - clawback fails when MPTCanClawback is disabled on issuance + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // enable asfAllowTrustLineClawback for alice + env(fset(alice, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(alice, asfAllowTrustLineClawback)); + + // Create issuance without enabling clawback + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = bob}); + + mptAlice.pay(alice, bob, 100); + + // alice cannot clawback before she didn't enable MPTCanClawback + // asfAllowTrustLineClawback has no effect + mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); + } + + // Preclaim - test various scenarios + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + env.fund(XRP(1000), carol); + env.close(); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + auto const fakeMpt = ripple::test::jtx::MPT( + alice.name(), makeMptID(env.seq(alice), alice)); + + // issuer tries to clawback MPT where issuance doesn't exist + env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND)); + env.close(); + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + + // alice tries to clawback from someone who doesn't have MPToken + mptAlice.claw(alice, bob, 1, tecOBJECT_NOT_FOUND); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // clawback fails because bob currently has a balance of zero + mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + // carol fails tries to clawback from bob because he is not the + // issuer + mptAlice.claw(carol, bob, 1, tecNO_PERMISSION); + } + + // clawback more than max amount + // fails in the json parser before + // transactor is called + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const mpt = ripple::test::jtx::MPT( + alice.name(), makeMptID(env.seq(alice), alice)); + + Json::Value jv = claw(alice, mpt(1), bob); + jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1); + Json::Value jv1; + jv1[jss::secret] = alice.name(); + jv1[jss::tx_json] = jv; + auto const jrr = env.rpc("json", "submit", to_string(jv1)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + } + } + + void + testClawback(FeatureBitset features) + { + testcase("MPT Clawback"); + using namespace test::jtx; + + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + mptAlice.claw(alice, bob, 1); + + mptAlice.claw(alice, bob, 1000); + + // clawback fails because bob currently has a balance of zero + mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS); + } + + // Test that globally locked funds can be clawed + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock | tfMPTCanClawback}); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + mptAlice.claw(alice, bob, 100); + } + + // Test that individually locked funds can be clawed + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock | tfMPTCanClawback}); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + mptAlice.claw(alice, bob, 100); + } + + // Test that unauthorized funds can be clawed back + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanClawback | tfMPTRequireAuth}); + + // bob creates a MPToken + mptAlice.authorize({.account = bob}); + + // alice authorizes bob + mptAlice.authorize({.account = alice, .holder = bob}); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + // alice unauthorizes bob + mptAlice.authorize( + {.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + + mptAlice.claw(alice, bob, 100); + } + } + + void + testTokensEquality() + { + using namespace test::jtx; + testcase("Tokens Equality"); + Currency const cur1{to_currency("CU1")}; + Currency const cur2{to_currency("CU2")}; + Account const gw1{"gw1"}; + Account const gw2{"gw2"}; + MPTID const mpt1 = makeMptID(1, gw1); + MPTID const mpt1a = makeMptID(1, gw1); + MPTID const mpt2 = makeMptID(1, gw2); + MPTID const mpt3 = makeMptID(2, gw2); + Asset const assetCur1Gw1{Issue{cur1, gw1}}; + Asset const assetCur1Gw1a{Issue{cur1, gw1}}; + Asset const assetCur2Gw1{Issue{cur2, gw1}}; + Asset const assetCur2Gw2{Issue{cur2, gw2}}; + Asset const assetMpt1Gw1{mpt1}; + Asset const assetMpt1Gw1a{mpt1a}; + Asset const assetMpt1Gw2{mpt2}; + Asset const assetMpt2Gw2{mpt3}; + + // Assets holding Issue + // Currencies are equal regardless of the issuer + BEAST_EXPECT(equalTokens(assetCur1Gw1, assetCur1Gw1a)); + BEAST_EXPECT(equalTokens(assetCur2Gw1, assetCur2Gw2)); + // Currencies are different regardless of whether the issuers + // are the same or not + BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw1)); + BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw2)); + + // Assets holding MPTIssue + // MPTIDs are the same if the sequence and the issuer are the same + BEAST_EXPECT(equalTokens(assetMpt1Gw1, assetMpt1Gw1a)); + // MPTIDs are different if sequence and the issuer don't match + BEAST_EXPECT(!equalTokens(assetMpt1Gw1, assetMpt1Gw2)); + BEAST_EXPECT(!equalTokens(assetMpt1Gw2, assetMpt2Gw2)); + + // Assets holding Issue and MPTIssue + BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetMpt1Gw1)); + BEAST_EXPECT(!equalTokens(assetMpt2Gw2, assetCur2Gw2)); + } + + void + testHelperFunctions() + { + using namespace test::jtx; + Account const gw{"gw"}; + Asset const asset1{makeMptID(1, gw)}; + Asset const asset2{makeMptID(2, gw)}; + Asset const asset3{makeMptID(3, gw)}; + STAmount const amt1{asset1, 100}; + STAmount const amt2{asset2, 100}; + STAmount const amt3{asset3, 10'000}; + + { + testcase("Test STAmount MPT arithmetics"); + using namespace std::string_literals; + STAmount res = multiply(amt1, amt2, asset3); + BEAST_EXPECT(res == amt3); + + res = mulRound(amt1, amt2, asset3, true); + BEAST_EXPECT(res == amt3); + + res = mulRoundStrict(amt1, amt2, asset3, true); + BEAST_EXPECT(res == amt3); + + // overflow, any value > 3037000499ull + STAmount mptOverflow{asset2, UINT64_C(3037000500)}; + try + { + res = multiply(mptOverflow, mptOverflow, asset3); + fail("should throw runtime exception 1"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what()); + } + // overflow, (v1 >> 32) * v2 > 2147483648ull + mptOverflow = STAmount{asset2, UINT64_C(2147483648)}; + uint64_t const mantissa = (2ull << 32) + 2; + try + { + res = multiply(STAmount{asset1, mantissa}, mptOverflow, asset3); + fail("should throw runtime exception 2"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what()); + } + } + + { + testcase("Test MPTAmount arithmetics"); + MPTAmount mptAmt1{100}; + MPTAmount const mptAmt2{100}; + BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200}); + BEAST_EXPECT(mptAmt1 == 200); + BEAST_EXPECT((mptAmt1 -= mptAmt2) == mptAmt1); + BEAST_EXPECT(mptAmt1 == mptAmt2); + BEAST_EXPECT(mptAmt1 == 100); + BEAST_EXPECT(MPTAmount::minPositiveAmount() == MPTAmount{1}); + } + + { + testcase("Test MPTIssue from/to Json"); + MPTIssue const issue1{asset1.get()}; + Json::Value const jv = to_json(issue1); + BEAST_EXPECT( + jv[jss::mpt_issuance_id] == to_string(asset1.get())); + BEAST_EXPECT(issue1 == mptIssueFromJson(jv)); + } + + { + testcase("Test Asset from/to Json"); + Json::Value const jv = to_json(asset1); + BEAST_EXPECT( + jv[jss::mpt_issuance_id] == to_string(asset1.get())); + BEAST_EXPECT( + to_string(jv) == + "{\"mpt_issuance_id\":" + "\"00000001A407AF5856CCF3C42619DAA925813FC955C72983\"}"); + BEAST_EXPECT(asset1 == assetFromJson(jv)); + } + } + +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + + // MPTokenIssuanceCreate + testCreateValidation(all); + testCreateEnabled(all); + + // MPTokenIssuanceDestroy + testDestroyValidation(all); + testDestroyEnabled(all); + + // MPTokenAuthorize + testAuthorizeValidation(all); + testAuthorizeEnabled(all); + + // MPTokenIssuanceSet + testSetValidation(all); + testSetEnabled(all); + + // MPT clawback + testClawbackValidation(all); + testClawback(all); + + // Test Direct Payment + testPayment(all); + testDepositPreauth(); + + // Test MPT Amount is invalid in Tx, which don't support MPT + testMPTInvalidInTx(all); + + // Test parsed MPTokenIssuanceID in API response metadata + testTxJsonMetaFields(all); + + // Test tokens equality + testTokensEquality(); + + // Test helpers + testHelperFunctions(); + } +}; + +BEAST_DEFINE_TESTSUITE_PRIO(MPToken, tx, ripple, 2); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index 0f21cab0d63..0e25300417c 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -17,20 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include namespace ripple { namespace test { diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 0e151b38d0a..77d85d9011b 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -15,10 +15,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index abd9ed56e83..a56f0a45674 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include @@ -28,16 +28,6 @@ namespace ripple { class NFTokenBurnBaseUtil_test : public beast::unit_test::suite { - // Helper function that returns the owner count of an account root. - static std::uint32_t - ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) - { - std::uint32_t ret{0}; - if (auto const sleAcct = env.le(acct)) - ret = sleAcct->at(sfOwnerCount); - return ret; - } - // Helper function that returns the number of nfts owned by an account. static std::uint32_t nftCount(test::jtx::Env& env, test::jtx::Account const& acct) @@ -80,6 +70,73 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite return nftokenID; }; + // printNFTPages is a helper function that may be used for debugging. + // + // It uses the ledger RPC command to show the NFT pages in the ledger. + // This parameter controls how noisy the output is. + enum Volume : bool { + quiet = false, + noisy = true, + }; + + void + printNFTPages(test::jtx::Env& env, Volume vol) + { + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::binary] = false; + { + Json::Value jrr = env.rpc( + "json", + "ledger_data", + boost::lexical_cast(jvParams)); + + // Iterate the state and print all NFTokenPages. + if (!jrr.isMember(jss::result) || + !jrr[jss::result].isMember(jss::state)) + { + std::cout << "No ledger state found!" << std::endl; + return; + } + Json::Value& state = jrr[jss::result][jss::state]; + if (!state.isArray()) + { + std::cout << "Ledger state is not array!" << std::endl; + return; + } + for (Json::UInt i = 0; i < state.size(); ++i) + { + if (state[i].isMember(sfNFTokens.jsonName) && + state[i][sfNFTokens.jsonName].isArray()) + { + std::uint32_t tokenCount = + state[i][sfNFTokens.jsonName].size(); + std::cout << tokenCount << " NFtokens in page " + << state[i][jss::index].asString() << std::endl; + + if (vol == noisy) + { + std::cout << state[i].toStyledString() << std::endl; + } + else + { + if (tokenCount > 0) + std::cout << "first: " + << state[i][sfNFTokens.jsonName][0u] + .toStyledString() + << std::endl; + if (tokenCount > 1) + std::cout + << "last: " + << state[i][sfNFTokens.jsonName][tokenCount - 1] + .toStyledString() + << std::endl; + } + } + } + } + } + void testBurnRandom(FeatureBitset features) { @@ -256,7 +313,8 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite // Otherwise either alice or minter can burn. AcctStat& burner = owner.acct == becky.acct ? *(stats[acctDist(engine)]) - : mintDist(engine) ? alice : minter; + : mintDist(engine) ? alice + : minter; if (owner.acct == burner.acct) env(token::burn(burner, nft)); @@ -297,76 +355,10 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite Env env{*this, features}; env.fund(XRP(1000), alice); - // printNFTPages is a lambda that may be used for debugging. - // - // It uses the ledger RPC command to show the NFT pages in the ledger. - // This parameter controls how noisy the output is. - enum Volume : bool { - quiet = false, - noisy = true, - }; - - [[maybe_unused]] auto printNFTPages = [&env](Volume vol) { - Json::Value jvParams; - jvParams[jss::ledger_index] = "current"; - jvParams[jss::binary] = false; - { - Json::Value jrr = env.rpc( - "json", - "ledger_data", - boost::lexical_cast(jvParams)); - - // Iterate the state and print all NFTokenPages. - if (!jrr.isMember(jss::result) || - !jrr[jss::result].isMember(jss::state)) - { - std::cout << "No ledger state found!" << std::endl; - return; - } - Json::Value& state = jrr[jss::result][jss::state]; - if (!state.isArray()) - { - std::cout << "Ledger state is not array!" << std::endl; - return; - } - for (Json::UInt i = 0; i < state.size(); ++i) - { - if (state[i].isMember(sfNFTokens.jsonName) && - state[i][sfNFTokens.jsonName].isArray()) - { - std::uint32_t tokenCount = - state[i][sfNFTokens.jsonName].size(); - std::cout << tokenCount << " NFTokens in page " - << state[i][jss::index].asString() - << std::endl; - - if (vol == noisy) - { - std::cout << state[i].toStyledString() << std::endl; - } - else - { - if (tokenCount > 0) - std::cout << "first: " - << state[i][sfNFTokens.jsonName][0u] - .toStyledString() - << std::endl; - if (tokenCount > 1) - std::cout << "last: " - << state[i][sfNFTokens.jsonName] - [tokenCount - 1] - .toStyledString() - << std::endl; - } - } - } - } - }; - // A lambda that generates 96 nfts packed into three pages of 32 each. - auto genPackedTokens = [this, &env, &alice]( - std::vector& nfts) { - nfts.clear(); + // Returns a sorted vector of the NFTokenIDs packed into the pages. + auto genPackedTokens = [this, &env, &alice]() { + std::vector nfts; nfts.reserve(96); // We want to create fully packed NFT pages. This is a little @@ -441,23 +433,24 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite // has changed. BEAST_EXPECT(pageCount == 3); } + return nfts; }; - - // Generate three packed pages. Then burn the tokens in order from - // first to last. This exercises specific cases where coalescing - // pages is not possible. - std::vector nfts; - genPackedTokens(nfts); - BEAST_EXPECT(nftCount(env, alice) == 96); - BEAST_EXPECT(ownerCount(env, alice) == 3); - - for (uint256 const& nft : nfts) { - env(token::burn(alice, {nft})); - env.close(); + // Generate three packed pages. Then burn the tokens in order from + // first to last. This exercises specific cases where coalescing + // pages is not possible. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + for (uint256 const& nft : nfts) + { + env(token::burn(alice, {nft})); + env.close(); + } + BEAST_EXPECT(nftCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 0); } - BEAST_EXPECT(nftCount(env, alice) == 0); - BEAST_EXPECT(ownerCount(env, alice) == 0); // A lambda verifies that the ledger no longer contains any NFT pages. auto checkNoTokenPages = [this, &env]() { @@ -479,48 +472,421 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite } }; checkNoTokenPages(); + { + // Generate three packed pages. Then burn the tokens in order from + // last to first. This exercises different specific cases where + // coalescing pages is not possible. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Verify that that all three pages are present and remember the + // indexes. + auto lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + + uint256 const middleNFTokenPageIndex = + lastNFTokenPage->at(sfPreviousPageMin); + auto middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + + uint256 const firstNFTokenPageIndex = + middleNFTokenPage->at(sfPreviousPageMin); + auto firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + if (!BEAST_EXPECT(firstNFTokenPage)) + return; + + // Burn almost all the tokens in the very last page. + for (int i = 0; i < 31; ++i) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } - // Generate three packed pages. Then burn the tokens in order from - // last to first. This exercises different specific cases where - // coalescing pages is not possible. - genPackedTokens(nfts); - BEAST_EXPECT(nftCount(env, alice) == 96); - BEAST_EXPECT(ownerCount(env, alice) == 3); + // Verify that the last page is still present and contains just one + // NFT. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; - std::reverse(nfts.begin(), nfts.end()); - for (uint256 const& nft : nfts) - { - env(token::burn(alice, {nft})); + BEAST_EXPECT( + lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1); + BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); + + // Delete the last token from the last page. + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); env.close(); + + if (features[fixNFTokenPageLinks]) + { + // Removing the last token from the last page deletes the + // _previous_ page because we need to preserve that last + // page an an anchor. The contents of the next-to-last page + // are moved into the last page. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(lastNFTokenPage); + BEAST_EXPECT( + lastNFTokenPage->at(~sfPreviousPageMin) == + firstNFTokenPageIndex); + BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); + BEAST_EXPECT( + lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32); + + // The "middle" page should be gone. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + BEAST_EXPECT(!middleNFTokenPage); + + // The "first" page should still be present and linked to + // the last page. + firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + BEAST_EXPECT(firstNFTokenPage); + BEAST_EXPECT( + !firstNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT( + firstNFTokenPage->at(~sfNextPageMin) == + lastNFTokenPage->key()); + BEAST_EXPECT( + lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32); + } + else + { + // Removing the last token from the last page deletes the last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(!lastNFTokenPage); + + // The "middle" page is still present, but has lost the + // NextPageMin field. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + BEAST_EXPECT( + middleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // Delete the rest of the NFTokens. + while (!nfts.empty()) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } + BEAST_EXPECT(nftCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 0); } - BEAST_EXPECT(nftCount(env, alice) == 0); - BEAST_EXPECT(ownerCount(env, alice) == 0); checkNoTokenPages(); + { + // Generate three packed pages. Then burn all tokens in the middle + // page. This exercises the case where a page is removed between + // two fully populated pages. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Verify that that all three pages are present and remember the + // indexes. + auto lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + + uint256 const middleNFTokenPageIndex = + lastNFTokenPage->at(sfPreviousPageMin); + auto middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + + uint256 const firstNFTokenPageIndex = + middleNFTokenPage->at(sfPreviousPageMin); + auto firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + if (!BEAST_EXPECT(firstNFTokenPage)) + return; + + for (std::size_t i = 32; i < 64; ++i) + { + env(token::burn(alice, nfts[i])); + env.close(); + } + nfts.erase(nfts.begin() + 32, nfts.begin() + 64); + BEAST_EXPECT(nftCount(env, alice) == 64); + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // Verify that middle page is gone and the links in the two + // remaining pages are correct. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + BEAST_EXPECT(!middleNFTokenPage); + + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); + BEAST_EXPECT( + lastNFTokenPage->getFieldH256(sfPreviousPageMin) == + firstNFTokenPageIndex); + + firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + BEAST_EXPECT( + firstNFTokenPage->getFieldH256(sfNextPageMin) == + keylet::nftpage_max(alice).key); + BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin)); + + // Burn the remaining nfts. + for (uint256 const& nft : nfts) + { + env(token::burn(alice, {nft})); + env.close(); + } + BEAST_EXPECT(nftCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 0); + } + checkNoTokenPages(); + { + // Generate three packed pages. Then burn all the tokens in the + // first page followed by all the tokens in the last page. This + // exercises a specific case where coalescing pages is not possible. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Verify that that all three pages are present and remember the + // indexes. + auto lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + + uint256 const middleNFTokenPageIndex = + lastNFTokenPage->at(sfPreviousPageMin); + auto middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + + uint256 const firstNFTokenPageIndex = + middleNFTokenPage->at(sfPreviousPageMin); + auto firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + if (!BEAST_EXPECT(firstNFTokenPage)) + return; + + // Burn all the tokens in the first page. + std::reverse(nfts.begin(), nfts.end()); + for (int i = 0; i < 32; ++i) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } - // Generate three packed pages. Then burn all tokens in the middle - // page. This exercises the case where a page is removed between - // two fully populated pages. - genPackedTokens(nfts); - BEAST_EXPECT(nftCount(env, alice) == 96); - BEAST_EXPECT(ownerCount(env, alice) == 3); + // Verify the first page is gone. + firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + BEAST_EXPECT(!firstNFTokenPage); + + // Check the links in the other two pages. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin)); + + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); + + // Burn all the tokens in the last page. + std::reverse(nfts.begin(), nfts.end()); + for (int i = 0; i < 32; ++i) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } - for (std::size_t i = 32; i < 64; ++i) - { - env(token::burn(alice, nfts[i])); - env.close(); + if (features[fixNFTokenPageLinks]) + { + // Removing the last token from the last page deletes the + // _previous_ page because we need to preserve that last + // page an an anchor. The contents of the next-to-last page + // are moved into the last page. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(lastNFTokenPage); + BEAST_EXPECT( + !lastNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); + BEAST_EXPECT( + lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32); + + // The "middle" page should be gone. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + BEAST_EXPECT(!middleNFTokenPage); + + // The "first" page should still be gone. + firstNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), firstNFTokenPageIndex)); + BEAST_EXPECT(!firstNFTokenPage); + } + else + { + // Removing the last token from the last page deletes the last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(!lastNFTokenPage); + + // The "middle" page is still present, but has lost the + // NextPageMin field. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + BEAST_EXPECT( + !middleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin)); + } + + // Delete the rest of the NFTokens. + while (!nfts.empty()) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } + BEAST_EXPECT(nftCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, alice) == 0); } - nfts.erase(nfts.begin() + 32, nfts.begin() + 64); - BEAST_EXPECT(nftCount(env, alice) == 64); - BEAST_EXPECT(ownerCount(env, alice) == 2); + checkNoTokenPages(); - // Burn the remaining nfts. - for (uint256 const& nft : nfts) + if (features[fixNFTokenPageLinks]) { - env(token::burn(alice, {nft})); - env.close(); + // Exercise the invariant that the final NFTokenPage of a directory + // may not be removed if there are NFTokens in other pages of the + // directory. + // + // We're going to fire an Invariant failure that is difficult to + // cause. We do it here because the tools are here. + // + // See Invariants_test.cpp for examples of other invariant tests + // that this one is modeled after. + + // Generate three closely packed NFTokenPages. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Burn almost all the tokens in the very last page. + for (int i = 0; i < 31; ++i) + { + env(token::burn(alice, {nfts.back()})); + nfts.pop_back(); + env.close(); + } + { + // Create an ApplyContext we can use to run the invariant + // checks. These variables must outlive the ApplyContext. + OpenView ov{*env.current()}; + STTx tx{ttACCOUNT_SET, [](STObject&) {}}; + test::StreamSink sink{beast::severities::kWarning}; + beast::Journal jlog{sink}; + ApplyContext ac{ + env.app(), + ov, + tx, + tesSUCCESS, + env.current()->fees().base, + tapNONE, + jlog}; + + // Verify that the last page is present and contains one NFT. + auto lastNFTokenPage = + ac.view().peek(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + BEAST_EXPECT( + lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1); + + // Erase that last page. + ac.view().erase(lastNFTokenPage); + + // Exercise the invariant. + TER terActual = tesSUCCESS; + for (TER const& terExpect : + {TER(tecINVARIANT_FAILED), TER(tefINVARIANT_FAILED)}) + { + terActual = ac.checkInvariants(terActual, XRPAmount{}); + BEAST_EXPECT(terExpect == terActual); + BEAST_EXPECT( + sink.messages().str().starts_with("Invariant failed:")); + // uncomment to log the invariant failure message + // log << " --> " << sink.messages().str() << std::endl; + BEAST_EXPECT( + sink.messages().str().find( + "Last NFT page deleted with non-empty directory") != + std::string::npos); + } + } + { + // Create an ApplyContext we can use to run the invariant + // checks. These variables must outlive the ApplyContext. + OpenView ov{*env.current()}; + STTx tx{ttACCOUNT_SET, [](STObject&) {}}; + test::StreamSink sink{beast::severities::kWarning}; + beast::Journal jlog{sink}; + ApplyContext ac{ + env.app(), + ov, + tx, + tesSUCCESS, + env.current()->fees().base, + tapNONE, + jlog}; + + // Verify that the middle page is present. + auto lastNFTokenPage = + ac.view().peek(keylet::nftpage_max(alice)); + auto middleNFTokenPage = ac.view().peek(keylet::nftpage( + keylet::nftpage_min(alice), + lastNFTokenPage->getFieldH256(sfPreviousPageMin))); + BEAST_EXPECT(middleNFTokenPage); + + // Remove the NextMinPage link from the middle page to fire + // the invariant. + middleNFTokenPage->makeFieldAbsent(sfNextPageMin); + ac.view().update(middleNFTokenPage); + + // Exercise the invariant. + TER terActual = tesSUCCESS; + for (TER const& terExpect : + {TER(tecINVARIANT_FAILED), TER(tefINVARIANT_FAILED)}) + { + terActual = ac.checkInvariants(terActual, XRPAmount{}); + BEAST_EXPECT(terExpect == terActual); + BEAST_EXPECT( + sink.messages().str().starts_with("Invariant failed:")); + // uncomment to log the invariant failure message + // log << " --> " << sink.messages().str() << std::endl; + BEAST_EXPECT( + sink.messages().str().find("Lost NextMinPage link") != + std::string::npos); + } + } } - BEAST_EXPECT(nftCount(env, alice) == 0); - checkNoTokenPages(); } void @@ -778,12 +1144,238 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite } } + void + exerciseBrokenLinks(FeatureBitset features) + { + // Amendment fixNFTokenPageLinks prevents the breakage we want + // to observe. + if (features[fixNFTokenPageLinks]) + return; + + // a couple of directory merging scenarios that can only be tested by + // inserting and deleting in an ordered fashion. We do that testing + // now. + testcase("Exercise broken links"); + + using namespace test::jtx; + + Account const alice{"alice"}; + Account const minter{"minter"}; + + Env env{*this, features}; + env.fund(XRP(1000), alice, minter); + + // A lambda that generates 96 nfts packed into three pages of 32 each. + // Returns a sorted vector of the NFTokenIDs packed into the pages. + auto genPackedTokens = [this, &env, &alice, &minter]() { + std::vector nfts; + nfts.reserve(96); + + // We want to create fully packed NFT pages. This is a little + // tricky since the system currently in place is inclined to + // assign consecutive tokens to only 16 entries per page. + // + // By manipulating the internal form of the taxon we can force + // creation of NFT pages that are completely full. This lambda + // tells us the taxon value we should pass in in order for the + // internal representation to match the passed in value. + auto internalTaxon = [&env]( + Account const& acct, + std::uint32_t taxon) -> std::uint32_t { + std::uint32_t tokenSeq = + env.le(acct)->at(~sfMintedNFTokens).value_or(0); + + // If fixNFTokenRemint amendment is on, we must + // add FirstNFTokenSequence. + if (env.current()->rules().enabled(fixNFTokenRemint)) + tokenSeq += env.le(acct) + ->at(~sfFirstNFTokenSequence) + .value_or(env.seq(acct)); + + return toUInt32( + nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon))); + }; + + for (std::uint32_t i = 0; i < 96; ++i) + { + // In order to fill the pages we use the taxon to break them + // into groups of 16 entries. By having the internal + // representation of the taxon go... + // 0, 3, 2, 5, 4, 7... + // in sets of 16 NFTs we can get each page to be fully + // populated. + std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0); + uint32_t const extTaxon = internalTaxon(minter, intTaxon); + nfts.push_back( + token::getNextID(env, minter, extTaxon, tfTransferable)); + env(token::mint(minter, extTaxon), txflags(tfTransferable)); + env.close(); + + // Minter creates an offer for the NFToken. + uint256 const minterOfferIndex = + keylet::nftoffer(minter, env.seq(minter)).key; + env(token::createOffer(minter, nfts.back(), XRP(0)), + txflags(tfSellNFToken)); + env.close(); + + // alice accepts the offer. + env(token::acceptSellOffer(alice, minterOfferIndex)); + env.close(); + } + + // Sort the NFTs so they are listed in storage order, not + // creation order. + std::sort(nfts.begin(), nfts.end()); + + // Verify that the ledger does indeed contain exactly three pages + // of NFTs with 32 entries in each page. + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::binary] = false; + { + Json::Value jrr = env.rpc( + "json", + "ledger_data", + boost::lexical_cast(jvParams)); + + Json::Value& state = jrr[jss::result][jss::state]; + + int pageCount = 0; + for (Json::UInt i = 0; i < state.size(); ++i) + { + if (state[i].isMember(sfNFTokens.jsonName) && + state[i][sfNFTokens.jsonName].isArray()) + { + BEAST_EXPECT( + state[i][sfNFTokens.jsonName].size() == 32); + ++pageCount; + } + } + // If this check fails then the internal NFT directory logic + // has changed. + BEAST_EXPECT(pageCount == 3); + } + return nfts; + }; + + // Generate three packed pages. + std::vector nfts = genPackedTokens(); + BEAST_EXPECT(nftCount(env, alice) == 96); + BEAST_EXPECT(ownerCount(env, alice) == 3); + + // Verify that that all three pages are present and remember the + // indexes. + auto lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + if (!BEAST_EXPECT(lastNFTokenPage)) + return; + + uint256 const middleNFTokenPageIndex = + lastNFTokenPage->at(sfPreviousPageMin); + auto middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + + uint256 const firstNFTokenPageIndex = + middleNFTokenPage->at(sfPreviousPageMin); + auto firstNFTokenPage = env.le( + keylet::nftpage(keylet::nftpage_min(alice), firstNFTokenPageIndex)); + if (!BEAST_EXPECT(firstNFTokenPage)) + return; + + // Sell all the tokens in the very last page back to minter. + std::vector last32NFTs; + for (int i = 0; i < 32; ++i) + { + last32NFTs.push_back(nfts.back()); + nfts.pop_back(); + + // alice creates an offer for the NFToken. + uint256 const aliceOfferIndex = + keylet::nftoffer(alice, env.seq(alice)).key; + env(token::createOffer(alice, last32NFTs.back(), XRP(0)), + txflags(tfSellNFToken)); + env.close(); + + // minter accepts the offer. + env(token::acceptSellOffer(minter, aliceOfferIndex)); + env.close(); + } + + // Removing the last token from the last page deletes alice's last + // page. This is a bug. The contents of the next-to-last page + // should have been moved into the last page. + lastNFTokenPage = env.le(keylet::nftpage_max(alice)); + BEAST_EXPECT(!lastNFTokenPage); + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // The "middle" page is still present, but has lost the + // NextPageMin field. + middleNFTokenPage = env.le(keylet::nftpage( + keylet::nftpage_min(alice), middleNFTokenPageIndex)); + if (!BEAST_EXPECT(middleNFTokenPage)) + return; + BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin)); + BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin)); + + // Attempt to delete alice's account, but fail because she owns NFTs. + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(alice, minter), + fee(acctDelFee), + ter(tecHAS_OBLIGATIONS)); + env.close(); + + // minter sells the last 32 NFTs back to alice. + for (uint256 nftID : last32NFTs) + { + // minter creates an offer for the NFToken. + uint256 const minterOfferIndex = + keylet::nftoffer(minter, env.seq(minter)).key; + env(token::createOffer(minter, nftID, XRP(0)), + txflags(tfSellNFToken)); + env.close(); + + // alice accepts the offer. + env(token::acceptSellOffer(alice, minterOfferIndex)); + env.close(); + } + BEAST_EXPECT(ownerCount(env, alice) == 3); // Three NFTokenPages. + + // alice has an NFToken directory with a broken link in the middle. + { + // Try the account_objects RPC command. Alice's account only shows + // two NFT pages even though she owns more. + Json::Value acctObjs = [&env, &alice]() { + Json::Value params; + params[jss::account] = alice.human(); + return env.rpc("json", "account_objects", to_string(params)); + }(); + BEAST_EXPECT(!acctObjs.isMember(jss::marker)); + BEAST_EXPECT( + acctObjs[jss::result][jss::account_objects].size() == 2); + } + { + // Try the account_nfts RPC command. It only returns 64 NFTs + // although alice owns 96. + Json::Value aliceNFTs = [&env, &alice]() { + Json::Value params; + params[jss::account] = alice.human(); + params[jss::type] = "state"; + return env.rpc("json", "account_nfts", to_string(params)); + }(); + BEAST_EXPECT(!aliceNFTs.isMember(jss::marker)); + BEAST_EXPECT( + aliceNFTs[jss::result][jss::account_nfts].size() == 64); + } + } + void testWithFeats(FeatureBitset features) { testBurnRandom(features); testBurnSequential(features); testBurnTooManyOffers(features); + exerciseBrokenLinks(features); } protected: @@ -792,13 +1384,18 @@ class NFTokenBurnBaseUtil_test : public beast::unit_test::suite { using namespace test::jtx; static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const fixNFTV1_2{fixNonFungibleTokensV1_2}; static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; - - static std::array const feats{ - all - fixNonFungibleTokensV1_2 - fixNFTDir - fixNFTokenRemint, - all - fixNonFungibleTokensV1_2 - fixNFTokenRemint, - all - fixNFTokenRemint, - all}; + static FeatureBitset const fixNFTRemint{fixNFTokenRemint}; + static FeatureBitset const fixNFTPageLinks{fixNFTokenPageLinks}; + + static std::array const feats{ + all - fixNFTV1_2 - fixNFTDir - fixNFTRemint - fixNFTPageLinks, + all - fixNFTV1_2 - fixNFTRemint - fixNFTPageLinks, + all - fixNFTRemint - fixNFTPageLinks, + all - fixNFTPageLinks, + all, + }; if (BEAST_EXPECT(instance < feats.size())) { @@ -835,19 +1432,30 @@ class NFTokenBurnWOFixTokenRemint_test : public NFTokenBurnBaseUtil_test } }; +class NFTokenBurnWOFixNFTPageLinks_test : public NFTokenBurnBaseUtil_test +{ +public: + void + run() override + { + NFTokenBurnBaseUtil_test::run(3); + } +}; + class NFTokenBurnAllFeatures_test : public NFTokenBurnBaseUtil_test { public: void run() override { - NFTokenBurnBaseUtil_test::run(3, true); + NFTokenBurnBaseUtil_test::run(4, true); } }; BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnBaseUtil, tx, ripple, 3); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOfixFungTokens, tx, ripple, 3); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOFixTokenRemint, tx, ripple, 3); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOFixNFTPageLinks, tx, ripple, 3); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnAllFeatures, tx, ripple, 3); } // namespace ripple diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index e9addfa83f7..23e4c671526 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include #include @@ -185,7 +185,7 @@ class NFTokenDir_test : public beast::unit_test::suite // Create accounts for all of the seeds and fund those accounts. std::vector accounts; accounts.reserve(seeds.size()); - for (std::string_view const& seed : seeds) + for (std::string_view seed : seeds) { Account const& account = accounts.emplace_back( Account::base58Seed, std::string(seed)); @@ -409,7 +409,7 @@ class NFTokenDir_test : public beast::unit_test::suite // Create accounts for all of the seeds and fund those accounts. std::vector accounts; accounts.reserve(seeds.size()); - for (std::string_view const& seed : seeds) + for (std::string_view seed : seeds) { Account const& account = accounts.emplace_back( Account::base58Seed, std::string(seed)); @@ -659,7 +659,7 @@ class NFTokenDir_test : public beast::unit_test::suite // Create accounts for all of the seeds and fund those accounts. std::vector accounts; accounts.reserve(seeds.size()); - for (std::string_view const& seed : seeds) + for (std::string_view seed : seeds) { Account const& account = accounts.emplace_back(Account::base58Seed, std::string(seed)); @@ -840,7 +840,7 @@ class NFTokenDir_test : public beast::unit_test::suite // Create accounts for all of the seeds and fund those accounts. std::vector accounts; accounts.reserve(seeds.size()); - for (std::string_view const& seed : seeds) + for (std::string_view seed : seeds) { Account const& account = accounts.emplace_back(Account::base58Seed, std::string(seed)); diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 8740b521132..0d4786ae72e 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include #include @@ -31,16 +31,6 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite { FeatureBitset const disallowIncoming{featureDisallowIncoming}; - // Helper function that returns the owner count of an account root. - static std::uint32_t - ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) - { - std::uint32_t ret{0}; - if (auto const sleAcct = env.le(acct)) - ret = sleAcct->at(sfOwnerCount); - return ret; - } - // Helper function that returns the number of NFTs minted by an issuer. static std::uint32_t mintedCount(test::jtx::Env const& env, test::jtx::Account const& issuer) @@ -3171,6 +3161,26 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite ter(tecNO_PERMISSION)); env.close(); } + + // minter mint and offer to buyer + if (features[featureNFTokenMintOffer]) + { + // enable flag + env(fset(buyer, asfDisallowIncomingNFTokenOffer)); + // a sell offer from the minter to the buyer should be rejected + env(token::mint(minter), + token::amount(drops(1)), + token::destination(buyer), + ter(tecNO_PERMISSION)); + env.close(); + + // disable flag + env(fclear(buyer, asfDisallowIncomingNFTokenOffer)); + env(token::mint(minter), + token::amount(drops(1)), + token::destination(buyer)); + env.close(); + } } void @@ -3928,7 +3938,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite for (Account const& acct : accounts) { if (std::uint32_t ownerCount = - this->ownerCount(env, acct); + test::jtx::ownerCount(env, acct); ownerCount != 1) { std::stringstream ss; @@ -6306,7 +6316,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite } // When an account mints and burns a batch of NFTokens using tickets, - // see if the the account can be deleted. + // see if the account can be deleted. { Env env{*this, features}; @@ -6566,6 +6576,281 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite } } + void + testFeatMintWithOffer(FeatureBitset features) + { + testcase("NFTokenMint with Create NFTokenOffer"); + + using namespace test::jtx; + + if (!features[featureNFTokenMintOffer]) + { + Env env{*this, features}; + Account const alice("alice"); + Account const buyer("buyer"); + + env.fund(XRP(10000), alice, buyer); + env.close(); + + env(token::mint(alice), + token::amount(XRP(10000)), + ter(temDISABLED)); + env.close(); + + env(token::mint(alice), + token::destination("buyer"), + ter(temDISABLED)); + env.close(); + + env(token::mint(alice), + token::expiration(lastClose(env) + 25), + ter(temDISABLED)); + env.close(); + + return; + } + + // The remaining tests assume featureNFTokenMintOffer is enabled. + { + Env env{*this, features}; + Account const alice("alice"); + Account const buyer{"buyer"}; + Account const gw("gw"); + Account const issuer("issuer"); + Account const minter("minter"); + Account const bob("bob"); + IOU const gwAUD(gw["AUD"]); + + env.fund(XRP(10000), alice, buyer, gw, issuer, minter); + env.close(); + + { + // Destination field specified but Amount field not specified + env(token::mint(alice), + token::destination(buyer), + ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Expiration field specified but Amount field not specified + env(token::mint(alice), + token::expiration(lastClose(env) + 25), + ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, buyer) == 0); + } + + { + // The destination may not be the account submitting the + // transaction. + env(token::mint(alice), + token::amount(XRP(1000)), + token::destination(alice), + ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // The destination must be an account already established in the + // ledger. + env(token::mint(alice), + token::amount(XRP(1000)), + token::destination(Account("demon")), + ter(tecNO_DST)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + } + + { + // Set a bad expiration. + env(token::mint(alice), + token::amount(XRP(1000)), + token::expiration(0), + ter(temBAD_EXPIRATION)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // The new NFTokenOffer may not have passed its expiration time. + env(token::mint(alice), + token::amount(XRP(1000)), + token::expiration(lastClose(env)), + ter(tecEXPIRED)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + } + + { + // Set an invalid amount. + env(token::mint(alice), + token::amount(buyer["USD"](1)), + txflags(tfOnlyXRP), + ter(temBAD_AMOUNT)); + env(token::mint(alice), + token::amount(buyer["USD"](0)), + ter(temBAD_AMOUNT)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Issuer (alice) must have a trust line for the offered funds. + env(token::mint(alice), + token::amount(gwAUD(1000)), + txflags(tfTransferable), + token::xferFee(10), + ter(tecNO_LINE)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // If the IOU issuer and the NFToken issuer are the same, + // then that issuer does not need a trust line to accept their + // fee. + env(token::mint(gw), + token::amount(gwAUD(1000)), + txflags(tfTransferable), + token::xferFee(10)); + env.close(); + + // Give alice the needed trust line, but freeze it. + env(trust(gw, alice["AUD"](999), tfSetFreeze)); + env.close(); + + // Issuer (alice) must have a trust line for the offered funds + // and the trust line may not be frozen. + env(token::mint(alice), + token::amount(gwAUD(1000)), + txflags(tfTransferable), + token::xferFee(10), + ter(tecFROZEN)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Seller (alice) must have a trust line may not be frozen. + env(token::mint(alice), + token::amount(gwAUD(1000)), + ter(tecFROZEN)); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + // Unfreeze alice's trustline. + env(trust(gw, alice["AUD"](999), tfClearFreeze)); + env.close(); + } + + { + // check reserve + auto const acctReserve = + env.current()->fees().accountReserve(0); + auto const incReserve = env.current()->fees().increment; + + env.fund(acctReserve + incReserve, bob); + env.close(); + + // doesn't have reserve for 2 objects (NFTokenPage, Offer) + env(token::mint(bob), + token::amount(XRP(0)), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + // have reserve for NFTokenPage, Offer + env(pay(env.master, bob, incReserve + drops(10))); + env.close(); + env(token::mint(bob), token::amount(XRP(0))); + env.close(); + + // doesn't have reserve for Offer + env(pay(env.master, bob, drops(10))); + env.close(); + env(token::mint(bob), + token::amount(XRP(0)), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + // have reserve for Offer + env(pay(env.master, bob, incReserve + drops(10))); + env.close(); + env(token::mint(bob), token::amount(XRP(0))); + env.close(); + } + + // Amount field specified + BEAST_EXPECT(ownerCount(env, alice) == 0); + env(token::mint(alice), token::amount(XRP(10))); + BEAST_EXPECT(ownerCount(env, alice) == 2); + env.close(); + + // Amount field and Destination field, Expiration field specified + env(token::mint(alice), + token::amount(XRP(10)), + token::destination(buyer), + token::expiration(lastClose(env) + 25)); + env.close(); + + // With TransferFee field + env(trust(alice, gwAUD(1000))); + env.close(); + env(token::mint(alice), + token::amount(gwAUD(1)), + token::destination(buyer), + token::expiration(lastClose(env) + 25), + txflags(tfTransferable), + token::xferFee(10)); + env.close(); + + // Can be canceled by the issuer. + env(token::mint(alice), + token::amount(XRP(10)), + token::destination(buyer), + token::expiration(lastClose(env) + 25)); + uint256 const offerAliceSellsToBuyer = + keylet::nftoffer(alice, env.seq(alice)).key; + env(token::cancelOffer(alice, {offerAliceSellsToBuyer})); + env.close(); + + // Can be canceled by the buyer. + env(token::mint(buyer), + token::amount(XRP(10)), + token::destination(alice), + token::expiration(lastClose(env) + 25)); + uint256 const offerBuyerSellsToAlice = + keylet::nftoffer(buyer, env.seq(buyer)).key; + env(token::cancelOffer(alice, {offerBuyerSellsToAlice})); + env.close(); + + env(token::setMinter(issuer, minter)); + env.close(); + + // Minter will have offer not issuer + BEAST_EXPECT(ownerCount(env, minter) == 0); + BEAST_EXPECT(ownerCount(env, issuer) == 0); + env(token::mint(minter), + token::issuer(issuer), + token::amount(drops(1))); + env.close(); + BEAST_EXPECT(ownerCount(env, minter) == 2); + BEAST_EXPECT(ownerCount(env, issuer) == 0); + } + + // Test sell offers with a destination with and without + // fixNFTokenNegOffer. + for (auto const& tweakedFeatures : + {features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1, + features | fixNFTokenNegOffer}) + { + Env env{*this, tweakedFeatures}; + Account const alice("alice"); + + env.fund(XRP(1000000), alice); + + TER const offerCreateTER = tweakedFeatures[fixNFTokenNegOffer] + ? static_cast(temBAD_AMOUNT) + : static_cast(tesSUCCESS); + + // Make offers with negative amounts for the NFTs + env(token::mint(alice), + token::amount(XRP(-2)), + ter(offerCreateTER)); + env.close(); + } + } + void testTxJsonMetaFields(FeatureBitset features) { @@ -6796,6 +7081,15 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite env.close(); verifyNFTokenIDsInCancelOffer({nftId}); } + + if (features[featureNFTokenMintOffer]) + { + uint256 const aliceMintWithOfferIndex1 = + keylet::nftoffer(alice, env.seq(alice)).key; + env(token::mint(alice), token::amount(XRP(0))); + env.close(); + verifyNFTokenOfferID(aliceMintWithOfferIndex1); + } } void @@ -7112,6 +7406,334 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite } } + void + testUnaskedForAutoTrustline(FeatureBitset features) + { + testcase("Test fix unasked for auto-trustline."); + + using namespace test::jtx; + + Account const issuer{"issuer"}; + Account const becky{"becky"}; + Account const cheri{"cheri"}; + Account const gw("gw"); + IOU const gwAUD(gw["AUD"]); + + // This test case covers issue... + // https://github.com/XRPLF/rippled/issues/4925 + // + // For an NFToken with a transfer fee, the issuer must be able to + // accept the transfer fee or else a transfer should fail. If the + // NFToken is transferred for a non-XRP asset, then the issuer must + // have a trustline to that asset to receive the fee. + // + // This test looks at a situation where issuer would get a trustline + // for the fee without the issuer's consent. Here are the steps: + // 1. Issuer has a trustline (i.e., USD) + // 2. Issuer mints NFToken with transfer fee. + // 3. Becky acquires the NFToken, paying with XRP. + // 4. Becky creates offer to sell NFToken for USD(100). + // 5. Issuer deletes trustline for USD. + // 6. Carol buys NFToken from Becky for USD(100). + // 7. The transfer fee from Carol's purchase re-establishes issuer's + // USD trustline. + // + // The fixEnforceNFTokenTrustline amendment addresses this oversight. + // + // We run this test case both with and without + // fixEnforceNFTokenTrustline enabled so we can see the change + // in behavior. + // + // In both cases we remove the fixRemoveNFTokenAutoTrustLine amendment. + // Otherwise we can't create NFTokens with tfTrustLine enabled. + FeatureBitset const localFeatures = + features - fixRemoveNFTokenAutoTrustLine; + for (FeatureBitset feats : + {localFeatures - fixEnforceNFTokenTrustline, + localFeatures | fixEnforceNFTokenTrustline}) + { + Env env{*this, feats}; + env.fund(XRP(1000), issuer, becky, cheri, gw); + env.close(); + + // Set trust lines so becky and cheri can use gw's currency. + env(trust(becky, gwAUD(1000))); + env(trust(cheri, gwAUD(1000))); + env.close(); + env(pay(gw, cheri, gwAUD(500))); + env.close(); + + // issuer creates two NFTs: one with and one without AutoTrustLine. + std::uint16_t xferFee = 5000; // 5% + uint256 const nftAutoTrustID{token::getNextID( + env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)}; + env(token::mint(issuer, 0u), + token::xferFee(xferFee), + txflags(tfTransferable | tfTrustLine)); + env.close(); + + uint256 const nftNoAutoTrustID{ + token::getNextID(env, issuer, 0u, tfTransferable, xferFee)}; + env(token::mint(issuer, 0u), + token::xferFee(xferFee), + txflags(tfTransferable)); + env.close(); + + // becky buys the nfts for 1 drop each. + { + uint256 const beckyBuyOfferIndex1 = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftAutoTrustID, drops(1)), + token::owner(issuer)); + + uint256 const beckyBuyOfferIndex2 = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftNoAutoTrustID, drops(1)), + token::owner(issuer)); + + env.close(); + env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1)); + env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2)); + env.close(); + } + + // becky creates offers to sell the nfts for AUD. + uint256 const beckyAutoTrustOfferIndex = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)), + txflags(tfSellNFToken)); + env.close(); + + // Creating an offer for the NFToken without tfTrustLine fails + // because issuer does not have a trust line for AUD. + env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)), + txflags(tfSellNFToken), + ter(tecNO_LINE)); + env.close(); + + // issuer creates a trust line. Now the offer create for the + // NFToken without tfTrustLine succeeds. + BEAST_EXPECT(ownerCount(env, issuer) == 0); + env(trust(issuer, gwAUD(1000))); + env.close(); + BEAST_EXPECT(ownerCount(env, issuer) == 1); + + uint256 const beckyNoAutoTrustOfferIndex = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)), + txflags(tfSellNFToken)); + env.close(); + + // Now that the offers are in place, issuer removes the trustline. + BEAST_EXPECT(ownerCount(env, issuer) == 1); + env(trust(issuer, gwAUD(0))); + env.close(); + BEAST_EXPECT(ownerCount(env, issuer) == 0); + + // cheri attempts to accept becky's offers. Behavior with the + // AutoTrustline NFT is uniform: issuer gets a new trust line. + env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex)); + env.close(); + + // Here's evidence that issuer got the new AUD trust line. + BEAST_EXPECT(ownerCount(env, issuer) == 1); + BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5)); + + // issuer once again removes the trust line for AUD. + env(pay(issuer, gw, gwAUD(5))); + env.close(); + BEAST_EXPECT(ownerCount(env, issuer) == 0); + + // cheri attempts to accept the NoAutoTrustLine NFT. Behavior + // depends on whether fixEnforceNFTokenTrustline is enabled. + if (feats[fixEnforceNFTokenTrustline]) + { + // With fixEnforceNFTokenTrustline cheri can't accept the + // offer because issuer could not get their transfer fee + // without the appropriate trustline. + env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex), + ter(tecNO_LINE)); + env.close(); + + // But if issuer re-establishes the trustline then the offer + // can be accepted. + env(trust(issuer, gwAUD(1000))); + env.close(); + BEAST_EXPECT(ownerCount(env, issuer) == 1); + + env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex)); + env.close(); + } + else + { + // Without fixEnforceNFTokenTrustline the offer just works + // and issuer gets a trustline that they did not request. + env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex)); + env.close(); + } + BEAST_EXPECT(ownerCount(env, issuer) == 1); + BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5)); + } // for feats + } + + void + testNFTIssuerIsIOUIssuer(FeatureBitset features) + { + testcase("Test fix NFT issuer is IOU issuer"); + + using namespace test::jtx; + + Account const issuer{"issuer"}; + Account const becky{"becky"}; + Account const cheri{"cheri"}; + IOU const isISU(issuer["ISU"]); + + // This test case covers issue... + // https://github.com/XRPLF/rippled/issues/4941 + // + // If an NFToken has a transfer fee then, when an offer is accepted, + // a portion of the sale price goes to the issuer. + // + // It is possible for an issuer to issue both an IOU (for remittances) + // and NFTokens. If the issuer's IOU is used to pay for the transfer + // of one of the issuer's NFTokens, then paying the fee for that + // transfer will fail with a tecNO_LINE. + // + // The problem occurs because the NFT code looks for a trust line to + // pay the transfer fee. However the issuer of an IOU does not need + // a trust line to accept their own issuance and, in fact, is not + // allowed to have a trust line to themselves. + // + // This test looks at a situation where transfer of an NFToken is + // prevented by this bug: + // 1. Issuer issues an IOU (e.g, isISU). + // 2. Becky and Cheri get trust lines for, and acquire, some isISU. + // 3. Issuer mints NFToken with transfer fee. + // 4. Becky acquires the NFToken, paying with XRP. + // 5. Becky attempts to create an offer to sell the NFToken for + // isISU(100). The attempt fails with `tecNO_LINE`. + // + // The featureNFTokenMintOffer amendment addresses this oversight. + // + // We remove the fixRemoveNFTokenAutoTrustLine amendment. Otherwise + // we can't create NFTokens with tfTrustLine enabled. + FeatureBitset const localFeatures = + features - fixRemoveNFTokenAutoTrustLine; + + Env env{*this, localFeatures}; + env.fund(XRP(1000), issuer, becky, cheri); + env.close(); + + // Set trust lines so becky and cheri can use isISU. + env(trust(becky, isISU(1000))); + env(trust(cheri, isISU(1000))); + env.close(); + env(pay(issuer, cheri, isISU(500))); + env.close(); + + // issuer creates two NFTs: one with and one without AutoTrustLine. + std::uint16_t xferFee = 5000; // 5% + uint256 const nftAutoTrustID{token::getNextID( + env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)}; + env(token::mint(issuer, 0u), + token::xferFee(xferFee), + txflags(tfTransferable | tfTrustLine)); + env.close(); + + uint256 const nftNoAutoTrustID{ + token::getNextID(env, issuer, 0u, tfTransferable, xferFee)}; + env(token::mint(issuer, 0u), + token::xferFee(xferFee), + txflags(tfTransferable)); + env.close(); + + // becky buys the nfts for 1 drop each. + { + uint256 const beckyBuyOfferIndex1 = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftAutoTrustID, drops(1)), + token::owner(issuer)); + + uint256 const beckyBuyOfferIndex2 = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftNoAutoTrustID, drops(1)), + token::owner(issuer)); + + env.close(); + env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1)); + env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2)); + env.close(); + } + + // Behavior from here down diverges significantly based on + // featureNFTokenMintOffer. + if (!localFeatures[featureNFTokenMintOffer]) + { + // Without featureNFTokenMintOffer becky simply can't + // create an offer for a non-tfTrustLine NFToken that would + // pay the transfer fee in issuer's own IOU. + env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)), + txflags(tfSellNFToken), + ter(tecNO_LINE)); + env.close(); + + // And issuer can't create a trust line to themselves. + env(trust(issuer, isISU(1000)), ter(temDST_IS_SRC)); + env.close(); + + // However if the NFToken has the tfTrustLine flag set, + // then becky can create the offer. + uint256 const beckyAutoTrustOfferIndex = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftAutoTrustID, isISU(100)), + txflags(tfSellNFToken)); + env.close(); + + // And cheri can accept the offer. + env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex)); + env.close(); + + // We verify that issuer got their transfer fee by seeing that + // ISU(5) has disappeared out of cheri's and becky's balances. + BEAST_EXPECT(env.balance(becky, isISU) == isISU(95)); + BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400)); + } + else + { + // With featureNFTokenMintOffer things go better. + // becky creates offers to sell the nfts for ISU. + uint256 const beckyNoAutoTrustOfferIndex = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)), + txflags(tfSellNFToken)); + env.close(); + uint256 const beckyAutoTrustOfferIndex = + keylet::nftoffer(becky, env.seq(becky)).key; + env(token::createOffer(becky, nftAutoTrustID, isISU(100)), + txflags(tfSellNFToken)); + env.close(); + + // cheri accepts becky's offers. Behavior is uniform: + // issuer gets paid. + env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex)); + env.close(); + + // We verify that issuer got their transfer fee by seeing that + // ISU(5) has disappeared out of cheri's and becky's balances. + BEAST_EXPECT(env.balance(becky, isISU) == isISU(95)); + BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400)); + + env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex)); + env.close(); + + // We verify that issuer got their transfer fee by seeing that + // an additional ISU(5) has disappeared out of cheri's and + // becky's balances. + BEAST_EXPECT(env.balance(becky, isISU) == isISU(190)); + BEAST_EXPECT(env.balance(cheri, isISU) == isISU(300)); + } + } + void testWithFeats(FeatureBitset features) { @@ -7144,8 +7766,11 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite testIOUWithTransferFee(features); testBrokeredSaleToSelf(features); testFixNFTokenRemint(features); + testFeatMintWithOffer(features); testTxJsonMetaFields(features); testFixNFTokenBuyerReserve(features); + testUnaskedForAutoTrustline(features); + testNFTIssuerIsIOUIssuer(features); } public: @@ -7156,15 +7781,17 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite static FeatureBitset const all{supported_amendments()}; static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; - static std::array const feats{ + static std::array const feats{ all - fixNFTDir - fixNonFungibleTokensV1_2 - fixNFTokenRemint - - fixNFTokenReserve, + fixNFTokenReserve - featureNFTokenMintOffer, all - disallowIncoming - fixNonFungibleTokensV1_2 - - fixNFTokenRemint - fixNFTokenReserve, + fixNFTokenRemint - fixNFTokenReserve - featureNFTokenMintOffer, all - fixNonFungibleTokensV1_2 - fixNFTokenRemint - - fixNFTokenReserve, - all - fixNFTokenRemint - fixNFTokenReserve, - all - fixNFTokenReserve, + fixNFTokenReserve - featureNFTokenMintOffer, + all - fixNFTokenRemint - fixNFTokenReserve - + featureNFTokenMintOffer, + all - fixNFTokenReserve - featureNFTokenMintOffer, + all - featureNFTokenMintOffer, all}; if (BEAST_EXPECT(instance < feats.size())) @@ -7217,12 +7844,21 @@ class NFTokenWOTokenReserve_test : public NFTokenBaseUtil_test } }; +class NFTokenWOMintOffer_test : public NFTokenBaseUtil_test +{ + void + run() override + { + NFTokenBaseUtil_test::run(5); + } +}; + class NFTokenAllFeatures_test : public NFTokenBaseUtil_test { void run() override { - NFTokenBaseUtil_test::run(5, true); + NFTokenBaseUtil_test::run(6, true); } }; @@ -7231,6 +7867,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDisallowIncoming, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOfixV1, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOTokenRemint, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOTokenReserve, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOMintOffer, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(NFTokenAllFeatures, tx, ripple, 2); } // namespace ripple diff --git a/src/test/app/NetworkID_test.cpp b/src/test/app/NetworkID_test.cpp index 8d1b891345d..2f02a1fd7d2 100644 --- a/src/test/app/NetworkID_test.cpp +++ b/src/test/app/NetworkID_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/OfferStream_test.cpp b/src/test/app/OfferStream_test.cpp index ce25481cde8..bf712503687 100644 --- a/src/test/app/OfferStream_test.cpp +++ b/src/test/app/OfferStream_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 95ffd9f3aee..2b4245a1ae4 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include #include #include #include +#include +#include +#include namespace ripple { namespace test { @@ -1387,78 +1387,106 @@ class OfferBaseUtil_test : public beast::unit_test::suite using namespace jtx; - Env env{*this, features}; + // This is one of the few tests where fixReducedOffersV2 changes the + // results. So test both with and without fixReducedOffersV2. + for (FeatureBitset localFeatures : + {features - fixReducedOffersV2, features | fixReducedOffersV2}) + { + Env env{*this, localFeatures}; - auto const gw = Account{"gateway"}; - auto const alice = Account{"alice"}; - auto const bob = Account{"bob"}; - auto const USD = gw["USD"]; - auto const BTC = gw["BTC"]; + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const USD = gw["USD"]; + auto const BTC = gw["BTC"]; - // these *interesting* amounts were taken - // from the original JS test that was ported here - auto const gw_initial_balance = drops(1149999730); - auto const alice_initial_balance = drops(499946999680); - auto const bob_initial_balance = drops(10199999920); - auto const small_amount = - STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33}; + // these *interesting* amounts were taken + // from the original JS test that was ported here + auto const gw_initial_balance = drops(1149999730); + auto const alice_initial_balance = drops(499946999680); + auto const bob_initial_balance = drops(10199999920); + auto const small_amount = + STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33}; - env.fund(gw_initial_balance, gw); - env.fund(alice_initial_balance, alice); - env.fund(bob_initial_balance, bob); + env.fund(gw_initial_balance, gw); + env.fund(alice_initial_balance, alice); + env.fund(bob_initial_balance, bob); - env(rate(gw, 1.005)); + env(rate(gw, 1.005)); - env(trust(alice, USD(500))); - env(trust(bob, USD(50))); - env(trust(gw, alice["USD"](100))); + env(trust(alice, USD(500))); + env(trust(bob, USD(50))); + env(trust(gw, alice["USD"](100))); - env(pay(gw, alice, alice["USD"](50))); - env(pay(gw, bob, small_amount)); + env(pay(gw, alice, alice["USD"](50))); + env(pay(gw, bob, small_amount)); - env(offer(alice, USD(50), XRP(150000))); + env(offer(alice, USD(50), XRP(150000))); - // unfund the offer - env(pay(alice, gw, USD(100))); + // unfund the offer + env(pay(alice, gw, USD(100))); - // drop the trust line (set to 0) - env(trust(gw, alice["USD"](0))); + // drop the trust line (set to 0) + env(trust(gw, alice["USD"](0))); - // verify balances - auto jrr = ledgerEntryState(env, alice, gw, "USD"); - BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "50"); + // verify balances + auto jrr = ledgerEntryState(env, alice, gw, "USD"); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName][jss::value] == "50"); - jrr = ledgerEntryState(env, bob, gw, "USD"); - BEAST_EXPECT( - jrr[jss::node][sfBalance.fieldName][jss::value] == - "-2710505431213761e-33"); + jrr = ledgerEntryState(env, bob, gw, "USD"); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName][jss::value] == + "-2710505431213761e-33"); - // create crossing offer - env(offer(bob, XRP(2000), USD(1))); + // create crossing offer + std::uint32_t const bobOfferSeq = env.seq(bob); + env(offer(bob, XRP(2000), USD(1))); - // verify balances again. - // - // NOTE : - // Here a difference in the rounding modes of our two offer crossing - // algorithms becomes apparent. The old offer crossing would consume - // small_amount and transfer no XRP. The new offer crossing transfers - // a single drop, rather than no drops. - auto const crossingDelta = - features[featureFlowCross] ? drops(1) : drops(0); + if (localFeatures[featureFlowCross] && + localFeatures[fixReducedOffersV2]) + { + // With the rounding introduced by fixReducedOffersV2, bob's + // offer does not cross alice's offer and goes straight into + // the ledger. + jrr = ledgerEntryState(env, bob, gw, "USD"); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName][jss::value] == + "-2710505431213761e-33"); + + Json::Value const bobOffer = + ledgerEntryOffer(env, bob, bobOfferSeq)[jss::node]; + BEAST_EXPECT(bobOffer[sfTakerGets.jsonName][jss::value] == "1"); + BEAST_EXPECT(bobOffer[sfTakerPays.jsonName] == "2000000000"); + return; + } - jrr = ledgerEntryState(env, alice, gw, "USD"); - BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "50"); - BEAST_EXPECT( - env.balance(alice, xrpIssue()) == - alice_initial_balance - env.current()->fees().base * 3 - - crossingDelta); + // verify balances again. + // + // NOTE: + // Here a difference in the rounding modes of our two offer + // crossing algorithms becomes apparent. The old offer crossing + // would consume small_amount and transfer no XRP. The new offer + // crossing transfers a single drop, rather than no drops. + auto const crossingDelta = + localFeatures[featureFlowCross] ? drops(1) : drops(0); + + jrr = ledgerEntryState(env, alice, gw, "USD"); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName][jss::value] == "50"); + BEAST_EXPECT( + env.balance(alice, xrpIssue()) == + alice_initial_balance - env.current()->fees().base * 3 - + crossingDelta); - jrr = ledgerEntryState(env, bob, gw, "USD"); - BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0"); - BEAST_EXPECT( - env.balance(bob, xrpIssue()) == - bob_initial_balance - env.current()->fees().base * 2 + - crossingDelta); + jrr = ledgerEntryState(env, bob, gw, "USD"); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName][jss::value] == "0"); + BEAST_EXPECT( + env.balance(bob, xrpIssue()) == + bob_initial_balance - env.current()->fees().base * 2 + + crossingDelta); + } } void @@ -5439,12 +5467,12 @@ class Offer_manual_test : public OfferBaseUtil_test } }; -BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, tx, ripple, 4); -BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFlowCross, tx, ripple, 4); -BEAST_DEFINE_TESTSUITE_PRIO(OfferWTakerDryOffer, tx, ripple, 4); -BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, tx, ripple, 4); -BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFillOrKill, tx, ripple, 4); -BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFlowCross, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(OfferWTakerDryOffer, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFillOrKill, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, tx, ripple, 20); } // namespace test diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index 16a72de70fd..44eeb1c9f98 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { @@ -28,16 +28,6 @@ namespace oracle { struct Oracle_test : public beast::unit_test::suite { private: - // Helper function that returns the owner count of an account root. - static std::uint32_t - ownerCount(jtx::Env const& env, jtx::Account const& acct) - { - std::uint32_t ret{0}; - if (auto const sleAcct = env.le(acct)) - ret = sleAcct->at(sfOwnerCount); - return ret; - } - void testInvalidSet() { diff --git a/src/test/app/OversizeMeta_test.cpp b/src/test/app/OversizeMeta_test.cpp index 554033eb531..0a7f42e1801 100644 --- a/src/test/app/OversizeMeta_test.cpp +++ b/src/test/app/OversizeMeta_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 0f9916cbcff..1db15388ff0 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -17,25 +17,25 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include namespace ripple { diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 4bd23a4edb8..bc1cbba69c0 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -832,6 +832,190 @@ struct PayChan_test : public beast::unit_test::suite } } + void + testDepositAuthCreds() + { + testcase("Deposit Authorization with Credentials"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + const char credType[] = "abcde"; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const dillon("dillon"); + Account const zelda("zelda"); + + { + Env env{*this}; + env.fund(XRP(10000), alice, bob, carol, dillon, zelda); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, XRP(1000), settleDelay, pk)); + env.close(); + + // alice add funds to the channel + env(fund(alice, chan, XRP(1000))); + env.close(); + + std::string const credBadIdx = + "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6" + "E1"; + + auto const delta = XRP(500).value(); + + { // create credentials + auto jv = credentials::create(alice, carol, credType); + uint32_t const t = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 100; + jv[sfExpiration.jsonName] = t; + env(jv); + env.close(); + } + + auto const jv = + credentials::ledgerEntry(env, alice, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // Fail, credentials not accepted + env(claim(alice, chan, delta, delta), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + env.close(); + + env(credentials::accept(alice, carol, credType)); + env.close(); + + // Fail, no depositPreauth object + env(claim(alice, chan, delta, delta), + credentials::ids({credIdx}), + ter(tecNO_PERMISSION)); + env.close(); + + // Setup deposit authorization + env(deposit::authCredentials(bob, {{carol, credType}})); + env.close(); + + // Fail, credentials doesn’t belong to root account + env(claim(dillon, chan, delta, delta), + credentials::ids({credIdx}), + ter(tecBAD_CREDENTIALS)); + + // Fails because bob's lsfDepositAuth flag is set. + env(claim(alice, chan, delta, delta), ter(tecNO_PERMISSION)); + + // Fail, bad credentials index. + env(claim(alice, chan, delta, delta), + credentials::ids({credBadIdx}), + ter(tecBAD_CREDENTIALS)); + + // Fail, empty credentials + env(claim(alice, chan, delta, delta), + credentials::ids({}), + ter(temMALFORMED)); + + { + // claim fails cause of expired credentials + + // Every cycle +10sec. + for (int i = 0; i < 10; ++i) + env.close(); + + env(claim(alice, chan, delta, delta), + credentials::ids({credIdx}), + ter(tecEXPIRED)); + env.close(); + } + + { // create credentials once more + env(credentials::create(alice, carol, credType)); + env.close(); + env(credentials::accept(alice, carol, credType)); + env.close(); + + auto const jv = + credentials::ledgerEntry(env, alice, carol, credType); + std::string const credIdx = + jv[jss::result][jss::index].asString(); + + // Success + env(claim(alice, chan, delta, delta), + credentials::ids({credIdx})); + } + } + + { + Env env{*this}; + env.fund(XRP(10000), alice, bob, carol, dillon, zelda); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, XRP(1000), settleDelay, pk)); + env.close(); + + // alice add funds to the channel + env(fund(alice, chan, XRP(1000))); + env.close(); + + auto const delta = XRP(500).value(); + + { // create credentials + env(credentials::create(alice, carol, credType)); + env.close(); + env(credentials::accept(alice, carol, credType)); + env.close(); + } + + auto const jv = + credentials::ledgerEntry(env, alice, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // Succeed, lsfDepositAuth is not set + env(claim(alice, chan, delta, delta), credentials::ids({credIdx})); + env.close(); + } + + { + // Credentials amendment not enabled + Env env(*this, supported_amendments() - featureCredentials); + env.fund(XRP(5000), "alice", "bob"); + env.close(); + + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, XRP(1000), settleDelay, pk)); + env.close(); + + env(fund(alice, chan, XRP(1000))); + env.close(); + std::string const credIdx = + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4"; + + // can't claim with old DepositPreauth because rule is not enabled. + env(fset(bob, asfDepositAuth)); + env.close(); + env(deposit::auth(bob, alice)); + env.close(); + + env(claim(alice, chan, XRP(500).value(), XRP(500).value()), + credentials::ids({credIdx}), + ter(temDISABLED)); + } + } + void testMultiple(FeatureBitset features) { @@ -2116,6 +2300,7 @@ struct PayChan_test : public beast::unit_test::suite FeatureBitset const all{supported_amendments()}; testWithFeats(all - disallowIncoming); testWithFeats(all); + testDepositAuthCreds(); } }; diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index ae17b8e0d43..f00a7361292 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -15,20 +15,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index 78ca7cc05b1..238041a17bc 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -15,11 +15,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/app/RCLCensorshipDetector_test.cpp b/src/test/app/RCLCensorshipDetector_test.cpp index 12bb3bbba3f..b5440b90903 100644 --- a/src/test/app/RCLCensorshipDetector_test.cpp +++ b/src/test/app/RCLCensorshipDetector_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 0380795a8ae..540d98bc1f1 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/ReducedOffer_test.cpp b/src/test/app/ReducedOffer_test.cpp index f82efcb7fc8..a070051e435 100644 --- a/src/test/app/ReducedOffer_test.cpp +++ b/src/test/app/ReducedOffer_test.cpp @@ -17,10 +17,12 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include + +#include namespace ripple { namespace test { @@ -56,13 +58,11 @@ class ReducedOffer_test : public beast::unit_test::suite static void cleanupOldOffers( jtx::Env& env, - jtx::Account const& acct1, - jtx::Account const& acct2, - std::uint32_t acct1OfferSeq, - std::uint32_t acct2OfferSeq) + std::initializer_list> + list) { - env(offer_cancel(acct1, acct1OfferSeq)); - env(offer_cancel(acct2, acct2OfferSeq)); + for (auto [acct, offerSeq] : list) + env(offer_cancel(acct, offerSeq)); env.close(); } @@ -180,7 +180,8 @@ class ReducedOffer_test : public beast::unit_test::suite // In preparation for the next iteration make sure the two // offers are gone from the ledger. - cleanupOldOffers(env, alice, bob, aliceOfferSeq, bobOfferSeq); + cleanupOldOffers( + env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}}); return badRate; }; @@ -279,7 +280,7 @@ class ReducedOffer_test : public beast::unit_test::suite // If the in-ledger offer was not consumed then further // results are meaningless. cleanupOldOffers( - env, alice, bob, aliceOfferSeq, bobOfferSeq); + env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}}); return 1; } // alice's offer should still be in the ledger, but reduced in @@ -337,7 +338,8 @@ class ReducedOffer_test : public beast::unit_test::suite // In preparation for the next iteration make sure the two // offers are gone from the ledger. - cleanupOldOffers(env, alice, bob, aliceOfferSeq, bobOfferSeq); + cleanupOldOffers( + env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}}); return badRate; }; @@ -452,7 +454,7 @@ class ReducedOffer_test : public beast::unit_test::suite // In preparation for the next iteration clean up any // leftover offers. cleanupOldOffers( - env, alice, bob, aliceOfferSeq, bobOfferSeq); + env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}}); // Zero out alice's and bob's USD balances. if (STAmount const aliceBalance = env.balance(alice, USD); @@ -573,7 +575,8 @@ class ReducedOffer_test : public beast::unit_test::suite // In preparation for the next iteration clean up any // leftover offers. - cleanupOldOffers(env, alice, bob, aliceOfferSeq, bobOfferSeq); + cleanupOldOffers( + env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}}); // Zero out alice's and bob's IOU balances. auto zeroBalance = [&env, &gw]( @@ -606,6 +609,185 @@ class ReducedOffer_test : public beast::unit_test::suite } } + Amounts + jsonOfferToAmounts(Json::Value const& json) + { + STAmount const in = + amountFromJson(sfTakerPays, json[sfTakerPays.jsonName]); + STAmount const out = + amountFromJson(sfTakerGets, json[sfTakerGets.jsonName]); + return {in, out}; + } + + void + testSellPartialCrossOldXrpIouQChange() + { + // This test case was motivated by Issue #4937. It recreates + // the specific failure identified in that issue and samples some other + // cases in the same vicinity to make sure that the new behavior makes + // sense. + testcase("exercise tfSell partial cross old XRP/IOU offer Q change"); + + using namespace jtx; + + Account const gw("gateway"); + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + auto const USD = gw["USD"]; + + // Make one test run without fixReducedOffersV2 and one with. + for (FeatureBitset features : + {supported_amendments() - fixReducedOffersV2, + supported_amendments() | fixReducedOffersV2}) + { + // Make sure none of the offers we generate are under funded. + Env env{*this, features}; + env.fund(XRP(10'000'000), gw, alice, bob, carol); + env.close(); + + env(trust(alice, USD(10'000'000))); + env(trust(bob, USD(10'000'000))); + env(trust(carol, USD(10'000'000))); + env.close(); + + env(pay(gw, alice, USD(10'000'000))); + env(pay(gw, bob, USD(10'000'000))); + env(pay(gw, carol, USD(10'000'000))); + env.close(); + + // Lambda that: + // 1. Exercises one offer trio, + // 2. Collects the results, and + // 3. Cleans up for the next offer trio. + auto exerciseOfferTrio = + [this, &env, &alice, &bob, &carol, &USD]( + Amounts const& carolOffer) -> unsigned int { + // alice submits an offer that may become a blocker. + std::uint32_t const aliceOfferSeq = env.seq(alice); + static Amounts const aliceInitialOffer(USD(2), drops(3382562)); + env(offer(alice, aliceInitialOffer.in, aliceInitialOffer.out)); + env.close(); + STAmount const initialRate = + Quality(jsonOfferToAmounts(ledgerEntryOffer( + env, alice, aliceOfferSeq)[jss::node])) + .rate(); + + // bob submits an offer that is more desirable than alice's + std::uint32_t const bobOfferSeq = env.seq(bob); + env(offer(bob, USD(0.97086565812384), drops(1642020))); + env.close(); + + // Now carol's offer consumes bob's and partially crosses + // alice's. The tfSell flag is important. + std::uint32_t const carolOfferSeq = env.seq(carol); + env(offer(carol, carolOffer.in, carolOffer.out), + txflags(tfSell)); + env.close(); + + // carol's offer should not have made it into the ledger and + // bob's offer should be fully consumed. + if (!BEAST_EXPECT( + !offerInLedger(env, carol, carolOfferSeq) && + !offerInLedger(env, bob, bobOfferSeq))) + { + // If carol's or bob's offers are still in the ledger then + // further results are meaningless. + cleanupOldOffers( + env, + {{alice, aliceOfferSeq}, + {bob, bobOfferSeq}, + {carol, carolOfferSeq}}); + return 1; + } + // alice's offer should still be in the ledger, but reduced in + // size. + unsigned int badRate = 1; + { + Json::Value aliceOffer = + ledgerEntryOffer(env, alice, aliceOfferSeq); + + Amounts aliceReducedOffer = + jsonOfferToAmounts(aliceOffer[jss::node]); + + BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in); + BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out); + STAmount const inLedgerRate = + Quality(aliceReducedOffer).rate(); + badRate = inLedgerRate > initialRate ? 1 : 0; + + // If the inLedgerRate is less than initial rate, then + // incrementing the mantissa of the reduced TakerGets + // should result in a rate higher than initial. Check + // this to verify that the largest allowable TakerGets + // was computed. + if (badRate == 0) + { + STAmount const tweakedTakerGets( + aliceReducedOffer.in.issue(), + aliceReducedOffer.in.mantissa() + 1, + aliceReducedOffer.in.exponent(), + aliceReducedOffer.in.negative()); + STAmount const tweakedRate = + Quality( + Amounts{aliceReducedOffer.in, tweakedTakerGets}) + .rate(); + BEAST_EXPECT(tweakedRate > initialRate); + } +#if 0 + std::cout << "Placed rate: " << initialRate + << "; in-ledger rate: " << inLedgerRate + << "; TakerPays: " << aliceReducedOffer.in + << "; TakerGets: " << aliceReducedOffer.out + << std::endl; +// #else + std::string_view filler = badRate ? "**" : " "; + std::cout << "| " << aliceReducedOffer.in << "` | `" + << aliceReducedOffer.out << "` | `" << initialRate + << "` | " << filler << "`" << inLedgerRate << "`" + << filler << std::endl; +#endif + } + + // In preparation for the next iteration make sure all three + // offers are gone from the ledger. + cleanupOldOffers( + env, + {{alice, aliceOfferSeq}, + {bob, bobOfferSeq}, + {carol, carolOfferSeq}}); + return badRate; + }; + + constexpr int loopCount = 100; + unsigned int blockedCount = 0; + { + STAmount increaseGets = USD(0); + STAmount const step(increaseGets.issue(), 1, -8); + for (unsigned int i = 0; i < loopCount; ++i) + { + blockedCount += exerciseOfferTrio( + Amounts(drops(1642020), USD(1) + increaseGets)); + increaseGets += step; + } + } + + // If fixReducedOffersV2 is enabled, then none of the test cases + // should produce a potentially blocking rate. + // + // Also verify that if fixReducedOffersV2 is not enabled then + // some of the test cases produced a potentially blocking rate. + if (features[fixReducedOffersV2]) + { + BEAST_EXPECT(blockedCount == 0); + } + else + { + BEAST_EXPECT(blockedCount > 80); + } + } + } + void run() override { @@ -613,6 +795,7 @@ class ReducedOffer_test : public beast::unit_test::suite testPartialCrossOldXrpIouQChange(); testUnderFundedXrpIouQChange(); testUnderFundedIouIouQChange(); + testSellPartialCrossOldXrpIouQChange(); } }; diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index f743a30f079..f54a88ace00 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -15,13 +15,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 8a47b186957..376cb4eb7ba 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index 8066ef0dacf..3dd8ab590a4 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { @@ -38,8 +38,8 @@ struct SetAuth_test : public beast::unit_test::suite using namespace jtx; Json::Value jv; jv[jss::Account] = account.human(); - jv[jss::LimitAmount] = - STAmount({to_currency(currency), dest}).getJson(JsonOptions::none); + jv[jss::LimitAmount] = STAmount(Issue{to_currency(currency), dest}) + .getJson(JsonOptions::none); jv[jss::TransactionType] = jss::TrustSet; jv[jss::Flags] = tfSetfAuth; return jv; diff --git a/src/test/app/SetRegularKey_test.cpp b/src/test/app/SetRegularKey_test.cpp index 799b7797b85..024d8de137b 100644 --- a/src/test/app/SetRegularKey_test.cpp +++ b/src/test/app/SetRegularKey_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/app/SetTrust_test.cpp b/src/test/app/SetTrust_test.cpp index 599ac1917f9..57e18d712f8 100644 --- a/src/test/app/SetTrust_test.cpp +++ b/src/test/app/SetTrust_test.cpp @@ -16,9 +16,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { @@ -29,6 +29,144 @@ class SetTrust_test : public beast::unit_test::suite FeatureBitset const disallowIncoming{featureDisallowIncoming}; public: + void + testTrustLineDelete() + { + testcase( + "Test deletion of trust lines: revert trust line limit to zero"); + + using namespace jtx; + Env env(*this); + + Account const alice = Account{"alice"}; + Account const becky = Account{"becky"}; + + env.fund(XRP(10000), becky, alice); + env.close(); + + // becky wants to hold at most 50 tokens of alice["USD"] + // becky is the customer, alice is the issuer + // becky can be sent at most 50 tokens of alice's USD + env(trust(becky, alice["USD"](50))); + env.close(); + + // Since the settings of the trust lines are non-default for both + // alice and becky, both of them will be charged an owner reserve + // Irrespective of whether the issuer or the customer initiated + // the trust-line creation, both will be charged + env.require(lines(alice, 1)); + env.require(lines(becky, 1)); + + // Fetch the trust-lines via RPC for verification + Json::Value jv; + jv["account"] = becky.human(); + auto beckyLines = env.rpc("json", "account_lines", to_string(jv)); + + jv["account"] = alice.human(); + auto aliceLines = env.rpc("json", "account_lines", to_string(jv)); + + BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 1); + BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 1); + + // reset the trust line limits to zero + env(trust(becky, alice["USD"](0))); + env.close(); + + // the reset of the trust line limits deletes the trust-line + // this occurs despite the authorization of the trust-line by the + // issuer(alice, in this unit test) + env.require(lines(becky, 0)); + env.require(lines(alice, 0)); + + // second verification check via RPC calls + jv["account"] = becky.human(); + beckyLines = env.rpc("json", "account_lines", to_string(jv)); + + jv["account"] = alice.human(); + aliceLines = env.rpc("json", "account_lines", to_string(jv)); + + BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0); + BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 0); + + // additionally, verify that account_objects is an empty array + jv["account"] = becky.human(); + auto const beckyObj = env.rpc("json", "account_objects", to_string(jv)); + BEAST_EXPECT(beckyObj[jss::result][jss::account_objects].size() == 0); + + jv["account"] = alice.human(); + auto const aliceObj = env.rpc("json", "account_objects", to_string(jv)); + BEAST_EXPECT(aliceObj[jss::result][jss::account_objects].size() == 0); + } + + void + testTrustLineResetWithAuthFlag() + { + testcase( + "Reset trust line limit with Authorised Lines: Verify " + "deletion of trust lines"); + + using namespace jtx; + Env env(*this); + + Account const alice = Account{"alice"}; + Account const becky = Account{"becky"}; + + env.fund(XRP(10000), becky, alice); + env.close(); + + // alice wants to ensure that all holders of her tokens are authorised + env(fset(alice, asfRequireAuth)); + env.close(); + + // becky wants to hold at most 50 tokens of alice["USD"] + // becky is the customer, alice is the issuer + // becky can be sent at most 50 tokens of alice's USD + env(trust(becky, alice["USD"](50))); + env.close(); + + // alice authorizes becky to hold alice["USD"] tokens + env(trust(alice, alice["USD"](0), becky, tfSetfAuth)); + env.close(); + + // Since the settings of the trust lines are non-default for both + // alice and becky, both of them will be charged an owner reserve + // Irrespective of whether the issuer or the customer initiated + // the trust-line creation, both will be charged + env.require(lines(alice, 1)); + env.require(lines(becky, 1)); + + // Fetch the trust-lines via RPC for verification + Json::Value jv; + jv["account"] = becky.human(); + auto beckyLines = env.rpc("json", "account_lines", to_string(jv)); + + jv["account"] = alice.human(); + auto aliceLines = env.rpc("json", "account_lines", to_string(jv)); + + BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 1); + BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 1); + + // reset the trust line limits to zero + env(trust(becky, alice["USD"](0))); + env.close(); + + // the reset of the trust line limits deletes the trust-line + // this occurs despite the authorization of the trust-line by the + // issuer(alice, in this unit test) + env.require(lines(becky, 0)); + env.require(lines(alice, 0)); + + // second verification check via RPC calls + jv["account"] = becky.human(); + beckyLines = env.rpc("json", "account_lines", to_string(jv)); + + jv["account"] = alice.human(); + aliceLines = env.rpc("json", "account_lines", to_string(jv)); + + BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0); + BEAST_EXPECT(beckyLines[jss::result][jss::lines].size() == 0); + } + void testFreeTrustlines( FeatureBitset features, @@ -203,6 +341,100 @@ class SetTrust_test : public beast::unit_test::suite env(trust(alice, gw["USD"](100), tfSetfAuth), ter(tefNO_AUTH_REQUIRED)); } + void + testExceedTrustLineLimit() + { + testcase( + "Ensure that trust line limits are respected in payment " + "transactions"); + + using namespace jtx; + Env env{*this}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), gw, alice); + + // alice wants to hold at most 100 of gw's USD tokens + env(trust(alice, gw["USD"](100))); + env.close(); + + // send a payment for a large quantity through the trust line + env(pay(gw, alice, gw["USD"](200)), ter(tecPATH_PARTIAL)); + env.close(); + + // on the other hand, smaller payments should succeed + env(pay(gw, alice, gw["USD"](20))); + env.close(); + } + + void + testAuthFlagTrustLines() + { + testcase( + "Ensure that authorised trust lines do not allow payments " + "from unauthorised counter-parties"); + + using namespace jtx; + Env env{*this}; + + auto const bob = Account{"bob"}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), bob, alice); + + // alice wants to ensure that all holders of her tokens are authorised + env(fset(alice, asfRequireAuth)); + env.close(); + + // create a trust line from bob to alice. bob wants to hold at most + // 100 of alice's USD tokens. Note: alice hasn't authorised this + // trust line yet. + env(trust(bob, alice["USD"](100))); + env.close(); + + // send a payment from alice to bob, validate that the payment fails + env(pay(alice, bob, alice["USD"](10)), ter(tecPATH_DRY)); + env.close(); + } + + void + testTrustLineLimitsWithRippling() + { + testcase( + "Check that trust line limits are respected in conjunction " + "with rippling feature"); + + using namespace jtx; + Env env{*this}; + + auto const bob = Account{"bob"}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), bob, alice); + + // create a trust line from bob to alice. bob wants to hold at most + // 100 of alice's USD tokens. + env(trust(bob, alice["USD"](100))); + env.close(); + + // archetypical payment transaction from alice to bob must succeed + env(pay(alice, bob, alice["USD"](20)), ter(tesSUCCESS)); + env.close(); + + // Issued tokens are fungible. i.e. alice's USD is identical to bob's + // USD + env(pay(bob, alice, bob["USD"](10)), ter(tesSUCCESS)); + env.close(); + + // bob cannot place alice in his debt i.e. alice's balance of the USD + // tokens cannot go below zero. + env(pay(bob, alice, bob["USD"](11)), ter(tecPATH_PARTIAL)); + env.close(); + + // payments that respect the trust line limits of alice should succeed + env(pay(bob, alice, bob["USD"](10)), ter(tesSUCCESS)); + env.close(); + } + void testModifyQualityOfTrustline( FeatureBitset features, @@ -402,6 +634,11 @@ class SetTrust_test : public beast::unit_test::suite testModifyQualityOfTrustline(features, true, false); testModifyQualityOfTrustline(features, true, true); testDisallowIncoming(features); + testTrustLineResetWithAuthFlag(); + testTrustLineDelete(); + testExceedTrustLineLimit(); + testAuthFlagTrustLines(); + testTrustLineLimitsWithRippling(); } public: diff --git a/src/test/app/Taker_test.cpp b/src/test/app/Taker_test.cpp index 0b69b25f24b..89e44b2b98b 100644 --- a/src/test/app/Taker_test.cpp +++ b/src/test/app/Taker_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index ae537a45657..917d23377bf 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -17,20 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index b50059711ec..9467390502c 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { diff --git a/src/test/app/Transaction_ordering_test.cpp b/src/test/app/Transaction_ordering_test.cpp index 0353df90663..928fcdb8389 100644 --- a/src/test/app/Transaction_ordering_test.cpp +++ b/src/test/app/Transaction_ordering_test.cpp @@ -15,9 +15,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/app/TrustAndBalance_test.cpp b/src/test/app/TrustAndBalance_test.cpp index 5b0c1d6480e..b438d797276 100644 --- a/src/test/app/TrustAndBalance_test.cpp +++ b/src/test/app/TrustAndBalance_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace ripple { @@ -400,7 +400,6 @@ class TrustAndBalance_test : public beast::unit_test::suite carol["USD"].issue(), 6500000000000000ull, -14, - false, true, STAmount::unchecked{}))); env.require(balance(carol, gw["USD"](35))); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 086bb787d68..e7b70203c91 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp index 99bd6188261..9281ec4bd77 100644 --- a/src/test/app/ValidatorKeys_test.cpp +++ b/src/test/app/ValidatorKeys_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 638c2060d32..05989c0f601 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 00512d157ec..1ec302efef8 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -17,25 +17,25 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include #include -#include -#include -#include namespace ripple { namespace test { @@ -78,20 +78,21 @@ class ValidatorSite_test : public beast::unit_test::suite BEAST_EXPECT(trustedSites->load(emptyCfgSites)); // load should accept valid validator site uris - std::vector cfgSites({ - "http://ripple.com/", "http://ripple.com/validators", - "http://ripple.com:8080/validators", - "http://207.261.33.37/validators", - "http://207.261.33.37:8080/validators", - "https://ripple.com/validators", - "https://ripple.com:443/validators", - "file:///etc/opt/ripple/validators.txt", - "file:///C:/Lib/validators.txt" + std::vector cfgSites( + {"http://ripple.com/", + "http://ripple.com/validators", + "http://ripple.com:8080/validators", + "http://207.261.33.37/validators", + "http://207.261.33.37:8080/validators", + "https://ripple.com/validators", + "https://ripple.com:443/validators", + "file:///etc/opt/ripple/validators.txt", + "file:///C:/Lib/validators.txt" #if !_MSC_VER - , - "file:///" + , + "file:///" #endif - }); + }); BEAST_EXPECT(trustedSites->load(cfgSites)); // load should reject validator site uris with invalid schemes diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index a0837782a9c..4f24d17601e 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/app/tx/apply_test.cpp b/src/test/app/tx/apply_test.cpp index 61a512805a4..df76e839a71 100644 --- a/src/test/app/tx/apply_test.cpp +++ b/src/test/app/tx/apply_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { diff --git a/src/test/basics/Buffer_test.cpp b/src/test/basics/Buffer_test.cpp index 45d2147a054..da8bcc8f12c 100644 --- a/src/test/basics/Buffer_test.cpp +++ b/src/test/basics/Buffer_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/basics/DetectCrash_test.cpp b/src/test/basics/DetectCrash_test.cpp index 70f0bdb83be..1ae761f34a4 100644 --- a/src/test/basics/DetectCrash_test.cpp +++ b/src/test/basics/DetectCrash_test.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include diff --git a/src/test/basics/Expected_test.cpp b/src/test/basics/Expected_test.cpp index b89b9f6d309..d60809aee1f 100644 --- a/src/test/basics/Expected_test.cpp +++ b/src/test/basics/Expected_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #if BOOST_VERSION >= 107500 #include // Not part of boost before version 1.75 #endif // BOOST_VERSION diff --git a/src/test/basics/FeeUnits_test.cpp b/src/test/basics/FeeUnits_test.cpp index 3ded5812947..6608a072621 100644 --- a/src/test/basics/FeeUnits_test.cpp +++ b/src/test/basics/FeeUnits_test.cpp @@ -16,9 +16,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/basics/FileUtilities_test.cpp b/src/test/basics/FileUtilities_test.cpp index 2a9710f8e95..2b84998da7c 100644 --- a/src/test/basics/FileUtilities_test.cpp +++ b/src/test/basics/FileUtilities_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { diff --git a/src/test/basics/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp index a5f7d0456ad..e588a08dc02 100644 --- a/src/test/basics/IOUAmount_test.cpp +++ b/src/test/basics/IOUAmount_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { @@ -219,55 +219,56 @@ class IOUAmount_test : public beast::unit_test::suite tinyNeg == mulRatio(tinyNeg, maxUInt - 1, maxUInt, false)); } - {// rounding - {IOUAmount one(1, 0); - auto const rup = mulRatio(one, maxUInt - 1, maxUInt, true); - auto const rdown = mulRatio(one, maxUInt - 1, maxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } - { - IOUAmount big(maxMantissa, maxExponent); - auto const rup = mulRatio(big, maxUInt - 1, maxUInt, true); - auto const rdown = mulRatio(big, maxUInt - 1, maxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } - - { - IOUAmount negOne(-1, 0); - auto const rup = mulRatio(negOne, maxUInt - 1, maxUInt, true); - auto const rdown = mulRatio(negOne, maxUInt - 1, maxUInt, false); - BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); - } -} + { // rounding + { + IOUAmount one(1, 0); + auto const rup = mulRatio(one, maxUInt - 1, maxUInt, true); + auto const rdown = mulRatio(one, maxUInt - 1, maxUInt, false); + BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); + } + { + IOUAmount big(maxMantissa, maxExponent); + auto const rup = mulRatio(big, maxUInt - 1, maxUInt, true); + auto const rdown = mulRatio(big, maxUInt - 1, maxUInt, false); + BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); + } + + { + IOUAmount negOne(-1, 0); + auto const rup = mulRatio(negOne, maxUInt - 1, maxUInt, true); + auto const rdown = + mulRatio(negOne, maxUInt - 1, maxUInt, false); + BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1); + } + } -{ - // division by zero - IOUAmount one(1, 0); - except([&] { mulRatio(one, 1, 0, true); }); -} + { + // division by zero + IOUAmount one(1, 0); + except([&] { mulRatio(one, 1, 0, true); }); + } -{ - // overflow - IOUAmount big(maxMantissa, maxExponent); - except([&] { mulRatio(big, 2, 0, true); }); -} -} // namespace ripple + { + // overflow + IOUAmount big(maxMantissa, maxExponent); + except([&] { mulRatio(big, 2, 0, true); }); + } + } // namespace ripple -//-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- -void -run() override -{ - testZero(); - testSigNum(); - testBeastZero(); - testComparisons(); - testToString(); - testMulRatio(); -} -} -; + void + run() override + { + testZero(); + testSigNum(); + testBeastZero(); + testComparisons(); + testToString(); + testMulRatio(); + } +}; BEAST_DEFINE_TESTSUITE(IOUAmount, protocol, ripple); -} // ripple +} // namespace ripple diff --git a/src/test/basics/KeyCache_test.cpp b/src/test/basics/KeyCache_test.cpp index 7f3f13e272d..eab0e87d0a7 100644 --- a/src/test/basics/KeyCache_test.cpp +++ b/src/test/basics/KeyCache_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 8c12ff7c5e4..cf626354e0f 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/test/basics/PerfLog_test.cpp b/src/test/basics/PerfLog_test.cpp index f0a6645195b..756988cbdac 100644 --- a/src/test/basics/PerfLog_test.cpp +++ b/src/test/basics/PerfLog_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/basics/RangeSet_test.cpp b/src/test/basics/RangeSet_test.cpp index ccf76fad0d4..e0136ab8907 100644 --- a/src/test/basics/RangeSet_test.cpp +++ b/src/test/basics/RangeSet_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { class RangeSet_test : public beast::unit_test::suite diff --git a/src/test/basics/Slice_test.cpp b/src/test/basics/Slice_test.cpp index fb88315bd98..a169de98539 100644 --- a/src/test/basics/Slice_test.cpp +++ b/src/test/basics/Slice_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/basics/StringUtilities_test.cpp b/src/test/basics/StringUtilities_test.cpp index 6146a3dcd41..cf916c62651 100644 --- a/src/test/basics/StringUtilities_test.cpp +++ b/src/test/basics/StringUtilities_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 6a5b4429996..6fbba1a4795 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/basics/XRPAmount_test.cpp b/src/test/basics/XRPAmount_test.cpp index 37c827180de..c57890fcfa5 100644 --- a/src/test/basics/XRPAmount_test.cpp +++ b/src/test/basics/XRPAmount_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { @@ -283,63 +283,67 @@ class XRPAmount_test : public beast::unit_test::suite tinyNeg == mulRatio(tinyNeg, maxUInt32 - 1, maxUInt32, false)); } - {// rounding - {XRPAmount one(1); - auto const rup = mulRatio(one, maxUInt32 - 1, maxUInt32, true); - auto const rdown = mulRatio(one, maxUInt32 - 1, maxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } + { // rounding + { + XRPAmount one(1); + auto const rup = mulRatio(one, maxUInt32 - 1, maxUInt32, true); + auto const rdown = + mulRatio(one, maxUInt32 - 1, maxUInt32, false); + BEAST_EXPECT(rup.drops() - rdown.drops() == 1); + } - { - XRPAmount big(maxXRP); - auto const rup = mulRatio(big, maxUInt32 - 1, maxUInt32, true); - auto const rdown = mulRatio(big, maxUInt32 - 1, maxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } + { + XRPAmount big(maxXRP); + auto const rup = mulRatio(big, maxUInt32 - 1, maxUInt32, true); + auto const rdown = + mulRatio(big, maxUInt32 - 1, maxUInt32, false); + BEAST_EXPECT(rup.drops() - rdown.drops() == 1); + } - { - XRPAmount negOne(-1); - auto const rup = mulRatio(negOne, maxUInt32 - 1, maxUInt32, true); - auto const rdown = mulRatio(negOne, maxUInt32 - 1, maxUInt32, false); - BEAST_EXPECT(rup.drops() - rdown.drops() == 1); - } -} + { + XRPAmount negOne(-1); + auto const rup = + mulRatio(negOne, maxUInt32 - 1, maxUInt32, true); + auto const rdown = + mulRatio(negOne, maxUInt32 - 1, maxUInt32, false); + BEAST_EXPECT(rup.drops() - rdown.drops() == 1); + } + } -{ - // division by zero - XRPAmount one(1); - except([&] { mulRatio(one, 1, 0, true); }); -} + { + // division by zero + XRPAmount one(1); + except([&] { mulRatio(one, 1, 0, true); }); + } -{ - // overflow - XRPAmount big(maxXRP); - except([&] { mulRatio(big, 2, 1, true); }); -} + { + // overflow + XRPAmount big(maxXRP); + except([&] { mulRatio(big, 2, 1, true); }); + } -{ - // underflow - XRPAmount bigNegative(minXRP + 10); - BEAST_EXPECT(mulRatio(bigNegative, 2, 1, true) == minXRP); -} -} // namespace ripple + { + // underflow + XRPAmount bigNegative(minXRP + 10); + BEAST_EXPECT(mulRatio(bigNegative, 2, 1, true) == minXRP); + } + } // namespace ripple -//-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- -void -run() override -{ - testSigNum(); - testBeastZero(); - testComparisons(); - testAddSub(); - testDecimal(); - testFunctions(); - testMulRatio(); -} -} -; + void + run() override + { + testSigNum(); + testBeastZero(); + testComparisons(); + testAddSub(); + testDecimal(); + testFunctions(); + testMulRatio(); + } +}; BEAST_DEFINE_TESTSUITE(XRPAmount, protocol, ripple); -} // ripple +} // namespace ripple diff --git a/src/test/basics/base58_test.cpp b/src/test/basics/base58_test.cpp index 8b79d2729d7..799f6537dc6 100644 --- a/src/test/basics/base58_test.cpp +++ b/src/test/basics/base58_test.cpp @@ -19,9 +19,9 @@ #ifndef _MSC_VER -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/test/basics/base64_test.cpp b/src/test/basics/base64_test.cpp index d9622a8c28a..b6d67c7c069 100644 --- a/src/test/basics/base64_test.cpp +++ b/src/test/basics/base64_test.cpp @@ -26,8 +26,8 @@ // Official repository: https://github.com/boostorg/beast // -#include -#include +#include +#include namespace ripple { diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index 9b1f7696dd5..9f3194f4fbc 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -48,7 +48,8 @@ struct nonhash memcpy(data_.data(), key, len); } - explicit operator std::size_t() noexcept + explicit + operator std::size_t() noexcept { return WIDTH; } diff --git a/src/test/basics/contract_test.cpp b/src/test/basics/contract_test.cpp index 58844ba97f8..99f118794ee 100644 --- a/src/test/basics/contract_test.cpp +++ b/src/test/basics/contract_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { diff --git a/src/test/basics/hardened_hash_test.cpp b/src/test/basics/hardened_hash_test.cpp index 9296b7faf02..343894e52b0 100644 --- a/src/test/basics/hardened_hash_test.cpp +++ b/src/test/basics/hardened_hash_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include #include diff --git a/src/test/basics/join_test.cpp b/src/test/basics/join_test.cpp index 730fcb69343..1b094828243 100644 --- a/src/test/basics/join_test.cpp +++ b/src/test/basics/join_test.cpp @@ -19,8 +19,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/basics/mulDiv_test.cpp b/src/test/basics/mulDiv_test.cpp index f51b91fecf4..47332fd45ba 100644 --- a/src/test/basics/mulDiv_test.cpp +++ b/src/test/basics/mulDiv_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/basics/scope_test.cpp b/src/test/basics/scope_test.cpp index 414e67b5c7e..654f7e0a11b 100644 --- a/src/test/basics/scope_test.cpp +++ b/src/test/basics/scope_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/basics/tagged_integer_test.cpp b/src/test/basics/tagged_integer_test.cpp index dd9d9022dde..22cdc5ad53f 100644 --- a/src/test/basics/tagged_integer_test.cpp +++ b/src/test/basics/tagged_integer_test.cpp @@ -18,8 +18,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { diff --git a/src/test/beast/IPEndpointCommon.h b/src/test/beast/IPEndpointCommon.h index bf8e7613fe5..aa2c1e597fb 100644 --- a/src/test/beast/IPEndpointCommon.h +++ b/src/test/beast/IPEndpointCommon.h @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace beast { namespace IP { diff --git a/src/test/beast/IPEndpoint_test.cpp b/src/test/beast/IPEndpoint_test.cpp index 1a29ddefa43..1ad49443dc3 100644 --- a/src/test/beast/IPEndpoint_test.cpp +++ b/src/test/beast/IPEndpoint_test.cpp @@ -20,13 +20,13 @@ // MODULES: ../impl/IPEndpoint.cpp ../impl/IPAddressV4.cpp // ../impl/IPAddressV6.cpp -#include -#include -#include +#include +#include +#include +#include #include #include #include -#include #include namespace beast { diff --git a/src/test/beast/LexicalCast_test.cpp b/src/test/beast/LexicalCast_test.cpp index 680dc6d69ac..22638f27e6e 100644 --- a/src/test/beast/LexicalCast_test.cpp +++ b/src/test/beast/LexicalCast_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace beast { diff --git a/src/test/beast/SemanticVersion_test.cpp b/src/test/beast/SemanticVersion_test.cpp index 5026f0fbae7..2f24a0c8926 100644 --- a/src/test/beast/SemanticVersion_test.cpp +++ b/src/test/beast/SemanticVersion_test.cpp @@ -16,8 +16,8 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include +#include +#include namespace beast { class SemanticVersion_test : public unit_test::suite diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index be17240811d..e5736764e06 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/beast/beast_CurrentThreadName_test.cpp b/src/test/beast/beast_CurrentThreadName_test.cpp index 6e46808f4b2..653b0a89618 100644 --- a/src/test/beast/beast_CurrentThreadName_test.cpp +++ b/src/test/beast/beast_CurrentThreadName_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/beast/beast_Journal_test.cpp b/src/test/beast/beast_Journal_test.cpp index 9d3567c277d..6f1652381a0 100644 --- a/src/test/beast/beast_Journal_test.cpp +++ b/src/test/beast/beast_Journal_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace beast { diff --git a/src/test/beast/beast_PropertyStream_test.cpp b/src/test/beast/beast_PropertyStream_test.cpp index 2c4d32ce459..e91f527d3c9 100644 --- a/src/test/beast/beast_PropertyStream_test.cpp +++ b/src/test/beast/beast_PropertyStream_test.cpp @@ -16,8 +16,8 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include +#include +#include namespace beast { class PropertyStream_test : public unit_test::suite diff --git a/src/test/beast/beast_Zero_test.cpp b/src/test/beast/beast_Zero_test.cpp index 42b07343741..8fd16a4d51f 100644 --- a/src/test/beast/beast_Zero_test.cpp +++ b/src/test/beast/beast_Zero_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include +#include -#include +#include namespace beast { @@ -27,7 +27,8 @@ struct adl_tester { }; -int signum(adl_tester) +int +signum(adl_tester) { return 0; } @@ -38,7 +39,8 @@ struct adl_tester2 { }; -int signum(adl_tester2) +int +signum(adl_tester2) { return 0; } diff --git a/src/test/beast/beast_abstract_clock_test.cpp b/src/test/beast/beast_abstract_clock_test.cpp index eba9a3d77b1..a026df26376 100644 --- a/src/test/beast/beast_abstract_clock_test.cpp +++ b/src/test/beast/beast_abstract_clock_test.cpp @@ -19,9 +19,9 @@ // MODULES: ../impl/chrono_io.cpp -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/test/beast/beast_basic_seconds_clock_test.cpp b/src/test/beast/beast_basic_seconds_clock_test.cpp index 5e55aab598b..f3926634dc4 100644 --- a/src/test/beast/beast_basic_seconds_clock_test.cpp +++ b/src/test/beast/beast_basic_seconds_clock_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include +#include -#include +#include namespace beast { diff --git a/src/test/beast/beast_io_latency_probe_test.cpp b/src/test/beast/beast_io_latency_probe_test.cpp index 32206ced7f7..96228194304 100644 --- a/src/test/beast/beast_io_latency_probe_test.cpp +++ b/src/test/beast/beast_io_latency_probe_test.cpp @@ -16,10 +16,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include +#include +#include -#include +#include #include #include diff --git a/src/test/beast/define_print.cpp b/src/test/beast/define_print.cpp index 3f527907a3f..0d36cd76982 100644 --- a/src/test/beast/define_print.cpp +++ b/src/test/beast/define_print.cpp @@ -5,9 +5,9 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include -#include -#include +#include +#include +#include #include // Include this .cpp in your project to gain access to the printing suite diff --git a/src/test/conditions/PreimageSha256_test.cpp b/src/test/conditions/PreimageSha256_test.cpp index cf4bc8c7c1e..9a6840b11c3 100644 --- a/src/test/conditions/PreimageSha256_test.cpp +++ b/src/test/conditions/PreimageSha256_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/test/consensus/ByzantineFailureSim_test.cpp b/src/test/consensus/ByzantineFailureSim_test.cpp index a907b7c224e..92dbfc0174d 100644 --- a/src/test/consensus/ByzantineFailureSim_test.cpp +++ b/src/test/consensus/ByzantineFailureSim_test.cpp @@ -16,9 +16,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include #include +#include +#include #include namespace ripple { @@ -66,8 +66,8 @@ class ByzantineFailureSim_test : public beast::unit_test::suite for (TrustGraph::ForkInfo const& fi : sim.trustGraph.forkablePairs(0.8)) { - std::cout << "Can fork " << PeerGroup{fi.unlA} << " " - << " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap + std::cout << "Can fork " << PeerGroup{fi.unlA} << " " << " " + << PeerGroup{fi.unlB} << " overlap " << fi.overlap << " required " << fi.required << "\n"; }; diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 5c7dc2626fe..88280994c10 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -16,12 +16,12 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include namespace ripple { @@ -806,7 +806,7 @@ class Consensus_test : public beast::unit_test::suite on(csf::PeerID who, csf::SimTime, csf::FullyValidateLedger const& e) { using namespace std::chrono; - // As soon as the the fastC node fully validates C, disconnect + // As soon as the fastC node fully validates C, disconnect // ALL c nodes from the network. The fast C node needs to disconnect // as well to prevent it from relaying the validations it did see if (who == groupCfast[0]->id && diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index ef1ae8b8722..3abac36b4f1 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -16,9 +16,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include #include +#include +#include #include #include diff --git a/src/test/consensus/LedgerTiming_test.cpp b/src/test/consensus/LedgerTiming_test.cpp index 98b0548bf11..95e27f5c854 100644 --- a/src/test/consensus/LedgerTiming_test.cpp +++ b/src/test/consensus/LedgerTiming_test.cpp @@ -16,8 +16,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 3feb3b09959..6f13db43be2 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -16,10 +16,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include namespace ripple { diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 8cbb57444bd..200cd166031 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/consensus/ScaleFreeSim_test.cpp b/src/test/consensus/ScaleFreeSim_test.cpp index 4fe911eacf4..3e3cec97618 100644 --- a/src/test/consensus/ScaleFreeSim_test.cpp +++ b/src/test/consensus/ScaleFreeSim_test.cpp @@ -16,10 +16,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include #include #include +#include +#include #include namespace ripple { diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 7dc2086e55c..0279d6330cc 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/test/core/ClosureCounter_test.cpp b/src/test/core/ClosureCounter_test.cpp index c4199a0b06e..83d2fdb6e4a 100644 --- a/src/test/core/ClosureCounter_test.cpp +++ b/src/test/core/ClosureCounter_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include #include diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index c991f3b11a2..3cf77fba2ef 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include namespace ripple { namespace detail { diff --git a/src/test/core/Coroutine_test.cpp b/src/test/core/Coroutine_test.cpp index 6d1e5e33304..0fdc5a4f49a 100644 --- a/src/test/core/Coroutine_test.cpp +++ b/src/test/core/Coroutine_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include +#include +#include #include #include #include -#include namespace ripple { namespace test { diff --git a/src/test/core/CryptoPRNG_test.cpp b/src/test/core/CryptoPRNG_test.cpp index 11bf9b7a161..303a2176315 100644 --- a/src/test/core/CryptoPRNG_test.cpp +++ b/src/test/core/CryptoPRNG_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include +#include +#include +#include #include #include #include -#include namespace ripple { diff --git a/src/test/core/JobQueue_test.cpp b/src/test/core/JobQueue_test.cpp index cba0217675c..42338063db3 100644 --- a/src/test/core/JobQueue_test.cpp +++ b/src/test/core/JobQueue_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index c0365ad9ae7..82d0cbe9035 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include -#include namespace ripple { class SociDB_test final : public TestSuite diff --git a/src/test/core/Workers_test.cpp b/src/test/core/Workers_test.cpp index 155dd14f030..3fac4808f9f 100644 --- a/src/test/core/Workers_test.cpp +++ b/src/test/core/Workers_test.cpp @@ -17,11 +17,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/test/csf/BasicNetwork.h b/src/test/csf/BasicNetwork.h index 3ce159a2333..a70ee6a3613 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/test/csf/BasicNetwork.h @@ -51,7 +51,7 @@ namespace csf { at either end of the connection will not be delivered. When creating the Peer set, the caller needs to provide a - Scheduler object for managing the the timing and delivery + Scheduler object for managing the timing and delivery of messages. After constructing the network, and establishing connections, the caller uses the scheduler's step_* functions to drive messages through the network. diff --git a/src/test/csf/BasicNetwork_test.cpp b/src/test/csf/BasicNetwork_test.cpp index f3b18216ac7..61660ae4c4e 100644 --- a/src/test/csf/BasicNetwork_test.cpp +++ b/src/test/csf/BasicNetwork_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include #include #include +#include +#include #include namespace ripple { diff --git a/src/test/csf/CollectorRef.h b/src/test/csf/CollectorRef.h index 0f8d90c8ec9..72d1e9545d8 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/test/csf/CollectorRef.h @@ -40,7 +40,7 @@ namespace csf { level when adding to the simulation. The example code below demonstrates the reason for storing the collector - as a reference. The collector's lifetime will generally be be longer than + as a reference. The collector's lifetime will generally be longer than the simulation; perhaps several simulations are run for a single collector instance. The collector potentially stores lots of data as well, so the simulation needs to point to the single instance, rather than requiring diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 2c6b356bf02..3f079eac17c 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -220,7 +220,7 @@ class Digraph @param fileName The output file (creates) @param vertexName A invokable T vertexName(Vertex const &) that returns the name target use for the vertex in the file - T must be be ostream-able + T must be ostream-able */ template void diff --git a/src/test/csf/Digraph_test.cpp b/src/test/csf/Digraph_test.cpp index ac2063e49f2..80319445caf 100644 --- a/src/test/csf/Digraph_test.cpp +++ b/src/test/csf/Digraph_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include namespace ripple { diff --git a/src/test/csf/Histogram_test.cpp b/src/test/csf/Histogram_test.cpp index bf392ec8ac1..ea8b864780a 100644 --- a/src/test/csf/Histogram_test.cpp +++ b/src/test/csf/Histogram_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 6d3008f7348..2f3b460e02f 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -19,13 +19,6 @@ #ifndef RIPPLE_TEST_CSF_PEER_H_INCLUDED #define RIPPLE_TEST_CSF_PEER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -33,6 +26,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -644,7 +644,8 @@ struct Peer } // Not interested in tracking consensus mode changes for now - void onModeChange(ConsensusMode, ConsensusMode) + void + onModeChange(ConsensusMode, ConsensusMode) { } diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index 1f3421a4adb..b59bfa6763c 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -19,9 +19,9 @@ #ifndef RIPPLE_TEST_CSF_PEERGROUP_H_INCLUDED #define RIPPLE_TEST_CSF_PEERGROUP_H_INCLUDED -#include #include #include +#include #include namespace ripple { diff --git a/src/test/csf/Proposal.h b/src/test/csf/Proposal.h index d1cee16c1a7..1d70464bab5 100644 --- a/src/test/csf/Proposal.h +++ b/src/test/csf/Proposal.h @@ -19,10 +19,10 @@ #ifndef RIPPLE_TEST_CSF_PROPOSAL_H_INCLUDED #define RIPPLE_TEST_CSF_PROPOSAL_H_INCLUDED -#include #include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/csf/README.md b/src/test/csf/README.md index ff6bdc5dfac..a4b69abab57 100644 --- a/src/test/csf/README.md +++ b/src/test/csf/README.md @@ -144,7 +144,7 @@ sim.collectors.add(simDur); ``` The next lines add a single collector to the simulation. The -`SimDurationCollector` is a a simple example collector which tracks the total +`SimDurationCollector` is a simple example collector which tracks the total duration of the simulation. More generally, a collector is any class that implements `void on(NodeID, SimTime, Event)` for all [Events](./events.h) emitted by a Peer. Events are arbitrary types used to indicate some action or diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index 2dc24222b26..62dff86402b 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_CSF_SCHEDULER_H_INCLUDED #define RIPPLE_TEST_CSF_SCHEDULER_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/test/csf/Scheduler_test.cpp b/src/test/csf/Scheduler_test.cpp index ac9fab2534b..931150bbeb2 100644 --- a/src/test/csf/Scheduler_test.cpp +++ b/src/test/csf/Scheduler_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include diff --git a/src/test/csf/SimTime.h b/src/test/csf/SimTime.h index 2e18a5f9ee5..930338108e1 100644 --- a/src/test/csf/SimTime.h +++ b/src/test/csf/SimTime.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TEST_CSF_SIMTIME_H_INCLUDED #define RIPPLE_TEST_CSF_SIMTIME_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/test/csf/TrustGraph.h b/src/test/csf/TrustGraph.h index 31bb8534037..649f210646f 100644 --- a/src/test/csf/TrustGraph.h +++ b/src/test/csf/TrustGraph.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_CSF_UNL_H_INCLUDED #define RIPPLE_TEST_CSF_UNL_H_INCLUDED -#include #include +#include #include #include diff --git a/src/test/csf/Tx.h b/src/test/csf/Tx.h index 7f3645a59e6..b21481dcbf9 100644 --- a/src/test/csf/Tx.h +++ b/src/test/csf/Tx.h @@ -18,8 +18,8 @@ //============================================================================== #ifndef RIPPLE_TEST_CSF_TX_H_INCLUDED #define RIPPLE_TEST_CSF_TX_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index aacb155fb1f..57c2bc66681 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -19,8 +19,8 @@ #ifndef RIPPLE_TEST_CSF_VALIDATION_H_INCLUDED #define RIPPLE_TEST_CSF_VALIDATION_H_INCLUDED -#include #include +#include #include #include diff --git a/src/test/csf/collectors.h b/src/test/csf/collectors.h index 352aa4cb8c2..8dcaa035314 100644 --- a/src/test/csf/collectors.h +++ b/src/test/csf/collectors.h @@ -19,10 +19,10 @@ #ifndef RIPPLE_TEST_CSF_COLLECTORS_H_INCLUDED #define RIPPLE_TEST_CSF_COLLECTORS_H_INCLUDED -#include #include #include #include +#include #include #include @@ -281,66 +281,62 @@ struct TxCollector if (printBreakline) { - log << std::setw(11) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(36) << std::setfill('-') << "-" - << std::endl; + log << std::setw(11) << std::setfill('-') << "-" << "-" + << std::setw(7) << std::setfill('-') << "-" << "-" + << std::setw(7) << std::setfill('-') << "-" << "-" + << std::setw(36) << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); } - log << std::left << std::setw(11) << "TxStats" - << "|" << std::setw(7) << "Count" - << "|" << std::setw(7) << "Per Sec" - << "|" << std::setw(15) << "Latency (sec)" << std::right - << std::setw(7) << "10-ile" << std::setw(7) << "50-ile" - << std::setw(7) << "90-ile" << std::left << std::endl; + log << std::left << std::setw(11) << "TxStats" << "|" << std::setw(7) + << "Count" << "|" << std::setw(7) << "Per Sec" << "|" + << std::setw(15) << "Latency (sec)" << std::right << std::setw(7) + << "10-ile" << std::setw(7) << "50-ile" << std::setw(7) << "90-ile" + << std::left << std::endl; - log << std::setw(11) << std::setfill('-') << "-" - << "|" << std::setw(7) << std::setfill('-') << "-" - << "|" << std::setw(7) << std::setfill('-') << "-" - << "|" << std::setw(36) << std::setfill('-') << "-" << std::endl; + log << std::setw(11) << std::setfill('-') << "-" << "|" << std::setw(7) + << std::setfill('-') << "-" << "|" << std::setw(7) + << std::setfill('-') << "-" << "|" << std::setw(36) + << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); - log << std::left << std::setw(11) << "Submit " - << "|" << std::right << std::setw(7) << submitted << "|" - << std::setw(7) << std::setprecision(2) << perSec(submitted) << "|" - << std::setw(36) << "" << std::endl; + log << std::left << std::setw(11) << "Submit " << "|" << std::right + << std::setw(7) << submitted << "|" << std::setw(7) + << std::setprecision(2) << perSec(submitted) << "|" << std::setw(36) + << "" << std::endl; - log << std::left << std::setw(11) << "Accept " - << "|" << std::right << std::setw(7) << accepted << "|" - << std::setw(7) << std::setprecision(2) << perSec(accepted) << "|" - << std::setw(15) << std::left << "From Submit" << std::right - << std::setw(7) << std::setprecision(2) - << fmtS(submitToAccept.percentile(0.1f)) << std::setw(7) - << std::setprecision(2) << fmtS(submitToAccept.percentile(0.5f)) + log << std::left << std::setw(11) << "Accept " << "|" << std::right + << std::setw(7) << accepted << "|" << std::setw(7) + << std::setprecision(2) << perSec(accepted) << "|" << std::setw(15) + << std::left << "From Submit" << std::right << std::setw(7) + << std::setprecision(2) << fmtS(submitToAccept.percentile(0.1f)) << std::setw(7) << std::setprecision(2) - << fmtS(submitToAccept.percentile(0.9f)) << std::endl; + << fmtS(submitToAccept.percentile(0.5f)) << std::setw(7) + << std::setprecision(2) << fmtS(submitToAccept.percentile(0.9f)) + << std::endl; - log << std::left << std::setw(11) << "Validate " - << "|" << std::right << std::setw(7) << validated << "|" - << std::setw(7) << std::setprecision(2) << perSec(validated) << "|" - << std::setw(15) << std::left << "From Submit" << std::right - << std::setw(7) << std::setprecision(2) - << fmtS(submitToValidate.percentile(0.1f)) << std::setw(7) - << std::setprecision(2) << fmtS(submitToValidate.percentile(0.5f)) + log << std::left << std::setw(11) << "Validate " << "|" << std::right + << std::setw(7) << validated << "|" << std::setw(7) + << std::setprecision(2) << perSec(validated) << "|" << std::setw(15) + << std::left << "From Submit" << std::right << std::setw(7) + << std::setprecision(2) << fmtS(submitToValidate.percentile(0.1f)) << std::setw(7) << std::setprecision(2) - << fmtS(submitToValidate.percentile(0.9f)) << std::endl; - - log << std::left << std::setw(11) << "Orphan" - << "|" << std::right << std::setw(7) << orphaned() << "|" - << std::setw(7) << "" - << "|" << std::setw(36) << std::endl; - - log << std::left << std::setw(11) << "Unvalidated" - << "|" << std::right << std::setw(7) << unvalidated() << "|" - << std::setw(7) << "" - << "|" << std::setw(43) << std::endl; - - log << std::setw(11) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(36) << std::setfill('-') << "-" << std::endl; + << fmtS(submitToValidate.percentile(0.5f)) << std::setw(7) + << std::setprecision(2) << fmtS(submitToValidate.percentile(0.9f)) + << std::endl; + + log << std::left << std::setw(11) << "Orphan" << "|" << std::right + << std::setw(7) << orphaned() << "|" << std::setw(7) << "" << "|" + << std::setw(36) << std::endl; + + log << std::left << std::setw(11) << "Unvalidated" << "|" << std::right + << std::setw(7) << unvalidated() << "|" << std::setw(7) << "" << "|" + << std::setw(43) << std::endl; + + log << std::setw(11) << std::setfill('-') << "-" << "-" << std::setw(7) + << std::setfill('-') << "-" << "-" << std::setw(7) + << std::setfill('-') << "-" << "-" << std::setw(36) + << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); } @@ -362,34 +358,15 @@ struct TxCollector if (printHeaders) { - log << "tag" - << "," - << "txNumSubmitted" - << "," - << "txNumAccepted" - << "," - << "txNumValidated" - << "," - << "txNumOrphaned" - << "," - << "txUnvalidated" - << "," - << "txRateSumbitted" - << "," - << "txRateAccepted" - << "," - << "txRateValidated" - << "," - << "txLatencySubmitToAccept10Pctl" - << "," - << "txLatencySubmitToAccept50Pctl" - << "," - << "txLatencySubmitToAccept90Pctl" - << "," - << "txLatencySubmitToValidatet10Pctl" - << "," - << "txLatencySubmitToValidatet50Pctl" - << "," + log << "tag" << "," << "txNumSubmitted" << "," << "txNumAccepted" + << "," << "txNumValidated" << "," << "txNumOrphaned" << "," + << "txUnvalidated" << "," << "txRateSumbitted" << "," + << "txRateAccepted" << "," << "txRateValidated" << "," + << "txLatencySubmitToAccept10Pctl" << "," + << "txLatencySubmitToAccept50Pctl" << "," + << "txLatencySubmitToAccept90Pctl" << "," + << "txLatencySubmitToValidatet10Pctl" << "," + << "txLatencySubmitToValidatet50Pctl" << "," << "txLatencySubmitToValidatet90Pctl" << std::endl; } @@ -548,52 +525,50 @@ struct LedgerCollector if (printBreakline) { - log << std::setw(11) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(36) << std::setfill('-') << "-" - << std::endl; + log << std::setw(11) << std::setfill('-') << "-" << "-" + << std::setw(7) << std::setfill('-') << "-" << "-" + << std::setw(7) << std::setfill('-') << "-" << "-" + << std::setw(36) << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); } - log << std::left << std::setw(11) << "LedgerStats" - << "|" << std::setw(7) << "Count" - << "|" << std::setw(7) << "Per Sec" + log << std::left << std::setw(11) << "LedgerStats" << "|" + << std::setw(7) << "Count" << "|" << std::setw(7) << "Per Sec" << "|" << std::setw(15) << "Latency (sec)" << std::right << std::setw(7) << "10-ile" << std::setw(7) << "50-ile" << std::setw(7) << "90-ile" << std::left << std::endl; - log << std::setw(11) << std::setfill('-') << "-" - << "|" << std::setw(7) << std::setfill('-') << "-" - << "|" << std::setw(7) << std::setfill('-') << "-" - << "|" << std::setw(36) << std::setfill('-') << "-" << std::endl; + log << std::setw(11) << std::setfill('-') << "-" << "|" << std::setw(7) + << std::setfill('-') << "-" << "|" << std::setw(7) + << std::setfill('-') << "-" << "|" << std::setw(36) + << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); - log << std::left << std::setw(11) << "Accept " - << "|" << std::right << std::setw(7) << accepted << "|" - << std::setw(7) << std::setprecision(2) << perSec(accepted) << "|" - << std::setw(15) << std::left << "From Accept" << std::right - << std::setw(7) << std::setprecision(2) - << fmtS(acceptToAccept.percentile(0.1f)) << std::setw(7) - << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.5f)) + log << std::left << std::setw(11) << "Accept " << "|" << std::right + << std::setw(7) << accepted << "|" << std::setw(7) + << std::setprecision(2) << perSec(accepted) << "|" << std::setw(15) + << std::left << "From Accept" << std::right << std::setw(7) + << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.1f)) << std::setw(7) << std::setprecision(2) - << fmtS(acceptToAccept.percentile(0.9f)) << std::endl; + << fmtS(acceptToAccept.percentile(0.5f)) << std::setw(7) + << std::setprecision(2) << fmtS(acceptToAccept.percentile(0.9f)) + << std::endl; - log << std::left << std::setw(11) << "Validate " - << "|" << std::right << std::setw(7) << fullyValidated << "|" - << std::setw(7) << std::setprecision(2) << perSec(fullyValidated) - << "|" << std::setw(15) << std::left << "From Validate " - << std::right << std::setw(7) << std::setprecision(2) + log << std::left << std::setw(11) << "Validate " << "|" << std::right + << std::setw(7) << fullyValidated << "|" << std::setw(7) + << std::setprecision(2) << perSec(fullyValidated) << "|" + << std::setw(15) << std::left << "From Validate " << std::right + << std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.1f)) << std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.5f)) << std::setw(7) << std::setprecision(2) << fmtS(fullyValidToFullyValid.percentile(0.9f)) << std::endl; - log << std::setw(11) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(7) << std::setfill('-') << "-" - << "-" << std::setw(36) << std::setfill('-') << "-" << std::endl; + log << std::setw(11) << std::setfill('-') << "-" << "-" << std::setw(7) + << std::setfill('-') << "-" << "-" << std::setw(7) + << std::setfill('-') << "-" << "-" << std::setw(36) + << std::setfill('-') << "-" << std::endl; log << std::setfill(' '); } @@ -615,26 +590,14 @@ struct LedgerCollector if (printHeaders) { - log << "tag" - << "," - << "ledgerNumAccepted" - << "," - << "ledgerNumFullyValidated" - << "," - << "ledgerRateAccepted" - << "," - << "ledgerRateFullyValidated" - << "," - << "ledgerLatencyAcceptToAccept10Pctl" - << "," - << "ledgerLatencyAcceptToAccept50Pctl" - << "," - << "ledgerLatencyAcceptToAccept90Pctl" - << "," - << "ledgerLatencyFullyValidToFullyValid10Pctl" - << "," - << "ledgerLatencyFullyValidToFullyValid50Pctl" - << "," + log << "tag" << "," << "ledgerNumAccepted" << "," + << "ledgerNumFullyValidated" << "," << "ledgerRateAccepted" + << "," << "ledgerRateFullyValidated" << "," + << "ledgerLatencyAcceptToAccept10Pctl" << "," + << "ledgerLatencyAcceptToAccept50Pctl" << "," + << "ledgerLatencyAcceptToAccept90Pctl" << "," + << "ledgerLatencyFullyValidToFullyValid10Pctl" << "," + << "ledgerLatencyFullyValidToFullyValid50Pctl" << "," << "ledgerLatencyFullyValidToFullyValid90Pctl" << std::endl; } @@ -695,16 +658,16 @@ struct StreamCollector on(PeerID who, SimTime when, AcceptLedger const& e) { out << when.time_since_epoch().count() << ": Node " << who - << " accepted " - << "L" << e.ledger.id() << " " << e.ledger.txs() << "\n"; + << " accepted " << "L" << e.ledger.id() << " " << e.ledger.txs() + << "\n"; } void on(PeerID who, SimTime when, FullyValidateLedger const& e) { out << when.time_since_epoch().count() << ": Node " << who - << " fully-validated " - << "L" << e.ledger.id() << " " << e.ledger.txs() << "\n"; + << " fully-validated " << "L" << e.ledger.id() << " " + << e.ledger.txs() << "\n"; } }; diff --git a/src/test/csf/events.h b/src/test/csf/events.h index cdda88cd094..37d1b4b0bd7 100644 --- a/src/test/csf/events.h +++ b/src/test/csf/events.h @@ -19,11 +19,11 @@ #ifndef RIPPLE_TEST_CSF_EVENTS_H_INCLUDED #define RIPPLE_TEST_CSF_EVENTS_H_INCLUDED -#include #include #include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/csf/impl/ledgers.cpp b/src/test/csf/impl/ledgers.cpp index ccc767008a7..775ec2e58b1 100644 --- a/src/test/csf/impl/ledgers.cpp +++ b/src/test/csf/impl/ledgers.cpp @@ -16,8 +16,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include #include +#include #include diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 987c5e706c6..1c8d4eb54c6 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -19,16 +19,16 @@ #ifndef RIPPLE_TEST_CSF_LEDGERS_H_INCLUDED #define RIPPLE_TEST_CSF_LEDGERS_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include namespace ripple { namespace test { diff --git a/src/test/csf/random.h b/src/test/csf/random.h index 5a512400704..e78bbf515be 100644 --- a/src/test/csf/random.h +++ b/src/test/csf/random.h @@ -121,7 +121,7 @@ makeSelector(Iter first, Iter last, std::vector const& w, Generator& g) } //------------------------------------------------------------------------------ -// Additional distributions of interest not defined in in +// Additional distributions of interest not defined in /** Constant "distribution" that always returns the same value */ diff --git a/src/test/csf/timers.h b/src/test/csf/timers.h index b2a2b5a2da5..7e5d88c6696 100644 --- a/src/test/csf/timers.h +++ b/src/test/csf/timers.h @@ -19,10 +19,10 @@ #ifndef RIPPLE_TEST_CSF_TIMERS_H_INCLUDED #define RIPPLE_TEST_CSF_TIMERS_H_INCLUDED -#include -#include #include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/json/Object_test.cpp b/src/test/json/Object_test.cpp index bfdb6427059..4a64d6538a3 100644 --- a/src/test/json/Object_test.cpp +++ b/src/test/json/Object_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace Json { diff --git a/src/test/json/Output_test.cpp b/src/test/json/Output_test.cpp index 670f9c191f0..ddc5f08992b 100644 --- a/src/test/json/Output_test.cpp +++ b/src/test/json/Output_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace Json { diff --git a/src/test/json/TestOutputSuite.h b/src/test/json/TestOutputSuite.h index 0634f42a796..a4f36dac4ee 100644 --- a/src/test/json/TestOutputSuite.h +++ b/src/test/json/TestOutputSuite.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_RPC_TESTOUTPUTSUITE_H_INCLUDED #define RIPPLE_RPC_TESTOUTPUTSUITE_H_INCLUDED -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/json/Writer_test.cpp b/src/test/json/Writer_test.cpp index 99450ffeec5..97d4297c926 100644 --- a/src/test/json/Writer_test.cpp +++ b/src/test/json/Writer_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace Json { diff --git a/src/test/json/json_value_test.cpp b/src/test/json/json_value_test.cpp index 543a91a8a06..dacc22cc014 100644 --- a/src/test/json/json_value_test.cpp +++ b/src/test/json/json_value_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/jtx.h b/src/test/jtx.h index e6651fc1f0d..b7b9a9fa05c 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -22,7 +22,6 @@ // Convenience header that includes everything -#include #include #include #include @@ -33,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +41,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -66,5 +68,6 @@ #include #include #include +#include #endif diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index b98065a077e..52039f74aea 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -20,15 +20,15 @@ #ifndef RIPPLE_TEST_JTX_AMM_H_INCLUDED #define RIPPLE_TEST_JTX_AMM_H_INCLUDED -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include namespace ripple { namespace test { @@ -174,7 +174,8 @@ class AMM std::optional issue1 = std::nullopt, std::optional issue2 = std::nullopt, std::optional const& ammAccount = std::nullopt, - bool ignoreParams = false) const; + bool ignoreParams = false, + unsigned apiVersion = RPC::apiInvalidVersion) const; /** Verify the AMM balances. */ @@ -437,6 +438,14 @@ trust( std::uint32_t flags = 0); Json::Value pay(Account const& account, AccountID const& to, STAmount const& amount); + +Json::Value +ammClawback( + Account const& issuer, + Account const& holder, + Issue const& asset, + Issue const& asset2, + std::optional const& amount); } // namespace amm } // namespace jtx diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 5de805a10a7..0481dc98a49 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -19,11 +19,11 @@ #ifndef RIPPLE_TEST_JTX_AMMTEST_H_INCLUDED #define RIPPLE_TEST_JTX_AMMTEST_H_INCLUDED -#include -#include #include #include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/AbstractClient.h b/src/test/jtx/AbstractClient.h index 672ce422f0e..b447b9dedb1 100644 --- a/src/test/jtx/AbstractClient.h +++ b/src/test/jtx/AbstractClient.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED #define RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/Account.h b/src/test/jtx/Account.h index cca92e76fe0..bcf117d9357 100644 --- a/src/test/jtx/Account.h +++ b/src/test/jtx/Account.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED #define RIPPLE_TEST_JTX_ACCOUNT_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/test/jtx/CaptureLogs.h b/src/test/jtx/CaptureLogs.h index 9a64396ab7f..62bf25bda90 100644 --- a/src/test/jtx/CaptureLogs.h +++ b/src/test/jtx/CaptureLogs.h @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/CheckMessageLogs.h b/src/test/jtx/CheckMessageLogs.h index 66f5f7e106c..dc3253278cd 100644 --- a/src/test/jtx/CheckMessageLogs.h +++ b/src/test/jtx/CheckMessageLogs.h @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 0f21bff9fb6..d90d2bc1228 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -20,25 +20,6 @@ #ifndef RIPPLE_TEST_JTX_ENV_H_INCLUDED #define RIPPLE_TEST_JTX_ENV_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -48,6 +29,25 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -446,6 +446,12 @@ class Env PrettyAmount balance(Account const& account, Issue const& issue) const; + /** Return the number of objects owned by an account. + * Returns 0 if the account does not exist. + */ + std::uint32_t + ownerCount(Account const& account) const; + /** Return an account root. @return empty if the account does not exist. */ diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index 5c08469e0b8..691f96c2591 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/jtx/JSONRPCClient.h b/src/test/jtx/JSONRPCClient.h index bfd3154579e..bbffc726694 100644 --- a/src/test/jtx/JSONRPCClient.h +++ b/src/test/jtx/JSONRPCClient.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_HTTPCLIENT_H_INCLUDED #define RIPPLE_TEST_HTTPCLIENT_H_INCLUDED -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/JTx.h b/src/test/jtx/JTx.h index 2eeb6ccf8f4..a5a4a9eb1b9 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TEST_JTX_JTX_H_INCLUDED #define RIPPLE_TEST_JTX_JTX_H_INCLUDED -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #include diff --git a/src/test/jtx/ManualTimeKeeper.h b/src/test/jtx/ManualTimeKeeper.h index f3adb29b5f0..86477bf99f6 100644 --- a/src/test/jtx/ManualTimeKeeper.h +++ b/src/test/jtx/ManualTimeKeeper.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TEST_MANUALTIMEKEEPER_H_INCLUDED #define RIPPLE_TEST_MANUALTIMEKEEPER_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/test/jtx/Oracle.h b/src/test/jtx/Oracle.h index ac46b32d092..32ec0b2e859 100644 --- a/src/test/jtx/Oracle.h +++ b/src/test/jtx/Oracle.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_ORACLE_H_INCLUDED #define RIPPLE_TEST_JTX_ORACLE_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h index f578ceb190d..0f4c4ddd3dd 100644 --- a/src/test/jtx/PathSet.h +++ b/src/test/jtx/PathSet.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_TESTS_PATHSET_H_INCLUDED #define RIPPLE_LEDGER_TESTS_PATHSET_H_INCLUDED -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index b065cec470a..d81551aa840 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -19,13 +19,13 @@ #ifndef RIPPLE_TEST_JTX_TESTHELPERS_H_INCLUDED #define RIPPLE_TEST_JTX_TESTHELPERS_H_INCLUDED -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include @@ -39,11 +39,11 @@ namespace jtx { // when we have moved to better compilers. template auto -make_vector(Input const& input) requires requires(Input& v) -{ - std::begin(v); - std::end(v); -} +make_vector(Input const& input) + requires requires(Input& v) { + std::begin(v); + std::end(v); + } { return std::vector(std::begin(input), std::end(input)); } @@ -96,6 +96,10 @@ getAccountLines(Env& env, AccountID const& acctId, IOU... ious) [[nodiscard]] bool checkArraySize(Json::Value const& val, unsigned int size); +// Helper function that returns the owner count on an account. +std::uint32_t +ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); + /* Path finding */ /******************************************************************************/ void diff --git a/src/test/jtx/TestSuite.h b/src/test/jtx/TestSuite.h index 9bd8d50ab57..22b8fb7e53b 100644 --- a/src/test/jtx/TestSuite.h +++ b/src/test/jtx/TestSuite.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_BASICS_TESTSUITE_H_INCLUDED #define RIPPLE_BASICS_TESTSUITE_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/test/jtx/TrustedPublisherServer.h b/src/test/jtx/TrustedPublisherServer.h index da7ea1c137a..b786cae2846 100644 --- a/src/test/jtx/TrustedPublisherServer.h +++ b/src/test/jtx/TrustedPublisherServer.h @@ -19,12 +19,13 @@ #ifndef RIPPLE_TEST_TRUSTED_PUBLISHER_SERVER_H_INCLUDED #define RIPPLE_TEST_TRUSTED_PUBLISHER_SERVER_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -33,7 +34,6 @@ #include #include #include -#include #include #include @@ -217,9 +217,8 @@ class TrustedPublisherServer getList_ = [blob = blob, sig, manifest, version](int interval) { // Build the contents of a version 1 format UNL file std::stringstream l; - l << "{\"blob\":\"" << blob << "\"" - << ",\"signature\":\"" << sig << "\"" - << ",\"manifest\":\"" << manifest << "\"" + l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig + << "\"" << ",\"manifest\":\"" << manifest << "\"" << ",\"refresh_interval\": " << interval << ",\"version\":" << version << '}'; return l.str(); @@ -254,15 +253,14 @@ class TrustedPublisherServer std::stringstream l; for (auto const& info : blobInfo) { - l << "{\"blob\":\"" << info.blob << "\"" - << ",\"signature\":\"" << info.signature << "\"},"; + l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\"" + << info.signature << "\"},"; } std::string blobs = l.str(); blobs.pop_back(); l.str(std::string()); l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest - << "\"" - << ",\"refresh_interval\": " << interval + << "\"" << ",\"refresh_interval\": " << interval << ",\"version\":" << (version + 1) << '}'; return l.str(); }; @@ -574,7 +572,7 @@ xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt if (ec) break; - std::string path = req.target(); + std::string_view const path = req.target(); res.insert("Server", "TrustedPublisherServer"); res.version(req.version()); res.keep_alive(req.keep_alive()); @@ -677,7 +675,9 @@ xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt // unknown request res.result(boost::beast::http::status::not_found); res.insert("Content-Type", "text/html"); - res.body() = "The file '" + path + "' was not found"; + res.body() = "The file '" + std::string(path) + + "' was not " + "found"; } if (prepare) diff --git a/src/test/jtx/WSClient.h b/src/test/jtx/WSClient.h index 8961522a038..d0356dbcbf7 100644 --- a/src/test/jtx/WSClient.h +++ b/src/test/jtx/WSClient.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_WSCLIENT_H_INCLUDED #define RIPPLE_TEST_WSCLIENT_H_INCLUDED -#include #include +#include #include #include diff --git a/src/test/jtx/WSClient_test.cpp b/src/test/jtx/WSClient_test.cpp index 85440003ba4..18dadb0bf9a 100644 --- a/src/test/jtx/WSClient_test.cpp +++ b/src/test/jtx/WSClient_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index f8a8f67c75e..9468b791f3d 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -20,16 +20,16 @@ #ifndef RIPPLE_TEST_JTX_AMOUNT_H_INCLUDED #define RIPPLE_TEST_JTX_AMOUNT_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -126,7 +126,7 @@ struct PrettyAmount return amount_; } - operator STAmount const &() const + operator STAmount const&() const { return amount_; } @@ -211,7 +211,8 @@ struct XRP_t /** @} */ /** Returns None-of-XRP */ - None operator()(none_t) const + None + operator()(none_t) const { return {xrpIssue()}; } @@ -305,15 +306,19 @@ class IOU return {currency, account.id()}; } - /** Implicit conversion to Issue. + /** Implicit conversion to Issue or Asset. This allows passing an IOU - value where an Issue is expected. + value where an Issue or Asset is expected. */ operator Issue() const { return issue(); } + operator Asset() const + { + return issue(); + } template < class T, @@ -327,14 +332,17 @@ class IOU return {amountFromString(issue(), std::to_string(v)), account.name()}; } - PrettyAmount operator()(epsilon_t) const; - PrettyAmount operator()(detail::epsilon_multiple) const; + PrettyAmount + operator()(epsilon_t) const; + PrettyAmount + operator()(detail::epsilon_multiple) const; // VFALCO TODO // STAmount operator()(char const* s) const; /** Returns None-of-Issue */ - None operator()(none_t) const + None + operator()(none_t) const { return {issue()}; } @@ -351,6 +359,67 @@ operator<<(std::ostream& os, IOU const& iou); //------------------------------------------------------------------------------ +/** Converts to MPT Issue or STAmount. + + Examples: + MPT Converts to the underlying Issue + MPT(10) Returns STAmount of 10 of + the underlying MPT +*/ +class MPT +{ +public: + std::string name; + ripple::MPTID issuanceID; + + MPT(std::string const& n, ripple::MPTID const& issuanceID_) + : name(n), issuanceID(issuanceID_) + { + } + + ripple::MPTID const& + mpt() const + { + return issuanceID; + } + + /** Implicit conversion to MPTIssue. + + This allows passing an MPT + value where an MPTIssue is expected. + */ + operator ripple::MPTIssue() const + { + return MPTIssue{issuanceID}; + } + + template + requires(sizeof(T) >= sizeof(int) && std::is_arithmetic_v) + PrettyAmount + operator()(T v) const + { + return {amountFromString(mpt(), std::to_string(v)), name}; + } + + PrettyAmount + operator()(epsilon_t) const; + PrettyAmount + operator()(detail::epsilon_multiple) const; + + friend BookSpec + operator~(MPT const& mpt) + { + assert(false); + Throw("MPT is not supported"); + return BookSpec{beast::zero, noCurrency()}; + } +}; + +std::ostream& +operator<<(std::ostream& os, MPT const& mpt); + +//------------------------------------------------------------------------------ + struct any_t { inline AnyAmount diff --git a/src/test/jtx/attester.h b/src/test/jtx/attester.h index 7741991b752..327fb2e4873 100644 --- a/src/test/jtx/attester.h +++ b/src/test/jtx/attester.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_ATTESTER_H_INCLUDED #define RIPPLE_TEST_JTX_ATTESTER_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/test/jtx/credentials.h b/src/test/jtx/credentials.h new file mode 100644 index 00000000000..2f5c63dccb8 --- /dev/null +++ b/src/test/jtx/credentials.h @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace credentials { + +// Sets the optional URI. +class uri +{ +private: + std::string const uri_; + +public: + explicit uri(std::string_view u) : uri_(strHex(u)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + jtx.jv[sfURI.jsonName] = uri_; + } +}; + +// Set credentialsIDs array +class ids +{ +private: + std::vector const credentials_; + +public: + explicit ids(std::vector const& creds) : credentials_(creds) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const + { + auto& arr(jtx.jv[sfCredentialIDs.jsonName] = Json::arrayValue); + for (auto const& hash : credentials_) + arr.append(hash); + } +}; + +Json::Value +create( + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType); + +Json::Value +accept( + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType); + +Json::Value +deleteCred( + jtx::Account const& acc, + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType); + +Json::Value +ledgerEntry( + jtx::Env& env, + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType); + +Json::Value +ledgerEntry(jtx::Env& env, std::string const& credIdx); + +} // namespace credentials + +} // namespace jtx + +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 579b66e97ff..46e633dab20 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_DELIVERMIN_H_INCLUDED #define RIPPLE_TEST_JTX_DELIVERMIN_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/deposit.h b/src/test/jtx/deposit.h index 720254e7eae..9de3416367c 100644 --- a/src/test/jtx/deposit.h +++ b/src/test/jtx/deposit.h @@ -38,6 +38,41 @@ auth(Account const& account, Account const& auth); Json::Value unauth(Account const& account, Account const& unauth); +struct AuthorizeCredentials +{ + jtx::Account issuer; + std::string credType; + + Json::Value + toJson() const + { + Json::Value jv; + jv[jss::Issuer] = issuer.human(); + jv[sfCredentialType.jsonName] = strHex(credType); + return jv; + } + + // "ledger_entry" uses a different naming convention + Json::Value + toLEJson() const + { + Json::Value jv; + jv[jss::issuer] = issuer.human(); + jv[jss::credential_type] = strHex(credType); + return jv; + } +}; + +Json::Value +authCredentials( + jtx::Account const& account, + std::vector const& auth); + +Json::Value +unauthCredentials( + jtx::Account const& account, + std::vector const& auth); + } // namespace deposit } // namespace jtx diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index 02c99c52f09..259b61b6caf 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TEST_JTX_ENVCONFIG_H_INCLUDED #define RIPPLE_TEST_JTX_ENVCONFIG_H_INCLUDED -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index 58813409edd..c671e0b2a1e 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TEST_JTX_FEE_H_INCLUDED #define RIPPLE_TEST_JTX_FEE_H_INCLUDED -#include -#include #include #include +#include +#include #include diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index 27b6ea95611..c8887cdee4b 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TEST_JTX_FLAGS_H_INCLUDED #define RIPPLE_TEST_JTX_FLAGS_H_INCLUDED -#include -#include -#include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index 38cdb201854..089d3508d70 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -19,12 +19,12 @@ #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -42,8 +42,8 @@ static IOUAmount initialTokens(STAmount const& asset1, STAmount const& asset2) { auto const product = number(asset1) * number(asset2); - return (IOUAmount)( - product.mantissa() >= 0 ? root2(product) : root2(-product)); + return (IOUAmount)(product.mantissa() >= 0 ? root2(product) + : root2(-product)); } AMM::AMM( @@ -140,7 +140,7 @@ AMM::create( if (flags) jv[jss::Flags] = *flags; if (fee_ != 0) - jv[jss::Fee] = std::to_string(fee_); + jv[sfFee] = std::to_string(fee_); else jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops()); submit(jv, seq, ter); @@ -163,7 +163,8 @@ AMM::ammRpcInfo( std::optional issue1, std::optional issue2, std::optional const& ammAccount, - bool ignoreParams) const + bool ignoreParams, + unsigned apiVersion) const { Json::Value jv; if (account) @@ -191,7 +192,10 @@ AMM::ammRpcInfo( if (ammAccount) jv[jss::amm_account] = to_string(*ammAccount); } - auto jr = env_.rpc("json", "amm_info", to_string(jv)); + auto jr = + (apiVersion == RPC::apiInvalidVersion + ? env_.rpc("json", "amm_info", to_string(jv)) + : env_.rpc(apiVersion, "json", "amm_info", to_string(jv))); if (jr.isObject() && jr.isMember(jss::result) && jr[jss::result].isMember(jss::status)) return jr[jss::result]; @@ -819,6 +823,26 @@ pay(Account const& account, AccountID const& to, STAmount const& amount) jv[jss::Flags] = tfUniversal; return jv; } + +Json::Value +ammClawback( + Account const& issuer, + Account const& holder, + Issue const& asset, + Issue const& asset2, + std::optional const& amount) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::AMMClawback; + jv[jss::Account] = issuer.human(); + jv[jss::Holder] = holder.human(); + jv[jss::Asset] = to_json(asset); + jv[jss::Asset2] = to_json(asset2); + if (amount) + jv[jss::Amount] = amount->getJson(JsonOptions::none); + + return jv; +} } // namespace amm } // namespace jtx } // namespace test diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 187cb3cee05..575e2e1d889 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -19,13 +19,13 @@ #include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/Account.cpp b/src/test/jtx/impl/Account.cpp index a17186d4ffb..9d41456ef75 100644 --- a/src/test/jtx/impl/Account.cpp +++ b/src/test/jtx/impl/Account.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 884caf9162e..ef5a2124e24 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -17,27 +17,6 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -49,6 +28,27 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -317,16 +317,24 @@ Env::submit(JTx const& jt) auto const jr = [&]() { if (jt.stx) { - txid_ = jt.stx->getTransactionID(); - Serializer s; - jt.stx->add(s); - auto const jr = rpc("submit", strHex(s.slice())); - - parsedResult = parseResult(jr); - test.expect(parsedResult.ter, "ter uninitialized!"); - ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); - - return jr; + // We shouldn't need to retry, but it fixes the test on macOS for + // the moment. + int retries = 3; + do + { + txid_ = jt.stx->getTransactionID(); + Serializer s; + jt.stx->add(s); + auto const jr = rpc("submit", strHex(s.slice())); + + parsedResult = parseResult(jr); + test.expect(parsedResult.ter, "ter uninitialized!"); + ter_ = parsedResult.ter.value_or(telENV_RPC_FAILED); + if (ter_ != telENV_RPC_FAILED || + parsedResult.rpcCode != rpcINTERNAL || + jt.ter == telENV_RPC_FAILED || --retries <= 0) + return jr; + } while (true); } else { diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index 1e3664c60ec..20f2149e4a0 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -16,10 +16,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -27,7 +28,6 @@ #include #include #include -#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/Oracle.cpp b/src/test/jtx/impl/Oracle.cpp index 34249a61228..df9483cbaae 100644 --- a/src/test/jtx/impl/Oracle.cpp +++ b/src/test/jtx/impl/Oracle.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include #include diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 41d9db7c7e5..b39cac7dcc1 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -19,9 +19,9 @@ #include -#include #include #include +#include namespace ripple { namespace test { @@ -50,6 +50,15 @@ checkArraySize(Json::Value const& val, unsigned int size) return val.isArray() && val.size() == size; } +std::uint32_t +ownerCount(Env const& env, Account const& account) +{ + std::uint32_t ret{0}; + if (auto const sleAccount = env.le(account)) + ret = sleAccount->getFieldU32(sfOwnerCount); + return ret; +} + /* Path finding */ /******************************************************************************/ void @@ -385,4 +394,4 @@ allpe(AccountID const& a, Issue const& iss) } // namespace jtx } // namespace test -} // namespace ripple \ No newline at end of file +} // namespace ripple diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index e9b27168108..185d0ea5dba 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -32,7 +32,7 @@ #include -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/acctdelete.cpp b/src/test/jtx/impl/acctdelete.cpp index a62ab6a48bb..d7f8f10e04d 100644 --- a/src/test/jtx/impl/acctdelete.cpp +++ b/src/test/jtx/impl/acctdelete.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index 86f51d135d7..9f374c2cb58 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include +#include +#include +#include #include #include #include -#include -#include namespace ripple { namespace test { @@ -104,7 +104,8 @@ operator<<(std::ostream& os, PrettyAmount const& amount) XRP_t const XRP{}; -PrettyAmount IOU::operator()(epsilon_t) const +PrettyAmount +IOU::operator()(epsilon_t) const { return {STAmount(issue(), 1, -81), account.name()}; } diff --git a/src/test/jtx/impl/attester.cpp b/src/test/jtx/impl/attester.cpp index dd00f536af8..66be9da83b3 100644 --- a/src/test/jtx/impl/attester.cpp +++ b/src/test/jtx/impl/attester.cpp @@ -19,11 +19,11 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/check.cpp b/src/test/jtx/impl/check.cpp index d862130bc70..21af6c9cc3f 100644 --- a/src/test/jtx/impl/check.cpp +++ b/src/test/jtx/impl/check.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/credentials.cpp b/src/test/jtx/impl/credentials.cpp new file mode 100644 index 00000000000..bc7ccf93cd4 --- /dev/null +++ b/src/test/jtx/impl/credentials.cpp @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace credentials { + +Json::Value +create( + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::CredentialCreate; + + jv[jss::Account] = issuer.human(); + jv[jss::Subject] = subject.human(); + + jv[jss::Flags] = tfUniversal; + jv[sfCredentialType.jsonName] = strHex(credType); + + return jv; +} + +Json::Value +accept( + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::CredentialAccept; + jv[jss::Account] = subject.human(); + jv[jss::Issuer] = issuer.human(); + jv[sfCredentialType.jsonName] = strHex(credType); + jv[jss::Flags] = tfUniversal; + + return jv; +} + +Json::Value +deleteCred( + jtx::Account const& acc, + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::CredentialDelete; + jv[jss::Account] = acc.human(); + jv[jss::Subject] = subject.human(); + jv[jss::Issuer] = issuer.human(); + jv[sfCredentialType.jsonName] = strHex(credType); + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +ledgerEntry( + jtx::Env& env, + jtx::Account const& subject, + jtx::Account const& issuer, + std::string_view credType) +{ + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::credential][jss::subject] = subject.human(); + jvParams[jss::credential][jss::issuer] = issuer.human(); + jvParams[jss::credential][jss::credential_type] = strHex(credType); + return env.rpc("json", "ledger_entry", to_string(jvParams)); +} + +Json::Value +ledgerEntry(jtx::Env& env, std::string const& credIdx) +{ + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::credential] = credIdx; + return env.rpc("json", "ledger_entry", to_string(jvParams)); +} + +} // namespace credentials + +} // namespace jtx + +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/impl/delivermin.cpp b/src/test/jtx/impl/delivermin.cpp index 464c31e0118..6e4838c7c0a 100644 --- a/src/test/jtx/impl/delivermin.cpp +++ b/src/test/jtx/impl/delivermin.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/deposit.cpp b/src/test/jtx/impl/deposit.cpp index 803705c8cb5..d91607c9906 100644 --- a/src/test/jtx/impl/deposit.cpp +++ b/src/test/jtx/impl/deposit.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { @@ -48,6 +48,46 @@ unauth(jtx::Account const& account, jtx::Account const& unauth) return jv; } +// Add DepositPreauth. +Json::Value +authCredentials( + jtx::Account const& account, + std::vector const& auth) +{ + Json::Value jv; + jv[sfAccount.jsonName] = account.human(); + jv[sfAuthorizeCredentials.jsonName] = Json::arrayValue; + auto& arr(jv[sfAuthorizeCredentials.jsonName]); + for (auto const& o : auth) + { + Json::Value j2; + j2[jss::Credential] = o.toJson(); + arr.append(std::move(j2)); + } + jv[sfTransactionType.jsonName] = jss::DepositPreauth; + return jv; +} + +// Remove DepositPreauth. +Json::Value +unauthCredentials( + jtx::Account const& account, + std::vector const& auth) +{ + Json::Value jv; + jv[sfAccount.jsonName] = account.human(); + jv[sfUnauthorizeCredentials.jsonName] = Json::arrayValue; + auto& arr(jv[sfUnauthorizeCredentials.jsonName]); + for (auto const& o : auth) + { + Json::Value j2; + j2[jss::Credential] = o.toJson(); + arr.append(std::move(j2)); + } + jv[sfTransactionType.jsonName] = jss::DepositPreauth; + return jv; +} + } // namespace deposit } // namespace jtx diff --git a/src/test/jtx/impl/did.cpp b/src/test/jtx/impl/did.cpp index 94dfcc32754..a9a6e974ef4 100644 --- a/src/test/jtx/impl/did.cpp +++ b/src/test/jtx/impl/did.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index cbca244be6d..c9788a6d70f 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -19,8 +19,8 @@ #include -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/fee.cpp b/src/test/jtx/impl/fee.cpp index dea666cd3e8..e390136a3c0 100644 --- a/src/test/jtx/impl/fee.cpp +++ b/src/test/jtx/impl/fee.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/flags.cpp b/src/test/jtx/impl/flags.cpp index c80e329fdc8..992e1a88bb2 100644 --- a/src/test/jtx/impl/flags.cpp +++ b/src/test/jtx/impl/flags.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/jtx_json.cpp b/src/test/jtx/impl/jtx_json.cpp index d5fb7bfae91..c9cf6636cf9 100644 --- a/src/test/jtx/impl/jtx_json.cpp +++ b/src/test/jtx/impl/jtx_json.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include #include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/last_ledger_sequence.cpp b/src/test/jtx/impl/last_ledger_sequence.cpp index b0d7bd5c5eb..57742bab2a1 100644 --- a/src/test/jtx/impl/last_ledger_sequence.cpp +++ b/src/test/jtx/impl/last_ledger_sequence.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/ledgerStateFix.cpp b/src/test/jtx/impl/ledgerStateFix.cpp new file mode 100644 index 00000000000..2f121dc2671 --- /dev/null +++ b/src/test/jtx/impl/ledgerStateFix.cpp @@ -0,0 +1,49 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace ledgerStateFix { + +// Fix NFTokenPage links on owner's account. acct pays fee. +Json::Value +nftPageLinks(jtx::Account const& acct, jtx::Account const& owner) +{ + Json::Value jv; + jv[sfAccount.jsonName] = acct.human(); + jv[sfLedgerFixType.jsonName] = LedgerStateFix::nfTokenPageLink; + jv[sfOwner.jsonName] = owner.human(); + jv[sfTransactionType.jsonName] = jss::LedgerStateFix; + jv[sfFlags.jsonName] = tfUniversal; + return jv; +} + +} // namespace ledgerStateFix + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/impl/memo.cpp b/src/test/jtx/impl/memo.cpp index 174a132eb3d..6469748b4f9 100644 --- a/src/test/jtx/impl/memo.cpp +++ b/src/test/jtx/impl/memo.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp new file mode 100644 index 00000000000..ead6a47c25e --- /dev/null +++ b/src/test/jtx/impl/mpt.cpp @@ -0,0 +1,421 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include + +namespace ripple { +namespace test { +namespace jtx { + +void +mptflags::operator()(Env& env) const +{ + env.test.expect(tester_.checkFlags(flags_, holder_)); +} + +void +mptbalance::operator()(Env& env) const +{ + env.test.expect(amount_ == tester_.getBalance(account_)); +} + +void +requireAny::operator()(Env& env) const +{ + env.test.expect(cb_()); +} + +std::unordered_map +MPTTester::makeHolders(std::vector const& holders) +{ + std::unordered_map accounts; + for (auto const& h : holders) + { + if (accounts.find(h.human()) != accounts.cend()) + Throw("Duplicate holder"); + accounts.emplace(h.human(), h); + } + return accounts; +} + +MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg) + : env_(env) + , issuer_(issuer) + , holders_(makeHolders(arg.holders)) + , close_(arg.close) +{ + if (arg.fund) + { + env_.fund(arg.xrp, issuer_); + for (auto it : holders_) + env_.fund(arg.xrpHolders, it.second); + } + if (close_) + env.close(); + if (arg.fund) + { + env_.require(owners(issuer_, 0)); + for (auto it : holders_) + { + if (issuer_.id() == it.second.id()) + Throw("Issuer can't be holder"); + env_.require(owners(it.second, 0)); + } + } +} + +void +MPTTester::create(const MPTCreate& arg) +{ + if (id_) + Throw("MPT can't be reused"); + id_ = makeMptID(env_.seq(issuer_), issuer_); + Json::Value jv; + jv[sfAccount] = issuer_.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + if (arg.assetScale) + jv[sfAssetScale] = *arg.assetScale; + if (arg.transferFee) + jv[sfTransferFee] = *arg.transferFee; + if (arg.metadata) + jv[sfMPTokenMetadata] = strHex(*arg.metadata); + if (arg.maxAmt) + jv[sfMaximumAmount] = std::to_string(*arg.maxAmt); + if (submit(arg, jv) != tesSUCCESS) + { + // Verify issuance doesn't exist + env_.require(requireAny([&]() -> bool { + return env_.le(keylet::mptIssuance(*id_)) == nullptr; + })); + + id_.reset(); + } + else + env_.require(mptflags(*this, arg.flags.value_or(0))); +} + +void +MPTTester::destroy(MPTDestroy const& arg) +{ + Json::Value jv; + if (arg.issuer) + jv[sfAccount] = arg.issuer->human(); + else + jv[sfAccount] = issuer_.human(); + if (arg.id) + jv[sfMPTokenIssuanceID] = to_string(*arg.id); + else + { + if (!id_) + Throw("MPT has not been created"); + jv[sfMPTokenIssuanceID] = to_string(*id_); + } + jv[sfTransactionType] = jss::MPTokenIssuanceDestroy; + submit(arg, jv); +} + +Account const& +MPTTester::holder(std::string const& holder_) const +{ + auto const& it = holders_.find(holder_); + if (it == holders_.cend()) + Throw("Holder is not found"); + return it->second; +} + +void +MPTTester::authorize(MPTAuthorize const& arg) +{ + Json::Value jv; + if (arg.account) + jv[sfAccount] = arg.account->human(); + else + jv[sfAccount] = issuer_.human(); + jv[sfTransactionType] = jss::MPTokenAuthorize; + if (arg.id) + jv[sfMPTokenIssuanceID] = to_string(*arg.id); + else + { + if (!id_) + Throw("MPT has not been created"); + jv[sfMPTokenIssuanceID] = to_string(*id_); + } + if (arg.holder) + jv[sfHolder] = arg.holder->human(); + if (auto const result = submit(arg, jv); result == tesSUCCESS) + { + // Issuer authorizes + if (!arg.account || *arg.account == issuer_) + { + auto const flags = getFlags(arg.holder); + // issuer un-authorizes the holder + if (arg.flags.value_or(0) == tfMPTUnauthorize) + env_.require(mptflags(*this, flags, arg.holder)); + // issuer authorizes the holder + else + env_.require( + mptflags(*this, flags | lsfMPTAuthorized, arg.holder)); + } + // Holder authorizes + else if (arg.flags.value_or(0) != tfMPTUnauthorize) + { + auto const flags = getFlags(arg.account); + // holder creates a token + env_.require(mptflags(*this, flags, arg.account)); + env_.require(mptbalance(*this, *arg.account, 0)); + } + else + { + // Verify that the MPToken doesn't exist. + forObject( + [&](SLEP const& sle) { return env_.test.BEAST_EXPECT(!sle); }, + arg.account); + } + } + else if ( + arg.account && *arg.account != issuer_ && + arg.flags.value_or(0) != tfMPTUnauthorize && id_) + { + if (result == tecDUPLICATE) + { + // Verify that MPToken already exists + env_.require(requireAny([&]() -> bool { + return env_.le(keylet::mptoken(*id_, arg.account->id())) != + nullptr; + })); + } + else + { + // Verify MPToken doesn't exist if holder failed authorizing(unless + // it already exists) + env_.require(requireAny([&]() -> bool { + return env_.le(keylet::mptoken(*id_, arg.account->id())) == + nullptr; + })); + } + } +} + +void +MPTTester::set(MPTSet const& arg) +{ + Json::Value jv; + if (arg.account) + jv[sfAccount] = arg.account->human(); + else + jv[sfAccount] = issuer_.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceSet; + if (arg.id) + jv[sfMPTokenIssuanceID] = to_string(*arg.id); + else + { + if (!id_) + Throw("MPT has not been created"); + jv[sfMPTokenIssuanceID] = to_string(*id_); + } + if (arg.holder) + jv[sfHolder] = arg.holder->human(); + if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0)) + { + auto require = [&](std::optional const& holder, + bool unchanged) { + auto flags = getFlags(holder); + if (!unchanged) + { + if (*arg.flags & tfMPTLock) + flags |= lsfMPTLocked; + else if (*arg.flags & tfMPTUnlock) + flags &= ~lsfMPTLocked; + else + Throw("Invalid flags"); + } + env_.require(mptflags(*this, flags, holder)); + }; + if (arg.account) + require(std::nullopt, arg.holder.has_value()); + if (arg.holder) + require(*arg.holder, false); + } +} + +bool +MPTTester::forObject( + std::function const& cb, + std::optional const& holder_) const +{ + if (!id_) + Throw("MPT has not been created"); + auto const key = holder_ ? keylet::mptoken(*id_, holder_->id()) + : keylet::mptIssuance(*id_); + if (auto const sle = env_.le(key)) + return cb(sle); + return false; +} + +[[nodiscard]] bool +MPTTester::checkMPTokenAmount( + Account const& holder_, + std::int64_t expectedAmount) const +{ + return forObject( + [&](SLEP const& sle) { return expectedAmount == (*sle)[sfMPTAmount]; }, + holder_); +} + +[[nodiscard]] bool +MPTTester::checkMPTokenOutstandingAmount(std::int64_t expectedAmount) const +{ + return forObject([&](SLEP const& sle) { + return expectedAmount == (*sle)[sfOutstandingAmount]; + }); +} + +[[nodiscard]] bool +MPTTester::checkFlags( + uint32_t const expectedFlags, + std::optional const& holder) const +{ + return expectedFlags == getFlags(holder); +} + +void +MPTTester::pay( + Account const& src, + Account const& dest, + std::int64_t amount, + std::optional err, + std::optional> credentials) +{ + if (!id_) + Throw("MPT has not been created"); + auto const srcAmt = getBalance(src); + auto const destAmt = getBalance(dest); + auto const outstnAmt = getBalance(issuer_); + + if (credentials) + env_( + jtx::pay(src, dest, mpt(amount)), + ter(err.value_or(tesSUCCESS)), + credentials::ids(*credentials)); + else + env_(jtx::pay(src, dest, mpt(amount)), ter(err.value_or(tesSUCCESS))); + + if (env_.ter() != tesSUCCESS) + amount = 0; + if (close_) + env_.close(); + if (src == issuer_) + { + env_.require(mptbalance(*this, src, srcAmt + amount)); + env_.require(mptbalance(*this, dest, destAmt + amount)); + } + else if (dest == issuer_) + { + env_.require(mptbalance(*this, src, srcAmt - amount)); + env_.require(mptbalance(*this, dest, destAmt - amount)); + } + else + { + STAmount const saAmount = {*id_, amount}; + auto const actual = + multiply(saAmount, transferRate(*env_.current(), *id_)) + .mpt() + .value(); + // Sender pays the transfer fee if any + env_.require(mptbalance(*this, src, srcAmt - actual)); + env_.require(mptbalance(*this, dest, destAmt + amount)); + // Outstanding amount is reduced by the transfer fee if any + env_.require(mptbalance(*this, issuer_, outstnAmt - (actual - amount))); + } +} + +void +MPTTester::claw( + Account const& issuer, + Account const& holder, + std::int64_t amount, + std::optional err) +{ + if (!id_) + Throw("MPT has not been created"); + auto const issuerAmt = getBalance(issuer); + auto const holderAmt = getBalance(holder); + env_(jtx::claw(issuer, mpt(amount), holder), ter(err.value_or(tesSUCCESS))); + if (env_.ter() != tesSUCCESS) + amount = 0; + if (close_) + env_.close(); + + env_.require( + mptbalance(*this, issuer, issuerAmt - std::min(holderAmt, amount))); + env_.require( + mptbalance(*this, holder, holderAmt - std::min(holderAmt, amount))); +} + +PrettyAmount +MPTTester::mpt(std::int64_t amount) const +{ + if (!id_) + Throw("MPT has not been created"); + return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount); +} + +std::int64_t +MPTTester::getBalance(Account const& account) const +{ + if (!id_) + Throw("MPT has not been created"); + if (account == issuer_) + { + if (auto const sle = env_.le(keylet::mptIssuance(*id_))) + return sle->getFieldU64(sfOutstandingAmount); + } + else + { + if (auto const sle = env_.le(keylet::mptoken(*id_, account.id()))) + return sle->getFieldU64(sfMPTAmount); + } + return 0; +} + +std::uint32_t +MPTTester::getFlags(std::optional const& holder) const +{ + std::uint32_t flags = 0; + if (!forObject( + [&](SLEP const& sle) { + flags = sle->getFlags(); + return true; + }, + holder)) + Throw("Failed to get the flags"); + return flags; +} + +MPT +MPTTester::operator[](const std::string& name) +{ + return MPT(name, issuanceID()); +} + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 1e1f5141798..42c3bfc78bf 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/offer.cpp b/src/test/jtx/impl/offer.cpp index 6e9a1b4f2ff..55a1af4beab 100644 --- a/src/test/jtx/impl/offer.cpp +++ b/src/test/jtx/impl/offer.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/paths.cpp b/src/test/jtx/impl/paths.cpp index 5de9761a110..393e36e9d61 100644 --- a/src/test/jtx/impl/paths.cpp +++ b/src/test/jtx/impl/paths.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/pay.cpp b/src/test/jtx/impl/pay.cpp index 35df2ec191c..2a627223fdd 100644 --- a/src/test/jtx/impl/pay.cpp +++ b/src/test/jtx/impl/pay.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/quality2.cpp b/src/test/jtx/impl/quality2.cpp index e1837035e2d..dd10b3c6117 100644 --- a/src/test/jtx/impl/quality2.cpp +++ b/src/test/jtx/impl/quality2.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/rate.cpp b/src/test/jtx/impl/rate.cpp index cf4cd3211a8..f95170537f3 100644 --- a/src/test/jtx/impl/rate.cpp +++ b/src/test/jtx/impl/rate.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/regkey.cpp b/src/test/jtx/impl/regkey.cpp index 917fb440624..9fc260d2d03 100644 --- a/src/test/jtx/impl/regkey.cpp +++ b/src/test/jtx/impl/regkey.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/sendmax.cpp b/src/test/jtx/impl/sendmax.cpp index a292e2a98ef..9514b923166 100644 --- a/src/test/jtx/impl/sendmax.cpp +++ b/src/test/jtx/impl/sendmax.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/seq.cpp b/src/test/jtx/impl/seq.cpp index ca57e79227d..3a3de002db6 100644 --- a/src/test/jtx/impl/seq.cpp +++ b/src/test/jtx/impl/seq.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/tag.cpp b/src/test/jtx/impl/tag.cpp index 33ec2c3b89b..be456a1e0e8 100644 --- a/src/test/jtx/impl/tag.cpp +++ b/src/test/jtx/impl/tag.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/ticket.cpp b/src/test/jtx/impl/ticket.cpp index 951c62ab72e..69bcab4d9f1 100644 --- a/src/test/jtx/impl/ticket.cpp +++ b/src/test/jtx/impl/ticket.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/token.cpp b/src/test/jtx/impl/token.cpp index 6c5cae4147a..5faf56185b9 100644 --- a/src/test/jtx/impl/token.cpp +++ b/src/test/jtx/impl/token.cpp @@ -20,9 +20,9 @@ #include #include -#include -#include -#include +#include +#include +#include namespace ripple { namespace test { @@ -57,6 +57,12 @@ uri::operator()(Env& env, JTx& jt) const jt.jv[sfURI.jsonName] = uri_; } +void +amount::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfAmount.jsonName] = amount_.getJson(JsonOptions::none); +} + uint256 getNextID( jtx::Env const& env, diff --git a/src/test/jtx/impl/trust.cpp b/src/test/jtx/impl/trust.cpp index cce4657e025..dee4b282367 100644 --- a/src/test/jtx/impl/trust.cpp +++ b/src/test/jtx/impl/trust.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { namespace test { @@ -39,6 +39,10 @@ trust(Account const& account, STAmount const& amount, std::uint32_t flags) return jv; } +// This function overload is especially useful for modelling Authorised trust +// lines. account (first function parameter) is the issuing authority, it +// authorises peer (third function parameter) to hold a certain currency +// (amount, the second function parameter) Json::Value trust( Account const& account, @@ -60,13 +64,19 @@ trust( } Json::Value -claw(Account const& account, STAmount const& amount) +claw( + Account const& account, + STAmount const& amount, + std::optional const& mptHolder) { Json::Value jv; jv[jss::Account] = account.human(); jv[jss::Amount] = amount.getJson(JsonOptions::none); jv[jss::TransactionType] = jss::Clawback; + if (mptHolder) + jv[sfHolder.jsonName] = mptHolder->human(); + return jv; } diff --git a/src/test/jtx/impl/txflags.cpp b/src/test/jtx/impl/txflags.cpp index 5c78be8bae6..7bc59876a99 100644 --- a/src/test/jtx/impl/txflags.cpp +++ b/src/test/jtx/impl/txflags.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/utility.cpp b/src/test/jtx/impl/utility.cpp index 196fd9258d7..c10fb918540 100644 --- a/src/test/jtx/impl/utility.cpp +++ b/src/test/jtx/impl/utility.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp index 0b81ccdcd91..43b0e7c2f96 100644 --- a/src/test/jtx/impl/xchain_bridge.cpp +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -19,17 +19,17 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/jtx_json.h b/src/test/jtx/jtx_json.h index 59cc09537e2..837d6524766 100644 --- a/src/test/jtx/jtx_json.h +++ b/src/test/jtx/jtx_json.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_JSON_H_INCLUDED #define RIPPLE_TEST_JTX_JSON_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/ripple/peerfinder/sim/Message.h b/src/test/jtx/ledgerStateFix.h similarity index 67% rename from src/ripple/peerfinder/sim/Message.h rename to src/test/jtx/ledgerStateFix.h index 69be553c01a..bf0a56cabe3 100644 --- a/src/ripple/peerfinder/sim/Message.h +++ b/src/test/jtx/ledgerStateFix.h @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + Copyright (c) 2024 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,31 +17,28 @@ */ //============================================================================== -#ifndef RIPPLE_PEERFINDER_SIM_MESSAGE_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_MESSAGE_H_INCLUDED +#ifndef RIPPLE_TEST_JTX_LEDGER_STATE_FIX_H_INCLUDED +#define RIPPLE_TEST_JTX_LEDGER_STATE_FIX_H_INCLUDED + +#include +#include namespace ripple { -namespace PeerFinder { -namespace Sim { - -class Message -{ -public: - explicit Message(Endpoints const& endpoints) : m_payload(endpoints) - { - } - Endpoints const& - payload() const - { - return m_payload; - } - -private: - Endpoints m_payload; -}; - -} // namespace Sim -} // namespace PeerFinder +namespace test { +namespace jtx { + +/** LedgerStateFix operations. */ +namespace ledgerStateFix { + +/** Repair the links in an NFToken directory. */ +Json::Value +nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); + +} // namespace ledgerStateFix + +} // namespace jtx + +} // namespace test } // namespace ripple #endif diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h new file mode 100644 index 00000000000..12b9d74d27c --- /dev/null +++ b/src/test/jtx/mpt.h @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_MPT_H_INCLUDED +#define RIPPLE_TEST_JTX_MPT_H_INCLUDED + +#include +#include +#include + +#include + +namespace ripple { +namespace test { +namespace jtx { + +class MPTTester; + +// Check flags settings on MPT create +class mptflags +{ +private: + MPTTester& tester_; + std::uint32_t flags_; + std::optional holder_; + +public: + mptflags( + MPTTester& tester, + std::uint32_t flags, + std::optional const& holder = std::nullopt) + : tester_(tester), flags_(flags), holder_(holder) + { + } + + void + operator()(Env& env) const; +}; + +// Check mptissuance or mptoken amount balances on payment +class mptbalance +{ +private: + MPTTester const& tester_; + Account const& account_; + std::int64_t const amount_; + +public: + mptbalance(MPTTester& tester, Account const& account, std::int64_t amount) + : tester_(tester), account_(account), amount_(amount) + { + } + + void + operator()(Env& env) const; +}; + +class requireAny +{ +private: + std::function cb_; + +public: + requireAny(std::function const& cb) : cb_(cb) + { + } + + void + operator()(Env& env) const; +}; + +struct MPTInit +{ + std::vector holders = {}; + PrettyAmount const& xrp = XRP(10'000); + PrettyAmount const& xrpHolders = XRP(10'000); + bool fund = true; + bool close = true; +}; + +struct MPTCreate +{ + std::optional maxAmt = std::nullopt; + std::optional assetScale = std::nullopt; + std::optional transferFee = std::nullopt; + std::optional metadata = std::nullopt; + std::optional ownerCount = std::nullopt; + std::optional holderCount = std::nullopt; + bool fund = true; + std::optional flags = {0}; + std::optional err = std::nullopt; +}; + +struct MPTDestroy +{ + std::optional issuer = std::nullopt; + std::optional id = std::nullopt; + std::optional ownerCount = std::nullopt; + std::optional holderCount = std::nullopt; + std::optional flags = std::nullopt; + std::optional err = std::nullopt; +}; + +struct MPTAuthorize +{ + std::optional account = std::nullopt; + std::optional holder = std::nullopt; + std::optional id = std::nullopt; + std::optional ownerCount = std::nullopt; + std::optional holderCount = std::nullopt; + std::optional flags = std::nullopt; + std::optional err = std::nullopt; +}; + +struct MPTSet +{ + std::optional account = std::nullopt; + std::optional holder = std::nullopt; + std::optional id = std::nullopt; + std::optional ownerCount = std::nullopt; + std::optional holderCount = std::nullopt; + std::optional flags = std::nullopt; + std::optional err = std::nullopt; +}; + +class MPTTester +{ + Env& env_; + Account const& issuer_; + std::unordered_map const holders_; + std::optional id_; + bool close_; + +public: + MPTTester(Env& env, Account const& issuer, MPTInit const& constr = {}); + + void + create(MPTCreate const& arg = MPTCreate{}); + + void + destroy(MPTDestroy const& arg = MPTDestroy{}); + + void + authorize(MPTAuthorize const& arg = MPTAuthorize{}); + + void + set(MPTSet const& set = {}); + + [[nodiscard]] bool + checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount) + const; + + [[nodiscard]] bool + checkMPTokenOutstandingAmount(std::int64_t expectedAmount) const; + + [[nodiscard]] bool + checkFlags( + uint32_t const expectedFlags, + std::optional const& holder = std::nullopt) const; + + Account const& + issuer() const + { + return issuer_; + } + Account const& + holder(std::string const& h) const; + + void + pay(Account const& src, + Account const& dest, + std::int64_t amount, + std::optional err = std::nullopt, + std::optional> credentials = std::nullopt); + + void + claw( + Account const& issuer, + Account const& holder, + std::int64_t amount, + std::optional err = std::nullopt); + + PrettyAmount + mpt(std::int64_t amount) const; + + MPTID const& + issuanceID() const + { + if (!env_.test.BEAST_EXPECT(id_)) + Throw("Uninitialized issuanceID"); + return *id_; + } + + std::int64_t + getBalance(Account const& account) const; + + MPT + operator[](std::string const& name); + +private: + using SLEP = std::shared_ptr; + bool + forObject( + std::function const& cb, + std::optional const& holder = std::nullopt) const; + + template + TER + submit(A const& arg, Json::Value const& jv) + { + env_( + jv, + txflags(arg.flags.value_or(0)), + ter(arg.err.value_or(tesSUCCESS))); + auto const err = env_.ter(); + if (close_) + env_.close(); + if (arg.ownerCount) + env_.require(owners(issuer_, *arg.ownerCount)); + if (arg.holderCount) + { + for (auto it : holders_) + env_.require(owners(it.second, *arg.holderCount)); + } + return err; + } + + static std::unordered_map + makeHolders(std::vector const& holders); + + std::uint32_t + getFlags(std::optional const& holder) const; +}; + +} // namespace jtx +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/jtx/multisign.h b/src/test/jtx/multisign.h index 0617ea2fcbb..44cee17b7bf 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_TEST_JTX_MULTISIGN_H_INCLUDED #define RIPPLE_TEST_JTX_MULTISIGN_H_INCLUDED -#include -#include -#include #include #include #include #include +#include +#include +#include namespace ripple { namespace test { @@ -100,9 +100,8 @@ class msig msig(std::vector signers_); template - requires std::convertible_to explicit msig( - AccountType&& a0, - Accounts&&... aN) + requires std::convertible_to + explicit msig(AccountType&& a0, Accounts&&... aN) : msig{std::vector{ std::forward(a0), std::forward(aN)...}} diff --git a/src/test/jtx/offer.h b/src/test/jtx/offer.h index 35a46d19b6b..3951f4f934a 100644 --- a/src/test/jtx/offer.h +++ b/src/test/jtx/offer.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_JTX_OFFER_H_INCLUDED #define RIPPLE_TEST_JTX_OFFER_H_INCLUDED -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index 9c802ec91dc..2299af2d7f3 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_TEST_JTX_OWNERS_H_INCLUDED #define RIPPLE_TEST_JTX_OWNERS_H_INCLUDED -#include -#include -#include -#include #include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/paths.h b/src/test/jtx/paths.h index 240582825f3..cc9caf3af72 100644 --- a/src/test/jtx/paths.h +++ b/src/test/jtx/paths.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_PATHS_H_INCLUDED #define RIPPLE_TEST_JTX_PATHS_H_INCLUDED -#include #include +#include #include namespace ripple { diff --git a/src/test/jtx/pay.h b/src/test/jtx/pay.h index f66075a3e80..6294b5b3082 100644 --- a/src/test/jtx/pay.h +++ b/src/test/jtx/pay.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_JTX_PAY_H_INCLUDED #define RIPPLE_TEST_JTX_PAY_H_INCLUDED -#include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/prop.h b/src/test/jtx/prop.h index 6724ebf8087..8dc5f21917d 100644 --- a/src/test/jtx/prop.h +++ b/src/test/jtx/prop.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_PROP_H_INCLUDED #define RIPPLE_TEST_JTX_PROP_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/rate.h b/src/test/jtx/rate.h index 6427b4988e5..0f8186accfc 100644 --- a/src/test/jtx/rate.h +++ b/src/test/jtx/rate.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_RATE_H_INCLUDED #define RIPPLE_TEST_JTX_RATE_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/regkey.h b/src/test/jtx/regkey.h index 94869d1d006..7d021e4f277 100644 --- a/src/test/jtx/regkey.h +++ b/src/test/jtx/regkey.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_JTX_REGKEY_H_INCLUDED #define RIPPLE_TEST_JTX_REGKEY_H_INCLUDED -#include #include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index 64b1f36b5fa..271419e51b9 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_REQUIRE_H_INCLUDED #define RIPPLE_TEST_JTX_REQUIRE_H_INCLUDED -#include #include +#include #include namespace ripple { diff --git a/src/test/jtx/sendmax.h b/src/test/jtx/sendmax.h index 41e09a4313b..495c61c33b8 100644 --- a/src/test/jtx/sendmax.h +++ b/src/test/jtx/sendmax.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TEST_JTX_SENDMAX_H_INCLUDED #define RIPPLE_TEST_JTX_SENDMAX_H_INCLUDED -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/token.h b/src/test/jtx/token.h index 150ddfab0ea..9b4f81b7061 100644 --- a/src/test/jtx/token.h +++ b/src/test/jtx/token.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include @@ -83,6 +83,21 @@ class uri operator()(Env&, JTx& jtx) const; }; +/** Sets the optional amount field on an NFTokenMint. */ +class amount +{ +private: + STAmount const amount_; + +public: + explicit amount(STAmount const amount) : amount_(amount) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + /** Get the next NFTokenID that will be issued. */ uint256 getNextID( diff --git a/src/test/jtx/trust.h b/src/test/jtx/trust.h index 5b6dd78b3cd..0d02c6e76c4 100644 --- a/src/test/jtx/trust.h +++ b/src/test/jtx/trust.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TEST_JTX_TRUST_H_INCLUDED #define RIPPLE_TEST_JTX_TRUST_H_INCLUDED -#include -#include #include +#include +#include namespace ripple { namespace test { @@ -41,7 +41,10 @@ trust( std::uint32_t flags); Json::Value -claw(Account const& account, STAmount const& amount); +claw( + Account const& account, + STAmount const& amount, + std::optional const& mptHolder = std::nullopt); } // namespace jtx } // namespace test diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index 42a2180eed2..6d34452cdf8 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TEST_JTX_UTILITY_H_INCLUDED #define RIPPLE_TEST_JTX_UTILITY_H_INCLUDED -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/xchain_bridge.h b/src/test/jtx/xchain_bridge.h index 8a398bc6f20..9968317c8de 100644 --- a/src/test/jtx/xchain_bridge.h +++ b/src/test/jtx/xchain_bridge.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TEST_JTX_XCHAINBRIDGE_H_INCLUDED #define RIPPLE_TEST_JTX_XCHAINBRIDGE_H_INCLUDED -#include -#include -#include #include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp index c7dbea3d6d9..b50f4381f48 100644 --- a/src/test/ledger/BookDirs_test.cpp +++ b/src/test/ledger/BookDirs_test.cpp @@ -15,9 +15,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index 42dcdab9bd1..bea394f2f36 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -15,22 +15,21 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { struct Directory_test : public beast::unit_test::suite { - // Map [0-15576] into a a unique 3 letter currency code + // Map [0-15576] into a unique 3 letter currency code std::string currcode(std::size_t i) { diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 898376bdaf7..8d7b08fa1ab 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -17,19 +17,31 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include +#include +#include +#include + +#include namespace ripple { class Invariants_test : public beast::unit_test::suite { + // The optional Preclose function is used to process additional transactions + // on the ledger after creating two accounts, but before closing it, and + // before the Precheck function. These should only be valid functions, and + // not direct manipulations. Preclose is not commonly used. + using Preclose = std::function; + // this is common setup/method for running a failing invariant check. The // precheck function is used to manipulate the ApplyContext with view // changes that will cause the check to fail. @@ -38,22 +50,42 @@ class Invariants_test : public beast::unit_test::suite test::jtx::Account const& b, ApplyContext& ac)>; + /** Run a specific test case to put the ledger into a state that will be + * detected by an invariant. Simulates the actions of a transaction that + * would violate an invariant. + * + * @param expect_logs One or more messages related to the failing invariant + * that should be in the log output + * @precheck See "Precheck" above + * @fee If provided, the fee amount paid by the simulated transaction. + * @tx A mock transaction that took the actions to trigger the invariant. In + * most cases, only the type matters. + * @ters The TER results expected on the two passes of the invariant + * checker. + * @preclose See "Preclose" above. Note that @preclose runs *before* + * @precheck, but is the last parameter for historical reasons + * + */ void doInvariantCheck( std::vector const& expect_logs, Precheck const& precheck, XRPAmount fee = XRPAmount{}, STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}}, - std::initializer_list ters = { - tecINVARIANT_FAILED, - tefINVARIANT_FAILED}) + std::initializer_list ters = + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + Preclose const& preclose = {}) { using namespace test::jtx; - Env env{*this}; + FeatureBitset amendments = + supported_amendments() | featureInvariantsV1_1; + Env env{*this, amendments}; - Account A1{"A1"}; - Account A2{"A2"}; + Account const A1{"A1"}; + Account const A2{"A2"}; env.fund(XRP(1000), A1, A2); + if (preclose) + BEAST_EXPECT(preclose(A1, A2, env)); env.close(); OpenView ov{*env.current()}; @@ -80,10 +112,9 @@ class Invariants_test : public beast::unit_test::suite terActual = ac.checkInvariants(terActual, fee); BEAST_EXPECT(terExpect == terActual); BEAST_EXPECT( - boost::starts_with( - sink.messages().str(), "Invariant failed:") || - boost::starts_with( - sink.messages().str(), "Transaction caused an exception")); + sink.messages().str().starts_with("Invariant failed:") || + sink.messages().str().starts_with( + "Transaction caused an exception")); // uncomment if you want to log the invariant failure message // log << " --> " << sink.messages().str() << std::endl; for (auto const& m : expect_logs) @@ -162,6 +193,165 @@ class Invariants_test : public beast::unit_test::suite STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); } + void + testAccountRootsDeletedClean() + { + using namespace test::jtx; + testcase << "account root deletion left artifact"; + + for (auto const& keyletInfo : directAccountKeylets) + { + // TODO: Use structured binding once LLVM 16 is the minimum + // supported version. See also: + // https://github.com/llvm/llvm-project/issues/48582 + // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c + if (!keyletInfo.includeInTests) + continue; + auto const& keyletfunc = keyletInfo.function; + auto const& type = keyletInfo.expectedLEName; + + using namespace std::string_literals; + + doInvariantCheck( + {{"account deletion left behind a "s + type.c_str() + + " object"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + // Add an object to the ledger for account A1, then delete + // A1 + auto const a1 = A1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1)); + if (!sleA1) + return false; + + auto const key = std::invoke(keyletfunc, a1); + auto const newSLE = std::make_shared(key); + ac.view().insert(newSLE); + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + }; + + // NFT special case + doInvariantCheck( + {{"account deletion left behind a NFTokenPage object"}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + // remove an account from the view + auto const sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + ac.view().erase(sle); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& A1, Account const&, Env& env) { + // Preclose callback to mint the NFT which will be deleted in + // the Precheck callback above. + env(token::mint(A1)); + + return true; + }); + + // AMM special cases + AccountID ammAcctID; + uint256 ammKey; + Issue ammIssue; + doInvariantCheck( + {{"account deletion left behind a DirectoryNode object"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + // Delete the AMM account without cleaning up the directory or + // deleting the AMM object + auto const sle = ac.view().peek(keylet::account(ammAcctID)); + if (!sle) + return false; + + BEAST_EXPECT(sle->at(~sfAMMID)); + BEAST_EXPECT(sle->at(~sfAMMID) == ammKey); + + ac.view().erase(sle); + + return true; + }, + XRPAmount{}, + STTx{ttAMM_WITHDRAW, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& A1, Account const& A2, Env& env) { + // Preclose callback to create the AMM which will be partially + // deleted in the Precheck callback above. + AMM const amm(env, A1, XRP(100), A1["USD"](50)); + ammAcctID = amm.ammAccount(); + ammKey = amm.ammID(); + ammIssue = amm.lptIssue(); + return true; + }); + doInvariantCheck( + {{"account deletion left behind a AMM object"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + // Delete all the AMM's trust lines, remove the AMM from the AMM + // account's directory (this deletes the directory), and delete + // the AMM account. Do not delete the AMM object. + auto const sle = ac.view().peek(keylet::account(ammAcctID)); + if (!sle) + return false; + + BEAST_EXPECT(sle->at(~sfAMMID)); + BEAST_EXPECT(sle->at(~sfAMMID) == ammKey); + + for (auto const& trustKeylet : + {keylet::line(ammAcctID, A1["USD"]), + keylet::line(A1, ammIssue)}) + { + if (auto const line = ac.view().peek(trustKeylet); !line) + { + return false; + } + else + { + STAmount const lowLimit = line->at(sfLowLimit); + STAmount const highLimit = line->at(sfHighLimit); + BEAST_EXPECT( + trustDelete( + ac.view(), + line, + lowLimit.getIssuer(), + highLimit.getIssuer(), + ac.journal) == tesSUCCESS); + } + } + + auto const ammSle = ac.view().peek(keylet::amm(ammKey)); + if (!BEAST_EXPECT(ammSle)) + return false; + auto const ownerDirKeylet = keylet::ownerDir(ammAcctID); + + BEAST_EXPECT(ac.view().dirRemove( + ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false)); + BEAST_EXPECT( + !ac.view().exists(ownerDirKeylet) || + ac.view().emptyDirDelete(ownerDirKeylet)); + + ac.view().erase(sle); + + return true; + }, + XRPAmount{}, + STTx{ttAMM_WITHDRAW, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& A1, Account const& A2, Env& env) { + // Preclose callback to create the AMM which will be partially + // deleted in the Precheck callback above. + AMM const amm(env, A1, XRP(100), A1["USD"](50)); + ammAcctID = amm.ammAccount(); + ammKey = amm.ammID(); + ammIssue = amm.lptIssue(); + return true; + }); + } + void testTypesMatch() { @@ -175,7 +365,7 @@ class Invariants_test : public beast::unit_test::suite auto const sle = ac.view().peek(keylet::account(A1.id())); if (!sle) return false; - auto sleNew = std::make_shared(ltTICKET, sle->key()); + auto const sleNew = std::make_shared(ltTICKET, sle->key()); ac.rawView().rawReplace(sleNew); return true; }); @@ -191,7 +381,7 @@ class Invariants_test : public beast::unit_test::suite // make a dummy escrow ledger entry, then change the type to an // unsupported value so that the valid type invariant check // will fail. - auto sleNew = std::make_shared( + auto const sleNew = std::make_shared( keylet::escrow(A1, (*sle)[sfSequence] + 2)); // We don't use ltNICKNAME directly since it's marked deprecated @@ -231,7 +421,7 @@ class Invariants_test : public beast::unit_test::suite auto const sle = ac.view().peek(keylet::account(A1.id())); if (!sle) return false; - STAmount nonNative(A2["USD"](51)); + STAmount const nonNative(A2["USD"](51)); sle->setFieldAmount(sfBalance, nonNative); ac.view().update(sle); return true; @@ -420,7 +610,7 @@ class Invariants_test : public beast::unit_test::suite [](Account const&, Account const&, ApplyContext& ac) { // Insert a new account root created by a non-payment into // the view. - const Account A3{"A3"}; + Account const A3{"A3"}; Keylet const acctKeylet = keylet::account(A3); auto const sleNew = std::make_shared(acctKeylet); ac.view().insert(sleNew); @@ -432,13 +622,13 @@ class Invariants_test : public beast::unit_test::suite [](Account const&, Account const&, ApplyContext& ac) { // Insert two new account roots into the view. { - const Account A3{"A3"}; + Account const A3{"A3"}; Keylet const acctKeylet = keylet::account(A3); auto const sleA3 = std::make_shared(acctKeylet); ac.view().insert(sleA3); } { - const Account A4{"A4"}; + Account const A4{"A4"}; Keylet const acctKeylet = keylet::account(A4); auto const sleA4 = std::make_shared(acctKeylet); ac.view().insert(sleA4); @@ -450,7 +640,7 @@ class Invariants_test : public beast::unit_test::suite {{"account created with wrong starting sequence number"}}, [](Account const&, Account const&, ApplyContext& ac) { // Insert a new account root with the wrong starting sequence. - const Account A3{"A3"}; + Account const A3{"A3"}; Keylet const acctKeylet = keylet::account(A3); auto const sleNew = std::make_shared(acctKeylet); sleNew->setFieldU32(sfSequence, ac.view().seq() + 1); @@ -461,12 +651,160 @@ class Invariants_test : public beast::unit_test::suite STTx{ttPAYMENT, [](STObject& tx) {}}); } + void + testNFTokenPageInvariants() + { + using namespace test::jtx; + testcase << "NFTokenPage"; + + // lambda that returns an STArray of NFTokenIDs. + uint256 const firstNFTID( + "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000"); + auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) { + SOTemplate const* nfTokenTemplate = + InnerObjectFormats::getInstance().findSOTemplateBySField( + sfNFToken); + + uint256 nftID(firstNFTID); + STArray ret; + for (int i = 0; i < nftCount; ++i) + { + STObject newNFToken( + *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) { + object.setFieldH256(sfNFTokenID, nftID); + }); + ret.push_back(std::move(newNFToken)); + ++nftID; + } + return ret; + }; + + doInvariantCheck( + {{"NFT page has invalid size"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0)); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT page has invalid size"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33)); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFTs on page are not sorted"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + STArray nfTokens = makeNFTokenIDs(2); + std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1); + + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, nfTokens); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT contains empty URI"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + STArray nfTokens = makeNFTokenIDs(1); + nfTokens[0].setFieldVL(sfURI, Blob{}); + + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, nfTokens); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT page is improperly linked"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1)); + nftPage->setFieldH256( + sfPreviousPageMin, keylet::nftpage_max(A1).key); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT page is improperly linked"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const& A2, ApplyContext& ac) { + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1)); + nftPage->setFieldH256( + sfPreviousPageMin, keylet::nftpage_min(A2).key); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT page is improperly linked"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + auto nftPage = std::make_shared(keylet::nftpage_max(A1)); + nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1)); + nftPage->setFieldH256(sfNextPageMin, nftPage->key()); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT page is improperly linked"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const& A2, ApplyContext& ac) { + STArray nfTokens = makeNFTokenIDs(1); + auto nftPage = std::make_shared(keylet::nftpage( + keylet::nftpage_max(A1), + ++(nfTokens[0].getFieldH256(sfNFTokenID)))); + nftPage->setFieldArray(sfNFTokens, std::move(nfTokens)); + nftPage->setFieldH256( + sfNextPageMin, keylet::nftpage_max(A2).key); + + ac.view().insert(nftPage); + return true; + }); + + doInvariantCheck( + {{"NFT found in incorrect page"}}, + [&makeNFTokenIDs]( + Account const& A1, Account const&, ApplyContext& ac) { + STArray nfTokens = makeNFTokenIDs(2); + auto nftPage = std::make_shared(keylet::nftpage( + keylet::nftpage_max(A1), + (nfTokens[1].getFieldH256(sfNFTokenID)))); + nftPage->setFieldArray(sfNFTokens, std::move(nfTokens)); + + ac.view().insert(nftPage); + return true; + }); + } + public: void run() override { testXRPNotCreated(); testAccountRootsNotRemoved(); + testAccountRootsDeletedClean(); testTypesMatch(); testNoXRPTrustLine(); testXRPBalanceCheck(); @@ -474,6 +812,7 @@ class Invariants_test : public beast::unit_test::suite testNoBadOffers(); testNoZeroEscrow(); testValidNewAccountRoot(); + testNFTokenPageInvariants(); } }; diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index b5ad8c7712c..dd9b5c5d88b 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { namespace test { @@ -316,14 +316,12 @@ class PaymentSandbox_test : public beast::unit_test::suite STAmount::cMinValue, STAmount::cMinOffset + 1, false, - false, STAmount::unchecked{}); STAmount hugeAmt( issue, STAmount::cMaxValue, STAmount::cMaxOffset - 1, false, - false, STAmount::unchecked{}); ApplyViewImpl av(&*env.current(), tapNONE); diff --git a/src/test/ledger/PendingSaves_test.cpp b/src/test/ledger/PendingSaves_test.cpp index d826d2019fd..1bd42a64a60 100644 --- a/src/test/ledger/PendingSaves_test.cpp +++ b/src/test/ledger/PendingSaves_test.cpp @@ -15,8 +15,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/ledger/SkipList_test.cpp b/src/test/ledger/SkipList_test.cpp index 4fd117a4ca1..3e8987a806a 100644 --- a/src/test/ledger/SkipList_test.cpp +++ b/src/test/ledger/SkipList_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index bbb1eec8fa5..29ceb54cc09 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/net/DatabaseDownloader_test.cpp b/src/test/net/DatabaseDownloader_test.cpp deleted file mode 100644 index 31c8abfd12c..00000000000 --- a/src/test/net/DatabaseDownloader_test.cpp +++ /dev/null @@ -1,315 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright 2019 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace test { - -#define REPORT_FAILURE(D) reportFailure(D, __FILE__, __LINE__) - -class DatabaseDownloader_test : public beast::unit_test::suite -{ - std::shared_ptr - createServer(jtx::Env& env, bool ssl = true) - { - std::vector list; - list.push_back(TrustedPublisherServer::randomValidator()); - return make_TrustedPublisherServer( - env.app().getIOService(), - list, - env.timeKeeper().now() + std::chrono::seconds{3600}, - // No future VLs - {}, - ssl); - } - - struct DownloadCompleter - { - std::mutex m; - std::condition_variable cv; - bool called = false; - boost::filesystem::path dest; - - void - operator()(boost::filesystem::path dst) - { - std::unique_lock lk(m); - called = true; - dest = std::move(dst); - cv.notify_one(); - }; - - bool - waitComplete() - { - std::unique_lock lk(m); - - auto stat = cv.wait_for( - lk, std::chrono::seconds(10), [this] { return called; }); - - called = false; - return stat; - }; - }; - DownloadCompleter cb; - - struct Downloader - { - test::StreamSink sink_; - beast::Journal journal_; - std::shared_ptr ptr_; - - Downloader(jtx::Env& env) - : journal_{sink_} - , ptr_{make_DatabaseDownloader( - env.app().getIOService(), - env.app().config(), - journal_)} - { - } - - ~Downloader() - { - ptr_->stop(); - } - - DatabaseDownloader* - operator->() - { - return ptr_.get(); - } - - DatabaseDownloader const* - operator->() const - { - return ptr_.get(); - } - }; - - void - reportFailure(Downloader const& dl, char const* file, int line) - { - std::stringstream ss; - ss << "Failed. LOGS:\n" - << dl.sink_.messages().str() - << "\nDownloadCompleter failure." - "\nDatabaseDownloader session active? " - << std::boolalpha << dl->sessionIsActive() - << "\nDatabaseDownloader is stopping? " << std::boolalpha - << dl->isStopping(); - - fail(ss.str(), file, line); - } - - void - testDownload(bool verify) - { - testcase << std::string("Basic download - SSL ") + - (verify ? "Verify" : "No Verify"); - - using namespace jtx; - - ripple::test::detail::FileDirGuard cert{ - *this, "_cert", "ca.pem", TrustedPublisherServer::ca_cert()}; - - Env env{*this, envconfig([&cert, &verify](std::unique_ptr cfg) { - if ((cfg->SSL_VERIFY = verify)) // yes, this is assignment - cfg->SSL_VERIFY_FILE = cert.file().string(); - return cfg; - })}; - - Downloader dl{env}; - - // create a TrustedPublisherServer as a simple HTTP - // server to request from. Use the /textfile endpoint - // to get a simple text file sent as response. - auto server = createServer(env); - log << "Downloading DB from " << server->local_endpoint() << std::endl; - - ripple::test::detail::FileDirGuard const data{ - *this, "downloads", "data", "", false, false}; - // initiate the download and wait for the callback - // to be invoked - auto stat = dl->download( - server->local_endpoint().address().to_string(), - std::to_string(server->local_endpoint().port()), - "/textfile", - 11, - data.file(), - std::function{std::ref(cb)}); - if (!BEAST_EXPECT(stat)) - { - REPORT_FAILURE(dl); - return; - } - if (!BEAST_EXPECT(cb.waitComplete())) - { - REPORT_FAILURE(dl); - return; - } - BEAST_EXPECT(cb.dest == data.file()); - if (!BEAST_EXPECT(boost::filesystem::exists(data.file()))) - return; - BEAST_EXPECT(boost::filesystem::file_size(data.file()) > 0); - } - - void - testFailures() - { - testcase("Error conditions"); - - using namespace jtx; - - Env env{*this}; - - { - // bad hostname - boost::system::error_code ec; - boost::asio::ip::tcp::resolver resolver{env.app().getIOService()}; - auto const results = resolver.resolve("badhostname", "443", ec); - // we require an error in resolving this name in order - // for this test to pass. Some networks might have DNS hijacking - // that prevent NXDOMAIN, in which case the failure is not - // possible, so we skip the test. - if (ec) - { - Downloader dl{env}; - ripple::test::detail::FileDirGuard const datafile{ - *this, "downloads", "data", "", false, false}; - BEAST_EXPECT(dl->download( - "badhostname", - "443", - "", - 11, - datafile.file(), - std::function{ - std::ref(cb)})); - if (!BEAST_EXPECT(cb.waitComplete())) - { - REPORT_FAILURE(dl); - } - BEAST_EXPECT(!boost::filesystem::exists(datafile.file())); - BEAST_EXPECTS( - dl.sink_.messages().str().find("async_resolve") != - std::string::npos, - dl.sink_.messages().str()); - } - } - { - // can't connect - Downloader dl{env}; - ripple::test::detail::FileDirGuard const datafile{ - *this, "downloads", "data", "", false, false}; - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - log << "Downloading DB from " << server->local_endpoint() - << std::endl; - server->stop(); - BEAST_EXPECT(dl->download( - host, - port, - "", - 11, - datafile.file(), - std::function{std::ref(cb)})); - if (!BEAST_EXPECT(cb.waitComplete())) - { - REPORT_FAILURE(dl); - } - BEAST_EXPECT(!boost::filesystem::exists(datafile.file())); - BEAST_EXPECTS( - dl.sink_.messages().str().find("async_connect") != - std::string::npos, - dl.sink_.messages().str()); - } - { - // not ssl (failed handlshake) - Downloader dl{env}; - ripple::test::detail::FileDirGuard const datafile{ - *this, "downloads", "data", "", false, false}; - auto server = createServer(env, false); - log << "Downloading DB from " << server->local_endpoint() - << std::endl; - BEAST_EXPECT(dl->download( - server->local_endpoint().address().to_string(), - std::to_string(server->local_endpoint().port()), - "", - 11, - datafile.file(), - std::function{std::ref(cb)})); - if (!BEAST_EXPECT(cb.waitComplete())) - { - REPORT_FAILURE(dl); - } - BEAST_EXPECT(!boost::filesystem::exists(datafile.file())); - BEAST_EXPECTS( - dl.sink_.messages().str().find("async_handshake") != - std::string::npos, - dl.sink_.messages().str()); - } - { - // huge file (content length) - Downloader dl{env}; - ripple::test::detail::FileDirGuard const datafile{ - *this, "downloads", "data", "", false, false}; - auto server = createServer(env); - log << "Downloading DB from " << server->local_endpoint() - << std::endl; - BEAST_EXPECT(dl->download( - server->local_endpoint().address().to_string(), - std::to_string(server->local_endpoint().port()), - "/textfile/huge", - 11, - datafile.file(), - std::function{std::ref(cb)})); - if (!BEAST_EXPECT(cb.waitComplete())) - { - REPORT_FAILURE(dl); - } - BEAST_EXPECT(!boost::filesystem::exists(datafile.file())); - BEAST_EXPECTS( - dl.sink_.messages().str().find("Insufficient disk space") != - std::string::npos, - dl.sink_.messages().str()); - } - } - -public: - void - run() override - { - testDownload(true); - testDownload(false); - testFailures(); - } -}; - -#undef REPORT_FAILURE - -BEAST_DEFINE_TESTSUITE(DatabaseDownloader, net, ripple); -} // namespace test -} // namespace ripple diff --git a/src/test/nodestore/Backend_test.cpp b/src/test/nodestore/Backend_test.cpp index 8c8432bb5b8..dc1fe8c7fe2 100644 --- a/src/test/nodestore/Backend_test.cpp +++ b/src/test/nodestore/Backend_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/nodestore/Basics_test.cpp b/src/test/nodestore/Basics_test.cpp index 92f2ae15aaf..aa423e7dc03 100644 --- a/src/test/nodestore/Basics_test.cpp +++ b/src/test/nodestore/Basics_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include namespace ripple { namespace NodeStore { diff --git a/src/test/nodestore/DatabaseShard_test.cpp b/src/test/nodestore/DatabaseShard_test.cpp deleted file mode 100644 index 5c074b14938..00000000000 --- a/src/test/nodestore/DatabaseShard_test.cpp +++ /dev/null @@ -1,1894 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace NodeStore { - -/** std::uniform_int_distribution is platform dependent. - * Unit test for deterministic shards is the following: it generates - * predictable accounts and transactions, packs them into ledgers - * and makes the shard. The hash of this shard should be equal to the - * given value. On different platforms (precisely, Linux and Mac) - * hashes of the resulting shard was different. It was unvestigated - * that the problem is in the class std::uniform_int_distribution - * which generates different pseudorandom sequences on different - * platforms, but we need predictable sequence. - */ -template -struct uniformIntDistribution -{ - using resultType = IntType; - - const resultType A, B; - - struct paramType - { - const resultType A, B; - - paramType(resultType aa, resultType bb) : A(aa), B(bb) - { - } - }; - - explicit uniformIntDistribution( - const resultType a = 0, - const resultType b = std::numeric_limits::max()) - : A(a), B(b) - { - } - - explicit uniformIntDistribution(const paramType& params) - : A(params.A), B(params.B) - { - } - - template - resultType - operator()(Generator& g) const - { - return rnd(g, A, B); - } - - template - resultType - operator()(Generator& g, const paramType& params) const - { - return rnd(g, params.A, params.B); - } - - resultType - a() const - { - return A; - } - - resultType - b() const - { - return B; - } - - resultType - min() const - { - return A; - } - - resultType - max() const - { - return B; - } - -private: - template - resultType - rnd(Generator& g, const resultType a, const resultType b) const - { - static_assert( - std::is_convertible:: - value, - "Ups..."); - static_assert( - Generator::min() == 0, "If non-zero we have handle the offset"); - const resultType range = b - a + 1; - assert(Generator::max() >= range); // Just for safety - const resultType rejectLim = g.max() % range; - resultType n; - do - n = g(); - while (n <= rejectLim); - return (n % range) + a; - } -}; - -template -Integral -randInt(Engine& engine, Integral min, Integral max) -{ - assert(max > min); - - // This should have no state and constructing it should - // be very cheap. If that turns out not to be the case - // it could be hand-optimized. - return uniformIntDistribution(min, max)(engine); -} - -template -Integral -randInt(Engine& engine, Integral max) -{ - return randInt(engine, Integral(0), max); -} - -// Tests DatabaseShard class -// -class DatabaseShard_test : public TestBase -{ - static constexpr std::uint32_t maxSizeGb = 10; - static constexpr std::uint32_t maxHistoricalShards = 100; - static constexpr std::uint32_t ledgersPerShard = 256; - static constexpr std::uint32_t earliestSeq = ledgersPerShard + 1; - static constexpr std::uint32_t dataSizeMax = 4; - static constexpr std::uint32_t iniAmount = 1000000; - static constexpr std::uint32_t nTestShards = 4; - static constexpr std::chrono::seconds shardStoreTimeout = - std::chrono::seconds(60); - test::SuiteJournal journal_; - beast::temp_dir defNodeDir; - - struct TestData - { - /* ring used to generate pseudo-random sequence */ - beast::xor_shift_engine rng_; - /* number of shards to generate */ - int numShards_; - /* vector of accounts used to send test transactions */ - std::vector accounts_; - /* nAccounts_[i] is the number of these accounts existed before i-th - * ledger */ - std::vector nAccounts_; - /* payAccounts_[i][j] = {from, to} is the pair which consists of two - * number of accounts: source and destinations, which participate in - * j-th payment on i-th ledger */ - std::vector>> payAccounts_; - /* xrpAmount_[i] is the amount for all payments on i-th ledger */ - std::vector xrpAmount_; - /* ledgers_[i] is the i-th ledger which contains the above described - * accounts and payments */ - std::vector> ledgers_; - - TestData( - std::uint64_t const seedValue, - int dataSize = dataSizeMax, - int numShards = 1) - : rng_(seedValue), numShards_(numShards) - { - std::uint32_t n = 0; - std::uint32_t nLedgers = ledgersPerShard * numShards; - - nAccounts_.reserve(nLedgers); - payAccounts_.reserve(nLedgers); - xrpAmount_.reserve(nLedgers); - - for (std::uint32_t i = 0; i < nLedgers; ++i) - { - int p; - if (n >= 2) - p = randInt(rng_, 2 * dataSize); - else - p = 0; - - std::vector> pay; - pay.reserve(p); - - for (int j = 0; j < p; ++j) - { - int from, to; - do - { - from = randInt(rng_, n - 1); - to = randInt(rng_, n - 1); - } while (from == to); - - pay.push_back(std::make_pair(from, to)); - } - - n += !randInt(rng_, nLedgers / dataSize); - - if (n > accounts_.size()) - { - char str[9]; - for (int j = 0; j < 8; ++j) - str[j] = 'a' + randInt(rng_, 'z' - 'a'); - str[8] = 0; - accounts_.emplace_back(str); - } - - nAccounts_.push_back(n); - payAccounts_.push_back(std::move(pay)); - xrpAmount_.push_back(randInt(rng_, 90) + 10); - } - } - - bool - isNewAccounts(int seq) - { - return nAccounts_[seq] > (seq ? nAccounts_[seq - 1] : 0); - } - - void - makeLedgerData(test::jtx::Env& env_, std::uint32_t seq) - { - using namespace test::jtx; - - // The local fee may go up, especially in the online delete tests - while (env_.app().getFeeTrack().lowerLocalFee()) - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - - if (isNewAccounts(seq)) - env_.fund(XRP(iniAmount), accounts_[nAccounts_[seq] - 1]); - - for (std::uint32_t i = 0; i < payAccounts_[seq].size(); ++i) - { - env_( - pay(accounts_[payAccounts_[seq][i].first], - accounts_[payAccounts_[seq][i].second], - XRP(xrpAmount_[seq]))); - } - } - - bool - makeLedgers(test::jtx::Env& env_, std::uint32_t startIndex = 0) - { - if (startIndex == 0) - { - for (std::uint32_t i = 3; i <= ledgersPerShard; ++i) - { - if (!env_.close()) - return false; - std::shared_ptr ledger = - env_.app().getLedgerMaster().getClosedLedger(); - if (ledger->info().seq != i) - return false; - } - } - - for (std::uint32_t i = 0; i < ledgersPerShard * numShards_; ++i) - { - auto const index = i + (startIndex * ledgersPerShard); - - makeLedgerData(env_, i); - if (!env_.close()) - return false; - std::shared_ptr ledger = - env_.app().getLedgerMaster().getClosedLedger(); - if (ledger->info().seq != index + ledgersPerShard + 1) - return false; - ledgers_.push_back(ledger); - } - - return true; - } - }; - - void - testLedgerData( - TestData& data, - std::shared_ptr ledger, - std::uint32_t seq) - { - using namespace test::jtx; - - auto rootCount{0}; - auto accCount{0}; - auto sothCount{0}; - for (auto const& sles : ledger->sles) - { - if (sles->getType() == ltACCOUNT_ROOT) - { - int sq = sles->getFieldU32(sfSequence); - int reqsq = -1; - const auto id = sles->getAccountID(sfAccount); - - for (int i = 0; i < data.accounts_.size(); ++i) - { - if (id == data.accounts_[i].id()) - { - reqsq = ledgersPerShard + 1; - for (int j = 0; j <= seq; ++j) - if (data.nAccounts_[j] > i + 1 || - (data.nAccounts_[j] == i + 1 && - !data.isNewAccounts(j))) - { - for (int k = 0; k < data.payAccounts_[j].size(); - ++k) - if (data.payAccounts_[j][k].first == i) - reqsq++; - } - else - reqsq++; - ++accCount; - break; - } - } - if (reqsq == -1) - { - reqsq = data.nAccounts_[seq] + 1; - ++rootCount; - } - BEAST_EXPECT(sq == reqsq); - } - else - ++sothCount; - } - BEAST_EXPECT(rootCount == 1); - BEAST_EXPECT(accCount == data.nAccounts_[seq]); - BEAST_EXPECT(sothCount == 3); - - auto iniCount{0}; - auto setCount{0}; - auto payCount{0}; - auto tothCount{0}; - for (auto const& tx : ledger->txs) - { - if (tx.first->getTxnType() == ttPAYMENT) - { - std::int64_t xrpAmount = - tx.first->getFieldAmount(sfAmount).xrp().decimalXRP(); - if (xrpAmount == iniAmount) - ++iniCount; - else - { - ++payCount; - BEAST_EXPECT(xrpAmount == data.xrpAmount_[seq]); - } - } - else if (tx.first->getTxnType() == ttACCOUNT_SET) - ++setCount; - else - ++tothCount; - } - int newacc = data.isNewAccounts(seq) ? 1 : 0; - BEAST_EXPECT(iniCount == newacc); - BEAST_EXPECT(setCount == newacc); - BEAST_EXPECT(payCount == data.payAccounts_[seq].size()); - BEAST_EXPECT(tothCount == !seq); - } - - bool - saveLedger( - Database& db, - Ledger const& ledger, - std::shared_ptr const& next = {}) - { - // Store header - { - Serializer s(sizeof(std::uint32_t) + sizeof(LedgerInfo)); - s.add32(HashPrefix::ledgerMaster); - addRaw(ledger.info(), s); - db.store( - hotLEDGER, - std::move(s.modData()), - ledger.info().hash, - ledger.info().seq); - } - - // Store the state map - auto visitAcc = [&](SHAMapTreeNode const& node) { - Serializer s; - node.serializeWithPrefix(s); - db.store( - node.getType() == SHAMapNodeType::tnINNER ? hotUNKNOWN - : hotACCOUNT_NODE, - std::move(s.modData()), - node.getHash().as_uint256(), - ledger.info().seq); - return true; - }; - - if (ledger.stateMap().getHash().isNonZero()) - { - if (!ledger.stateMap().isValid()) - return false; - if (next && next->info().parentHash == ledger.info().hash) - { - auto have = next->stateMap().snapShot(false); - ledger.stateMap().snapShot(false)->visitDifferences( - &(*have), visitAcc); - } - else - ledger.stateMap().snapShot(false)->visitNodes(visitAcc); - } - - // Store the transaction map - auto visitTx = [&](SHAMapTreeNode& node) { - Serializer s; - node.serializeWithPrefix(s); - db.store( - node.getType() == SHAMapNodeType::tnINNER ? hotUNKNOWN - : hotTRANSACTION_NODE, - std::move(s.modData()), - node.getHash().as_uint256(), - ledger.info().seq); - return true; - }; - - if (ledger.info().txHash.isNonZero()) - { - if (!ledger.txMap().isValid()) - return false; - ledger.txMap().snapShot(false)->visitNodes(visitTx); - } - - return true; - } - - void - checkLedger(TestData& data, DatabaseShard& db, Ledger const& ledger) - { - auto fetched = db.fetchLedger(ledger.info().hash, ledger.info().seq); - if (!BEAST_EXPECT(fetched)) - return; - - testLedgerData(data, fetched, ledger.info().seq - ledgersPerShard - 1); - - // verify the metadata/header info by serializing to json - BEAST_EXPECT( - getJson(LedgerFill{ - ledger, nullptr, LedgerFill::full | LedgerFill::expand}) == - getJson(LedgerFill{ - *fetched, nullptr, LedgerFill::full | LedgerFill::expand})); - - BEAST_EXPECT( - getJson(LedgerFill{ - ledger, nullptr, LedgerFill::full | LedgerFill::binary}) == - getJson(LedgerFill{ - *fetched, nullptr, LedgerFill::full | LedgerFill::binary})); - - // walk shamap and validate each node - auto fcompAcc = [&](SHAMapTreeNode& node) -> bool { - Serializer s; - node.serializeWithPrefix(s); - auto nSrc{NodeObject::createObject( - node.getType() == SHAMapNodeType::tnINNER ? hotUNKNOWN - : hotACCOUNT_NODE, - std::move(s.modData()), - node.getHash().as_uint256())}; - if (!BEAST_EXPECT(nSrc)) - return false; - - auto nDst = db.fetchNodeObject( - node.getHash().as_uint256(), ledger.info().seq); - if (!BEAST_EXPECT(nDst)) - return false; - - BEAST_EXPECT(isSame(nSrc, nDst)); - - return true; - }; - if (ledger.stateMap().getHash().isNonZero()) - ledger.stateMap().snapShot(false)->visitNodes(fcompAcc); - - auto fcompTx = [&](SHAMapTreeNode& node) -> bool { - Serializer s; - node.serializeWithPrefix(s); - auto nSrc{NodeObject::createObject( - node.getType() == SHAMapNodeType::tnINNER ? hotUNKNOWN - : hotTRANSACTION_NODE, - std::move(s.modData()), - node.getHash().as_uint256())}; - if (!BEAST_EXPECT(nSrc)) - return false; - - auto nDst = db.fetchNodeObject( - node.getHash().as_uint256(), ledger.info().seq); - if (!BEAST_EXPECT(nDst)) - return false; - - BEAST_EXPECT(isSame(nSrc, nDst)); - - return true; - }; - if (ledger.info().txHash.isNonZero()) - ledger.txMap().snapShot(false)->visitNodes(fcompTx); - } - - std::string - bitmask2Rangeset(std::uint64_t bitmask) - { - std::string set; - if (!bitmask) - return set; - bool empty = true; - - for (std::uint32_t i = 0; i < 64 && bitmask; i++) - { - if (bitmask & (1ll << i)) - { - if (!empty) - set += ","; - set += std::to_string(i); - empty = false; - } - } - - RangeSet rs; - BEAST_EXPECT(from_string(rs, set)); - return ripple::to_string(rs); - } - - std::unique_ptr - testConfig( - std::string const& shardDir, - std::string const& nodeDir = std::string()) - { - using namespace test::jtx; - - return envconfig([&](std::unique_ptr cfg) { - // Shard store configuration - cfg->overwrite(ConfigSection::shardDatabase(), "path", shardDir); - cfg->overwrite( - ConfigSection::shardDatabase(), - "max_historical_shards", - std::to_string(maxHistoricalShards)); - cfg->overwrite( - ConfigSection::shardDatabase(), - "ledgers_per_shard", - std::to_string(ledgersPerShard)); - cfg->overwrite( - ConfigSection::shardDatabase(), - "earliest_seq", - std::to_string(earliestSeq)); - - // Node store configuration - cfg->overwrite( - ConfigSection::nodeDatabase(), - "path", - nodeDir.empty() ? defNodeDir.path() : nodeDir); - cfg->overwrite( - ConfigSection::nodeDatabase(), - "ledgers_per_shard", - std::to_string(ledgersPerShard)); - cfg->overwrite( - ConfigSection::nodeDatabase(), - "earliest_seq", - std::to_string(earliestSeq)); - return cfg; - }); - } - - std::optional - waitShard( - DatabaseShard& shardStore, - std::uint32_t shardIndex, - std::chrono::seconds timeout = shardStoreTimeout) - { - auto const end{std::chrono::system_clock::now() + timeout}; - while (shardStore.getNumTasks() || - !boost::icl::contains( - shardStore.getShardInfo()->finalized(), shardIndex)) - { - if (!BEAST_EXPECT(std::chrono::system_clock::now() < end)) - return std::nullopt; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - return shardIndex; - } - - std::optional - createShard( - TestData& data, - DatabaseShard& shardStore, - int maxShardIndex = 1, - int shardOffset = 0) - { - int shardIndex{-1}; - - for (std::uint32_t i = 0; i < ledgersPerShard; ++i) - { - auto const ledgerSeq{shardStore.prepareLedger( - (maxShardIndex + 1) * ledgersPerShard)}; - if (!BEAST_EXPECT(ledgerSeq != std::nullopt)) - return std::nullopt; - - shardIndex = shardStore.seqToShardIndex(*ledgerSeq); - - int const arrInd = *ledgerSeq - (ledgersPerShard * shardOffset) - - ledgersPerShard - 1; - BEAST_EXPECT( - arrInd >= 0 && arrInd < maxShardIndex * ledgersPerShard); - BEAST_EXPECT(saveLedger(shardStore, *data.ledgers_[arrInd])); - if (arrInd % ledgersPerShard == (ledgersPerShard - 1)) - { - uint256 const finalKey_{0}; - Serializer s; - s.add32(Shard::version); - s.add32(shardStore.firstLedgerSeq(shardIndex)); - s.add32(shardStore.lastLedgerSeq(shardIndex)); - s.addRaw(data.ledgers_[arrInd]->info().hash.data(), 256 / 8); - shardStore.store( - hotUNKNOWN, std::move(s.modData()), finalKey_, *ledgerSeq); - } - shardStore.setStored(data.ledgers_[arrInd]); - } - - return waitShard(shardStore, shardIndex); - } - - void - testStandalone() - { - testcase("Standalone"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - DummyScheduler scheduler; - { - Env env{*this, testConfig(shardDir.path())}; - std::unique_ptr shardStore{ - make_ShardStore(env.app(), scheduler, 2, journal_)}; - - BEAST_EXPECT(shardStore); - BEAST_EXPECT(shardStore->init()); - BEAST_EXPECT(shardStore->ledgersPerShard() == ledgersPerShard); - BEAST_EXPECT(shardStore->seqToShardIndex(ledgersPerShard + 1) == 1); - BEAST_EXPECT(shardStore->seqToShardIndex(2 * ledgersPerShard) == 1); - BEAST_EXPECT( - shardStore->seqToShardIndex(2 * ledgersPerShard + 1) == 2); - BEAST_EXPECT( - shardStore->earliestShardIndex() == - (earliestSeq - 1) / ledgersPerShard); - BEAST_EXPECT(shardStore->firstLedgerSeq(1) == ledgersPerShard + 1); - BEAST_EXPECT(shardStore->lastLedgerSeq(1) == 2 * ledgersPerShard); - BEAST_EXPECT(shardStore->getRootDir().string() == shardDir.path()); - } - - { - Env env{*this, testConfig(shardDir.path())}; - std::unique_ptr shardStore{ - make_ShardStore(env.app(), scheduler, 2, journal_)}; - - env.app().config().overwrite( - ConfigSection::shardDatabase(), "ledgers_per_shard", "512"); - BEAST_EXPECT(!shardStore->init()); - } - - Env env{*this, testConfig(shardDir.path())}; - std::unique_ptr shardStore{ - make_ShardStore(env.app(), scheduler, 2, journal_)}; - - env.app().config().overwrite( - ConfigSection::shardDatabase(), - "earliest_seq", - std::to_string(std::numeric_limits::max())); - BEAST_EXPECT(!shardStore->init()); - } - - void - testCreateShard(std::uint64_t const seedValue) - { - testcase("Create shard"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - if (!createShard(data, *db, 1)) - return; - - for (std::uint32_t i = 0; i < ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - } - - void - testReopenDatabase(std::uint64_t const seedValue) - { - testcase("Reopen shard store"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (auto i = 0; i < 2; ++i) - { - if (!createShard(data, *db, 2)) - return; - } - } - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t i = 1; i <= 2; ++i) - waitShard(*db, i); - - for (std::uint32_t i = 0; i < 2 * ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - } - } - - void - testGetFinalShards(std::uint64_t const seedValue) - { - testcase("Get final shards"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 2, nTestShards); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - - for (auto i = 0; i < nTestShards; ++i) - { - auto const shardIndex{createShard(data, *db, nTestShards)}; - if (!BEAST_EXPECT( - shardIndex && *shardIndex >= 1 && - *shardIndex <= nTestShards)) - { - return; - } - - BEAST_EXPECT(boost::icl::contains( - db->getShardInfo()->finalized(), *shardIndex)); - } - } - - void - testPrepareShards(std::uint64_t const seedValue) - { - testcase("Prepare shards"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 1, nTestShards); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - BEAST_EXPECT(db->getPreShards() == ""); - BEAST_EXPECT(!db->prepareShards({})); - - std::uint64_t bitMask = 0; - for (std::uint32_t i = 0; i < nTestShards * 2; ++i) - { - std::uint32_t const shardIndex{ - randInt(data.rng_, nTestShards - 1) + 1}; - if (bitMask & (1ll << shardIndex)) - { - db->removePreShard(shardIndex); - bitMask &= ~(1ll << shardIndex); - } - else - { - BEAST_EXPECT(db->prepareShards({shardIndex})); - bitMask |= 1ll << shardIndex; - } - BEAST_EXPECT(db->getPreShards() == bitmask2Rangeset(bitMask)); - } - - // test illegal cases - // adding shards with too large number - BEAST_EXPECT(!db->prepareShards({0})); - BEAST_EXPECT(db->getPreShards() == bitmask2Rangeset(bitMask)); - BEAST_EXPECT(!db->prepareShards({nTestShards + 1})); - BEAST_EXPECT(db->getPreShards() == bitmask2Rangeset(bitMask)); - BEAST_EXPECT(!db->prepareShards({nTestShards + 2})); - BEAST_EXPECT(db->getPreShards() == bitmask2Rangeset(bitMask)); - - // create shards which are not prepared for import - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - - std::uint64_t bitMask2 = 0; - for (auto i = 0; i < nTestShards; ++i) - { - auto const shardIndex{createShard(data, *db, nTestShards)}; - if (!BEAST_EXPECT( - shardIndex && *shardIndex >= 1 && - *shardIndex <= nTestShards)) - { - return; - } - - BEAST_EXPECT(boost::icl::contains( - db->getShardInfo()->finalized(), *shardIndex)); - - bitMask2 |= 1ll << *shardIndex; - BEAST_EXPECT((bitMask & bitMask2) == 0); - if ((bitMask | bitMask2) == ((1ll << nTestShards) - 1) << 1) - break; - } - - // try to create another shard - BEAST_EXPECT( - db->prepareLedger((nTestShards + 1) * ledgersPerShard) == - std::nullopt); - } - - void - testImportShard(std::uint64_t const seedValue) - { - testcase("Import shard"); - - using namespace test::jtx; - - beast::temp_dir importDir; - TestData data(seedValue, 2); - - { - Env env{*this, testConfig(importDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - if (!createShard(data, *db, 1)) - return; - - for (std::uint32_t i = 0; i < ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - - data.ledgers_.clear(); - } - - boost::filesystem::path importPath(importDir.path()); - importPath /= "1"; - - { - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - BEAST_EXPECT(!db->importShard(1, importPath / "not_exist")); - BEAST_EXPECT(db->prepareShards({1})); - BEAST_EXPECT(db->getPreShards() == "1"); - - using namespace boost::filesystem; - remove_all(importPath / LgrDBName); - remove_all(importPath / TxDBName); - - if (!BEAST_EXPECT(db->importShard(1, importPath))) - return; - - BEAST_EXPECT(db->getPreShards() == ""); - - auto n = waitShard(*db, 1); - if (!BEAST_EXPECT(n && *n == 1)) - return; - - for (std::uint32_t i = 0; i < ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - } - } - - void - testCorruptedDatabase(std::uint64_t const seedValue) - { - testcase("Corrupted shard store"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - { - TestData data(seedValue, 4, 2); - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (auto i = 0; i < 2; ++i) - { - if (!BEAST_EXPECT(createShard(data, *db, 2))) - return; - } - } - - boost::filesystem::path path = shardDir.path(); - path /= std::string("2"); - path /= "nudb.dat"; - - FILE* f = fopen(path.string().c_str(), "r+b"); - if (!BEAST_EXPECT(f)) - return; - char buf[256]; - beast::rngfill(buf, sizeof(buf), data.rng_); - BEAST_EXPECT(fwrite(buf, 1, 256, f) == 256); - fclose(f); - } - - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t shardIndex = 1; shardIndex <= 1; ++shardIndex) - waitShard(*db, shardIndex); - - BEAST_EXPECT(boost::icl::contains(db->getShardInfo()->finalized(), 1)); - - for (std::uint32_t i = 0; i < 1 * ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - } - - void - testIllegalFinalKey(std::uint64_t const seedValue) - { - testcase("Illegal finalKey"); - - using namespace test::jtx; - - for (int i = 0; i < 5; ++i) - { - beast::temp_dir shardDir; - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue + i, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - int shardIndex{-1}; - for (std::uint32_t j = 0; j < ledgersPerShard; ++j) - { - auto const ledgerSeq{ - db->prepareLedger(2 * ledgersPerShard)}; - if (!BEAST_EXPECT(ledgerSeq != std::nullopt)) - return; - - shardIndex = db->seqToShardIndex(*ledgerSeq); - int arrInd = *ledgerSeq - ledgersPerShard - 1; - BEAST_EXPECT(arrInd >= 0 && arrInd < ledgersPerShard); - BEAST_EXPECT(saveLedger(*db, *data.ledgers_[arrInd])); - if (arrInd % ledgersPerShard == (ledgersPerShard - 1)) - { - uint256 const finalKey_{0}; - Serializer s; - s.add32(Shard::version + (i == 0)); - s.add32(db->firstLedgerSeq(shardIndex) + (i == 1)); - s.add32(db->lastLedgerSeq(shardIndex) - (i == 3)); - s.addRaw( - data.ledgers_[arrInd - (i == 4)] - ->info() - .hash.data(), - 256 / 8); - db->store( - hotUNKNOWN, - std::move(s.modData()), - finalKey_, - *ledgerSeq); - } - db->setStored(data.ledgers_[arrInd]); - } - - if (i == 2) - { - waitShard(*db, shardIndex); - BEAST_EXPECT(boost::icl::contains( - db->getShardInfo()->finalized(), 1)); - } - else - { - boost::filesystem::path path(shardDir.path()); - path /= "1"; - boost::system::error_code ec; - auto start = std::chrono::system_clock::now(); - auto end = start + shardStoreTimeout; - while (std::chrono::system_clock::now() < end && - boost::filesystem::exists(path, ec)) - { - std::this_thread::yield(); - } - - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - } - } - - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue + i, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - if (i == 2) - { - waitShard(*db, 1); - BEAST_EXPECT(boost::icl::contains( - db->getShardInfo()->finalized(), 1)); - - for (std::uint32_t j = 0; j < ledgersPerShard; ++j) - checkLedger(data, *db, *data.ledgers_[j]); - } - else - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - } - } - } - - std::string - ripemd160File(std::string filename) - { - using beast::hash_append; - std::ifstream input(filename, std::ios::in | std::ios::binary); - char buf[4096]; - ripemd160_hasher h; - - while (input.read(buf, 4096), input.gcount() > 0) - hash_append(h, buf, input.gcount()); - - auto const binResult = static_cast(h); - const auto charDigest = binResult.data(); - std::string result; - boost::algorithm::hex( - charDigest, - charDigest + sizeof(binResult), - std::back_inserter(result)); - - return result; - } - - void - testDeterministicShard(std::uint64_t const seedValue) - { - testcase("Deterministic shards"); - - using namespace test::jtx; - - for (int i = 0; i < 2; i++) - { - beast::temp_dir shardDir; - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - if (!BEAST_EXPECT(createShard(data, *db) != std::nullopt)) - return; - } - - boost::filesystem::path path(shardDir.path()); - path /= "1"; - - auto static const ripemd160Key = - ripemd160File((path / "nudb.key").string()); - auto static const ripemd160Dat = - ripemd160File((path / "nudb.dat").string()); - - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - if (!BEAST_EXPECT(waitShard(*db, 1) != std::nullopt)) - return; - - for (std::uint32_t j = 0; j < ledgersPerShard; ++j) - checkLedger(data, *db, *data.ledgers_[j]); - } - - BEAST_EXPECT( - ripemd160File((path / "nudb.key").string()) == ripemd160Key); - BEAST_EXPECT( - ripemd160File((path / "nudb.dat").string()) == ripemd160Dat); - } - } - - void - testImportNodeStore(std::uint64_t const seedValue) - { - testcase("Import node store"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - { - beast::temp_dir nodeDir; - Env env{*this, testConfig(shardDir.path(), nodeDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - Database& ndb = env.app().getNodeStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t i = 0; i < 2 * ledgersPerShard; ++i) - BEAST_EXPECT(saveLedger(ndb, *data.ledgers_[i])); - - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - db->importDatabase(ndb); - for (std::uint32_t i = 1; i <= 2; ++i) - waitShard(*db, i); - - auto const finalShards{db->getShardInfo()->finalized()}; - for (std::uint32_t shardIndex : {1, 2}) - BEAST_EXPECT(boost::icl::contains(finalShards, shardIndex)); - } - { - Env env{*this, testConfig(shardDir.path())}; - DatabaseShard* db = env.app().getShardStore(); - BEAST_EXPECT(db); - - TestData data(seedValue, 4, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t i = 1; i <= 2; ++i) - waitShard(*db, i); - - auto const finalShards{db->getShardInfo()->finalized()}; - for (std::uint32_t shardIndex : {1, 2}) - BEAST_EXPECT(boost::icl::contains(finalShards, shardIndex)); - - for (std::uint32_t i = 0; i < 2 * ledgersPerShard; ++i) - checkLedger(data, *db, *data.ledgers_[i]); - } - } - - void - testImportWithOnlineDelete(std::uint64_t const seedValue) - { - testcase("Import node store with online delete"); - - using namespace test::jtx; - using test::CaptureLogs; - - beast::temp_dir shardDir; - beast::temp_dir nodeDir; - std::string capturedLogs; - - { - auto c = testConfig(shardDir.path(), nodeDir.path()); - auto& section = c->section(ConfigSection::nodeDatabase()); - section.set("online_delete", "550"); - section.set("advisory_delete", "1"); - - // Adjust the log level to capture relevant output - c->section(SECTION_RPC_STARTUP) - .append( - "{ \"command\": \"log_level\", \"severity\": \"trace\" " - "}"); - - std::unique_ptr logs(new CaptureLogs(&capturedLogs)); - Env env{*this, std::move(c), std::move(logs)}; - - DatabaseShard* db = env.app().getShardStore(); - Database& ndb = env.app().getNodeStore(); - BEAST_EXPECT(db); - - auto& store = env.app().getSHAMapStore(); - - // Allow online delete to delete the startup ledgers - // so that it will take some time for the import to - // catch up to the point of the next rotation - store.setCanDelete(10); - - // Create some ledgers for the shard store to import - auto const shardCount = 5; - TestData data(seedValue, 4, shardCount); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - store.rendezvous(); - auto const lastRotated = store.getLastRotated(); - BEAST_EXPECT(lastRotated >= 553 && lastRotated < 1103); - - // Start the import - db->importDatabase(ndb); - - while (!db->getDatabaseImportSequence()) - { - // Wait until the import starts - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - // Enable unimpeded online deletion now that the import has started - store.setCanDelete(std::numeric_limits::max()); - - auto pauseVerifier = std::thread([lastRotated, &store, db, this] { - // The import should still be running when this thread starts - BEAST_EXPECT(db->getDatabaseImportSequence()); - auto rotationProgress = lastRotated; - while (auto const ledgerSeq = db->getDatabaseImportSequence()) - { - // Make sure database rotations dont interfere - // with the import - - auto const last = store.getLastRotated(); - if (last != rotationProgress) - { - // A rotation occurred during shard import. Not - // necessarily an error - - BEAST_EXPECT( - !ledgerSeq || ledgerSeq >= rotationProgress); - rotationProgress = last; - } - } - }); - - auto join = [&pauseVerifier]() { - if (pauseVerifier.joinable()) - pauseVerifier.join(); - }; - - // Create more ledgers to trigger online deletion - data = TestData(seedValue * 2); - if (!BEAST_EXPECT(data.makeLedgers(env, shardCount))) - { - join(); - return; - } - - join(); - BEAST_EXPECT(store.getLastRotated() != lastRotated); - } - - // Database rotation should have been postponed at some - // point during the import - auto const expectedLogMessage = - "rotation would interfere with ShardStore import"; - BEAST_EXPECT( - capturedLogs.find(expectedLogMessage) != std::string::npos); - } - - void - testImportWithHistoricalPaths(std::uint64_t const seedValue) - { - testcase("Import with historical paths"); - - using namespace test::jtx; - - // Test importing with multiple historical paths - { - beast::temp_dir shardDir; - std::array historicalDirs; - std::array historicalPaths; - - std::transform( - historicalDirs.begin(), - historicalDirs.end(), - historicalPaths.begin(), - [](const beast::temp_dir& dir) { return dir.path(); }); - - beast::temp_dir nodeDir; - auto c = testConfig(shardDir.path(), nodeDir.path()); - - auto& historyPaths = c->section(SECTION_HISTORICAL_SHARD_PATHS); - historyPaths.append( - {historicalPaths[0].string(), - historicalPaths[1].string(), - historicalPaths[2].string(), - historicalPaths[3].string()}); - - Env env{*this, std::move(c)}; - DatabaseShard* db = env.app().getShardStore(); - Database& ndb = env.app().getNodeStore(); - BEAST_EXPECT(db); - - auto const shardCount = 4; - - TestData data(seedValue, 4, shardCount); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t i = 0; i < shardCount * ledgersPerShard; ++i) - BEAST_EXPECT(saveLedger(ndb, *data.ledgers_[i])); - - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - - db->importDatabase(ndb); - for (std::uint32_t i = 1; i <= shardCount; ++i) - waitShard(*db, i); - - auto const final{db->getShardInfo()->finalized()}; - for (std::uint32_t shardIndex : {1, 2, 3, 4}) - BEAST_EXPECT(boost::icl::contains(final, shardIndex)); - - auto const mainPathCount = std::distance( - boost::filesystem::directory_iterator(shardDir.path()), - boost::filesystem::directory_iterator()); - - // Only the two most recent shards - // should be stored at the main path - BEAST_EXPECT(mainPathCount == 2); - - auto const historicalPathCount = std::accumulate( - historicalPaths.begin(), - historicalPaths.end(), - 0, - [](int const sum, boost::filesystem::path const& path) { - return sum + - std::distance( - boost::filesystem::directory_iterator(path), - boost::filesystem::directory_iterator()); - }); - - // All historical shards should be stored - // at historical paths - BEAST_EXPECT(historicalPathCount == shardCount - 2); - } - - // Test importing with a single historical path - { - beast::temp_dir shardDir; - beast::temp_dir historicalDir; - beast::temp_dir nodeDir; - - auto c = testConfig(shardDir.path(), nodeDir.path()); - - auto& historyPaths = c->section(SECTION_HISTORICAL_SHARD_PATHS); - historyPaths.append({historicalDir.path()}); - - Env env{*this, std::move(c)}; - DatabaseShard* db = env.app().getShardStore(); - Database& ndb = env.app().getNodeStore(); - BEAST_EXPECT(db); - - auto const shardCount = 4; - - TestData data(seedValue * 2, 4, shardCount); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - for (std::uint32_t i = 0; i < shardCount * ledgersPerShard; ++i) - BEAST_EXPECT(saveLedger(ndb, *data.ledgers_[i])); - - BEAST_EXPECT(db->getShardInfo()->finalized().empty()); - - db->importDatabase(ndb); - for (std::uint32_t i = 1; i <= shardCount; ++i) - waitShard(*db, i); - - auto const finalShards{db->getShardInfo()->finalized()}; - for (std::uint32_t shardIndex : {1, 2, 3, 4}) - BEAST_EXPECT(boost::icl::contains(finalShards, shardIndex)); - - auto const mainPathCount = std::distance( - boost::filesystem::directory_iterator(shardDir.path()), - boost::filesystem::directory_iterator()); - - // Only the two most recent shards - // should be stored at the main path - BEAST_EXPECT(mainPathCount == 2); - - auto const historicalPathCount = std::distance( - boost::filesystem::directory_iterator(historicalDir.path()), - boost::filesystem::directory_iterator()); - - // All historical shards should be stored - // at historical paths - BEAST_EXPECT(historicalPathCount == shardCount - 2); - } - } - - void - testPrepareWithHistoricalPaths(std::uint64_t const seedValue) - { - testcase("Prepare with historical paths"); - - using namespace test::jtx; - - // Create the primary shard directory - beast::temp_dir primaryDir; - auto config{testConfig(primaryDir.path())}; - - // Create four historical directories - std::array historicalDirs; - { - auto& paths{config->section(SECTION_HISTORICAL_SHARD_PATHS)}; - for (auto const& dir : historicalDirs) - paths.append(dir.path()); - } - - Env env{*this, std::move(config)}; - - // Create some shards - std::uint32_t constexpr numShards{4}; - TestData data(seedValue, 4, numShards); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - auto shardStore{env.app().getShardStore()}; - BEAST_EXPECT(shardStore); - - for (auto i = 0; i < numShards; ++i) - { - auto const shardIndex{createShard(data, *shardStore, numShards)}; - if (!BEAST_EXPECT( - shardIndex && *shardIndex >= 1 && *shardIndex <= numShards)) - { - return; - } - } - - { - // Confirm finalized shards are in the shard store - auto const finalized{shardStore->getShardInfo()->finalized()}; - BEAST_EXPECT(boost::icl::length(finalized) == numShards); - BEAST_EXPECT(boost::icl::first(finalized) == 1); - BEAST_EXPECT(boost::icl::last(finalized) == numShards); - } - - using namespace boost::filesystem; - auto const dirContains = [](beast::temp_dir const& dir, - std::uint32_t shardIndex) { - boost::filesystem::path const path(std::to_string(shardIndex)); - for (auto const& it : directory_iterator(dir.path())) - if (boost::filesystem::path(it).stem() == path) - return true; - return false; - }; - auto const historicalDirsContains = [&](std::uint32_t shardIndex) { - for (auto const& dir : historicalDirs) - if (dirContains(dir, shardIndex)) - return true; - return false; - }; - - // Confirm two most recent shards are in the primary shard directory - for (auto const shardIndex : {numShards - 1, numShards}) - { - BEAST_EXPECT(dirContains(primaryDir, shardIndex)); - BEAST_EXPECT(!historicalDirsContains(shardIndex)); - } - - // Confirm remaining shards are in the historical shard directories - for (auto shardIndex = 1; shardIndex < numShards - 1; ++shardIndex) - { - BEAST_EXPECT(!dirContains(primaryDir, shardIndex)); - BEAST_EXPECT(historicalDirsContains(shardIndex)); - } - - // Create some more shards to exercise recent shard rotation - data = TestData(seedValue * 2, 4, numShards); - if (!BEAST_EXPECT(data.makeLedgers(env, numShards))) - return; - - for (auto i = 0; i < numShards; ++i) - { - auto const shardIndex{ - createShard(data, *shardStore, numShards * 2, numShards)}; - if (!BEAST_EXPECT( - shardIndex && *shardIndex >= numShards + 1 && - *shardIndex <= numShards * 2)) - { - return; - } - } - - { - // Confirm finalized shards are in the shard store - auto const finalized{shardStore->getShardInfo()->finalized()}; - BEAST_EXPECT(boost::icl::length(finalized) == numShards * 2); - BEAST_EXPECT(boost::icl::first(finalized) == 1); - BEAST_EXPECT(boost::icl::last(finalized) == numShards * 2); - } - - // Confirm two most recent shards are in the primary shard directory - for (auto const shardIndex : {numShards * 2 - 1, numShards * 2}) - { - BEAST_EXPECT(dirContains(primaryDir, shardIndex)); - BEAST_EXPECT(!historicalDirsContains(shardIndex)); - } - - // Confirm remaining shards are in the historical shard directories - for (auto shardIndex = 1; shardIndex < numShards * 2 - 1; ++shardIndex) - { - BEAST_EXPECT(!dirContains(primaryDir, shardIndex)); - BEAST_EXPECT(historicalDirsContains(shardIndex)); - } - } - - void - testOpenShardManagement(std::uint64_t const seedValue) - { - testcase("Open shard management"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - - auto shardStore{env.app().getShardStore()}; - BEAST_EXPECT(shardStore); - - // Create one shard more than the open final limit - auto const openFinalLimit{env.app().config().getValueFor( - SizedItem::openFinalLimit, std::nullopt)}; - auto const numShards{openFinalLimit + 1}; - - TestData data(seedValue, 2, numShards); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - BEAST_EXPECT(shardStore->getShardInfo()->finalized().empty()); - - int oldestShardIndex{-1}; - for (auto i = 0; i < numShards; ++i) - { - auto shardIndex{createShard(data, *shardStore, numShards)}; - if (!BEAST_EXPECT( - shardIndex && *shardIndex >= 1 && *shardIndex <= numShards)) - { - return; - } - - BEAST_EXPECT(boost::icl::contains( - shardStore->getShardInfo()->finalized(), *shardIndex)); - - if (oldestShardIndex == -1) - oldestShardIndex = *shardIndex; - } - - // The number of open shards exceeds the open limit by one. - // A sweep will close enough shards to be within the limit. - shardStore->sweep(); - - // Read from the closed shard and automatically open it - auto const ledgerSeq{shardStore->lastLedgerSeq(oldestShardIndex)}; - auto const index{ledgerSeq - ledgersPerShard - 1}; - BEAST_EXPECT(shardStore->fetchNodeObject( - data.ledgers_[index]->info().hash, ledgerSeq)); - } - - void - testShardInfo(std::uint64_t const seedValue) - { - testcase("Shard info"); - - using namespace test::jtx; - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - - auto shardStore{env.app().getShardStore()}; - BEAST_EXPECT(shardStore); - - // Check shard store is empty - { - auto const shardInfo{shardStore->getShardInfo()}; - BEAST_EXPECT( - shardInfo->msgTimestamp().time_since_epoch().count() == 0); - BEAST_EXPECT(shardInfo->finalizedToString().empty()); - BEAST_EXPECT(shardInfo->finalized().empty()); - BEAST_EXPECT(shardInfo->incompleteToString().empty()); - BEAST_EXPECT(shardInfo->incomplete().empty()); - } - - // Create an incomplete shard with index 1 - TestData data(seedValue, dataSizeMax, 2); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - if (!BEAST_EXPECT(shardStore->prepareLedger(2 * ledgersPerShard))) - return; - - // Check shard is incomplete - { - auto const shardInfo{shardStore->getShardInfo()}; - BEAST_EXPECT(shardInfo->finalizedToString().empty()); - BEAST_EXPECT(shardInfo->finalized().empty()); - BEAST_EXPECT(shardInfo->incompleteToString() == "1:0"); - BEAST_EXPECT( - shardInfo->incomplete().find(1) != - shardInfo->incomplete().end()); - } - - // Finalize the shard - { - auto shardIndex{createShard(data, *shardStore)}; - if (!BEAST_EXPECT(shardIndex && *shardIndex == 1)) - return; - } - - // Check shard is finalized - { - auto const shardInfo{shardStore->getShardInfo()}; - BEAST_EXPECT(shardInfo->finalizedToString() == "1"); - BEAST_EXPECT(boost::icl::contains(shardInfo->finalized(), 1)); - BEAST_EXPECT(shardInfo->incompleteToString().empty()); - BEAST_EXPECT(shardInfo->incomplete().empty()); - BEAST_EXPECT(!shardInfo->update(1, ShardState::finalized, 0)); - BEAST_EXPECT(shardInfo->setFinalizedFromString("2")); - BEAST_EXPECT(shardInfo->finalizedToString() == "2"); - BEAST_EXPECT(boost::icl::contains(shardInfo->finalized(), 2)); - } - - // Create an incomplete shard with index 2 - if (!BEAST_EXPECT(shardStore->prepareLedger(3 * ledgersPerShard))) - return; - - // Store 10 percent of the ledgers - for (std::uint32_t i = 0; i < (ledgersPerShard / 10); ++i) - { - auto const ledgerSeq{ - shardStore->prepareLedger(3 * ledgersPerShard)}; - if (!BEAST_EXPECT(ledgerSeq != std::nullopt)) - return; - - auto const arrInd{*ledgerSeq - ledgersPerShard - 1}; - if (!BEAST_EXPECT(saveLedger(*shardStore, *data.ledgers_[arrInd]))) - return; - - shardStore->setStored(data.ledgers_[arrInd]); - } - - auto const shardInfo{shardStore->getShardInfo()}; - BEAST_EXPECT(shardInfo->incompleteToString() == "2:10"); - BEAST_EXPECT( - shardInfo->incomplete().find(2) != shardInfo->incomplete().end()); - - auto const timeStamp{env.app().timeKeeper().now()}; - shardInfo->setMsgTimestamp(timeStamp); - BEAST_EXPECT(timeStamp == shardInfo->msgTimestamp()); - - // Check message - auto const msg{shardInfo->makeMessage(env.app())}; - Serializer s; - s.add32(HashPrefix::shardInfo); - - BEAST_EXPECT(msg.timestamp() != 0); - s.add32(msg.timestamp()); - - // Verify incomplete shard - { - BEAST_EXPECT(msg.incomplete_size() == 1); - - auto const& incomplete{msg.incomplete(0)}; - BEAST_EXPECT(incomplete.shardindex() == 2); - s.add32(incomplete.shardindex()); - - BEAST_EXPECT( - static_cast(incomplete.state()) == - ShardState::acquire); - s.add32(incomplete.state()); - - BEAST_EXPECT(incomplete.has_progress()); - BEAST_EXPECT(incomplete.progress() == 10); - s.add32(incomplete.progress()); - } - - // Verify finalized shard - BEAST_EXPECT(msg.has_finalized()); - BEAST_EXPECT(msg.finalized() == "1"); - s.addRaw(msg.finalized().data(), msg.finalized().size()); - - // Verify public key - auto slice{makeSlice(msg.publickey())}; - BEAST_EXPECT(publicKeyType(slice)); - - // Verify signature - BEAST_EXPECT(verify( - PublicKey(slice), s.slice(), makeSlice(msg.signature()), false)); - - BEAST_EXPECT(msg.peerchain_size() == 0); - } - - void - testSQLiteDatabase(std::uint64_t const seedValue) - { - testcase("SQLite Database"); - - using namespace test::jtx; - - beast::temp_dir shardDir; - Env env{*this, testConfig(shardDir.path())}; - - auto shardStore{env.app().getShardStore()}; - BEAST_EXPECT(shardStore); - - auto const shardCount = 3; - TestData data(seedValue, 3, shardCount); - if (!BEAST_EXPECT(data.makeLedgers(env))) - return; - - BEAST_EXPECT(shardStore->getShardInfo()->finalized().empty()); - BEAST_EXPECT(shardStore->getShardInfo()->incompleteToString().empty()); - - auto rdb = - dynamic_cast(&env.app().getRelationalDatabase()); - - BEAST_EXPECT(rdb); - - for (std::uint32_t i = 0; i < shardCount; ++i) - { - // Populate the shard store - - auto n = createShard(data, *shardStore, shardCount); - if (!BEAST_EXPECT(n && *n >= 1 && *n <= shardCount)) - return; - } - - // Close these databases to force the SQLiteDatabase - // to use the shard databases and lookup tables. - rdb->closeLedgerDB(); - rdb->closeTransactionDB(); - - // Lambda for comparing Ledger objects - auto infoCmp = [](auto const& a, auto const& b) { - return a.hash == b.hash && a.txHash == b.txHash && - a.accountHash == b.accountHash && - a.parentHash == b.parentHash && a.drops == b.drops && - a.accepted == b.accepted && a.closeFlags == b.closeFlags && - a.closeTimeResolution == b.closeTimeResolution && - a.closeTime == b.closeTime; - }; - - for (auto const& ledger : data.ledgers_) - { - // Compare each test ledger to the data retrieved - // from the SQLiteDatabase class - - if (shardStore->seqToShardIndex(ledger->seq()) < - shardStore->earliestShardIndex() || - ledger->info().seq < shardStore->earliestLedgerSeq()) - continue; - - auto info = rdb->getLedgerInfoByHash(ledger->info().hash); - - BEAST_EXPECT(info); - BEAST_EXPECT(infoCmp(*info, ledger->info())); - - for (auto const& transaction : ledger->txs) - { - // Compare each test transaction to the data - // retrieved from the SQLiteDatabase class - - error_code_i error{rpcSUCCESS}; - - auto reference = rdb->getTransaction( - transaction.first->getTransactionID(), {}, error); - - BEAST_EXPECT(error == rpcSUCCESS); - if (!BEAST_EXPECT(reference.index() == 0)) - continue; - - auto txn = std::get<0>(reference).first->getSTransaction(); - - BEAST_EXPECT( - transaction.first->getFullText() == txn->getFullText()); - } - } - - // Create additional ledgers to test a pathway in - // 'ripple::saveLedgerMeta' wherein fetching the - // accepted ledger fails - data = TestData(seedValue * 2, 4, 1); - if (!BEAST_EXPECT(data.makeLedgers(env, shardCount))) - return; - } - -public: - DatabaseShard_test() : journal_("DatabaseShard_test", *this) - { - } - - void - run() override - { - auto seedValue = [] { - static std::uint64_t seedValue = 41; - seedValue += 10; - return seedValue; - }; - - testStandalone(); - testCreateShard(seedValue()); - testReopenDatabase(seedValue()); - testGetFinalShards(seedValue()); - testPrepareShards(seedValue()); - testImportShard(seedValue()); - testCorruptedDatabase(seedValue()); - testIllegalFinalKey(seedValue()); - testDeterministicShard(seedValue()); - testImportNodeStore(seedValue()); - testImportWithOnlineDelete(seedValue()); - testImportWithHistoricalPaths(seedValue()); - testPrepareWithHistoricalPaths(seedValue()); - testOpenShardManagement(seedValue()); - testShardInfo(seedValue()); - testSQLiteDatabase(seedValue()); - } -}; - -BEAST_DEFINE_TESTSUITE_MANUAL(DatabaseShard, NodeStore, ripple); - -} // namespace NodeStore -} // namespace ripple diff --git a/src/test/nodestore/Database_test.cpp b/src/test/nodestore/Database_test.cpp index 0cf2afb21a7..59557f3c79f 100644 --- a/src/test/nodestore/Database_test.cpp +++ b/src/test/nodestore/Database_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include namespace ripple { @@ -439,6 +439,115 @@ class Database_test : public TestBase BEAST_EXPECT(found); } } + { + // N/A: Default values + Env env(*this); + auto const s = setup_DatabaseCon(env.app().config()); + if (BEAST_EXPECT(s.txPragma.size() == 4)) + { + BEAST_EXPECT(s.txPragma.at(0) == "PRAGMA page_size=4096;"); + BEAST_EXPECT( + s.txPragma.at(1) == "PRAGMA journal_size_limit=1582080;"); + BEAST_EXPECT( + s.txPragma.at(2) == "PRAGMA max_page_count=4294967294;"); + BEAST_EXPECT( + s.txPragma.at(3) == "PRAGMA mmap_size=17179869184;"); + } + } + { + // Success: Valid values + Env env = [&]() { + auto p = test::jtx::envconfig(); + { + auto& section = p->section("sqlite"); + section.set("page_size", "512"); + section.set("journal_size_limit", "2582080"); + } + return Env(*this, std::move(p)); + }(); + auto const s = setup_DatabaseCon(env.app().config()); + if (BEAST_EXPECT(s.txPragma.size() == 4)) + { + BEAST_EXPECT(s.txPragma.at(0) == "PRAGMA page_size=512;"); + BEAST_EXPECT( + s.txPragma.at(1) == "PRAGMA journal_size_limit=2582080;"); + BEAST_EXPECT( + s.txPragma.at(2) == "PRAGMA max_page_count=4294967294;"); + BEAST_EXPECT( + s.txPragma.at(3) == "PRAGMA mmap_size=17179869184;"); + } + } + { + // Error: Invalid values + auto const expected = + "Invalid page_size. Must be between 512 and 65536."; + bool found = false; + auto p = test::jtx::envconfig(); + { + auto& section = p->section("sqlite"); + section.set("page_size", "256"); + } + try + { + Env env( + *this, + std::move(p), + std::make_unique(expected, &found), + beast::severities::kWarning); + fail(); + } + catch (...) + { + BEAST_EXPECT(found); + } + } + { + // Error: Invalid values + auto const expected = + "Invalid page_size. Must be between 512 and 65536."; + bool found = false; + auto p = test::jtx::envconfig(); + { + auto& section = p->section("sqlite"); + section.set("page_size", "131072"); + } + try + { + Env env( + *this, + std::move(p), + std::make_unique(expected, &found), + beast::severities::kWarning); + fail(); + } + catch (...) + { + BEAST_EXPECT(found); + } + } + { + // Error: Invalid values + auto const expected = "Invalid page_size. Must be a power of 2."; + bool found = false; + auto p = test::jtx::envconfig(); + { + auto& section = p->section("sqlite"); + section.set("page_size", "513"); + } + try + { + Env env( + *this, + std::move(p), + std::make_unique(expected, &found), + beast::severities::kWarning); + fail(); + } + catch (...) + { + BEAST_EXPECT(found); + } + } } //-------------------------------------------------------------------------- @@ -616,37 +725,6 @@ class Database_test : public TestBase std::strcmp(e.what(), "earliest_seq set more than once") == 0); } - - // Verify default ledgers per shard - { - std::unique_ptr db = - Manager::instance().make_Database( - megabytes(4), scheduler, 2, nodeParams, journal_); - BEAST_EXPECT( - db->ledgersPerShard() == DEFAULT_LEDGERS_PER_SHARD); - } - - // Set an invalid ledgers per shard - try - { - nodeParams.set("ledgers_per_shard", "100"); - std::unique_ptr db = - Manager::instance().make_Database( - megabytes(4), scheduler, 2, nodeParams, journal_); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT( - std::strcmp(e.what(), "Invalid ledgers_per_shard") == 0); - } - - // Set a valid ledgers per shard - nodeParams.set("ledgers_per_shard", "256"); - std::unique_ptr db = Manager::instance().make_Database( - megabytes(4), scheduler, 2, nodeParams, journal_); - - // Verify database uses the ledgers per shard - BEAST_EXPECT(db->ledgersPerShard() == 256); } } diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index 9ccf2e1698d..98608c5fb11 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_NODESTORE_BASE_H_INCLUDED #define RIPPLE_NODESTORE_BASE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index 61cfb0994dc..73789c02449 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -17,16 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -36,8 +38,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index 4a9ee9f0ac3..e13fe9c0c8d 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -34,7 +34,7 @@ #include #include -#include +#include /* diff --git a/src/test/nodestore/varint_test.cpp b/src/test/nodestore/varint_test.cpp index a84506b7eac..da7ae9d2858 100644 --- a/src/test/nodestore/varint_test.cpp +++ b/src/test/nodestore/varint_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/overlay/ProtocolVersion_test.cpp b/src/test/overlay/ProtocolVersion_test.cpp index a5a26fe74ec..dfc0ee70b8e 100644 --- a/src/test/overlay/ProtocolVersion_test.cpp +++ b/src/test/overlay/ProtocolVersion_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/overlay/cluster_test.cpp b/src/test/overlay/cluster_test.cpp index afbc12a20ee..d22674d28c6 100644 --- a/src/test/overlay/cluster_test.cpp +++ b/src/test/overlay/cluster_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace ripple { namespace tests { diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index 81f21258e30..19669efc23f 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -17,34 +17,34 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/overlay/handshake_test.cpp b/src/test/overlay/handshake_test.cpp index 25bf20add12..93038e522a4 100644 --- a/src/test/overlay/handshake_test.cpp +++ b/src/test/overlay/handshake_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 025e3295f2a..e0b0d006a2f 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -16,15 +16,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -888,9 +888,8 @@ class reduce_relay_test : public beast::unit_test::suite printPeers(const std::string& msg, std::uint16_t validator = 0) { auto peers = network_.overlay().getPeers(network_.validator(validator)); - std::cout << msg << " " - << "num peers " << (int)network_.overlay().getNumPeers() - << std::endl; + std::cout << msg << " " << "num peers " + << (int)network_.overlay().getNumPeers() << std::endl; for (auto& [k, v] : peers) std::cout << k << ":" << (int)std::get(v) << " "; diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 434b4100852..6dd4f8c00b8 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include #include diff --git a/src/test/overlay/tx_reduce_relay_test.cpp b/src/test/overlay/tx_reduce_relay_test.cpp index 7b26ad0962f..3065bcbb685 100644 --- a/src/test/overlay/tx_reduce_relay_test.cpp +++ b/src/test/overlay/tx_reduce_relay_test.cpp @@ -16,12 +16,12 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include namespace ripple { @@ -189,7 +189,10 @@ class tx_reduce_relay_test : public beast::unit_test::suite consumer, std::move(stream_ptr), overlay); + BEAST_EXPECT( + overlay.findPeerByPublicKey(key) == std::shared_ptr{}); overlay.add_active(peer); + BEAST_EXPECT(overlay.findPeerByPublicKey(key) == peer); peers.emplace_back(peer); // overlay stores week ptr to PeerImp lid_ += 2; rid_ += 2; diff --git a/src/test/peerfinder/Livecache_test.cpp b/src/test/peerfinder/Livecache_test.cpp index b6d193f21f5..7fb9f02f42a 100644 --- a/src/test/peerfinder/Livecache_test.cpp +++ b/src/test/peerfinder/Livecache_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/test/peerfinder/PeerFinder_test.cpp b/src/test/peerfinder/PeerFinder_test.cpp index feabe69b647..a1cb8d7408d 100644 --- a/src/test/peerfinder/PeerFinder_test.cpp +++ b/src/test/peerfinder/PeerFinder_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/test/protocol/ApiVersion_test.cpp b/src/test/protocol/ApiVersion_test.cpp index 81ff0184e1e..7d634dc569d 100644 --- a/src/test/protocol/ApiVersion_test.cpp +++ b/src/test/protocol/ApiVersion_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/test/protocol/BuildInfo_test.cpp b/src/test/protocol/BuildInfo_test.cpp index 82ad4d67963..2c40681603c 100644 --- a/src/test/protocol/BuildInfo_test.cpp +++ b/src/test/protocol/BuildInfo_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/protocol/Hooks_test.cpp b/src/test/protocol/Hooks_test.cpp index 1f71abb3af7..fb61d10a739 100644 --- a/src/test/protocol/Hooks_test.cpp +++ b/src/test/protocol/Hooks_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include namespace ripple { diff --git a/src/test/protocol/InnerObjectFormats_test.cpp b/src/test/protocol/InnerObjectFormats_test.cpp index 009ceef2563..cfc8d123c22 100644 --- a/src/test/protocol/InnerObjectFormats_test.cpp +++ b/src/test/protocol/InnerObjectFormats_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include // Json::Reader -#include // RPC::containsError -#include -#include // STParsedJSONObject #include +#include +#include +#include // Json::Reader +#include // RPC::containsError +#include +#include // STParsedJSONObject namespace ripple { diff --git a/src/test/protocol/Issue_test.cpp b/src/test/protocol/Issue_test.cpp index 44eb6e0c0e3..7ec1507e04b 100644 --- a/src/test/protocol/Issue_test.cpp +++ b/src/test/protocol/Issue_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/test/protocol/Memo_test.cpp b/src/test/protocol/Memo_test.cpp index e119ee76eec..bd63e7003ad 100644 --- a/src/test/protocol/Memo_test.cpp +++ b/src/test/protocol/Memo_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/protocol/MultiApiJson_test.cpp b/src/test/protocol/MultiApiJson_test.cpp index a7b0e7e9aec..a5c37d257c4 100644 --- a/src/test/protocol/MultiApiJson_test.cpp +++ b/src/test/protocol/MultiApiJson_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include @@ -105,8 +105,8 @@ struct MultiApiJson_test : beast::unit_test::suite BEAST_EXPECT(!s1.valid(0)); BEAST_EXPECT(!s1.valid(RPC::apiMaximumValidVersion + 1)); BEAST_EXPECT( - !s1.valid(std::numeric_limits::max())); + !s1.valid(std::numeric_limits< + decltype(RPC::apiMaximumValidVersion.value)>::max())); int result = 1; static_assert( @@ -165,32 +165,28 @@ struct MultiApiJson_test : beast::unit_test::suite // Several overloads we want to fail static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // [](Json::Value&, auto) {}); // missing const }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // [](Json::Value&) {}); // missing const }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // []() {}); // missing parameters }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // [](auto) {}, @@ -198,8 +194,7 @@ struct MultiApiJson_test : beast::unit_test::suite }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // [](auto, auto) {}, @@ -207,8 +202,7 @@ struct MultiApiJson_test : beast::unit_test::suite }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { forAllApiVersions( std::forward(v).visit(), // [](auto, auto, const char*) {}, @@ -218,40 +212,35 @@ struct MultiApiJson_test : beast::unit_test::suite // Sanity checks static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](auto) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](Json::Value const&) {}); }; }(std::as_const(s1))); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](Json::Value const&, auto...) {}); }; }(std::as_const(s1))); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](Json::Value&, auto, auto, auto...) {}, @@ -260,8 +249,7 @@ struct MultiApiJson_test : beast::unit_test::suite }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // []( @@ -274,16 +262,14 @@ struct MultiApiJson_test : beast::unit_test::suite }; }(std::as_const(s1))); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](auto...) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return requires - { + return requires { forAllApiVersions( std::forward(v).visit(), // [](auto...) {}); @@ -342,45 +328,25 @@ struct MultiApiJson_test : beast::unit_test::suite // Tests of requires clause - these are expected to match static_assert([](auto&& v) { - return requires - { - v.set("name", Json::nullValue); - }; + return requires { v.set("name", Json::nullValue); }; }(x)); static_assert([](auto&& v) { - return requires - { - v.set("name", "value"); - }; - }(x)); - static_assert([](auto&& v) { - return requires - { - v.set("name", true); - }; - }(x)); - static_assert([](auto&& v) { - return requires - { - v.set("name", 42); - }; + return requires { v.set("name", "value"); }; }(x)); + static_assert( + [](auto&& v) { return requires { v.set("name", true); }; }(x)); + static_assert( + [](auto&& v) { return requires { v.set("name", 42); }; }(x)); // Tests of requires clause - these are expected NOT to match struct foo_t final { }; static_assert([](auto&& v) { - return !requires - { - v.set("name", foo_t{}); - }; + return !requires { v.set("name", foo_t{}); }; }(x)); static_assert([](auto&& v) { - return !requires - { - v.set("name", std::nullopt); - }; + return !requires { v.set("name", std::nullopt); }; }(x)); } @@ -436,8 +402,7 @@ struct MultiApiJson_test : beast::unit_test::suite // Test different overloads static_assert([](auto&& v) { - return requires - { + return requires { v.visitor( v, std::integral_constant{}, @@ -458,8 +423,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor( v, std::integral_constant{}, @@ -476,8 +440,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor( v, std::integral_constant{}, @@ -498,8 +461,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor( v, std::integral_constant{}, @@ -516,8 +478,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor(v, 1, [](Json::Value&, unsigned) {}); }; }(s1)); @@ -533,10 +494,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](Json::Value&) {}); - }; + return requires { v.visitor(v, 1, [](Json::Value&) {}); }; }(s1)); BEAST_EXPECT( s1.visitor( @@ -548,8 +506,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor(v, 1, [](Json::Value const&, unsigned) {}); }; }(std::as_const(s1))); @@ -565,10 +522,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](Json::Value const&) {}); - }; + return requires { v.visitor(v, 1, [](Json::Value const&) {}); }; }(std::as_const(s1))); BEAST_EXPECT( s1.visitor( @@ -671,8 +625,7 @@ struct MultiApiJson_test : beast::unit_test::suite // Several overloads we want to fail static_assert([](auto&& v) { - return !requires - { + return !requires { v.visitor( v, 1, // @@ -681,8 +634,7 @@ struct MultiApiJson_test : beast::unit_test::suite }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { v.visitor( std::move(v), // cannot bind rvalue 1, @@ -691,8 +643,7 @@ struct MultiApiJson_test : beast::unit_test::suite }(s1)); static_assert([](auto&& v) { - return !requires - { + return !requires { v.visitor( v, 1, // @@ -701,8 +652,7 @@ struct MultiApiJson_test : beast::unit_test::suite }(s1)); static_assert([](auto&& v) { - return !requires - { + return !requires { v.visitor( v, 1, // @@ -712,74 +662,52 @@ struct MultiApiJson_test : beast::unit_test::suite // Want these to be unambiguous static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](auto) {}); - }; + return requires { v.visitor(v, 1, [](auto) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](Json::Value&) {}); - }; + return requires { v.visitor(v, 1, [](Json::Value&) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor(v, 1, [](Json::Value&, auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](Json::Value const&) {}); - }; + return requires { v.visitor(v, 1, [](Json::Value const&) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor(v, 1, [](Json::Value const&, auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](auto...) {}); - }; + return requires { v.visitor(v, 1, [](auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor(v, 1, [](auto, auto...) {}); - }; + return requires { v.visitor(v, 1, [](auto, auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { + return requires { v.visitor(v, 1, [](auto, auto, auto...) {}); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor( - v, 1, [](auto, auto, auto...) {}, ""); + return requires { + v.visitor(v, 1, [](auto, auto, auto...) {}, ""); }; }(s1)); static_assert([](auto&& v) { - return requires - { - v.visitor( - v, 1, [](auto, auto, auto, auto...) {}, ""); + return requires { + v.visitor(v, 1, [](auto, auto, auto, auto...) {}, ""); }; }(s1)); } @@ -794,8 +722,7 @@ struct MultiApiJson_test : beast::unit_test::suite // Test different overloads static_assert([](auto&& v) { - return requires - { + return requires { v.visit( std::integral_constant{}, [](Json::Value&, std::integral_constant) { @@ -813,8 +740,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value const&, auto) { return 0; }, [](auto, auto) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()( std::integral_constant{}, [](Json::Value&, std::integral_constant) { @@ -833,8 +759,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visit( std::integral_constant{}, [](Json::Value&) {}); @@ -848,8 +773,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value const&) { return 0; }, [](auto...) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()( std::integral_constant{}, [](Json::Value&) {}); @@ -864,8 +788,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 2); static_assert([](auto&& v) { - return requires - { + return requires { v.visit( std::integral_constant{}, [](Json::Value const&, @@ -883,8 +806,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value&, auto) { return 0; }, [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()( std::integral_constant{}, [](Json::Value const&, @@ -903,8 +825,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visit( std::integral_constant{}, [](Json::Value const&) {}); @@ -918,8 +839,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value&) { return 0; }, [](auto...) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()( std::integral_constant{}, [](Json::Value const&) {}); @@ -934,10 +854,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { - v.visit(1, [](Json::Value&, unsigned) {}); - }; + return requires { v.visit(1, [](Json::Value&, unsigned) {}); }; }(s1)); BEAST_EXPECT( s1.visit( @@ -950,8 +867,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value&, auto) { return 0; }, [](auto, auto) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()(1, [](Json::Value&, unsigned) {}); }; }(s1)); @@ -967,10 +883,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { - v.visit(1, [](Json::Value&) {}); - }; + return requires { v.visit(1, [](Json::Value&) {}); }; }(s1)); BEAST_EXPECT( s1.visit( @@ -980,10 +893,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value const&) { return 0; }, [](auto...) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { - v.visit()(1, [](Json::Value&) {}); - }; + return requires { v.visit()(1, [](Json::Value&) {}); }; }(s1)); BEAST_EXPECT( s1.visit()( @@ -994,8 +904,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto...) { return 0; }}) == 5); static_assert([](auto&& v) { - return requires - { + return requires { v.visit(1, [](Json::Value const&, unsigned) {}); }; }(std::as_const(s1))); @@ -1010,8 +919,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value&, unsigned) { return 0; }, [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { + return requires { v.visit()(1, [](Json::Value const&, unsigned) {}); }; }(std::as_const(s1))); @@ -1027,10 +935,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](auto, auto) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { - v.visit(1, [](Json::Value const&) {}); - }; + return requires { v.visit(1, [](Json::Value const&) {}); }; }(std::as_const(s1))); BEAST_EXPECT( std::as_const(s1).visit( @@ -1040,10 +945,7 @@ struct MultiApiJson_test : beast::unit_test::suite [](Json::Value&) { return 0; }, [](auto...) { return 0; }}) == 3); static_assert([](auto&& v) { - return requires - { - v.visit()(1, [](Json::Value const&) {}); - }; + return requires { v.visit()(1, [](Json::Value const&) {}); }; }(std::as_const(s1))); BEAST_EXPECT( std::as_const(s1).visit()( @@ -1055,83 +957,71 @@ struct MultiApiJson_test : beast::unit_test::suite // Rvalue MultivarJson visitor only binds to regular reference static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit(1, [](Json::Value&&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit( 1, [](Json::Value const&&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit(1, [](Json::Value&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit( 1, [](Json::Value const&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit()( 1, [](Json::Value&&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit()( 1, [](Json::Value const&&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit()( 1, [](Json::Value&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit()( 1, [](Json::Value const&) {}); }; }(std::move(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit( 1, [](Json::Value const&&) {}); }; }(std::move(std::as_const(s1)))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit( 1, [](Json::Value const&) {}); }; }(std::move(std::as_const(s1)))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit()( 1, [](Json::Value const&&) {}); }; }(std::move(std::as_const(s1)))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit()( 1, [](Json::Value const&) {}); }; @@ -1139,15 +1029,13 @@ struct MultiApiJson_test : beast::unit_test::suite // Missing const static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit( 1, [](Json::Value&, auto) {}); }; }(std::as_const(s1))); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit()( 1, [](Json::Value&, auto) {}); }; @@ -1155,28 +1043,24 @@ struct MultiApiJson_test : beast::unit_test::suite // Missing parameter static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit(1, []() {}); }; }(s1)); static_assert([](auto&& v) { - return !requires - { + return !requires { std::forward(v).visit()(1, []() {}); }; }(s1)); // Sanity checks static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit(1, [](auto...) {}); }; }(std::as_const(s1))); static_assert([](auto&& v) { - return requires - { + return requires { std::forward(v).visit()(1, [](auto...) {}); }; }(std::as_const(s1))); diff --git a/src/test/protocol/PublicKey_test.cpp b/src/test/protocol/PublicKey_test.cpp index 040d752f481..7fa798d1483 100644 --- a/src/test/protocol/PublicKey_test.cpp +++ b/src/test/protocol/PublicKey_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/protocol/Quality_test.cpp b/src/test/protocol/Quality_test.cpp index a36591b0247..64cf0c71b3a 100644 --- a/src/test/protocol/Quality_test.cpp +++ b/src/test/protocol/Quality_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { @@ -29,7 +29,7 @@ class Quality_test : public beast::unit_test::suite // Create a raw, non-integral amount from mantissa and exponent STAmount static raw(std::uint64_t mantissa, int exponent) { - return STAmount({Currency(3), AccountID(3)}, mantissa, exponent); + return STAmount(Issue{Currency(3), AccountID(3)}, mantissa, exponent); } template diff --git a/src/test/protocol/STAccount_test.cpp b/src/test/protocol/STAccount_test.cpp index f8f79752a5a..034e0e9e087 100644 --- a/src/test/protocol/STAccount_test.cpp +++ b/src/test/protocol/STAccount_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index bd3e96694fd..b512c42a643 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -62,7 +62,6 @@ class STAmount_test : public beast::unit_test::suite amount.issue(), mantissa, amount.exponent(), - amount.native(), amount.negative(), STAmount::unchecked{}}; } @@ -82,7 +81,6 @@ class STAmount_test : public beast::unit_test::suite amount.issue(), mantissa, amount.exponent(), - amount.native(), amount.negative(), STAmount::unchecked{}}; } diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp new file mode 100644 index 00000000000..ed255e32f1c --- /dev/null +++ b/src/test/protocol/STNumber_test.cpp @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include + +#include +#include + +namespace ripple { + +struct STNumber_test : public beast::unit_test::suite +{ + void + testCombo(Number number) + { + STNumber const before{sfNumber, number}; + BEAST_EXPECT(number == before); + Serializer s; + before.add(s); + BEAST_EXPECT(s.size() == 12); + SerialIter sit(s.slice()); + STNumber const after{sit, sfNumber}; + BEAST_EXPECT(after.isEquivalent(before)); + BEAST_EXPECT(number == after); + } + + void + run() override + { + static_assert(!std::is_convertible_v); + + { + STNumber const stnum{sfNumber}; + BEAST_EXPECT(stnum.getSType() == STI_NUMBER); + BEAST_EXPECT(stnum.getText() == "0"); + BEAST_EXPECT(stnum.isDefault() == true); + BEAST_EXPECT(stnum.value() == Number{0}); + } + + std::initializer_list const mantissas = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int64_t mantissa : mantissas) + testCombo(Number{mantissa}); + + std::initializer_list const exponents = { + Number::minExponent, -1, 0, 1, Number::maxExponent - 1}; + for (std::int32_t exponent : exponents) + testCombo(Number{123, exponent}); + + { + STAmount const strikePrice{noIssue(), 100}; + STNumber const factor{sfNumber, 100}; + auto const iouValue = strikePrice.iou(); + IOUAmount totalValue{iouValue * factor}; + STAmount const totalAmount{totalValue, strikePrice.issue()}; + BEAST_EXPECT(totalAmount == Number{10'000}); + } + } +}; + +BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple); + +void +testCompile(std::ostream& out) +{ + STNumber number{sfNumber, 42}; + out << number; +} + +} // namespace ripple diff --git a/src/test/protocol/STObject_test.cpp b/src/test/protocol/STObject_test.cpp index 34d1cc82fac..071a5f4f63c 100644 --- a/src/test/protocol/STObject_test.cpp +++ b/src/test/protocol/STObject_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include @@ -243,9 +243,8 @@ class STObject_test : public beast::unit_test::suite unexpected(sfGeneric.isUseful(), "sfGeneric must not be useful"); { // Try to put sfGeneric in an SOTemplate. - except([&]() { - SOTemplate elements{{sfGeneric, soeREQUIRED}}; - }); + except( + [&]() { SOTemplate elements{{sfGeneric, soeREQUIRED}}; }); } unexpected(sfInvalid.isUseful(), "sfInvalid must not be useful"); @@ -263,9 +262,8 @@ class STObject_test : public beast::unit_test::suite } { // Try to put sfInvalid in an SOTemplate. - except([&]() { - SOTemplate elements{{sfInvalid, soeREQUIRED}}; - }); + except( + [&]() { SOTemplate elements{{sfInvalid, soeREQUIRED}}; }); } { // Try to put the same SField into an SOTemplate twice. @@ -396,6 +394,7 @@ class STObject_test : public beast::unit_test::suite auto const& sf1Outer = sfSequence; auto const& sf2Outer = sfExpiration; auto const& sf3Outer = sfQualityIn; + auto const& sf4Outer = sfAmount; auto const& sf4 = sfSignature; auto const& sf5 = sfPublicKey; @@ -427,6 +426,7 @@ class STObject_test : public beast::unit_test::suite {sf1Outer, soeREQUIRED}, {sf2Outer, soeOPTIONAL}, {sf3Outer, soeDEFAULT}, + {sf4Outer, soeOPTIONAL}, {sf4, soeOPTIONAL}, {sf5, soeDEFAULT}, }; @@ -494,6 +494,16 @@ class STObject_test : public beast::unit_test::suite BEAST_EXPECT(st[sf1Outer] == 4); BEAST_EXPECT(st[sf2Outer] == 4); BEAST_EXPECT(st[sf2Outer] == st[sf1Outer]); + st[sf1Outer] += 1; + BEAST_EXPECT(st[sf1Outer] == 5); + st[sf4Outer] = STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{1}); + st[sf4Outer] += STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{2}); + st[sf1Outer] -= 1; + BEAST_EXPECT(st[sf1Outer] == 4); + st[sf4Outer] -= STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{1}); } // Write templated object @@ -542,6 +552,16 @@ class STObject_test : public beast::unit_test::suite BEAST_EXPECT(st[sf3Outer] == 0); BEAST_EXPECT(*st[~sf3Outer] == 0); BEAST_EXPECT(!!st[~sf3Outer]); + st[sf1Outer] += 1; + BEAST_EXPECT(st[sf1Outer] == 1); + st[sf4Outer] = STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{1}); + st[sf4Outer] += STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{2}); + st[sf1Outer] -= 1; + BEAST_EXPECT(st[sf1Outer] == 0); + st[sf4Outer] -= STAmount{1}; + BEAST_EXPECT(st[sf4Outer] == STAmount{1}); } // coercion operator to std::optional @@ -570,150 +590,152 @@ class STObject_test : public beast::unit_test::suite // STBlob and slice - {{STObject st(sfGeneric); - Buffer b(1); - BEAST_EXPECT(!b.empty()); - st[sf4] = std::move(b); - BEAST_EXPECT(b.empty()); - BEAST_EXPECT(Slice(st[sf4]).size() == 1); - st[~sf4] = std::nullopt; - BEAST_EXPECT(!~st[~sf4]); - b = Buffer{2}; - st[sf4] = Slice(b); - BEAST_EXPECT(b.size() == 2); - BEAST_EXPECT(Slice(st[sf4]).size() == 2); - st[sf5] = st[sf4]; - BEAST_EXPECT(Slice(st[sf4]).size() == 2); - BEAST_EXPECT(Slice(st[sf5]).size() == 2); - } - { - STObject st(sotOuter, sfGeneric); - BEAST_EXPECT(st[sf5] == Slice{}); - BEAST_EXPECT(!!st[~sf5]); - BEAST_EXPECT(!!~st[~sf5]); - Buffer b(1); - st[sf5] = std::move(b); - BEAST_EXPECT(b.empty()); - BEAST_EXPECT(Slice(st[sf5]).size() == 1); - st[~sf4] = std::nullopt; - BEAST_EXPECT(!~st[~sf4]); - } -} + { + { + STObject st(sfGeneric); + Buffer b(1); + BEAST_EXPECT(!b.empty()); + st[sf4] = std::move(b); + BEAST_EXPECT(b.empty()); + BEAST_EXPECT(Slice(st[sf4]).size() == 1); + st[~sf4] = std::nullopt; + BEAST_EXPECT(!~st[~sf4]); + b = Buffer{2}; + st[sf4] = Slice(b); + BEAST_EXPECT(b.size() == 2); + BEAST_EXPECT(Slice(st[sf4]).size() == 2); + st[sf5] = st[sf4]; + BEAST_EXPECT(Slice(st[sf4]).size() == 2); + BEAST_EXPECT(Slice(st[sf5]).size() == 2); + } + { + STObject st(sotOuter, sfGeneric); + BEAST_EXPECT(st[sf5] == Slice{}); + BEAST_EXPECT(!!st[~sf5]); + BEAST_EXPECT(!!~st[~sf5]); + Buffer b(1); + st[sf5] = std::move(b); + BEAST_EXPECT(b.empty()); + BEAST_EXPECT(Slice(st[sf5]).size() == 1); + st[~sf4] = std::nullopt; + BEAST_EXPECT(!~st[~sf4]); + } + } -// UDT blobs + // UDT blobs -{ - STObject st(sfGeneric); - BEAST_EXPECT(!st[~sf5]); - auto const kp = - generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase")); - st[sf5] = kp.first; - st[~sf5] = std::nullopt; -} + { + STObject st(sfGeneric); + BEAST_EXPECT(!st[~sf5]); + auto const kp = generateKeyPair( + KeyType::secp256k1, generateSeed("masterpassphrase")); + st[sf5] = kp.first; + st[~sf5] = std::nullopt; + } -// By reference fields + // By reference fields -{ - auto const& sf = sfIndexes; - STObject st(sfGeneric); - std::vector v; - v.emplace_back(1); - v.emplace_back(2); - st[sf] = v; - st[sf] = std::move(v); - auto const& cst = st; - BEAST_EXPECT(cst[sf].size() == 2); - BEAST_EXPECT(cst[~sf]->size() == 2); - BEAST_EXPECT(cst[sf][0] == 1); - BEAST_EXPECT(cst[sf][1] == 2); - static_assert( - std::is_same const&>:: - value, - ""); -} - -// Default by reference field + { + auto const& sf = sfIndexes; + STObject st(sfGeneric); + std::vector v; + v.emplace_back(1); + v.emplace_back(2); + st[sf] = v; + st[sf] = std::move(v); + auto const& cst = st; + BEAST_EXPECT(cst[sf].size() == 2); + BEAST_EXPECT(cst[~sf]->size() == 2); + BEAST_EXPECT(cst[sf][0] == 1); + BEAST_EXPECT(cst[sf][1] == 2); + static_assert( + std::is_same< + decltype(cst[sfIndexes]), + std::vector const&>::value, + ""); + } -{ - auto const& sf1 = sfIndexes; - auto const& sf2 = sfHashes; - auto const& sf3 = sfAmendments; - SOTemplate const sot{ - {sf1, soeREQUIRED}, - {sf2, soeOPTIONAL}, - {sf3, soeDEFAULT}, - }; - - STObject st(sot, sfGeneric); - auto const& cst(st); - BEAST_EXPECT(cst[sf1].size() == 0); - BEAST_EXPECT(!cst[~sf2]); - BEAST_EXPECT(cst[sf3].size() == 0); - std::vector v; - v.emplace_back(1); - st[sf1] = v; - BEAST_EXPECT(cst[sf1].size() == 1); - BEAST_EXPECT(cst[sf1][0] == uint256{1}); - st[sf2] = v; - BEAST_EXPECT(cst[sf2].size() == 1); - BEAST_EXPECT(cst[sf2][0] == uint256{1}); - st[~sf2] = std::nullopt; - BEAST_EXPECT(!st[~sf2]); - st[sf3] = v; - BEAST_EXPECT(cst[sf3].size() == 1); - BEAST_EXPECT(cst[sf3][0] == uint256{1}); - st[sf3] = std::vector{}; - BEAST_EXPECT(cst[sf3].size() == 0); -} -} // namespace ripple + // Default by reference field -void -testMalformed() -{ - testcase("Malformed serialized forms"); + { + auto const& sf1 = sfIndexes; + auto const& sf2 = sfHashes; + auto const& sf3 = sfAmendments; + SOTemplate const sot{ + {sf1, soeREQUIRED}, + {sf2, soeOPTIONAL}, + {sf3, soeDEFAULT}, + }; - try - { - std::array const payload{ - {0xe9, 0x12, 0xab, 0xcd, 0x12, 0xfe, 0xdc}}; - SerialIter sit{makeSlice(payload)}; - auto obj = std::make_shared(sit, sfMetadata); - BEAST_EXPECT(!obj); - } - catch (std::exception const& e) - { - BEAST_EXPECT(strcmp(e.what(), "Duplicate field detected") == 0); - } + STObject st(sot, sfGeneric); + auto const& cst(st); + BEAST_EXPECT(cst[sf1].size() == 0); + BEAST_EXPECT(!cst[~sf2]); + BEAST_EXPECT(cst[sf3].size() == 0); + std::vector v; + v.emplace_back(1); + st[sf1] = v; + BEAST_EXPECT(cst[sf1].size() == 1); + BEAST_EXPECT(cst[sf1][0] == uint256{1}); + st[sf2] = v; + BEAST_EXPECT(cst[sf2].size() == 1); + BEAST_EXPECT(cst[sf2][0] == uint256{1}); + st[~sf2] = std::nullopt; + BEAST_EXPECT(!st[~sf2]); + st[sf3] = v; + BEAST_EXPECT(cst[sf3].size() == 1); + BEAST_EXPECT(cst[sf3][0] == uint256{1}); + st[sf3] = std::vector{}; + BEAST_EXPECT(cst[sf3].size() == 0); + } + } // namespace ripple - try + void + testMalformed() { - std::array const payload{{0xe2, 0xe1, 0xe2}}; - SerialIter sit{makeSlice(payload)}; - auto obj = std::make_shared(sit, sfMetadata); - BEAST_EXPECT(!obj); + testcase("Malformed serialized forms"); + + try + { + std::array const payload{ + {0xe9, 0x12, 0xab, 0xcd, 0x12, 0xfe, 0xdc}}; + SerialIter sit{makeSlice(payload)}; + auto obj = std::make_shared(sit, sfMetadata); + BEAST_EXPECT(!obj); + } + catch (std::exception const& e) + { + BEAST_EXPECT(strcmp(e.what(), "Duplicate field detected") == 0); + } + + try + { + std::array const payload{{0xe2, 0xe1, 0xe2}}; + SerialIter sit{makeSlice(payload)}; + auto obj = std::make_shared(sit, sfMetadata); + BEAST_EXPECT(!obj); + } + catch (std::exception const& e) + { + BEAST_EXPECT(strcmp(e.what(), "Duplicate field detected") == 0); + } } - catch (std::exception const& e) + + void + run() override { - BEAST_EXPECT(strcmp(e.what(), "Duplicate field detected") == 0); + // Instantiate a jtx::Env so debugLog writes are exercised. + test::jtx::Env env(*this); + + testFields(); + testSerialization(); + testParseJSONArray(); + testParseJSONArrayWithInvalidChildrenObjects(); + testParseJSONEdgeCases(); + testMalformed(); } -} - -void -run() override -{ - // Instantiate a jtx::Env so debugLog writes are exercised. - test::jtx::Env env(*this); - - testFields(); - testSerialization(); - testParseJSONArray(); - testParseJSONArrayWithInvalidChildrenObjects(); - testParseJSONEdgeCases(); - testMalformed(); -} -} -; +}; BEAST_DEFINE_TESTSUITE(STObject, protocol, ripple); -} // ripple +} // namespace ripple diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index f41c283e3ab..54037eaa5ba 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -1361,6 +1361,31 @@ class STTx_test : public beast::unit_test::suite 0x10, 0x00, 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe}; + constexpr unsigned char payload4[] = { + 0x12, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1e, 0x00, + 0x4f, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, 0x00, 0x00, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe5, 0xfe, 0xf3, 0xe7, 0xe5, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, + 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x12, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x1e, 0x00, 0x4f, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, + 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x24, 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x54, 0x72, 0x61, 0x6e, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe, 0xf3, 0xe7, 0xe5, 0x65, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x20, + 0xf6, 0x00, 0x00, 0x03, 0x1f, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x73, 0x00, 0x81, 0x14, + 0x00, 0x10, 0x00, 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe}; + // Construct an STObject with 11 levels of object nesting so the // maximum nesting level exception is thrown. { @@ -1574,6 +1599,18 @@ class STTx_test : public beast::unit_test::suite fail("An exception should have been thrown"); } catch (std::exception const& ex) + { + BEAST_EXPECT( + strcmp(ex.what(), "gFID: uncommon name out of range 0") == 0); + } + + try + { + ripple::SerialIter sit{payload4}; + auto stx = std::make_shared(sit); + fail("An exception should have been thrown"); + } + catch (std::exception const& ex) { BEAST_EXPECT(strcmp(ex.what(), "Unknown field") == 0); } diff --git a/src/test/protocol/STValidation_test.cpp b/src/test/protocol/STValidation_test.cpp index 3435db22ca7..8efb5847488 100644 --- a/src/test/protocol/STValidation_test.cpp +++ b/src/test/protocol/STValidation_test.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/protocol/SecretKey_test.cpp b/src/test/protocol/SecretKey_test.cpp index 08a19124508..bce89bce734 100644 --- a/src/test/protocol/SecretKey_test.cpp +++ b/src/test/protocol/SecretKey_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/test/protocol/Seed_test.cpp b/src/test/protocol/Seed_test.cpp index 86cf449e1ca..3619e84541e 100644 --- a/src/test/protocol/Seed_test.cpp +++ b/src/test/protocol/Seed_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/protocol/SeqProxy_test.cpp b/src/test/protocol/SeqProxy_test.cpp index 99a29dfe000..5e44bca5b40 100644 --- a/src/test/protocol/SeqProxy_test.cpp +++ b/src/test/protocol/SeqProxy_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/protocol/Serializer_test.cpp b/src/test/protocol/Serializer_test.cpp new file mode 100644 index 00000000000..d707943856f --- /dev/null +++ b/src/test/protocol/Serializer_test.cpp @@ -0,0 +1,69 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include + +#include + +namespace ripple { + +struct Serializer_test : public beast::unit_test::suite +{ + void + run() override + { + { + std::initializer_list const values = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int32_t value : values) + { + Serializer s; + s.add32(value); + BEAST_EXPECT(s.size() == 4); + SerialIter sit(s.slice()); + BEAST_EXPECT(sit.geti32() == value); + } + } + { + std::initializer_list const values = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int64_t value : values) + { + Serializer s; + s.add64(value); + BEAST_EXPECT(s.size() == 8); + SerialIter sit(s.slice()); + BEAST_EXPECT(sit.geti64() == value); + } + } + } +}; + +BEAST_DEFINE_TESTSUITE(Serializer, protocol, ripple); + +} // namespace ripple diff --git a/src/test/protocol/TER_test.cpp b/src/test/protocol/TER_test.cpp index 3cf21db2c0a..a43fd8758ad 100644 --- a/src/test/protocol/TER_test.cpp +++ b/src/test/protocol/TER_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/test/protocol/types_test.cpp b/src/test/protocol/types_test.cpp index 2ae7d15ef5c..ac4314df640 100644 --- a/src/test/protocol/types_test.cpp +++ b/src/test/protocol/types_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/test/resource/Logic_test.cpp b/src/test/resource/Logic_test.cpp index 25379370f4d..b445890c326 100644 --- a/src/test/resource/Logic_test.cpp +++ b/src/test/resource/Logic_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index cfcb672c032..c1e059a3ead 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include #include #include #include +#include +#include #include @@ -37,6 +37,19 @@ class AMMInfo_test : public jtx::AMMTestBase testcase("Errors"); using namespace jtx; + + Account const bogie("bogie"); + enum TestAccount { None, Alice, Bogie }; + auto accountId = [&](AMM const& ammAlice, + TestAccount v) -> std::optional { + if (v == Alice) + return ammAlice.ammAccount(); + else if (v == Bogie) + return bogie; + else + return std::nullopt; + }; + // Invalid tokens pair testAMM([&](AMM& ammAlice, Env&) { Account const gw("gw"); @@ -48,36 +61,70 @@ class AMMInfo_test : public jtx::AMMTestBase // Invalid LP account id testAMM([&](AMM& ammAlice, Env&) { - Account bogie("bogie"); auto const jv = ammAlice.ammRpcInfo(bogie.id()); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); + std::vector, + std::optional, + TestAccount, + bool>> const invalidParams = { + {xrpIssue(), std::nullopt, None, false}, + {std::nullopt, USD.issue(), None, false}, + {xrpIssue(), std::nullopt, Alice, false}, + {std::nullopt, USD.issue(), Alice, false}, + {xrpIssue(), USD.issue(), Alice, false}, + {std::nullopt, std::nullopt, None, true}}; + // Invalid parameters testAMM([&](AMM& ammAlice, Env&) { - std::vector, - std::optional, - std::optional, - bool>> - vals = { - {xrpIssue(), std::nullopt, std::nullopt, false}, - {std::nullopt, USD.issue(), std::nullopt, false}, - {xrpIssue(), std::nullopt, ammAlice.ammAccount(), false}, - {std::nullopt, USD.issue(), ammAlice.ammAccount(), false}, - {xrpIssue(), USD.issue(), ammAlice.ammAccount(), false}, - {std::nullopt, std::nullopt, std::nullopt, true}}; - for (auto const& [iss1, iss2, acct, ignoreParams] : vals) + for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) { auto const jv = ammAlice.ammRpcInfo( - std::nullopt, std::nullopt, iss1, iss2, acct, ignoreParams); + std::nullopt, + std::nullopt, + iss1, + iss2, + accountId(ammAlice, acct), + ignoreParams); BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); } }); + // Invalid parameters *and* invalid LP account, default API version + testAMM([&](AMM& ammAlice, Env&) { + for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) + { + auto const jv = ammAlice.ammRpcInfo( + bogie, // + std::nullopt, + iss1, + iss2, + accountId(ammAlice, acct), + ignoreParams); + BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); + } + }); + + // Invalid parameters *and* invalid LP account, API version 3 + testAMM([&](AMM& ammAlice, Env&) { + for (auto const& [iss1, iss2, acct, ignoreParams] : invalidParams) + { + auto const jv = ammAlice.ammRpcInfo( + bogie, // + std::nullopt, + iss1, + iss2, + accountId(ammAlice, acct), + ignoreParams, + 3); + BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); + } + }); + // Invalid AMM account id testAMM([&](AMM& ammAlice, Env&) { - Account bogie("bogie"); auto const jv = ammAlice.ammRpcInfo( std::nullopt, std::nullopt, @@ -86,6 +133,54 @@ class AMMInfo_test : public jtx::AMMTestBase bogie.id()); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); + + std::vector, + std::optional, + TestAccount, + bool>> const invalidParamsBadAccount = { + {xrpIssue(), std::nullopt, None, false}, + {std::nullopt, USD.issue(), None, false}, + {xrpIssue(), std::nullopt, Bogie, false}, + {std::nullopt, USD.issue(), Bogie, false}, + {xrpIssue(), USD.issue(), Bogie, false}, + {std::nullopt, std::nullopt, None, true}}; + + // Invalid parameters *and* invalid AMM account, default API version + testAMM([&](AMM& ammAlice, Env&) { + for (auto const& [iss1, iss2, acct, ignoreParams] : + invalidParamsBadAccount) + { + auto const jv = ammAlice.ammRpcInfo( + std::nullopt, + std::nullopt, + iss1, + iss2, + accountId(ammAlice, acct), + ignoreParams); + BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); + } + }); + + // Invalid parameters *and* invalid AMM account, API version 3 + testAMM([&](AMM& ammAlice, Env&) { + for (auto const& [iss1, iss2, acct, ignoreParams] : + invalidParamsBadAccount) + { + auto const jv = ammAlice.ammRpcInfo( + std::nullopt, + std::nullopt, + iss1, + iss2, + accountId(ammAlice, acct), + ignoreParams, + 3); + BEAST_EXPECT( + jv[jss::error_message] == + (acct == Bogie ? std::string("Account malformed.") + : std::string("Invalid parameters."))); + } + }); } void @@ -221,6 +316,24 @@ class AMMInfo_test : public jtx::AMMTestBase }); } + void + testInvalidAmmField() + { + using namespace jtx; + testcase("Invalid amm field"); + + testAMM([&](AMM& amm, Env&) { + auto const resp = amm.ammRpcInfo( + std::nullopt, + jss::validated.c_str(), + std::nullopt, + std::nullopt, + gw); + BEAST_EXPECT( + resp.isMember("error") && resp["error"] == "actNotFound"); + }); + } + void run() override { @@ -228,6 +341,7 @@ class AMMInfo_test : public jtx::AMMTestBase testSimpleRpc(); testVoteAndBid(); testFreeze(); + testInvalidAmmField(); } }; diff --git a/src/test/rpc/AccountCurrencies_test.cpp b/src/test/rpc/AccountCurrencies_test.cpp index 64b73743024..7d16ca48a93 100644 --- a/src/test/rpc/AccountCurrencies_test.cpp +++ b/src/test/rpc/AccountCurrencies_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 2d6832cee05..cb9712aef56 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include #include +#include +#include -#include -#include -#include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/AccountLines_test.cpp b/src/test/rpc/AccountLines_test.cpp index c02cc25e1a8..d104ea14b0a 100644 --- a/src/test/rpc/AccountLines_test.cpp +++ b/src/test/rpc/AccountLines_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 46b092c4a6d..7326fff0c76 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -17,13 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include #include @@ -578,11 +580,11 @@ class AccountObjects_test : public beast::unit_test::suite Env env(*this, features); // Make a lambda we can use to get "account_objects" easily. - auto acct_objs = [&env]( - AccountID const& acct, - std::optional const& type, - std::optional limit = std::nullopt, - std::optional marker = std::nullopt) { + auto acctObjs = [&env]( + AccountID const& acct, + std::optional const& type, + std::optional limit = std::nullopt, + std::optional marker = std::nullopt) { Json::Value params; params[jss::account] = to_string(acct); if (type) @@ -596,32 +598,42 @@ class AccountObjects_test : public beast::unit_test::suite }; // Make a lambda that easily identifies the size of account objects. - auto acct_objs_is_size = [](Json::Value const& resp, unsigned size) { + auto acctObjsIsSize = [](Json::Value const& resp, unsigned size) { return resp[jss::result][jss::account_objects].isArray() && (resp[jss::result][jss::account_objects].size() == size); }; + // Make a lambda that checks if the response has error for invalid type + auto acctObjsTypeIsInvalid = [](Json::Value const& resp) { + return resp[jss::result].isMember(jss::error) && + resp[jss::result][jss::error_message] == + "Invalid field \'type\'."; + }; + env.fund(XRP(10000), gw, alice); env.close(); // Since the account is empty now, all account objects should come // back empty. - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::account), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amendments), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::check), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::deposit_preauth), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::directory), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::escrow), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::fee), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::hashes), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::nft_page), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::offer), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::payment_channel), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::signer_list), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::state), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::ticket), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::did), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::check), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::deposit_preauth), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::escrow), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::nft_page), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::offer), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::payment_channel), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::signer_list), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::state), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::ticket), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0)); + + // we expect invalid field type reported for the following types + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL))); // gw mints an NFT so we can find it. uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)}; @@ -629,8 +641,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the NFToken page and make sure it's the right one. - Json::Value const resp = acct_objs(gw, jss::nft_page); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::nft_page); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& nftPage = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(nftPage[sfNFTokens.jsonName].size() == 1); @@ -646,8 +658,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the trustline and make sure it's the right one. - Json::Value const resp = acct_objs(gw, jss::state); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::state); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& state = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(state[sfBalance.jsonName][jss::value].asInt() == -5); @@ -659,8 +671,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the check. - Json::Value const resp = acct_objs(gw, jss::check); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::check); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& check = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(check[sfAccount.jsonName] == gw.human()); @@ -672,8 +684,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the preauthorization. - Json::Value const resp = acct_objs(gw, jss::deposit_preauth); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::deposit_preauth); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& preauth = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(preauth[sfAccount.jsonName] == gw.human()); @@ -694,8 +706,8 @@ class AccountObjects_test : public beast::unit_test::suite } { // Find the escrow. - Json::Value const resp = acct_objs(gw, jss::escrow); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::escrow); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& escrow = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human()); @@ -708,7 +720,7 @@ class AccountObjects_test : public beast::unit_test::suite Env scEnv(*this, envconfig(port_increment, 3), features); x.createScBridgeObjects(scEnv); - auto scenv_acct_objs = [&](Account const& acct, char const* type) { + auto scEnvAcctObjs = [&](Account const& acct, char const* type) { Json::Value params; params[jss::account] = acct.human(); params[jss::type] = type; @@ -717,9 +729,9 @@ class AccountObjects_test : public beast::unit_test::suite }; Json::Value const resp = - scenv_acct_objs(Account::master, jss::bridge); + scEnvAcctObjs(Account::master, jss::bridge); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& acct_bridge = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT( @@ -755,7 +767,7 @@ class AccountObjects_test : public beast::unit_test::suite scEnv(xchain_create_claim_id(x.scBob, x.jvb, x.reward, x.mcBob)); scEnv.close(); - auto scenv_acct_objs = [&](Account const& acct, char const* type) { + auto scEnvAcctObjs = [&](Account const& acct, char const* type) { Json::Value params; params[jss::account] = acct.human(); params[jss::type] = type; @@ -766,8 +778,8 @@ class AccountObjects_test : public beast::unit_test::suite { // Find the xchain sequence number for Andrea. Json::Value const resp = - scenv_acct_objs(x.scAlice, jss::xchain_owned_claim_id); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + scEnvAcctObjs(x.scAlice, jss::xchain_owned_claim_id); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& xchain_seq = resp[jss::result][jss::account_objects][0u]; @@ -779,8 +791,8 @@ class AccountObjects_test : public beast::unit_test::suite { // and the one for Bob Json::Value const resp = - scenv_acct_objs(x.scBob, jss::xchain_owned_claim_id); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + scEnvAcctObjs(x.scBob, jss::xchain_owned_claim_id); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& xchain_seq = resp[jss::result][jss::account_objects][0u]; @@ -812,7 +824,7 @@ class AccountObjects_test : public beast::unit_test::suite x.signers[0])); scEnv.close(); - auto scenv_acct_objs = [&](Account const& acct, char const* type) { + auto scEnvAcctObjs = [&](Account const& acct, char const* type) { Json::Value params; params[jss::account] = acct.human(); params[jss::type] = type; @@ -822,9 +834,9 @@ class AccountObjects_test : public beast::unit_test::suite { // Find the xchain_create_account_claim_id - Json::Value const resp = scenv_acct_objs( + Json::Value const resp = scEnvAcctObjs( Account::master, jss::xchain_owned_create_account_claim_id); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& xchain_create_account_claim_id = resp[jss::result][jss::account_objects][0u]; @@ -843,8 +855,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the offer. - Json::Value const resp = acct_objs(gw, jss::offer); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::offer); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& offer = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human()); @@ -868,8 +880,8 @@ class AccountObjects_test : public beast::unit_test::suite } { // Find the payment channel. - Json::Value const resp = acct_objs(gw, jss::payment_channel); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::payment_channel); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& payChan = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human()); @@ -890,8 +902,8 @@ class AccountObjects_test : public beast::unit_test::suite } { // Find the DID. - Json::Value const resp = acct_objs(gw, jss::did); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::did); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& did = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(did[sfAccount.jsonName] == gw.human()); @@ -902,8 +914,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the signer list. - Json::Value const resp = acct_objs(gw, jss::signer_list); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::signer_list); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& signerList = resp[jss::result][jss::account_objects][0u]; @@ -918,8 +930,8 @@ class AccountObjects_test : public beast::unit_test::suite env.close(); { // Find the ticket. - Json::Value const resp = acct_objs(gw, jss::ticket); - BEAST_EXPECT(acct_objs_is_size(resp, 1)); + Json::Value const resp = acctObjs(gw, jss::ticket); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); auto const& ticket = resp[jss::result][jss::account_objects][0u]; BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human()); @@ -947,7 +959,7 @@ class AccountObjects_test : public beast::unit_test::suite std::uint32_t const expectedAccountObjects{ static_cast(std::size(expectedLedgerTypes))}; - if (BEAST_EXPECT(acct_objs_is_size(resp, expectedAccountObjects))) + if (BEAST_EXPECT(acctObjsIsSize(resp, expectedAccountObjects))) { auto const& aobjs = resp[jss::result][jss::account_objects]; std::vector gotLedgerTypes; @@ -970,7 +982,7 @@ class AccountObjects_test : public beast::unit_test::suite params[jss::type] = jss::escrow; auto resp = env.rpc("json", "account_objects", to_string(params)); - if (BEAST_EXPECT(acct_objs_is_size(resp, 1u))) + if (BEAST_EXPECT(acctObjsIsSize(resp, 1u))) { auto const& aobjs = resp[jss::result][jss::account_objects]; BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow); @@ -991,7 +1003,7 @@ class AccountObjects_test : public beast::unit_test::suite auto expectObjects = [&](Json::Value const& resp, std::vector const& types) -> bool { - if (!acct_objs_is_size(resp, types.size())) + if (!acctObjsIsSize(resp, types.size())) return false; std::vector typesOut; getTypes(resp, typesOut); @@ -1005,13 +1017,13 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(lines[jss::lines].size() == 3); // request AMM only, doesn't depend on the limit BEAST_EXPECT( - acct_objs_is_size(acct_objs(amm.ammAccount(), jss::amm), 1)); + acctObjsIsSize(acctObjs(amm.ammAccount(), jss::amm), 1)); // request first two objects - auto resp = acct_objs(amm.ammAccount(), std::nullopt, 2); + auto resp = acctObjs(amm.ammAccount(), std::nullopt, 2); std::vector typesOut; getTypes(resp, typesOut); // request next two objects - resp = acct_objs( + resp = acctObjs( amm.ammAccount(), std::nullopt, 10, @@ -1025,7 +1037,7 @@ class AccountObjects_test : public beast::unit_test::suite jss::RippleState.c_str(), jss::RippleState.c_str()})); // filter by state: there are three trustlines - resp = acct_objs(amm.ammAccount(), jss::state, 10); + resp = acctObjs(amm.ammAccount(), jss::state, 10); BEAST_EXPECT(expectObjects( resp, {jss::RippleState.c_str(), @@ -1033,11 +1045,18 @@ class AccountObjects_test : public beast::unit_test::suite jss::RippleState.c_str()})); // AMM account doesn't own offers BEAST_EXPECT( - acct_objs_is_size(acct_objs(amm.ammAccount(), jss::offer), 0)); + acctObjsIsSize(acctObjs(amm.ammAccount(), jss::offer), 0)); // gw account doesn't own AMM object - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0)); } + // we still expect invalid field type reported for the following types + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes))); + BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL))); + // Run up the number of directory entries so gw has two // directory nodes. for (int d = 1'000'032; d >= 1'000'000; --d) @@ -1047,11 +1066,129 @@ class AccountObjects_test : public beast::unit_test::suite } // Verify that the non-returning types still don't return anything. - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::account), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amendments), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::directory), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::fee), 0)); - BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::hashes), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0)); + } + + void + testNFTsMarker() + { + // there's some bug found in account_nfts method that it did not + // return invalid params when providing unassociated nft marker. + // this test tests both situations when providing valid nft marker + // and unassociated nft marker. + testcase("NFTsMarker"); + + using namespace jtx; + Env env(*this); + + Account const bob{"bob"}; + env.fund(XRP(10000), bob); + + static constexpr unsigned nftsSize = 10; + for (unsigned i = 0; i < nftsSize; i++) + { + env(token::mint(bob, 0)); + } + + env.close(); + + // save the NFTokenIDs to use later + std::vector tokenIDs; + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::ledger_index] = "validated"; + Json::Value const resp = + env.rpc("json", "account_nfts", to_string(params)); + Json::Value const& nfts = resp[jss::result][jss::account_nfts]; + for (Json::Value const& nft : nfts) + tokenIDs.push_back(nft["NFTokenID"]); + } + + // this lambda function is used to check if the account_nfts method + // returns the correct token information. lastIndex is used to query the + // last marker. + auto compareNFTs = [&tokenIDs, &env, &bob]( + unsigned const limit, unsigned const lastIndex) { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::marker] = tokenIDs[lastIndex]; + params[jss::ledger_index] = "validated"; + Json::Value const resp = + env.rpc("json", "account_nfts", to_string(params)); + + if (resp[jss::result].isMember(jss::error)) + return false; + + Json::Value const& nfts = resp[jss::result][jss::account_nfts]; + unsigned const nftsCount = tokenIDs.size() - lastIndex - 1 < limit + ? tokenIDs.size() - lastIndex - 1 + : limit; + + if (nfts.size() != nftsCount) + return false; + + for (unsigned i = 0; i < nftsCount; i++) + { + if (nfts[i]["NFTokenID"] != tokenIDs[lastIndex + 1 + i]) + return false; + } + + return true; + }; + + // test a valid marker which is equal to the third tokenID + BEAST_EXPECT(compareNFTs(4, 2)); + + // test a valid marker which is equal to the 8th tokenID + BEAST_EXPECT(compareNFTs(4, 7)); + + // lambda that holds common code for invalid cases. + auto testInvalidMarker = [&env, &bob]( + auto marker, char const* errorMessage) { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = 4; + params[jss::ledger_index] = jss::validated; + params[jss::marker] = marker; + Json::Value const resp = + env.rpc("json", "account_nfts", to_string(params)); + return resp[jss::result][jss::error_message] == errorMessage; + }; + + // test an invalid marker that is not a string + BEAST_EXPECT( + testInvalidMarker(17, "Invalid field \'marker\', not string.")); + + // test an invalid marker that has a non-hex character + BEAST_EXPECT(testInvalidMarker( + "00000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B900000000000000000G", + "Invalid field \'marker\'.")); + + // this lambda function is used to create some fake marker using given + // taxon and sequence because we want to test some unassociated markers + // later + auto createFakeNFTMarker = [](AccountID const& issuer, + std::uint32_t taxon, + std::uint32_t tokenSeq, + std::uint16_t flags = 0, + std::uint16_t fee = 0) { + // the marker has the exact same format as an NFTokenID + return to_string(NFTokenMint::createNFTokenID( + flags, fee, issuer, nft::toTaxon(taxon), tokenSeq)); + }; + + // test an unassociated marker which does not exist in the NFTokenIDs + BEAST_EXPECT(testInvalidMarker( + createFakeNFTMarker(bob.id(), 0x000000000, 0x00000000), + "Invalid field \'marker\'.")); + + // test an unassociated marker which exceeds the maximum value of the + // existing NFTokenID + BEAST_EXPECT(testInvalidMarker( + createFakeNFTMarker(bob.id(), 0xFFFFFFFF, 0xFFFFFFFF), + "Invalid field \'marker\'.")); } void @@ -1083,6 +1220,173 @@ class AccountObjects_test : public beast::unit_test::suite } } + void + testAccountObjectMarker() + { + testcase("AccountObjectMarker"); + + using namespace jtx; + Env env(*this); + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + env.fund(XRP(10000), alice, bob, carol); + + unsigned const accountObjectSize = 30; + for (unsigned i = 0; i < accountObjectSize; i++) + env(check::create(alice, bob, XRP(10))); + + for (unsigned i = 0; i < 10; i++) + env(token::mint(carol, 0)); + + env.close(); + + unsigned const limit = 11; + Json::Value marker; + + // test account_objects with a limit and update marker + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::ledger_index] = "validated"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& accountObjects = resp[jss::result][jss::account_objects]; + marker = resp[jss::result][jss::marker]; + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(accountObjects.size() == limit); + } + + // test account_objects with valid marker and update marker + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::marker] = marker; + params[jss::ledger_index] = "validated"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& accountObjects = resp[jss::result][jss::account_objects]; + marker = resp[jss::result][jss::marker]; + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(accountObjects.size() == limit); + } + + // this lambda function is used to check invalid marker response. + auto testInvalidMarker = [&](std::string& marker) { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::ledger_index] = jss::validated; + params[jss::marker] = marker; + Json::Value const resp = + env.rpc("json", "account_objects", to_string(params)); + return resp[jss::result][jss::error_message] == + "Invalid field \'marker\'."; + }; + + auto const markerStr = marker.asString(); + auto const& idx = markerStr.find(','); + auto const dirIndex = markerStr.substr(0, idx); + auto const entryIndex = markerStr.substr(idx + 1); + + // test account_objects with an invalid marker that contains no ',' + { + std::string s = dirIndex + entryIndex; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test invalid marker by adding invalid string after the maker: + // "dirIndex,entryIndex,1234" + { + std::string s = markerStr + ",1234"; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test account_objects with an invalid marker containing invalid + // dirIndex by replacing some characters from the dirIndex. + { + std::string s = markerStr; + s.replace(0, 7, "FFFFFFF"); + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test account_objects with an invalid marker containing invalid + // entryIndex by replacing some characters from the entryIndex. + { + std::string s = entryIndex; + s.replace(0, 7, "FFFFFFF"); + s = dirIndex + ',' + s; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test account_objects with an invalid marker containing invalid + // dirIndex with marker: ",entryIndex" + { + std::string s = ',' + entryIndex; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test account_objects with marker: "0,entryIndex", this is still + // valid, because when dirIndex = 0, we will use root key to find + // dir. + { + std::string s = "0," + entryIndex; + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::marker] = s; + params[jss::ledger_index] = "validated"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& accountObjects = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(accountObjects.size() == limit); + } + + // test account_objects with an invalid marker containing invalid + // entryIndex with marker: "dirIndex," + { + std::string s = dirIndex + ','; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // test account_objects with an invalid marker containing invalid + // entryIndex with marker: "dirIndex,0" + { + std::string s = dirIndex + ",0"; + BEAST_EXPECT(testInvalidMarker(s)); + } + + // continue getting account_objects with valid marker. This will be the + // last page, so response will not contain any marker. + { + Json::Value params; + params[jss::account] = bob.human(); + params[jss::limit] = limit; + params[jss::marker] = marker; + params[jss::ledger_index] = "validated"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& accountObjects = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT( + accountObjects.size() == accountObjectSize - limit * 2); + BEAST_EXPECT(!resp[jss::result].isMember(jss::marker)); + } + + // test account_objects when the account only have nft pages, but + // provided invalid entry index. + { + Json::Value params; + params[jss::account] = carol.human(); + params[jss::limit] = 10; + params[jss::marker] = "0," + entryIndex; + params[jss::ledger_index] = "validated"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + auto& accountObjects = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(accountObjects.size() == 0); + } + } + void run() override { @@ -1090,7 +1394,9 @@ class AccountObjects_test : public beast::unit_test::suite testUnsteppedThenStepped(); testUnsteppedThenSteppedWithNFTs(); testObjectTypes(); + testNFTsMarker(); testAccountNFTs(); + testAccountObjectMarker(); } }; diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 635dd1a63b4..da6acc97e98 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index 3fa214c5b4e..e5475e3f530 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 2cec59d7198..f6a9225ec48 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include diff --git a/src/test/rpc/AmendmentBlocked_test.cpp b/src/test/rpc/AmendmentBlocked_test.cpp index a90bcdcd0c4..196ce0e463d 100644 --- a/src/test/rpc/AmendmentBlocked_test.cpp +++ b/src/test/rpc/AmendmentBlocked_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/BookChanges_test.cpp b/src/test/rpc/BookChanges_test.cpp new file mode 100644 index 00000000000..95997538d79 --- /dev/null +++ b/src/test/rpc/BookChanges_test.cpp @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +namespace ripple { +namespace test { + +class BookChanges_test : public beast::unit_test::suite +{ +public: + void + testConventionalLedgerInputStrings() + { + testcase("Specify well-known strings as ledger input"); + jtx::Env env(*this); + Json::Value params, resp; + + // As per convention in XRPL, ledgers can be specified with strings + // "closed", "validated" or "current" + params["ledger"] = "validated"; + resp = env.rpc("json", "book_changes", to_string(params)); + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(resp[jss::result][jss::status] == "success"); + BEAST_EXPECT(resp[jss::result][jss::validated] == true); + + params["ledger"] = "current"; + resp = env.rpc("json", "book_changes", to_string(params)); + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(resp[jss::result][jss::status] == "success"); + BEAST_EXPECT(resp[jss::result][jss::validated] == false); + + params["ledger"] = "closed"; + resp = env.rpc("json", "book_changes", to_string(params)); + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(resp[jss::result][jss::status] == "success"); + + // In the unit-test framework, requesting for "closed" ledgers appears + // to yield "validated" ledgers. This is not new behavior. It is also + // observed in the unit tests for the LedgerHeader class. + BEAST_EXPECT(resp[jss::result][jss::validated] == true); + + // non-conventional ledger input should throw an error + params["ledger"] = "non_conventional_ledger_input"; + resp = env.rpc("json", "book_changes", to_string(params)); + BEAST_EXPECT(resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(resp[jss::result][jss::status] != "success"); + } + + void + testLedgerInputDefaultBehavior() + { + testcase( + "If ledger_hash or ledger_index is not specified, the behavior " + "must default to the `current` ledger"); + jtx::Env env(*this); + + // As per convention in XRPL, ledgers can be specified with strings + // "closed", "validated" or "current" + Json::Value const resp = + env.rpc("json", "book_changes", to_string(Json::Value{})); + BEAST_EXPECT(!resp[jss::result].isMember(jss::error)); + BEAST_EXPECT(resp[jss::result][jss::status] == "success"); + + // I dislike asserting the below statement, because its dependent on the + // unit-test framework BEAST_EXPECT(resp[jss::result][jss::ledger_index] + // == 3); + } + + void + run() override + { + testConventionalLedgerInputStrings(); + testLedgerInputDefaultBehavior(); + + // Note: Other aspects of the book_changes rpc are fertile grounds for + // unit-testing purposes. It can be included in future work + } +}; + +BEAST_DEFINE_TESTSUITE(BookChanges, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index fcc0017f539..6bcc0c20809 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -15,12 +15,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/DeliveredAmount_test.cpp b/src/test/rpc/DeliveredAmount_test.cpp index c17bd0c025d..9ed858e2a33 100644 --- a/src/test/rpc/DeliveredAmount_test.cpp +++ b/src/test/rpc/DeliveredAmount_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/DepositAuthorized_test.cpp b/src/test/rpc/DepositAuthorized_test.cpp index 42b871e31e5..46637d421e1 100644 --- a/src/test/rpc/DepositAuthorized_test.cpp +++ b/src/test/rpc/DepositAuthorized_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include namespace ripple { namespace test { @@ -31,13 +31,22 @@ class DepositAuthorized_test : public beast::unit_test::suite depositAuthArgs( jtx::Account const& source, jtx::Account const& dest, - std::string const& ledger = "") + std::string const& ledger = "", + std::vector const& credentials = {}) { Json::Value args{Json::objectValue}; args[jss::source_account] = source.human(); args[jss::destination_account] = dest.human(); if (!ledger.empty()) args[jss::ledger_index] = ledger; + + if (!credentials.empty()) + { + auto& arr(args[jss::credentials] = Json::arrayValue); + for (auto const& s : credentials) + arr.append(s); + } + return args; } @@ -128,7 +137,7 @@ class DepositAuthorized_test : public beast::unit_test::suite depositAuthArgs(carol, becky).toStyledString()), false); - // becky clears the the DepositAuth flag so carol becomes authorized. + // becky clears the DepositAuth flag so carol becomes authorized. env(fclear(becky, asfDepositAuth)); env.close(); @@ -276,11 +285,351 @@ class DepositAuthorized_test : public beast::unit_test::suite } } + void + checkCredentialsResponse( + Json::Value const& result, + jtx::Account const& src, + jtx::Account const& dst, + bool authorized, + std::vector credentialIDs = {}, + std::string_view error = "") + { + BEAST_EXPECT( + result[jss::status] == authorized ? jss::success : jss::error); + if (result.isMember(jss::deposit_authorized)) + BEAST_EXPECT(result[jss::deposit_authorized] == authorized); + if (authorized) + BEAST_EXPECT( + result.isMember(jss::deposit_authorized) && + (result[jss::deposit_authorized] == true)); + + BEAST_EXPECT(result.isMember(jss::error) == !error.empty()); + if (!error.empty()) + BEAST_EXPECT(result[jss::error].asString() == error); + + if (authorized) + { + BEAST_EXPECT(result[jss::source_account] == src.human()); + BEAST_EXPECT(result[jss::destination_account] == dst.human()); + + for (unsigned i = 0; i < credentialIDs.size(); ++i) + BEAST_EXPECT(result[jss::credentials][i] == credentialIDs[i]); + } + else + { + BEAST_EXPECT(result[jss::request].isObject()); + + auto const& request = result[jss::request]; + BEAST_EXPECT(request[jss::command] == jss::deposit_authorized); + BEAST_EXPECT(request[jss::source_account] == src.human()); + BEAST_EXPECT(request[jss::destination_account] == dst.human()); + + for (unsigned i = 0; i < credentialIDs.size(); ++i) + BEAST_EXPECT(request[jss::credentials][i] == credentialIDs[i]); + } + } + + void + testCredentials() + { + using namespace jtx; + + const char credType[] = "abcde"; + + Account const alice{"alice"}; + Account const becky{"becky"}; + Account const diana{"diana"}; + Account const carol{"carol"}; + + Env env(*this); + env.fund(XRP(1000), alice, becky, carol, diana); + env.close(); + + // carol recognize alice + env(credentials::create(alice, carol, credType)); + env.close(); + // retrieve the index of the credentials + auto const jv = credentials::ledgerEntry(env, alice, carol, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // becky sets the DepositAuth flag in the current ledger. + env(fset(becky, asfDepositAuth)); + env.close(); + + // becky authorize any account recognized by carol to make a payment + env(deposit::authCredentials(becky, {{carol, credType}})); + env.close(); + + { + testcase( + "deposit_authorized with credentials failed: empty array."); + + auto args = depositAuthArgs(alice, becky, "validated"); + args[jss::credentials] = Json::arrayValue; + + auto const jv = + env.rpc("json", "deposit_authorized", args.toStyledString()); + checkCredentialsResponse( + jv[jss::result], alice, becky, false, {}, "invalidParams"); + } + + { + testcase( + "deposit_authorized with credentials failed: not a string " + "credentials"); + + auto args = depositAuthArgs(alice, becky, "validated"); + args[jss::credentials] = Json::arrayValue; + args[jss::credentials].append(1); + args[jss::credentials].append(3); + + auto const jv = + env.rpc("json", "deposit_authorized", args.toStyledString()); + checkCredentialsResponse( + jv[jss::result], alice, becky, false, {}, "invalidParams"); + } + + { + testcase( + "deposit_authorized with credentials failed: not a hex string " + "credentials"); + + auto args = depositAuthArgs(alice, becky, "validated"); + args[jss::credentials] = Json::arrayValue; + args[jss::credentials].append("hello world"); + + auto const jv = + env.rpc("json", "deposit_authorized", args.toStyledString()); + checkCredentialsResponse( + jv[jss::result], + alice, + becky, + false, + {"hello world"}, + "invalidParams"); + } + + { + testcase( + "deposit_authorized with credentials failed: not a credential " + "index"); + + auto args = depositAuthArgs( + alice, + becky, + "validated", + {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516" + "473"}); + + auto const jv = + env.rpc("json", "deposit_authorized", args.toStyledString()); + checkCredentialsResponse( + jv[jss::result], + alice, + becky, + false, + {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516" + "473"}, + "badCredentials"); + } + + { + testcase( + "deposit_authorized with credentials not authorized: " + "credential not accepted"); + auto const jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", {credIdx}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], + alice, + becky, + false, + {credIdx}, + "badCredentials"); + } + + // alice accept credentials + env(credentials::accept(alice, carol, credType)); + env.close(); + + { + testcase("deposit_authorized with duplicates in credentials"); + auto const jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", {credIdx, credIdx}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], + alice, + becky, + false, + {credIdx, credIdx}, + "badCredentials"); + } + + { + static const std::vector credIds = { + "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4", + "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4"}; + assert(credIds.size() > maxCredentialsArraySize); + + testcase("deposit_authorized too long credentials"); + auto const jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", credIds) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], alice, becky, false, credIds, "invalidParams"); + } + + { + testcase("deposit_authorized with credentials"); + auto const jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", {credIdx}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], alice, becky, true, {credIdx}); + } + + { + // diana recognize becky + env(credentials::create(becky, diana, credType)); + env.close(); + env(credentials::accept(becky, diana, credType)); + env.close(); + + // retrieve the index of the credentials + auto jv = credentials::ledgerEntry(env, becky, diana, credType); + std::string const credBecky = + jv[jss::result][jss::index].asString(); + + testcase("deposit_authorized account without preauth"); + jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(becky, alice, "validated", {credBecky}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], becky, alice, true, {credBecky}); + } + + { + // carol recognize diana + env(credentials::create(diana, carol, credType)); + env.close(); + env(credentials::accept(diana, carol, credType)); + env.close(); + // retrieve the index of the credentials + auto jv = credentials::ledgerEntry(env, alice, carol, credType); + std::string const credDiana = + jv[jss::result][jss::index].asString(); + + // alice try to use credential for different account + jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(becky, alice, "validated", {credDiana}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], + becky, + alice, + false, + {credDiana}, + "badCredentials"); + } + + { + testcase("deposit_authorized with expired credentials"); + + // check expired credentials + const char credType2[] = "fghijk"; + std::uint32_t const x = env.current() + ->info() + .parentCloseTime.time_since_epoch() + .count() + + 40; + + // create credentials with expire time 40s + auto jv = credentials::create(alice, carol, credType2); + jv[sfExpiration.jsonName] = x; + env(jv); + env.close(); + env(credentials::accept(alice, carol, credType2)); + env.close(); + jv = credentials::ledgerEntry(env, alice, carol, credType2); + std::string const credIdx2 = jv[jss::result][jss::index].asString(); + + // becky sets the DepositAuth flag in the current ledger. + env(fset(becky, asfDepositAuth)); + env.close(); + + // becky authorize any account recognized by carol to make a payment + env(deposit::authCredentials(becky, {{carol, credType2}})); + env.close(); + + { + // this should be fine + jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", {credIdx2}) + .toStyledString()); + checkCredentialsResponse( + jv[jss::result], alice, becky, true, {credIdx2}); + } + + // increase timer by 20s + env.close(); + env.close(); + { + // now credentials expired + jv = env.rpc( + "json", + "deposit_authorized", + depositAuthArgs(alice, becky, "validated", {credIdx2}) + .toStyledString()); + + checkCredentialsResponse( + jv[jss::result], + alice, + becky, + false, + {credIdx2}, + "badCredentials"); + } + } + } + void run() override { testValid(); testErrors(); + testCredentials(); } }; diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index fd63aee98e2..12d4b27745c 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { @@ -31,34 +31,75 @@ class Feature_test : public beast::unit_test::suite { testcase("internals"); - std::map const& supported = - ripple::detail::supportedAmendments(); + auto const& supportedAmendments = ripple::detail::supportedAmendments(); + auto const& allAmendments = ripple::allAmendments(); + BEAST_EXPECT( - supported.size() == + supportedAmendments.size() == ripple::detail::numDownVotedAmendments() + ripple::detail::numUpVotedAmendments()); - std::size_t up = 0, down = 0, obsolete = 0; - for (std::pair const& amendment : - supported) { - switch (amendment.second) + std::size_t up = 0, down = 0, obsolete = 0; + for (auto const& [name, vote] : supportedAmendments) { - case VoteBehavior::DefaultYes: - ++up; - break; - case VoteBehavior::DefaultNo: - ++down; - break; - case VoteBehavior::Obsolete: - ++obsolete; - break; - default: - fail("Unknown VoteBehavior", __FILE__, __LINE__); + switch (vote) + { + case VoteBehavior::DefaultYes: + ++up; + break; + case VoteBehavior::DefaultNo: + ++down; + break; + case VoteBehavior::Obsolete: + ++obsolete; + break; + default: + fail("Unknown VoteBehavior", __FILE__, __LINE__); + } + + if (vote == VoteBehavior::Obsolete) + { + BEAST_EXPECT( + allAmendments.contains(name) && + allAmendments.at(name) == AmendmentSupport::Retired); + } + else + { + BEAST_EXPECT( + allAmendments.contains(name) && + allAmendments.at(name) == AmendmentSupport::Supported); + } } + BEAST_EXPECT( + down + obsolete == ripple::detail::numDownVotedAmendments()); + BEAST_EXPECT(up == ripple::detail::numUpVotedAmendments()); + } + { + std::size_t supported = 0, unsupported = 0, retired = 0; + for (auto const& [name, support] : allAmendments) + { + switch (support) + { + case AmendmentSupport::Supported: + ++supported; + BEAST_EXPECT(supportedAmendments.contains(name)); + break; + case AmendmentSupport::Unsupported: + ++unsupported; + break; + case AmendmentSupport::Retired: + ++retired; + break; + default: + fail("Unknown AmendmentSupport", __FILE__, __LINE__); + } + } + + BEAST_EXPECT(supported + retired == supportedAmendments.size()); + BEAST_EXPECT( + allAmendments.size() - unsupported == + supportedAmendments.size()); } - BEAST_EXPECT( - down + obsolete == ripple::detail::numDownVotedAmendments()); - BEAST_EXPECT(up == ripple::detail::numUpVotedAmendments()); } void @@ -188,9 +229,28 @@ class Feature_test : public beast::unit_test::suite using namespace test::jtx; Env env{*this}; - auto jrr = env.rpc("feature", "AllTheThings")[jss::result]; - BEAST_EXPECT(jrr[jss::error] == "badFeature"); - BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid."); + auto testInvalidParam = [&](auto const& param) { + Json::Value params; + params[jss::feature] = param; + auto jrr = + env.rpc("json", "feature", to_string(params))[jss::result]; + BEAST_EXPECT(jrr[jss::error] == "invalidParams"); + BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters."); + }; + + testInvalidParam(1); + testInvalidParam(1.1); + testInvalidParam(true); + testInvalidParam(Json::Value(Json::nullValue)); + testInvalidParam(Json::Value(Json::objectValue)); + testInvalidParam(Json::Value(Json::arrayValue)); + + { + auto jrr = env.rpc("feature", "AllTheThings")[jss::result]; + BEAST_EXPECT(jrr[jss::error] == "badFeature"); + BEAST_EXPECT( + jrr[jss::error_message] == "Feature unknown or invalid."); + } } void diff --git a/src/test/rpc/GRPCTestClientBase.h b/src/test/rpc/GRPCTestClientBase.h index a5c613f55e2..f8b74ed6d5c 100644 --- a/src/test/rpc/GRPCTestClientBase.h +++ b/src/test/rpc/GRPCTestClientBase.h @@ -20,9 +20,9 @@ #ifndef RIPPLED_GRPCTESTCLIENTBASE_H #define RIPPLED_GRPCTESTCLIENTBASE_H -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 4c6ec9aaf1b..4e847c8dbdd 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -15,12 +15,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/GetAggregatePrice_test.cpp b/src/test/rpc/GetAggregatePrice_test.cpp index 1fb263bbc55..aad3c9be99f 100644 --- a/src/test/rpc/GetAggregatePrice_test.cpp +++ b/src/test/rpc/GetAggregatePrice_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include #include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/GetCounts_test.cpp b/src/test/rpc/GetCounts_test.cpp index 52b645ed717..132ed93d7be 100644 --- a/src/test/rpc/GetCounts_test.cpp +++ b/src/test/rpc/GetCounts_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/Handler_test.cpp b/src/test/rpc/Handler_test.cpp index 5160a68aac2..2c3bfd30d4c 100644 --- a/src/test/rpc/Handler_test.cpp +++ b/src/test/rpc/Handler_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include #include #include diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index 8fd453ee9f6..b2b9e7a55b8 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/KeyGeneration_test.cpp b/src/test/rpc/KeyGeneration_test.cpp index 28f9afd3b7f..e136bb04beb 100644 --- a/src/test/rpc/KeyGeneration_test.cpp +++ b/src/test/rpc/KeyGeneration_test.cpp @@ -16,13 +16,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/LedgerClosed_test.cpp b/src/test/rpc/LedgerClosed_test.cpp index 2f81031f85c..c3f7f900e3c 100644 --- a/src/test/rpc/LedgerClosed_test.cpp +++ b/src/test/rpc/LedgerClosed_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index f0811ba34c4..1e4f97a935f 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { @@ -300,201 +300,214 @@ class LedgerData_test : public beast::unit_test::suite { // Put a bunch of different LedgerEntryTypes into a ledger using namespace test::jtx; - using namespace std::chrono; - Env env{*this, envconfig(validator, "")}; - Account const gw{"gateway"}; - auto const USD = gw["USD"]; - env.fund(XRP(100000), gw); - - auto makeRequest = [&env](Json::StaticString const& type) { - Json::Value jvParams; - jvParams[jss::ledger_index] = "current"; - jvParams[jss::type] = type; - return env.rpc( - "json", - "ledger_data", - boost::lexical_cast(jvParams))[jss::result]; - }; - - // Assert that state is an empty array. - for (auto const& type : - {jss::amendments, - jss::check, - jss::directory, - jss::offer, - jss::signer_list, - jss::state, - jss::ticket, - jss::escrow, - jss::payment_channel, - jss::deposit_preauth}) + // Make sure fixInnerObjTemplate2 doesn't break amendments. + for (FeatureBitset const& features : + {supported_amendments() - fixInnerObjTemplate2, + supported_amendments() | fixInnerObjTemplate2}) { - auto const jrr = makeRequest(type); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 0)); - } + using namespace std::chrono; + Env env{*this, envconfig(validator, ""), features}; + + Account const gw{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(100000), gw); + + auto makeRequest = [&env](Json::StaticString const& type) { + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = type; + return env.rpc( + "json", + "ledger_data", + boost::lexical_cast(jvParams))[jss::result]; + }; + + // Assert that state is an empty array. + for (auto const& type : + {jss::amendments, + jss::check, + jss::directory, + jss::offer, + jss::signer_list, + jss::state, + jss::ticket, + jss::escrow, + jss::payment_channel, + jss::deposit_preauth}) + { + auto const jrr = makeRequest(type); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 0)); + } - int const num_accounts = 10; + int const num_accounts = 10; - for (auto i = 0; i < num_accounts; i++) - { - Account const bob{std::string("bob") + std::to_string(i)}; - env.fund(XRP(1000), bob); - } - env(offer(Account{"bob0"}, USD(100), XRP(100))); - env.trust(Account{"bob2"}["USD"](100), Account{"bob3"}); + for (auto i = 0; i < num_accounts; i++) + { + Account const bob{std::string("bob") + std::to_string(i)}; + env.fund(XRP(1000), bob); + } + env(offer(Account{"bob0"}, USD(100), XRP(100))); + env.trust(Account{"bob2"}["USD"](100), Account{"bob3"}); - auto majorities = getMajorityAmendments(*env.closed()); - for (int i = 0; i <= 256; ++i) - { - env.close(); - majorities = getMajorityAmendments(*env.closed()); - if (!majorities.empty()) - break; - } - env(signers( - Account{"bob0"}, 1, {{Account{"bob1"}, 1}, {Account{"bob2"}, 1}})); - env(ticket::create(env.master, 1)); + auto majorities = getMajorityAmendments(*env.closed()); + for (int i = 0; i <= 256; ++i) + { + env.close(); + majorities = getMajorityAmendments(*env.closed()); + if (!majorities.empty()) + break; + } - { - Json::Value jv; - jv[jss::TransactionType] = jss::EscrowCreate; - jv[jss::Flags] = tfUniversal; - jv[jss::Account] = Account{"bob5"}.human(); - jv[jss::Destination] = Account{"bob6"}.human(); - jv[jss::Amount] = XRP(50).value().getJson(JsonOptions::none); - jv[sfFinishAfter.fieldName] = NetClock::time_point{env.now() + 10s} - .time_since_epoch() - .count(); - env(jv); - } + env(signers( + Account{"bob0"}, + 1, + {{Account{"bob1"}, 1}, {Account{"bob2"}, 1}})); + env(ticket::create(env.master, 1)); - { - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Flags] = tfUniversal; - jv[jss::Account] = Account{"bob6"}.human(); - jv[jss::Destination] = Account{"bob7"}.human(); - jv[jss::Amount] = XRP(100).value().getJson(JsonOptions::none); - jv[jss::SettleDelay] = NetClock::duration{10s}.count(); - jv[sfPublicKey.fieldName] = strHex(Account{"bob6"}.pk().slice()); - jv[sfCancelAfter.fieldName] = NetClock::time_point{env.now() + 300s} - .time_since_epoch() - .count(); - env(jv); - } + { + Json::Value jv; + jv[jss::TransactionType] = jss::EscrowCreate; + jv[jss::Flags] = tfUniversal; + jv[jss::Account] = Account{"bob5"}.human(); + jv[jss::Destination] = Account{"bob6"}.human(); + jv[jss::Amount] = XRP(50).value().getJson(JsonOptions::none); + jv[sfFinishAfter.fieldName] = + NetClock::time_point{env.now() + 10s} + .time_since_epoch() + .count(); + env(jv); + } - env(check::create("bob6", "bob7", XRP(100))); + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Flags] = tfUniversal; + jv[jss::Account] = Account{"bob6"}.human(); + jv[jss::Destination] = Account{"bob7"}.human(); + jv[jss::Amount] = XRP(100).value().getJson(JsonOptions::none); + jv[jss::SettleDelay] = NetClock::duration{10s}.count(); + jv[sfPublicKey.fieldName] = + strHex(Account{"bob6"}.pk().slice()); + jv[sfCancelAfter.fieldName] = + NetClock::time_point{env.now() + 300s} + .time_since_epoch() + .count(); + env(jv); + } - // bob9 DepositPreauths bob4 and bob8. - env(deposit::auth(Account{"bob9"}, Account{"bob4"})); - env(deposit::auth(Account{"bob9"}, Account{"bob8"})); - env.close(); + env(check::create("bob6", "bob7", XRP(100))); - // Now fetch each type + // bob9 DepositPreauths bob4 and bob8. + env(deposit::auth(Account{"bob9"}, Account{"bob4"})); + env(deposit::auth(Account{"bob9"}, Account{"bob8"})); + env.close(); - { // jvParams[jss::type] = "account"; - auto const jrr = makeRequest(jss::account); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 12)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::AccountRoot); - } + // Now fetch each type - { // jvParams[jss::type] = "amendments"; - auto const jrr = makeRequest(jss::amendments); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::Amendments); - } + { // jvParams[jss::type] = "account"; + auto const jrr = makeRequest(jss::account); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 12)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::AccountRoot); + } - { // jvParams[jss::type] = "check"; - auto const jrr = makeRequest(jss::check); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::Check); - } + { // jvParams[jss::type] = "amendments"; + auto const jrr = makeRequest(jss::amendments); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::Amendments); + } - { // jvParams[jss::type] = "directory"; - auto const jrr = makeRequest(jss::directory); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 9)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::DirectoryNode); - } + { // jvParams[jss::type] = "check"; + auto const jrr = makeRequest(jss::check); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::Check); + } - { // jvParams[jss::type] = "fee"; - auto const jrr = makeRequest(jss::fee); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::FeeSettings); - } + { // jvParams[jss::type] = "directory"; + auto const jrr = makeRequest(jss::directory); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 9)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::DirectoryNode); + } - { // jvParams[jss::type] = "hashes"; - auto const jrr = makeRequest(jss::hashes); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 2)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::LedgerHashes); - } + { // jvParams[jss::type] = "fee"; + auto const jrr = makeRequest(jss::fee); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::FeeSettings); + } - { // jvParams[jss::type] = "offer"; - auto const jrr = makeRequest(jss::offer); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::Offer); - } + { // jvParams[jss::type] = "hashes"; + auto const jrr = makeRequest(jss::hashes); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 2)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::LedgerHashes); + } - { // jvParams[jss::type] = "signer_list"; - auto const jrr = makeRequest(jss::signer_list); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::SignerList); - } + { // jvParams[jss::type] = "offer"; + auto const jrr = makeRequest(jss::offer); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::Offer); + } - { // jvParams[jss::type] = "state"; - auto const jrr = makeRequest(jss::state); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::RippleState); - } + { // jvParams[jss::type] = "signer_list"; + auto const jrr = makeRequest(jss::signer_list); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::SignerList); + } - { // jvParams[jss::type] = "ticket"; - auto const jrr = makeRequest(jss::ticket); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::Ticket); - } + { // jvParams[jss::type] = "state"; + auto const jrr = makeRequest(jss::state); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::RippleState); + } - { // jvParams[jss::type] = "escrow"; - auto const jrr = makeRequest(jss::escrow); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::Escrow); - } + { // jvParams[jss::type] = "ticket"; + auto const jrr = makeRequest(jss::ticket); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::Ticket); + } - { // jvParams[jss::type] = "payment_channel"; - auto const jrr = makeRequest(jss::payment_channel); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::PayChannel); - } + { // jvParams[jss::type] = "escrow"; + auto const jrr = makeRequest(jss::escrow); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::Escrow); + } - { // jvParams[jss::type] = "deposit_preauth"; - auto const jrr = makeRequest(jss::deposit_preauth); - BEAST_EXPECT(checkArraySize(jrr[jss::state], 2)); - for (auto const& j : jrr[jss::state]) - BEAST_EXPECT(j["LedgerEntryType"] == jss::DepositPreauth); - } + { // jvParams[jss::type] = "payment_channel"; + auto const jrr = makeRequest(jss::payment_channel); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 1)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::PayChannel); + } - { // jvParams[jss::type] = "misspelling"; - Json::Value jvParams; - jvParams[jss::ledger_index] = "current"; - jvParams[jss::type] = "misspelling"; - auto const jrr = env.rpc( - "json", - "ledger_data", - boost::lexical_cast(jvParams))[jss::result]; - BEAST_EXPECT(jrr.isMember("error")); - BEAST_EXPECT(jrr["error"] == "invalidParams"); - BEAST_EXPECT(jrr["error_message"] == "Invalid field 'type'."); + { // jvParams[jss::type] = "deposit_preauth"; + auto const jrr = makeRequest(jss::deposit_preauth); + BEAST_EXPECT(checkArraySize(jrr[jss::state], 2)); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT(j["LedgerEntryType"] == jss::DepositPreauth); + } + + { // jvParams[jss::type] = "misspelling"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "misspelling"; + auto const jrr = env.rpc( + "json", + "ledger_data", + boost::lexical_cast(jvParams))[jss::result]; + BEAST_EXPECT(jrr.isMember("error")); + BEAST_EXPECT(jrr["error"] == "invalidParams"); + BEAST_EXPECT(jrr["error_message"] == "Invalid field 'type'."); + } } } diff --git a/src/test/rpc/LedgerHeader_test.cpp b/src/test/rpc/LedgerHeader_test.cpp index d6c0652d5a2..d8fe738b888 100644 --- a/src/test/rpc/LedgerHeader_test.cpp +++ b/src/test/rpc/LedgerHeader_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include #include #include +#include namespace ripple { diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 79f244c4711..41657468666 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -17,19 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -727,6 +728,204 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntryCredentials() + { + testcase("ledger_entry credentials"); + + using namespace test::jtx; + + Env env(*this); + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + const char credType[] = "abcde"; + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + // Setup credentials with DepositAuth object for Alice and Bob + env(credentials::create(alice, issuer, credType)); + env.close(); + + { + // Succeed + auto jv = credentials::ledgerEntry(env, alice, issuer, credType); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::Credential); + + std::string const credIdx = jv[jss::result][jss::index].asString(); + + jv = credentials::ledgerEntry(env, credIdx); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::Credential); + } + + { + // Fail, index not a hash + auto const jv = credentials::ledgerEntry(env, ""); + checkErrorValue(jv[jss::result], "malformedRequest", ""); + } + + { + // Fail, credential doesn't exist + auto const jv = credentials::ledgerEntry( + env, + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4"); + checkErrorValue(jv[jss::result], "entryNotFound", ""); + } + + { + // Fail, invalid subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = 42; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, invalid issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = 42; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, invalid credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = 42; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = ""; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = ""; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = ""; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, not AccountID subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = "wehsdbvasbdfvj"; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, not AccountID issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu"; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, credentials type isn't hex encoded + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = "12KK"; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + } + void testLedgerEntryDepositPreauth() { @@ -858,6 +1057,428 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntryDepositPreauthCred() + { + testcase("ledger_entry Deposit Preauth with credentials"); + + using namespace test::jtx; + + Env env(*this); + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + const char credType[] = "abcde"; + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + { + // Setup Bob with DepositAuth + env(fset(bob, asfDepositAuth), fee(drops(10))); + env.close(); + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + } + + { + // Succeed + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + + BEAST_EXPECT( + jrr.isObject() && jrr.isMember(jss::result) && + !jrr[jss::result].isMember(jss::error) && + jrr[jss::result].isMember(jss::node) && + jrr[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::DepositPreauth); + } + + { + // Failed, invalid account + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = to_string(xrpAccount()); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, duplicates in credentials + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(jo); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, invalid credential_type + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = ""; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized and authorized_credentials both present + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized] = alice.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Failed, authorized_credentials is not an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42; + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Failed, authorized_credentials contains string data + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + arr.append("foobar"); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials contains arrays + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + Json::Value payload = Json::arrayValue; + payload.append(42); + arr.append(std::move(payload)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials is empty array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials is too long + + static const std::string_view credTypes[] = { + "cred1", + "cred2", + "cred3", + "cred4", + "cred5", + "cred6", + "cred7", + "cred8", + "cred9"}; + static_assert( + sizeof(credTypes) / sizeof(credTypes[0]) > + maxCredentialsArraySize); + + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]); + ++i) + { + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = + strHex(std::string_view(credTypes[i])); + arr.append(std::move(jo)); + } + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer is not set + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer isn't string + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = 42; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer is an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + Json::Value payload = Json::arrayValue; + payload.append(42); + jo[jss::issuer] = std::move(payload); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer isn't valid encoded account + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = "invalid_account"; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type is not set + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type isn't string + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = 42; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type is an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + Json::Value payload = Json::arrayValue; + payload.append(42); + jo[jss::credential_type] = std::move(payload); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type isn't hex encoded + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = "12KK"; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + } + void testLedgerEntryDirectory() { @@ -2361,6 +2982,79 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntryMPT() + { + testcase("ledger_entry Request MPT"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Env env{*this}; + Account const alice{"alice"}; + Account const bob("bob"); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.transferFee = 10, + .metadata = "123", + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | + tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback}); + mptAlice.authorize({.account = bob, .holderCount = 1}); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + std::string const badMptID = + "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315"; + { + // Request the MPTIssuance using its MPTIssuanceID. + Json::Value jvParams; + jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID()); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfMPTokenMetadata.jsonName] == + strHex(std::string{"123"})); + BEAST_EXPECT( + jrr[jss::node][jss::mpt_issuance_id] == + strHex(mptAlice.issuanceID())); + } + { + // Request an index that is not a MPTIssuance. + Json::Value jvParams; + jvParams[jss::mpt_issuance] = badMptID; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // Request the MPToken using its owner + mptIssuanceID. + Json::Value jvParams; + jvParams[jss::mptoken] = Json::objectValue; + jvParams[jss::mptoken][jss::account] = bob.human(); + jvParams[jss::mptoken][jss::mpt_issuance_id] = + strHex(mptAlice.issuanceID()); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfMPTokenIssuanceID.jsonName] == + strHex(mptAlice.issuanceID())); + } + { + // Request the MPToken using a bad mptIssuanceID. + Json::Value jvParams; + jvParams[jss::mptoken] = Json::objectValue; + jvParams[jss::mptoken][jss::account] = bob.human(); + jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + public: void run() override @@ -2374,7 +3068,9 @@ class LedgerRPC_test : public beast::unit_test::suite testLedgerAccounts(); testLedgerEntryAccountRoot(); testLedgerEntryCheck(); + testLedgerEntryCredentials(); testLedgerEntryDepositPreauth(); + testLedgerEntryDepositPreauthCred(); testLedgerEntryDirectory(); testLedgerEntryEscrow(); testLedgerEntryOffer(); @@ -2388,6 +3084,7 @@ class LedgerRPC_test : public beast::unit_test::suite testLedgerEntryDID(); testInvalidOracleLedgerEntry(); testOracleLedgerEntry(); + testLedgerEntryMPT(); forAllApiVersions(std::bind_front( &LedgerRPC_test::testLedgerEntryInvalidParams, this)); diff --git a/src/test/rpc/LedgerRequestRPC_test.cpp b/src/test/rpc/LedgerRequestRPC_test.cpp index 6c59e72c4b8..8922cd38386 100644 --- a/src/test/rpc/LedgerRequestRPC_test.cpp +++ b/src/test/rpc/LedgerRequestRPC_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include diff --git a/src/test/rpc/ManifestRPC_test.cpp b/src/test/rpc/ManifestRPC_test.cpp index 3c32f4d7ba7..fcf47a5745d 100644 --- a/src/test/rpc/ManifestRPC_test.cpp +++ b/src/test/rpc/ManifestRPC_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index 9858adf8466..4551365029f 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index 8da80e6483c..aa5e6e1efef 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/NodeToShardRPC_test.cpp b/src/test/rpc/NodeToShardRPC_test.cpp deleted file mode 100644 index 7736d776995..00000000000 --- a/src/test/rpc/NodeToShardRPC_test.cpp +++ /dev/null @@ -1,414 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2021 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace test { - -class NodeToShardRPC_test : public beast::unit_test::suite -{ - bool - importCompleted( - NodeStore::DatabaseShard* shardStore, - std::uint8_t const numberOfShards, - Json::Value const& result) - { - auto const info = shardStore->getShardInfo(); - - // Assume completed if the import isn't running - auto const completed = - result[jss::error_message] == "Database import not running"; - - if (completed) - { - BEAST_EXPECT( - info->incomplete().size() + info->finalized().size() == - numberOfShards); - } - - return completed; - } - -public: - void - testDisabled() - { - testcase("Disabled"); - - beast::temp_dir tempDir; - - jtx::Env env = [&] { - auto c = jtx::envconfig(); - auto& sectionNode = c->section(ConfigSection::nodeDatabase()); - sectionNode.set("earliest_seq", "257"); - sectionNode.set("ledgers_per_shard", "256"); - c->setupControl(true, true, true); - - return jtx::Env(*this, std::move(c)); - }(); - - std::uint8_t const numberOfShards = 10; - - // Create some ledgers so that we can initiate a - // shard store database import. - for (int i = 0; i < 256 * (numberOfShards + 1); ++i) - { - env.close(); - } - - { - auto shardStore = env.app().getShardStore(); - if (!BEAST_EXPECT(!shardStore)) - return; - } - - { - // Try the node_to_shard status RPC command. Should fail. - - Json::Value jvParams; - jvParams[jss::action] = "status"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED); - } - - { - // Try to start a shard store import via the RPC - // interface. Should fail. - - Json::Value jvParams; - jvParams[jss::action] = "start"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED); - } - - { - // Try the node_to_shard status RPC command. Should fail. - - Json::Value jvParams; - jvParams[jss::action] = "status"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED); - } - } - - void - testStart() - { - testcase("Start"); - - beast::temp_dir tempDir; - - jtx::Env env = [&] { - auto c = jtx::envconfig(); - auto& section = c->section(ConfigSection::shardDatabase()); - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - auto& sectionNode = c->section(ConfigSection::nodeDatabase()); - sectionNode.set("earliest_seq", "257"); - sectionNode.set("ledgers_per_shard", "256"); - c->setupControl(true, true, true); - - return jtx::Env(*this, std::move(c)); - }(); - - std::uint8_t const numberOfShards = 10; - - // Create some ledgers so that we can initiate a - // shard store database import. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfShards + 1); - ++i) - { - env.close(); - } - - auto shardStore = env.app().getShardStore(); - if (!BEAST_EXPECT(shardStore)) - return; - - { - // Initiate a shard store import via the RPC - // interface. - - Json::Value jvParams; - jvParams[jss::action] = "start"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::message] == "Database import initiated..."); - } - - while (!shardStore->getDatabaseImportSequence()) - { - // Wait until the import starts - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - { - // Verify that the import is in progress with - // the node_to_shard status RPC command - - Json::Value jvParams; - jvParams[jss::action] = "status"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::status] == "success" || - importCompleted(shardStore, numberOfShards, result)); - - std::chrono::seconds const maxWait{180}; - - { - auto const start = std::chrono::system_clock::now(); - while (true) - { - // Verify that the status object accurately - // reflects import progress. - - auto const completeShards = - shardStore->getShardInfo()->finalized(); - - if (!completeShards.empty()) - { - auto const result = env.rpc( - "json", - "node_to_shard", - to_string(jvParams))[jss::result]; - - if (!importCompleted( - shardStore, numberOfShards, result)) - { - BEAST_EXPECT(result[jss::firstShardIndex] == 1); - BEAST_EXPECT(result[jss::lastShardIndex] == 10); - } - } - - if (boost::icl::contains(completeShards, 1)) - { - auto const result = env.rpc( - "json", - "node_to_shard", - to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::currentShardIndex] >= 1 || - importCompleted( - shardStore, numberOfShards, result)); - - break; - } - - if (std::this_thread::sleep_for( - std::chrono::milliseconds{100}); - std::chrono::system_clock::now() - start > maxWait) - { - BEAST_EXPECTS( - false, - "Import timeout: could just be a slow machine."); - break; - } - } - } - - { - // Wait for the import to complete - auto const start = std::chrono::system_clock::now(); - while (!boost::icl::contains( - shardStore->getShardInfo()->finalized(), 10)) - { - if (std::this_thread::sleep_for( - std::chrono::milliseconds{100}); - std::chrono::system_clock::now() - start > maxWait) - { - BEAST_EXPECT(importCompleted( - shardStore, numberOfShards, result)); - break; - } - } - } - } - } - - void - testStop() - { - testcase("Stop"); - - beast::temp_dir tempDir; - - jtx::Env env = [&] { - auto c = jtx::envconfig(); - auto& section = c->section(ConfigSection::shardDatabase()); - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - auto& sectionNode = c->section(ConfigSection::nodeDatabase()); - sectionNode.set("earliest_seq", "257"); - sectionNode.set("ledgers_per_shard", "256"); - c->setupControl(true, true, true); - - return jtx::Env( - *this, std::move(c), nullptr, beast::severities::kDisabled); - }(); - - std::uint8_t const numberOfShards = 10; - - // Create some ledgers so that we can initiate a - // shard store database import. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfShards + 1); - ++i) - { - env.close(); - } - - auto shardStore = env.app().getShardStore(); - if (!BEAST_EXPECT(shardStore)) - return; - - { - // Initiate a shard store import via the RPC - // interface. - - Json::Value jvParams; - jvParams[jss::action] = "start"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::message] == "Database import initiated..."); - } - - { - // Verify that the import is in progress with - // the node_to_shard status RPC command - - Json::Value jvParams; - jvParams[jss::action] = "status"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::status] == "success" || - importCompleted(shardStore, numberOfShards, result)); - - std::chrono::seconds const maxWait{30}; - auto const start = std::chrono::system_clock::now(); - - while (shardStore->getShardInfo()->finalized().empty()) - { - // Wait for at least one shard to complete - - if (std::this_thread::sleep_for(std::chrono::milliseconds{100}); - std::chrono::system_clock::now() - start > maxWait) - { - BEAST_EXPECTS( - false, "Import timeout: could just be a slow machine."); - break; - } - } - } - - { - Json::Value jvParams; - jvParams[jss::action] = "stop"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - result[jss::message] == "Database import halt initiated..." || - importCompleted(shardStore, numberOfShards, result)); - } - - std::chrono::seconds const maxWait{30}; - auto const start = std::chrono::system_clock::now(); - - while (true) - { - // Wait until we can verify that the import has - // stopped - - Json::Value jvParams; - jvParams[jss::action] = "status"; - - auto const result = env.rpc( - "json", "node_to_shard", to_string(jvParams))[jss::result]; - - // When the import has stopped, polling the - // status returns an error - if (result.isMember(jss::error)) - { - if (BEAST_EXPECT(result.isMember(jss::error_message))) - { - BEAST_EXPECT( - result[jss::error_message] == - "Database import not running"); - } - - break; - } - - if (std::this_thread::sleep_for(std::chrono::milliseconds{100}); - std::chrono::system_clock::now() - start > maxWait) - { - BEAST_EXPECTS( - false, "Import timeout: could just be a slow machine."); - break; - } - } - } - - void - run() override - { - testDisabled(); - testStart(); - testStop(); - } -}; - -BEAST_DEFINE_TESTSUITE_MANUAL(NodeToShardRPC, rpc, ripple); -} // namespace test -} // namespace ripple diff --git a/src/test/rpc/OwnerInfo_test.cpp b/src/test/rpc/OwnerInfo_test.cpp index c510c35afc8..606f2274b88 100644 --- a/src/test/rpc/OwnerInfo_test.cpp +++ b/src/test/rpc/OwnerInfo_test.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/Peers_test.cpp b/src/test/rpc/Peers_test.cpp index 43fa601e1c0..ab9b17b2593 100644 --- a/src/test/rpc/Peers_test.cpp +++ b/src/test/rpc/Peers_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include #include #include +#include +#include +#include #include namespace ripple { diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 09096ba76af..b812740fb3f 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -15,13 +15,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include @@ -2458,7 +2458,15 @@ static RPCCallTestData const rpcCallTestArray[] = { {"deposit_authorized", "source_account_NotValidated", "destination_account_NotValidated", - "4294967295"}, + "4294967295", + "cred1", + "cred2", + "cred3", + "cred4", + "cred5", + "cred6", + "cred7", + "cred8"}, RPCCallTestData::no_exception, R"({ "method" : "deposit_authorized", @@ -2467,7 +2475,8 @@ static RPCCallTestData const rpcCallTestArray[] = { "api_version" : %API_VER%, "destination_account" : "destination_account_NotValidated", "ledger_index" : 4294967295, - "source_account" : "source_account_NotValidated" + "source_account" : "source_account_NotValidated", + "credentials": ["cred1", "cred2", "cred3", "cred4", "cred5", "cred6", "cred7", "cred8"] } ] })"}, @@ -2512,7 +2521,15 @@ static RPCCallTestData const rpcCallTestArray[] = { "source_account_NotValidated", "destination_account_NotValidated", "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789", - "spare"}, + "cred1", + "cred2", + "cred3", + "cred4", + "cred5", + "cred6", + "cred7", + "cred8", + "too_much"}, RPCCallTestData::no_exception, R"({ "method" : "deposit_authorized", @@ -2545,231 +2562,6 @@ static RPCCallTestData const rpcCallTestArray[] = { ] })"}, - // download_shard - // -------------------------------------------------------------- - {"download_shard: minimal.", - __LINE__, - { - "download_shard", - "20", - "url_NotValidated", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "api_version" : %API_VER%, - "shards" : [ - { - "index" : 20, - "url" : "url_NotValidated" - } - ] - } - ] - })"}, - {"download_shard:", - __LINE__, - { - "download_shard", - "20", - "url_NotValidated", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "api_version" : %API_VER%, - "shards" : [ - { - "index" : 20, - "url" : "url_NotValidated" - } - ] - } - ] - })"}, - {"download_shard: many shards.", - __LINE__, - { - "download_shard", - "200000000", - "url_NotValidated0", - "199999999", - "url_NotValidated1", - "199999998", - "url_NotValidated2", - "199999997", - "url_NotValidated3", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "api_version" : %API_VER%, - "shards" : [ - { - "index" : 200000000, - "url" : "url_NotValidated0" - }, - { - "index" : 199999999, - "url" : "url_NotValidated1" - }, - { - "index" : 199999998, - "url" : "url_NotValidated2" - }, - { - "index" : 199999997, - "url" : "url_NotValidated3" - } - ] - } - ] - })"}, - {"download_shard: many shards.", - __LINE__, - { - "download_shard", - "2000000", - "url_NotValidated0", - "2000001", - "url_NotValidated1", - "2000002", - "url_NotValidated2", - "2000003", - "url_NotValidated3", - "2000004", - "url_NotValidated4", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "api_version" : %API_VER%, - "shards" : [ - { - "index" : 2000000, - "url" : "url_NotValidated0" - }, - { - "index" : 2000001, - "url" : "url_NotValidated1" - }, - { - "index" : 2000002, - "url" : "url_NotValidated2" - }, - { - "index" : 2000003, - "url" : "url_NotValidated3" - }, - { - "index" : 2000004, - "url" : "url_NotValidated4" - } - ] - } - ] - })"}, - {"download_shard: too few arguments.", - __LINE__, - {"download_shard", "20"}, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "error" : "badSyntax", - "error_code" : 1, - "error_message" : "Syntax error." - } - ] - })"}, - {// Note: this should return an error but not throw. - "download_shard: novalidate too few arguments.", - __LINE__, - {"download_shard", "novalidate", "20"}, - RPCCallTestData::bad_cast, - R"()"}, - {"download_shard: novalidate at end.", - __LINE__, - { - "download_shard", - "20", - "url_NotValidated", - "novalidate", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "api_version" : %API_VER%, - "shards" : [ - { - "index" : 20, - "url" : "url_NotValidated" - } - ] - } - ] - })"}, - {"download_shard: novalidate in middle.", - __LINE__, - { - "download_shard", - "20", - "url_NotValidated20", - "novalidate", - "200", - "url_NotValidated200", - }, - RPCCallTestData::no_exception, - R"({ - "method" : "download_shard", - "params" : [ - { - "error" : "invalidParams", - "error_code" : 31, - "error_message" : "Invalid parameters." - } - ] - })"}, - {// Note: this should return an error but not throw. - "download_shard: arguments swapped.", - __LINE__, - { - "download_shard", - "url_NotValidated", - "20", - }, - RPCCallTestData::bad_cast, - R"()"}, - {"download_shard: index too small.", - __LINE__, - { - "download_shard", - "-1", - "url_NotValidated", - }, - RPCCallTestData::bad_cast, - R"()"}, - {"download_shard: index too big.", - __LINE__, - { - "download_shard", - "4294967296", - "url_NotValidated", - }, - RPCCallTestData::bad_cast, - R"()"}, - // feature // --------------------------------------------------------------------- {"feature: minimal.", @@ -4232,75 +4024,6 @@ static RPCCallTestData const rpcCallTestArray[] = { ] })"}, - // node_to_shard - // ------------------------------------------------------------------- - {"node_to_shard: status.", - __LINE__, - {"node_to_shard", "status"}, - RPCCallTestData::no_exception, - R"({ - "method" : "node_to_shard", - "params" : [ - { - "api_version" : %API_VER%, - "action" : "status" - } - ] - })"}, - {"node_to_shard: start.", - __LINE__, - {"node_to_shard", "start"}, - RPCCallTestData::no_exception, - R"({ - "method" : "node_to_shard", - "params" : [ - { - "api_version" : %API_VER%, - "action" : "start" - } - ] - })"}, - {"node_to_shard: stop.", - __LINE__, - {"node_to_shard", "stop"}, - RPCCallTestData::no_exception, - R"({ - "method" : "node_to_shard", - "params" : [ - { - "api_version" : %API_VER%, - "action" : "stop" - } - ] - })"}, - {"node_to_shard: too many arguments.", - __LINE__, - {"node_to_shard", "start", "stop"}, - RPCCallTestData::no_exception, - R"({ - "method" : "node_to_shard", - "params" : [ - { - "error" : "badSyntax", - "error_code" : 1, - "error_message" : "Syntax error." - } - ] - })"}, - {"node_to_shard: invalid argument.", - __LINE__, - {"node_to_shard", "invalid"}, - RPCCallTestData::no_exception, - R"({ - "method" : "node_to_shard", - "params" : [ - { - "api_version" : %API_VER%, - "action" : "invalid" - } - ] - })"}, - // owner_info // ------------------------------------------------------------------ {"owner_info: minimal.", diff --git a/src/test/rpc/RPCOverload_test.cpp b/src/test/rpc/RPCOverload_test.cpp index 088a8179043..c3328fb0b28 100644 --- a/src/test/rpc/RPCOverload_test.cpp +++ b/src/test/rpc/RPCOverload_test.cpp @@ -15,12 +15,12 @@ */ //============================================================================== -#include -#include -#include #include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/ReportingETL_test.cpp b/src/test/rpc/ReportingETL_test.cpp deleted file mode 100644 index 078a51d7bcd..00000000000 --- a/src/test/rpc/ReportingETL_test.cpp +++ /dev/null @@ -1,1144 +0,0 @@ - -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include - -#include -#include -#include -#include -#include - -namespace ripple { -namespace test { - -class ReportingETL_test : public beast::unit_test::suite -{ - // gRPC stuff - class GrpcLedgerClient : public GRPCTestClientBase - { - public: - org::xrpl::rpc::v1::GetLedgerRequest request; - org::xrpl::rpc::v1::GetLedgerResponse reply; - - explicit GrpcLedgerClient(std::string const& port) - : GRPCTestClientBase(port) - { - } - - void - GetLedger() - { - status = stub_->GetLedger(&context, request, &reply); - } - }; - void - testGetLedger() - { - testcase("GetLedger"); - using namespace test::jtx; - std::unique_ptr config = envconfig(addGrpcConfig); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - env.close(); - - auto ledger = env.app().getLedgerMaster().getLedgerBySeq(3); - - BEAST_EXPECT(env.current()->info().seq == 4); - - auto grpcLedger = [&grpcPort]( - auto sequence, - bool transactions, - bool expand, - bool get_objects, - bool get_object_neighbors) { - GrpcLedgerClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_transactions(transactions); - grpcClient.request.set_expand(expand); - grpcClient.request.set_get_objects(get_objects); - grpcClient.request.set_get_object_neighbors(get_object_neighbors); - - grpcClient.GetLedger(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - { - auto [status, reply] = grpcLedger(3, false, false, false, false); - - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - BEAST_EXPECT(!reply.has_hashes_list()); - BEAST_EXPECT(!reply.has_transactions_list()); - BEAST_EXPECT(!reply.skiplist_included()); - BEAST_EXPECT(reply.ledger_objects().objects_size() == 0); - - Serializer s; - addRaw(ledger->info(), s, true); - BEAST_EXPECT(s.slice() == makeSlice(reply.ledger_header())); - } - - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10000), alice); - env.fund(XRP(10000), bob); - env.close(); - - ledger = env.app().getLedgerMaster().getLedgerBySeq(4); - - std::vector hashes; - std::vector> transactions; - std::vector> metas; - for (auto& [sttx, meta] : ledger->txs) - { - hashes.push_back(sttx->getTransactionID()); - transactions.push_back(sttx); - metas.push_back(meta); - } - - Serializer s; - addRaw(ledger->info(), s, true); - - { - auto [status, reply] = grpcLedger(4, true, false, false, false); - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - BEAST_EXPECT(reply.has_hashes_list()); - BEAST_EXPECT(reply.hashes_list().hashes_size() == hashes.size()); - BEAST_EXPECT( - uint256::fromVoid(reply.hashes_list().hashes(0).data()) == - hashes[0]); - BEAST_EXPECT( - uint256::fromVoid(reply.hashes_list().hashes(1).data()) == - hashes[1]); - BEAST_EXPECT( - uint256::fromVoid(reply.hashes_list().hashes(2).data()) == - hashes[2]); - BEAST_EXPECT( - uint256::fromVoid(reply.hashes_list().hashes(3).data()) == - hashes[3]); - - BEAST_EXPECT(!reply.has_transactions_list()); - BEAST_EXPECT(!reply.skiplist_included()); - BEAST_EXPECT(reply.ledger_objects().objects_size() == 0); - - BEAST_EXPECT(s.slice() == makeSlice(reply.ledger_header())); - } - - { - auto [status, reply] = grpcLedger(4, true, true, false, false); - - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - BEAST_EXPECT(!reply.has_hashes_list()); - - BEAST_EXPECT(reply.has_transactions_list()); - BEAST_EXPECT(reply.transactions_list().transactions_size() == 4); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .transaction_blob()) == - transactions[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .metadata_blob()) == - metas[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .transaction_blob()) == - transactions[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .metadata_blob()) == - metas[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .transaction_blob()) == - transactions[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .metadata_blob()) == - metas[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .transaction_blob()) == - transactions[3]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .metadata_blob()) == - metas[3]->getSerializer().slice()); - - BEAST_EXPECT(!reply.skiplist_included()); - BEAST_EXPECT(reply.ledger_objects().objects_size() == 0); - - BEAST_EXPECT(s.slice() == makeSlice(reply.ledger_header())); - } - - { - auto [status, reply] = grpcLedger(4, true, true, true, false); - - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - BEAST_EXPECT(!reply.has_hashes_list()); - - BEAST_EXPECT(reply.has_transactions_list()); - BEAST_EXPECT(reply.transactions_list().transactions_size() == 4); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .transaction_blob()) == - transactions[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .metadata_blob()) == - metas[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .transaction_blob()) == - transactions[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .metadata_blob()) == - metas[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .transaction_blob()) == - transactions[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .metadata_blob()) == - metas[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .transaction_blob()) == - transactions[3]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .metadata_blob()) == - metas[3]->getSerializer().slice()); - BEAST_EXPECT(reply.skiplist_included()); - - BEAST_EXPECT(s.slice() == makeSlice(reply.ledger_header())); - - auto parent = env.app().getLedgerMaster().getLedgerBySeq(3); - - SHAMap::Delta differences; - - int maxDifferences = std::numeric_limits::max(); - - bool res = parent->stateMap().compare( - ledger->stateMap(), differences, maxDifferences); - BEAST_EXPECT(res); - - size_t idx = 0; - for (auto& [k, v] : differences) - { - BEAST_EXPECT( - k == - uint256::fromVoid( - reply.ledger_objects().objects(idx).key().data())); - if (v.second) - { - BEAST_EXPECT( - v.second->slice() == - makeSlice(reply.ledger_objects().objects(idx).data())); - } - ++idx; - } - } - { - auto [status, reply] = grpcLedger(4, true, true, true, true); - - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - BEAST_EXPECT(!reply.has_hashes_list()); - BEAST_EXPECT(reply.object_neighbors_included()); - - BEAST_EXPECT(reply.has_transactions_list()); - BEAST_EXPECT(reply.transactions_list().transactions_size() == 4); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .transaction_blob()) == - transactions[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(0) - .metadata_blob()) == - metas[0]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .transaction_blob()) == - transactions[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(1) - .metadata_blob()) == - metas[1]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .transaction_blob()) == - transactions[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(2) - .metadata_blob()) == - metas[2]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .transaction_blob()) == - transactions[3]->getSerializer().slice()); - - BEAST_EXPECT( - makeSlice(reply.transactions_list() - .transactions(3) - .metadata_blob()) == - metas[3]->getSerializer().slice()); - BEAST_EXPECT(reply.skiplist_included()); - - BEAST_EXPECT(s.slice() == makeSlice(reply.ledger_header())); - - auto parent = env.app().getLedgerMaster().getLedgerBySeq(3); - - SHAMap::Delta differences; - - int maxDifferences = std::numeric_limits::max(); - - bool res = parent->stateMap().compare( - ledger->stateMap(), differences, maxDifferences); - BEAST_EXPECT(res); - - size_t idx = 0; - - for (auto& [k, v] : differences) - { - auto obj = reply.ledger_objects().objects(idx); - BEAST_EXPECT(k == uint256::fromVoid(obj.key().data())); - if (v.second) - { - BEAST_EXPECT(v.second->slice() == makeSlice(obj.data())); - } - else - BEAST_EXPECT(obj.data().size() == 0); - - if (!(v.first && v.second)) - { - auto succ = ledger->stateMap().upper_bound(k); - auto pred = ledger->stateMap().lower_bound(k); - - if (succ != ledger->stateMap().end()) - BEAST_EXPECT( - succ->key() == - uint256::fromVoid(obj.successor().data())); - else - BEAST_EXPECT(obj.successor().size() == 0); - if (pred != ledger->stateMap().end()) - BEAST_EXPECT( - pred->key() == - uint256::fromVoid(obj.predecessor().data())); - else - BEAST_EXPECT(obj.predecessor().size() == 0); - } - ++idx; - } - } - - // Delete an account - - env(noop(alice)); - - std::uint32_t const ledgerCount{ - env.current()->seq() + 257 - env.seq(alice)}; - - for (std::uint32_t i = 0; i < ledgerCount; ++i) - env.close(); - - auto const acctDelFee{drops(env.current()->fees().increment)}; - env(acctdelete(alice, bob), fee(acctDelFee)); - env.close(); - - { - auto [status, reply] = - grpcLedger(env.closed()->seq(), true, true, true, true); - - BEAST_EXPECT(status.ok()); - BEAST_EXPECT(reply.validated()); - auto base = - env.app().getLedgerMaster().getLedgerBySeq(env.closed()->seq()); - - auto parent = env.app().getLedgerMaster().getLedgerBySeq( - env.closed()->seq() - 1); - - SHAMap::Delta differences; - - int maxDifferences = std::numeric_limits::max(); - - bool res = parent->stateMap().compare( - base->stateMap(), differences, maxDifferences); - BEAST_EXPECT(res); - - size_t idx = 0; - for (auto& [k, v] : differences) - { - auto obj = reply.ledger_objects().objects(idx); - BEAST_EXPECT(k == uint256::fromVoid(obj.key().data())); - if (v.second) - { - BEAST_EXPECT( - v.second->slice() == - makeSlice(reply.ledger_objects().objects(idx).data())); - } - else - BEAST_EXPECT(obj.data().size() == 0); - if (!(v.first && v.second)) - { - auto succ = base->stateMap().upper_bound(k); - auto pred = base->stateMap().lower_bound(k); - - if (succ != base->stateMap().end()) - BEAST_EXPECT( - succ->key() == - uint256::fromVoid(obj.successor().data())); - else - BEAST_EXPECT(obj.successor().size() == 0); - if (pred != base->stateMap().end()) - BEAST_EXPECT( - pred->key() == - uint256::fromVoid(obj.predecessor().data())); - else - BEAST_EXPECT(obj.predecessor().size() == 0); - } - - ++idx; - } - } - } - - // gRPC stuff - class GrpcLedgerDataClient : public GRPCTestClientBase - { - public: - org::xrpl::rpc::v1::GetLedgerDataRequest request; - org::xrpl::rpc::v1::GetLedgerDataResponse reply; - - explicit GrpcLedgerDataClient(std::string const& port) - : GRPCTestClientBase(port) - { - } - - void - GetLedgerData() - { - status = stub_->GetLedgerData(&context, request, &reply); - } - }; - void - testGetLedgerData() - { - testcase("GetLedgerData"); - using namespace test::jtx; - std::unique_ptr config = envconfig(addGrpcConfig); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - auto grpcLedgerData = [&grpcPort]( - auto sequence, std::string marker = "") { - GrpcLedgerDataClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - if (marker.size()) - { - grpcClient.request.set_marker(marker); - } - - grpcClient.GetLedgerData(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - Account const alice{"alice"}; - env.fund(XRP(100000), alice); - - int num_accounts = 10; - - for (auto i = 0; i < num_accounts; i++) - { - Account const bob{std::string("bob") + std::to_string(i)}; - env.fund(XRP(1000), bob); - } - env.close(); - - { - auto [status, reply] = grpcLedgerData(env.closed()->seq()); - BEAST_EXPECT(status.ok()); - - BEAST_EXPECT( - reply.ledger_objects().objects_size() == num_accounts + 4); - BEAST_EXPECT(reply.marker().size() == 0); - auto ledger = env.closed(); - size_t idx = 0; - for (auto& sle : ledger->sles) - { - BEAST_EXPECT( - sle->getSerializer().slice() == - makeSlice(reply.ledger_objects().objects(idx).data())); - ++idx; - } - } - - { - auto [status, reply] = - grpcLedgerData(env.closed()->seq(), "bad marker"); - BEAST_EXPECT(!status.ok()); - BEAST_EXPECT( - status.error_code() == grpc::StatusCode::INVALID_ARGUMENT); - } - - num_accounts = 3000; - - for (auto i = 0; i < num_accounts; i++) - { - Account const cat{std::string("cat") + std::to_string(i)}; - env.fund(XRP(1000), cat); - if (i % 100 == 0) - env.close(); - } - env.close(); - - { - auto [status, reply] = grpcLedgerData(env.closed()->seq()); - BEAST_EXPECT(status.ok()); - - int maxLimit = RPC::Tuning::pageLength(true); - BEAST_EXPECT(reply.ledger_objects().objects_size() == maxLimit); - BEAST_EXPECT(reply.marker().size() != 0); - - auto [status2, reply2] = - grpcLedgerData(env.closed()->seq(), reply.marker()); - BEAST_EXPECT(status2.ok()); - BEAST_EXPECT(reply2.marker().size() == 0); - - auto ledger = env.closed(); - size_t idx = 0; - for (auto& sle : ledger->sles) - { - auto& obj = idx < maxLimit - ? reply.ledger_objects().objects(idx) - : reply2.ledger_objects().objects(idx - maxLimit); - - BEAST_EXPECT( - sle->getSerializer().slice() == makeSlice(obj.data())); - ++idx; - } - BEAST_EXPECT( - idx == - reply.ledger_objects().objects_size() + - reply2.ledger_objects().objects_size()); - } - } - - // gRPC stuff - class GrpcLedgerDiffClient : public GRPCTestClientBase - { - public: - org::xrpl::rpc::v1::GetLedgerDiffRequest request; - org::xrpl::rpc::v1::GetLedgerDiffResponse reply; - - explicit GrpcLedgerDiffClient(std::string const& port) - : GRPCTestClientBase(port) - { - } - - void - GetLedgerDiff() - { - status = stub_->GetLedgerDiff(&context, request, &reply); - } - }; - - void - testGetLedgerDiff() - { - testcase("GetLedgerDiff"); - using namespace test::jtx; - std::unique_ptr config = envconfig(addGrpcConfig); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - auto grpcLedgerDiff = [&grpcPort]( - auto baseSequence, auto desiredSequence) { - GrpcLedgerDiffClient grpcClient{grpcPort}; - - grpcClient.request.mutable_base_ledger()->set_sequence( - baseSequence); - grpcClient.request.mutable_desired_ledger()->set_sequence( - desiredSequence); - grpcClient.request.set_include_blobs(true); - - grpcClient.GetLedgerDiff(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - int num_accounts = 20; - for (auto i = 0; i < num_accounts; i++) - { - Account const cat{std::string("cat") + std::to_string(i)}; - env.fund(XRP(1000), cat); - if (i % 2 == 0) - env.close(); - } - env.close(); - - auto compareDiffs = [&](auto baseSequence, auto desiredSequence) { - auto [status, reply] = - grpcLedgerDiff(baseSequence, desiredSequence); - - BEAST_EXPECT(status.ok()); - auto desired = - env.app().getLedgerMaster().getLedgerBySeq(desiredSequence); - - auto base = - env.app().getLedgerMaster().getLedgerBySeq(baseSequence); - - SHAMap::Delta differences; - - int maxDifferences = std::numeric_limits::max(); - - bool res = base->stateMap().compare( - desired->stateMap(), differences, maxDifferences); - if (!BEAST_EXPECT(res)) - return false; - - size_t idx = 0; - for (auto& [k, v] : differences) - { - if (!BEAST_EXPECT( - k == - uint256::fromVoid( - reply.ledger_objects().objects(idx).key().data()))) - return false; - if (v.second) - { - if (!BEAST_EXPECT( - v.second->slice() == - makeSlice( - reply.ledger_objects().objects(idx).data()))) - return false; - } - - ++idx; - } - return true; - }; - - // Adjacent ledgers - BEAST_EXPECT( - compareDiffs(env.closed()->seq() - 1, env.closed()->seq())); - - // Adjacent ledgers further in the past - BEAST_EXPECT( - compareDiffs(env.closed()->seq() - 3, env.closed()->seq() - 2)); - - // Non-adjacent ledgers - BEAST_EXPECT( - compareDiffs(env.closed()->seq() - 5, env.closed()->seq() - 1)); - - // Adjacent ledgers but in reverse order - BEAST_EXPECT( - compareDiffs(env.closed()->seq(), env.closed()->seq() - 1)); - - // Non-adjacent ledgers in reverse order - BEAST_EXPECT( - compareDiffs(env.closed()->seq() - 1, env.closed()->seq() - 5)); - } - - // gRPC stuff - class GrpcLedgerEntryClient : public GRPCTestClientBase - { - public: - org::xrpl::rpc::v1::GetLedgerEntryRequest request; - org::xrpl::rpc::v1::GetLedgerEntryResponse reply; - - explicit GrpcLedgerEntryClient(std::string const& port) - : GRPCTestClientBase(port) - { - } - - void - GetLedgerEntry() - { - status = stub_->GetLedgerEntry(&context, request, &reply); - } - }; - - void - testGetLedgerEntry() - { - testcase("GetLedgerDiff"); - using namespace test::jtx; - std::unique_ptr config = envconfig(addGrpcConfig); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - auto grpcLedgerEntry = [&grpcPort](auto sequence, auto key) { - GrpcLedgerEntryClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_key(key.data(), key.size()); - - grpcClient.GetLedgerEntry(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - Account const alice{"alice"}; - env.fund(XRP(1000), alice); - env.close(); - - for (auto& sle : env.closed()->sles) - { - auto [status, reply] = - grpcLedgerEntry(env.closed()->seq(), sle->key()); - - BEAST_EXPECT(status.ok()); - - BEAST_EXPECT( - uint256::fromVoid(reply.ledger_object().key().data()) == - sle->key()); - BEAST_EXPECT( - makeSlice(reply.ledger_object().data()) == - sle->getSerializer().slice()); - } - } - - void - testNeedCurrentOrClosed() - { - testcase("NeedCurrentOrClosed"); - - { - org::xrpl::rpc::v1::GetLedgerRequest request; - request.mutable_ledger()->set_sequence(1); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_hash(""); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED); - BEAST_EXPECT(needCurrentOrClosed(request)); - } - - { - org::xrpl::rpc::v1::GetLedgerDataRequest request; - request.mutable_ledger()->set_sequence(1); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_hash(""); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED); - BEAST_EXPECT(needCurrentOrClosed(request)); - } - - { - org::xrpl::rpc::v1::GetLedgerEntryRequest request; - request.mutable_ledger()->set_sequence(1); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_hash(""); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - request.mutable_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED); - BEAST_EXPECT(needCurrentOrClosed(request)); - } - - { - org::xrpl::rpc::v1::GetLedgerDiffRequest request; - - // set desired ledger, so desired ledger does not need current or - // closed - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - - request.mutable_base_ledger()->set_sequence(1); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_base_ledger()->set_hash(""); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED); - BEAST_EXPECT(needCurrentOrClosed(request)); - - // reset base ledger, so base ledger doesn't need current or closed - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - - request.mutable_desired_ledger()->set_sequence(1); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_desired_ledger()->set_hash(""); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_desired_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_desired_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED); - BEAST_EXPECT(!needCurrentOrClosed(request)); - request.mutable_desired_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - request.mutable_desired_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED); - BEAST_EXPECT(needCurrentOrClosed(request)); - - // both base and desired need current or closed - request.mutable_base_ledger()->set_shortcut( - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT); - BEAST_EXPECT(needCurrentOrClosed(request)); - } - } - - void - testSecureGateway() - { - testcase("SecureGateway"); - using namespace test::jtx; - { - std::unique_ptr config = envconfig( - addGrpcConfigWithSecureGateway, getEnvLocalhostAddr()); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - env.close(); - - auto ledger = env.app().getLedgerMaster().getLedgerBySeq(3); - - BEAST_EXPECT(env.current()->info().seq == 4); - - auto grpcLedger = [&grpcPort]( - auto sequence, - std::string const& clientIp, - std::string const& user) { - GrpcLedgerClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_client_ip(clientIp); - grpcClient.request.set_user(user); - - grpcClient.GetLedger(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "", "ETL"); - BEAST_EXPECT(reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "", "Reporting"); - BEAST_EXPECT(reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "127.0.0.1", "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "127.0.0.1", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - } - - { - std::string secureGatewayIp = "44.124.234.79"; - std::unique_ptr config = - envconfig(addGrpcConfigWithSecureGateway, secureGatewayIp); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - env.close(); - - auto ledger = env.app().getLedgerMaster().getLedgerBySeq(3); - - BEAST_EXPECT(env.current()->info().seq == 4); - - auto grpcLedger = [&grpcPort]( - auto sequence, - std::string const& clientIp, - std::string const& user) { - GrpcLedgerClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_client_ip(clientIp); - grpcClient.request.set_user(user); - - grpcClient.GetLedger(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, "", "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = grpcLedger( - env.current()->info().seq, secureGatewayIp, "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedger(env.current()->info().seq, secureGatewayIp, ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - } - - { - std::unique_ptr config = envconfig( - addGrpcConfigWithSecureGateway, getEnvLocalhostAddr()); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - env.close(); - - auto ledger = env.app().getLedgerMaster().getLedgerBySeq(3); - - BEAST_EXPECT(env.current()->info().seq == 4); - auto grpcLedgerData = [&grpcPort]( - auto sequence, - std::string const& clientIp, - std::string const& user) { - GrpcLedgerDataClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_client_ip(clientIp); - grpcClient.request.set_user(user); - - grpcClient.GetLedgerData(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "", "ETL"); - BEAST_EXPECT(reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "", "Reporting"); - BEAST_EXPECT(reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = grpcLedgerData( - env.current()->info().seq, "127.0.0.1", "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "127.0.0.1", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - } - { - std::string secureGatewayIp = "44.124.234.79"; - std::unique_ptr config = - envconfig(addGrpcConfigWithSecureGateway, secureGatewayIp); - std::string grpcPort = - *(*config)[SECTION_PORT_GRPC].get("port"); - Env env(*this, std::move(config)); - - env.close(); - - auto ledger = env.app().getLedgerMaster().getLedgerBySeq(3); - - BEAST_EXPECT(env.current()->info().seq == 4); - - auto grpcLedgerData = [&grpcPort]( - auto sequence, - std::string const& clientIp, - std::string const& user) { - GrpcLedgerDataClient grpcClient{grpcPort}; - - grpcClient.request.mutable_ledger()->set_sequence(sequence); - grpcClient.request.set_client_ip(clientIp); - grpcClient.request.set_user(user); - - grpcClient.GetLedgerData(); - return std::make_pair(grpcClient.status, grpcClient.reply); - }; - - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "", ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = - grpcLedgerData(env.current()->info().seq, "", "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = grpcLedgerData( - env.current()->info().seq, secureGatewayIp, "ETL"); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - { - auto [status, reply] = grpcLedgerData( - env.current()->info().seq, secureGatewayIp, ""); - BEAST_EXPECT(!reply.is_unlimited()); - BEAST_EXPECT(status.ok()); - } - } - } - -public: - void - run() override - { - testGetLedger(); - - testGetLedgerData(); - - testGetLedgerDiff(); - - testGetLedgerEntry(); - - testNeedCurrentOrClosed(); - - testSecureGateway(); - } -}; - -BEAST_DEFINE_TESTSUITE_PRIO(ReportingETL, app, ripple, 2); - -} // namespace test -} // namespace ripple diff --git a/src/test/rpc/RobustTransaction_test.cpp b/src/test/rpc/RobustTransaction_test.cpp index 37b16c58d7f..b0506224f79 100644 --- a/src/test/rpc/RobustTransaction_test.cpp +++ b/src/test/rpc/RobustTransaction_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include #include #include +#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/rpc/Roles_test.cpp b/src/test/rpc/Roles_test.cpp index 079fd2f9f26..8cef5dcbc61 100644 --- a/src/test/rpc/Roles_test.cpp +++ b/src/test/rpc/Roles_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include #include #include +#include +#include +#include #include diff --git a/src/test/rpc/ServerInfo_test.cpp b/src/test/rpc/ServerInfo_test.cpp index ece98e99a7e..e6f889c5bdf 100644 --- a/src/test/rpc/ServerInfo_test.cpp +++ b/src/test/rpc/ServerInfo_test.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include #include +#include +#include +#include +#include #include @@ -197,6 +197,37 @@ admin = 127.0.0.1 .asUInt() == 0); BEAST_EXPECT( result[jss::result][jss::TYPES]["AccountID"].asUInt() == 8); + + // check exception SFields + { + auto const fieldExists = [&](std::string name) { + for (auto& field : result[jss::result][jss::FIELDS]) + { + if (field[0u].asString() == name) + { + return true; + } + } + return false; + }; + BEAST_EXPECT(fieldExists("Generic")); + BEAST_EXPECT(fieldExists("Invalid")); + BEAST_EXPECT(fieldExists("ObjectEndMarker")); + BEAST_EXPECT(fieldExists("ArrayEndMarker")); + BEAST_EXPECT(fieldExists("taker_gets_funded")); + BEAST_EXPECT(fieldExists("taker_pays_funded")); + BEAST_EXPECT(fieldExists("hash")); + BEAST_EXPECT(fieldExists("index")); + } + + // test that base_uint types are replaced with "Hash" prefix + { + auto const types = result[jss::result][jss::TYPES]; + BEAST_EXPECT(types["Hash128"].asUInt() == 4); + BEAST_EXPECT(types["Hash160"].asUInt() == 17); + BEAST_EXPECT(types["Hash192"].asUInt() == 21); + BEAST_EXPECT(types["Hash256"].asUInt() == 5); + } } // test providing the same hash diff --git a/src/test/rpc/ShardArchiveHandler_test.cpp b/src/test/rpc/ShardArchiveHandler_test.cpp deleted file mode 100644 index ee0bec1eadf..00000000000 --- a/src/test/rpc/ShardArchiveHandler_test.cpp +++ /dev/null @@ -1,705 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2020 Ripple Labs Inc. - - Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace test { - -class ShardArchiveHandler_test : public beast::unit_test::suite -{ - using Downloads = std::vector>; - - std::shared_ptr - createServer(jtx::Env& env, bool ssl = true) - { - std::vector list; - list.push_back(TrustedPublisherServer::randomValidator()); - return make_TrustedPublisherServer( - env.app().getIOService(), - list, - env.timeKeeper().now() + std::chrono::seconds{3600}, - // No future VLs - {}, - ssl); - } - -public: - // Test the shard downloading module by queueing - // a download and verifying the contents of the - // state database. - void - testSingleDownloadAndStateDB() - { - testcase("testSingleDownloadAndStateDB"); - - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - auto& section = c->section(ConfigSection::shardDatabase()); - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - c->setupControl(true, true, true); - - jtx::Env env(*this, std::move(c)); - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT(dynamic_cast(handler) == nullptr); - - std::string const rawUrl = "https://foo:443/1.tar.lz4"; - parsedURL url; - - parseUrl(url, rawUrl); - handler->add(1, {url, rawUrl}); - - { - std::lock_guard lock(handler->m_); - std::uint64_t rowCount = 0; - - readArchiveDB( - *handler->sqlDB_, [&](std::string const& url, int state) { - BEAST_EXPECT(state == 1); - BEAST_EXPECT(url == rawUrl); - ++rowCount; - }); - - BEAST_EXPECT(rowCount == 1); - } - - handler->release(); - } - - // Test the shard downloading module by queueing - // three downloads and verifying the contents of - // the state database. - void - testDownloadsAndStateDB() - { - testcase("testDownloadsAndStateDB"); - - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - auto& section = c->section(ConfigSection::shardDatabase()); - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - c->setupControl(true, true, true); - - jtx::Env env(*this, std::move(c)); - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT(dynamic_cast(handler) == nullptr); - - Downloads const dl = { - {1, "https://foo:443/1.tar.lz4"}, - {2, "https://foo:443/2.tar.lz4"}, - {3, "https://foo:443/3.tar.lz4"}}; - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - { - std::lock_guard lock(handler->m_); - std::uint64_t pos = 0; - - readArchiveDB( - *handler->sqlDB_, [&](std::string const& url, int state) { - BEAST_EXPECT(state == dl[pos].first); - BEAST_EXPECT(url == dl[pos].second); - ++pos; - }); - - BEAST_EXPECT(pos == dl.size()); - } - - handler->release(); - } - - // Test the shard downloading module by initiating - // and completing ten downloads and verifying the - // contents of the filesystem and the handler's - // archives. - void - testDownloadsAndFileSystem() - { - testcase("testDownloadsAndFileSystem"); - - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - jtx::Env env( - *this, std::move(c), nullptr, beast::severities::kDisabled); - - std::uint8_t const numberOfDownloads = 10; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfDownloads + 1); - ++i) - { - env.close(); - } - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT(dynamic_cast(handler) == nullptr); - - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - server->stop(); - - Downloads const dl = [count = numberOfDownloads, &host, &port] { - Downloads ret; - - for (int i = 1; i <= count; ++i) - { - ret.push_back( - {i, - (boost::format("https://%s:%d/%d.tar.lz4") % host % port % - i) - .str()}); - } - - return ret; - }(); - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - BEAST_EXPECT(handler->start()); - - auto stateDir = - RPC::ShardArchiveHandler::getDownloadDirectory(env.app().config()); - - std::unique_lock lock(handler->m_); - - BEAST_EXPECT( - boost::filesystem::exists(stateDir) || handler->archives_.empty()); - - using namespace std::chrono_literals; - auto waitMax = 60s; - - while (!handler->archives_.empty()) - { - lock.unlock(); - std::this_thread::sleep_for(1s); - - if (waitMax -= 1s; waitMax <= 0s) - { - BEAST_EXPECT(false); - break; - } - - lock.lock(); - } - - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - } - - // Test the shard downloading module by initiating - // and completing ten downloads and verifying the - // contents of the filesystem and the handler's - // archives. Then restart the application and ensure - // that the handler is created and started automatically. - void - testDownloadsAndRestart() - { - testcase("testDownloadsAndRestart"); - - beast::temp_dir tempDir; - - { - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - jtx::Env env( - *this, std::move(c), nullptr, beast::severities::kDisabled); - - std::uint8_t const numberOfDownloads = 10; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfDownloads + 1); - ++i) - { - env.close(); - } - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT( - dynamic_cast(handler) == nullptr); - - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - server->stop(); - - Downloads const dl = [count = numberOfDownloads, &host, &port] { - Downloads ret; - - for (int i = 1; i <= count; ++i) - { - ret.push_back( - {i, - (boost::format("https://%s:%d/%d.tar.lz4") % host % - port % i) - .str()}); - } - - return ret; - }(); - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - auto stateDir = RPC::ShardArchiveHandler::getDownloadDirectory( - env.app().config()); - - boost::filesystem::copy_file( - stateDir / stateDBName, - boost::filesystem::path(tempDir.path()) / stateDBName); - - BEAST_EXPECT(handler->start()); - - std::unique_lock lock(handler->m_); - - BEAST_EXPECT( - boost::filesystem::exists(stateDir) || - handler->archives_.empty()); - - using namespace std::chrono_literals; - auto waitMax = 60s; - - while (!handler->archives_.empty()) - { - lock.unlock(); - std::this_thread::sleep_for(1s); - - if (waitMax -= 1s; waitMax <= 0s) - { - BEAST_EXPECT(false); - break; - } - - lock.lock(); - } - - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - - boost::filesystem::create_directory(stateDir); - - boost::filesystem::copy_file( - boost::filesystem::path(tempDir.path()) / stateDBName, - stateDir / stateDBName); - } - - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "20"); - section.set("shard_verification_retry_interval", "1"); - section.set("shard_verification_max_attempts", "10000"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - jtx::Env env( - *this, std::move(c), nullptr, beast::severities::kDisabled); - std::uint8_t const numberOfDownloads = 10; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfDownloads + 1); - ++i) - { - env.close(); - } - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(dynamic_cast(handler) != nullptr); - - auto stateDir = - RPC::ShardArchiveHandler::getDownloadDirectory(env.app().config()); - - std::unique_lock lock(handler->m_); - - BEAST_EXPECT( - boost::filesystem::exists(stateDir) || handler->archives_.empty()); - - using namespace std::chrono_literals; - auto waitMax = 60s; - - while (!handler->archives_.empty()) - { - lock.unlock(); - std::this_thread::sleep_for(1s); - - if (waitMax -= 1s; waitMax <= 0s) - { - BEAST_EXPECT(false); - break; - } - - lock.lock(); - } - - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - } - - // Ensure that downloads fail when the shard - // database cannot store any more shards - void - testShardCountFailure() - { - testcase("testShardCountFailure"); - std::string capturedLogs; - - { - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "1"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - std::unique_ptr logs(new CaptureLogs(&capturedLogs)); - jtx::Env env(*this, std::move(c), std::move(logs)); - - std::uint8_t const numberOfDownloads = 10; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfDownloads + 1); - ++i) - { - env.close(); - } - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT( - dynamic_cast(handler) == nullptr); - - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - server->stop(); - - Downloads const dl = [count = numberOfDownloads, &host, &port] { - Downloads ret; - - for (int i = 1; i <= count; ++i) - { - ret.push_back( - {i, - (boost::format("https://%s:%d/%d.tar.lz4") % host % - port % i) - .str()}); - } - - return ret; - }(); - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - BEAST_EXPECT(!handler->start()); - auto stateDir = RPC::ShardArchiveHandler::getDownloadDirectory( - env.app().config()); - - handler->release(); - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - } - - auto const expectedErrorMessage = - "shards 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 maximum number of historical " - "shards reached"; - BEAST_EXPECT( - capturedLogs.find(expectedErrorMessage) != std::string::npos); - - { - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "0"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - std::unique_ptr logs(new CaptureLogs(&capturedLogs)); - jtx::Env env(*this, std::move(c), std::move(logs)); - - std::uint8_t const numberOfDownloads = 1; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - ((numberOfDownloads * 3) + 1); - ++i) - { - env.close(); - } - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT( - dynamic_cast(handler) == nullptr); - - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - server->stop(); - - Downloads const dl = [count = numberOfDownloads, &host, &port] { - Downloads ret; - - for (int i = 1; i <= count; ++i) - { - ret.push_back( - {i, - (boost::format("https://%s:%d/%d.tar.lz4") % host % - port % i) - .str()}); - } - - return ret; - }(); - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - BEAST_EXPECT(!handler->start()); - auto stateDir = RPC::ShardArchiveHandler::getDownloadDirectory( - env.app().config()); - - handler->release(); - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - } - - auto const expectedErrorMessage2 = - "shard 1 maximum number of historical shards reached"; - BEAST_EXPECT( - capturedLogs.find(expectedErrorMessage2) != std::string::npos); - } - - // Ensure that downloads fail when the shard - // database has already stored one of the - // queued shards - void - testRedundantShardFailure() - { - testcase("testRedundantShardFailure"); - std::string capturedLogs; - - { - beast::temp_dir tempDir; - - auto c = jtx::envconfig(); - { - auto& section{c->section(ConfigSection::shardDatabase())}; - section.set("path", tempDir.path()); - section.set("max_historical_shards", "1"); - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - { - auto& section{c->section(ConfigSection::nodeDatabase())}; - section.set("ledgers_per_shard", "256"); - section.set("earliest_seq", "257"); - } - c->setupControl(true, true, true); - - std::unique_ptr logs(new CaptureLogs(&capturedLogs)); - jtx::Env env( - *this, - std::move(c), - std::move(logs), - beast::severities::kDebug); - - std::uint8_t const numberOfDownloads = 10; - - // Create some ledgers so that the ShardArchiveHandler - // can verify the last ledger hash for the shard - // downloads. - for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() * - (numberOfDownloads + 1); - ++i) - { - env.close(); - } - - BEAST_EXPECT(env.app().getShardStore()->prepareShards({1})); - - auto handler = env.app().getShardArchiveHandler(); - BEAST_EXPECT(handler); - BEAST_EXPECT( - dynamic_cast(handler) == nullptr); - - auto server = createServer(env); - auto host = server->local_endpoint().address().to_string(); - auto port = std::to_string(server->local_endpoint().port()); - server->stop(); - - Downloads const dl = [count = numberOfDownloads, &host, &port] { - Downloads ret; - - for (int i = 1; i <= count; ++i) - { - ret.push_back( - {i, - (boost::format("https://%s:%d/%d.tar.lz4") % host % - port % i) - .str()}); - } - - return ret; - }(); - - for (auto const& entry : dl) - { - parsedURL url; - parseUrl(url, entry.second); - handler->add(entry.first, {url, entry.second}); - } - - BEAST_EXPECT(!handler->start()); - auto stateDir = RPC::ShardArchiveHandler::getDownloadDirectory( - env.app().config()); - - handler->release(); - BEAST_EXPECT(!boost::filesystem::exists(stateDir)); - } - - auto const expectedErrorMessage = - "shard 1 is already queued for import"; - BEAST_EXPECT( - capturedLogs.find(expectedErrorMessage) != std::string::npos); - } - - void - run() override - { - testSingleDownloadAndStateDB(); - testDownloadsAndStateDB(); - testDownloadsAndFileSystem(); - testDownloadsAndRestart(); - testShardCountFailure(); - testRedundantShardFailure(); - } -}; - -BEAST_DEFINE_TESTSUITE_PRIO(ShardArchiveHandler, app, ripple, 3); - -} // namespace test -} // namespace ripple diff --git a/src/test/rpc/Status_test.cpp b/src/test/rpc/Status_test.cpp index ab94191e1de..c68131e8131 100644 --- a/src/test/rpc/Status_test.cpp +++ b/src/test/rpc/Status_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { @@ -76,7 +76,7 @@ class codeString_test : public beast::unit_test::suite { auto s = codeString(temBAD_AMOUNT); - expect(s == "temBAD_AMOUNT: Can only send positive amounts.", s); + expect(s == "temBAD_AMOUNT: Malformed: Bad amount.", s); } { @@ -176,7 +176,7 @@ class fillJson_test : public beast::unit_test::suite "temBAD_AMOUNT", temBAD_AMOUNT, {}, - "temBAD_AMOUNT: Can only send positive amounts."); + "temBAD_AMOUNT: Malformed: Bad amount."); expectFill( "rpcBAD_SYNTAX", diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 24ceb54bb94..f1cb2f9a135 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -15,17 +15,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index 431c812d625..09423ed25d5 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include diff --git a/src/test/rpc/TransactionHistory_test.cpp b/src/test/rpc/TransactionHistory_test.cpp index 862eaaee507..63151bdaeb6 100644 --- a/src/test/rpc/TransactionHistory_test.cpp +++ b/src/test/rpc/TransactionHistory_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include #include #include #include +#include +#include +#include namespace ripple { diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index ac02dd11cda..2bd20eb3707 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -17,16 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include #include #include - +#include +#include +#include +#include +#include +#include + +#include #include #include @@ -671,6 +672,47 @@ class Transaction_test : public beast::unit_test::suite BEAST_EXPECT(jrr[jss::hash]); } + // test querying with mixed case ctid + { + Env env{*this, makeNetworkConfig(11111)}; + std::uint32_t const netID = env.app().config().NETWORK_ID; + + Account const alice = Account("alice"); + Account const bob = Account("bob"); + + std::uint32_t const startLegSeq = env.current()->info().seq; + env.fund(XRP(10000), alice, bob); + env(pay(alice, bob, XRP(10))); + env.close(); + + std::string const ctid = *RPC::encodeCTID(startLegSeq, 0, netID); + auto isUpper = [](char c) { return std::isupper(c) != 0; }; + + // Verify that there are at least two upper case letters in ctid and + // test a mixed case + if (BEAST_EXPECT( + std::count_if(ctid.begin(), ctid.end(), isUpper) > 1)) + { + // Change the first upper case letter to lower case. + std::string mixedCase = ctid; + { + auto const iter = std::find_if( + mixedCase.begin(), mixedCase.end(), isUpper); + *iter = std::tolower(*iter); + } + BEAST_EXPECT(ctid != mixedCase); + + Json::Value jsonTx; + jsonTx[jss::binary] = false; + jsonTx[jss::ctid] = mixedCase; + jsonTx[jss::id] = 1; + Json::Value const jrr = + env.rpc("json", "tx", to_string(jsonTx))[jss::result]; + BEAST_EXPECT(jrr[jss::ctid] == ctid); + BEAST_EXPECT(jrr[jss::hash]); + } + } + // test that if the network is 65535 the ctid is not in the response { Env env{*this, makeNetworkConfig(65535)}; diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index f45d6867596..603a0ad9d23 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include #include +#include +#include +#include #include #include diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index c9b11cdb992..2bd4b69c37b 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/test/rpc/Version_test.cpp b/src/test/rpc/Version_test.cpp index 34e55b2be93..b66e502b9f3 100644 --- a/src/test/rpc/Version_test.cpp +++ b/src/test/rpc/Version_test.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include #include +#include +#include namespace ripple { diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 249b0fd4512..8aa7bf19f30 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -17,13 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -33,10 +37,6 @@ #include #include #include -#include -#include -#include -#include namespace ripple { namespace test { diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index b5eb71f361c..c7d7dc3b911 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/test/shamap/FetchPack_test.cpp b/src/test/shamap/FetchPack_test.cpp index f15f3163e55..ac2d16ecc99 100644 --- a/src/test/shamap/FetchPack_test.cpp +++ b/src/test/shamap/FetchPack_test.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace tests { diff --git a/src/test/shamap/SHAMapSync_test.cpp b/src/test/shamap/SHAMapSync_test.cpp index 70e578b5fb5..627e97e152c 100644 --- a/src/test/shamap/SHAMapSync_test.cpp +++ b/src/test/shamap/SHAMapSync_test.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include namespace ripple { namespace tests { diff --git a/src/test/shamap/SHAMap_test.cpp b/src/test/shamap/SHAMap_test.cpp index 83bbc13253e..c8c877935f5 100644 --- a/src/test/shamap/SHAMap_test.cpp +++ b/src/test/shamap/SHAMap_test.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include namespace ripple { namespace tests { diff --git a/src/test/shamap/common.h b/src/test/shamap/common.h index d89acb988d7..db5a2c40acf 100644 --- a/src/test/shamap/common.h +++ b/src/test/shamap/common.h @@ -20,11 +20,10 @@ #ifndef RIPPLE_SHAMAP_TESTS_COMMON_H_INCLUDED #define RIPPLE_SHAMAP_TESTS_COMMON_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { namespace tests { @@ -81,12 +80,14 @@ class TestNodeFamily : public Family return j_; } - std::shared_ptr getFullBelowCache(std::uint32_t) override + std::shared_ptr + getFullBelowCache() override { return fbCache_; } - std::shared_ptr getTreeNodeCache(std::uint32_t) override + std::shared_ptr + getTreeNodeCache() override { return tnCache_; } @@ -98,12 +99,6 @@ class TestNodeFamily : public Family tnCache_->sweep(); } - bool - isShardBacked() const override - { - return true; - } - void missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) override diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index 3c79fb11b8e..246ac262378 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -20,9 +20,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #ifndef TEST_UNIT_TEST_DIRGUARD_H #define TEST_UNIT_TEST_DIRGUARD_H -#include -#include #include +#include +#include namespace ripple { namespace test { diff --git a/src/test/unit_test/SuiteJournal.h b/src/test/unit_test/SuiteJournal.h index 0e80e83cd7e..211bec0e4d3 100644 --- a/src/test/unit_test/SuiteJournal.h +++ b/src/test/unit_test/SuiteJournal.h @@ -20,8 +20,8 @@ #ifndef TEST_UNIT_TEST_SUITE_JOURNAL_H #define TEST_UNIT_TEST_SUITE_JOURNAL_H -#include -#include +#include +#include namespace ripple { namespace test { diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index cd84c3c999f..60487cadfb8 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -19,7 +19,7 @@ #include -#include +#include #include diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index c003a99f998..a4435dd1af7 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -20,8 +20,8 @@ #ifndef TEST_UNIT_TEST_MULTI_RUNNER_H #define TEST_UNIT_TEST_MULTI_RUNNER_H -#include -#include +#include +#include #include #include diff --git a/src/ripple/README.md b/src/xrpld/README.md similarity index 100% rename from src/ripple/README.md rename to src/xrpld/README.md diff --git a/src/ripple/app/consensus/RCLCensorshipDetector.h b/src/xrpld/app/consensus/RCLCensorshipDetector.h similarity index 98% rename from src/ripple/app/consensus/RCLCensorshipDetector.h rename to src/xrpld/app/consensus/RCLCensorshipDetector.h index f661a80362b..12a768ca4c8 100644 --- a/src/ripple/app/consensus/RCLCensorshipDetector.h +++ b/src/xrpld/app/consensus/RCLCensorshipDetector.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_CONSENSUS_RCLCENSORSHIPDETECTOR_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCENSORSHIPDETECTOR_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp similarity index 96% rename from src/ripple/app/consensus/RCLConsensus.cpp rename to src/xrpld/app/consensus/RCLConsensus.cpp index 0335e9979d2..263d660d003 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -17,32 +17,31 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/xrpld/app/consensus/RCLConsensus.h similarity index 96% rename from src/ripple/app/consensus/RCLConsensus.h rename to src/xrpld/app/consensus/RCLConsensus.h index f8c01e93caa..893e5cf0847 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/xrpld/app/consensus/RCLConsensus.h @@ -20,21 +20,21 @@ #ifndef RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/consensus/RCLCxLedger.h b/src/xrpld/app/consensus/RCLCxLedger.h similarity index 95% rename from src/ripple/app/consensus/RCLCxLedger.h rename to src/xrpld/app/consensus/RCLCxLedger.h index b30bef135c0..ed7ad9faeb7 100644 --- a/src/ripple/app/consensus/RCLCxLedger.h +++ b/src/xrpld/app/consensus/RCLCxLedger.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/consensus/RCLCxPeerPos.cpp b/src/xrpld/app/consensus/RCLCxPeerPos.cpp similarity index 91% rename from src/ripple/app/consensus/RCLCxPeerPos.cpp rename to src/xrpld/app/consensus/RCLCxPeerPos.cpp index ee5a45b943f..74747853a2d 100644 --- a/src/ripple/app/consensus/RCLCxPeerPos.cpp +++ b/src/xrpld/app/consensus/RCLCxPeerPos.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/consensus/RCLCxPeerPos.h b/src/xrpld/app/consensus/RCLCxPeerPos.h similarity index 93% rename from src/ripple/app/consensus/RCLCxPeerPos.h rename to src/xrpld/app/consensus/RCLCxPeerPos.h index e82a85d422b..4236e2ab128 100644 --- a/src/ripple/app/consensus/RCLCxPeerPos.h +++ b/src/xrpld/app/consensus/RCLCxPeerPos.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_CONSENSUS_RCLCXPEERPOS_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCXPEERPOS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/consensus/RCLCxTx.h b/src/xrpld/app/consensus/RCLCxTx.h similarity index 97% rename from src/ripple/app/consensus/RCLCxTx.h rename to src/xrpld/app/consensus/RCLCxTx.h index c6abfdfee94..58e58ac3b7d 100644 --- a/src/ripple/app/consensus/RCLCxTx.h +++ b/src/xrpld/app/consensus/RCLCxTx.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/xrpld/app/consensus/RCLValidations.cpp similarity index 93% rename from src/ripple/app/consensus/RCLValidations.cpp rename to src/xrpld/app/consensus/RCLValidations.cpp index b5069bd25c9..096ec56df6f 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/xrpld/app/consensus/RCLValidations.cpp @@ -17,20 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/xrpld/app/consensus/RCLValidations.h similarity index 96% rename from src/ripple/app/consensus/RCLValidations.h rename to src/xrpld/app/consensus/RCLValidations.h index e141731e14d..7b1559fba43 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/xrpld/app/consensus/RCLValidations.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED #define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/consensus/README.md b/src/xrpld/app/consensus/README.md similarity index 100% rename from src/ripple/app/consensus/README.md rename to src/xrpld/app/consensus/README.md diff --git a/src/ripple/app/ledger/AbstractFetchPackContainer.h b/src/xrpld/app/ledger/AbstractFetchPackContainer.h similarity index 96% rename from src/ripple/app/ledger/AbstractFetchPackContainer.h rename to src/xrpld/app/ledger/AbstractFetchPackContainer.h index 43552a757b5..2b27471c64a 100644 --- a/src/ripple/app/ledger/AbstractFetchPackContainer.h +++ b/src/xrpld/app/ledger/AbstractFetchPackContainer.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_ABSTRACTFETCHPACKCONTAINER_H_INCLUDED #define RIPPLE_APP_LEDGER_ABSTRACTFETCHPACKCONTAINER_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/AcceptedLedger.cpp b/src/xrpld/app/ledger/AcceptedLedger.cpp similarity index 82% rename from src/ripple/app/ledger/AcceptedLedger.cpp rename to src/xrpld/app/ledger/AcceptedLedger.cpp index 4f308653dcf..a82323f6286 100644 --- a/src/ripple/app/ledger/AcceptedLedger.cpp +++ b/src/xrpld/app/ledger/AcceptedLedger.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { @@ -36,17 +36,8 @@ AcceptedLedger::AcceptedLedger( ledger, item.first, item.second)); }; - if (app.config().reporting()) - { - auto const txs = flatFetchTransactions(*ledger, app); - transactions_.reserve(txs.size()); - insertAll(txs); - } - else - { - transactions_.reserve(256); - insertAll(ledger->txs); - } + transactions_.reserve(256); + insertAll(ledger->txs); std::sort( transactions_.begin(), diff --git a/src/ripple/app/ledger/AcceptedLedger.h b/src/xrpld/app/ledger/AcceptedLedger.h similarity index 96% rename from src/ripple/app/ledger/AcceptedLedger.h rename to src/xrpld/app/ledger/AcceptedLedger.h index 0187fdfb679..65a782aef70 100644 --- a/src/ripple/app/ledger/AcceptedLedger.h +++ b/src/xrpld/app/ledger/AcceptedLedger.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_ACCEPTEDLEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_ACCEPTEDLEDGER_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/AcceptedLedgerTx.cpp b/src/xrpld/app/ledger/AcceptedLedgerTx.cpp similarity index 93% rename from src/ripple/app/ledger/AcceptedLedgerTx.cpp rename to src/xrpld/app/ledger/AcceptedLedgerTx.cpp index 613a91e437a..e1ad68dff37 100644 --- a/src/ripple/app/ledger/AcceptedLedgerTx.cpp +++ b/src/xrpld/app/ledger/AcceptedLedgerTx.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/AcceptedLedgerTx.h b/src/xrpld/app/ledger/AcceptedLedgerTx.h similarity index 97% rename from src/ripple/app/ledger/AcceptedLedgerTx.h rename to src/xrpld/app/ledger/AcceptedLedgerTx.h index 2995d447bba..725057aa02c 100644 --- a/src/ripple/app/ledger/AcceptedLedgerTx.h +++ b/src/xrpld/app/ledger/AcceptedLedgerTx.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_ACCEPTEDLEDGERTX_H_INCLUDED #define RIPPLE_APP_LEDGER_ACCEPTEDLEDGERTX_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/AccountStateSF.cpp b/src/xrpld/app/ledger/AccountStateSF.cpp similarity index 96% rename from src/ripple/app/ledger/AccountStateSF.cpp rename to src/xrpld/app/ledger/AccountStateSF.cpp index 5c66469d5ab..c1f2ca26fc5 100644 --- a/src/ripple/app/ledger/AccountStateSF.cpp +++ b/src/xrpld/app/ledger/AccountStateSF.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/AccountStateSF.h b/src/xrpld/app/ledger/AccountStateSF.h similarity index 92% rename from src/ripple/app/ledger/AccountStateSF.h rename to src/xrpld/app/ledger/AccountStateSF.h index 43822add593..16cc686e3de 100644 --- a/src/ripple/app/ledger/AccountStateSF.h +++ b/src/xrpld/app/ledger/AccountStateSF.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_ACCOUNTSTATESF_H_INCLUDED #define RIPPLE_APP_LEDGER_ACCOUNTSTATESF_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/BookListeners.cpp b/src/xrpld/app/ledger/BookListeners.cpp similarity index 94% rename from src/ripple/app/ledger/BookListeners.cpp rename to src/xrpld/app/ledger/BookListeners.cpp index 3c7e013e1dd..2379807a91a 100644 --- a/src/ripple/app/ledger/BookListeners.cpp +++ b/src/xrpld/app/ledger/BookListeners.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/BookListeners.h b/src/xrpld/app/ledger/BookListeners.h similarity index 96% rename from src/ripple/app/ledger/BookListeners.h rename to src/xrpld/app/ledger/BookListeners.h index 605cf6dc6af..6ac52de28ee 100644 --- a/src/ripple/app/ledger/BookListeners.h +++ b/src/xrpld/app/ledger/BookListeners.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED #define RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/BuildLedger.h b/src/xrpld/app/ledger/BuildLedger.h similarity index 96% rename from src/ripple/app/ledger/BuildLedger.h rename to src/xrpld/app/ledger/BuildLedger.h index acefee24e70..409fa3529c4 100644 --- a/src/ripple/app/ledger/BuildLedger.h +++ b/src/xrpld/app/ledger/BuildLedger.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_BUILD_LEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_BUILD_LEDGER_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/ConsensusTransSetSF.cpp b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp similarity index 88% rename from src/ripple/app/ledger/ConsensusTransSetSF.cpp rename to src/xrpld/app/ledger/ConsensusTransSetSF.cpp index 476c757515f..4aed7d94bf3 100644 --- a/src/ripple/app/ledger/ConsensusTransSetSF.cpp +++ b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/ConsensusTransSetSF.h b/src/xrpld/app/ledger/ConsensusTransSetSF.h similarity index 94% rename from src/ripple/app/ledger/ConsensusTransSetSF.h rename to src/xrpld/app/ledger/ConsensusTransSetSF.h index ad5b2a23a48..857f2b8eae2 100644 --- a/src/ripple/app/ledger/ConsensusTransSetSF.h +++ b/src/xrpld/app/ledger/ConsensusTransSetSF.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_CONSENSUSTRANSSETSF_H_INCLUDED #define RIPPLE_APP_LEDGER_CONSENSUSTRANSSETSF_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h similarity index 95% rename from src/ripple/app/ledger/InboundLedger.h rename to src/xrpld/app/ledger/InboundLedger.h index 357a6fcad1e..13f603e79d0 100644 --- a/src/ripple/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_LEDGER_INBOUNDLEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_INBOUNDLEDGER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -42,7 +42,6 @@ class InboundLedger final : public TimeoutCounter, // These are the reasons we might acquire a ledger enum class Reason { HISTORY, // Acquiring past ledger - SHARD, // Acquiring for shard GENERIC, // Generic other reasons CONSENSUS // We believe the consensus round requires this ledger }; diff --git a/src/ripple/app/ledger/InboundLedgers.h b/src/xrpld/app/ledger/InboundLedgers.h similarity index 97% rename from src/ripple/app/ledger/InboundLedgers.h rename to src/xrpld/app/ledger/InboundLedgers.h index aa173925893..937a1515890 100644 --- a/src/ripple/app/ledger/InboundLedgers.h +++ b/src/xrpld/app/ledger/InboundLedgers.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_INBOUNDLEDGERS_H_INCLUDED #define RIPPLE_APP_LEDGER_INBOUNDLEDGERS_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/InboundTransactions.h b/src/xrpld/app/ledger/InboundTransactions.h similarity index 96% rename from src/ripple/app/ledger/InboundTransactions.h rename to src/xrpld/app/ledger/InboundTransactions.h index f5b44f38616..368375c07c5 100644 --- a/src/ripple/app/ledger/InboundTransactions.h +++ b/src/xrpld/app/ledger/InboundTransactions.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_INBOUNDTRANSACTIONS_H_INCLUDED #define RIPPLE_APP_LEDGER_INBOUNDTRANSACTIONS_H_INCLUDED -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/InboundTransactions.uml b/src/xrpld/app/ledger/InboundTransactions.uml similarity index 100% rename from src/ripple/app/ledger/InboundTransactions.uml rename to src/xrpld/app/ledger/InboundTransactions.uml diff --git a/src/ripple/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp similarity index 86% rename from src/ripple/app/ledger/Ledger.cpp rename to src/xrpld/app/ledger/Ledger.cpp index 7cd6f89cad3..4991b551cd1 100644 --- a/src/ripple/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -17,45 +17,43 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include +#include namespace ripple { @@ -258,11 +256,6 @@ Ledger::Ledger( if (info_.txHash.isNonZero() && !txMap_.fetchRoot(SHAMapHash{info_.txHash}, nullptr)) { - if (config.reporting()) - { - // Reporting should never have incomplete data - Throw("Missing tx map root for ledger"); - } loaded = false; JLOG(j.warn()) << "Don't have transaction root for ledger" << info_.seq; } @@ -270,11 +263,6 @@ Ledger::Ledger( if (info_.accountHash.isNonZero() && !stateMap_.fetchRoot(SHAMapHash{info_.accountHash}, nullptr)) { - if (config.reporting()) - { - // Reporting should never have incomplete data - Throw("Missing state map root for ledger"); - } loaded = false; JLOG(j.warn()) << "Don't have state data root for ledger" << info_.seq; } @@ -289,7 +277,7 @@ Ledger::Ledger( if (!loaded) { info_.hash = calculateLedgerHash(info_); - if (acquire && !config.reporting()) + if (acquire) family.missingNodeAcquireByHash(info_.hash, info_.seq); } } @@ -793,7 +781,7 @@ Ledger::updateNegativeUNL() if (hasToDisable) { - newNUnl.emplace_back(sfDisabledValidator); + newNUnl.push_back(STObject::makeInnerObject(sfDisabledValidator)); newNUnl.back().setFieldVL( sfPublicKey, sle->getFieldVL(sfValidatorToDisable)); newNUnl.back().setFieldU32(sfFirstLedgerSequence, seq()); @@ -1146,92 +1134,4 @@ loadByHash(uint256 const& ledgerHash, Application& app, bool acquire) return {}; } -std::vector< - std::pair, std::shared_ptr>> -flatFetchTransactions(Application& app, std::vector& nodestoreHashes) -{ - if (!app.config().reporting()) - { - assert(false); - Throw( - "flatFetchTransactions: not running in reporting mode"); - } - - std::vector< - std::pair, std::shared_ptr>> - txns; - auto start = std::chrono::system_clock::now(); - auto nodeDb = - dynamic_cast(&(app.getNodeStore())); - if (!nodeDb) - { - assert(false); - Throw( - "Called flatFetchTransactions but database is not DatabaseNodeImp"); - } - auto objs = nodeDb->fetchBatch(nodestoreHashes); - - auto end = std::chrono::system_clock::now(); - JLOG(app.journal("Ledger").debug()) - << " Flat fetch time : " << ((end - start).count() / 1000000000.0) - << " number of transactions " << nodestoreHashes.size(); - assert(objs.size() == nodestoreHashes.size()); - for (size_t i = 0; i < objs.size(); ++i) - { - uint256& nodestoreHash = nodestoreHashes[i]; - auto& obj = objs[i]; - if (obj) - { - auto node = SHAMapTreeNode::makeFromPrefix( - makeSlice(obj->getData()), SHAMapHash{nodestoreHash}); - if (!node) - { - assert(false); - Throw( - "flatFetchTransactions : Error making SHAMap node"); - } - auto item = (static_cast(node.get()))->peekItem(); - if (!item) - { - assert(false); - Throw( - "flatFetchTransactions : Error reading SHAMap node"); - } - auto txnPlusMeta = deserializeTxPlusMeta(*item); - if (!txnPlusMeta.first || !txnPlusMeta.second) - { - assert(false); - Throw( - "flatFetchTransactions : Error deserializing SHAMap node"); - } - txns.push_back(std::move(txnPlusMeta)); - } - else - { - assert(false); - Throw( - "flatFetchTransactions : Containing SHAMap node not found"); - } - } - return txns; -} -std::vector< - std::pair, std::shared_ptr>> -flatFetchTransactions(ReadView const& ledger, Application& app) -{ - if (!app.config().reporting()) - { - assert(false); - return {}; - } - - auto const db = - dynamic_cast(&app.getRelationalDatabase()); - if (!db) - Throw("Failed to get relational database"); - - auto nodestoreHashes = db->getTxHashes(ledger.info().seq); - - return flatFetchTransactions(app, nodestoreHashes); -} } // namespace ripple diff --git a/src/ripple/app/ledger/Ledger.h b/src/xrpld/app/ledger/Ledger.h similarity index 86% rename from src/ripple/app/ledger/Ledger.h rename to src/xrpld/app/ledger/Ledger.h index 051b322e27a..0eb102eb518 100644 --- a/src/ripple/app/ledger/Ledger.h +++ b/src/xrpld/app/ledger/Ledger.h @@ -20,17 +20,17 @@ #ifndef RIPPLE_APP_LEDGER_LEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -454,32 +454,6 @@ loadByHash(uint256 const& ledgerHash, Application& app, bool acquire = true); extern std::tuple, std::uint32_t, uint256> getLatestLedger(Application& app); -// *** Reporting Mode Only *** -// Fetch all of the transactions contained in ledger from the nodestore. -// The transactions are fetched directly as a batch, instead of traversing the -// transaction SHAMap. Fetching directly is significantly faster than -// traversing, as there are less database reads, and all of the reads can -// executed concurrently. This function only works in reporting mode. -// @param ledger the ledger for which to fetch the contained transactions -// @param app reference to the Application -// @return vector of (transaction, metadata) pairs -extern std::vector< - std::pair, std::shared_ptr>> -flatFetchTransactions(ReadView const& ledger, Application& app); - -// *** Reporting Mode Only *** -// For each nodestore hash, fetch the transaction. -// The transactions are fetched directly as a batch, instead of traversing the -// transaction SHAMap. Fetching directly is significantly faster than -// traversing, as there are less database reads, and all of the reads can -// executed concurrently. This function only works in reporting mode. -// @param nodestoreHashes hashes of the transactions to fetch -// @param app reference to the Application -// @return vector of (transaction, metadata) pairs -extern std::vector< - std::pair, std::shared_ptr>> -flatFetchTransactions(Application& app, std::vector& nodestoreHashes); - /** Deserialize a SHAMapItem containing a single STTx Throw: diff --git a/src/ripple/app/ledger/LedgerCleaner.h b/src/xrpld/app/ledger/LedgerCleaner.h similarity index 92% rename from src/ripple/app/ledger/LedgerCleaner.h rename to src/xrpld/app/ledger/LedgerCleaner.h index 9f82a851f21..251e8d51281 100644 --- a/src/ripple/app/ledger/LedgerCleaner.h +++ b/src/xrpld/app/ledger/LedgerCleaner.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERCLEANER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERCLEANER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp similarity index 88% rename from src/ripple/app/ledger/LedgerHistory.cpp rename to src/xrpld/app/ledger/LedgerHistory.cpp index ed2ccd07434..b63cd84772a 100644 --- a/src/ripple/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -204,30 +204,30 @@ log_metadata_difference( { JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and index!"; - JLOG(j.debug()) << " Built:" - << " Result: " << builtMetaData->getResult() - << " Index: " << builtMetaData->getIndex(); - JLOG(j.debug()) << " Valid:" - << " Result: " << validMetaData->getResult() - << " Index: " << validMetaData->getIndex(); + JLOG(j.debug()) + << " Built:" << " Result: " << builtMetaData->getResult() + << " Index: " << builtMetaData->getIndex(); + JLOG(j.debug()) + << " Valid:" << " Result: " << validMetaData->getResult() + << " Index: " << validMetaData->getIndex(); } else if (result_diff) { JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result!"; - JLOG(j.debug()) << " Built:" - << " Result: " << builtMetaData->getResult(); - JLOG(j.debug()) << " Valid:" - << " Result: " << validMetaData->getResult(); + JLOG(j.debug()) + << " Built:" << " Result: " << builtMetaData->getResult(); + JLOG(j.debug()) + << " Valid:" << " Result: " << validMetaData->getResult(); } else if (index_diff) { JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index!"; - JLOG(j.debug()) << " Built:" - << " Index: " << builtMetaData->getIndex(); - JLOG(j.debug()) << " Valid:" - << " Index: " << validMetaData->getIndex(); + JLOG(j.debug()) + << " Built:" << " Index: " << builtMetaData->getIndex(); + JLOG(j.debug()) + << " Valid:" << " Index: " << validMetaData->getIndex(); } } else @@ -246,12 +246,12 @@ log_metadata_difference( JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and nodes!"; JLOG(j.debug()) - << " Built:" - << " Result: " << builtMetaData->getResult() << " Nodes:\n" + << " Built:" << " Result: " << builtMetaData->getResult() + << " Nodes:\n" << builtNodes.getJson(JsonOptions::none); JLOG(j.debug()) - << " Valid:" - << " Result: " << validMetaData->getResult() << " Nodes:\n" + << " Valid:" << " Result: " << validMetaData->getResult() + << " Nodes:\n" << validNodes.getJson(JsonOptions::none); } else if (index_diff) @@ -259,23 +259,21 @@ log_metadata_difference( JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index and nodes!"; JLOG(j.debug()) - << " Built:" - << " Index: " << builtMetaData->getIndex() << " Nodes:\n" + << " Built:" << " Index: " << builtMetaData->getIndex() + << " Nodes:\n" << builtNodes.getJson(JsonOptions::none); JLOG(j.debug()) - << " Valid:" - << " Index: " << validMetaData->getIndex() << " Nodes:\n" + << " Valid:" << " Index: " << validMetaData->getIndex() + << " Nodes:\n" << validNodes.getJson(JsonOptions::none); } else // nodes_diff { JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different nodes!"; - JLOG(j.debug()) << " Built:" - << " Nodes:\n" + JLOG(j.debug()) << " Built:" << " Nodes:\n" << builtNodes.getJson(JsonOptions::none); - JLOG(j.debug()) << " Valid:" - << " Nodes:\n" + JLOG(j.debug()) << " Valid:" << " Nodes:\n" << validNodes.getJson(JsonOptions::none); } } @@ -330,10 +328,10 @@ LedgerHistory::handleMismatch( if (!builtLedger || !validLedger) { - JLOG(j_.error()) << "MISMATCH cannot be analyzed:" - << " builtLedger: " << to_string(built) << " -> " - << builtLedger << " validLedger: " << to_string(valid) - << " -> " << validLedger; + JLOG(j_.error()) << "MISMATCH cannot be analyzed:" << " builtLedger: " + << to_string(built) << " -> " << builtLedger + << " validLedger: " << to_string(valid) << " -> " + << validLedger; return; } diff --git a/src/ripple/app/ledger/LedgerHistory.h b/src/xrpld/app/ledger/LedgerHistory.h similarity index 96% rename from src/ripple/app/ledger/LedgerHistory.h rename to src/xrpld/app/ledger/LedgerHistory.h index 092ad7d8371..9d414e4aad3 100644 --- a/src/ripple/app/ledger/LedgerHistory.h +++ b/src/xrpld/app/ledger/LedgerHistory.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERHISTORY_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERHISTORY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/ledger/LedgerHolder.h b/src/xrpld/app/ledger/LedgerHolder.h similarity index 96% rename from src/ripple/app/ledger/LedgerHolder.h rename to src/xrpld/app/ledger/LedgerHolder.h index 93d67400e05..5636a8ca20d 100644 --- a/src/ripple/app/ledger/LedgerHolder.h +++ b/src/xrpld/app/ledger/LedgerHolder.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/LedgerMaster.h b/src/xrpld/app/ledger/LedgerMaster.h similarity index 87% rename from src/ripple/app/ledger/LedgerMaster.h rename to src/xrpld/app/ledger/LedgerMaster.h index 980506c2267..dd7f0b6a614 100644 --- a/src/ripple/app/ledger/LedgerMaster.h +++ b/src/xrpld/app/ledger/LedgerMaster.h @@ -20,23 +20,23 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERMASTER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERMASTER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -46,24 +46,6 @@ namespace ripple { class Peer; class Transaction; -// This error is thrown when a codepath tries to access the open or closed -// ledger while the server is running in reporting mode. Any RPCs that request -// the open or closed ledger should be forwarded to a p2p node. Usually, the -// decision to forward is made based on the required condition of the handler, -// or which ledger is specified. However, there are some codepaths which are not -// covered by the aforementioned logic (though they probably should), so this -// error is thrown in case a codepath falls through the cracks. -class ReportingShouldProxy : public std::runtime_error -{ -public: - ReportingShouldProxy() - : std::runtime_error( - "Reporting mode has no open or closed ledger. Proxy this " - "request") - { - } -}; - // Tracks the current ledger and any ledgers in the process of closing // Tracks ledger history // Tracks held transactions @@ -97,10 +79,6 @@ class LedgerMaster : public AbstractFetchPackContainer std::shared_ptr getClosedLedger() { - if (app_.config().reporting()) - { - Throw(); - } return mClosedLedger.get(); } @@ -357,9 +335,6 @@ class LedgerMaster : public AbstractFetchPackContainer // The last ledger we handled fetching history std::shared_ptr mHistLedger; - // The last ledger we handled fetching for a shard - std::shared_ptr mShardLedger; - // Fully validated ledger, whether or not we have the ledger resident. std::pair mLastValidLedger{uint256(), 0}; diff --git a/src/ripple/app/ledger/LedgerReplay.h b/src/xrpld/app/ledger/LedgerReplay.h similarity index 98% rename from src/ripple/app/ledger/LedgerReplay.h rename to src/xrpld/app/ledger/LedgerReplay.h index 0365dea1b7e..8e52c8d5d5d 100644 --- a/src/ripple/app/ledger/LedgerReplay.h +++ b/src/xrpld/app/ledger/LedgerReplay.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/app/ledger/LedgerReplayTask.h b/src/xrpld/app/ledger/LedgerReplayTask.h similarity index 97% rename from src/ripple/app/ledger/LedgerReplayTask.h rename to src/xrpld/app/ledger/LedgerReplayTask.h index 4330f0b6247..54863e70956 100644 --- a/src/ripple/app/ledger/LedgerReplayTask.h +++ b/src/xrpld/app/ledger/LedgerReplayTask.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERREPLAYTASK_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERREPLAYTASK_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/LedgerReplayer.h b/src/xrpld/app/ledger/LedgerReplayer.h similarity index 96% rename from src/ripple/app/ledger/LedgerReplayer.h rename to src/xrpld/app/ledger/LedgerReplayer.h index b06dd2cc858..4ce4b20b221 100644 --- a/src/ripple/app/ledger/LedgerReplayer.h +++ b/src/xrpld/app/ledger/LedgerReplayer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERREPLAYER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERREPLAYER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/LedgerToJson.h b/src/xrpld/app/ledger/LedgerToJson.h similarity index 85% rename from src/ripple/app/ledger/LedgerToJson.h rename to src/xrpld/app/ledger/LedgerToJson.h index 78947ca91d1..8f9316cbc66 100644 --- a/src/ripple/app/ledger/LedgerToJson.h +++ b/src/xrpld/app/ledger/LedgerToJson.h @@ -20,16 +20,16 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERTOJSON_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERTOJSON_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/LocalTxs.h b/src/xrpld/app/ledger/LocalTxs.h similarity index 95% rename from src/ripple/app/ledger/LocalTxs.h rename to src/xrpld/app/ledger/LocalTxs.h index f427a5e0477..638e070f444 100644 --- a/src/ripple/app/ledger/LocalTxs.h +++ b/src/xrpld/app/ledger/LocalTxs.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_LOCALTXS_H_INCLUDED #define RIPPLE_APP_LEDGER_LOCALTXS_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/OpenLedger.h b/src/xrpld/app/ledger/OpenLedger.h similarity index 96% rename from src/ripple/app/ledger/OpenLedger.h rename to src/xrpld/app/ledger/OpenLedger.h index e3cb2b10028..b218b1d6e11 100644 --- a/src/ripple/app/ledger/OpenLedger.h +++ b/src/xrpld/app/ledger/OpenLedger.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_LEDGER_OPENLEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_OPENLEDGER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/OrderBookDB.cpp b/src/xrpld/app/ledger/OrderBookDB.cpp similarity index 96% rename from src/ripple/app/ledger/OrderBookDB.cpp rename to src/xrpld/app/ledger/OrderBookDB.cpp index e1fc8a248f0..d0eddadbacb 100644 --- a/src/ripple/app/ledger/OrderBookDB.cpp +++ b/src/xrpld/app/ledger/OrderBookDB.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/OrderBookDB.h b/src/xrpld/app/ledger/OrderBookDB.h similarity index 93% rename from src/ripple/app/ledger/OrderBookDB.h rename to src/xrpld/app/ledger/OrderBookDB.h index 45705a61572..ce0d9f0fafe 100644 --- a/src/ripple/app/ledger/OrderBookDB.h +++ b/src/xrpld/app/ledger/OrderBookDB.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_LEDGER_ORDERBOOKDB_H_INCLUDED #define RIPPLE_APP_LEDGER_ORDERBOOKDB_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/ledger/PendingSaves.h b/src/xrpld/app/ledger/PendingSaves.h similarity index 99% rename from src/ripple/app/ledger/PendingSaves.h rename to src/xrpld/app/ledger/PendingSaves.h index f031baf4a93..d296fcacc96 100644 --- a/src/ripple/app/ledger/PendingSaves.h +++ b/src/xrpld/app/ledger/PendingSaves.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_PENDINGSAVES_H_INCLUDED #define RIPPLE_APP_PENDINGSAVES_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/app/ledger/README.md b/src/xrpld/app/ledger/README.md similarity index 100% rename from src/ripple/app/ledger/README.md rename to src/xrpld/app/ledger/README.md diff --git a/src/ripple/app/ledger/TransactionMaster.h b/src/xrpld/app/ledger/TransactionMaster.h similarity index 93% rename from src/ripple/app/ledger/TransactionMaster.h rename to src/xrpld/app/ledger/TransactionMaster.h index 9bebac37d73..65f27af6021 100644 --- a/src/ripple/app/ledger/TransactionMaster.h +++ b/src/xrpld/app/ledger/TransactionMaster.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_LEDGER_TRANSACTIONMASTER_H_INCLUDED #define RIPPLE_APP_LEDGER_TRANSACTIONMASTER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/TransactionStateSF.cpp b/src/xrpld/app/ledger/TransactionStateSF.cpp similarity index 96% rename from src/ripple/app/ledger/TransactionStateSF.cpp rename to src/xrpld/app/ledger/TransactionStateSF.cpp index f65de5f9997..fac28cd2aa8 100644 --- a/src/ripple/app/ledger/TransactionStateSF.cpp +++ b/src/xrpld/app/ledger/TransactionStateSF.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/TransactionStateSF.h b/src/xrpld/app/ledger/TransactionStateSF.h similarity index 92% rename from src/ripple/app/ledger/TransactionStateSF.h rename to src/xrpld/app/ledger/TransactionStateSF.h index 6a5fa69ebd1..721f1870621 100644 --- a/src/ripple/app/ledger/TransactionStateSF.h +++ b/src/xrpld/app/ledger/TransactionStateSF.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_TRANSACTIONSTATESF_H_INCLUDED #define RIPPLE_APP_LEDGER_TRANSACTIONSTATESF_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp similarity index 95% rename from src/ripple/app/ledger/impl/BuildLedger.cpp rename to src/xrpld/app/ledger/detail/BuildLedger.cpp index 363c110cd18..8c4a7a3f41d 100644 --- a/src/ripple/app/ledger/impl/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp similarity index 94% rename from src/ripple/app/ledger/impl/InboundLedger.cpp rename to src/xrpld/app/ledger/detail/InboundLedger.cpp index c8dc005097b..16b15c2fce7 100644 --- a/src/ripple/app/ledger/impl/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -17,21 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -112,40 +111,6 @@ InboundLedger::init(ScopedLockType& collectionLock) if (failed_) return; - if (!complete_) - { - auto shardStore = app_.getShardStore(); - if (mReason == Reason::SHARD) - { - if (!shardStore) - { - JLOG(journal_.error()) - << "Acquiring shard with no shard store available"; - failed_ = true; - return; - } - - mHaveHeader = false; - mHaveTransactions = false; - mHaveState = false; - mLedger.reset(); - - tryDB(app_.getShardFamily()->db()); - if (failed_) - return; - } - else if (shardStore && mSeq >= shardStore->earliestLedgerSeq()) - { - if (auto l = shardStore->fetchLedger(hash_, mSeq)) - { - mHaveHeader = true; - mHaveTransactions = true; - mHaveState = true; - complete_ = true; - mLedger = std::move(l); - } - } - } if (!complete_) { addPeers(); @@ -160,7 +125,7 @@ InboundLedger::init(ScopedLockType& collectionLock) mLedger->read(keylet::fees())); mLedger->setImmutable(); - if (mReason == Reason::HISTORY || mReason == Reason::SHARD) + if (mReason == Reason::HISTORY) return; app_.getLedgerMaster().storeLedger(mLedger); @@ -200,8 +165,6 @@ InboundLedger::checkLocal() { if (mLedger) tryDB(mLedger->stateMap().family().db()); - else if (mReason == Reason::SHARD) - tryDB(app_.getShardFamily()->db()); else tryDB(app_.getNodeFamily().db()); if (failed_ || complete_) @@ -283,8 +246,7 @@ InboundLedger::tryDB(NodeStore::Database& srcDB) mLedger = std::make_shared( deserializePrefixedHeader(makeSlice(data)), app_.config(), - mReason == Reason::SHARD ? *app_.getShardFamily() - : app_.getNodeFamily()); + app_.getNodeFamily()); if (mLedger->info().hash != hash_ || (mSeq != 0 && mSeq != mLedger->info().seq)) { @@ -495,9 +457,6 @@ InboundLedger::done() mLedger->setImmutable(); switch (mReason) { - case Reason::SHARD: - app_.getShardStore()->setStored(mLedger); - [[fallthrough]]; case Reason::HISTORY: app_.getInboundLedgers().onLedgerFetched(); break; @@ -551,9 +510,7 @@ InboundLedger::trigger(std::shared_ptr const& peer, TriggerReason reason) if (!mHaveHeader) { - tryDB( - mReason == Reason::SHARD ? app_.getShardFamily()->db() - : app_.getNodeFamily().db()); + tryDB(app_.getNodeFamily().db()); if (failed_) { JLOG(journal_.warn()) << " failed local for " << hash_; @@ -854,8 +811,7 @@ InboundLedger::takeHeader(std::string const& data) if (complete_ || failed_ || mHaveHeader) return true; - auto* f = mReason == Reason::SHARD ? app_.getShardFamily() - : &app_.getNodeFamily(); + auto* f = &app_.getNodeFamily(); mLedger = std::make_shared( deserializeHeader(makeSlice(data)), app_.config(), *f); if (mLedger->info().hash != hash_ || diff --git a/src/ripple/app/ledger/impl/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp similarity index 89% rename from src/ripple/app/ledger/impl/InboundLedgers.cpp rename to src/xrpld/app/ledger/detail/InboundLedgers.cpp index f88137e8501..f6d86a4d737 100644 --- a/src/ripple/app/ledger/impl/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -17,18 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -73,9 +74,6 @@ class InboundLedgersImp : public InboundLedgers { auto doAcquire = [&, seq, reason]() -> std::shared_ptr { assert(hash.isNonZero()); - assert( - reason != InboundLedger::Reason::SHARD || - (seq != 0 && app_.getShardStore())); // probably not the right rule if (app_.getOPs().isNeedNetworkLedger() && @@ -122,25 +120,6 @@ class InboundLedgersImp : public InboundLedgers if (!inbound->isComplete()) return {}; - if (reason == InboundLedger::Reason::HISTORY) - { - if (inbound->getLedger()->stateMap().family().isShardBacked()) - app_.getNodeStore().storeLedger(inbound->getLedger()); - } - else if (reason == InboundLedger::Reason::SHARD) - { - auto shardStore = app_.getShardStore(); - if (!shardStore) - { - JLOG(j_.error()) - << "Acquiring shard with no shard store available"; - return {}; - } - if (inbound->getLedger()->stateMap().family().isShardBacked()) - shardStore->setStored(inbound->getLedger()); - else - shardStore->storeLedger(inbound->getLedger()); - } return inbound->getLedger(); }; using namespace std::chrono_literals; @@ -162,7 +141,7 @@ class InboundLedgersImp : public InboundLedgers if (pendingAcquires_.contains(hash)) return; pendingAcquires_.insert(hash); - lock.unlock(); + scope_unlock unlock(lock); acquire(hash, seq, reason); } catch (std::exception const& e) @@ -177,7 +156,6 @@ class InboundLedgersImp : public InboundLedgers << "Unknown exception thrown for acquiring new inbound ledger " << hash; } - lock.lock(); pendingAcquires_.erase(hash); } @@ -325,7 +303,7 @@ class InboundLedgersImp : public InboundLedgers } // Should only be called with an inboundledger that has - // a reason of history or shard + // a reason of history void onLedgerFetched() override { diff --git a/src/ripple/app/ledger/impl/InboundTransactions.cpp b/src/xrpld/app/ledger/detail/InboundTransactions.cpp similarity index 95% rename from src/ripple/app/ledger/impl/InboundTransactions.cpp rename to src/xrpld/app/ledger/detail/InboundTransactions.cpp index 7a863bce16b..83b074f317a 100644 --- a/src/ripple/app/ledger/impl/InboundTransactions.cpp +++ b/src/xrpld/app/ledger/detail/InboundTransactions.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/impl/LedgerCleaner.cpp b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp similarity index 98% rename from src/ripple/app/ledger/impl/LedgerCleaner.cpp rename to src/xrpld/app/ledger/detail/LedgerCleaner.cpp index e5ee6409d34..3021c691c53 100644 --- a/src/ripple/app/ledger/impl/LedgerCleaner.cpp +++ b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/LedgerDeltaAcquire.cpp b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp similarity index 95% rename from src/ripple/app/ledger/impl/LedgerDeltaAcquire.cpp rename to src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp index 3c19c6ee156..e079fb3ee27 100644 --- a/src/ripple/app/ledger/impl/LedgerDeltaAcquire.cpp +++ b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/LedgerDeltaAcquire.h b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h similarity index 96% rename from src/ripple/app/ledger/impl/LedgerDeltaAcquire.h rename to src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h index 2f767cf27be..b0b2c76c245 100644 --- a/src/ripple/app/ledger/impl/LedgerDeltaAcquire.h +++ b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERDELTAACQUIRE_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERDELTAACQUIRE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp similarity index 88% rename from src/ripple/app/ledger/impl/LedgerMaster.cpp rename to src/xrpld/app/ledger/detail/LedgerMaster.cpp index 9388a3005ba..53edef17d33 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -17,41 +17,41 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -62,86 +62,6 @@ namespace ripple { -namespace { - -//============================================================================== -/** - Automatically unlocks and re-locks a unique_lock object. - - This is the reverse of a std::unique_lock object - instead of locking the - mutex for the lifetime of this object, it unlocks it. - - Make sure you don't try to unlock mutexes that aren't actually locked! - - This is essentially a less-versatile boost::reverse_lock. - - e.g. @code - - std::mutex mut; - - for (;;) - { - std::unique_lock myScopedLock{mut}; - // mut is now locked - - ... do some stuff with it locked .. - - while (xyz) - { - ... do some stuff with it locked .. - - ScopedUnlock unlocker{myScopedLock}; - - // mut is now unlocked for the remainder of this block, - // and re-locked at the end. - - ...do some stuff with it unlocked ... - } // mut gets locked here. - - } // mut gets unlocked here - @endcode -*/ -template -class ScopedUnlock -{ - std::unique_lock& lock_; - -public: - /** Creates a ScopedUnlock. - - As soon as it is created, this will unlock the unique_lock, and - when the ScopedLock object is deleted, the unique_lock will - be re-locked. - - Make sure this object is created and deleted by the same thread, - otherwise there are no guarantees what will happen! Best just to use it - as a local stack object, rather than creating on the heap. - */ - explicit ScopedUnlock(std::unique_lock& lock) : lock_(lock) - { - assert(lock_.owns_lock()); - lock_.unlock(); - } - - ScopedUnlock(ScopedUnlock const&) = delete; - ScopedUnlock& - operator=(ScopedUnlock const&) = delete; - - /** Destructor. - - The unique_lock will be locked after the destructor is called. - - Make sure this object is created and deleted by the same thread, - otherwise there are no guarantees what will happen! - */ - ~ScopedUnlock() noexcept(false) - { - lock_.lock(); - } -}; - -} // namespace - // Don't catch up more than 100 ledgers (cannot exceed 256) static constexpr int MAX_LEDGER_GAP{100}; @@ -275,12 +195,6 @@ LedgerMaster::getValidatedLedgerAge() { using namespace std::chrono_literals; -#ifdef RIPPLED_REPORTING - if (app_.config().reporting()) - return static_cast(&app_.getRelationalDatabase()) - ->getValidatedLedgerAge(); -#endif - std::chrono::seconds valClose{mValidLedgerSign.load()}; if (valClose == 0s) { @@ -306,12 +220,6 @@ LedgerMaster::isCaughtUp(std::string& reason) { using namespace std::chrono_literals; -#ifdef RIPPLED_REPORTING - if (app_.config().reporting()) - return static_cast(&app_.getRelationalDatabase()) - ->isCaughtUp(reason); -#endif - if (getPublishedLedgerAge() > 3min) { reason = "No recently-published ledger"; @@ -601,9 +509,6 @@ LedgerMaster::clearLedger(std::uint32_t seq) bool LedgerMaster::isValidated(ReadView const& ledger) { - if (app_.config().reporting()) - return true; // Reporting mode only supports validated ledger - if (ledger.open()) return false; @@ -677,32 +582,6 @@ LedgerMaster::getFullValidatedRange( bool LedgerMaster::getValidatedRange(std::uint32_t& minVal, std::uint32_t& maxVal) { - if (app_.config().reporting()) - { - std::string res = getCompleteLedgers(); - try - { - if (res == "empty" || res == "error" || res.empty()) - return false; - else if (size_t delim = res.find('-'); delim != std::string::npos) - { - minVal = std::stol(res.substr(0, delim)); - maxVal = std::stol(res.substr(delim + 1)); - } - else - { - minVal = maxVal = std::stol(res); - } - return true; - } - catch (std::exception const& e) - { - JLOG(m_journal.error()) << "LedgerMaster::getValidatedRange: " - "exception parsing complete ledgers: " - << e.what(); - return false; - } - } if (!getFullValidatedRange(minVal, maxVal)) return false; @@ -830,38 +709,13 @@ LedgerMaster::tryFill(std::shared_ptr ledger) void LedgerMaster::getFetchPack(LedgerIndex missing, InboundLedger::Reason reason) { - LedgerIndex const ledgerIndex([&]() { - if (reason == InboundLedger::Reason::SHARD) - { - // Do not acquire a ledger sequence greater - // than the last ledger in the shard - auto const shardStore{app_.getShardStore()}; - auto const shardIndex{shardStore->seqToShardIndex(missing)}; - return std::min(missing + 1, shardStore->lastLedgerSeq(shardIndex)); - } - return missing + 1; - }()); + LedgerIndex const ledgerIndex = missing + 1; auto const haveHash{getLedgerHashForHistory(ledgerIndex, reason)}; if (!haveHash || haveHash->isZero()) { - if (reason == InboundLedger::Reason::SHARD) - { - auto const shardStore{app_.getShardStore()}; - auto const shardIndex{shardStore->seqToShardIndex(missing)}; - if (missing < shardStore->lastLedgerSeq(shardIndex)) - { - JLOG(m_journal.error()) - << "No hash for fetch pack. " - << "Missing ledger sequence " << missing - << " while acquiring shard " << shardIndex; - } - } - else - { - JLOG(m_journal.error()) - << "No hash for fetch pack. Missing Index " << missing; - } + JLOG(m_journal.error()) + << "No hash for fetch pack. Missing Index " << missing; return; } @@ -1342,8 +1196,7 @@ LedgerMaster::getLedgerHashForHistory( { // Try to get the hash of a ledger we need to fetch for history std::optional ret; - auto const& l{ - reason == InboundLedger::Reason::SHARD ? mShardLedger : mHistLedger}; + auto const& l{mHistLedger}; if (l && l->info().seq >= index) { @@ -1405,7 +1258,7 @@ LedgerMaster::findNewLedgersToPublish( auto valLedger = mValidLedger.get(); std::uint32_t valSeq = valLedger->info().seq; - ScopedUnlock sul{sl}; + scope_unlock sul{sl}; try { for (std::uint32_t seq = pubSeq; seq <= valSeq; ++seq) @@ -1706,25 +1559,12 @@ LedgerMaster::peekMutex() std::shared_ptr LedgerMaster::getCurrentLedger() { - if (app_.config().reporting()) - { - Throw(); - } return app_.openLedger().current(); } std::shared_ptr LedgerMaster::getValidatedLedger() { -#ifdef RIPPLED_REPORTING - if (app_.config().reporting()) - { - auto seq = app_.getRelationalDatabase().getMaxLedgerSeq(); - if (!seq) - return {}; - return getLedgerBySeq(*seq); - } -#endif return mValidLedger.get(); } @@ -1753,11 +1593,6 @@ LedgerMaster::getPublishedLedger() std::string LedgerMaster::getCompleteLedgers() { -#ifdef RIPPLED_REPORTING - if (app_.config().reporting()) - return static_cast(&app_.getRelationalDatabase()) - ->getCompleteLedgers(); -#endif std::lock_guard sl(mCompleteLock); return to_string(mCompleteLedgers); } @@ -1969,7 +1804,7 @@ LedgerMaster::fetchForHistory( InboundLedger::Reason reason, std::unique_lock& sl) { - ScopedUnlock sul{sl}; + scope_unlock sul{sl}; if (auto hash = getLedgerHashForHistory(missing, reason)) { assert(hash->isNonZero()); @@ -2001,54 +1836,35 @@ LedgerMaster::fetchForHistory( auto seq = ledger->info().seq; assert(seq == missing); JLOG(m_journal.trace()) << "fetchForHistory acquired " << seq; - if (reason == InboundLedger::Reason::SHARD) + setFullLedger(ledger, false, false); + int fillInProgress; { - ledger->setFull(); - { - std::lock_guard lock(m_mutex); - mShardLedger = ledger; - } - if (!ledger->stateMap().family().isShardBacked()) - app_.getShardStore()->storeLedger(ledger); + std::lock_guard lock(m_mutex); + mHistLedger = ledger; + fillInProgress = mFillInProgress; } - else + if (fillInProgress == 0 && + app_.getRelationalDatabase().getHashByIndex(seq - 1) == + ledger->info().parentHash) { - setFullLedger(ledger, false, false); - int fillInProgress; { + // Previous ledger is in DB std::lock_guard lock(m_mutex); - mHistLedger = ledger; - fillInProgress = mFillInProgress; - } - if (fillInProgress == 0 && - app_.getRelationalDatabase().getHashByIndex(seq - 1) == - ledger->info().parentHash) - { - { - // Previous ledger is in DB - std::lock_guard lock(m_mutex); - mFillInProgress = seq; - } - app_.getJobQueue().addJob( - jtADVANCE, "tryFill", [this, ledger]() { - tryFill(ledger); - }); + mFillInProgress = seq; } + app_.getJobQueue().addJob( + jtADVANCE, "tryFill", [this, ledger]() { + tryFill(ledger); + }); } progress = true; } else { std::uint32_t fetchSz; - if (reason == InboundLedger::Reason::SHARD) - // Do not fetch ledger sequences lower - // than the shard's first ledger sequence - fetchSz = app_.getShardStore()->firstLedgerSeq( - app_.getShardStore()->seqToShardIndex(missing)); - else - // Do not fetch ledger sequences lower - // than the earliest ledger sequence - fetchSz = app_.getNodeStore().earliestLedgerSeq(); + // Do not fetch ledger sequences lower + // than the earliest ledger sequence + fetchSz = app_.getNodeStore().earliestLedgerSeq(); fetchSz = missing >= fetchSz ? std::min(ledger_fetch_size_, (missing - fetchSz) + 1) : 0; @@ -2081,7 +1897,8 @@ LedgerMaster::fetchForHistory( << "Ledgers: " << app_.getLedgerMaster().getCompleteLedgers(); JLOG(m_journal.fatal()) << "Acquire reason: " - << (reason == InboundLedger::Reason::HISTORY ? "HISTORY" : "SHARD"); + << (reason == InboundLedger::Reason::HISTORY ? "HISTORY" + : "NOT HISTORY"); clearLedger(missing + 1); progress = true; } @@ -2133,15 +1950,6 @@ LedgerMaster::doAdvance(std::unique_lock& sl) else missing = std::nullopt; } - if (!missing && mFillInProgress == 0) - { - if (auto shardStore = app_.getShardStore()) - { - missing = shardStore->prepareLedger(mValidLedgerSeq); - if (missing) - reason = InboundLedger::Reason::SHARD; - } - } if (missing) { fetchForHistory(*missing, progress, reason, sl); @@ -2156,7 +1964,6 @@ LedgerMaster::doAdvance(std::unique_lock& sl) else { mHistLedger.reset(); - mShardLedger.reset(); JLOG(m_journal.trace()) << "tryAdvance not fetching history"; } } @@ -2167,7 +1974,7 @@ LedgerMaster::doAdvance(std::unique_lock& sl) for (auto const& ledger : pubLedgers) { { - ScopedUnlock sul{sl}; + scope_unlock sul{sl}; JLOG(m_journal.debug()) << "tryAdvance publishing seq " << ledger->info().seq; setFullLedger(ledger, true, true); @@ -2176,7 +1983,7 @@ LedgerMaster::doAdvance(std::unique_lock& sl) setPubLedger(ledger); { - ScopedUnlock sul{sl}; + scope_unlock sul{sl}; app_.getOPs().pubLedger(ledger); } } diff --git a/src/ripple/app/ledger/impl/LedgerReplay.cpp b/src/xrpld/app/ledger/detail/LedgerReplay.cpp similarity index 95% rename from src/ripple/app/ledger/impl/LedgerReplay.cpp rename to src/xrpld/app/ledger/detail/LedgerReplay.cpp index 4a917cedf4a..40b4f9e412b 100644 --- a/src/ripple/app/ledger/impl/LedgerReplay.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplay.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/LedgerReplayMsgHandler.cpp b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp similarity index 97% rename from src/ripple/app/ledger/impl/LedgerReplayMsgHandler.cpp rename to src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp index c5301be7ea2..7b9d881d544 100644 --- a/src/ripple/app/ledger/impl/LedgerReplayMsgHandler.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/ledger/impl/LedgerReplayMsgHandler.h b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.h similarity index 97% rename from src/ripple/app/ledger/impl/LedgerReplayMsgHandler.h rename to src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.h index 169bedea057..151a21b9563 100644 --- a/src/ripple/app/ledger/impl/LedgerReplayMsgHandler.h +++ b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERREPLAYMSGHANDLER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERREPLAYMSGHANDLER_H_INCLUDED -#include -#include +#include +#include namespace ripple { class Application; diff --git a/src/ripple/app/ledger/impl/LedgerReplayTask.cpp b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp similarity index 96% rename from src/ripple/app/ledger/impl/LedgerReplayTask.cpp rename to src/xrpld/app/ledger/detail/LedgerReplayTask.cpp index 46ce9815a6e..d7f29e33b3b 100644 --- a/src/ripple/app/ledger/impl/LedgerReplayTask.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/LedgerReplayer.cpp b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp similarity index 97% rename from src/ripple/app/ledger/impl/LedgerReplayer.cpp rename to src/xrpld/app/ledger/detail/LedgerReplayer.cpp index 903f72dd117..4aa0e4beb79 100644 --- a/src/ripple/app/ledger/impl/LedgerReplayer.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/LedgerToJson.cpp b/src/xrpld/app/ledger/detail/LedgerToJson.cpp similarity index 91% rename from src/ripple/app/ledger/impl/LedgerToJson.cpp rename to src/xrpld/app/ledger/detail/LedgerToJson.cpp index 1310dd13a65..3f6869df1d8 100644 --- a/src/ripple/app/ledger/impl/LedgerToJson.cpp +++ b/src/xrpld/app/ledger/detail/LedgerToJson.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -157,6 +157,12 @@ fillJsonTx( fill.ledger, txn, {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); + + // If applicable, insert mpt issuance id + RPC::insertMPTokenIssuanceID( + txJson[jss::meta], + txn, + {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); } if (!fill.ledger.open()) @@ -188,6 +194,12 @@ fillJsonTx( fill.ledger, txn, {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); + + // If applicable, insert mpt issuance id + RPC::insertMPTokenIssuanceID( + txJson[jss::metaData], + txn, + {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); } } @@ -232,14 +244,7 @@ fillJsonTx(Object& json, LedgerFill const& fill) } }; - if (fill.context && fill.context->app.config().reporting()) - { - appendAll(flatFetchTransactions(fill.ledger, fill.context->app)); - } - else - { - appendAll(fill.ledger.txs); - } + appendAll(fill.ledger.txs); } catch (std::exception const& ex) { diff --git a/src/ripple/app/ledger/impl/LocalTxs.cpp b/src/xrpld/app/ledger/detail/LocalTxs.cpp similarity index 97% rename from src/ripple/app/ledger/impl/LocalTxs.cpp rename to src/xrpld/app/ledger/detail/LocalTxs.cpp index afee5e2d4d0..a6eb7721a3e 100644 --- a/src/ripple/app/ledger/impl/LocalTxs.cpp +++ b/src/xrpld/app/ledger/detail/LocalTxs.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include /* This code prevents scenarios like the following: diff --git a/src/ripple/app/ledger/impl/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp similarity index 94% rename from src/ripple/app/ledger/impl/OpenLedger.cpp rename to src/xrpld/app/ledger/detail/OpenLedger.cpp index 7eef84fc554..461d98ae4ac 100644 --- a/src/ripple/app/ledger/impl/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/impl/SkipListAcquire.cpp b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp similarity index 96% rename from src/ripple/app/ledger/impl/SkipListAcquire.cpp rename to src/xrpld/app/ledger/detail/SkipListAcquire.cpp index aa9b8564eb3..1d1de62b61b 100644 --- a/src/ripple/app/ledger/impl/SkipListAcquire.cpp +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/SkipListAcquire.h b/src/xrpld/app/ledger/detail/SkipListAcquire.h similarity index 96% rename from src/ripple/app/ledger/impl/SkipListAcquire.h rename to src/xrpld/app/ledger/detail/SkipListAcquire.h index df24d68312c..03c8181df68 100644 --- a/src/ripple/app/ledger/impl/SkipListAcquire.h +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_LEDGER_SKIPLISTACQUIRE_H_INCLUDED #define RIPPLE_APP_LEDGER_SKIPLISTACQUIRE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/ledger/impl/TimeoutCounter.cpp b/src/xrpld/app/ledger/detail/TimeoutCounter.cpp similarity index 91% rename from src/ripple/app/ledger/impl/TimeoutCounter.cpp rename to src/xrpld/app/ledger/detail/TimeoutCounter.cpp index 9ea20c06384..f70e54f8cd4 100644 --- a/src/ripple/app/ledger/impl/TimeoutCounter.cpp +++ b/src/xrpld/app/ledger/detail/TimeoutCounter.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -100,8 +100,8 @@ TimeoutCounter::invokeOnTimer() if (!progress_) { ++timeouts_; - JLOG(journal_.debug()) << "Timeout(" << timeouts_ << ") " - << " acquiring " << hash_; + JLOG(journal_.debug()) + << "Timeout(" << timeouts_ << ") " << " acquiring " << hash_; onTimer(false, sl); } else diff --git a/src/ripple/app/ledger/impl/TimeoutCounter.h b/src/xrpld/app/ledger/detail/TimeoutCounter.h similarity index 96% rename from src/ripple/app/ledger/impl/TimeoutCounter.h rename to src/xrpld/app/ledger/detail/TimeoutCounter.h index 88eda551acc..228e879d4de 100644 --- a/src/ripple/app/ledger/impl/TimeoutCounter.h +++ b/src/xrpld/app/ledger/detail/TimeoutCounter.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_LEDGER_TIMEOUTCOUNTER_H_INCLUDED #define RIPPLE_APP_LEDGER_TIMEOUTCOUNTER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/ledger/impl/TransactionAcquire.cpp b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp similarity index 94% rename from src/ripple/app/ledger/impl/TransactionAcquire.cpp rename to src/xrpld/app/ledger/detail/TransactionAcquire.cpp index 24a03a16ffb..b3561875e96 100644 --- a/src/ripple/app/ledger/impl/TransactionAcquire.cpp +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/ledger/impl/TransactionAcquire.h b/src/xrpld/app/ledger/detail/TransactionAcquire.h similarity index 95% rename from src/ripple/app/ledger/impl/TransactionAcquire.h rename to src/xrpld/app/ledger/detail/TransactionAcquire.h index 3863868fae0..230bce2fc94 100644 --- a/src/ripple/app/ledger/impl/TransactionAcquire.h +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_TRANSACTIONACQUIRE_H_INCLUDED #define RIPPLE_APP_LEDGER_TRANSACTIONACQUIRE_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/ledger/impl/TransactionMaster.cpp b/src/xrpld/app/ledger/detail/TransactionMaster.cpp similarity index 95% rename from src/ripple/app/ledger/impl/TransactionMaster.cpp rename to src/xrpld/app/ledger/detail/TransactionMaster.cpp index c4205887740..e2e1213a37e 100644 --- a/src/ripple/app/ledger/impl/TransactionMaster.cpp +++ b/src/xrpld/app/ledger/detail/TransactionMaster.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp similarity index 82% rename from src/ripple/app/main/Application.cpp rename to src/xrpld/app/main/Application.cpp index b1305e46672..a9d66679010 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -17,63 +17,59 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -192,9 +188,6 @@ class ApplicationImp : public Application, public BasicApp std::unique_ptr m_nodeStore; NodeFamily nodeFamily_; - std::unique_ptr shardStore_; - std::unique_ptr shardFamily_; - std::unique_ptr shardArchiveHandler_; // VFALCO TODO Make OrderBookDB abstract OrderBookDB m_orderBookDB; std::unique_ptr m_pathRequests; @@ -225,6 +218,7 @@ class ApplicationImp : public Application, public BasicApp std::unique_ptr mRelationalDatabase; std::unique_ptr mWalletDB; std::unique_ptr overlay_; + std::optional trapTxID_; boost::asio::signal_set m_signals; @@ -241,7 +235,6 @@ class ApplicationImp : public Application, public BasicApp io_latency_sampler m_io_latency_sampler; std::unique_ptr grpcServer_; - std::unique_ptr reportingETL_; //-------------------------------------------------------------------------- @@ -301,8 +294,7 @@ class ApplicationImp : public Application, public BasicApp , m_jobQueue(std::make_unique( [](std::unique_ptr const& config) { - if (config->standalone() && !config->reporting() && - !config->FORCE_MULTI_THREAD) + if (config->standalone() && !config->FORCE_MULTI_THREAD) return 1; if (config->WORKERS) @@ -360,13 +352,6 @@ class ApplicationImp : public Application, public BasicApp , nodeFamily_(*this, *m_collectorManager) - // The shard store is optional and make_ShardStore can return null. - , shardStore_(make_ShardStore( - *this, - m_nodeStoreScheduler, - 4, - logs_->journal("ShardStore"))) - , m_orderBookDB(*this) , m_pathRequests(std::make_unique( @@ -487,9 +472,6 @@ class ApplicationImp : public Application, public BasicApp std::chrono::milliseconds(100), get_io_service()) , grpcServer_(std::make_unique(*this)) - , reportingETL_( - config_->reporting() ? std::make_unique(*this) - : nullptr) { initAccountIdCache(config_->getValueFor(SizedItem::accountIdCacheSize)); @@ -564,14 +546,6 @@ class ApplicationImp : public Application, public BasicApp return nodeFamily_; } - // The shard store is an optional feature. If the sever is configured for - // shards, this function will return a valid pointer, otherwise a nullptr. - Family* - getShardFamily() override - { - return shardFamily_.get(); - } - TimeKeeper& timeKeeper() override { @@ -695,72 +669,6 @@ class ApplicationImp : public Application, public BasicApp return *m_nodeStore; } - // The shard store is an optional feature. If the sever is configured for - // shards, this function will return a valid pointer, otherwise a nullptr. - NodeStore::DatabaseShard* - getShardStore() override - { - return shardStore_.get(); - } - - RPC::ShardArchiveHandler* - getShardArchiveHandler(bool tryRecovery) override - { - static std::mutex handlerMutex; - std::lock_guard lock(handlerMutex); - - // After constructing the handler, try to - // initialize it. Log on error; set the - // member variable on success. - auto initAndSet = - [this](std::unique_ptr&& handler) { - if (!handler) - return false; - - if (!handler->init()) - { - JLOG(m_journal.error()) - << "Failed to initialize ShardArchiveHandler."; - - return false; - } - - shardArchiveHandler_ = std::move(handler); - return true; - }; - - // Need to resume based on state from a previous - // run. - if (tryRecovery) - { - if (shardArchiveHandler_ != nullptr) - { - JLOG(m_journal.error()) - << "ShardArchiveHandler already created at startup."; - - return nullptr; - } - - auto handler = - RPC::ShardArchiveHandler::tryMakeRecoveryHandler(*this); - - if (!initAndSet(std::move(handler))) - return nullptr; - } - - // Construct the ShardArchiveHandler - if (shardArchiveHandler_ == nullptr) - { - auto handler = - RPC::ShardArchiveHandler::makeShardArchiveHandler(*this); - - if (!initAndSet(std::move(handler))) - return nullptr; - } - - return shardArchiveHandler_.get(); - } - Application::MutexType& getMasterMutex() override { @@ -872,16 +780,12 @@ class ApplicationImp : public Application, public BasicApp OpenLedger& openLedger() override { - if (config_->reporting()) - Throw(); return *openLedger_; } OpenLedger const& openLedger() const override { - if (config_->reporting()) - Throw(); return *openLedger_; } @@ -913,13 +817,6 @@ class ApplicationImp : public Application, public BasicApp return *mWalletDB; } - ReportingETL& - getReportingETL() override - { - assert(reportingETL_.get() != nullptr); - return *reportingETL_; - } - bool serverOkay(std::string& reason) override; @@ -1074,10 +971,10 @@ class ApplicationImp : public Application, public BasicApp { std::shared_ptr const fullBelowCache = - nodeFamily_.getFullBelowCache(0); + nodeFamily_.getFullBelowCache(); std::shared_ptr const treeNodeCache = - nodeFamily_.getTreeNodeCache(0); + nodeFamily_.getTreeNodeCache(); std::size_t const oldFullBelowSize = fullBelowCache->size(); std::size_t const oldTreeNodeSize = treeNodeCache->size(); @@ -1093,25 +990,6 @@ class ApplicationImp : public Application, public BasicApp << "NodeFamily::TreeNodeCache sweep. Size before: " << oldTreeNodeSize << "; size after: " << treeNodeCache->size(); } - if (shardFamily_) - { - std::size_t const oldFullBelowSize = - shardFamily_->getFullBelowCacheSize(); - std::size_t const oldTreeNodeSize = - shardFamily_->getTreeNodeCacheSize().second; - - shardFamily_->sweep(); - - JLOG(m_journal.debug()) - << "ShardFamily::FullBelowCache sweep. Size before: " - << oldFullBelowSize - << "; size after: " << shardFamily_->getFullBelowCacheSize(); - - JLOG(m_journal.debug()) - << "ShardFamily::TreeNodeCache sweep. Size before: " - << oldTreeNodeSize << "; size after: " - << shardFamily_->getTreeNodeCacheSize().second; - } { TaggedCache const& masterTxCache = getMasterTransaction().getCache(); @@ -1128,11 +1006,6 @@ class ApplicationImp : public Application, public BasicApp // Does not appear to have an associated cache. getNodeStore().sweep(); } - if (shardStore_) - { - // Does not appear to have an associated cache. - shardStore_->sweep(); - } { std::size_t const oldLedgerMasterCacheSize = getLedgerMaster().getFetchPackCacheSize(); @@ -1239,11 +1112,6 @@ class ApplicationImp : public Application, public BasicApp << "; size after: " << cachedSLEs_.size(); } -#ifdef RIPPLED_REPORTING - if (auto pg = dynamic_cast(&*mRelationalDatabase)) - pg->sweep(); -#endif - // Set timer to do another sweep later. setSweepTimer(); } @@ -1254,14 +1122,17 @@ class ApplicationImp : public Application, public BasicApp return maxDisallowedLedger_; } + virtual const std::optional& + trapTxID() const override + { + return trapTxID_; + } + private: // For a newly-started validator, this is the greatest persisted ledger // and new validations must be greater than this. std::atomic maxDisallowedLedger_{0}; - bool - nodeToShards(); - void startGenesisLedger(); @@ -1272,7 +1143,11 @@ class ApplicationImp : public Application, public BasicApp loadLedgerFromFile(std::string const& ledgerID); bool - loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename); + loadOldLedger( + std::string const& ledgerID, + bool replay, + bool isFilename, + std::optional trapTxID); void setMaxDisallowedLedger(); @@ -1337,15 +1212,6 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) if (!initRelationalDatabase() || !initNodeStore()) return false; - if (shardStore_) - { - shardFamily_ = - std::make_unique(*this, *m_collectorManager); - - if (!shardStore_->init()) - return false; - } - if (!peerReservations_->load(getWalletDB())) { JLOG(m_journal.fatal()) << "Cannot find peer reservations!"; @@ -1387,52 +1253,50 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) auto const startUp = config_->START_UP; JLOG(m_journal.debug()) << "startUp: " << startUp; - if (!config_->reporting()) + if (startUp == Config::FRESH) { - if (startUp == Config::FRESH) - { - JLOG(m_journal.info()) << "Starting new Ledger"; + JLOG(m_journal.info()) << "Starting new Ledger"; - startGenesisLedger(); - } - else if ( - startUp == Config::LOAD || startUp == Config::LOAD_FILE || - startUp == Config::REPLAY) - { - JLOG(m_journal.info()) << "Loading specified Ledger"; + startGenesisLedger(); + } + else if ( + startUp == Config::LOAD || startUp == Config::LOAD_FILE || + startUp == Config::REPLAY) + { + JLOG(m_journal.info()) << "Loading specified Ledger"; - if (!loadOldLedger( - config_->START_LEDGER, - startUp == Config::REPLAY, - startUp == Config::LOAD_FILE)) + if (!loadOldLedger( + config_->START_LEDGER, + startUp == Config::REPLAY, + startUp == Config::LOAD_FILE, + config_->TRAP_TX_HASH)) + { + JLOG(m_journal.error()) + << "The specified ledger could not be loaded."; + if (config_->FAST_LOAD) { - JLOG(m_journal.error()) - << "The specified ledger could not be loaded."; - if (config_->FAST_LOAD) - { - // Fall back to syncing from the network, such as - // when there's no existing data. - startGenesisLedger(); - } - else - { - return false; - } + // Fall back to syncing from the network, such as + // when there's no existing data. + startGenesisLedger(); + } + else + { + return false; } } - else if (startUp == Config::NETWORK) - { - // This should probably become the default once we have a stable - // network. - if (!config_->standalone()) - m_networkOPs->setNeedNetworkLedger(); + } + else if (startUp == Config::NETWORK) + { + // This should probably become the default once we have a stable + // network. + if (!config_->standalone()) + m_networkOPs->setNeedNetworkLedger(); - startGenesisLedger(); - } - else - { - startGenesisLedger(); - } + startGenesisLedger(); + } + else + { + startGenesisLedger(); } if (auto const& forcedRange = config().FORCED_LEDGER_RANGE_PRESENT) @@ -1441,8 +1305,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) forcedRange->first, forcedRange->second); } - if (!config().reporting()) - m_orderBookDB.setup(getLedgerMaster().getCurrentLedger()); + m_orderBookDB.setup(getLedgerMaster().getCurrentLedger()); nodeIdentity_ = getNodeIdentity(*this, cmdline); @@ -1452,60 +1315,55 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) return false; } - if (!config().reporting()) { - { - if (validatorKeys_.configInvalid()) - return false; + if (validatorKeys_.configInvalid()) + return false; - if (!validatorManifests_->load( - getWalletDB(), - "ValidatorManifests", - validatorKeys_.manifest, - config() - .section(SECTION_VALIDATOR_KEY_REVOCATION) - .values())) - { - JLOG(m_journal.fatal()) - << "Invalid configured validator manifest."; - return false; - } + if (!validatorManifests_->load( + getWalletDB(), + "ValidatorManifests", + validatorKeys_.manifest, + config().section(SECTION_VALIDATOR_KEY_REVOCATION).values())) + { + JLOG(m_journal.fatal()) << "Invalid configured validator manifest."; + return false; + } - publisherManifests_->load(getWalletDB(), "PublisherManifests"); + publisherManifests_->load(getWalletDB(), "PublisherManifests"); - // It is possible to have a valid ValidatorKeys object without - // setting the signingKey or masterKey. This occurs if the - // configuration file does not have either - // SECTION_VALIDATOR_TOKEN or SECTION_VALIDATION_SEED section. + // It is possible to have a valid ValidatorKeys object without + // setting the signingKey or masterKey. This occurs if the + // configuration file does not have either + // SECTION_VALIDATOR_TOKEN or SECTION_VALIDATION_SEED section. - // masterKey for the configuration-file specified validator keys - std::optional localSigningKey; - if (validatorKeys_.keys) - localSigningKey = validatorKeys_.keys->publicKey; + // masterKey for the configuration-file specified validator keys + std::optional localSigningKey; + if (validatorKeys_.keys) + localSigningKey = validatorKeys_.keys->publicKey; - // Setup trusted validators - if (!validators_->load( - localSigningKey, - config().section(SECTION_VALIDATORS).values(), - config().section(SECTION_VALIDATOR_LIST_KEYS).values())) - { - JLOG(m_journal.fatal()) - << "Invalid entry in validator configuration."; - return false; - } - } - - if (!validatorSites_->load( - config().section(SECTION_VALIDATOR_LIST_SITES).values())) + // Setup trusted validators + if (!validators_->load( + localSigningKey, + config().section(SECTION_VALIDATORS).values(), + config().section(SECTION_VALIDATOR_LIST_KEYS).values())) { JLOG(m_journal.fatal()) - << "Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]"; + << "Invalid entry in validator configuration."; return false; } + } - // Tell the AmendmentTable who the trusted validators are. - m_amendmentTable->trustChanged(validators_->getQuorumKeys().second); + if (!validatorSites_->load( + config().section(SECTION_VALIDATOR_LIST_SITES).values())) + { + JLOG(m_journal.fatal()) + << "Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]"; + return false; } + + // Tell the AmendmentTable who the trusted validators are. + m_amendmentTable->trustChanged(validators_->getQuorumKeys().second); + //---------------------------------------------------------------------- // // Server @@ -1517,30 +1375,19 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) // move the instantiation inside a conditional: // // if (!config_.standalone()) - if (!config_->reporting()) - { - overlay_ = make_Overlay( - *this, - setup_Overlay(*config_), - *serverHandler_, - *m_resourceManager, - *m_resolver, - get_io_service(), - *config_, - m_collectorManager->collector()); - add(*overlay_); // add to PropertyStream - } - - if (!config_->standalone()) - { - // NodeStore import into the ShardStore requires the SQLite database - if (config_->nodeToShard && !nodeToShards()) - return false; - } + overlay_ = make_Overlay( + *this, + setup_Overlay(*config_), + *serverHandler_, + *m_resourceManager, + *m_resolver, + get_io_service(), + *config_, + m_collectorManager->collector()); + add(*overlay_); // add to PropertyStream // start first consensus round - if (!config_->reporting() && - !m_networkOPs->beginConsensus( + if (!m_networkOPs->beginConsensus( m_ledgerMaster->getClosedLedger()->info().hash)) { JLOG(m_journal.fatal()) << "Unable to start consensus"; @@ -1652,43 +1499,8 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } } - RPC::ShardArchiveHandler* shardArchiveHandler = nullptr; - if (shardStore_) - { - try - { - // Create a ShardArchiveHandler if recovery - // is needed (there's a state database left - // over from a previous run). - auto handler = getShardArchiveHandler(true); - - // Recovery is needed. - if (handler) - shardArchiveHandler = handler; - } - catch (std::exception const& e) - { - JLOG(m_journal.fatal()) - << "Exception when starting ShardArchiveHandler from " - "state database: " - << e.what(); - - return false; - } - } - - if (shardArchiveHandler && !shardArchiveHandler->start()) - { - JLOG(m_journal.fatal()) << "Failed to start ShardArchiveHandler."; - - return false; - } - validatorSites_->start(); - if (reportingETL_) - reportingETL_->start(); - return true; } @@ -1795,12 +1607,8 @@ ApplicationImp::run() m_loadManager->stop(); m_shaMapStore->stop(); m_jobQueue->stop(); - if (shardArchiveHandler_) - shardArchiveHandler_->stop(); if (overlay_) overlay_->stop(); - if (shardStore_) - shardStore_->stop(); grpcServer_->stop(); m_networkOPs->stop(); serverHandler_->stop(); @@ -1808,10 +1616,6 @@ ApplicationImp::run() m_inboundTransactions->stop(); m_inboundLedgers->stop(); ledgerCleaner_->stop(); - if (reportingETL_) - reportingETL_->stop(); - if (auto pg = dynamic_cast(&*mRelationalDatabase)) - pg->stop(); m_nodeStore->stop(); perfLog_->stop(); @@ -1864,9 +1668,6 @@ ApplicationImp::fdRequired() const // doubled if online delete is enabled). needed += std::max(5, m_shaMapStore->fdRequired()); - if (shardStore_) - needed += shardStore_->fdRequired(); - // One fd per incoming connection a port can accept, or // if no limit is set, assume it'll handle 256 clients. for (auto const& p : serverHandler_->setup().ports) @@ -2086,7 +1887,8 @@ bool ApplicationImp::loadOldLedger( std::string const& ledgerID, bool replay, - bool isFileName) + bool isFileName, + std::optional trapTxID) { try { @@ -2233,6 +2035,11 @@ ApplicationImp::loadOldLedger( { (void)_; auto txID = tx->getTransactionID(); + if (trapTxID == txID) + { + trapTxID_ = txID; + JLOG(m_journal.debug()) << "Trap transaction set: " << txID; + } auto s = std::make_shared(); tx->add(*s); @@ -2247,6 +2054,14 @@ ApplicationImp::loadOldLedger( } m_ledgerMaster->takeReplay(std::move(replayData)); + + if (trapTxID && !trapTxID_) + { + JLOG(m_journal.fatal()) + << "Ledger " << replayLedger->info().seq + << " does not contain the transaction hash " << *trapTxID; + return false; + } } } catch (SHAMapMissingNode const& mn) @@ -2319,27 +2134,6 @@ ApplicationImp::journal(std::string const& name) return logs_->journal(name); } -bool -ApplicationImp::nodeToShards() -{ - assert(overlay_); - assert(!config_->standalone()); - - if (config_->section(ConfigSection::shardDatabase()).empty()) - { - JLOG(m_journal.fatal()) - << "The [shard_db] configuration setting must be set"; - return false; - } - if (!shardStore_) - { - JLOG(m_journal.fatal()) << "Invalid [shard_db] configuration"; - return false; - } - shardStore_->importDatabase(getNodeStore()); - return true; -} - void ApplicationImp::setMaxDisallowedLedger() { diff --git a/src/ripple/app/main/Application.h b/src/xrpld/app/main/Application.h similarity index 91% rename from src/ripple/app/main/Application.h rename to src/xrpld/app/main/Application.h index 3fa8d13e870..8f2dd606ded 100644 --- a/src/ripple/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_MAIN_APPLICATION_H_INCLUDED #define RIPPLE_APP_MAIN_APPLICATION_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -42,14 +42,10 @@ class Manager; } namespace NodeStore { class Database; -class DatabaseShard; } // namespace NodeStore namespace perf { class PerfLog; } -namespace RPC { -class ShardArchiveHandler; -} // VFALCO TODO Fix forward declares required for header dependency loops class AmendmentTable; @@ -104,8 +100,6 @@ class RelationalDatabase; class DatabaseCon; class SHAMapStore; -class ReportingETL; - using NodeCache = TaggedCache; template @@ -172,8 +166,6 @@ class Application : public beast::PropertyStream::Source getCollectorManager() = 0; virtual Family& getNodeFamily() = 0; - virtual Family* - getShardFamily() = 0; virtual TimeKeeper& timeKeeper() = 0; virtual JobQueue& @@ -210,10 +202,6 @@ class Application : public beast::PropertyStream::Source getValidations() = 0; virtual NodeStore::Database& getNodeStore() = 0; - virtual NodeStore::DatabaseShard* - getShardStore() = 0; - virtual RPC::ShardArchiveHandler* - getShardArchiveHandler(bool tryRecovery = false) = 0; virtual InboundLedgers& getInboundLedgers() = 0; virtual InboundTransactions& @@ -263,9 +251,6 @@ class Application : public beast::PropertyStream::Source virtual std::chrono::milliseconds getIOLatency() = 0; - virtual ReportingETL& - getReportingETL() = 0; - virtual bool serverOkay(std::string& reason) = 0; @@ -284,6 +269,9 @@ class Application : public beast::PropertyStream::Source * than the last ledger it persisted. */ virtual LedgerIndex getMaxDisallowedLedger() = 0; + + virtual const std::optional& + trapTxID() const = 0; }; std::unique_ptr diff --git a/src/ripple/app/main/BasicApp.cpp b/src/xrpld/app/main/BasicApp.cpp similarity index 94% rename from src/ripple/app/main/BasicApp.cpp rename to src/xrpld/app/main/BasicApp.cpp index 5993df62fa7..504309d0838 100644 --- a/src/ripple/app/main/BasicApp.cpp +++ b/src/xrpld/app/main/BasicApp.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include BasicApp::BasicApp(std::size_t numberOfThreads) { diff --git a/src/ripple/app/main/BasicApp.h b/src/xrpld/app/main/BasicApp.h similarity index 100% rename from src/ripple/app/main/BasicApp.h rename to src/xrpld/app/main/BasicApp.h diff --git a/src/ripple/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp similarity index 98% rename from src/ripple/app/main/CollectorManager.cpp rename to src/xrpld/app/main/CollectorManager.cpp index 6e6d0f47f24..ae7ff965f5c 100644 --- a/src/ripple/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/app/main/CollectorManager.h b/src/xrpld/app/main/CollectorManager.h similarity index 95% rename from src/ripple/app/main/CollectorManager.h rename to src/xrpld/app/main/CollectorManager.h index 46e113082d4..0bb3ae65c47 100644 --- a/src/ripple/app/main/CollectorManager.h +++ b/src/xrpld/app/main/CollectorManager.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_MAIN_COLLECTORMANAGER_H_INCLUDED #define RIPPLE_APP_MAIN_COLLECTORMANAGER_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/main/DBInit.h b/src/xrpld/app/main/DBInit.h similarity index 54% rename from src/ripple/app/main/DBInit.h rename to src/xrpld/app/main/DBInit.h index 43b29312e63..192b1bedae3 100644 --- a/src/ripple/app/main/DBInit.h +++ b/src/xrpld/app/main/DBInit.h @@ -42,9 +42,6 @@ inline constexpr std::uint32_t SQLITE_TUNING_CUTOFF = 10'000'000; // Ledger database holds ledgers and ledger confirmations inline constexpr auto LgrDBName{"ledger.db"}; -inline constexpr std::array LgrDBPragma{ - {"PRAGMA journal_size_limit=1582080;"}}; - inline constexpr std::array LgrDBInit{ {"BEGIN TRANSACTION;", @@ -72,25 +69,6 @@ inline constexpr std::array LgrDBInit{ // Transaction database holds transactions and public keys inline constexpr auto TxDBName{"transaction.db"}; -// In C++17 omitting the explicit template parameters caused -// a crash -inline constexpr std::array TxDBPragma -{ - "PRAGMA page_size=4096;", "PRAGMA journal_size_limit=1582080;", - "PRAGMA max_page_count=4294967294;", - -#if (ULONG_MAX > UINT_MAX) && !defined(NO_SQLITE_MMAP) - "PRAGMA mmap_size=17179869184;" -#else - - // Provide an explicit `no-op` SQL statement - // in order to keep the size of the array - // constant regardless of the preprocessor - // condition evaluation - "PRAGMA sqlite_noop_statement;" -#endif -}; - inline constexpr std::array TxDBInit{ {"BEGIN TRANSACTION;", @@ -124,96 +102,6 @@ inline constexpr std::array TxDBInit{ //////////////////////////////////////////////////////////////////////////////// -// The Ledger Meta database maps ledger hashes to shard indexes -inline constexpr auto LgrMetaDBName{"ledger_meta.db"}; - -// In C++17 omitting the explicit template parameters caused -// a crash -inline constexpr std::array LgrMetaDBPragma -{ - "PRAGMA page_size=4096;", "PRAGMA journal_size_limit=1582080;", - "PRAGMA max_page_count=2147483646;", - -#if (ULONG_MAX > UINT_MAX) && !defined(NO_SQLITE_MMAP) - "PRAGMA mmap_size=17179869184;" -#else - - // Provide an explicit `no-op` SQL statement - // in order to keep the size of the array - // constant regardless of the preprocessor - // condition evaluation - "PRAGMA sqlite_noop_statement;" -#endif -}; - -inline constexpr std::array LgrMetaDBInit{ - {"BEGIN TRANSACTION;", - - "CREATE TABLE IF NOT EXISTS LedgerMeta ( \ - LedgerHash CHARACTER(64) PRIMARY KEY, \ - ShardIndex INTEGER \ - );", - - "END TRANSACTION;"}}; - -//////////////////////////////////////////////////////////////////////////////// - -// Transaction Meta database maps transaction IDs to shard indexes -inline constexpr auto TxMetaDBName{"transaction_meta.db"}; - -// In C++17 omitting the explicit template parameters caused -// a crash -inline constexpr std::array TxMetaDBPragma -{ - "PRAGMA page_size=4096;", "PRAGMA journal_size_limit=1582080;", - "PRAGMA max_page_count=2147483646;", - -#if (ULONG_MAX > UINT_MAX) && !defined(NO_SQLITE_MMAP) - "PRAGMA mmap_size=17179869184;" -#else - - // Provide an explicit `no-op` SQL statement - // in order to keep the size of the array - // constant regardless of the preprocessor - // condition evaluation - "PRAGMA sqlite_noop_statement;" -#endif -}; - -inline constexpr std::array TxMetaDBInit{ - {"BEGIN TRANSACTION;", - - "CREATE TABLE IF NOT EXISTS TransactionMeta ( \ - TransID CHARACTER(64) PRIMARY KEY, \ - ShardIndex INTEGER \ - );", - - "END TRANSACTION;"}}; - -//////////////////////////////////////////////////////////////////////////////// - -// Temporary database used with an incomplete shard that is being acquired -inline constexpr auto AcquireShardDBName{"acquire.db"}; - -inline constexpr std::array AcquireShardDBPragma{ - {"PRAGMA journal_size_limit=1582080;"}}; - -inline constexpr std::array AcquireShardDBInit{ - {"CREATE TABLE IF NOT EXISTS Shard ( \ - ShardIndex INTEGER PRIMARY KEY, \ - LastLedgerHash CHARACTER(64), \ - StoredLedgerSeqs BLOB \ - );"}}; - -//////////////////////////////////////////////////////////////////////////////// - -// Pragma for Ledger and Transaction databases with final shards -// These override the CommonDBPragma values defined above. -inline constexpr std::array FinalShardDBPragma{ - {"PRAGMA synchronous=OFF;", "PRAGMA journal_mode=OFF;"}}; - -//////////////////////////////////////////////////////////////////////////////// - inline constexpr auto WalletDBName{"wallet.db"}; inline constexpr std::array WalletDBInit{ @@ -247,36 +135,6 @@ inline constexpr std::array WalletDBInit{ "END TRANSACTION;"}}; -//////////////////////////////////////////////////////////////////////////////// - -static constexpr auto stateDBName{"state.db"}; - -// These override the CommonDBPragma values defined above. -static constexpr std::array DownloaderDBPragma{ - {"PRAGMA synchronous=FULL;", "PRAGMA journal_mode=DELETE;"}}; - -static constexpr std::array ShardArchiveHandlerDBInit{ - {"BEGIN TRANSACTION;", - - "CREATE TABLE IF NOT EXISTS State ( \ - ShardIndex INTEGER PRIMARY KEY, \ - URL TEXT \ - );", - - "END TRANSACTION;"}}; - -static constexpr std::array DatabaseBodyDBInit{ - {"BEGIN TRANSACTION;", - - "CREATE TABLE IF NOT EXISTS download ( \ - Path TEXT, \ - Data BLOB, \ - Size BIGINT UNSIGNED, \ - Part BIGINT UNSIGNED PRIMARY KEY \ - );", - - "END TRANSACTION;"}}; - } // namespace ripple #endif diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp similarity index 83% rename from src/ripple/app/main/GRPCServer.cpp rename to src/xrpld/app/main/GRPCServer.cpp index a535a4a1a53..5a231dfc9e6 100644 --- a/src/ripple/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -17,13 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include -#include -#include +#include +#include namespace ripple { @@ -187,11 +186,6 @@ GRPCServerImpl::CallData::process( InfoSub::pointer(), apiVersion}, request_}; - if (shouldForwardToP2p(context, requiredCondition_)) - { - forwardToP2p(context); - return; - } // Make sure we can currently handle the rpc error_code_i conditionMetRes = @@ -207,18 +201,9 @@ GRPCServerImpl::CallData::process( } else { - try - { - std::pair result = - handler_(context); - setIsUnlimited(result.first, isUnlimited); - responder_.Finish(result.first, result.second, this); - } - catch (ReportingShouldProxy&) - { - forwardToP2p(context); - return; - } + std::pair result = handler_(context); + setIsUnlimited(result.first, isUnlimited); + responder_.Finish(result.first, result.second, this); } } } @@ -229,46 +214,6 @@ GRPCServerImpl::CallData::process( } } -template -void -GRPCServerImpl::CallData::forwardToP2p( - RPC::GRPCContext& context) -{ - if (auto descriptor = - Request::GetDescriptor()->FindFieldByName("client_ip")) - { - Request::GetReflection()->SetString(&request_, descriptor, ctx_.peer()); - JLOG(app_.journal("gRPCServer").debug()) - << "Set client_ip to " << ctx_.peer(); - } - else - { - assert(false); - Throw( - "Attempting to forward but no client_ip field in " - "protobuf message"); - } - auto stub = getP2pForwardingStub(context); - if (stub) - { - grpc::ClientContext clientContext; - Response response; - auto status = forward_(stub.get(), &clientContext, request_, &response); - responder_.Finish(response, status, this); - JLOG(app_.journal("gRPCServer").debug()) << "Forwarded request to tx"; - } - else - { - JLOG(app_.journal("gRPCServer").error()) - << "Failed to forward request to tx"; - grpc::Status status{ - grpc::StatusCode::INTERNAL, - "Attempted to act as proxy but failed " - "to create forwarding stub"}; - responder_.FinishWithError(status, this); - } -} - template bool GRPCServerImpl::CallData::isFinished() @@ -289,29 +234,10 @@ GRPCServerImpl::CallData::getRole(bool isUnlimited) { if (isUnlimited) return Role::IDENTIFIED; - else if (wasForwarded()) - return Role::PROXY; else return Role::USER; } -template -bool -GRPCServerImpl::CallData::wasForwarded() -{ - if (auto descriptor = - Request::GetDescriptor()->FindFieldByName("client_ip")) - { - std::string clientIp = - Request::GetReflection()->GetString(request_, descriptor); - if (!clientIp.empty()) - { - return true; - } - } - return false; -} - template std::optional GRPCServerImpl::CallData::getUser() @@ -338,35 +264,6 @@ GRPCServerImpl::CallData::getClientIpAddress() return {}; } -template -std::optional -GRPCServerImpl::CallData::getProxiedClientIpAddress() -{ - auto endpoint = getProxiedClientEndpoint(); - if (endpoint) - return endpoint->address(); - return {}; -} - -template -std::optional -GRPCServerImpl::CallData::getProxiedClientEndpoint() -{ - auto descriptor = Request::GetDescriptor()->FindFieldByName("client_ip"); - if (descriptor) - { - std::string clientIp = - Request::GetReflection()->GetString(request_, descriptor); - if (!clientIp.empty()) - { - JLOG(app_.journal("gRPCServer").debug()) - << "Got client_ip from request : " << clientIp; - return getEndpoint(clientIp); - } - } - return {}; -} - template std::optional GRPCServerImpl::CallData::getClientEndpoint() @@ -381,8 +278,7 @@ GRPCServerImpl::CallData::clientIsUnlimited() if (!getUser()) return false; auto clientIp = getClientIpAddress(); - auto proxiedIp = getProxiedClientIpAddress(); - if (clientIp && !proxiedIp) + if (clientIp) { for (auto& ip : secureGatewayIPs_) { @@ -414,11 +310,7 @@ Resource::Consumer GRPCServerImpl::CallData::getUsage() { auto endpoint = getClientEndpoint(); - auto proxiedEndpoint = getProxiedClientEndpoint(); - if (proxiedEndpoint) - return app_.getResourceManager().newInboundEndpoint( - beast::IP::from_asio(proxiedEndpoint.value())); - else if (endpoint) + if (endpoint) return app_.getResourceManager().newInboundEndpoint( beast::IP::from_asio(endpoint.value())); Throw("Failed to get client endpoint"); @@ -555,8 +447,8 @@ GRPCServerImpl::handleRpcs() if (!ok) { - JLOG(journal_.debug()) << "Request listener cancelled. " - << "Destroying object"; + JLOG(journal_.debug()) + << "Request listener cancelled. " << "Destroying object"; erase(ptr); } else diff --git a/src/ripple/app/main/GRPCServer.h b/src/xrpld/app/main/GRPCServer.h similarity index 95% rename from src/ripple/app/main/GRPCServer.h rename to src/xrpld/app/main/GRPCServer.h index 79780e10137..39bfc0c9760 100644 --- a/src/ripple/app/main/GRPCServer.h +++ b/src/xrpld/app/main/GRPCServer.h @@ -20,19 +20,19 @@ #ifndef RIPPLE_CORE_GRPCSERVER_H_INCLUDED #define RIPPLE_CORE_GRPCSERVER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include namespace ripple { diff --git a/src/ripple/app/main/LoadManager.cpp b/src/xrpld/app/main/LoadManager.cpp similarity index 95% rename from src/ripple/app/main/LoadManager.cpp rename to src/xrpld/app/main/LoadManager.cpp index 5e87063f000..f5dd8719470 100644 --- a/src/ripple/app/main/LoadManager.cpp +++ b/src/xrpld/app/main/LoadManager.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/main/LoadManager.h b/src/xrpld/app/main/LoadManager.h similarity index 98% rename from src/ripple/app/main/LoadManager.h rename to src/xrpld/app/main/LoadManager.h index 905006f5e41..f818068dcfa 100644 --- a/src/ripple/app/main/LoadManager.h +++ b/src/xrpld/app/main/LoadManager.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED #define RIPPLE_APP_MAIN_LOADMANAGER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp similarity index 94% rename from src/ripple/app/main/Main.cpp rename to src/xrpld/app/main/Main.cpp index 02989b9ee6c..169a6dad912 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -17,26 +17,26 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef ENABLE_TESTS -#include #include +#include #endif // ENABLE_TESTS #include @@ -143,8 +143,7 @@ printHelp(const po::options_description& desc) " connect []\n" " consensus_info\n" " deposit_authorized " - "[]\n" - " download_shard [[ ]]\n" + "[ [, ...]]\n" " feature [ [accept|reject]]\n" " fetch_info [clear]\n" " gateway_balances [] [ [ " @@ -160,7 +159,6 @@ printHelp(const po::options_description& desc) " log_level [[] ]\n" " logrotate\n" " manifest \n" - " node_to_shard [status|start|stop]\n" " peers\n" " ping\n" " random\n" @@ -378,7 +376,6 @@ run(int argc, char** argv) "quorum", po::value(), "Override the minimum validation quorum.")( - "reportingReadOnly", "Run in read-only reporting mode")( "silent", "No output to the console after startup.")( "standalone,a", "Run with no peers.")("verbose,v", "Verbose logging.") @@ -398,12 +395,11 @@ run(int argc, char** argv) "Load the specified ledger file.")( "load", "Load the current ledger from the local DB.")( "net", "Get the initial ledger from the network.")( - "nodetoshard", "Import node store into shards")( "replay", "Replay a ledger close.")( - "start", "Start from a fresh Ledger.")( - "startReporting", + "trap_tx_hash", po::value(), - "Start reporting from a fresh Ledger.")( + "Trap a specific transaction during replay.")( + "start", "Start from a fresh Ledger.")( "vacuum", "VACUUM the transaction db.")( "valid", "Consider the initial ledger a valid network ledger."); @@ -558,6 +554,7 @@ run(int argc, char** argv) argc, argv); } + // LCOV_EXCL_START else { if (vm.count("unittest-jobs")) @@ -658,28 +655,32 @@ run(int argc, char** argv) config->START_UP = Config::FRESH; } - if (vm.count("startReporting")) - { - config->START_UP = Config::FRESH; - config->START_LEDGER = vm["startReporting"].as(); - } - - if (vm.count("reportingReadOnly")) - { - config->setReportingReadOnly(true); - } - if (vm.count("import")) config->doImport = true; - if (vm.count("nodetoshard")) - config->nodeToShard = true; - if (vm.count("ledger")) { config->START_LEDGER = vm["ledger"].as(); if (vm.count("replay")) + { config->START_UP = Config::REPLAY; + if (vm.count("trap_tx_hash")) + { + uint256 tmp = {}; + auto hash = vm["trap_tx_hash"].as(); + if (tmp.parseHex(hash)) + { + config->TRAP_TX_HASH = tmp; + } + else + { + std::cerr << "Trap parameter was ill-formed, expected " + "valid transaction hash but received: " + << hash << std::endl; + return -1; + } + } + } else config->START_UP = Config::LOAD; } @@ -693,6 +694,13 @@ run(int argc, char** argv) config->START_UP = Config::LOAD; } + if (vm.count("trap_tx_hash") && vm.count("replay") == 0) + { + std::cerr << "Cannot use trap option without replay option" + << std::endl; + return -1; + } + if (vm.count("net") && !config->FAST_LOAD) { if ((config->START_UP == Config::LOAD) || @@ -828,6 +836,7 @@ run(int argc, char** argv) beast::setCurrentThreadName("rippled: rpc"); return RPCCall::fromCommandLine( *config, vm["parameters"].as>(), *logs); + // LCOV_EXCL_STOP } } // namespace ripple diff --git a/src/ripple/app/main/NodeIdentity.cpp b/src/xrpld/app/main/NodeIdentity.cpp similarity index 92% rename from src/ripple/app/main/NodeIdentity.cpp rename to src/xrpld/app/main/NodeIdentity.cpp index e66b9e8400f..e0b83d54c8d 100644 --- a/src/ripple/app/main/NodeIdentity.cpp +++ b/src/xrpld/app/main/NodeIdentity.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/main/NodeIdentity.h b/src/xrpld/app/main/NodeIdentity.h similarity index 92% rename from src/ripple/app/main/NodeIdentity.h rename to src/xrpld/app/main/NodeIdentity.h index b82b3657aeb..b4da8651949 100644 --- a/src/ripple/app/main/NodeIdentity.h +++ b/src/xrpld/app/main/NodeIdentity.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_MAIN_NODEIDENTITY_H_INCLUDED #define RIPPLE_APP_MAIN_NODEIDENTITY_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/main/NodeStoreScheduler.cpp b/src/xrpld/app/main/NodeStoreScheduler.cpp similarity index 97% rename from src/ripple/app/main/NodeStoreScheduler.cpp rename to src/xrpld/app/main/NodeStoreScheduler.cpp index 0ac89096410..bf07e559fd3 100644 --- a/src/ripple/app/main/NodeStoreScheduler.cpp +++ b/src/xrpld/app/main/NodeStoreScheduler.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/app/main/NodeStoreScheduler.h b/src/xrpld/app/main/NodeStoreScheduler.h similarity index 95% rename from src/ripple/app/main/NodeStoreScheduler.h rename to src/xrpld/app/main/NodeStoreScheduler.h index 5c68dc24f8d..b16142b2613 100644 --- a/src/ripple/app/main/NodeStoreScheduler.h +++ b/src/xrpld/app/main/NodeStoreScheduler.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_MAIN_NODESTORESCHEDULER_H_INCLUDED #define RIPPLE_APP_MAIN_NODESTORESCHEDULER_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/main/Tuning.h b/src/xrpld/app/main/Tuning.h similarity index 100% rename from src/ripple/app/main/Tuning.h rename to src/xrpld/app/main/Tuning.h diff --git a/src/ripple/app/misc/AMMHelpers.h b/src/xrpld/app/misc/AMMHelpers.h similarity index 97% rename from src/ripple/app/misc/AMMHelpers.h rename to src/xrpld/app/misc/AMMHelpers.h index 787bb2300a3..7ad0093a2e4 100644 --- a/src/ripple/app/misc/AMMHelpers.h +++ b/src/xrpld/app/misc/AMMHelpers.h @@ -20,18 +20,18 @@ #ifndef RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED #define RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -385,9 +385,9 @@ changeSpotPriceQuality( { JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) - << " " << to_string(pool.out) << " " - << " " << quality << " " << tfee << " " - << to_string(amounts.in) << " " << to_string(amounts.out); + << " " << to_string(pool.out) << " " << " " << quality + << " " << tfee << " " << to_string(amounts.in) << " " + << to_string(amounts.out); Throw("changeSpotPriceQuality failed"); } else diff --git a/src/ripple/app/misc/AMMUtils.h b/src/xrpld/app/misc/AMMUtils.h similarity index 94% rename from src/ripple/app/misc/AMMUtils.h rename to src/xrpld/app/misc/AMMUtils.h index 9cd4b7d6fec..52fe819a28e 100644 --- a/src/ripple/app/misc/AMMUtils.h +++ b/src/xrpld/app/misc/AMMUtils.h @@ -19,12 +19,12 @@ #ifndef RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED #define RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/xrpld/app/misc/AmendmentTable.h similarity index 97% rename from src/ripple/app/misc/AmendmentTable.h rename to src/xrpld/app/misc/AmendmentTable.h index fef13d50ddb..538d7299f3b 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/xrpld/app/misc/AmendmentTable.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_MISC_AMENDMENTTABLE_H_INCLUDED #define RIPPLE_APP_MISC_AMENDMENTTABLE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/misc/CanonicalTXSet.cpp b/src/xrpld/app/misc/CanonicalTXSet.cpp similarity index 98% rename from src/ripple/app/misc/CanonicalTXSet.cpp rename to src/xrpld/app/misc/CanonicalTXSet.cpp index a9fcd17f056..bb89b598962 100644 --- a/src/ripple/app/misc/CanonicalTXSet.cpp +++ b/src/xrpld/app/misc/CanonicalTXSet.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/misc/CanonicalTXSet.h b/src/xrpld/app/misc/CanonicalTXSet.h similarity index 96% rename from src/ripple/app/misc/CanonicalTXSet.h rename to src/xrpld/app/misc/CanonicalTXSet.h index 3ca2179448f..b061ff10dd6 100644 --- a/src/ripple/app/misc/CanonicalTXSet.h +++ b/src/xrpld/app/misc/CanonicalTXSet.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED #define RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/xrpld/app/misc/CredentialHelpers.cpp b/src/xrpld/app/misc/CredentialHelpers.cpp new file mode 100644 index 00000000000..08b5d804d4b --- /dev/null +++ b/src/xrpld/app/misc/CredentialHelpers.cpp @@ -0,0 +1,262 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include + +#include + +namespace ripple { +namespace credentials { + +bool +checkExpired( + std::shared_ptr const& sleCredential, + NetClock::time_point const& closed) +{ + std::uint32_t const exp = (*sleCredential)[~sfExpiration].value_or( + std::numeric_limits::max()); + std::uint32_t const now = closed.time_since_epoch().count(); + return now > exp; +} + +bool +removeExpired(ApplyView& view, STTx const& tx, beast::Journal const j) +{ + auto const closeTime = view.info().parentCloseTime; + bool foundExpired = false; + + STVector256 const& arr(tx.getFieldV256(sfCredentialIDs)); + for (auto const& h : arr) + { + // Credentials already checked in preclaim. Look only for expired here. + auto const k = keylet::credential(h); + auto const sleCred = view.peek(k); + + if (sleCred && checkExpired(sleCred, closeTime)) + { + JLOG(j.trace()) + << "Credentials are expired. Cred: " << sleCred->getText(); + // delete expired credentials even if the transaction failed + deleteSLE(view, sleCred, j); + foundExpired = true; + } + } + + return foundExpired; +} + +TER +deleteSLE( + ApplyView& view, + std::shared_ptr const& sleCredential, + beast::Journal j) +{ + if (!sleCredential) + return tecNO_ENTRY; + + auto delSLE = + [&view, &sleCredential, j]( + AccountID const& account, SField const& node, bool isOwner) -> TER { + auto const sleAccount = view.peek(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.fatal()) << "Internal error: can't retrieve Owner account."; + return tecINTERNAL; + } + + // Remove object from owner directory + std::uint64_t const page = sleCredential->getFieldU64(node); + if (!view.dirRemove( + keylet::ownerDir(account), page, sleCredential->key(), false)) + { + JLOG(j.fatal()) << "Unable to delete Credential from owner."; + return tefBAD_LEDGER; + } + + if (isOwner) + adjustOwnerCount(view, sleAccount, -1, j); + + return tesSUCCESS; + }; + + auto const issuer = sleCredential->getAccountID(sfIssuer); + auto const subject = sleCredential->getAccountID(sfSubject); + bool const accepted = sleCredential->getFlags() & lsfAccepted; + + auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer)); + if (!isTesSuccess(err)) + return err; + + if (subject != issuer) + { + err = delSLE(subject, sfSubjectNode, accepted); + if (!isTesSuccess(err)) + return err; + } + + // Remove object from ledger + view.erase(sleCredential); + + return tesSUCCESS; +} + +NotTEC +checkFields(PreflightContext const& ctx) +{ + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + return tesSUCCESS; + + auto const& credentials = ctx.tx.getFieldV256(sfCredentialIDs); + if (credentials.empty() || (credentials.size() > maxCredentialsArraySize)) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: Credentials array size is invalid: " + << credentials.size(); + return temMALFORMED; + } + + std::unordered_set duplicates; + for (auto const& cred : credentials) + { + auto [it, ins] = duplicates.insert(cred); + if (!ins) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: duplicates in credentials."; + return temMALFORMED; + } + } + + return tesSUCCESS; +} + +TER +valid(PreclaimContext const& ctx, AccountID const& src) +{ + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + return tesSUCCESS; + + auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs)); + for (auto const& h : credIDs) + { + auto const sleCred = ctx.view.read(keylet::credential(h)); + if (!sleCred) + { + JLOG(ctx.j.trace()) << "Credential doesn't exist. Cred: " << h; + return tecBAD_CREDENTIALS; + } + + if (sleCred->getAccountID(sfSubject) != src) + { + JLOG(ctx.j.trace()) + << "Credential doesn’t belong to the source account. Cred: " + << h; + return tecBAD_CREDENTIALS; + } + + if (!(sleCred->getFlags() & lsfAccepted)) + { + JLOG(ctx.j.trace()) << "Credential isn't accepted. Cred: " << h; + return tecBAD_CREDENTIALS; + } + + // Expiration checks are in doApply + } + + return tesSUCCESS; +} + +TER +authorized(ApplyContext const& ctx, AccountID const& dst) +{ + auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs)); + std::set> sorted; + std::vector> lifeExtender; + lifeExtender.reserve(credIDs.size()); + for (auto const& h : credIDs) + { + auto sleCred = ctx.view().read(keylet::credential(h)); + if (!sleCred) // already checked in preclaim + return tefINTERNAL; + + auto [it, ins] = + sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]); + if (!ins) + return tefINTERNAL; + lifeExtender.push_back(std::move(sleCred)); + } + + if (!ctx.view().exists(keylet::depositPreauth(dst, sorted))) + { + JLOG(ctx.journal.trace()) << "DepositPreauth doesn't exist"; + return tecNO_PERMISSION; + } + + return tesSUCCESS; +} + +std::set> +makeSorted(STArray const& in) +{ + std::set> out; + for (auto const& cred : in) + { + auto [it, ins] = out.emplace(cred[sfIssuer], cred[sfCredentialType]); + if (!ins) + return {}; + } + return out; +} + +} // namespace credentials + +TER +verifyDepositPreauth( + ApplyContext& ctx, + AccountID const& src, + AccountID const& dst, + std::shared_ptr const& sleDst) +{ + // If depositPreauth is enabled, then an account that requires + // authorization has at least two ways to get a payment in: + // 1. If src == dst, or + // 2. If src is deposit preauthorized by dst (either by account or by + // credentials). + + bool const credentialsPresent = ctx.tx.isFieldPresent(sfCredentialIDs); + + if (credentialsPresent && + credentials::removeExpired(ctx.view(), ctx.tx, ctx.journal)) + return tecEXPIRED; + + if (sleDst && (sleDst->getFlags() & lsfDepositAuth)) + { + if (src != dst) + { + if (!ctx.view().exists(keylet::depositPreauth(dst, src))) + return !credentialsPresent ? tecNO_PERMISSION + : credentials::authorized(ctx, dst); + } + } + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/xrpld/app/misc/CredentialHelpers.h b/src/xrpld/app/misc/CredentialHelpers.h new file mode 100644 index 00000000000..3291fc1daa6 --- /dev/null +++ b/src/xrpld/app/misc/CredentialHelpers.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include + +namespace ripple { +namespace credentials { + +// These function will be used by the code that use DepositPreauth / Credentials +// (and any future preauthorization modes) as part of authorization (all the +// transfer funds transactions) + +// Check if credential sfExpiration field has passed ledger's parentCloseTime +bool +checkExpired( + std::shared_ptr const& sleCredential, + NetClock::time_point const& closed); + +// Return true if at least 1 expired credentials was found(and deleted) +bool +removeExpired(ApplyView& view, STTx const& tx, beast::Journal const j); + +// Actually remove a credentials object from the ledger +TER +deleteSLE( + ApplyView& view, + std::shared_ptr const& sleCredential, + beast::Journal j); + +// Amendment and parameters checks for sfCredentialIDs field +NotTEC +checkFields(PreflightContext const& ctx); + +// Accessing the ledger to check if provided credentials are valid +TER +valid(PreclaimContext const& ctx, AccountID const& src); + +// This function is only called when we about to return tecNO_PERMISSION because +// all the checks for the DepositPreauth authorization failed. +TER +authorized(ApplyContext const& ctx, AccountID const& dst); + +// return empty set if there are duplicates +std::set> +makeSorted(STArray const& in); + +} // namespace credentials + +// Check expired credentials and for existing DepositPreauth ledger object +TER +verifyDepositPreauth( + ApplyContext& ctx, + AccountID const& src, + AccountID const& dst, + std::shared_ptr const& sleDst); + +} // namespace ripple diff --git a/src/ripple/app/misc/DeliverMax.h b/src/xrpld/app/misc/DeliverMax.h similarity index 97% rename from src/ripple/app/misc/DeliverMax.h rename to src/xrpld/app/misc/DeliverMax.h index ddc20bdd7b4..3bc875ee4ba 100644 --- a/src/ripple/app/misc/DeliverMax.h +++ b/src/xrpld/app/misc/DeliverMax.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_MISC_DELIVERMAX_H_INCLUDED #define RIPPLE_APP_MISC_DELIVERMAX_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/app/misc/FeeEscalation.md b/src/xrpld/app/misc/FeeEscalation.md similarity index 100% rename from src/ripple/app/misc/FeeEscalation.md rename to src/xrpld/app/misc/FeeEscalation.md diff --git a/src/ripple/app/misc/FeeVote.h b/src/xrpld/app/misc/FeeVote.h similarity index 91% rename from src/ripple/app/misc/FeeVote.h rename to src/xrpld/app/misc/FeeVote.h index a90f82efb35..47769e21e4d 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/xrpld/app/misc/FeeVote.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED #define RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp similarity index 95% rename from src/ripple/app/misc/FeeVoteImpl.cpp rename to src/xrpld/app/misc/FeeVoteImpl.cpp index 0d60dc6b78e..cb4e57b0f73 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -277,9 +277,9 @@ FeeVoteImpl::doVoting( } // choose our positions - // TODO: Use structured binding once LLVM issue - // https://github.com/llvm/llvm-project/issues/48582 - // is fixed. + // TODO: Use structured binding once LLVM 16 is the minimum supported + // version. See also: https://github.com/llvm/llvm-project/issues/48582 + // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c auto const baseFee = baseFeeVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes(); diff --git a/src/ripple/app/misc/HashRouter.cpp b/src/xrpld/app/misc/HashRouter.cpp similarity index 98% rename from src/ripple/app/misc/HashRouter.cpp rename to src/xrpld/app/misc/HashRouter.cpp index 8085d6892ab..c117d20fe2b 100644 --- a/src/ripple/app/misc/HashRouter.cpp +++ b/src/xrpld/app/misc/HashRouter.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/misc/HashRouter.h b/src/xrpld/app/misc/HashRouter.h similarity index 96% rename from src/ripple/app/misc/HashRouter.h rename to src/xrpld/app/misc/HashRouter.h index 8c546b2c51d..e9d040fc8bf 100644 --- a/src/ripple/app/misc/HashRouter.h +++ b/src/xrpld/app/misc/HashRouter.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_MISC_HASHROUTER_H_INCLUDED #define RIPPLE_APP_MISC_HASHROUTER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/misc/LoadFeeTrack.h b/src/xrpld/app/misc/LoadFeeTrack.h similarity index 96% rename from src/ripple/app/misc/LoadFeeTrack.h rename to src/xrpld/app/misc/LoadFeeTrack.h index d670c0b7e11..6c37864e2fd 100644 --- a/src/ripple/app/misc/LoadFeeTrack.h +++ b/src/xrpld/app/misc/LoadFeeTrack.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_CORE_LOADFEETRACK_H_INCLUDED #define RIPPLE_CORE_LOADFEETRACK_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/misc/Manifest.h b/src/xrpld/app/misc/Manifest.h similarity index 98% rename from src/ripple/app/misc/Manifest.h rename to src/xrpld/app/misc/Manifest.h index b1cb5d2f325..1b53fda77b5 100644 --- a/src/ripple/app/misc/Manifest.h +++ b/src/xrpld/app/misc/Manifest.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_MISC_MANIFEST_H_INCLUDED #define RIPPLE_APP_MISC_MANIFEST_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/NegativeUNLVote.cpp b/src/xrpld/app/misc/NegativeUNLVote.cpp similarity index 98% rename from src/ripple/app/misc/NegativeUNLVote.cpp rename to src/xrpld/app/misc/NegativeUNLVote.cpp index 9b616be6ce1..45d72bcd2b3 100644 --- a/src/ripple/app/misc/NegativeUNLVote.cpp +++ b/src/xrpld/app/misc/NegativeUNLVote.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/misc/NegativeUNLVote.h b/src/xrpld/app/misc/NegativeUNLVote.h similarity index 97% rename from src/ripple/app/misc/NegativeUNLVote.h rename to src/xrpld/app/misc/NegativeUNLVote.h index 6ba8b3bf26e..f0284f267a8 100644 --- a/src/ripple/app/misc/NegativeUNLVote.h +++ b/src/xrpld/app/misc/NegativeUNLVote.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_MISC_NEGATIVEUNLVOTE_H_INCLUDED #define RIPPLE_APP_MISC_NEGATIVEUNLVOTE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp similarity index 91% rename from src/ripple/app/misc/NetworkOPs.cpp rename to src/xrpld/app/misc/NetworkOPs.cpp index e2f5ef1a0b3..d647df91f1e 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -17,57 +17,56 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -458,15 +457,6 @@ class NetworkOPsImp final : public NetworkOPs void pubValidation(std::shared_ptr const& val) override; - void - forwardValidation(Json::Value const& jvObj) override; - void - forwardManifest(Json::Value const& jvObj) override; - void - forwardProposedTransaction(Json::Value const& jvObj) override; - void - forwardProposedAccountTransaction(Json::Value const& jvObj) override; - //-------------------------------------------------------------------------- // // InfoSub::Source. @@ -2321,7 +2311,7 @@ NetworkOPsImp::recvValidation( bypassAccept = BypassAccept::yes; else pendingValidations_.insert(val->getLedgerHash()); - lock.unlock(); + scope_unlock unlock(lock); handleNewValidation(app_, val, source, bypassAccept, m_journal); } catch (std::exception const& e) @@ -2338,10 +2328,9 @@ NetworkOPsImp::recvValidation( } if (bypassAccept == BypassAccept::no) { - lock.lock(); pendingValidations_.erase(val->getLedgerHash()); - lock.unlock(); } + lock.unlock(); pubValidation(val); @@ -2506,10 +2495,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) info[jss::counters] = app_.getPerfLog().countersJson(); Json::Value nodestore(Json::objectValue); - if (app_.getShardStore()) - app_.getShardStore()->getCountsJson(nodestore); - else - app_.getNodeStore().getCountsJson(nodestore); + app_.getNodeStore().getCountsJson(nodestore); info[jss::counters][jss::nodestore] = nodestore; info[jss::current_activities] = app_.getPerfLog().currentJson(); } @@ -2527,8 +2513,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) if (fp != 0) info[jss::fetch_pack] = Json::UInt(fp); - if (!app_.config().reporting()) - info[jss::peers] = Json::UInt(app_.overlay().size()); + info[jss::peers] = Json::UInt(app_.overlay().size()); Json::Value lastClose = Json::objectValue; lastClose[jss::proposers] = Json::UInt(mConsensus.prevProposers()); @@ -2551,85 +2536,80 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) if (admin) info[jss::load] = m_job_queue.getJson(); - if (!app_.config().reporting()) + if (auto const netid = app_.overlay().networkID()) + info[jss::network_id] = static_cast(*netid); + + auto const escalationMetrics = + app_.getTxQ().getMetrics(*app_.openLedger().current()); + + auto const loadFactorServer = app_.getFeeTrack().getLoadFactor(); + auto const loadBaseServer = app_.getFeeTrack().getLoadBase(); + /* Scale the escalated fee level to unitless "load factor". + In practice, this just strips the units, but it will continue + to work correctly if either base value ever changes. */ + auto const loadFactorFeeEscalation = + mulDiv( + escalationMetrics.openLedgerFeeLevel, + loadBaseServer, + escalationMetrics.referenceFeeLevel) + .value_or(ripple::muldiv_max); + + auto const loadFactor = std::max( + safe_cast(loadFactorServer), loadFactorFeeEscalation); + + if (!human) + { + info[jss::load_base] = loadBaseServer; + info[jss::load_factor] = trunc32(loadFactor); + info[jss::load_factor_server] = loadFactorServer; + + /* Json::Value doesn't support uint64, so clamp to max + uint32 value. This is mostly theoretical, since there + probably isn't enough extant XRP to drive the factor + that high. + */ + info[jss::load_factor_fee_escalation] = + escalationMetrics.openLedgerFeeLevel.jsonClipped(); + info[jss::load_factor_fee_queue] = + escalationMetrics.minProcessingFeeLevel.jsonClipped(); + info[jss::load_factor_fee_reference] = + escalationMetrics.referenceFeeLevel.jsonClipped(); + } + else { - if (auto const netid = app_.overlay().networkID()) - info[jss::network_id] = static_cast(*netid); - - auto const escalationMetrics = - app_.getTxQ().getMetrics(*app_.openLedger().current()); - - auto const loadFactorServer = app_.getFeeTrack().getLoadFactor(); - auto const loadBaseServer = app_.getFeeTrack().getLoadBase(); - /* Scale the escalated fee level to unitless "load factor". - In practice, this just strips the units, but it will continue - to work correctly if either base value ever changes. */ - auto const loadFactorFeeEscalation = - mulDiv( - escalationMetrics.openLedgerFeeLevel, - loadBaseServer, - escalationMetrics.referenceFeeLevel) - .value_or(ripple::muldiv_max); - - auto const loadFactor = std::max( - safe_cast(loadFactorServer), - loadFactorFeeEscalation); + info[jss::load_factor] = + static_cast(loadFactor) / loadBaseServer; - if (!human) + if (loadFactorServer != loadFactor) + info[jss::load_factor_server] = + static_cast(loadFactorServer) / loadBaseServer; + + if (admin) { - info[jss::load_base] = loadBaseServer; - info[jss::load_factor] = trunc32(loadFactor); - info[jss::load_factor_server] = loadFactorServer; - - /* Json::Value doesn't support uint64, so clamp to max - uint32 value. This is mostly theoretical, since there - probably isn't enough extant XRP to drive the factor - that high. - */ + std::uint32_t fee = app_.getFeeTrack().getLocalFee(); + if (fee != loadBaseServer) + info[jss::load_factor_local] = + static_cast(fee) / loadBaseServer; + fee = app_.getFeeTrack().getRemoteFee(); + if (fee != loadBaseServer) + info[jss::load_factor_net] = + static_cast(fee) / loadBaseServer; + fee = app_.getFeeTrack().getClusterFee(); + if (fee != loadBaseServer) + info[jss::load_factor_cluster] = + static_cast(fee) / loadBaseServer; + } + if (escalationMetrics.openLedgerFeeLevel != + escalationMetrics.referenceFeeLevel && + (admin || loadFactorFeeEscalation != loadFactor)) info[jss::load_factor_fee_escalation] = - escalationMetrics.openLedgerFeeLevel.jsonClipped(); + escalationMetrics.openLedgerFeeLevel.decimalFromReference( + escalationMetrics.referenceFeeLevel); + if (escalationMetrics.minProcessingFeeLevel != + escalationMetrics.referenceFeeLevel) info[jss::load_factor_fee_queue] = - escalationMetrics.minProcessingFeeLevel.jsonClipped(); - info[jss::load_factor_fee_reference] = - escalationMetrics.referenceFeeLevel.jsonClipped(); - } - else - { - info[jss::load_factor] = - static_cast(loadFactor) / loadBaseServer; - - if (loadFactorServer != loadFactor) - info[jss::load_factor_server] = - static_cast(loadFactorServer) / loadBaseServer; - - if (admin) - { - std::uint32_t fee = app_.getFeeTrack().getLocalFee(); - if (fee != loadBaseServer) - info[jss::load_factor_local] = - static_cast(fee) / loadBaseServer; - fee = app_.getFeeTrack().getRemoteFee(); - if (fee != loadBaseServer) - info[jss::load_factor_net] = - static_cast(fee) / loadBaseServer; - fee = app_.getFeeTrack().getClusterFee(); - if (fee != loadBaseServer) - info[jss::load_factor_cluster] = - static_cast(fee) / loadBaseServer; - } - if (escalationMetrics.openLedgerFeeLevel != - escalationMetrics.referenceFeeLevel && - (admin || loadFactorFeeEscalation != loadFactor)) - info[jss::load_factor_fee_escalation] = - escalationMetrics.openLedgerFeeLevel.decimalFromReference( - escalationMetrics.referenceFeeLevel); - if (escalationMetrics.minProcessingFeeLevel != - escalationMetrics.referenceFeeLevel) - info[jss::load_factor_fee_queue] = - escalationMetrics.minProcessingFeeLevel - .decimalFromReference( - escalationMetrics.referenceFeeLevel); - } + escalationMetrics.minProcessingFeeLevel.decimalFromReference( + escalationMetrics.referenceFeeLevel); } bool valid = false; @@ -2637,7 +2617,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) if (lpClosed) valid = true; - else if (!app_.config().reporting()) + else lpClosed = m_ledgerMaster.getClosedLedger(); if (lpClosed) @@ -2668,11 +2648,6 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) l[jss::close_time_offset] = static_cast(closeOffset.count()); -#if RIPPLED_REPORTING - std::int64_t const dbAge = - std::max(m_ledgerMaster.getValidatedLedgerAge().count(), 0L); - l[jss::age] = Json::UInt(dbAge); -#else constexpr std::chrono::seconds highAgeThreshold{1000000}; if (m_ledgerMaster.haveValidated()) { @@ -2692,7 +2667,6 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) Json::UInt(age < highAgeThreshold ? age.count() : 0); } } -#endif } if (valid) @@ -2709,19 +2683,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) accounting_.json(info); info[jss::uptime] = UptimeClock::now().time_since_epoch().count(); - if (!app_.config().reporting()) - { - info[jss::jq_trans_overflow] = - std::to_string(app_.overlay().getJqTransOverflow()); - info[jss::peer_disconnects] = - std::to_string(app_.overlay().getPeerDisconnect()); - info[jss::peer_disconnects_resources] = - std::to_string(app_.overlay().getPeerDisconnectCharges()); - } - else - { - info["reporting"] = app_.getReportingETL().getInfo(); - } + info[jss::jq_trans_overflow] = + std::to_string(app_.overlay().getJqTransOverflow()); + info[jss::peer_disconnects] = + std::to_string(app_.overlay().getPeerDisconnect()); + info[jss::peer_disconnects_resources] = + std::to_string(app_.overlay().getPeerDisconnectCharges()); // This array must be sorted in increasing order. static constexpr std::array protocols{ @@ -2817,163 +2784,6 @@ NetworkOPsImp::pubProposedTransaction( pubProposedAccountTransaction(ledger, transaction, result); } -void -NetworkOPsImp::forwardProposedTransaction(Json::Value const& jvObj) -{ - // reporting does not forward validated transactions - // validated transactions will be published to the proper streams when the - // etl process writes a validated ledger - if (jvObj[jss::validated].asBool()) - return; - { - std::lock_guard sl(mSubLock); - - auto it = mStreamMaps[sRTTransactions].begin(); - while (it != mStreamMaps[sRTTransactions].end()) - { - InfoSub::pointer p = it->second.lock(); - - if (p) - { - p->send(jvObj, true); - ++it; - } - else - { - it = mStreamMaps[sRTTransactions].erase(it); - } - } - } - - forwardProposedAccountTransaction(jvObj); -} - -void -NetworkOPsImp::forwardValidation(Json::Value const& jvObj) -{ - std::lock_guard sl(mSubLock); - - for (auto i = mStreamMaps[sValidations].begin(); - i != mStreamMaps[sValidations].end();) - { - if (auto p = i->second.lock()) - { - p->send(jvObj, true); - ++i; - } - else - { - i = mStreamMaps[sValidations].erase(i); - } - } -} - -void -NetworkOPsImp::forwardManifest(Json::Value const& jvObj) -{ - std::lock_guard sl(mSubLock); - - for (auto i = mStreamMaps[sManifests].begin(); - i != mStreamMaps[sManifests].end();) - { - if (auto p = i->second.lock()) - { - p->send(jvObj, true); - ++i; - } - else - { - i = mStreamMaps[sManifests].erase(i); - } - } -} - -static void -getAccounts(Json::Value const& jvObj, std::vector& accounts) -{ - for (auto& jv : jvObj) - { - if (jv.isObject()) - { - getAccounts(jv, accounts); - } - else if (jv.isString()) - { - auto account = RPC::accountFromStringStrict(jv.asString()); - if (account) - accounts.push_back(*account); - } - } -} - -void -NetworkOPsImp::forwardProposedAccountTransaction(Json::Value const& jvObj) -{ - hash_set notify; - int iProposed = 0; - // check if there are any subscribers before attempting to parse the JSON - { - std::lock_guard sl(mSubLock); - - if (mSubRTAccount.empty()) - return; - } - - // parse the JSON outside of the lock - std::vector accounts; - if (jvObj.isMember(jss::transaction)) - { - try - { - getAccounts(jvObj[jss::transaction], accounts); - } - catch (...) - { - JLOG(m_journal.debug()) - << __func__ << " : " - << "error parsing json for accounts affected"; - return; - } - } - { - std::lock_guard sl(mSubLock); - - if (!mSubRTAccount.empty()) - { - for (auto const& affectedAccount : accounts) - { - auto simiIt = mSubRTAccount.find(affectedAccount); - if (simiIt != mSubRTAccount.end()) - { - auto it = simiIt->second.begin(); - - while (it != simiIt->second.end()) - { - InfoSub::pointer p = it->second.lock(); - - if (p) - { - notify.insert(p); - ++it; - ++iProposed; - } - else - it = simiIt->second.erase(it); - } - } - } - } - } - JLOG(m_journal.trace()) << "forwardProposedAccountTransaction:" - << " iProposed=" << iProposed; - - if (!notify.empty()) - { - for (InfoSub::ref isrListener : notify) - isrListener->send(jvObj, true); - } -} - void NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) { @@ -3090,8 +2900,6 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) void NetworkOPsImp::reportFeeChange() { - if (app_.config().reporting()) - return; ServerFeeSummary f{ app_.openLedger().current()->fees().base, app_.getTxQ().getMetrics(*app_.openLedger().current()), @@ -3155,6 +2963,8 @@ NetworkOPsImp::transJson( jvObj[jss::meta] = meta->get().getJson(JsonOptions::none); RPC::insertDeliveredAmount( jvObj[jss::meta], *ledger, transaction, meta->get()); + RPC::insertMPTokenIssuanceID( + jvObj[jss::meta], transaction, meta->get()); } if (!ledger->open()) @@ -3370,8 +3180,8 @@ NetworkOPsImp::pubAccountTransaction( } JLOG(m_journal.trace()) - << "pubAccountTransaction: " - << "proposed=" << iProposed << ", accepted=" << iAccepted; + << "pubAccountTransaction: " << "proposed=" << iProposed + << ", accepted=" << iAccepted; if (!notify.empty() || !accountHistoryNotify.empty()) { @@ -3571,30 +3381,8 @@ NetworkOPsImp::unsubAccountInternal( void NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) { - enum DatabaseType { Postgres, Sqlite, None }; + enum DatabaseType { Sqlite, None }; static const auto databaseType = [&]() -> DatabaseType { -#ifdef RIPPLED_REPORTING - if (app_.config().reporting()) - { - // Use a dynamic_cast to return DatabaseType::None - // on failure. - if (dynamic_cast(&app_.getRelationalDatabase())) - { - return DatabaseType::Postgres; - } - return DatabaseType::None; - } - else - { - // Use a dynamic_cast to return DatabaseType::None - // on failure. - if (dynamic_cast(&app_.getRelationalDatabase())) - { - return DatabaseType::Sqlite; - } - return DatabaseType::None; - } -#else // Use a dynamic_cast to return DatabaseType::None // on failure. if (dynamic_cast(&app_.getRelationalDatabase())) @@ -3602,7 +3390,6 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) return DatabaseType::Sqlite; } return DatabaseType::None; -#endif }(); if (databaseType == DatabaseType::None) @@ -3705,40 +3492,6 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) std::optional>> { switch (dbType) { - case Postgres: { - auto db = static_cast( - &app_.getRelationalDatabase()); - RelationalDatabase::AccountTxArgs args; - args.account = accountId; - LedgerRange range{minLedger, maxLedger}; - args.ledger = range; - args.marker = marker; - auto [txResult, status] = db->getAccountTx(args); - if (status != rpcSUCCESS) - { - JLOG(m_journal.debug()) - << "AccountHistory job for account " - << toBase58(accountId) - << " getAccountTx failed"; - return {}; - } - - if (auto txns = - std::get_if( - &txResult.transactions); - txns) - { - return std::make_pair(*txns, txResult.marker); - } - else - { - JLOG(m_journal.debug()) - << "AccountHistory job for account " - << toBase58(accountId) - << " getAccountTx wrong data"; - return {}; - } - } case Sqlite: { auto db = static_cast( &app_.getRelationalDatabase()); diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/xrpld/app/misc/NetworkOPs.h similarity index 93% rename from src/ripple/app/misc/NetworkOPs.h rename to src/xrpld/app/misc/NetworkOPs.h index d53127ed3b6..166b9e9e11f 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/xrpld/app/misc/NetworkOPs.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED #define RIPPLE_APP_MISC_NETWORKOPS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -261,15 +261,6 @@ class NetworkOPs : public InfoSub::Source virtual void pubValidation(std::shared_ptr const& val) = 0; - virtual void - forwardValidation(Json::Value const& jvObj) = 0; - virtual void - forwardManifest(Json::Value const& jvObj) = 0; - virtual void - forwardProposedTransaction(Json::Value const& jvObj) = 0; - virtual void - forwardProposedAccountTransaction(Json::Value const& jvObj) = 0; - virtual void stateAccounting(Json::Value& obj) = 0; }; diff --git a/src/ripple/app/misc/README.md b/src/xrpld/app/misc/README.md similarity index 100% rename from src/ripple/app/misc/README.md rename to src/xrpld/app/misc/README.md diff --git a/src/ripple/app/misc/SHAMapStore.h b/src/xrpld/app/misc/SHAMapStore.h similarity index 96% rename from src/ripple/app/misc/SHAMapStore.h rename to src/xrpld/app/misc/SHAMapStore.h index c42e5f5a52a..d8415713a76 100644 --- a/src/ripple/app/misc/SHAMapStore.h +++ b/src/xrpld/app/misc/SHAMapStore.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_MISC_SHAMAPSTORE_H_INCLUDED #define RIPPLE_APP_MISC_SHAMAPSTORE_H_INCLUDED -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp similarity index 90% rename from src/ripple/app/misc/SHAMapStoreImp.cpp rename to src/xrpld/app/misc/SHAMapStoreImp.cpp index d5cb07792dc..1ce862b095f 100644 --- a/src/ripple/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -17,18 +17,17 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -120,13 +119,6 @@ SHAMapStoreImp::SHAMapStoreImp( if (deleteInterval_) { - if (app_.config().reporting()) - { - Throw( - "Reporting does not support online_delete. Remove " - "online_delete info from config"); - } - // Configuration that affects the behavior of online delete get_if_exists(section, "delete_batch", deleteBatch_); std::uint32_t temp; @@ -188,12 +180,6 @@ SHAMapStoreImp::makeNodeStore(int readThreads) if (deleteInterval_) { - if (app_.config().reporting()) - { - Throw( - "Reporting does not support online_delete. Remove " - "online_delete info from config"); - } SavedState state = state_db_.getState(); auto writableBackend = makeBackendRotating(state.writableDb); auto archiveBackend = makeBackendRotating(state.archiveDb); @@ -279,19 +265,12 @@ SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node) void SHAMapStoreImp::run() { - if (app_.config().reporting()) - { - assert(false); - Throw( - "Reporting does not support online_delete. Remove " - "online_delete info from config"); - } beast::setCurrentThreadName("SHAMapStore"); LedgerIndex lastRotated = state_db_.getState().lastRotated; netOPs_ = &app_.getOPs(); ledgerMaster_ = &app_.getLedgerMaster(); - fullBelowCache_ = &(*app_.getNodeFamily().getFullBelowCache(0)); - treeNodeCache_ = &(*app_.getNodeFamily().getTreeNodeCache(0)); + fullBelowCache_ = &(*app_.getNodeFamily().getFullBelowCache()); + treeNodeCache_ = &(*app_.getNodeFamily().getTreeNodeCache()); if (advisoryDelete_) canDelete_ = state_db_.getCanDelete(); @@ -329,27 +308,8 @@ SHAMapStoreImp::run() validatedSeq >= lastRotated + deleteInterval_ && canDelete_ >= lastRotated - 1 && healthWait() == keepGoing; - // Make sure we don't delete ledgers currently being - // imported into the ShardStore - bool const waitForImport = readyToRotate && [this, lastRotated] { - if (auto shardStore = app_.getShardStore()) - { - if (auto sequence = shardStore->getDatabaseImportSequence()) - return sequence <= lastRotated - 1; - } - - return false; - }(); - - if (waitForImport) - { - JLOG(journal_.info()) - << "NOT rotating validatedSeq " << validatedSeq - << " as rotation would interfere with ShardStore import"; - } - // will delete up to (not including) lastRotated - if (readyToRotate && !waitForImport) + if (readyToRotate) { JLOG(journal_.warn()) << "rotating validatedSeq " << validatedSeq << " lastRotated " @@ -616,13 +576,6 @@ SHAMapStoreImp::freshenCaches() void SHAMapStoreImp::clearPrior(LedgerIndex lastRotated) { - if (app_.config().reporting()) - { - assert(false); - Throw( - "Reporting does not support online_delete. Remove " - "online_delete info from config"); - } // Do not allow ledgers to be acquired from the network // that are about to be deleted. minimumOnline_ = lastRotated + 1; diff --git a/src/ripple/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h similarity index 96% rename from src/ripple/app/misc/SHAMapStoreImp.h rename to src/xrpld/app/misc/SHAMapStoreImp.h index 995ee0267bb..7d36f092be8 100644 --- a/src/ripple/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_MISC_SHAMAPSTOREIMP_H_INCLUDED #define RIPPLE_APP_MISC_SHAMAPSTOREIMP_H_INCLUDED -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include diff --git a/src/ripple/app/misc/Transaction.h b/src/xrpld/app/misc/Transaction.h similarity index 97% rename from src/ripple/app/misc/Transaction.h rename to src/xrpld/app/misc/Transaction.h index 36815ba0aa0..a2ef496dffd 100644 --- a/src/ripple/app/misc/Transaction.h +++ b/src/xrpld/app/misc/Transaction.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_MISC_TRANSACTION_H_INCLUDED #define RIPPLE_APP_MISC_TRANSACTION_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h similarity index 99% rename from src/ripple/app/misc/TxQ.h rename to src/xrpld/app/misc/TxQ.h index 7be29b49021..b962d96d50f 100644 --- a/src/ripple/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_TXQ_H_INCLUDED #define RIPPLE_TXQ_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/misc/ValidatorKeys.h b/src/xrpld/app/misc/ValidatorKeys.h similarity index 94% rename from src/ripple/app/misc/ValidatorKeys.h rename to src/xrpld/app/misc/ValidatorKeys.h index a6b53841739..f5b9e5735a6 100644 --- a/src/ripple/app/misc/ValidatorKeys.h +++ b/src/xrpld/app/misc/ValidatorKeys.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_MISC_VALIDATOR_KEYS_H_INCLUDED #define RIPPLE_APP_MISC_VALIDATOR_KEYS_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h similarity index 98% rename from src/ripple/app/misc/ValidatorList.h rename to src/xrpld/app/misc/ValidatorList.h index 280818abd35..543eba2f6b7 100644 --- a/src/ripple/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED #define RIPPLE_APP_MISC_VALIDATORLIST_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -633,7 +633,7 @@ class ValidatorList */ std::optional getAvailable( - boost::beast::string_view const& pubKey, + std::string_view pubKey, std::optional forceVersion = {}); /** Return the number of configured validator list sites. */ diff --git a/src/ripple/app/misc/ValidatorSite.h b/src/xrpld/app/misc/ValidatorSite.h similarity index 96% rename from src/ripple/app/misc/ValidatorSite.h rename to src/xrpld/app/misc/ValidatorSite.h index 57606066bf0..39bf895807f 100644 --- a/src/ripple/app/misc/ValidatorSite.h +++ b/src/xrpld/app/misc/ValidatorSite.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_MISC_VALIDATORSITE_H_INCLUDED #define RIPPLE_APP_MISC_VALIDATORSITE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/misc/impl/AMMHelpers.cpp b/src/xrpld/app/misc/detail/AMMHelpers.cpp similarity index 99% rename from src/ripple/app/misc/impl/AMMHelpers.cpp rename to src/xrpld/app/misc/detail/AMMHelpers.cpp index 5ad59ea1c28..f10b4c15eb0 100644 --- a/src/ripple/app/misc/impl/AMMHelpers.cpp +++ b/src/xrpld/app/misc/detail/AMMHelpers.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/misc/impl/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp similarity index 97% rename from src/ripple/app/misc/impl/AMMUtils.cpp rename to src/xrpld/app/misc/detail/AMMUtils.cpp index 4f6f0fbd3b5..efc80cf17b6 100644 --- a/src/ripple/app/misc/impl/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -16,12 +16,12 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -312,7 +312,7 @@ initializeFeeAuctionVote( auto const& rules = view.rules(); // AMM creator gets the voting slot. STArray voteSlots; - STObject voteEntry = STObject::makeInnerObject(sfVoteEntry, rules); + STObject voteEntry = STObject::makeInnerObject(sfVoteEntry); if (tfee != 0) voteEntry.setFieldU16(sfTradingFee, tfee); voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR); @@ -325,7 +325,7 @@ initializeFeeAuctionVote( if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot)) { - STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot, rules); + STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot); ammSle->set(std::move(auctionSlot)); } STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot); diff --git a/src/ripple/app/misc/impl/AccountTxPaging.cpp b/src/xrpld/app/misc/detail/AccountTxPaging.cpp similarity index 86% rename from src/ripple/app/misc/impl/AccountTxPaging.cpp rename to src/xrpld/app/misc/detail/AccountTxPaging.cpp index 433463e2826..898c41b40c4 100644 --- a/src/ripple/app/misc/impl/AccountTxPaging.cpp +++ b/src/xrpld/app/misc/detail/AccountTxPaging.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/impl/AccountTxPaging.h b/src/xrpld/app/misc/detail/AccountTxPaging.h similarity index 97% rename from src/ripple/app/misc/impl/AccountTxPaging.h rename to src/xrpld/app/misc/detail/AccountTxPaging.h index 6b8f235b5a8..23a5cbd9c23 100644 --- a/src/ripple/app/misc/impl/AccountTxPaging.h +++ b/src/xrpld/app/misc/detail/AccountTxPaging.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_MISC_IMPL_ACCOUNTTXPAGING_H_INCLUDED #define RIPPLE_APP_MISC_IMPL_ACCOUNTTXPAGING_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/xrpld/app/misc/detail/AmendmentTable.cpp similarity index 99% rename from src/ripple/app/misc/impl/AmendmentTable.cpp rename to src/xrpld/app/misc/detail/AmendmentTable.cpp index 8f9556e0714..62b80890821 100644 --- a/src/ripple/app/misc/impl/AmendmentTable.cpp +++ b/src/xrpld/app/misc/detail/AmendmentTable.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/misc/impl/DeliverMax.cpp b/src/xrpld/app/misc/detail/DeliverMax.cpp similarity index 95% rename from src/ripple/app/misc/impl/DeliverMax.cpp rename to src/xrpld/app/misc/detail/DeliverMax.cpp index 810b750a355..58a23599728 100644 --- a/src/ripple/app/misc/impl/DeliverMax.cpp +++ b/src/xrpld/app/misc/detail/DeliverMax.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include +#include -#include +#include namespace ripple { namespace RPC { diff --git a/src/ripple/app/misc/impl/LoadFeeTrack.cpp b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp similarity index 90% rename from src/ripple/app/misc/impl/LoadFeeTrack.cpp rename to src/xrpld/app/misc/detail/LoadFeeTrack.cpp index 86d145c856c..1267af594d0 100644 --- a/src/ripple/app/misc/impl/LoadFeeTrack.cpp +++ b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/impl/Manifest.cpp b/src/xrpld/app/misc/detail/Manifest.cpp similarity index 98% rename from src/ripple/app/misc/impl/Manifest.cpp rename to src/xrpld/app/misc/detail/Manifest.cpp index 2916d6d2f32..a17858ceb39 100644 --- a/src/ripple/app/misc/impl/Manifest.cpp +++ b/src/xrpld/app/misc/detail/Manifest.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/misc/impl/Transaction.cpp b/src/xrpld/app/misc/detail/Transaction.cpp similarity index 84% rename from src/ripple/app/misc/impl/Transaction.cpp rename to src/xrpld/app/misc/detail/Transaction.cpp index e3ec41a25d4..c8f9df232e0 100644 --- a/src/ripple/app/misc/impl/Transaction.cpp +++ b/src/xrpld/app/misc/detail/Transaction.cpp @@ -17,21 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -130,20 +128,6 @@ Transaction::load( return load(id, app, op{range}, ec); } -Transaction::Locator -Transaction::locate(uint256 const& id, Application& app) -{ - auto const db = - dynamic_cast(&app.getRelationalDatabase()); - - if (!db) - { - Throw("Failed to get relational database"); - } - - return db->locateTransaction(id); -} - std::variant< std::pair, std::shared_ptr>, TxSearched> diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp similarity index 99% rename from src/ripple/app/misc/impl/TxQ.cpp rename to src/xrpld/app/misc/detail/TxQ.cpp index faaca0655cf..a4e62b382a7 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -1485,11 +1485,11 @@ TxQ::accept(Application& app, OpenView& view) } else { - JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID - << " failed with " << transToken(txnResult) - << ". Leave in queue." - << " Applied: " << didApply - << ". Flags: " << candidateIter->flags; + JLOG(j_.debug()) + << "Queued transaction " << candidateIter->txID + << " failed with " << transToken(txnResult) + << ". Leave in queue." << " Applied: " << didApply + << ". Flags: " << candidateIter->flags; if (account.retryPenalty && candidateIter->retriesRemaining > 2) candidateIter->retriesRemaining = 1; else diff --git a/src/ripple/app/misc/impl/ValidatorKeys.cpp b/src/xrpld/app/misc/detail/ValidatorKeys.cpp similarity index 93% rename from src/ripple/app/misc/impl/ValidatorKeys.cpp rename to src/xrpld/app/misc/detail/ValidatorKeys.cpp index 8da1992a2ef..2b36848c882 100644 --- a/src/ripple/app/misc/impl/ValidatorKeys.cpp +++ b/src/xrpld/app/misc/detail/ValidatorKeys.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp similarity index 98% rename from src/ripple/app/misc/impl/ValidatorList.cpp rename to src/xrpld/app/misc/detail/ValidatorList.cpp index ff5fbd90eac..9a323e0116b 100644 --- a/src/ripple/app/misc/impl/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -17,24 +17,23 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include #include @@ -215,7 +214,8 @@ ValidatorList::load( return false; } - auto const id = parseBase58(TokenType::NodePublic, match[1]); + auto const id = + parseBase58(TokenType::NodePublic, match[1].str()); if (!id) { @@ -689,8 +689,7 @@ ValidatorList::sendValidatorList( beast::Journal j) { std::size_t const messageVersion = - peer.supportsFeature(ProtocolFeature::ValidatorList2Propagation) - ? 2 + peer.supportsFeature(ProtocolFeature::ValidatorList2Propagation) ? 2 : peer.supportsFeature(ProtocolFeature::ValidatorListPropagation) ? 1 : 0; if (!messageVersion) @@ -1707,7 +1706,7 @@ ValidatorList::for_each_available( std::optional ValidatorList::getAvailable( - boost::beast::string_view const& pubKey, + std::string_view pubKey, std::optional forceVersion /* = {} */) { std::shared_lock read_lock{mutex_}; diff --git a/src/ripple/app/misc/impl/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp similarity index 98% rename from src/ripple/app/misc/impl/ValidatorSite.cpp rename to src/xrpld/app/misc/detail/ValidatorSite.cpp index 013d7e96ffd..0c12816c1b8 100644 --- a/src/ripple/app/misc/impl/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/misc/detail/Work.h b/src/xrpld/app/misc/detail/Work.h similarity index 100% rename from src/ripple/app/misc/detail/Work.h rename to src/xrpld/app/misc/detail/Work.h diff --git a/src/ripple/app/misc/detail/WorkBase.h b/src/xrpld/app/misc/detail/WorkBase.h similarity index 98% rename from src/ripple/app/misc/detail/WorkBase.h rename to src/xrpld/app/misc/detail/WorkBase.h index 4b2c88f71a1..d7795af8bf0 100644 --- a/src/ripple/app/misc/detail/WorkBase.h +++ b/src/xrpld/app/misc/detail/WorkBase.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_MISC_DETAIL_WORKBASE_H_INCLUDED #define RIPPLE_APP_MISC_DETAIL_WORKBASE_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/detail/WorkFile.h b/src/xrpld/app/misc/detail/WorkFile.h similarity index 95% rename from src/ripple/app/misc/detail/WorkFile.h rename to src/xrpld/app/misc/detail/WorkFile.h index 2dc451feaca..266e5098cb6 100644 --- a/src/ripple/app/misc/detail/WorkFile.h +++ b/src/xrpld/app/misc/detail/WorkFile.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_MISC_DETAIL_WORKFILE_H_INCLUDED #define RIPPLE_APP_MISC_DETAIL_WORKFILE_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/detail/WorkPlain.h b/src/xrpld/app/misc/detail/WorkPlain.h similarity index 98% rename from src/ripple/app/misc/detail/WorkPlain.h rename to src/xrpld/app/misc/detail/WorkPlain.h index e70d327c223..16bf424131f 100644 --- a/src/ripple/app/misc/detail/WorkPlain.h +++ b/src/xrpld/app/misc/detail/WorkPlain.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_MISC_DETAIL_WORKPLAIN_H_INCLUDED #define RIPPLE_APP_MISC_DETAIL_WORKPLAIN_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/misc/detail/WorkSSL.h b/src/xrpld/app/misc/detail/WorkSSL.h similarity index 93% rename from src/ripple/app/misc/detail/WorkSSL.h rename to src/xrpld/app/misc/detail/WorkSSL.h index c7e3de614c2..d48f0b5fdff 100644 --- a/src/ripple/app/misc/detail/WorkSSL.h +++ b/src/xrpld/app/misc/detail/WorkSSL.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED #define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/misc/detail/impl/WorkSSL.cpp b/src/xrpld/app/misc/detail/detail/WorkSSL.cpp similarity index 98% rename from src/ripple/app/misc/detail/impl/WorkSSL.cpp rename to src/xrpld/app/misc/detail/detail/WorkSSL.cpp index 78a269e67cc..0285f435026 100644 --- a/src/ripple/app/misc/detail/impl/WorkSSL.cpp +++ b/src/xrpld/app/misc/detail/detail/WorkSSL.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace detail { diff --git a/src/ripple/app/paths/AMMContext.h b/src/xrpld/app/paths/AMMContext.h similarity index 98% rename from src/ripple/app/paths/AMMContext.h rename to src/xrpld/app/paths/AMMContext.h index 06835189bb7..f744b8a6c91 100644 --- a/src/ripple/app/paths/AMMContext.h +++ b/src/xrpld/app/paths/AMMContext.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_PATHS_AMMCONTEXT_H_INCLUDED #define RIPPLE_APP_PATHS_AMMCONTEXT_H_INCLUDED -#include +#include #include diff --git a/src/ripple/app/paths/AMMLiquidity.h b/src/xrpld/app/paths/AMMLiquidity.h similarity index 94% rename from src/ripple/app/paths/AMMLiquidity.h rename to src/xrpld/app/paths/AMMLiquidity.h index 60155dbf13f..fe60d39262f 100644 --- a/src/ripple/app/paths/AMMLiquidity.h +++ b/src/xrpld/app/paths/AMMLiquidity.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_TX_AMMLIQUIDITY_H_INCLUDED #define RIPPLE_APP_TX_AMMLIQUIDITY_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/AMMOffer.h b/src/xrpld/app/paths/AMMOffer.h similarity index 96% rename from src/ripple/app/paths/AMMOffer.h rename to src/xrpld/app/paths/AMMOffer.h index e3fb41e220b..e90a5b8611f 100644 --- a/src/ripple/app/paths/AMMOffer.h +++ b/src/xrpld/app/paths/AMMOffer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_AMMOFFER_H_INCLUDED #define RIPPLE_APP_AMMOFFER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -103,7 +103,6 @@ class AMMOffer limitOut( TAmounts const& offrAmt, TOut const& limit, - bool fixReducedOffers, bool roundUp) const; /** Limit in of the provided offer. If one-path then swapIn @@ -111,7 +110,8 @@ class AMMOffer * current quality. */ TAmounts - limitIn(TAmounts const& offrAmt, TIn const& limit) const; + limitIn(TAmounts const& offrAmt, TIn const& limit, bool roundUp) + const; QualityFunction getQualityFunc() const; diff --git a/src/ripple/app/paths/AccountCurrencies.cpp b/src/xrpld/app/paths/AccountCurrencies.cpp similarity index 98% rename from src/ripple/app/paths/AccountCurrencies.cpp rename to src/xrpld/app/paths/AccountCurrencies.cpp index 18452725b67..8646b46939a 100644 --- a/src/ripple/app/paths/AccountCurrencies.cpp +++ b/src/xrpld/app/paths/AccountCurrencies.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/paths/AccountCurrencies.h b/src/xrpld/app/paths/AccountCurrencies.h similarity index 94% rename from src/ripple/app/paths/AccountCurrencies.h rename to src/xrpld/app/paths/AccountCurrencies.h index fa70ec2a081..26282e742c3 100644 --- a/src/ripple/app/paths/AccountCurrencies.h +++ b/src/xrpld/app/paths/AccountCurrencies.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_PATHS_ACCOUNTCURRENCIES_H_INCLUDED #define RIPPLE_APP_PATHS_ACCOUNTCURRENCIES_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/Credit.cpp b/src/xrpld/app/paths/Credit.cpp similarity index 90% rename from src/ripple/app/paths/Credit.cpp rename to src/xrpld/app/paths/Credit.cpp index b9b19d29f90..b3870937367 100644 --- a/src/ripple/app/paths/Credit.cpp +++ b/src/xrpld/app/paths/Credit.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -31,7 +31,7 @@ creditLimit( AccountID const& issuer, Currency const& currency) { - STAmount result({currency, account}); + STAmount result(Issue{currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); @@ -64,7 +64,7 @@ creditBalance( AccountID const& issuer, Currency const& currency) { - STAmount result({currency, account}); + STAmount result(Issue{currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); diff --git a/src/ripple/app/paths/Credit.h b/src/xrpld/app/paths/Credit.h similarity index 95% rename from src/ripple/app/paths/Credit.h rename to src/xrpld/app/paths/Credit.h index dea9cbcde3e..1f9d5b04fe9 100644 --- a/src/ripple/app/paths/Credit.h +++ b/src/xrpld/app/paths/Credit.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_PATHS_CREDIT_H_INCLUDED #define RIPPLE_APP_PATHS_CREDIT_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/Flow.cpp b/src/xrpld/app/paths/Flow.cpp similarity index 94% rename from src/ripple/app/paths/Flow.cpp rename to src/xrpld/app/paths/Flow.cpp index 83379d34e79..c21d40c33b5 100644 --- a/src/ripple/app/paths/Flow.cpp +++ b/src/xrpld/app/paths/Flow.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/paths/Flow.h b/src/xrpld/app/paths/Flow.h similarity index 95% rename from src/ripple/app/paths/Flow.h rename to src/xrpld/app/paths/Flow.h index deafd1c7716..5390394b7f0 100644 --- a/src/ripple/app/paths/Flow.h +++ b/src/xrpld/app/paths/Flow.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_PATHS_FLOW_H_INCLUDED #define RIPPLE_APP_PATHS_FLOW_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/xrpld/app/paths/PathRequest.cpp similarity index 97% rename from src/ripple/app/paths/PathRequest.cpp rename to src/xrpld/app/paths/PathRequest.cpp index 948c6698ad1..bb6a104bca2 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/xrpld/app/paths/PathRequest.cpp @@ -17,22 +17,22 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include @@ -562,7 +562,7 @@ PathRequest::findPaths( }(); STAmount saMaxAmount = saSendMax.value_or( - STAmount({issue.currency, sourceAccount}, 1u, 0, true)); + STAmount(Issue{issue.currency, sourceAccount}, 1u, 0, true)); JLOG(m_journal.debug()) << iIdentifier << " Paths found, calling rippleCalc"; diff --git a/src/ripple/app/paths/PathRequest.h b/src/xrpld/app/paths/PathRequest.h similarity index 95% rename from src/ripple/app/paths/PathRequest.h rename to src/xrpld/app/paths/PathRequest.h index 70c286d6e1f..21f10d066ba 100644 --- a/src/ripple/app/paths/PathRequest.h +++ b/src/xrpld/app/paths/PathRequest.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_APP_PATHS_PATHREQUEST_H_INCLUDED #define RIPPLE_APP_PATHS_PATHREQUEST_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/paths/PathRequests.cpp b/src/xrpld/app/paths/PathRequests.cpp similarity index 96% rename from src/ripple/app/paths/PathRequests.cpp rename to src/xrpld/app/paths/PathRequests.cpp index 700cf137209..86560445ec7 100644 --- a/src/ripple/app/paths/PathRequests.cpp +++ b/src/xrpld/app/paths/PathRequests.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/paths/PathRequests.h b/src/xrpld/app/paths/PathRequests.h similarity index 95% rename from src/ripple/app/paths/PathRequests.h rename to src/xrpld/app/paths/PathRequests.h index db683ee4c13..670790518a1 100644 --- a/src/ripple/app/paths/PathRequests.h +++ b/src/xrpld/app/paths/PathRequests.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_PATHS_PATHREQUESTS_H_INCLUDED #define RIPPLE_APP_PATHS_PATHREQUESTS_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/xrpld/app/paths/Pathfinder.cpp similarity index 98% rename from src/ripple/app/paths/Pathfinder.cpp rename to src/xrpld/app/paths/Pathfinder.cpp index 556622ee7bf..5122bc7d6b8 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/xrpld/app/paths/Pathfinder.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -176,9 +176,10 @@ Pathfinder::Pathfinder( , mSrcCurrency(uSrcCurrency) , mSrcIssuer(uSrcIssuer) , mSrcAmount(srcAmount.value_or(STAmount( - {uSrcCurrency, - uSrcIssuer.value_or( - isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, + Issue{ + uSrcCurrency, + uSrcIssuer.value_or( + isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, 1u, 0, true))) @@ -233,8 +234,7 @@ Pathfinder::findPaths( mSource = STPathElement(account, mSrcCurrency, issuer); auto issuerString = mSrcIssuer ? to_string(*mSrcIssuer) : std::string("none"); - JLOG(j_.trace()) << "findPaths>" - << " mSrcAccount=" << mSrcAccount + JLOG(j_.trace()) << "findPaths>" << " mSrcAccount=" << mSrcAccount << " mDstAccount=" << mDstAccount << " mDstAmount=" << mDstAmount.getFullText() << " mSrcCurrency=" << mSrcCurrency diff --git a/src/ripple/app/paths/Pathfinder.h b/src/xrpld/app/paths/Pathfinder.h similarity index 96% rename from src/ripple/app/paths/Pathfinder.h rename to src/xrpld/app/paths/Pathfinder.h index 375e5e24677..01556a3c63f 100644 --- a/src/ripple/app/paths/Pathfinder.h +++ b/src/xrpld/app/paths/Pathfinder.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_APP_PATHS_PATHFINDER_H_INCLUDED #define RIPPLE_APP_PATHS_PATHFINDER_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/RippleCalc.cpp b/src/xrpld/app/paths/RippleCalc.cpp similarity index 95% rename from src/ripple/app/paths/RippleCalc.cpp rename to src/xrpld/app/paths/RippleCalc.cpp index 87ef694fa58..c7b2e1f01e0 100644 --- a/src/ripple/app/paths/RippleCalc.cpp +++ b/src/xrpld/app/paths/RippleCalc.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace path { diff --git a/src/ripple/app/paths/RippleCalc.h b/src/xrpld/app/paths/RippleCalc.h similarity index 96% rename from src/ripple/app/paths/RippleCalc.h rename to src/xrpld/app/paths/RippleCalc.h index fd9ff598114..9e03da9c906 100644 --- a/src/ripple/app/paths/RippleCalc.h +++ b/src/xrpld/app/paths/RippleCalc.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED #define RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/paths/RippleLineCache.cpp b/src/xrpld/app/paths/RippleLineCache.cpp similarity index 97% rename from src/ripple/app/paths/RippleLineCache.cpp rename to src/xrpld/app/paths/RippleLineCache.cpp index 2487924ff0e..0ff967c0821 100644 --- a/src/ripple/app/paths/RippleLineCache.cpp +++ b/src/xrpld/app/paths/RippleLineCache.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/RippleLineCache.h b/src/xrpld/app/paths/RippleLineCache.h similarity index 96% rename from src/ripple/app/paths/RippleLineCache.h rename to src/xrpld/app/paths/RippleLineCache.h index 590c50082f7..cde1d589f92 100644 --- a/src/ripple/app/paths/RippleLineCache.h +++ b/src/xrpld/app/paths/RippleLineCache.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_PATHS_RIPPLELINECACHE_H_INCLUDED #define RIPPLE_APP_PATHS_RIPPLELINECACHE_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/paths/TrustLine.cpp b/src/xrpld/app/paths/TrustLine.cpp similarity index 96% rename from src/ripple/app/paths/TrustLine.cpp rename to src/xrpld/app/paths/TrustLine.cpp index 14a5d6f8823..6390c8d2117 100644 --- a/src/ripple/app/paths/TrustLine.cpp +++ b/src/xrpld/app/paths/TrustLine.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/app/paths/TrustLine.h b/src/xrpld/app/paths/TrustLine.h similarity index 97% rename from src/ripple/app/paths/TrustLine.h rename to src/xrpld/app/paths/TrustLine.h index 6b27dca3669..381ef471875 100644 --- a/src/ripple/app/paths/TrustLine.h +++ b/src/xrpld/app/paths/TrustLine.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_PATHS_RIPPLESTATE_H_INCLUDED #define RIPPLE_APP_PATHS_RIPPLESTATE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/paths/impl/AMMLiquidity.cpp b/src/xrpld/app/paths/detail/AMMLiquidity.cpp similarity index 95% rename from src/ripple/app/paths/impl/AMMLiquidity.cpp rename to src/xrpld/app/paths/detail/AMMLiquidity.cpp index 9ec23d08a1a..7b1649c649e 100644 --- a/src/ripple/app/paths/impl/AMMLiquidity.cpp +++ b/src/xrpld/app/paths/detail/AMMLiquidity.cpp @@ -16,9 +16,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include +#include -#include +#include namespace ripple { @@ -211,6 +211,13 @@ AMMLiquidity::getOffer( return AMMOffer( *this, *amounts, balances, Quality{*amounts}); } + else if (view.rules().enabled(fixAMMv1_2)) + { + if (auto const maxAMMOffer = maxOffer(balances, view.rules()); + maxAMMOffer && + Quality{maxAMMOffer->amount()} > *clobQuality) + return maxAMMOffer; + } } catch (std::overflow_error const& e) { diff --git a/src/ripple/app/paths/impl/AMMOffer.cpp b/src/xrpld/app/paths/detail/AMMOffer.cpp similarity index 92% rename from src/ripple/app/paths/impl/AMMOffer.cpp rename to src/xrpld/app/paths/detail/AMMOffer.cpp index 697bac9c790..16ea8628f3b 100644 --- a/src/ripple/app/paths/impl/AMMOffer.cpp +++ b/src/xrpld/app/paths/detail/AMMOffer.cpp @@ -16,10 +16,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //==============================================================================/ -#include +#include -#include -#include +#include +#include namespace ripple { @@ -81,7 +81,6 @@ TAmounts AMMOffer::limitOut( TAmounts const& offrAmt, TOut const& limit, - bool fixReducedOffers, bool roundUp) const { // Change the offer size proportionally to the original offer quality @@ -92,7 +91,8 @@ AMMOffer::limitOut( // poolPays * poolGets < (poolPays - assetOut) * (poolGets + assetIn) if (ammLiquidity_.multiPath()) { - if (fixReducedOffers) + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(fixReducedOffersV1)) // It turns out that the ceil_out implementation has some slop in // it. ceil_out_strict removes that slop. But removing that slop // affects transaction outcomes, so the change must be made using @@ -110,11 +110,18 @@ template TAmounts AMMOffer::limitIn( TAmounts const& offrAmt, - TIn const& limit) const + TIn const& limit, + bool roundUp) const { // See the comments above in limitOut(). if (ammLiquidity_.multiPath()) + { + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(fixReducedOffersV2)) + return quality().ceil_in_strict(offrAmt, limit, roundUp); + return quality().ceil_in(offrAmt, limit); + } return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())}; } diff --git a/src/ripple/app/paths/impl/AmountSpec.h b/src/xrpld/app/paths/detail/AmountSpec.h similarity index 97% rename from src/ripple/app/paths/impl/AmountSpec.h rename to src/xrpld/app/paths/detail/AmountSpec.h index ca814c7b3ac..8a1117f9920 100644 --- a/src/ripple/app/paths/impl/AmountSpec.h +++ b/src/xrpld/app/paths/detail/AmountSpec.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED #define RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp similarity index 97% rename from src/ripple/app/paths/impl/BookStep.cpp rename to src/xrpld/app/paths/detail/BookStep.cpp index 4a43d653e0c..b22102119df 100644 --- a/src/ripple/app/paths/impl/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -17,21 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -189,8 +188,7 @@ class BookStep : public StepImp> logStringImpl(char const* name) const { std::ostringstream ostr; - ostr << name << ": " - << "\ninIss: " << book_.in.account + ostr << name << ": " << "\ninIss: " << book_.in.account << "\noutIss: " << book_.out.account << "\ninCur: " << book_.in.currency << "\noutCur: " << book_.out.currency; @@ -668,7 +666,14 @@ limitStepIn( stpAmt.in = limit; auto const inLmt = mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false); - ofrAmt = offer.limitIn(ofrAmt, inLmt); + // It turns out we can prevent order book blocking by (strictly) + // rounding down the ceil_in() result. By rounding down we guarantee + // that the quality of an offer left in the ledger is as good or + // better than the quality of the containing order book page. + // + // This adjustment changes transaction outcomes, so it must be made + // under an amendment. + ofrAmt = offer.limitIn(ofrAmt, inLmt, /* roundUp */ false); stpAmt.out = ofrAmt.out; ownerGives = mulRatio( ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false); @@ -685,8 +690,7 @@ limitStepOut( TOut& ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, - TOut const& limit, - Rules const& rules) + TOut const& limit) { if (limit < stpAmt.out) { @@ -696,7 +700,6 @@ limitStepOut( ofrAmt = offer.limitOut( ofrAmt, stpAmt.out, - rules.enabled(fixReducedOffersV1), /*roundUp*/ true); stpAmt.in = mulRatio(ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true); @@ -736,7 +739,6 @@ BookStep::forEachOffer( sb, afView, book_, sb.parentCloseTime(), counter, j_); bool const flowCross = afView.rules().enabled(featureFlowCross); - bool const fixReduced = afView.rules().enabled(fixReducedOffersV1); bool offerAttempted = false; std::optional ofrQ; auto execOffer = [&](auto& offer) { @@ -817,8 +819,7 @@ BookStep::forEachOffer( // It turns out we can prevent order book blocking by (strictly) // rounding down the ceil_out() result. This adjustment changes // transaction outcomes, so it must be made under an amendment. - ofrAmt = offer.limitOut( - ofrAmt, stpAmt.out, fixReduced, /*roundUp*/ false); + ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false); stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true); @@ -1055,8 +1056,7 @@ BookStep::revImp( ownerGivesAdj, transferRateIn, transferRateOut, - remainingOut, - afView.rules()); + remainingOut); remainingOut = beast::zero; savedIns.insert(stpAdjAmt.in); savedOuts.insert(remainingOut); @@ -1208,8 +1208,7 @@ BookStep::fwdImp( ownerGivesAdjRev, transferRateIn, transferRateOut, - remainingOut, - afView.rules()); + remainingOut); if (stpAdjAmtRev.in == remainingIn) { @@ -1228,7 +1227,7 @@ BookStep::fwdImp( } else { - // This is (likely) a problem case, and wil be caught + // This is (likely) a problem case, and will be caught // with later checks savedOuts.insert(lastOutAmt); } diff --git a/src/ripple/app/paths/impl/DirectStep.cpp b/src/xrpld/app/paths/detail/DirectStep.cpp similarity index 98% rename from src/ripple/app/paths/impl/DirectStep.cpp rename to src/xrpld/app/paths/detail/DirectStep.cpp index 00647121b74..7df06751140 100644 --- a/src/ripple/app/paths/impl/DirectStep.cpp +++ b/src/xrpld/app/paths/detail/DirectStep.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -204,8 +204,7 @@ class DirectStepI : public StepImp> logStringImpl(char const* name) const { std::ostringstream ostr; - ostr << name << ": " - << "\nSrc: " << src_ << "\nDst: " << dst_; + ostr << name << ": " << "\nSrc: " << src_ << "\nDst: " << dst_; return ostr.str(); } @@ -236,7 +235,8 @@ class DirectIPaymentStep : public DirectStepI using DirectStepI::DirectStepI; using DirectStepI::check; - bool verifyPrevStepDebtDirection(DebtDirection) const + bool + verifyPrevStepDebtDirection(DebtDirection) const { // A payment doesn't care whether or not prevStepRedeems. return true; diff --git a/src/ripple/app/paths/impl/FlatSets.h b/src/xrpld/app/paths/detail/FlatSets.h similarity index 100% rename from src/ripple/app/paths/impl/FlatSets.h rename to src/xrpld/app/paths/detail/FlatSets.h diff --git a/src/ripple/app/paths/impl/FlowDebugInfo.h b/src/xrpld/app/paths/detail/FlowDebugInfo.h similarity index 98% rename from src/ripple/app/paths/impl/FlowDebugInfo.h rename to src/xrpld/app/paths/detail/FlowDebugInfo.h index 2d1728b5f88..000db4e5714 100644 --- a/src/ripple/app/paths/impl/FlowDebugInfo.h +++ b/src/xrpld/app/paths/detail/FlowDebugInfo.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PATH_IMPL_FLOWDEBUGINFO_H_INCLUDED #define RIPPLE_PATH_IMPL_FLOWDEBUGINFO_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/paths/impl/PathfinderUtils.h b/src/xrpld/app/paths/detail/PathfinderUtils.h similarity index 97% rename from src/ripple/app/paths/impl/PathfinderUtils.h rename to src/xrpld/app/paths/detail/PathfinderUtils.h index 066d905a1cd..b06dded75bd 100644 --- a/src/ripple/app/paths/impl/PathfinderUtils.h +++ b/src/xrpld/app/paths/detail/PathfinderUtils.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PATH_IMPL_PATHFINDERUTILS_H_INCLUDED #define RIPPLE_PATH_IMPL_PATHFINDERUTILS_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/paths/impl/PaySteps.cpp b/src/xrpld/app/paths/detail/PaySteps.cpp similarity index 98% rename from src/ripple/app/paths/impl/PaySteps.cpp rename to src/xrpld/app/paths/detail/PaySteps.cpp index b96d6ee57b2..f28c1b96a7c 100644 --- a/src/ripple/app/paths/impl/PaySteps.cpp +++ b/src/xrpld/app/paths/detail/PaySteps.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/paths/impl/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h similarity index 93% rename from src/ripple/app/paths/impl/StepChecks.h rename to src/xrpld/app/paths/detail/StepChecks.h index 9d8664a8dc1..140c9d1fe46 100644 --- a/src/ripple/app/paths/impl/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED #define RIPPLE_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/paths/impl/Steps.h b/src/xrpld/app/paths/detail/Steps.h similarity index 97% rename from src/ripple/app/paths/impl/Steps.h rename to src/xrpld/app/paths/detail/Steps.h index 033b9696f1d..dee90f617a5 100644 --- a/src/ripple/app/paths/impl/Steps.h +++ b/src/xrpld/app/paths/detail/Steps.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_APP_PATHS_IMPL_PAYSTEPS_H_INCLUDED #define RIPPLE_APP_PATHS_IMPL_PAYSTEPS_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -89,7 +89,7 @@ class Step subject to liquidity limits @param sb view with the strand's state of balances and offers - @param afView view the the state of balances before the strand runs + @param afView view the state of balances before the strand runs this determines if an offer becomes unfunded or is found unfunded @param ofrsToRm offers found unfunded or in an error state are added to this collection @@ -107,7 +107,7 @@ class Step subject to liquidity limits @param sb view with the strand's state of balances and offers - @param afView view the the state of balances before the strand runs + @param afView view the state of balances before the strand runs this determines if an offer becomes unfunded or is found unfunded @param ofrsToRm offers found unfunded or in an error state are added to this collection @@ -256,7 +256,7 @@ class Step Check that the step can correctly execute in the forward direction @param sb view with the strands state of balances and offers - @param afView view the the state of balances before the strand runs + @param afView view the state of balances before the strand runs this determines if an offer becomes unfunded or is found unfunded @param in requested step input @return first element is true if step is valid, second element is out diff --git a/src/ripple/app/paths/impl/StrandFlow.h b/src/xrpld/app/paths/detail/StrandFlow.h similarity index 98% rename from src/ripple/app/paths/impl/StrandFlow.h rename to src/xrpld/app/paths/detail/StrandFlow.h index 2df496d8311..329a4cc643f 100644 --- a/src/ripple/app/paths/impl/StrandFlow.h +++ b/src/xrpld/app/paths/detail/StrandFlow.h @@ -20,18 +20,18 @@ #ifndef RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED #define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/paths/impl/XRPEndpointStep.cpp b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp similarity index 96% rename from src/ripple/app/paths/impl/XRPEndpointStep.cpp rename to src/xrpld/app/paths/detail/XRPEndpointStep.cpp index 4878463801c..ac178cbe2f2 100644 --- a/src/ripple/app/paths/impl/XRPEndpointStep.cpp +++ b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -132,8 +132,7 @@ class XRPEndpointStep logStringImpl(char const* name) const { std::ostringstream ostr; - ostr << name << ": " - << "\nAcc: " << acc_; + ostr << name << ": " << "\nAcc: " << acc_; return ostr.str(); } diff --git a/src/ripple/app/rdb/PeerFinder.h b/src/xrpld/app/rdb/PeerFinder.h similarity index 95% rename from src/ripple/app/rdb/PeerFinder.h rename to src/xrpld/app/rdb/PeerFinder.h index 06cd80f670b..8becd053fbe 100644 --- a/src/ripple/app/rdb/PeerFinder.h +++ b/src/xrpld/app/rdb/PeerFinder.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_RDB_PEERFINDER_H_INCLUDED #define RIPPLE_APP_RDB_PEERFINDER_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/README.md b/src/xrpld/app/rdb/README.md similarity index 62% rename from src/ripple/app/rdb/README.md rename to src/xrpld/app/rdb/README.md index 1a68a1ae5e3..81aaa32f2cf 100644 --- a/src/ripple/app/rdb/README.md +++ b/src/xrpld/app/rdb/README.md @@ -2,9 +2,8 @@ The guiding principles of the Relational Database Interface are summarized below: -* All hard-coded SQL statements should be stored in the [files](#source-files) under the `ripple/app/rdb` directory. With the exception of test modules, no hard-coded SQL should be added to any other file in rippled. +* All hard-coded SQL statements should be stored in the [files](#source-files) under the `xrpld/app/rdb` directory. With the exception of test modules, no hard-coded SQL should be added to any other file in rippled. * The base class `RelationalDatabase` is inherited by derived classes that each provide an interface for operating on distinct relational database systems. -* For future use, the shard store will be used if the node store is absent. ## Overview @@ -12,7 +11,7 @@ Firstly, the interface `RelationalDatabase` is inherited by the classes `SQLiteD ## Configuration -The config section `[relational_db]` has a property named `backend` whose value designates which database implementation will be used for node or shard databases. Presently the only valid value for this property is `sqlite`: +The config section `[relational_db]` has a property named `backend` whose value designates which database implementation will be used for node databases. Presently the only valid value for this property is `sqlite`: ``` [relational_db] @@ -24,35 +23,23 @@ backend=sqlite The Relational Database Interface consists of the following directory structure (as of November 2021): ``` -src/ripple/app/rdb/ +src/xrpld/app/rdb/ ├── backend │   ├── detail -│   │   ├── impl -│   │   │   ├── Node.cpp -│   │   │   └── Shard.cpp +│   │   ├── Node.cpp │   │   ├── Node.h -│   │   └── Shard.h -│   ├── impl -│   │   ├── PostgresDatabase.cpp │   │   └── SQLiteDatabase.cpp -│   ├── PostgresDatabase.h │   └── SQLiteDatabase.h -├── impl -│   ├── Download.cpp +├── detail │   ├── PeerFinder.cpp │   ├── RelationalDatabase.cpp -│   ├── ShardArchive.cpp │   ├── State.cpp -│   ├── UnitaryShard.cpp │   ├── Vacuum.cpp │   └── Wallet.cpp -├── Download.h ├── PeerFinder.h ├── RelationalDatabase.h ├── README.md -├── ShardArchive.h ├── State.h -├── UnitaryShard.h ├── Vacuum.h └── Wallet.h ``` @@ -61,16 +48,11 @@ src/ripple/app/rdb/ | File | Contents | | ----------- | ----------- | | `Node.[h\|cpp]` | Defines/Implements methods used by `SQLiteDatabase` for interacting with SQLite node databases| -| `Shard.[h\|cpp]` | Defines/Implements methods used by `SQLiteDatabase` for interacting with SQLite shard databases | -| `PostgresDatabase.[h\|cpp]` | Defines/Implements the class `PostgresDatabase`/`PostgresDatabaseImp` which inherits from `RelationalDatabase` and is used to operate on the main stores | |`SQLiteDatabase.[h\|cpp]`| Defines/Implements the class `SQLiteDatabase`/`SQLiteDatabaseImp` which inherits from `RelationalDatabase` and is used to operate on the main stores | -| `Download.[h\|cpp]` | Defines/Implements methods for persisting file downloads to a SQLite database | | `PeerFinder.[h\|cpp]` | Defines/Implements methods for interacting with the PeerFinder SQLite database | |`RelationalDatabase.cpp`| Implements the static method `RelationalDatabase::init` which is used to initialize an instance of `RelationalDatabase` | | `RelationalDatabase.h` | Defines the abstract class `RelationalDatabase`, the primary class of the Relational Database Interface | -| `ShardArchive.[h\|cpp]` | Defines/Implements methods used by `ShardArchiveHandler` for interacting with SQLite databases containing metadata regarding shard downloads | | `State.[h\|cpp]` | Defines/Implements methods for interacting with the State SQLite database which concerns ledger deletion and database rotation | -| `UnitaryShard.[h\|cpp]` | Defines/Implements methods used by a unitary instance of `Shard` for interacting with the various SQLite databases thereof. These files are distinct from `Shard.[h\|cpp]` which contain methods used by `SQLiteDatabaseImp` | | `Vacuum.[h\|cpp]` | Defines/Implements a method for performing the `VACUUM` operation on SQLite databases | | `Wallet.[h\|cpp]` | Defines/Implements methods for interacting with Wallet SQLite databases | @@ -84,19 +66,15 @@ The Relational Database Interface provides three categories of methods for inter * Free functions for interacting with SQLite databases used by various components of the software. These methods feature a `soci::session` parameter which facilitates connecting to SQLite databases, and are defined and implemented in the following files: - * `Download.[h\|cpp]` * `PeerFinder.[h\|cpp]` - * `ShardArchive.[h\|cpp]` * `State.[h\|cpp]` - * `UnitaryShard.[h\|cpp]` * `Vacuum.[h\|cpp]` * `Wallet.[h\|cpp]` -* Free functions used exclusively by `SQLiteDatabaseImp` for interacting with SQLite databases owned by the node store or shard store. Unlike the free functions in the files listed above, these are not intended to be invoked directly by clients. Rather, these methods are invoked by derived instances of `RelationalDatabase`. These methods are defined in the following files: +* Free functions used exclusively by `SQLiteDatabaseImp` for interacting with SQLite databases owned by the node store. Unlike the free functions in the files listed above, these are not intended to be invoked directly by clients. Rather, these methods are invoked by derived instances of `RelationalDatabase`. These methods are defined in the following files: * `Node.[h|cpp]` - * `Shard.[h|cpp]` -* Member functions of `RelationalDatabase`, `SQLiteDatabase`, and `PostgresDatabase` which are used to access the main stores (node store, shard store). The `SQLiteDatabase` class will access the node store by default, but will use shard databases if the node store is not present and the shard store is available. The class `PostgresDatabase` uses only the node store. +* Member functions of `RelationalDatabase`, `SQLiteDatabase`, and `PostgresDatabase` which are used to access the node store. diff --git a/src/ripple/app/rdb/RelationalDatabase.h b/src/xrpld/app/rdb/RelationalDatabase.h similarity index 86% rename from src/ripple/app/rdb/RelationalDatabase.h rename to src/xrpld/app/rdb/RelationalDatabase.h index a269bf256c8..5b06aa24d0e 100644 --- a/src/ripple/app/rdb/RelationalDatabase.h +++ b/src/xrpld/app/rdb/RelationalDatabase.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_RDB_RELATIONALDATABASE_H_INCLUDED #define RIPPLE_APP_RDB_RELATIONALDATABASE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -111,29 +111,6 @@ class RelationalDatabase std::optional marker; }; - /// Struct used to keep track of what to write to transactions and - /// account_transactions tables in Postgres - struct AccountTransactionsData - { - boost::container::flat_set accounts; - uint32_t ledgerSequence; - uint32_t transactionIndex; - uint256 txHash; - uint256 nodestoreHash; - - AccountTransactionsData( - TxMeta const& meta, - uint256 const& nodestoreHash, - beast::Journal j) - : accounts(meta.getAffectedAccounts()) - , ledgerSequence(meta.getLgrSeq()) - , transactionIndex(meta.getIndex()) - , txHash(meta.getTxID()) - , nodestoreHash(nodestoreHash) - { - } - }; - /** * @brief init Creates and returns an appropriate RelationalDatabase * instance based on configuration. @@ -260,8 +237,8 @@ rangeCheckedCast(C c) /* This should never happen */ assert(0); JLOG(debugLog().error()) - << "rangeCheckedCast domain error:" - << " value = " << c << " min = " << std::numeric_limits::lowest() + << "rangeCheckedCast domain error:" << " value = " << c + << " min = " << std::numeric_limits::lowest() << " max: " << std::numeric_limits::max(); } diff --git a/src/ripple/app/rdb/State.h b/src/xrpld/app/rdb/State.h similarity index 92% rename from src/ripple/app/rdb/State.h rename to src/xrpld/app/rdb/State.h index fe74d5f19d3..e65e9d4d57a 100644 --- a/src/ripple/app/rdb/State.h +++ b/src/xrpld/app/rdb/State.h @@ -20,12 +20,11 @@ #ifndef RIPPLE_APP_RDB_STATE_H_INCLUDED #define RIPPLE_APP_RDB_STATE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/rdb/Vacuum.h b/src/xrpld/app/rdb/Vacuum.h similarity index 97% rename from src/ripple/app/rdb/Vacuum.h rename to src/xrpld/app/rdb/Vacuum.h index 463f046e0a5..795c564db23 100644 --- a/src/ripple/app/rdb/Vacuum.h +++ b/src/xrpld/app/rdb/Vacuum.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_RDB_VACUUM_H_INCLUDED #define RIPPLE_APP_RDB_VACUUM_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/Wallet.h b/src/xrpld/app/rdb/Wallet.h similarity index 96% rename from src/ripple/app/rdb/Wallet.h rename to src/xrpld/app/rdb/Wallet.h index 6a15997ffe4..6130c9fc263 100644 --- a/src/ripple/app/rdb/Wallet.h +++ b/src/xrpld/app/rdb/Wallet.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_APP_RDB_WALLET_H_INCLUDED #define RIPPLE_APP_RDB_WALLET_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/backend/SQLiteDatabase.h b/src/xrpld/app/rdb/backend/SQLiteDatabase.h similarity index 99% rename from src/ripple/app/rdb/backend/SQLiteDatabase.h rename to src/xrpld/app/rdb/backend/SQLiteDatabase.h index 07f9be3e228..27c01c1b83d 100644 --- a/src/ripple/app/rdb/backend/SQLiteDatabase.h +++ b/src/xrpld/app/rdb/backend/SQLiteDatabase.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_APP_RDB_BACKEND_SQLITEDATABASE_H_INCLUDED #define RIPPLE_APP_RDB_BACKEND_SQLITEDATABASE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/backend/detail/impl/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp similarity index 92% rename from src/ripple/app/rdb/backend/detail/impl/Node.cpp rename to src/xrpld/app/rdb/backend/detail/Node.cpp index 894cae1385c..2ea6bd12c62 100644 --- a/src/ripple/app/rdb/backend/detail/impl/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -72,7 +72,7 @@ makeLedgerDBs( { // ledger database auto lgr{std::make_unique( - setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup, j)}; + setup, LgrDBName, setup.lgrPragma, LgrDBInit, checkpointerSetup, j)}; lgr->getSession() << boost::str( boost::format("PRAGMA cache_size=-%d;") % kilobytes(config.getValueFor(SizedItem::lgrDBCache))); @@ -81,7 +81,7 @@ makeLedgerDBs( { // transaction database auto tx{std::make_unique( - setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup, j)}; + setup, TxDBName, setup.txPragma, TxDBInit, checkpointerSetup, j)}; tx->getSession() << boost::str( boost::format("PRAGMA cache_size=-%d;") % kilobytes(config.getValueFor(SizedItem::txnDBCache))); @@ -623,8 +623,7 @@ getTxHistory( soci::session& session, Application& app, LedgerIndex startIndex, - int quantity, - bool count) + int quantity) { std::string sql = boost::str( boost::format( @@ -664,13 +663,6 @@ getTxHistory( txs.push_back(trans); } } - - if (!total && count) - { - session << "SELECT COUNT(*) FROM Transactions;", soci::into(total); - - total = -total; - } } return {txs, total}; @@ -686,9 +678,6 @@ getTxHistory( * the account, the ledger search range, the offset of the first entry to * return, the number of transactions to return, and a flag if this * number is unlimited. - * @param limit_used Number of transactions already returned in calls - * to other shard databases, if shard databases are used. - * No value if the node database is used. * @param descending True for descending order, false for ascending. * @param binary True for binary form, false for decoded. * @param count True for counting the number of transactions, false for @@ -701,7 +690,6 @@ transactionsSQL( Application& app, std::string selection, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, bool descending, bool binary, bool count, @@ -730,14 +718,6 @@ transactionsSQL( numberOfResults = options.limit; } - if (limit_used) - { - if (numberOfResults <= *limit_used) - return ""; - else - numberOfResults -= *limit_used; - } - std::string maxClause = ""; std::string minClause = ""; @@ -791,9 +771,6 @@ transactionsSQL( * the account, the ledger search range, the offset of the first entry to * return, the number of transactions to return, and a flag if this * number is unlimited. - * @param limit_used Number of transactions already returned in calls - * to other shard databases, if shard databases are used. - * No value if the node database is used. * @param descending True for descending order, false for ascending. * @param j Journal. * @return Vector of pairs of found transactions and their metadata sorted by @@ -810,7 +787,6 @@ getAccountTxs( Application& app, LedgerMaster& ledgerMaster, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, bool descending, beast::Journal j) { @@ -820,7 +796,6 @@ getAccountTxs( app, "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta", options, - limit_used, descending, false, false, @@ -881,18 +856,6 @@ getAccountTxs( total++; } } - - if (!total && limit_used) - { - RelationalDatabase::AccountTxOptions opt = options; - opt.offset = 0; - std::string sql1 = transactionsSQL( - app, "COUNT(*)", opt, limit_used, descending, false, false, j); - - session << sql1, soci::into(total); - - total = -total; - } } return {ret, total}; @@ -904,11 +867,9 @@ getOldestAccountTxs( Application& app, LedgerMaster& ledgerMaster, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j) { - return getAccountTxs( - session, app, ledgerMaster, options, limit_used, false, j); + return getAccountTxs(session, app, ledgerMaster, options, false, j); } std::pair @@ -917,11 +878,9 @@ getNewestAccountTxs( Application& app, LedgerMaster& ledgerMaster, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j) { - return getAccountTxs( - session, app, ledgerMaster, options, limit_used, true, j); + return getAccountTxs(session, app, ledgerMaster, options, true, j); } /** @@ -934,9 +893,6 @@ getNewestAccountTxs( * the account, the ledger search range, the offset of the first entry to * return, the number of transactions to return, and a flag if this * number is unlimited. - * @param limit_used Number of transactions already returned in calls to other - * shard databases, if shard databases are used. No value if the node - * database is used. * @param descending True for descending order, false for ascending. * @param j Journal. * @return Vector of tuples each containing (the found transactions, their @@ -952,7 +908,6 @@ getAccountTxsB( soci::session& session, Application& app, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, bool descending, beast::Journal j) { @@ -962,7 +917,6 @@ getAccountTxsB( app, "AccountTransactions.LedgerSeq,Status,RawTxn,TxnMeta", options, - limit_used, descending, true /*binary*/, false, @@ -1002,18 +956,6 @@ getAccountTxsB( ret.emplace_back(std::move(rawTxn), std::move(txnMeta), seq); total++; } - - if (!total && limit_used) - { - RelationalDatabase::AccountTxOptions opt = options; - opt.offset = 0; - std::string sql1 = transactionsSQL( - app, "COUNT(*)", opt, limit_used, descending, true, false, j); - - session << sql1, soci::into(total); - - total = -total; - } } return {ret, total}; @@ -1024,10 +966,9 @@ getOldestAccountTxsB( soci::session& session, Application& app, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j) { - return getAccountTxsB(session, app, options, limit_used, false, j); + return getAccountTxsB(session, app, options, false, j); } std::pair, int> @@ -1035,10 +976,9 @@ getNewestAccountTxsB( soci::session& session, Application& app, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j) { - return getAccountTxsB(session, app, options, limit_used, true, j); + return getAccountTxsB(session, app, options, true, j); } /** @@ -1053,8 +993,6 @@ getNewestAccountTxsB( * match: the account, the ledger search range, the marker of the first * returned entry, the number of transactions to return, and a flag if * this number unlimited. - * @param limit_used Number of transactions already returned in calls - * to other shard databases. * @param page_length Total number of transactions to return. * @param forward True for ascending order, false for descending. * @return Vector of tuples of found transactions, their metadata and account @@ -1070,7 +1008,6 @@ accountTxPage( void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction, RelationalDatabase::AccountTxPageOptions const& options, - int limit_used, std::uint32_t page_length, bool forward) { @@ -1086,10 +1023,6 @@ accountTxPage( else numberOfResults = options.limit; - if (numberOfResults < limit_used) - return {options.marker, -1}; - numberOfResults -= limit_used; - // As an account can have many thousands of transactions, there is a limit // placed on the amount of transactions returned. If the limit is reached // before the result set has been exhausted (we always query for one more @@ -1105,8 +1038,6 @@ accountTxPage( } std::optional newmarker; - if (limit_used > 0) - newmarker = options.marker; static std::string const prefix( R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, @@ -1252,17 +1183,10 @@ oldestAccountTxPage( void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction, RelationalDatabase::AccountTxPageOptions const& options, - int limit_used, std::uint32_t page_length) { return accountTxPage( - session, - onUnsavedLedger, - onTransaction, - options, - limit_used, - page_length, - true); + session, onUnsavedLedger, onTransaction, options, page_length, true); } std::pair, int> @@ -1273,17 +1197,10 @@ newestAccountTxPage( void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction, RelationalDatabase::AccountTxPageOptions const& options, - int limit_used, std::uint32_t page_length) { return accountTxPage( - session, - onUnsavedLedger, - onTransaction, - options, - limit_used, - page_length, - false); + session, onUnsavedLedger, onTransaction, options, page_length, false); } std::variant diff --git a/src/ripple/app/rdb/backend/detail/Node.h b/src/xrpld/app/rdb/backend/detail/Node.h similarity index 92% rename from src/ripple/app/rdb/backend/detail/Node.h rename to src/xrpld/app/rdb/backend/detail/Node.h index 2c3f264d7a9..59c484d2063 100644 --- a/src/ripple/app/rdb/backend/detail/Node.h +++ b/src/xrpld/app/rdb/backend/detail/Node.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_APP_RDB_BACKEND_DETAIL_NODE_H_INCLUDED #define RIPPLE_APP_RDB_BACKEND_DETAIL_NODE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -251,7 +251,6 @@ getHashesByIndex( * @param app Application object. * @param startIndex Offset of first returned entry. * @param quantity Number of returned entries. - * @param count True if counting of all transaction in that shard required. * @return Vector of shared pointers to transactions sorted in * descending order by ledger sequence. Also number of transactions * if count == true. @@ -261,8 +260,7 @@ getTxHistory( soci::session& session, Application& app, LedgerIndex startIndex, - int quantity, - bool count); + int quantity); /** * @brief getOldestAccountTxs Returns oldest transactions for given @@ -274,9 +272,6 @@ getTxHistory( * the account, minimum and maximum ledger numbers to search, * offset of first entry to return, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases, if shard databases are used. - * None if node database is used. * @param j Journal. * @return Vector of pairs of found transactions and their metadata * sorted in ascending order by account sequence. @@ -292,7 +287,6 @@ getOldestAccountTxs( Application& app, LedgerMaster& ledgerMaster, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j); /** @@ -305,9 +299,6 @@ getOldestAccountTxs( * the account, minimum and maximum ledger numbers to search, * offset of first entry to return, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases, if shard databases are used. - * None if node database is used. * @param j Journal. * @return Vector of pairs of found transactions and their metadata * sorted in descending order by account sequence. @@ -323,7 +314,6 @@ getNewestAccountTxs( Application& app, LedgerMaster& ledgerMaster, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j); /** @@ -336,9 +326,6 @@ getNewestAccountTxs( * the account, minimum and maximum ledger numbers to search, * offset of first entry to return, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases, if shard databases are used. - * None if node database is used. * @param j Journal. * @return Vector of tuples of found transactions, their metadata and * account sequences sorted in ascending order by account @@ -353,7 +340,6 @@ getOldestAccountTxsB( soci::session& session, Application& app, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j); /** @@ -366,9 +352,6 @@ getOldestAccountTxsB( * the account, minimum and maximum ledger numbers to search, * offset of first entry to return, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases, if shard databases are used. - * None if node database is used. * @param j Journal. * @return Vector of tuples of found transactions, their metadata and * account sequences sorted in descending order by account @@ -383,7 +366,6 @@ getNewestAccountTxsB( soci::session& session, Application& app, RelationalDatabase::AccountTxOptions const& options, - std::optional const& limit_used, beast::Journal j); /** @@ -398,8 +380,6 @@ getNewestAccountTxsB( * match: the account, minimum and maximum ledger numbers to search, * marker of first returned entry, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases. * @param page_length Total number of transactions to return. * @return Vector of tuples of found transactions, their metadata and * account sequences sorted in ascending order by account @@ -414,7 +394,6 @@ oldestAccountTxPage( void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction, RelationalDatabase::AccountTxPageOptions const& options, - int limit_used, std::uint32_t page_length); /** @@ -429,8 +408,6 @@ oldestAccountTxPage( * match: the account, minimum and maximum ledger numbers to search, * marker of first returned entry, number of transactions to return, * flag if this number unlimited. - * @param limit_used Number or transactions already returned in calls - * to another shard databases. * @param page_length Total number of transactions to return. * @return Vector of tuples of found transactions, their metadata and * account sequences sorted in descending order by account @@ -445,7 +422,6 @@ newestAccountTxPage( void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction, RelationalDatabase::AccountTxPageOptions const& options, - int limit_used, std::uint32_t page_length); /** diff --git a/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp new file mode 100644 index 00000000000..95836a85bbf --- /dev/null +++ b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp @@ -0,0 +1,866 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2020 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +class SQLiteDatabaseImp final : public SQLiteDatabase +{ +public: + SQLiteDatabaseImp( + Application& app, + Config const& config, + JobQueue& jobQueue) + : app_(app) + , useTxTables_(config.useTxTables()) + , j_(app_.journal("SQLiteDatabaseImp")) + { + DatabaseCon::Setup const setup = setup_DatabaseCon(config, j_); + if (!makeLedgerDBs( + config, + setup, + DatabaseCon::CheckpointerSetup{&jobQueue, &app_.logs()})) + { + std::string_view constexpr error = + "Failed to create ledger databases"; + + JLOG(j_.fatal()) << error; + Throw(error.data()); + } + } + + std::optional + getMinLedgerSeq() override; + + std::optional + getTransactionsMinLedgerSeq() override; + + std::optional + getAccountTransactionsMinLedgerSeq() override; + + std::optional + getMaxLedgerSeq() override; + + void + deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq) override; + + void + deleteBeforeLedgerSeq(LedgerIndex ledgerSeq) override; + + void + deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) override; + + void + deleteAccountTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) override; + + std::size_t + getTransactionCount() override; + + std::size_t + getAccountTransactionCount() override; + + RelationalDatabase::CountMinMax + getLedgerCountMinMax() override; + + bool + saveValidatedLedger( + std::shared_ptr const& ledger, + bool current) override; + + std::optional + getLedgerInfoByIndex(LedgerIndex ledgerSeq) override; + + std::optional + getNewestLedgerInfo() override; + + std::optional + getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) override; + + std::optional + getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) override; + + std::optional + getLedgerInfoByHash(uint256 const& ledgerHash) override; + + uint256 + getHashByIndex(LedgerIndex ledgerIndex) override; + + std::optional + getHashesByIndex(LedgerIndex ledgerIndex) override; + + std::map + getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) override; + + std::vector> + getTxHistory(LedgerIndex startIndex) override; + + AccountTxs + getOldestAccountTxs(AccountTxOptions const& options) override; + + AccountTxs + getNewestAccountTxs(AccountTxOptions const& options) override; + + MetaTxsList + getOldestAccountTxsB(AccountTxOptions const& options) override; + + MetaTxsList + getNewestAccountTxsB(AccountTxOptions const& options) override; + + std::pair> + oldestAccountTxPage(AccountTxPageOptions const& options) override; + + std::pair> + newestAccountTxPage(AccountTxPageOptions const& options) override; + + std::pair> + oldestAccountTxPageB(AccountTxPageOptions const& options) override; + + std::pair> + newestAccountTxPageB(AccountTxPageOptions const& options) override; + + std::variant + getTransaction( + uint256 const& id, + std::optional> const& range, + error_code_i& ec) override; + + bool + ledgerDbHasSpace(Config const& config) override; + + bool + transactionDbHasSpace(Config const& config) override; + + std::uint32_t + getKBUsedAll() override; + + std::uint32_t + getKBUsedLedger() override; + + std::uint32_t + getKBUsedTransaction() override; + + void + closeLedgerDB() override; + + void + closeTransactionDB() override; + +private: + Application& app_; + bool const useTxTables_; + beast::Journal j_; + std::unique_ptr lgrdb_, txdb_; + + /** + * @brief makeLedgerDBs Opens ledger and transaction databases for the node + * store, and stores their descriptors in private member variables. + * @param config Config object. + * @param setup Path to the databases and other opening parameters. + * @param checkpointerSetup Checkpointer parameters. + * @return True if node databases opened successfully. + */ + bool + makeLedgerDBs( + Config const& config, + DatabaseCon::Setup const& setup, + DatabaseCon::CheckpointerSetup const& checkpointerSetup); + + /** + * @brief existsLedger Checks if the node store ledger database exists. + * @return True if the node store ledger database exists. + */ + bool + existsLedger() + { + return static_cast(lgrdb_); + } + + /** + * @brief existsTransaction Checks if the node store transaction database + * exists. + * @return True if the node store transaction database exists. + */ + bool + existsTransaction() + { + return static_cast(txdb_); + } + + /** + * @brief checkoutTransaction Checks out and returns node store ledger + * database. + * @return Session to the node store ledger database. + */ + auto + checkoutLedger() + { + return lgrdb_->checkoutDb(); + } + + /** + * @brief checkoutTransaction Checks out and returns the node store + * transaction database. + * @return Session to the node store transaction database. + */ + auto + checkoutTransaction() + { + return txdb_->checkoutDb(); + } +}; + +bool +SQLiteDatabaseImp::makeLedgerDBs( + Config const& config, + DatabaseCon::Setup const& setup, + DatabaseCon::CheckpointerSetup const& checkpointerSetup) +{ + auto [lgr, tx, res] = + detail::makeLedgerDBs(config, setup, checkpointerSetup, j_); + txdb_ = std::move(tx); + lgrdb_ = std::move(lgr); + return res; +} + +std::optional +SQLiteDatabaseImp::getMinLedgerSeq() +{ + /* if databases exists, use it */ + if (existsLedger()) + { + auto db = checkoutLedger(); + return detail::getMinLedgerSeq(*db, detail::TableType::Ledgers); + } + + /* else return empty value */ + return {}; +} + +std::optional +SQLiteDatabaseImp::getTransactionsMinLedgerSeq() +{ + if (!useTxTables_) + return {}; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getMinLedgerSeq(*db, detail::TableType::Transactions); + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getAccountTransactionsMinLedgerSeq() +{ + if (!useTxTables_) + return {}; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getMinLedgerSeq( + *db, detail::TableType::AccountTransactions); + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getMaxLedgerSeq() +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + return detail::getMaxLedgerSeq(*db, detail::TableType::Ledgers); + } + + return {}; +} + +void +SQLiteDatabaseImp::deleteTransactionByLedgerSeq(LedgerIndex ledgerSeq) +{ + if (!useTxTables_) + return; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + detail::deleteByLedgerSeq( + *db, detail::TableType::Transactions, ledgerSeq); + return; + } +} + +void +SQLiteDatabaseImp::deleteBeforeLedgerSeq(LedgerIndex ledgerSeq) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + detail::deleteBeforeLedgerSeq( + *db, detail::TableType::Ledgers, ledgerSeq); + return; + } +} + +void +SQLiteDatabaseImp::deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq) +{ + if (!useTxTables_) + return; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + detail::deleteBeforeLedgerSeq( + *db, detail::TableType::Transactions, ledgerSeq); + return; + } +} + +void +SQLiteDatabaseImp::deleteAccountTransactionsBeforeLedgerSeq( + LedgerIndex ledgerSeq) +{ + if (!useTxTables_) + return; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + detail::deleteBeforeLedgerSeq( + *db, detail::TableType::AccountTransactions, ledgerSeq); + return; + } +} + +std::size_t +SQLiteDatabaseImp::getTransactionCount() +{ + if (!useTxTables_) + return 0; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getRows(*db, detail::TableType::Transactions); + } + + return 0; +} + +std::size_t +SQLiteDatabaseImp::getAccountTransactionCount() +{ + if (!useTxTables_) + return 0; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getRows(*db, detail::TableType::AccountTransactions); + } + + return 0; +} + +RelationalDatabase::CountMinMax +SQLiteDatabaseImp::getLedgerCountMinMax() +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + return detail::getRowsMinMax(*db, detail::TableType::Ledgers); + } + + return {0, 0, 0}; +} + +bool +SQLiteDatabaseImp::saveValidatedLedger( + std::shared_ptr const& ledger, + bool current) +{ + if (existsLedger()) + { + if (!detail::saveValidatedLedger( + *lgrdb_, *txdb_, app_, ledger, current)) + return false; + } + + return true; +} + +std::optional +SQLiteDatabaseImp::getLedgerInfoByIndex(LedgerIndex ledgerSeq) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getLedgerInfoByIndex(*db, ledgerSeq, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getNewestLedgerInfo() +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getNewestLedgerInfo(*db, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = + detail::getLimitedOldestLedgerInfo(*db, ledgerFirstIndex, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = + detail::getLimitedNewestLedgerInfo(*db, ledgerFirstIndex, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +std::optional +SQLiteDatabaseImp::getLedgerInfoByHash(uint256 const& ledgerHash) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getLedgerInfoByHash(*db, ledgerHash, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +uint256 +SQLiteDatabaseImp::getHashByIndex(LedgerIndex ledgerIndex) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getHashByIndex(*db, ledgerIndex); + + if (res.isNonZero()) + return res; + } + + return uint256(); +} + +std::optional +SQLiteDatabaseImp::getHashesByIndex(LedgerIndex ledgerIndex) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getHashesByIndex(*db, ledgerIndex, j_); + + if (res.has_value()) + return res; + } + + return {}; +} + +std::map +SQLiteDatabaseImp::getHashesByIndex(LedgerIndex minSeq, LedgerIndex maxSeq) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + auto const res = detail::getHashesByIndex(*db, minSeq, maxSeq, j_); + + if (!res.empty()) + return res; + } + + return {}; +} + +std::vector> +SQLiteDatabaseImp::getTxHistory(LedgerIndex startIndex) +{ + if (!useTxTables_) + return {}; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + auto const res = detail::getTxHistory(*db, app_, startIndex, 20).first; + + if (!res.empty()) + return res; + } + + return {}; +} + +RelationalDatabase::AccountTxs +SQLiteDatabaseImp::getOldestAccountTxs(AccountTxOptions const& options) +{ + if (!useTxTables_) + return {}; + + LedgerMaster& ledgerMaster = app_.getLedgerMaster(); + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getOldestAccountTxs(*db, app_, ledgerMaster, options, j_) + .first; + } + + return {}; +} + +RelationalDatabase::AccountTxs +SQLiteDatabaseImp::getNewestAccountTxs(AccountTxOptions const& options) +{ + if (!useTxTables_) + return {}; + + LedgerMaster& ledgerMaster = app_.getLedgerMaster(); + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getNewestAccountTxs(*db, app_, ledgerMaster, options, j_) + .first; + } + + return {}; +} + +RelationalDatabase::MetaTxsList +SQLiteDatabaseImp::getOldestAccountTxsB(AccountTxOptions const& options) +{ + if (!useTxTables_) + return {}; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getOldestAccountTxsB(*db, app_, options, j_).first; + } + + return {}; +} + +RelationalDatabase::MetaTxsList +SQLiteDatabaseImp::getNewestAccountTxsB(AccountTxOptions const& options) +{ + if (!useTxTables_) + return {}; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getNewestAccountTxsB(*db, app_, options, j_).first; + } + + return {}; +} + +std::pair< + RelationalDatabase::AccountTxs, + std::optional> +SQLiteDatabaseImp::oldestAccountTxPage(AccountTxPageOptions const& options) +{ + if (!useTxTables_) + return {}; + + static std::uint32_t const page_length(200); + auto onUnsavedLedger = + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); + AccountTxs ret; + Application& app = app_; + auto onTransaction = [&ret, &app]( + std::uint32_t ledger_index, + std::string const& status, + Blob&& rawTxn, + Blob&& rawMeta) { + convertBlobsToTxResult(ret, ledger_index, status, rawTxn, rawMeta, app); + }; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + auto newmarker = + detail::oldestAccountTxPage( + *db, onUnsavedLedger, onTransaction, options, page_length) + .first; + return {ret, newmarker}; + } + + return {}; +} + +std::pair< + RelationalDatabase::AccountTxs, + std::optional> +SQLiteDatabaseImp::newestAccountTxPage(AccountTxPageOptions const& options) +{ + if (!useTxTables_) + return {}; + + static std::uint32_t const page_length(200); + auto onUnsavedLedger = + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); + AccountTxs ret; + Application& app = app_; + auto onTransaction = [&ret, &app]( + std::uint32_t ledger_index, + std::string const& status, + Blob&& rawTxn, + Blob&& rawMeta) { + convertBlobsToTxResult(ret, ledger_index, status, rawTxn, rawMeta, app); + }; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + auto newmarker = + detail::newestAccountTxPage( + *db, onUnsavedLedger, onTransaction, options, page_length) + .first; + return {ret, newmarker}; + } + + return {}; +} + +std::pair< + RelationalDatabase::MetaTxsList, + std::optional> +SQLiteDatabaseImp::oldestAccountTxPageB(AccountTxPageOptions const& options) +{ + if (!useTxTables_) + return {}; + + static std::uint32_t const page_length(500); + auto onUnsavedLedger = + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); + MetaTxsList ret; + auto onTransaction = [&ret]( + std::uint32_t ledgerIndex, + std::string const& status, + Blob&& rawTxn, + Blob&& rawMeta) { + ret.emplace_back(std::move(rawTxn), std::move(rawMeta), ledgerIndex); + }; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + auto newmarker = + detail::oldestAccountTxPage( + *db, onUnsavedLedger, onTransaction, options, page_length) + .first; + return {ret, newmarker}; + } + + return {}; +} + +std::pair< + RelationalDatabase::MetaTxsList, + std::optional> +SQLiteDatabaseImp::newestAccountTxPageB(AccountTxPageOptions const& options) +{ + if (!useTxTables_) + return {}; + + static std::uint32_t const page_length(500); + auto onUnsavedLedger = + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1); + MetaTxsList ret; + auto onTransaction = [&ret]( + std::uint32_t ledgerIndex, + std::string const& status, + Blob&& rawTxn, + Blob&& rawMeta) { + ret.emplace_back(std::move(rawTxn), std::move(rawMeta), ledgerIndex); + }; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + auto newmarker = + detail::newestAccountTxPage( + *db, onUnsavedLedger, onTransaction, options, page_length) + .first; + return {ret, newmarker}; + } + + return {}; +} + +std::variant +SQLiteDatabaseImp::getTransaction( + uint256 const& id, + std::optional> const& range, + error_code_i& ec) +{ + if (!useTxTables_) + return TxSearched::unknown; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::getTransaction(*db, app_, id, range, ec); + } + + return TxSearched::unknown; +} + +bool +SQLiteDatabaseImp::ledgerDbHasSpace(Config const& config) +{ + if (existsLedger()) + { + auto db = checkoutLedger(); + return detail::dbHasSpace(*db, config, j_); + } + + return true; +} + +bool +SQLiteDatabaseImp::transactionDbHasSpace(Config const& config) +{ + if (!useTxTables_) + return true; + + if (existsTransaction()) + { + auto db = checkoutTransaction(); + return detail::dbHasSpace(*db, config, j_); + } + + return true; +} + +std::uint32_t +SQLiteDatabaseImp::getKBUsedAll() +{ + if (existsLedger()) + { + return ripple::getKBUsedAll(lgrdb_->getSession()); + } + + return 0; +} + +std::uint32_t +SQLiteDatabaseImp::getKBUsedLedger() +{ + if (existsLedger()) + { + return ripple::getKBUsedDB(lgrdb_->getSession()); + } + + return 0; +} + +std::uint32_t +SQLiteDatabaseImp::getKBUsedTransaction() +{ + if (!useTxTables_) + return 0; + + if (existsTransaction()) + { + return ripple::getKBUsedDB(txdb_->getSession()); + } + + return 0; +} + +void +SQLiteDatabaseImp::closeLedgerDB() +{ + lgrdb_.reset(); +} + +void +SQLiteDatabaseImp::closeTransactionDB() +{ + txdb_.reset(); +} + +std::unique_ptr +getSQLiteDatabase(Application& app, Config const& config, JobQueue& jobQueue) +{ + return std::make_unique(app, config, jobQueue); +} + +} // namespace ripple diff --git a/src/ripple/app/rdb/impl/PeerFinder.cpp b/src/xrpld/app/rdb/detail/PeerFinder.cpp similarity index 99% rename from src/ripple/app/rdb/impl/PeerFinder.cpp rename to src/xrpld/app/rdb/detail/PeerFinder.cpp index 46dca3760c7..b2eaf3dfe11 100644 --- a/src/ripple/app/rdb/impl/PeerFinder.cpp +++ b/src/xrpld/app/rdb/detail/PeerFinder.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/impl/RelationalDatabase.cpp b/src/xrpld/app/rdb/detail/RelationalDatabase.cpp similarity index 63% rename from src/ripple/app/rdb/impl/RelationalDatabase.cpp rename to src/xrpld/app/rdb/detail/RelationalDatabase.cpp index 8a3ce5b016d..4a95134d705 100644 --- a/src/ripple/app/rdb/impl/RelationalDatabase.cpp +++ b/src/xrpld/app/rdb/detail/RelationalDatabase.cpp @@ -17,19 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include namespace ripple { extern std::unique_ptr getSQLiteDatabase(Application& app, Config const& config, JobQueue& jobQueue); -extern std::unique_ptr -getPostgresDatabase(Application& app, Config const& config, JobQueue& jobQueue); - std::unique_ptr RelationalDatabase::init( Application& app, @@ -37,42 +33,30 @@ RelationalDatabase::init( JobQueue& jobQueue) { bool use_sqlite = false; - bool use_postgres = false; - if (config.reporting()) - { - use_postgres = true; - } - else + const Section& rdb_section{config.section(SECTION_RELATIONAL_DB)}; + if (!rdb_section.empty()) { - const Section& rdb_section{config.section(SECTION_RELATIONAL_DB)}; - if (!rdb_section.empty()) + if (boost::iequals(get(rdb_section, "backend"), "sqlite")) { - if (boost::iequals(get(rdb_section, "backend"), "sqlite")) - { - use_sqlite = true; - } - else - { - Throw( - "Invalid rdb_section backend value: " + - get(rdb_section, "backend")); - } + use_sqlite = true; } else { - use_sqlite = true; + Throw( + "Invalid rdb_section backend value: " + + get(rdb_section, "backend")); } } + else + { + use_sqlite = true; + } if (use_sqlite) { return getSQLiteDatabase(app, config, jobQueue); } - else if (use_postgres) - { - return getPostgresDatabase(app, config, jobQueue); - } return std::unique_ptr(); } diff --git a/src/ripple/app/rdb/impl/State.cpp b/src/xrpld/app/rdb/detail/State.cpp similarity index 99% rename from src/ripple/app/rdb/impl/State.cpp rename to src/xrpld/app/rdb/detail/State.cpp index 8f8beb0c7e1..c3de860689c 100644 --- a/src/ripple/app/rdb/impl/State.cpp +++ b/src/xrpld/app/rdb/detail/State.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/app/rdb/impl/Vacuum.cpp b/src/xrpld/app/rdb/detail/Vacuum.cpp similarity index 94% rename from src/ripple/app/rdb/impl/Vacuum.cpp rename to src/xrpld/app/rdb/detail/Vacuum.cpp index 20386f07b18..cc7f01a8409 100644 --- a/src/ripple/app/rdb/impl/Vacuum.cpp +++ b/src/xrpld/app/rdb/detail/Vacuum.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { @@ -40,8 +40,8 @@ doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j) return false; } - auto txnDB = - std::make_unique(setup, TxDBName, TxDBPragma, TxDBInit, j); + auto txnDB = std::make_unique( + setup, TxDBName, setup.txPragma, TxDBInit, j); auto& session = txnDB->getSession(); std::uint32_t pageSize; diff --git a/src/ripple/app/rdb/impl/Wallet.cpp b/src/xrpld/app/rdb/detail/Wallet.cpp similarity index 98% rename from src/ripple/app/rdb/impl/Wallet.cpp rename to src/xrpld/app/rdb/detail/Wallet.cpp index 79734ab67dc..ffb28596918 100644 --- a/src/ripple/app/rdb/impl/Wallet.cpp +++ b/src/xrpld/app/rdb/detail/Wallet.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { @@ -27,7 +27,7 @@ makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j) { // wallet database return std::make_unique( - setup, WalletDBName, std::array(), WalletDBInit, j); + setup, WalletDBName, std::array(), WalletDBInit, j); } std::unique_ptr @@ -38,7 +38,7 @@ makeTestWalletDB( { // wallet database return std::make_unique( - setup, dbname.data(), std::array(), WalletDBInit, j); + setup, dbname.data(), std::array(), WalletDBInit, j); } void diff --git a/src/ripple/app/tx/apply.h b/src/xrpld/app/tx/apply.h similarity index 96% rename from src/ripple/app/tx/apply.h rename to src/xrpld/app/tx/apply.h index b71e7ed4a9d..6a0401e2b55 100644 --- a/src/ripple/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_TX_APPLY_H_INCLUDED #define RIPPLE_TX_APPLY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/tx/applySteps.h b/src/xrpld/app/tx/applySteps.h similarity index 99% rename from src/ripple/app/tx/applySteps.h rename to src/xrpld/app/tx/applySteps.h index ede7bd8cc09..1df537515e9 100644 --- a/src/ripple/app/tx/applySteps.h +++ b/src/xrpld/app/tx/applySteps.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_TX_APPLYSTEPS_H_INCLUDED #define RIPPLE_TX_APPLYSTEPS_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp similarity index 97% rename from src/ripple/app/tx/impl/AMMBid.cpp rename to src/xrpld/app/tx/detail/AMMBid.cpp index e49c378ceeb..9de3762d2e3 100644 --- a/src/ripple/app/tx/impl/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMBid.h b/src/xrpld/app/tx/detail/AMMBid.h similarity index 98% rename from src/ripple/app/tx/impl/AMMBid.h rename to src/xrpld/app/tx/detail/AMMBid.h index db3d8d9b016..4bb3a2adfd2 100644 --- a/src/ripple/app/tx/impl/AMMBid.h +++ b/src/xrpld/app/tx/detail/AMMBid.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_AMMBID_H_INCLUDED #define RIPPLE_TX_AMMBID_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp new file mode 100644 index 00000000000..cd1e3008e97 --- /dev/null +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -0,0 +1,294 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +NotTEC +AMMClawback::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureAMMClawback)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; // LCOV_EXCL_LINE + + auto const flags = ctx.tx.getFlags(); + if (flags & tfAMMClawbackMask) + return temINVALID_FLAG; + + AccountID const issuer = ctx.tx[sfAccount]; + AccountID const holder = ctx.tx[sfHolder]; + + if (issuer == holder) + { + JLOG(ctx.j.trace()) + << "AMMClawback: holder cannot be the same as issuer."; + return temMALFORMED; + } + + std::optional const clawAmount = ctx.tx[~sfAmount]; + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; + + if (isXRP(asset)) + return temMALFORMED; + + if (flags & tfClawTwoAssets && asset.account != asset2.account) + { + JLOG(ctx.j.trace()) + << "AMMClawback: tfClawTwoAssets can only be enabled when two " + "assets in the AMM pool are both issued by the issuer"; + return temINVALID_FLAG; + } + + if (asset.account != issuer) + { + JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not " + "match Account field."; + return temMALFORMED; + } + + if (clawAmount && clawAmount->issue() != asset) + { + JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield " + "does not match Asset field"; + return temBAD_AMOUNT; + } + + if (clawAmount && *clawAmount <= beast::zero) + return temBAD_AMOUNT; + + return preflight2(ctx); +} + +TER +AMMClawback::preclaim(PreclaimContext const& ctx) +{ + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; + auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount])); + if (!sleIssuer) + return terNO_ACCOUNT; // LCOV_EXCL_LINE + + if (!ctx.view.read(keylet::account(ctx.tx[sfHolder]))) + return terNO_ACCOUNT; + + auto const ammSle = ctx.view.read(keylet::amm(asset, asset2)); + if (!ammSle) + { + JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair."; + return terNO_AMM; + } + + std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags); + + // If AllowTrustLineClawback is not set or NoFreeze is set, return no + // permission + if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || + (issuerFlagsIn & lsfNoFreeze)) + return tecNO_PERMISSION; + + return tesSUCCESS; +} + +TER +AMMClawback::doApply() +{ + Sandbox sb(&ctx_.view()); + + auto const ter = applyGuts(sb); + if (ter == tesSUCCESS) + sb.apply(ctx_.rawView()); + + return ter; +} + +TER +AMMClawback::applyGuts(Sandbox& sb) +{ + std::optional const clawAmount = ctx_.tx[~sfAmount]; + AccountID const issuer = ctx_.tx[sfAccount]; + AccountID const holder = ctx_.tx[sfHolder]; + Issue const asset = ctx_.tx[sfAsset]; + Issue const asset2 = ctx_.tx[sfAsset2]; + + auto ammSle = sb.peek(keylet::amm(asset, asset2)); + if (!ammSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const ammAccount = (*ammSle)[sfAccount]; + auto const accountSle = sb.read(keylet::account(ammAccount)); + if (!accountSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const expected = ammHolds( + sb, + *ammSle, + asset, + asset2, + FreezeHandling::fhIGNORE_FREEZE, + ctx_.journal); + + if (!expected) + return expected.error(); // LCOV_EXCL_LINE + auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected; + + TER result; + STAmount newLPTokenBalance; + STAmount amountWithdraw; + std::optional amount2Withdraw; + + auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_); + if (holdLPtokens == beast::zero) + return tecAMM_BALANCE; + + if (!clawAmount) + // Because we are doing a two-asset withdrawal, + // tfee is actually not used, so pass tfee as 0. + std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) = + AMMWithdraw::equalWithdrawTokens( + sb, + *ammSle, + holder, + ammAccount, + amountBalance, + amount2Balance, + lptAMMBalance, + holdLPtokens, + holdLPtokens, + 0, + FreezeHandling::fhIGNORE_FREEZE, + WithdrawAll::Yes, + mPriorBalance, + ctx_.journal); + else + std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) = + equalWithdrawMatchingOneAmount( + sb, + *ammSle, + holder, + ammAccount, + amountBalance, + amount2Balance, + lptAMMBalance, + holdLPtokens, + *clawAmount); + + if (result != tesSUCCESS) + return result; // LCOV_EXCL_LINE + + auto const res = AMMWithdraw::deleteAMMAccountIfEmpty( + sb, ammSle, newLPTokenBalance, asset, asset2, j_); + if (!res.second) + return res.first; // LCOV_EXCL_LINE + + JLOG(ctx_.journal.trace()) + << "AMM Withdraw during AMMClawback: lptoken new balance: " + << to_string(newLPTokenBalance.iou()) + << " old balance: " << to_string(lptAMMBalance.iou()); + + auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_); + if (ter != tesSUCCESS) + return ter; // LCOV_EXCL_LINE + + // if the issuer issues both assets and sets flag tfClawTwoAssets, we + // will claw the paired asset as well. We already checked if + // tfClawTwoAssets is enabled, the two assets have to be issued by the + // same issuer. + if (!amount2Withdraw) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const flags = ctx_.tx.getFlags(); + if (flags & tfClawTwoAssets) + return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_); + + return tesSUCCESS; +} + +std::tuple> +AMMClawback::equalWithdrawMatchingOneAmount( + Sandbox& sb, + SLE const& ammSle, + AccountID const& holder, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + STAmount const& holdLPtokens, + STAmount const& amount) +{ + auto frac = Number{amount} / amountBalance; + auto const amount2Withdraw = amount2Balance * frac; + + auto const lpTokensWithdraw = + toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); + if (lpTokensWithdraw > holdLPtokens) + // if lptoken balance less than what the issuer intended to clawback, + // clawback all the tokens. Because we are doing a two-asset withdrawal, + // tfee is actually not used, so pass tfee as 0. + return AMMWithdraw::equalWithdrawTokens( + sb, + ammSle, + holder, + ammAccount, + amountBalance, + amount2Balance, + lptAMMBalance, + holdLPtokens, + holdLPtokens, + 0, + FreezeHandling::fhIGNORE_FREEZE, + WithdrawAll::Yes, + mPriorBalance, + ctx_.journal); + + // Because we are doing a two-asset withdrawal, + // tfee is actually not used, so pass tfee as 0. + return AMMWithdraw::withdraw( + sb, + ammSle, + ammAccount, + holder, + amountBalance, + amount, + toSTAmount(amount2Balance.issue(), amount2Withdraw), + lptAMMBalance, + toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), + 0, + FreezeHandling::fhIGNORE_FREEZE, + WithdrawAll::No, + mPriorBalance, + ctx_.journal); +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/AMMClawback.h b/src/xrpld/app/tx/detail/AMMClawback.h new file mode 100644 index 00000000000..fdcfc53e2ca --- /dev/null +++ b/src/xrpld/app/tx/detail/AMMClawback.h @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_AMMCLAWBACK_H_INCLUDED +#define RIPPLE_TX_AMMCLAWBACK_H_INCLUDED + +#include + +namespace ripple { +class Sandbox; +class AMMClawback : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit AMMClawback(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; + +private: + TER + applyGuts(Sandbox& view); + + /** Withdraw both assets by providing maximum amount of asset1, + * asset2's amount will be calculated according to the current proportion. + * Since it is two-asset withdrawal, tfee is omitted. + * @param view + * @param ammAccount current AMM account + * @param amountBalance current AMM asset1 balance + * @param amount2Balance current AMM asset2 balance + * @param lptAMMBalance current AMM LPT balance + * @param amount asset1 withdraw amount + * @return + */ + std::tuple> + equalWithdrawMatchingOneAmount( + Sandbox& view, + SLE const& ammSle, + AccountID const& holder, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + STAmount const& holdLPtokens, + STAmount const& amount); +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp similarity index 94% rename from src/ripple/app/tx/impl/AMMCreate.cpp rename to src/xrpld/app/tx/detail/AMMCreate.cpp index cab99f1669e..31773166d4a 100644 --- a/src/ripple/app/tx/impl/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -184,7 +184,13 @@ AMMCreate::preclaim(PreclaimContext const& ctx) return tecAMM_INVALID_TOKENS; } - // Disallow AMM if the issuer has clawback enabled + // If featureAMMClawback is enabled, allow AMMCreate without checking + // if the issuer has clawback enabled + if (ctx.view.rules().enabled(featureAMMClawback)) + return tesSUCCESS; + + // Disallow AMM if the issuer has clawback enabled when featureAMMClawback + // is not enabled auto clawbackDisabled = [&](Issue const& issue) -> TER { if (isXRP(issue)) return tesSUCCESS; diff --git a/src/ripple/app/tx/impl/AMMCreate.h b/src/xrpld/app/tx/detail/AMMCreate.h similarity index 98% rename from src/ripple/app/tx/impl/AMMCreate.h rename to src/xrpld/app/tx/detail/AMMCreate.h index f521f5870b4..189d66a55ac 100644 --- a/src/ripple/app/tx/impl/AMMCreate.h +++ b/src/xrpld/app/tx/detail/AMMCreate.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_AMMCREATE_H_INCLUDED #define RIPPLE_TX_AMMCREATE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMDelete.cpp b/src/xrpld/app/tx/detail/AMMDelete.cpp similarity index 88% rename from src/ripple/app/tx/impl/AMMDelete.cpp rename to src/xrpld/app/tx/detail/AMMDelete.cpp index 25502be4f44..89ce34052d2 100644 --- a/src/ripple/app/tx/impl/AMMDelete.cpp +++ b/src/xrpld/app/tx/detail/AMMDelete.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMDelete.h b/src/xrpld/app/tx/detail/AMMDelete.h similarity index 97% rename from src/ripple/app/tx/impl/AMMDelete.h rename to src/xrpld/app/tx/detail/AMMDelete.h index cf7f55cb715..19885b1dad6 100644 --- a/src/ripple/app/tx/impl/AMMDelete.h +++ b/src/xrpld/app/tx/detail/AMMDelete.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_AMMDELETE_H_INCLUDED #define RIPPLE_TX_AMMDELETE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp similarity index 95% rename from src/ripple/app/tx/impl/AMMDeposit.cpp rename to src/xrpld/app/tx/detail/AMMDeposit.cpp index 74cba160edc..3448401eb79 100644 --- a/src/ripple/app/tx/impl/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -244,6 +244,37 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) : tecUNFUNDED_AMM; }; + if (ctx.view.rules().enabled(featureAMMClawback)) + { + // Check if either of the assets is frozen, AMMDeposit is not allowed + // if either asset is frozen + auto checkAsset = [&](Issue const& asset) -> TER { + if (auto const ter = requireAuth(ctx.view, asset, accountID)) + { + JLOG(ctx.j.debug()) + << "AMM Deposit: account is not authorized, " << asset; + return ter; + } + + if (isFrozen(ctx.view, accountID, asset)) + { + JLOG(ctx.j.debug()) + << "AMM Deposit: account or currency is frozen, " + << to_string(accountID) << " " << to_string(asset.currency); + + return tecFROZEN; + } + + return tesSUCCESS; + }; + + if (auto const ter = checkAsset(ctx.tx[sfAsset])) + return ter; + + if (auto const ter = checkAsset(ctx.tx[sfAsset2])) + return ter; + } + auto const amount = ctx.tx[~sfAmount]; auto const amount2 = ctx.tx[~sfAmount2]; auto const ammAccountID = ammSle->getAccountID(sfAccount); diff --git a/src/ripple/app/tx/impl/AMMDeposit.h b/src/xrpld/app/tx/detail/AMMDeposit.h similarity index 99% rename from src/ripple/app/tx/impl/AMMDeposit.h rename to src/xrpld/app/tx/detail/AMMDeposit.h index 385ce7c24e5..0acb1dd9ab3 100644 --- a/src/ripple/app/tx/impl/AMMDeposit.h +++ b/src/xrpld/app/tx/detail/AMMDeposit.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_AMMDEPOSIT_H_INCLUDED #define RIPPLE_TX_AMMDEPOSIT_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMVote.cpp b/src/xrpld/app/tx/detail/AMMVote.cpp similarity index 95% rename from src/ripple/app/tx/impl/AMMVote.cpp rename to src/xrpld/app/tx/detail/AMMVote.cpp index d908a93c383..c4b6c612c63 100644 --- a/src/ripple/app/tx/impl/AMMVote.cpp +++ b/src/xrpld/app/tx/detail/AMMVote.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -104,7 +104,7 @@ applyVote( Number den{0}; // Account already has vote entry bool foundAccount = false; - auto const& rules = ctx_.view().rules(); + // Iterate over the current vote entries and update each entry // per current total tokens balance and each LP tokens balance. // Find the entry with the least tokens and whether the account @@ -120,7 +120,7 @@ applyVote( continue; } auto feeVal = entry[sfTradingFee]; - STObject newEntry = STObject::makeInnerObject(sfVoteEntry, rules); + STObject newEntry = STObject::makeInnerObject(sfVoteEntry); // The account already has the vote entry. if (account == account_) { @@ -159,7 +159,7 @@ applyVote( { auto update = [&](std::optional const& minPos = std::nullopt) { - STObject newEntry = STObject::makeInnerObject(sfVoteEntry, rules); + STObject newEntry = STObject::makeInnerObject(sfVoteEntry); if (feeNew != 0) newEntry.setFieldU16(sfTradingFee, feeNew); newEntry.setFieldU32( diff --git a/src/ripple/app/tx/impl/AMMVote.h b/src/xrpld/app/tx/detail/AMMVote.h similarity index 98% rename from src/ripple/app/tx/impl/AMMVote.h rename to src/xrpld/app/tx/detail/AMMVote.h index 97d257d0513..2bee01aff53 100644 --- a/src/ripple/app/tx/impl/AMMVote.h +++ b/src/xrpld/app/tx/detail/AMMVote.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_AMMVOTE_H_INCLUDED #define RIPPLE_TX_AMMVOTE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp similarity index 77% rename from src/ripple/app/tx/impl/AMMWithdraw.cpp rename to src/xrpld/app/tx/detail/AMMWithdraw.cpp index c8d0d67dc97..118262905c1 100644 --- a/src/ripple/app/tx/impl/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -17,16 +17,15 @@ */ //============================================================================== -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include @@ -358,6 +357,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfTwoAsset) return equalWithdrawLimit( sb, + *ammSle, ammAccountID, amountBalance, amount2Balance, @@ -368,6 +368,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll) return singleWithdrawTokens( sb, + *ammSle, ammAccountID, amountBalance, lptAMMBalance, @@ -377,6 +378,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfLimitLPToken) return singleWithdrawEPrice( sb, + *ammSle, ammAccountID, amountBalance, lptAMMBalance, @@ -385,10 +387,18 @@ AMMWithdraw::applyGuts(Sandbox& sb) tfee); if (subTxType & tfSingleAsset) return singleWithdraw( - sb, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee); + sb, + *ammSle, + ammAccountID, + amountBalance, + lptAMMBalance, + *amount, + tfee); if (subTxType & tfLPToken || subTxType & tfWithdrawAll) + { return equalWithdrawTokens( sb, + *ammSle, ammAccountID, amountBalance, amount2Balance, @@ -396,6 +406,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) lpTokens, *lpTokensWithdraw, tfee); + } // should not happen. // LCOV_EXCL_START JLOG(j_.error()) << "AMM Withdraw: invalid options."; @@ -406,22 +417,12 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (result != tesSUCCESS) return {result, false}; - bool updateBalance = true; - if (newLPTokenBalance == beast::zero) - { - if (auto const ter = - deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); - ter != tesSUCCESS && ter != tecINCOMPLETE) - return {ter, false}; - else - updateBalance = (ter == tecINCOMPLETE); - } - - if (updateBalance) - { - ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance); - sb.update(ammSle); - } + auto const res = deleteAMMAccountIfEmpty( + sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); + // LCOV_EXCL_START + if (!res.second) + return {res.first, false}; + // LCOV_EXCL_STOP JLOG(ctx_.journal.trace()) << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " " @@ -447,6 +448,7 @@ AMMWithdraw::doApply() std::pair AMMWithdraw::withdraw( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& amountWithdraw, @@ -455,27 +457,62 @@ AMMWithdraw::withdraw( STAmount const& lpTokensWithdraw, std::uint16_t tfee) { - auto const ammSle = - ctx_.view().read(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2])); - if (!ammSle) - return {tecINTERNAL, STAmount{}}; // LCOV_EXCL_LINE - auto const lpTokens = ammLPHolds(view, *ammSle, account_, ctx_.journal); + TER ter; + STAmount newLPTokenBalance; + std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw( + view, + ammSle, + ammAccount, + account_, + amountBalance, + amountWithdraw, + amount2Withdraw, + lpTokensAMMBalance, + lpTokensWithdraw, + tfee, + FreezeHandling::fhZERO_IF_FROZEN, + isWithdrawAll(ctx_.tx), + mPriorBalance, + j_); + return {ter, newLPTokenBalance}; +} + +std::tuple> +AMMWithdraw::withdraw( + Sandbox& view, + SLE const& ammSle, + AccountID const& ammAccount, + AccountID const& account, + STAmount const& amountBalance, + STAmount const& amountWithdraw, + std::optional const& amount2Withdraw, + STAmount const& lpTokensAMMBalance, + STAmount const& lpTokensWithdraw, + std::uint16_t tfee, + FreezeHandling freezeHandling, + WithdrawAll withdrawAll, + XRPAmount const& priorBalance, + beast::Journal const& journal) +{ + auto const lpTokens = ammLPHolds(view, ammSle, account, journal); auto const expected = ammHolds( view, - *ammSle, + ammSle, amountWithdraw.issue(), std::nullopt, - FreezeHandling::fhZERO_IF_FROZEN, - j_); + freezeHandling, + journal); + // LCOV_EXCL_START if (!expected) - return {expected.error(), STAmount{}}; + return {expected.error(), STAmount{}, STAmount{}, STAmount{}}; + // LCOV_EXCL_STOP auto const [curBalance, curBalance2, _] = *expected; (void)_; auto const [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] = [&]() -> std::tuple, STAmount> { - if (!(ctx_.tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))) + if (withdrawAll == WithdrawAll::No) return adjustAmountsByLPTokens( amountBalance, amountWithdraw, @@ -491,11 +528,11 @@ AMMWithdraw::withdraw( if (lpTokensWithdrawActual <= beast::zero || lpTokensWithdrawActual > lpTokens) { - JLOG(ctx_.journal.debug()) + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, invalid LP tokens: " << lpTokensWithdrawActual << " " << lpTokens << " " << lpTokensAMMBalance; - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, STAmount{}}; } // Should not happen since the only LP on last withdraw @@ -503,11 +540,13 @@ AMMWithdraw::withdraw( if (view.rules().enabled(fixAMMv1_1) && lpTokensWithdrawActual > lpTokensAMMBalance) { - JLOG(ctx_.journal.debug()) + // LCOV_EXCL_START + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, unexpected LP tokens: " << lpTokensWithdrawActual << " " << lpTokens << " " << lpTokensAMMBalance; - return {tecINTERNAL, STAmount{}}; + return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}}; + // LCOV_EXCL_STOP } // Withdrawing one side of the pool @@ -516,12 +555,12 @@ AMMWithdraw::withdraw( (amount2WithdrawActual == curBalance2 && amountWithdrawActual != curBalance)) { - JLOG(ctx_.journal.debug()) + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw one side of the pool " << " curBalance: " << curBalance << " " << amountWithdrawActual << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance; - return {tecAMM_BALANCE, STAmount{}}; + return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; } // May happen if withdrawing an amount close to one side of the pool @@ -529,84 +568,123 @@ AMMWithdraw::withdraw( (amountWithdrawActual != curBalance || amount2WithdrawActual != curBalance2)) { - JLOG(ctx_.journal.debug()) + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw all tokens " << " curBalance: " << curBalance << " " << amountWithdrawActual << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0}) << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance; - return {tecAMM_BALANCE, STAmount{}}; + return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; } // Withdrawing more than the pool's balance if (amountWithdrawActual > curBalance || amount2WithdrawActual > curBalance2) { - JLOG(ctx_.journal.debug()) + JLOG(journal.debug()) << "AMM Withdraw: withdrawing more than the pool's balance " << " curBalance: " << curBalance << " " << amountWithdrawActual << " curBalance2: " << curBalance2 << " " << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{}) << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance; - return {tecAMM_BALANCE, STAmount{}}; + return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; } + // Check the reserve in case a trustline has to be created + bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2); + auto sufficientReserve = [&](Issue const& issue) -> TER { + if (!enabledFixAMMv1_2 || isXRP(issue)) + return tesSUCCESS; + if (!view.exists(keylet::line(account, issue))) + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + return tecINTERNAL; // LCOV_EXCL_LINE + auto const balance = (*sleAccount)[sfBalance].xrp(); + std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); + + // See also SetTrust::doApply() + XRPAmount const reserve( + (ownerCount < 2) ? XRPAmount(beast::zero) + : view.fees().accountReserve(ownerCount + 1)); + + if (std::max(priorBalance, balance) < reserve) + return tecINSUFFICIENT_RESERVE; + } + return tesSUCCESS; + }; + + if (auto const err = sufficientReserve(amountWithdrawActual.issue())) + return {err, STAmount{}, STAmount{}, STAmount{}}; + // Withdraw amountWithdraw auto res = accountSend( view, ammAccount, - account_, + account, amountWithdrawActual, - ctx_.journal, + journal, WaiveTransferFee::Yes); if (res != tesSUCCESS) { - JLOG(ctx_.journal.debug()) + // LCOV_EXCL_START + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << amountWithdrawActual; - return {res, STAmount{}}; + return {res, STAmount{}, STAmount{}, STAmount{}}; + // LCOV_EXCL_STOP } // Withdraw amount2Withdraw if (amount2WithdrawActual) { + if (auto const err = sufficientReserve(amount2WithdrawActual->issue()); + err != tesSUCCESS) + return {err, STAmount{}, STAmount{}, STAmount{}}; + res = accountSend( view, ammAccount, - account_, + account, *amount2WithdrawActual, - ctx_.journal, + journal, WaiveTransferFee::Yes); if (res != tesSUCCESS) { - JLOG(ctx_.journal.debug()) << "AMM Withdraw: failed to withdraw " - << *amount2WithdrawActual; - return {res, STAmount{}}; + // LCOV_EXCL_START + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " + << *amount2WithdrawActual; + return {res, STAmount{}, STAmount{}, STAmount{}}; + // LCOV_EXCL_STOP } } // Withdraw LP tokens res = redeemIOU( view, - account_, + account, lpTokensWithdrawActual, lpTokensWithdrawActual.issue(), - ctx_.journal); + journal); if (res != tesSUCCESS) { - JLOG(ctx_.journal.debug()) - << "AMM Withdraw: failed to withdraw LPTokens"; - return {res, STAmount{}}; + // LCOV_EXCL_START + JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens"; + return {res, STAmount{}, STAmount{}, STAmount{}}; + // LCOV_EXCL_STOP } - return {tesSUCCESS, lpTokensAMMBalance - lpTokensWithdrawActual}; + return std::make_tuple( + tesSUCCESS, + lpTokensAMMBalance - lpTokensWithdrawActual, + amountWithdrawActual, + amount2WithdrawActual); } -/** Proportional withdrawal of pool assets for the amount of LPTokens. - */ std::pair AMMWithdraw::equalWithdrawTokens( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& amount2Balance, @@ -614,20 +692,97 @@ AMMWithdraw::equalWithdrawTokens( STAmount const& lpTokens, STAmount const& lpTokensWithdraw, std::uint16_t tfee) +{ + TER ter; + STAmount newLPTokenBalance; + std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = + equalWithdrawTokens( + view, + ammSle, + account_, + ammAccount, + amountBalance, + amount2Balance, + lptAMMBalance, + lpTokens, + lpTokensWithdraw, + tfee, + FreezeHandling::fhZERO_IF_FROZEN, + isWithdrawAll(ctx_.tx), + mPriorBalance, + ctx_.journal); + return {ter, newLPTokenBalance}; +} + +std::pair +AMMWithdraw::deleteAMMAccountIfEmpty( + Sandbox& sb, + std::shared_ptr const ammSle, + STAmount const& lpTokenBalance, + Issue const& issue1, + Issue const& issue2, + beast::Journal const& journal) +{ + TER ter; + bool updateBalance = true; + if (lpTokenBalance == beast::zero) + { + ter = deleteAMMAccount(sb, issue1, issue2, journal); + if (ter != tesSUCCESS && ter != tecINCOMPLETE) + return {ter, false}; // LCOV_EXCL_LINE + else + updateBalance = (ter == tecINCOMPLETE); + } + + if (updateBalance) + { + ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance); + sb.update(ammSle); + } + + return {ter, true}; +} + +/** Proportional withdrawal of pool assets for the amount of LPTokens. + */ +std::tuple> +AMMWithdraw::equalWithdrawTokens( + Sandbox& view, + SLE const& ammSle, + AccountID const account, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + STAmount const& lpTokensWithdraw, + std::uint16_t tfee, + FreezeHandling freezeHanding, + WithdrawAll withdrawAll, + XRPAmount const& priorBalance, + beast::Journal const& journal) { try { // Withdrawing all tokens in the pool if (lpTokensWithdraw == lptAMMBalance) + { return withdraw( view, + ammSle, ammAccount, + account, amountBalance, amountBalance, amount2Balance, lptAMMBalance, lpTokensWithdraw, - tfee); + tfee, + freezeHanding, + WithdrawAll::Yes, + priorBalance, + journal); + } auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue()); auto const withdrawAmount = @@ -639,25 +794,31 @@ AMMWithdraw::equalWithdrawTokens( // withdrawal due to round off. Fail so the user withdraws // more tokens. if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero) - return {tecAMM_FAILED, STAmount{}}; + return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}}; return withdraw( view, + ammSle, ammAccount, + account, amountBalance, withdrawAmount, withdraw2Amount, lptAMMBalance, lpTokensWithdraw, - tfee); + tfee, + freezeHanding, + withdrawAll, + priorBalance, + journal); } // LCOV_EXCL_START catch (std::exception const& e) { - JLOG(j_.error()) << "AMMWithdraw::equalWithdrawTokens exception " - << e.what(); + JLOG(journal.error()) + << "AMMWithdraw::equalWithdrawTokens exception " << e.what(); } - return {tecINTERNAL, STAmount{}}; + return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}}; // LCOV_EXCL_STOP } @@ -689,6 +850,7 @@ AMMWithdraw::equalWithdrawTokens( std::pair AMMWithdraw::equalWithdrawLimit( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& amount2Balance, @@ -700,8 +862,10 @@ AMMWithdraw::equalWithdrawLimit( auto frac = Number{amount} / amountBalance; auto const amount2Withdraw = amount2Balance * frac; if (amount2Withdraw <= amount2) + { return withdraw( view, + ammSle, ammAccount, amountBalance, amount, @@ -709,11 +873,14 @@ AMMWithdraw::equalWithdrawLimit( lptAMMBalance, toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), tfee); + } + frac = Number{amount2} / amount2Balance; auto const amountWithdraw = amountBalance * frac; assert(amountWithdraw <= amount); return withdraw( view, + ammSle, ammAccount, amountBalance, toSTAmount(amount.issue(), amountWithdraw), @@ -731,6 +898,7 @@ AMMWithdraw::equalWithdrawLimit( std::pair AMMWithdraw::singleWithdraw( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, @@ -740,8 +908,10 @@ AMMWithdraw::singleWithdraw( auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee); if (tokens == beast::zero) return {tecAMM_FAILED, STAmount{}}; + return withdraw( view, + ammSle, ammAccount, amountBalance, amount, @@ -764,6 +934,7 @@ AMMWithdraw::singleWithdraw( std::pair AMMWithdraw::singleWithdrawTokens( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, @@ -774,8 +945,10 @@ AMMWithdraw::singleWithdrawTokens( auto const amountWithdraw = withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee); if (amount == beast::zero || amountWithdraw >= amount) + { return withdraw( view, + ammSle, ammAccount, amountBalance, amountWithdraw, @@ -783,6 +956,8 @@ AMMWithdraw::singleWithdrawTokens( lptAMMBalance, lpTokensWithdraw, tfee); + } + return {tecAMM_FAILED, STAmount{}}; } @@ -808,6 +983,7 @@ AMMWithdraw::singleWithdrawTokens( std::pair AMMWithdraw::singleWithdrawEPrice( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, @@ -833,8 +1009,10 @@ AMMWithdraw::singleWithdrawEPrice( return {tecAMM_FAILED, STAmount{}}; auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice); if (amount == beast::zero || amountWithdraw >= amount) + { return withdraw( view, + ammSle, ammAccount, amountBalance, amountWithdraw, @@ -842,8 +1020,16 @@ AMMWithdraw::singleWithdrawEPrice( lptAMMBalance, toSTAmount(lptAMMBalance.issue(), tokens), tfee); + } return {tecAMM_FAILED, STAmount{}}; } +WithdrawAll +AMMWithdraw::isWithdrawAll(STTx const& tx) +{ + if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)) + return WithdrawAll::Yes; + return WithdrawAll::No; +} } // namespace ripple diff --git a/src/ripple/app/tx/impl/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h similarity index 70% rename from src/ripple/app/tx/impl/AMMWithdraw.h rename to src/xrpld/app/tx/detail/AMMWithdraw.h index 40686266315..ae9328cb05e 100644 --- a/src/ripple/app/tx/impl/AMMWithdraw.h +++ b/src/xrpld/app/tx/detail/AMMWithdraw.h @@ -20,7 +20,8 @@ #ifndef RIPPLE_TX_AMMWITHDRAW_H_INCLUDED #define RIPPLE_TX_AMMWITHDRAW_H_INCLUDED -#include +#include +#include namespace ripple { @@ -62,6 +63,9 @@ class Sandbox; * @see [XLS30d:AMMWithdraw * transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78) */ + +enum class WithdrawAll : bool { No = false, Yes }; + class AMMWithdraw : public Transactor { public: @@ -80,6 +84,80 @@ class AMMWithdraw : public Transactor TER doApply() override; + /** Equal-asset withdrawal (LPTokens) of some AMM instance pools + * shares represented by the number of LPTokens . + * The trading fee is not charged. + * @param view + * @param ammAccount + * @param amountBalance current LP asset1 balance + * @param amount2Balance current LP asset2 balance + * @param lptAMMBalance current AMM LPT balance + * @param lpTokens current LPT balance + * @param lpTokensWithdraw amount of tokens to withdraw + * @param tfee trading fee in basis points + * @param withdrawAll if withdrawing all lptokens + * @param priorBalance balance before fees + * @return + */ + static std::tuple> + equalWithdrawTokens( + Sandbox& view, + SLE const& ammSle, + AccountID const account, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + STAmount const& lpTokensWithdraw, + std::uint16_t tfee, + FreezeHandling freezeHanding, + WithdrawAll withdrawAll, + XRPAmount const& priorBalance, + beast::Journal const& journal); + + /** Withdraw requested assets and token from AMM into LP account. + * Return new total LPToken balance and the withdrawn amounts for both + * assets. + * @param view + * @param ammSle AMM ledger entry + * @param ammAccount AMM account + * @param amountBalance current LP asset1 balance + * @param amountWithdraw asset1 withdraw amount + * @param amount2Withdraw asset2 withdraw amount + * @param lpTokensAMMBalance current AMM LPT balance + * @param lpTokensWithdraw amount of lptokens to withdraw + * @param tfee trading fee in basis points + * @param withdrawAll if withdraw all lptokens + * @param priorBalance balance before fees + * @return + */ + static std::tuple> + withdraw( + Sandbox& view, + SLE const& ammSle, + AccountID const& ammAccount, + AccountID const& account, + STAmount const& amountBalance, + STAmount const& amountWithdraw, + std::optional const& amount2Withdraw, + STAmount const& lpTokensAMMBalance, + STAmount const& lpTokensWithdraw, + std::uint16_t tfee, + FreezeHandling freezeHandling, + WithdrawAll withdrawAll, + XRPAmount const& priorBalance, + beast::Journal const& journal); + + static std::pair + deleteAMMAccountIfEmpty( + Sandbox& sb, + std::shared_ptr const ammSle, + STAmount const& lpTokenBalance, + Issue const& issue1, + Issue const& issue2, + beast::Journal const& journal); + private: std::pair applyGuts(Sandbox& view); @@ -87,21 +165,22 @@ class AMMWithdraw : public Transactor /** Withdraw requested assets and token from AMM into LP account. * Return new total LPToken balance. * @param view - * @param ammAccount - * @param amountBalance - * @param amountWithdraw - * @param amount2Withdraw + * @param ammSle AMM ledger entry + * @param ammAccount AMM account + * @param amountBalance current LP asset1 balance + * @param amountWithdraw asset1 withdraw amount + * @param amount2Withdraw asset2 withdraw amount * @param lpTokensAMMBalance current AMM LPT balance - * @param lpTokensWithdraw - * @param tfee + * @param lpTokensWithdraw amount of lptokens to withdraw * @return */ std::pair withdraw( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, - STAmount const& amountWithdraw, STAmount const& amountBalance, + STAmount const& amountWithdraw, std::optional const& amount2Withdraw, STAmount const& lpTokensAMMBalance, STAmount const& lpTokensWithdraw, @@ -123,6 +202,7 @@ class AMMWithdraw : public Transactor std::pair equalWithdrawTokens( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& amount2Balance, @@ -147,6 +227,7 @@ class AMMWithdraw : public Transactor std::pair equalWithdrawLimit( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& amount2Balance, @@ -168,6 +249,7 @@ class AMMWithdraw : public Transactor std::pair singleWithdraw( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, @@ -188,6 +270,7 @@ class AMMWithdraw : public Transactor std::pair singleWithdrawTokens( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, @@ -209,12 +292,17 @@ class AMMWithdraw : public Transactor std::pair singleWithdrawEPrice( Sandbox& view, + SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, STAmount const& lptAMMBalance, STAmount const& amount, STAmount const& ePrice, std::uint16_t tfee); + + /** Check from the flags if it's withdraw all */ + WithdrawAll + isWithdrawAll(STTx const& tx); }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp similarity index 94% rename from src/ripple/app/tx/impl/ApplyContext.cpp rename to src/xrpld/app/tx/detail/ApplyContext.cpp index 27287241637..969af7960eb 100644 --- a/src/ripple/app/tx/impl/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/tx/impl/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h similarity index 93% rename from src/ripple/app/tx/impl/ApplyContext.h rename to src/xrpld/app/tx/detail/ApplyContext.h index 415054ef4bb..45de05a73db 100644 --- a/src/ripple/app/tx/impl/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TX_APPLYCONTEXT_H_INCLUDED #define RIPPLE_TX_APPLYCONTEXT_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/tx/impl/BookTip.cpp b/src/xrpld/app/tx/detail/BookTip.cpp similarity index 97% rename from src/ripple/app/tx/impl/BookTip.cpp rename to src/xrpld/app/tx/detail/BookTip.cpp index 88bfd1d516c..011d992dd72 100644 --- a/src/ripple/app/tx/impl/BookTip.cpp +++ b/src/xrpld/app/tx/detail/BookTip.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/BookTip.h b/src/xrpld/app/tx/detail/BookTip.h similarity index 95% rename from src/ripple/app/tx/impl/BookTip.h rename to src/xrpld/app/tx/detail/BookTip.h index d57700ad4f8..4de6b785b90 100644 --- a/src/ripple/app/tx/impl/BookTip.h +++ b/src/xrpld/app/tx/detail/BookTip.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_BOOK_BOOKTIP_H_INCLUDED #define RIPPLE_APP_BOOK_BOOKTIP_H_INCLUDED -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/app/tx/impl/CancelCheck.cpp b/src/xrpld/app/tx/detail/CancelCheck.cpp similarity index 92% rename from src/ripple/app/tx/impl/CancelCheck.cpp rename to src/xrpld/app/tx/detail/CancelCheck.cpp index 5e7050d86a3..7954e86cf3b 100644 --- a/src/ripple/app/tx/impl/CancelCheck.cpp +++ b/src/xrpld/app/tx/detail/CancelCheck.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/CancelCheck.h b/src/xrpld/app/tx/detail/CancelCheck.h similarity index 95% rename from src/ripple/app/tx/impl/CancelCheck.h rename to src/xrpld/app/tx/detail/CancelCheck.h index a7e0f6e5d93..d9b70f919c1 100644 --- a/src/ripple/app/tx/impl/CancelCheck.h +++ b/src/xrpld/app/tx/detail/CancelCheck.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_CANCELCHECK_H_INCLUDED #define RIPPLE_TX_CANCELCHECK_H_INCLUDED -#include +#include namespace ripple { @@ -43,6 +43,8 @@ class CancelCheck : public Transactor doApply() override; }; +using CheckCancel = CancelCheck; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/CancelOffer.cpp b/src/xrpld/app/tx/detail/CancelOffer.cpp similarity index 87% rename from src/ripple/app/tx/impl/CancelOffer.cpp rename to src/xrpld/app/tx/detail/CancelOffer.cpp index 95d51501cb9..30e955a8282 100644 --- a/src/ripple/app/tx/impl/CancelOffer.cpp +++ b/src/xrpld/app/tx/detail/CancelOffer.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -34,8 +34,8 @@ CancelOffer::preflight(PreflightContext const& ctx) if (uTxFlags & tfUniversalMask) { - JLOG(ctx.j.trace()) << "Malformed transaction: " - << "Invalid flags set."; + JLOG(ctx.j.trace()) + << "Malformed transaction: " << "Invalid flags set."; return temINVALID_FLAG; } @@ -62,8 +62,8 @@ CancelOffer::preclaim(PreclaimContext const& ctx) if ((*sle)[sfSequence] <= offerSequence) { - JLOG(ctx.j.trace()) << "Malformed transaction: " - << "Sequence " << offerSequence << " is invalid."; + JLOG(ctx.j.trace()) << "Malformed transaction: " << "Sequence " + << offerSequence << " is invalid."; return temBAD_SEQUENCE; } diff --git a/src/ripple/app/tx/impl/CancelOffer.h b/src/xrpld/app/tx/detail/CancelOffer.h similarity index 89% rename from src/ripple/app/tx/impl/CancelOffer.h rename to src/xrpld/app/tx/detail/CancelOffer.h index 32aee335375..0942e61282b 100644 --- a/src/ripple/app/tx/impl/CancelOffer.h +++ b/src/xrpld/app/tx/detail/CancelOffer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_CANCELOFFER_H_INCLUDED #define RIPPLE_TX_CANCELOFFER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -46,6 +46,8 @@ class CancelOffer : public Transactor doApply() override; }; +using OfferCancel = CancelOffer; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp similarity index 98% rename from src/ripple/app/tx/impl/CashCheck.cpp rename to src/xrpld/app/tx/detail/CashCheck.cpp index bc3d838540b..8b5ef79b6d4 100644 --- a/src/ripple/app/tx/impl/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/tx/impl/CashCheck.h b/src/xrpld/app/tx/detail/CashCheck.h similarity index 95% rename from src/ripple/app/tx/impl/CashCheck.h rename to src/xrpld/app/tx/detail/CashCheck.h index 3acc3204a3d..da7ef22e3c3 100644 --- a/src/ripple/app/tx/impl/CashCheck.h +++ b/src/xrpld/app/tx/detail/CashCheck.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_CASHCHECK_H_INCLUDED #define RIPPLE_TX_CASHCHECK_H_INCLUDED -#include +#include namespace ripple { @@ -43,6 +43,8 @@ class CashCheck : public Transactor doApply() override; }; +using CheckCash = CashCheck; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp similarity index 96% rename from src/ripple/app/tx/impl/Change.cpp rename to src/xrpld/app/tx/detail/Change.cpp index b36ae88a75e..909f35fc799 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -300,11 +300,11 @@ Change::applyAmendment() if (gotMajority) { // This amendment now has a majority - newMajorities.push_back(STObject(sfMajority)); + newMajorities.push_back(STObject::makeInnerObject(sfMajority)); auto& entry = newMajorities.back(); - entry.emplace_back(STUInt256(sfAmendment, amendment)); - entry.emplace_back(STUInt32( - sfCloseTime, view().parentCloseTime().time_since_epoch().count())); + entry[sfAmendment] = amendment; + entry[sfCloseTime] = + view().parentCloseTime().time_since_epoch().count(); if (!ctx_.app.getAmendmentTable().isSupported(amendment)) { diff --git a/src/ripple/app/tx/impl/Change.h b/src/xrpld/app/tx/detail/Change.h similarity index 85% rename from src/ripple/app/tx/impl/Change.h rename to src/xrpld/app/tx/detail/Change.h index f366a5754ce..b0780a75c04 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/xrpld/app/tx/detail/Change.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TX_CHANGE_H_INCLUDED #define RIPPLE_TX_CHANGE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -69,6 +69,10 @@ class Change : public Transactor applyUNLModify(); }; +using EnableAmendment = Change; +using SetFee = Change; +using UNLModify = Change; + } // namespace ripple #endif diff --git a/src/xrpld/app/tx/detail/Clawback.cpp b/src/xrpld/app/tx/detail/Clawback.cpp new file mode 100644 index 00000000000..f1040790a42 --- /dev/null +++ b/src/xrpld/app/tx/detail/Clawback.cpp @@ -0,0 +1,290 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +template +static NotTEC +preflightHelper(PreflightContext const& ctx); + +template <> +NotTEC +preflightHelper(PreflightContext const& ctx) +{ + if (ctx.tx.isFieldPresent(sfHolder)) + return temMALFORMED; + + AccountID const issuer = ctx.tx[sfAccount]; + STAmount const clawAmount = ctx.tx[sfAmount]; + + // The issuer field is used for the token holder instead + AccountID const& holder = clawAmount.getIssuer(); + + if (issuer == holder || isXRP(clawAmount) || clawAmount <= beast::zero) + return temBAD_AMOUNT; + + return tesSUCCESS; +} + +template <> +NotTEC +preflightHelper(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + auto const mptHolder = ctx.tx[~sfHolder]; + auto const clawAmount = ctx.tx[sfAmount]; + + if (!mptHolder) + return temMALFORMED; + + // issuer is the same as holder + if (ctx.tx[sfAccount] == *mptHolder) + return temMALFORMED; + + if (clawAmount.mpt() > MPTAmount{maxMPTokenAmount} || + clawAmount <= beast::zero) + return temBAD_AMOUNT; + + return tesSUCCESS; +} + +NotTEC +Clawback::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureClawback)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfClawbackMask) + return temINVALID_FLAG; + + if (auto const ret = std::visit( + [&](T const&) { return preflightHelper(ctx); }, + ctx.tx[sfAmount].asset().value()); + !isTesSuccess(ret)) + return ret; + + return preflight2(ctx); +} + +template +static TER +preclaimHelper( + PreclaimContext const& ctx, + SLE const& sleIssuer, + AccountID const& issuer, + AccountID const& holder, + STAmount const& clawAmount); + +template <> +TER +preclaimHelper( + PreclaimContext const& ctx, + SLE const& sleIssuer, + AccountID const& issuer, + AccountID const& holder, + STAmount const& clawAmount) +{ + std::uint32_t const issuerFlagsIn = sleIssuer.getFieldU32(sfFlags); + + // If AllowTrustLineClawback is not set or NoFreeze is set, return no + // permission + if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || + (issuerFlagsIn & lsfNoFreeze)) + return tecNO_PERMISSION; + + auto const sleRippleState = + ctx.view.read(keylet::line(holder, issuer, clawAmount.getCurrency())); + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + // If balance is positive, issuer must have higher address than holder + if (balance > beast::zero && issuer < holder) + return tecNO_PERMISSION; + + // If balance is negative, issuer must have lower address than holder + if (balance < beast::zero && issuer > holder) + return tecNO_PERMISSION; + + // At this point, we know that issuer and holder accounts + // are correct and a trustline exists between them. + // + // Must now explicitly check the balance to make sure + // available balance is non-zero. + // + // We can't directly check the balance of trustline because + // the available balance of a trustline is prone to new changes (eg. + // XLS-34). So we must use `accountHolds`. + if (accountHolds( + ctx.view, + holder, + clawAmount.getCurrency(), + issuer, + fhIGNORE_FREEZE, + ctx.j) <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +template <> +TER +preclaimHelper( + PreclaimContext const& ctx, + SLE const& sleIssuer, + AccountID const& issuer, + AccountID const& holder, + STAmount const& clawAmount) +{ + auto const issuanceKey = + keylet::mptIssuance(clawAmount.get().getMptID()); + auto const sleIssuance = ctx.view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + if (!((*sleIssuance)[sfFlags] & lsfMPTCanClawback)) + return tecNO_PERMISSION; + + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; + + if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder))) + return tecOBJECT_NOT_FOUND; + + if (accountHolds( + ctx.view, + holder, + clawAmount.get(), + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + ctx.j) <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +TER +Clawback::preclaim(PreclaimContext const& ctx) +{ + AccountID const issuer = ctx.tx[sfAccount]; + auto const clawAmount = ctx.tx[sfAmount]; + AccountID const holder = + clawAmount.holds() ? clawAmount.getIssuer() : ctx.tx[sfHolder]; + + auto const sleIssuer = ctx.view.read(keylet::account(issuer)); + auto const sleHolder = ctx.view.read(keylet::account(holder)); + if (!sleIssuer || !sleHolder) + return terNO_ACCOUNT; + + if (sleHolder->isFieldPresent(sfAMMID)) + return tecAMM_ACCOUNT; + + return std::visit( + [&](T const&) { + return preclaimHelper( + ctx, *sleIssuer, issuer, holder, clawAmount); + }, + ctx.tx[sfAmount].asset().value()); +} + +template +static TER +applyHelper(ApplyContext& ctx); + +template <> +TER +applyHelper(ApplyContext& ctx) +{ + AccountID const issuer = ctx.tx[sfAccount]; + STAmount clawAmount = ctx.tx[sfAmount]; + AccountID const holder = clawAmount.getIssuer(); // cannot be reference + + // Replace the `issuer` field with issuer's account + clawAmount.setIssuer(issuer); + if (holder == issuer) + return tecINTERNAL; + + // Get the spendable balance. Must use `accountHolds`. + STAmount const spendableAmount = accountHolds( + ctx.view(), + holder, + clawAmount.getCurrency(), + clawAmount.getIssuer(), + fhIGNORE_FREEZE, + ctx.journal); + + return rippleCredit( + ctx.view(), + holder, + issuer, + std::min(spendableAmount, clawAmount), + true, + ctx.journal); +} + +template <> +TER +applyHelper(ApplyContext& ctx) +{ + AccountID const issuer = ctx.tx[sfAccount]; + auto clawAmount = ctx.tx[sfAmount]; + AccountID const holder = ctx.tx[sfHolder]; + + // Get the spendable balance. Must use `accountHolds`. + STAmount const spendableAmount = accountHolds( + ctx.view(), + holder, + clawAmount.get(), + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + ctx.journal); + + return rippleCredit( + ctx.view(), + holder, + issuer, + std::min(spendableAmount, clawAmount), + /*checkIssuer*/ false, + ctx.journal); +} + +TER +Clawback::doApply() +{ + return std::visit( + [&](T const&) { return applyHelper(ctx_); }, + ctx_.tx[sfAmount].asset().value()); +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/Clawback.h b/src/xrpld/app/tx/detail/Clawback.h similarity index 97% rename from src/ripple/app/tx/impl/Clawback.h rename to src/xrpld/app/tx/detail/Clawback.h index c5f072c8463..d908a2e4ef2 100644 --- a/src/ripple/app/tx/impl/Clawback.h +++ b/src/xrpld/app/tx/detail/Clawback.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_CLAWBACK_H_INCLUDED #define RIPPLE_TX_CLAWBACK_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp similarity index 96% rename from src/ripple/app/tx/impl/CreateCheck.cpp rename to src/xrpld/app/tx/detail/CreateCheck.cpp index 77ce4d017a1..3a278eed738 100644 --- a/src/ripple/app/tx/impl/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/CreateCheck.h b/src/xrpld/app/tx/detail/CreateCheck.h similarity index 95% rename from src/ripple/app/tx/impl/CreateCheck.h rename to src/xrpld/app/tx/detail/CreateCheck.h index 537b230eb46..0e414ce0123 100644 --- a/src/ripple/app/tx/impl/CreateCheck.h +++ b/src/xrpld/app/tx/detail/CreateCheck.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_CREATECHECK_H_INCLUDED #define RIPPLE_TX_CREATECHECK_H_INCLUDED -#include +#include namespace ripple { @@ -43,6 +43,8 @@ class CreateCheck : public Transactor doApply() override; }; +using CheckCreate = CreateCheck; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp similarity index 99% rename from src/ripple/app/tx/impl/CreateOffer.cpp rename to src/xrpld/app/tx/detail/CreateOffer.cpp index 17f7e2853db..2a5145594a1 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/CreateOffer.h b/src/xrpld/app/tx/detail/CreateOffer.h similarity index 96% rename from src/ripple/app/tx/impl/CreateOffer.h rename to src/xrpld/app/tx/detail/CreateOffer.h index 597ee22c0d9..234267804c9 100644 --- a/src/ripple/app/tx/impl/CreateOffer.h +++ b/src/xrpld/app/tx/detail/CreateOffer.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED #define RIPPLE_TX_CREATEOFFER_H_INCLUDED -#include -#include -#include +#include +#include +#include #include namespace ripple { @@ -142,6 +142,8 @@ class CreateOffer : public Transactor OfferStream::StepCounter stepCounter_; }; +using OfferCreate = CreateOffer; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/CreateTicket.cpp b/src/xrpld/app/tx/detail/CreateTicket.cpp similarity index 95% rename from src/ripple/app/tx/impl/CreateTicket.cpp rename to src/xrpld/app/tx/detail/CreateTicket.cpp index a901231822c..b04f4af1d30 100644 --- a/src/ripple/app/tx/impl/CreateTicket.cpp +++ b/src/xrpld/app/tx/detail/CreateTicket.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/CreateTicket.h b/src/xrpld/app/tx/detail/CreateTicket.h similarity index 94% rename from src/ripple/app/tx/impl/CreateTicket.h rename to src/xrpld/app/tx/detail/CreateTicket.h index c1909ddf724..099dcde9347 100644 --- a/src/ripple/app/tx/impl/CreateTicket.h +++ b/src/xrpld/app/tx/detail/CreateTicket.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_CREATETICKET_H_INCLUDED #define RIPPLE_TX_CREATETICKET_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -84,6 +84,8 @@ class CreateTicket : public Transactor doApply() override; }; +using TicketCreate = CreateTicket; + } // namespace ripple #endif diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp new file mode 100644 index 00000000000..4da875f8d7c --- /dev/null +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -0,0 +1,382 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ripple { + +/* + Credentials + ====== + + A verifiable credentials (VC + https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C + specification (https://www.w3.org/TR/vc-data-model-2.0/), is a + secure and tamper-evident way to represent information about a subject, such + as an individual, organization, or even an IoT device. These credentials are + issued by a trusted entity and can be verified by third parties without + directly involving the issuer at all. +*/ + +using namespace credentials; + +// ------- CREATE -------------------------- + +NotTEC +CredentialCreate::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureCredentials)) + { + JLOG(ctx.j.trace()) << "featureCredentials is disabled."; + return temDISABLED; + } + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto const& tx = ctx.tx; + auto& j = ctx.j; + + if (!tx[sfSubject]) + { + JLOG(j.trace()) << "Malformed transaction: Invalid Subject"; + return temMALFORMED; + } + + auto const uri = tx[~sfURI]; + if (uri && (uri->empty() || (uri->size() > maxCredentialURILength))) + { + JLOG(j.trace()) << "Malformed transaction: invalid size of URI."; + return temMALFORMED; + } + + auto const credType = tx[sfCredentialType]; + if (credType.empty() || (credType.size() > maxCredentialTypeLength)) + { + JLOG(j.trace()) + << "Malformed transaction: invalid size of CredentialType."; + return temMALFORMED; + } + + return preflight2(ctx); +} + +TER +CredentialCreate::preclaim(PreclaimContext const& ctx) +{ + auto const credType(ctx.tx[sfCredentialType]); + auto const subject = ctx.tx[sfSubject]; + + if (!ctx.view.exists(keylet::account(subject))) + { + JLOG(ctx.j.trace()) << "Subject doesn't exist."; + return tecNO_TARGET; + } + + if (ctx.view.exists( + keylet::credential(subject, ctx.tx[sfAccount], credType))) + { + JLOG(ctx.j.trace()) << "Credential already exists."; + return tecDUPLICATE; + } + + return tesSUCCESS; +} + +TER +CredentialCreate::doApply() +{ + auto const subject = ctx_.tx[sfSubject]; + auto const credType(ctx_.tx[sfCredentialType]); + Keylet const credentialKey = + keylet::credential(subject, account_, credType); + + auto const sleCred = std::make_shared(credentialKey); + if (!sleCred) + return tefINTERNAL; + + auto const optExp = ctx_.tx[~sfExpiration]; + if (optExp) + { + std::uint32_t const closeTime = + ctx_.view().info().parentCloseTime.time_since_epoch().count(); + + if (closeTime > *optExp) + { + JLOG(j_.trace()) << "Malformed transaction: " + "Expiration time is in the past."; + return tecEXPIRED; + } + + sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration)); + } + + auto const sleIssuer = view().peek(keylet::account(account_)); + if (!sleIssuer) + return tefINTERNAL; + + { + STAmount const reserve{view().fees().accountReserve( + sleIssuer->getFieldU32(sfOwnerCount) + 1)}; + if (mPriorBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + sleCred->setAccountID(sfSubject, subject); + sleCred->setAccountID(sfIssuer, account_); + sleCred->setFieldVL(sfCredentialType, credType); + + if (ctx_.tx.isFieldPresent(sfURI)) + sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI)); + + { + auto const page = view().dirInsert( + keylet::ownerDir(account_), + credentialKey, + describeOwnerDir(account_)); + JLOG(j_.trace()) << "Adding Credential to owner directory " + << to_string(credentialKey.key) << ": " + << (page ? "success" : "failure"); + if (!page) + return tecDIR_FULL; + sleCred->setFieldU64(sfIssuerNode, *page); + + adjustOwnerCount(view(), sleIssuer, 1, j_); + } + + if (subject == account_) + { + sleCred->setFieldU32(sfFlags, lsfAccepted); + } + else + { + auto const page = view().dirInsert( + keylet::ownerDir(subject), + credentialKey, + describeOwnerDir(subject)); + JLOG(j_.trace()) << "Adding Credential to owner directory " + << to_string(credentialKey.key) << ": " + << (page ? "success" : "failure"); + if (!page) + return tecDIR_FULL; + sleCred->setFieldU64(sfSubjectNode, *page); + view().update(view().peek(keylet::account(subject))); + } + + view().insert(sleCred); + + return tesSUCCESS; +} + +// ------- DELETE -------------------------- +NotTEC +CredentialDelete::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureCredentials)) + { + JLOG(ctx.j.trace()) << "featureCredentials is disabled."; + return temDISABLED; + } + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto const subject = ctx.tx[~sfSubject]; + auto const issuer = ctx.tx[~sfIssuer]; + + if (!subject && !issuer) + { + // Neither field is present, the transaction is malformed. + JLOG(ctx.j.trace()) << "Malformed transaction: " + "No Subject or Issuer fields."; + return temMALFORMED; + } + + // Make sure that the passed account is valid. + if ((subject && subject->isZero()) || (issuer && issuer->isZero())) + { + JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer " + "field zeroed."; + return temINVALID_ACCOUNT_ID; + } + + auto const credType = ctx.tx[sfCredentialType]; + if (credType.empty() || (credType.size() > maxCredentialTypeLength)) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: invalid size of CredentialType."; + return temMALFORMED; + } + + return preflight2(ctx); +} + +TER +CredentialDelete::preclaim(PreclaimContext const& ctx) +{ + AccountID const account{ctx.tx[sfAccount]}; + auto const subject = ctx.tx[~sfSubject].value_or(account); + auto const issuer = ctx.tx[~sfIssuer].value_or(account); + auto const credType(ctx.tx[sfCredentialType]); + + if (!ctx.view.exists(keylet::credential(subject, issuer, credType))) + return tecNO_ENTRY; + + return tesSUCCESS; +} + +TER +CredentialDelete::doApply() +{ + auto const subject = ctx_.tx[~sfSubject].value_or(account_); + auto const issuer = ctx_.tx[~sfIssuer].value_or(account_); + + auto const credType(ctx_.tx[sfCredentialType]); + auto const sleCred = + view().peek(keylet::credential(subject, issuer, credType)); + if (!sleCred) + return tefINTERNAL; + + if ((subject != account_) && (issuer != account_) && + !checkExpired(sleCred, ctx_.view().info().parentCloseTime)) + { + JLOG(j_.trace()) << "Can't delete non-expired credential."; + return tecNO_PERMISSION; + } + + return deleteSLE(view(), sleCred, j_); +} + +// ------- APPLY -------------------------- + +NotTEC +CredentialAccept::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureCredentials)) + { + JLOG(ctx.j.trace()) << "featureCredentials is disabled."; + return temDISABLED; + } + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (!ctx.tx[sfIssuer]) + { + JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed."; + return temINVALID_ACCOUNT_ID; + } + + auto const credType = ctx.tx[sfCredentialType]; + if (credType.empty() || (credType.size() > maxCredentialTypeLength)) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: invalid size of CredentialType."; + return temMALFORMED; + } + + return preflight2(ctx); +} + +TER +CredentialAccept::preclaim(PreclaimContext const& ctx) +{ + AccountID const subject = ctx.tx[sfAccount]; + AccountID const issuer = ctx.tx[sfIssuer]; + auto const credType(ctx.tx[sfCredentialType]); + + if (!ctx.view.exists(keylet::account(issuer))) + { + JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer); + return tecNO_ISSUER; + } + + auto const sleCred = + ctx.view.read(keylet::credential(subject, issuer, credType)); + if (!sleCred) + { + JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", " + << to_string(issuer) << ", " << credType; + return tecNO_ENTRY; + } + + if (sleCred->getFieldU32(sfFlags) & lsfAccepted) + { + JLOG(ctx.j.warn()) << "Credential already accepted: " + << to_string(subject) << ", " << to_string(issuer) + << ", " << credType; + return tecDUPLICATE; + } + + return tesSUCCESS; +} + +TER +CredentialAccept::doApply() +{ + AccountID const issuer{ctx_.tx[sfIssuer]}; + + // Both exist as credential object exist itself (checked in preclaim) + auto const sleSubject = view().peek(keylet::account(account_)); + auto const sleIssuer = view().peek(keylet::account(issuer)); + + if (!sleSubject || !sleIssuer) + return tefINTERNAL; + + { + STAmount const reserve{view().fees().accountReserve( + sleSubject->getFieldU32(sfOwnerCount) + 1)}; + if (mPriorBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + auto const credType(ctx_.tx[sfCredentialType]); + Keylet const credentialKey = keylet::credential(account_, issuer, credType); + auto const sleCred = view().peek(credentialKey); // Checked in preclaim() + + if (checkExpired(sleCred, view().info().parentCloseTime)) + { + JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText(); + // delete expired credentials even if the transaction failed + auto const err = credentials::deleteSLE(view(), sleCred, j_); + return isTesSuccess(err) ? tecEXPIRED : err; + } + + sleCred->setFieldU32(sfFlags, lsfAccepted); + view().update(sleCred); + + adjustOwnerCount(view(), sleIssuer, -1, j_); + adjustOwnerCount(view(), sleSubject, 1, j_); + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/Credentials.h b/src/xrpld/app/tx/detail/Credentials.h new file mode 100644 index 00000000000..7e7522d82c1 --- /dev/null +++ b/src/xrpld/app/tx/detail/Credentials.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +namespace ripple { + +class CredentialCreate : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit CredentialCreate(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +class CredentialDelete : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit CredentialDelete(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +class CredentialAccept : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit CredentialAccept(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/DID.cpp b/src/xrpld/app/tx/detail/DID.cpp similarity index 95% rename from src/ripple/app/tx/impl/DID.cpp rename to src/xrpld/app/tx/detail/DID.cpp index 1cad8992ad9..6e5a3108c72 100644 --- a/src/ripple/app/tx/impl/DID.cpp +++ b/src/xrpld/app/tx/detail/DID.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/DID.h b/src/xrpld/app/tx/detail/DID.h similarity index 97% rename from src/ripple/app/tx/impl/DID.h rename to src/xrpld/app/tx/detail/DID.h index 13d5a261542..54ae6630f2e 100644 --- a/src/ripple/app/tx/impl/DID.h +++ b/src/xrpld/app/tx/detail/DID.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_DID_H_INCLUDED #define RIPPLE_TX_DID_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp similarity index 83% rename from src/ripple/app/tx/impl/DeleteAccount.cpp rename to src/xrpld/app/tx/detail/DeleteAccount.cpp index efa38a4b74e..a7f33a3d8dd 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -17,21 +17,22 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -41,6 +42,10 @@ DeleteAccount::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureDeletableAccounts)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfCredentialIDs) && + !ctx.rules.enabled(featureCredentials)) + return temDISABLED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -51,6 +56,9 @@ DeleteAccount::preflight(PreflightContext const& ctx) // An account cannot be deleted and give itself the resulting XRP. return temDST_IS_SRC; + if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + return err; + return preflight2(ctx); } @@ -110,14 +118,14 @@ removeTicketFromLedger( TER removeDepositPreauthFromLedger( - Application& app, + Application&, ApplyView& view, - AccountID const& account, + AccountID const&, uint256 const& delIndex, - std::shared_ptr const& sleDel, + std::shared_ptr const&, beast::Journal j) { - return DepositPreauth::removeFromLedger(app, view, delIndex, j); + return DepositPreauth::removeFromLedger(view, delIndex, j); } TER @@ -159,6 +167,18 @@ removeOracleFromLedger( return DeleteOracle::deleteOracle(view, sleDel, account, j); } +TER +removeCredentialFromLedger( + Application&, + ApplyView& view, + AccountID const&, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return credentials::deleteSLE(view, sleDel, j); +} + // Return nullptr if the LedgerEntryType represents an obligation that can't // be deleted. Otherwise return the pointer to the function that can delete // the non-obligation @@ -181,6 +201,8 @@ nonObligationDeleter(LedgerEntryType t) return removeDIDFromLedger; case ltORACLE: return removeOracleFromLedger; + case ltCREDENTIAL: + return removeCredentialFromLedger; default: return nullptr; } @@ -202,12 +224,21 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag]) return tecDST_TAG_NEEDED; - // Check whether the destination account requires deposit authorization. - if (ctx.view.rules().enabled(featureDepositAuth) && - (sleDst->getFlags() & lsfDepositAuth)) + // If credentials are provided - check them anyway + if (auto const err = credentials::valid(ctx, account); !isTesSuccess(err)) + return err; + + // if credentials then postpone auth check to doApply, to check for expired + // credentials + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) { - if (!ctx.view.exists(keylet::depositPreauth(dst, account))) - return tecNO_PERMISSION; + // Check whether the destination account requires deposit authorization. + if (ctx.view.rules().enabled(featureDepositAuth) && + (sleDst->getFlags() & lsfDepositAuth)) + { + if (!ctx.view.exists(keylet::depositPreauth(dst, account))) + return tecNO_PERMISSION; + } } auto sleAccount = ctx.view.read(keylet::account(account)); @@ -316,12 +347,21 @@ DeleteAccount::doApply() auto src = view().peek(keylet::account(account_)); assert(src); - auto dst = view().peek(keylet::account(ctx_.tx[sfDestination])); + auto const dstID = ctx_.tx[sfDestination]; + auto dst = view().peek(keylet::account(dstID)); assert(dst); if (!src || !dst) return tefBAD_LEDGER; + if (ctx_.view().rules().enabled(featureDepositAuth) && + ctx_.tx.isFieldPresent(sfCredentialIDs)) + { + if (auto err = verifyDepositPreauth(ctx_, account_, dstID, dst); + !isTesSuccess(err)) + return err; + } + Keylet const ownerDirKeylet{keylet::ownerDir(account_)}; auto const ter = cleanupOnAccountDelete( view(), diff --git a/src/ripple/app/tx/impl/DeleteAccount.h b/src/xrpld/app/tx/detail/DeleteAccount.h similarity index 91% rename from src/ripple/app/tx/impl/DeleteAccount.h rename to src/xrpld/app/tx/detail/DeleteAccount.h index 0f298bb8596..5f88a9ba718 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.h +++ b/src/xrpld/app/tx/detail/DeleteAccount.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TX_DELETEACCOUNT_H_INCLUDED #define RIPPLE_TX_DELETEACCOUNT_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { @@ -48,6 +48,8 @@ class DeleteAccount : public Transactor doApply() override; }; +using AccountDelete = DeleteAccount; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/DeleteOracle.cpp b/src/xrpld/app/tx/detail/DeleteOracle.cpp similarity index 93% rename from src/ripple/app/tx/impl/DeleteOracle.cpp rename to src/xrpld/app/tx/detail/DeleteOracle.cpp index 9331daee97d..807e52ee6b4 100644 --- a/src/ripple/app/tx/impl/DeleteOracle.cpp +++ b/src/xrpld/app/tx/detail/DeleteOracle.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/DeleteOracle.h b/src/xrpld/app/tx/detail/DeleteOracle.h similarity index 96% rename from src/ripple/app/tx/impl/DeleteOracle.h rename to src/xrpld/app/tx/detail/DeleteOracle.h index e578adaaaf0..bbbfc6f5256 100644 --- a/src/ripple/app/tx/impl/DeleteOracle.h +++ b/src/xrpld/app/tx/detail/DeleteOracle.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_DELETEORACLE_H_INCLUDED #define RIPPLE_TX_DELETEORACLE_H_INCLUDED -#include +#include namespace ripple { @@ -59,6 +59,8 @@ class DeleteOracle : public Transactor beast::Journal j); }; +using OracleDelete = DeleteOracle; + } // namespace ripple #endif // RIPPLE_TX_DELETEORACLE_H_INCLUDED diff --git a/src/xrpld/app/tx/detail/DepositPreauth.cpp b/src/xrpld/app/tx/detail/DepositPreauth.cpp new file mode 100644 index 00000000000..73cd19e4120 --- /dev/null +++ b/src/xrpld/app/tx/detail/DepositPreauth.cpp @@ -0,0 +1,355 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2018 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { + +NotTEC +DepositPreauth::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureDepositPreauth)) + return temDISABLED; + + bool const authArrPresent = ctx.tx.isFieldPresent(sfAuthorizeCredentials); + bool const unauthArrPresent = + ctx.tx.isFieldPresent(sfUnauthorizeCredentials); + int const authCredPresent = + static_cast(authArrPresent) + static_cast(unauthArrPresent); + + if (authCredPresent && !ctx.rules.enabled(featureCredentials)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto& tx = ctx.tx; + + if (tx.getFlags() & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "Malformed transaction: Invalid flags set."; + return temINVALID_FLAG; + } + + auto const optAuth = ctx.tx[~sfAuthorize]; + auto const optUnauth = ctx.tx[~sfUnauthorize]; + int const authPresent = static_cast(optAuth.has_value()) + + static_cast(optUnauth.has_value()); + + if (authPresent + authCredPresent != 1) + { + // There can only be 1 field out of 4 or the transaction is malformed. + JLOG(ctx.j.trace()) + << "Malformed transaction: " + "Invalid Authorize and Unauthorize field combination."; + return temMALFORMED; + } + + if (authPresent) + { + // Make sure that the passed account is valid. + AccountID const& target(optAuth ? *optAuth : *optUnauth); + if (!target) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: Authorized or Unauthorized " + "field zeroed."; + return temINVALID_ACCOUNT_ID; + } + + // An account may not preauthorize itself. + if (optAuth && (target == ctx.tx[sfAccount])) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: Attempting to DepositPreauth self."; + return temCANNOT_PREAUTH_SELF; + } + } + else + { + STArray const& arr(ctx.tx.getFieldArray( + authArrPresent ? sfAuthorizeCredentials + : sfUnauthorizeCredentials)); + if (arr.empty() || (arr.size() > maxCredentialsArraySize)) + { + JLOG(ctx.j.trace()) << "Malformed transaction: " + "Invalid AuthorizeCredentials size: " + << arr.size(); + return temMALFORMED; + } + + std::unordered_set duplicates; + for (auto const& o : arr) + { + auto const& issuer(o[sfIssuer]); + if (!issuer) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: " + "AuthorizeCredentials Issuer account is invalid."; + return temINVALID_ACCOUNT_ID; + } + + auto const ct = o[sfCredentialType]; + if (ct.empty() || (ct.size() > maxCredentialTypeLength)) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: invalid size of CredentialType."; + return temMALFORMED; + } + + auto [it, ins] = duplicates.insert(sha512Half(issuer, ct)); + if (!ins) + { + JLOG(ctx.j.trace()) + << "Malformed transaction: duplicates in credentials."; + return temMALFORMED; + } + } + } + + return preflight2(ctx); +} + +TER +DepositPreauth::preclaim(PreclaimContext const& ctx) +{ + AccountID const account(ctx.tx[sfAccount]); + + // Determine which operation we're performing: authorizing or unauthorizing. + if (ctx.tx.isFieldPresent(sfAuthorize)) + { + // Verify that the Authorize account is present in the ledger. + AccountID const auth{ctx.tx[sfAuthorize]}; + if (!ctx.view.exists(keylet::account(auth))) + return tecNO_TARGET; + + // Verify that the Preauth entry they asked to add is not already + // in the ledger. + if (ctx.view.exists(keylet::depositPreauth(account, auth))) + return tecDUPLICATE; + } + else if (ctx.tx.isFieldPresent(sfUnauthorize)) + { + // Verify that the Preauth entry they asked to remove is in the ledger. + if (!ctx.view.exists( + keylet::depositPreauth(account, ctx.tx[sfUnauthorize]))) + return tecNO_ENTRY; + } + else if (ctx.tx.isFieldPresent(sfAuthorizeCredentials)) + { + STArray const& authCred(ctx.tx.getFieldArray(sfAuthorizeCredentials)); + std::set> sorted; + for (auto const& o : authCred) + { + auto const& issuer = o[sfIssuer]; + if (!ctx.view.exists(keylet::account(issuer))) + return tecNO_ISSUER; + auto [it, ins] = sorted.emplace(issuer, o[sfCredentialType]); + if (!ins) + return tefINTERNAL; + } + + // Verify that the Preauth entry they asked to add is not already + // in the ledger. + if (ctx.view.exists(keylet::depositPreauth(account, sorted))) + return tecDUPLICATE; + } + else if (ctx.tx.isFieldPresent(sfUnauthorizeCredentials)) + { + // Verify that the Preauth entry is in the ledger. + if (!ctx.view.exists(keylet::depositPreauth( + account, + credentials::makeSorted( + ctx.tx.getFieldArray(sfUnauthorizeCredentials))))) + return tecNO_ENTRY; + } + return tesSUCCESS; +} + +TER +DepositPreauth::doApply() +{ + if (ctx_.tx.isFieldPresent(sfAuthorize)) + { + auto const sleOwner = view().peek(keylet::account(account_)); + if (!sleOwner) + return {tefINTERNAL}; + + // A preauth counts against the reserve of the issuing account, but we + // check the starting balance because we want to allow dipping into the + // reserve to pay fees. + { + STAmount const reserve{view().fees().accountReserve( + sleOwner->getFieldU32(sfOwnerCount) + 1)}; + + if (mPriorBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + // Preclaim already verified that the Preauth entry does not yet exist. + // Create and populate the Preauth entry. + AccountID const auth{ctx_.tx[sfAuthorize]}; + Keylet const preauthKeylet = keylet::depositPreauth(account_, auth); + auto slePreauth = std::make_shared(preauthKeylet); + + slePreauth->setAccountID(sfAccount, account_); + slePreauth->setAccountID(sfAuthorize, auth); + view().insert(slePreauth); + + auto const page = view().dirInsert( + keylet::ownerDir(account_), + preauthKeylet, + describeOwnerDir(account_)); + + JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " + << to_string(preauthKeylet.key) << ": " + << (page ? "success" : "failure"); + + if (!page) + return tecDIR_FULL; + + slePreauth->setFieldU64(sfOwnerNode, *page); + + // If we succeeded, the new entry counts against the creator's reserve. + adjustOwnerCount(view(), sleOwner, 1, j_); + } + else if (ctx_.tx.isFieldPresent(sfUnauthorize)) + { + auto const preauth = + keylet::depositPreauth(account_, ctx_.tx[sfUnauthorize]); + + return DepositPreauth::removeFromLedger(view(), preauth.key, j_); + } + else if (ctx_.tx.isFieldPresent(sfAuthorizeCredentials)) + { + auto const sleOwner = view().peek(keylet::account(account_)); + if (!sleOwner) + return tefINTERNAL; + + // A preauth counts against the reserve of the issuing account, but we + // check the starting balance because we want to allow dipping into the + // reserve to pay fees. + { + STAmount const reserve{view().fees().accountReserve( + sleOwner->getFieldU32(sfOwnerCount) + 1)}; + + if (mPriorBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + // Preclaim already verified that the Preauth entry does not yet exist. + // Create and populate the Preauth entry. + + auto const sortedTX = credentials::makeSorted( + ctx_.tx.getFieldArray(sfAuthorizeCredentials)); + STArray sortedLE(sfAuthorizeCredentials, sortedTX.size()); + for (auto const& p : sortedTX) + { + auto cred = STObject::makeInnerObject(sfCredential); + cred.setAccountID(sfIssuer, p.first); + cred.setFieldVL(sfCredentialType, p.second); + sortedLE.push_back(std::move(cred)); + } + + Keylet const preauthKey = keylet::depositPreauth(account_, sortedTX); + auto slePreauth = std::make_shared(preauthKey); + if (!slePreauth) + return tefINTERNAL; + + slePreauth->setAccountID(sfAccount, account_); + slePreauth->peekFieldArray(sfAuthorizeCredentials) = + std::move(sortedLE); + + view().insert(slePreauth); + + auto const page = view().dirInsert( + keylet::ownerDir(account_), preauthKey, describeOwnerDir(account_)); + + JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " + << to_string(preauthKey.key) << ": " + << (page ? "success" : "failure"); + + if (!page) + return tecDIR_FULL; + + slePreauth->setFieldU64(sfOwnerNode, *page); + + // If we succeeded, the new entry counts against the creator's reserve. + adjustOwnerCount(view(), sleOwner, 1, j_); + } + else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials)) + { + auto const preauthKey = keylet::depositPreauth( + account_, + credentials::makeSorted( + ctx_.tx.getFieldArray(sfUnauthorizeCredentials))); + return DepositPreauth::removeFromLedger(view(), preauthKey.key, j_); + } + + return tesSUCCESS; +} + +TER +DepositPreauth::removeFromLedger( + ApplyView& view, + uint256 const& preauthIndex, + beast::Journal j) +{ + // Existence already checked in preclaim and DeleteAccount + auto const slePreauth{view.peek(keylet::depositPreauth(preauthIndex))}; + if (!slePreauth) + { + JLOG(j.warn()) << "Selected DepositPreauth does not exist."; + return tecNO_ENTRY; + } + + AccountID const account{(*slePreauth)[sfAccount]}; + std::uint64_t const page{(*slePreauth)[sfOwnerNode]}; + if (!view.dirRemove(keylet::ownerDir(account), page, preauthIndex, false)) + { + JLOG(j.fatal()) << "Unable to delete DepositPreauth from owner."; + return tefBAD_LEDGER; + } + + // If we succeeded, update the DepositPreauth owner's reserve. + auto const sleOwner = view.peek(keylet::account(account)); + if (!sleOwner) + return tefINTERNAL; + + adjustOwnerCount(view, sleOwner, -1, j); + + // Remove DepositPreauth from ledger. + view.erase(slePreauth); + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/DepositPreauth.h b/src/xrpld/app/tx/detail/DepositPreauth.h similarity index 96% rename from src/ripple/app/tx/impl/DepositPreauth.h rename to src/xrpld/app/tx/detail/DepositPreauth.h index 8111c215602..76a7c080737 100644 --- a/src/ripple/app/tx/impl/DepositPreauth.h +++ b/src/xrpld/app/tx/detail/DepositPreauth.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_DEPOSIT_PREAUTH_H_INCLUDED #define RIPPLE_TX_DEPOSIT_PREAUTH_H_INCLUDED -#include +#include namespace ripple { @@ -45,7 +45,6 @@ class DepositPreauth : public Transactor // Interface used by DeleteAccount static TER removeFromLedger( - Application& app, ApplyView& view, uint256 const& delIndex, beast::Journal j); diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp similarity index 93% rename from src/ripple/app/tx/impl/Escrow.cpp rename to src/xrpld/app/tx/detail/Escrow.cpp index 9d8aa6a2289..f98e72f23dd 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -17,22 +17,23 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // During an EscrowFinish, the transaction must specify both // a condition and a fulfillment. We track whether that @@ -309,6 +310,10 @@ EscrowFinish::preflight(PreflightContext const& ctx) if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; + if (ctx.tx.isFieldPresent(sfCredentialIDs) && + !ctx.rules.enabled(featureCredentials)) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -347,6 +352,9 @@ EscrowFinish::preflight(PreflightContext const& ctx) } } + if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + return err; + return tesSUCCESS; } @@ -363,6 +371,19 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) return Transactor::calculateBaseFee(view, tx) + extraFee; } +TER +EscrowFinish::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureCredentials)) + return Transactor::preclaim(ctx); + + if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + !isTesSuccess(err)) + return err; + + return tesSUCCESS; +} + TER EscrowFinish::doApply() { @@ -456,19 +477,9 @@ EscrowFinish::doApply() if (ctx_.view().rules().enabled(featureDepositAuth)) { - // Is EscrowFinished authorized? - if (sled->getFlags() & lsfDepositAuth) - { - // A destination account that requires authorization has two - // ways to get an EscrowFinished into the account: - // 1. If Account == Destination, or - // 2. If Account is deposit preauthorized by destination. - if (account_ != destID) - { - if (!view().exists(keylet::depositPreauth(destID, account_))) - return tecNO_PERMISSION; - } - } + if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled); + !isTesSuccess(err)) + return err; } AccountID const account = (*slep)[sfAccount]; diff --git a/src/ripple/app/tx/impl/Escrow.h b/src/xrpld/app/tx/detail/Escrow.h similarity index 96% rename from src/ripple/app/tx/impl/Escrow.h rename to src/xrpld/app/tx/detail/Escrow.h index 0f5fd940144..78acdbee00c 100644 --- a/src/ripple/app/tx/impl/Escrow.h +++ b/src/xrpld/app/tx/detail/Escrow.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_ESCROW_H_INCLUDED #define RIPPLE_TX_ESCROW_H_INCLUDED -#include +#include namespace ripple { @@ -63,6 +63,9 @@ class EscrowFinish : public Transactor static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx); + static TER + preclaim(PreclaimContext const& ctx); + TER doApply() override; }; diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp similarity index 64% rename from src/ripple/app/tx/impl/InvariantCheck.cpp rename to src/xrpld/app/tx/detail/InvariantCheck.cpp index c0ef5bbf0c5..90fc399b344 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -342,11 +342,12 @@ AccountRootsNotDeleted::finalize( return false; } - // A successful AMMWithdraw MAY delete one account root + // A successful AMMWithdraw/AMMClawback MAY delete one account root // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw // deletes the AMM account, accountsDeleted_ is set if it is deleted. - if (tx.getTxnType() == ttAMM_WITHDRAW && result == tesSUCCESS && - accountsDeleted_ == 1) + if ((tx.getTxnType() == ttAMM_WITHDRAW || + tx.getTxnType() == ttAMM_CLAWBACK) && + result == tesSUCCESS && accountsDeleted_ == 1) return true; if (accountsDeleted_ == 0) @@ -358,6 +359,91 @@ AccountRootsNotDeleted::finalize( //------------------------------------------------------------------------------ +void +AccountRootsDeletedClean::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const&) +{ + if (isDelete && before && before->getType() == ltACCOUNT_ROOT) + accountsDeleted_.emplace_back(before); +} + +bool +AccountRootsDeletedClean::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Always check for objects in the ledger, but to prevent differing + // transaction processing results, however unlikely, only fail if the + // feature is enabled. Enabled, or not, though, a fatal-level message will + // be logged + bool const enforce = view.rules().enabled(featureInvariantsV1_1); + + auto const objectExists = [&view, enforce, &j](auto const& keylet) { + if (auto const sle = view.read(keylet)) + { + // Finding the object is bad + auto const typeName = [&sle]() { + auto item = + LedgerFormats::getInstance().findByType(sle->getType()); + + if (item != nullptr) + return item->getName(); + return std::to_string(sle->getType()); + }(); + + JLOG(j.fatal()) + << "Invariant failed: account deletion left behind a " + << typeName << " object"; + (void)enforce; + assert(enforce); + return true; + } + return false; + }; + + for (auto const& accountSLE : accountsDeleted_) + { + auto const accountID = accountSLE->getAccountID(sfAccount); + // Simple types + for (auto const& [keyletfunc, _, __] : directAccountKeylets) + { + if (objectExists(std::invoke(keyletfunc, accountID)) && enforce) + return false; + } + + { + // NFT pages. ntfpage_min and nftpage_max were already explicitly + // checked above as entries in directAccountKeylets. This uses + // view.succ() to check for any NFT pages in between the two + // endpoints. + Keylet const first = keylet::nftpage_min(accountID); + Keylet const last = keylet::nftpage_max(accountID); + + std::optional key = view.succ(first.key, last.key.next()); + + // current page + if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce) + return false; + } + + // Keys directly stored in the AccountRoot object + if (auto const ammKey = accountSLE->at(~sfAMMID)) + { + if (objectExists(keylet::amm(*ammKey)) && enforce) + return false; + } + } + + return true; +} + +//------------------------------------------------------------------------------ + void LedgerEntryTypesMatch::visitEntry( bool, @@ -393,6 +479,9 @@ LedgerEntryTypesMatch::visitEntry( case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: case ltDID: case ltORACLE: + case ltMPTOKEN_ISSUANCE: + case ltMPTOKEN: + case ltCREDENTIAL: break; default: invalidTypeAdded_ = true; @@ -527,6 +616,10 @@ ValidNFTokenPage::visitEntry( static constexpr uint256 const& pageBits = nft::pageMask; static constexpr uint256 const accountBits = ~pageBits; + if ((before && before->getType() != ltNFTOKEN_PAGE) || + (after && after->getType() != ltNFTOKEN_PAGE)) + return; + auto check = [this, isDelete](std::shared_ptr const& sle) { uint256 const account = sle->key() & accountBits; uint256 const hiLimit = sle->key() & pageBits; @@ -588,11 +681,37 @@ ValidNFTokenPage::visitEntry( } }; - if (before && before->getType() == ltNFTOKEN_PAGE) + if (before) + { check(before); - if (after && after->getType() == ltNFTOKEN_PAGE) + // While an account's NFToken directory contains any NFTokens, the last + // NFTokenPage (with 96 bits of 1 in the low part of the index) should + // never be deleted. + if (isDelete && (before->key() & nft::pageMask) == nft::pageMask && + before->isFieldPresent(sfPreviousPageMin)) + { + deletedFinalPage_ = true; + } + } + + if (after) check(after); + + if (!isDelete && before && after) + { + // If the NFTokenPage + // 1. Has a NextMinPage field in before, but loses it in after, and + // 2. This is not the last page in the directory + // Then we have identified a corruption in the links between the + // NFToken pages in the NFToken directory. + if ((before->key() & nft::pageMask) != nft::pageMask && + before->isFieldPresent(sfNextPageMin) && + !after->isFieldPresent(sfNextPageMin)) + { + deletedLink_ = true; + } + } } bool @@ -633,6 +752,21 @@ ValidNFTokenPage::finalize( return false; } + if (view.rules().enabled(fixNFTokenPageLinks)) + { + if (deletedFinalPage_) + { + JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with " + "non-empty directory."; + return false; + } + if (deletedLink_) + { + JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link."; + return false; + } + } + return true; } @@ -752,6 +886,9 @@ ValidClawback::visitEntry( { if (before && before->getType() == ltRIPPLE_STATE) trustlinesChanged++; + + if (before && before->getType() == ltMPTOKEN) + mptokensChanged++; } bool @@ -774,18 +911,28 @@ ValidClawback::finalize( return false; } - AccountID const issuer = tx.getAccountID(sfAccount); - STAmount const amount = tx.getFieldAmount(sfAmount); - AccountID const& holder = amount.getIssuer(); - STAmount const holderBalance = accountHolds( - view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); - - if (holderBalance.signum() < 0) + if (mptokensChanged > 1) { JLOG(j.fatal()) - << "Invariant failed: trustline balance is negative"; + << "Invariant failed: more than one mptokens changed."; return false; } + + if (trustlinesChanged == 1) + { + AccountID const issuer = tx.getAccountID(sfAccount); + STAmount const& amount = tx.getFieldAmount(sfAmount); + AccountID const& holder = amount.getIssuer(); + STAmount const holderBalance = accountHolds( + view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); + + if (holderBalance.signum() < 0) + { + JLOG(j.fatal()) + << "Invariant failed: trustline balance is negative"; + return false; + } + } } else { @@ -795,9 +942,182 @@ ValidClawback::finalize( "despite failure of the transaction."; return false; } + + if (mptokensChanged != 0) + { + JLOG(j.fatal()) << "Invariant failed: some mptokens were changed " + "despite failure of the transaction."; + return false; + } } return true; } +//------------------------------------------------------------------------------ + +void +ValidMPTIssuance::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltMPTOKEN_ISSUANCE) + { + if (isDelete) + mptIssuancesDeleted_++; + else if (!before) + mptIssuancesCreated_++; + } + + if (after && after->getType() == ltMPTOKEN) + { + if (isDelete) + mptokensDeleted_++; + else if (!before) + mptokensCreated_++; + } +} + +bool +ValidMPTIssuance::finalize( + STTx const& tx, + TER const result, + XRPAmount const _fee, + ReadView const& _view, + beast::Journal const& j) +{ + if (result == tesSUCCESS) + { + if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE) + { + if (mptIssuancesCreated_ == 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance creation " + "succeeded without creating a MPT issuance"; + } + else if (mptIssuancesDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance creation " + "succeeded while removing MPT issuances"; + } + else if (mptIssuancesCreated_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance creation " + "succeeded but created multiple issuances"; + } + + return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0; + } + + if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY) + { + if (mptIssuancesDeleted_ == 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded without removing a MPT issuance"; + } + else if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded while creating MPT issuances"; + } + else if (mptIssuancesDeleted_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded but deleted multiple issuances"; + } + + return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; + } + + if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE) + { + bool const submittedByIssuer = tx.isFieldPresent(sfHolder); + + if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but created MPT issuances"; + return false; + } + else if (mptIssuancesDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but deleted issuances"; + return false; + } + else if ( + submittedByIssuer && + (mptokensCreated_ > 0 || mptokensDeleted_ > 0)) + { + JLOG(j.fatal()) + << "Invariant failed: MPT authorize submitted by issuer " + "succeeded but created/deleted mptokens"; + return false; + } + else if ( + !submittedByIssuer && + (mptokensCreated_ + mptokensDeleted_ != 1)) + { + // if the holder submitted this tx, then a mptoken must be + // either created or deleted. + JLOG(j.fatal()) + << "Invariant failed: MPT authorize submitted by holder " + "succeeded but created/deleted bad number of mptokens"; + return false; + } + + return true; + } + + if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET) + { + if (mptIssuancesDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance set " + "succeeded while removing MPT issuances"; + } + else if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance set " + "succeeded while creating MPT issuances"; + } + else if (mptokensDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance set " + "succeeded while removing MPTokens"; + } + else if (mptokensCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance set " + "succeeded while creating MPTokens"; + } + + return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && + mptokensCreated_ == 0 && mptokensDeleted_ == 0; + } + } + + if (mptIssuancesCreated_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created"; + } + else if (mptIssuancesDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted"; + } + else if (mptokensCreated_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPToken was created"; + } + else if (mptokensDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted"; + } + + return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && + mptokensCreated_ == 0 && mptokensDeleted_ == 0; +} + } // namespace ripple diff --git a/src/ripple/app/tx/impl/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h similarity index 87% rename from src/ripple/app/tx/impl/InvariantCheck.h rename to src/xrpld/app/tx/detail/InvariantCheck.h index eb606c2ed3b..23ec8005556 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_TX_INVARIANTCHECK_H_INCLUDED #define RIPPLE_APP_TX_INVARIANTCHECK_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -164,6 +164,36 @@ class AccountRootsNotDeleted beast::Journal const&); }; +/** + * @brief Invariant: a deleted account must not have any objects left + * + * We iterate all deleted account roots, and ensure that there are no + * objects left that are directly accessible with that account's ID. + * + * There should only be one deleted account, but that's checked by + * AccountRootsNotDeleted. This invariant will handle multiple deleted account + * roots without a problem. + */ +class AccountRootsDeletedClean +{ + std::vector> accountsDeleted_; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + /** * @brief Invariant: An account XRP balance must be in XRP and take a value * between 0 and INITIAL_XRP drops, inclusive. @@ -337,6 +367,8 @@ class ValidNFTokenPage bool badSort_ = false; bool badURI_ = false; bool invalidSize_ = false; + bool deletedFinalPage_ = false; + bool deletedLink_ = false; public: void @@ -401,6 +433,31 @@ class NFTokenCountTracking class ValidClawback { std::uint32_t trustlinesChanged = 0; + std::uint32_t mptokensChanged = 0; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + +class ValidMPTIssuance +{ + std::uint32_t mptIssuancesCreated_ = 0; + std::uint32_t mptIssuancesDeleted_ = 0; + + std::uint32_t mptokensCreated_ = 0; + std::uint32_t mptokensDeleted_ = 0; public: void @@ -423,6 +480,7 @@ class ValidClawback using InvariantChecks = std::tuple< TransactionFeeCheck, AccountRootsNotDeleted, + AccountRootsDeletedClean, LedgerEntryTypesMatch, XRPBalanceChecks, XRPNotCreated, @@ -432,7 +490,8 @@ using InvariantChecks = std::tuple< ValidNewAccountRoot, ValidNFTokenPage, NFTokenCountTracking, - ValidClawback>; + ValidClawback, + ValidMPTIssuance>; /** * @brief get a tuple of all invariant checks diff --git a/src/xrpld/app/tx/detail/LedgerStateFix.cpp b/src/xrpld/app/tx/detail/LedgerStateFix.cpp new file mode 100644 index 00000000000..568ed49304a --- /dev/null +++ b/src/xrpld/app/tx/detail/LedgerStateFix.cpp @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +NotTEC +LedgerStateFix::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(fixNFTokenPageLinks)) + return temDISABLED; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + switch (ctx.tx[sfLedgerFixType]) + { + case FixType::nfTokenPageLink: + if (!ctx.tx.isFieldPresent(sfOwner)) + return temINVALID; + break; + + default: + return tefINVALID_LEDGER_FIX_TYPE; + } + + return preflight2(ctx); +} + +XRPAmount +LedgerStateFix::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // The fee required for LedgerStateFix is one owner reserve, just like + // the fee for AccountDelete. + return view.fees().increment; +} + +TER +LedgerStateFix::preclaim(PreclaimContext const& ctx) +{ + switch (ctx.tx[sfLedgerFixType]) + { + case FixType::nfTokenPageLink: { + AccountID const owner{ctx.tx[sfOwner]}; + if (!ctx.view.read(keylet::account(owner))) + return tecOBJECT_NOT_FOUND; + + return tesSUCCESS; + } + } + + // preflight is supposed to verify that only valid FixTypes get to preclaim. + return tecINTERNAL; +} + +TER +LedgerStateFix::doApply() +{ + switch (ctx_.tx[sfLedgerFixType]) + { + case FixType::nfTokenPageLink: + if (!nft::repairNFTokenDirectoryLinks(view(), ctx_.tx[sfOwner])) + return tecFAILED_PROCESSING; + + return tesSUCCESS; + } + + // preflight is supposed to verify that only valid FixTypes get to doApply. + return tecINTERNAL; +} + +} // namespace ripple diff --git a/src/ripple/peerfinder/sim/Params.h b/src/xrpld/app/tx/detail/LedgerStateFix.h similarity index 59% rename from src/ripple/peerfinder/sim/Params.h rename to src/xrpld/app/tx/detail/LedgerStateFix.h index c3c288bb985..b480d239291 100644 --- a/src/ripple/peerfinder/sim/Params.h +++ b/src/xrpld/app/tx/detail/LedgerStateFix.h @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + Copyright (c) 2024 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,29 +17,41 @@ */ //============================================================================== -#ifndef RIPPLE_PEERFINDER_SIM_PARAMS_H_INCLUDED -#define RIPPLE_PEERFINDER_SIM_PARAMS_H_INCLUDED +#ifndef RIPPLE_TX_LEDGER_STATE_FIX_H_INCLUDED +#define RIPPLE_TX_LEDGER_STATE_FIX_H_INCLUDED + +#include +#include +#include namespace ripple { -namespace PeerFinder { -namespace Sim { -/** Defines the parameters for a network simulation. */ -struct Params +class LedgerStateFix : public Transactor { - Params() : steps(50), nodes(10), maxPeers(20), outPeers(9.5), firewalled(0) +public: + enum FixType : std::uint16_t { + nfTokenPageLink = 1, + }; + + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit LedgerStateFix(ApplyContext& ctx) : Transactor(ctx) { } - int steps; - int nodes; - int maxPeers; - double outPeers; - double firewalled; // [0, 1) + static NotTEC + preflight(PreflightContext const& ctx); + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; }; -} // namespace Sim -} // namespace PeerFinder } // namespace ripple #endif diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp new file mode 100644 index 00000000000..8042c9c6982 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp @@ -0,0 +1,267 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { + +NotTEC +MPTokenAuthorize::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfMPTokenAuthorizeMask) + return temINVALID_FLAG; + + if (ctx.tx[sfAccount] == ctx.tx[~sfHolder]) + return temMALFORMED; + + return preflight2(ctx); +} + +TER +MPTokenAuthorize::preclaim(PreclaimContext const& ctx) +{ + auto const accountID = ctx.tx[sfAccount]; + auto const holderID = ctx.tx[~sfHolder]; + + // if non-issuer account submits this tx, then they are trying either: + // 1. Unauthorize/delete MPToken + // 2. Use/create MPToken + // + // Note: `accountID` is holder's account + // `holderID` is NOT used + if (!holderID) + { + std::shared_ptr sleMpt = ctx.view.read( + keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID)); + + // There is an edge case where all holders have zero balance, issuance + // is legally destroyed, then outstanding MPT(s) are deleted afterwards. + // Thus, there is no need to check for the existence of the issuance if + // the MPT is being deleted with a zero balance. Check for unauthorize + // before fetching the MPTIssuance object. + + // if holder wants to delete/unauthorize a mpt + if (ctx.tx.getFlags() & tfMPTUnauthorize) + { + if (!sleMpt) + return tecOBJECT_NOT_FOUND; + + if ((*sleMpt)[sfMPTAmount] != 0) + { + auto const sleMptIssuance = ctx.view.read( + keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + if (!sleMptIssuance) + return tefINTERNAL; + + return tecHAS_OBLIGATIONS; + } + + return tesSUCCESS; + } + + // Now test when the holder wants to hold/create/authorize a new MPT + auto const sleMptIssuance = + ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + + if (!sleMptIssuance) + return tecOBJECT_NOT_FOUND; + + if (accountID == (*sleMptIssuance)[sfIssuer]) + return tecNO_PERMISSION; + + // if holder wants to use and create a mpt + if (sleMpt) + return tecDUPLICATE; + + return tesSUCCESS; + } + + if (!ctx.view.exists(keylet::account(*holderID))) + return tecNO_DST; + + auto const sleMptIssuance = + ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + if (!sleMptIssuance) + return tecOBJECT_NOT_FOUND; + + std::uint32_t const mptIssuanceFlags = sleMptIssuance->getFieldU32(sfFlags); + + // If tx is submitted by issuer, they would either try to do the following + // for allowlisting: + // 1. authorize an account + // 2. unauthorize an account + // + // Note: `accountID` is issuer's account + // `holderID` is holder's account + if (accountID != (*sleMptIssuance)[sfIssuer]) + return tecNO_PERMISSION; + + // If tx is submitted by issuer, it only applies for MPT with + // lsfMPTRequireAuth set + if (!(mptIssuanceFlags & lsfMPTRequireAuth)) + return tecNO_AUTH; + + // The holder must create the MPT before the issuer can authorize it. + if (!ctx.view.exists( + keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID))) + return tecOBJECT_NOT_FOUND; + + return tesSUCCESS; +} + +TER +MPTokenAuthorize::authorize( + ApplyView& view, + beast::Journal journal, + MPTAuthorizeArgs const& args) +{ + auto const sleAcct = view.peek(keylet::account(args.account)); + if (!sleAcct) + return tecINTERNAL; + + // If the account that submitted the tx is a holder + // Note: `account_` is holder's account + // `holderID` is NOT used + if (!args.holderID) + { + // When a holder wants to unauthorize/delete a MPT, the ledger must + // - delete mptokenKey from owner directory + // - delete the MPToken + if (args.flags & tfMPTUnauthorize) + { + auto const mptokenKey = + keylet::mptoken(args.mptIssuanceID, args.account); + auto const sleMpt = view.peek(mptokenKey); + if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0) + return tecINTERNAL; + + if (!view.dirRemove( + keylet::ownerDir(args.account), + (*sleMpt)[sfOwnerNode], + sleMpt->key(), + false)) + return tecINTERNAL; + + adjustOwnerCount(view, sleAcct, -1, journal); + + view.erase(sleMpt); + return tesSUCCESS; + } + + // A potential holder wants to authorize/hold a mpt, the ledger must: + // - add the new mptokenKey to the owner directory + // - create the MPToken object for the holder + + // The reserve that is required to create the MPToken. Note + // that although the reserve increases with every item + // an account owns, in the case of MPTokens we only + // *enforce* a reserve if the user owns more than two + // items. This is similar to the reserve requirements of trust lines. + std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); + XRPAmount const reserveCreate( + (uOwnerCount < 2) ? XRPAmount(beast::zero) + : view.fees().accountReserve(uOwnerCount + 1)); + + if (args.priorBalance < reserveCreate) + return tecINSUFFICIENT_RESERVE; + + auto const mptokenKey = + keylet::mptoken(args.mptIssuanceID, args.account); + + auto const ownerNode = view.dirInsert( + keylet::ownerDir(args.account), + mptokenKey, + describeOwnerDir(args.account)); + + if (!ownerNode) + return tecDIR_FULL; + + auto mptoken = std::make_shared(mptokenKey); + (*mptoken)[sfAccount] = args.account; + (*mptoken)[sfMPTokenIssuanceID] = args.mptIssuanceID; + (*mptoken)[sfFlags] = 0; + (*mptoken)[sfOwnerNode] = *ownerNode; + view.insert(mptoken); + + // Update owner count. + adjustOwnerCount(view, sleAcct, 1, journal); + + return tesSUCCESS; + } + + auto const sleMptIssuance = + view.read(keylet::mptIssuance(args.mptIssuanceID)); + if (!sleMptIssuance) + return tecINTERNAL; + + // If the account that submitted this tx is the issuer of the MPT + // Note: `account_` is issuer's account + // `holderID` is holder's account + if (args.account != (*sleMptIssuance)[sfIssuer]) + return tecINTERNAL; + + auto const sleMpt = + view.peek(keylet::mptoken(args.mptIssuanceID, *args.holderID)); + if (!sleMpt) + return tecINTERNAL; + + std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags); + std::uint32_t flagsOut = flagsIn; + + // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on + // their MPToken + if (args.flags & tfMPTUnauthorize) + flagsOut &= ~lsfMPTAuthorized; + // Issuer wants to authorize a holder, set lsfMPTAuthorized on their + // MPToken + else + flagsOut |= lsfMPTAuthorized; + + if (flagsIn != flagsOut) + sleMpt->setFieldU32(sfFlags, flagsOut); + + view.update(sleMpt); + return tesSUCCESS; +} + +TER +MPTokenAuthorize::doApply() +{ + auto const& tx = ctx_.tx; + return authorize( + ctx_.view(), + ctx_.journal, + {.priorBalance = mPriorBalance, + .mptIssuanceID = tx[sfMPTokenIssuanceID], + .account = account_, + .flags = tx.getFlags(), + .holderID = tx[~sfHolder]}); +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.h b/src/xrpld/app/tx/detail/MPTokenAuthorize.h new file mode 100644 index 00000000000..79dc1734b5b --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.h @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_MPTOKENAUTHORIZE_H_INCLUDED +#define RIPPLE_TX_MPTOKENAUTHORIZE_H_INCLUDED + +#include + +namespace ripple { + +struct MPTAuthorizeArgs +{ + XRPAmount const& priorBalance; + uint192 const& mptIssuanceID; + AccountID const& account; + std::uint32_t flags; + std::optional holderID; +}; + +class MPTokenAuthorize : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit MPTokenAuthorize(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + static TER + authorize( + ApplyView& view, + beast::Journal journal, + MPTAuthorizeArgs const& args); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp new file mode 100644 index 00000000000..1297a918e1d --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -0,0 +1,142 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { + +NotTEC +MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfMPTokenIssuanceCreateMask) + return temINVALID_FLAG; + + if (auto const fee = ctx.tx[~sfTransferFee]) + { + if (fee > maxTransferFee) + return temBAD_TRANSFER_FEE; + + // If a non-zero TransferFee is set then the tfTransferable flag + // must also be set. + if (fee > 0u && !ctx.tx.isFlag(tfMPTCanTransfer)) + return temMALFORMED; + } + + if (auto const metadata = ctx.tx[~sfMPTokenMetadata]) + { + if (metadata->length() == 0 || + metadata->length() > maxMPTokenMetadataLength) + return temMALFORMED; + } + + // Check if maximumAmount is within unsigned 63 bit range + if (auto const maxAmt = ctx.tx[~sfMaximumAmount]) + { + if (maxAmt == 0) + return temMALFORMED; + + if (maxAmt > maxMPTokenAmount) + return temMALFORMED; + } + return preflight2(ctx); +} + +TER +MPTokenIssuanceCreate::create( + ApplyView& view, + beast::Journal journal, + MPTCreateArgs const& args) +{ + auto const acct = view.peek(keylet::account(args.account)); + if (!acct) + return tecINTERNAL; + + if (args.priorBalance < + view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) + return tecINSUFFICIENT_RESERVE; + + auto const mptIssuanceKeylet = + keylet::mptIssuance(args.sequence, args.account); + + // create the MPTokenIssuance + { + auto const ownerNode = view.dirInsert( + keylet::ownerDir(args.account), + mptIssuanceKeylet, + describeOwnerDir(args.account)); + + if (!ownerNode) + return tecDIR_FULL; + + auto mptIssuance = std::make_shared(mptIssuanceKeylet); + (*mptIssuance)[sfFlags] = args.flags & ~tfUniversal; + (*mptIssuance)[sfIssuer] = args.account; + (*mptIssuance)[sfOutstandingAmount] = 0; + (*mptIssuance)[sfOwnerNode] = *ownerNode; + (*mptIssuance)[sfSequence] = args.sequence; + + if (args.maxAmount) + (*mptIssuance)[sfMaximumAmount] = *args.maxAmount; + + if (args.assetScale) + (*mptIssuance)[sfAssetScale] = *args.assetScale; + + if (args.transferFee) + (*mptIssuance)[sfTransferFee] = *args.transferFee; + + if (args.metadata) + (*mptIssuance)[sfMPTokenMetadata] = *args.metadata; + + view.insert(mptIssuance); + } + + // Update owner count. + adjustOwnerCount(view, acct, 1, journal); + + return tesSUCCESS; +} + +TER +MPTokenIssuanceCreate::doApply() +{ + auto const& tx = ctx_.tx; + return create( + ctx_.view(), + ctx_.journal, + {.priorBalance = mPriorBalance, + .account = account_, + .sequence = tx.getSeqProxy().value(), + .flags = tx.getFlags(), + .maxAmount = tx[~sfMaximumAmount], + .assetScale = tx[~sfAssetScale], + .transferFee = tx[~sfTransferFee], + .metadata = tx[~sfMPTokenMetadata]}); +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h new file mode 100644 index 00000000000..1346c3e31d7 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_MPTOKENISSUANCECREATE_H_INCLUDED +#define RIPPLE_TX_MPTOKENISSUANCECREATE_H_INCLUDED + +#include + +namespace ripple { + +struct MPTCreateArgs +{ + XRPAmount const& priorBalance; + AccountID const& account; + std::uint32_t sequence; + std::uint32_t flags; + std::optional maxAmount; + std::optional assetScale; + std::optional transferFee; + std::optional const& metadata; +}; + +class MPTokenIssuanceCreate : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit MPTokenIssuanceCreate(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + TER + doApply() override; + + static TER + create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); +}; + +} // namespace ripple + +#endif diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp new file mode 100644 index 00000000000..a0f0b9d8602 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include +#include +#include +#include + +namespace ripple { + +NotTEC +MPTokenIssuanceDestroy::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + // check flags + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfMPTokenIssuanceDestroyMask) + return temINVALID_FLAG; + + return preflight2(ctx); +} + +TER +MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx) +{ + // ensure that issuance exists + auto const sleMPT = + ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + if (!sleMPT) + return tecOBJECT_NOT_FOUND; + + // ensure it is issued by the tx submitter + if ((*sleMPT)[sfIssuer] != ctx.tx[sfAccount]) + return tecNO_PERMISSION; + + // ensure it has no outstanding balances + if ((*sleMPT)[~sfOutstandingAmount] != 0) + return tecHAS_OBLIGATIONS; + + return tesSUCCESS; +} + +TER +MPTokenIssuanceDestroy::doApply() +{ + auto const mpt = + view().peek(keylet::mptIssuance(ctx_.tx[sfMPTokenIssuanceID])); + if (account_ != mpt->getAccountID(sfIssuer)) + return tecINTERNAL; + + if (!view().dirRemove( + keylet::ownerDir(account_), (*mpt)[sfOwnerNode], mpt->key(), false)) + return tefBAD_LEDGER; + + view().erase(mpt); + + adjustOwnerCount(view(), view().peek(keylet::account(account_)), -1, j_); + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.h b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.h new file mode 100644 index 00000000000..69abb99feb0 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.h @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_MPTOKENISSUANCEDESTROY_H_INCLUDED +#define RIPPLE_TX_MPTOKENISSUANCEDESTROY_H_INCLUDED + +#include + +namespace ripple { + +class MPTokenIssuanceDestroy : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit MPTokenIssuanceDestroy(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp new file mode 100644 index 00000000000..4e395c30be6 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include + +namespace ripple { + +NotTEC +MPTokenIssuanceSet::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto const txFlags = ctx.tx.getFlags(); + + // check flags + if (txFlags & tfMPTokenIssuanceSetMask) + return temINVALID_FLAG; + // fails if both flags are set + else if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock)) + return temINVALID_FLAG; + + auto const accountID = ctx.tx[sfAccount]; + auto const holderID = ctx.tx[~sfHolder]; + if (holderID && accountID == holderID) + return temMALFORMED; + + return preflight2(ctx); +} + +TER +MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) +{ + // ensure that issuance exists + auto const sleMptIssuance = + ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + if (!sleMptIssuance) + return tecOBJECT_NOT_FOUND; + + // if the mpt has disabled locking + if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock)) + return tecNO_PERMISSION; + + // ensure it is issued by the tx submitter + if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount]) + return tecNO_PERMISSION; + + if (auto const holderID = ctx.tx[~sfHolder]) + { + // make sure holder account exists + if (!ctx.view.exists(keylet::account(*holderID))) + return tecNO_DST; + + // the mptoken must exist + if (!ctx.view.exists( + keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID))) + return tecOBJECT_NOT_FOUND; + } + + return tesSUCCESS; +} + +TER +MPTokenIssuanceSet::doApply() +{ + auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; + auto const txFlags = ctx_.tx.getFlags(); + auto const holderID = ctx_.tx[~sfHolder]; + std::shared_ptr sle; + + if (holderID) + sle = view().peek(keylet::mptoken(mptIssuanceID, *holderID)); + else + sle = view().peek(keylet::mptIssuance(mptIssuanceID)); + + if (!sle) + return tecINTERNAL; + + std::uint32_t const flagsIn = sle->getFieldU32(sfFlags); + std::uint32_t flagsOut = flagsIn; + + if (txFlags & tfMPTLock) + flagsOut |= lsfMPTLocked; + else if (txFlags & tfMPTUnlock) + flagsOut &= ~lsfMPTLocked; + + if (flagsIn != flagsOut) + sle->setFieldU32(sfFlags, flagsOut); + + view().update(sle); + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.h b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.h new file mode 100644 index 00000000000..895be973120 --- /dev/null +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.h @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_MPTOKENISSUANCESET_H_INCLUDED +#define RIPPLE_TX_MPTOKENISSUANCESET_H_INCLUDED + +#include + +namespace ripple { + +class MPTokenIssuanceSet : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit MPTokenIssuanceSet(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp similarity index 93% rename from src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp rename to src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index 3b6aaf60ea1..b884a791e78 100644 --- a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -270,6 +270,28 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) } } + // Fix a bug where the transfer of an NFToken with a transfer fee could + // give the NFToken issuer an undesired trust line. + if (ctx.view.rules().enabled(fixEnforceNFTokenTrustline)) + { + std::shared_ptr const& offer = bo ? bo : so; + if (!offer) + // Should be caught in preflight. + return tecINTERNAL; + + uint256 const& tokenID = offer->at(sfNFTokenID); + STAmount const& amount = offer->at(sfAmount); + if (nft::getTransferFee(tokenID) != 0 && + (nft::getFlags(tokenID) & nft::flagCreateTrustLines) == 0 && + !amount.native()) + { + auto const issuer = nft::getIssuer(tokenID); + // Issuer doesn't need a trust line to accept their own currency. + if (issuer != amount.getIssuer() && + !ctx.view.read(keylet::line(issuer, amount.issue()))) + return tecNO_LINE; + } + } return tesSUCCESS; } diff --git a/src/ripple/app/tx/impl/NFTokenAcceptOffer.h b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h similarity index 97% rename from src/ripple/app/tx/impl/NFTokenAcceptOffer.h rename to src/xrpld/app/tx/detail/NFTokenAcceptOffer.h index e1b26cbecea..dff3febbb21 100644 --- a/src/ripple/app/tx/impl/NFTokenAcceptOffer.h +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_NFTOKENACCEPTOFFER_H_INCLUDED #define RIPPLE_TX_NFTOKENACCEPTOFFER_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/NFTokenBurn.cpp b/src/xrpld/app/tx/detail/NFTokenBurn.cpp similarity index 93% rename from src/ripple/app/tx/impl/NFTokenBurn.cpp rename to src/xrpld/app/tx/detail/NFTokenBurn.cpp index 99acfd61dca..725e35791f9 100644 --- a/src/ripple/app/tx/impl/NFTokenBurn.cpp +++ b/src/xrpld/app/tx/detail/NFTokenBurn.cpp @@ -17,14 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/tx/impl/NFTokenBurn.h b/src/xrpld/app/tx/detail/NFTokenBurn.h similarity index 97% rename from src/ripple/app/tx/impl/NFTokenBurn.h rename to src/xrpld/app/tx/detail/NFTokenBurn.h index 61079c4a49a..3f5296c217b 100644 --- a/src/ripple/app/tx/impl/NFTokenBurn.h +++ b/src/xrpld/app/tx/detail/NFTokenBurn.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_BURNNFT_H_INCLUDED #define RIPPLE_TX_BURNNFT_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/NFTokenCancelOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp similarity index 93% rename from src/ripple/app/tx/impl/NFTokenCancelOffer.cpp rename to src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp index 50199ace88b..ef66ceecd0c 100644 --- a/src/ripple/app/tx/impl/NFTokenCancelOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/tx/impl/NFTokenCancelOffer.h b/src/xrpld/app/tx/detail/NFTokenCancelOffer.h similarity index 97% rename from src/ripple/app/tx/impl/NFTokenCancelOffer.h rename to src/xrpld/app/tx/detail/NFTokenCancelOffer.h index 752d33ac818..d460675711e 100644 --- a/src/ripple/app/tx/impl/NFTokenCancelOffer.h +++ b/src/xrpld/app/tx/detail/NFTokenCancelOffer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_NFTOKENCANCELOFFER_H_INCLUDED #define RIPPLE_TX_NFTOKENCANCELOFFER_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp new file mode 100644 index 00000000000..43178d31b4a --- /dev/null +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2021 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include + +namespace ripple { + +NotTEC +NFTokenCreateOffer::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureNonFungibleTokensV1)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto const txFlags = ctx.tx.getFlags(); + + if (txFlags & tfNFTokenCreateOfferMask) + return temINVALID_FLAG; + + auto const nftFlags = nft::getFlags(ctx.tx[sfNFTokenID]); + + // Use implementation shared with NFTokenMint + if (NotTEC notTec = nft::tokenOfferCreatePreflight( + ctx.tx[sfAccount], + ctx.tx[sfAmount], + ctx.tx[~sfDestination], + ctx.tx[~sfExpiration], + nftFlags, + ctx.rules, + ctx.tx[~sfOwner], + txFlags); + !isTesSuccess(notTec)) + return notTec; + + return preflight2(ctx); +} + +TER +NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) +{ + if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) + return tecEXPIRED; + + uint256 const nftokenID = ctx.tx[sfNFTokenID]; + std::uint32_t const txFlags = {ctx.tx.getFlags()}; + + if (!nft::findToken( + ctx.view, + ctx.tx[(txFlags & tfSellNFToken) ? sfAccount : sfOwner], + nftokenID)) + return tecNO_ENTRY; + + // Use implementation shared with NFTokenMint + return nft::tokenOfferCreatePreclaim( + ctx.view, + ctx.tx[sfAccount], + nft::getIssuer(nftokenID), + ctx.tx[sfAmount], + ctx.tx[~sfDestination], + nft::getFlags(nftokenID), + nft::getTransferFee(nftokenID), + ctx.j, + ctx.tx[~sfOwner], + txFlags); +} + +TER +NFTokenCreateOffer::doApply() +{ + // Use implementation shared with NFTokenMint + return nft::tokenOfferCreateApply( + view(), + ctx_.tx[sfAccount], + ctx_.tx[sfAmount], + ctx_.tx[~sfDestination], + ctx_.tx[~sfExpiration], + ctx_.tx.getSeqProxy(), + ctx_.tx[sfNFTokenID], + mPriorBalance, + j_, + ctx_.tx.getFlags()); +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/NFTokenCreateOffer.h b/src/xrpld/app/tx/detail/NFTokenCreateOffer.h similarity index 97% rename from src/ripple/app/tx/impl/NFTokenCreateOffer.h rename to src/xrpld/app/tx/detail/NFTokenCreateOffer.h index 676b546f4b9..075a5a712fd 100644 --- a/src/ripple/app/tx/impl/NFTokenCreateOffer.h +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_NFTOKENOFFERCREATE_H_INCLUDED #define RIPPLE_TX_NFTOKENOFFERCREATE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp similarity index 72% rename from src/ripple/app/tx/impl/NFTokenMint.cpp rename to src/xrpld/app/tx/detail/NFTokenMint.cpp index c26fb1fb12a..d5c3a8707c2 100644 --- a/src/ripple/app/tx/impl/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -17,26 +17,39 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include namespace ripple { +static std::uint16_t +extractNFTokenFlagsFromTxFlags(std::uint32_t txFlags) +{ + return static_cast(txFlags & 0x0000FFFF); +} + NotTEC NFTokenMint::preflight(PreflightContext const& ctx) { if (!ctx.rules.enabled(featureNonFungibleTokensV1)) return temDISABLED; + bool const hasOfferFields = ctx.tx.isFieldPresent(sfAmount) || + ctx.tx.isFieldPresent(sfDestination) || + ctx.tx.isFieldPresent(sfExpiration); + + if (!ctx.rules.enabled(featureNFTokenMintOffer) && hasOfferFields) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -80,6 +93,29 @@ NFTokenMint::preflight(PreflightContext const& ctx) return temMALFORMED; } + if (hasOfferFields) + { + // The Amount field must be present if either the Destination or + // Expiration fields are present. + if (!ctx.tx.isFieldPresent(sfAmount)) + return temMALFORMED; + + // Rely on the common code shared with NFTokenCreateOffer to + // do the validation. We pass tfSellNFToken as the transaction flags + // because a Mint is only allowed to create a sell offer. + if (NotTEC notTec = nft::tokenOfferCreatePreflight( + ctx.tx[sfAccount], + ctx.tx[sfAmount], + ctx.tx[~sfDestination], + ctx.tx[~sfExpiration], + extractNFTokenFlagsFromTxFlags(ctx.tx.getFlags()), + ctx.rules); + !isTesSuccess(notTec)) + { + return notTec; + } + } + return preflight2(ctx); } @@ -146,6 +182,27 @@ NFTokenMint::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; } + if (ctx.tx.isFieldPresent(sfAmount)) + { + // The Amount field says create an offer for the minted token. + if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) + return tecEXPIRED; + + // Rely on the common code shared with NFTokenCreateOffer to + // do the validation. We pass tfSellNFToken as the transaction flags + // because a Mint is only allowed to create a sell offer. + if (TER const ter = nft::tokenOfferCreatePreclaim( + ctx.view, + ctx.tx[sfAccount], + ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]), + ctx.tx[sfAmount], + ctx.tx[~sfDestination], + extractNFTokenFlagsFromTxFlags(ctx.tx.getFlags()), + ctx.tx[~sfTransferFee].value_or(0), + ctx.j); + !isTesSuccess(ter)) + return ter; + } return tesSUCCESS; } @@ -238,18 +295,16 @@ NFTokenMint::doApply() // Should never happen. return tecINTERNAL; + auto const nftokenID = createNFTokenID( + extractNFTokenFlagsFromTxFlags(ctx_.tx.getFlags()), + ctx_.tx[~sfTransferFee].value_or(0), + issuer, + nft::toTaxon(ctx_.tx[sfNFTokenTaxon]), + tokenSeq.value()); + STObject newToken( - *nfTokenTemplate, - sfNFToken, - [this, &issuer, &tokenSeq](STObject& object) { - object.setFieldH256( - sfNFTokenID, - createNFTokenID( - static_cast(ctx_.tx.getFlags() & 0x0000FFFF), - ctx_.tx[~sfTransferFee].value_or(0), - issuer, - nft::toTaxon(ctx_.tx[sfNFTokenTaxon]), - tokenSeq.value())); + *nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) { + object.setFieldH256(sfNFTokenID, nftokenID); if (auto const uri = ctx_.tx[~sfURI]) object.setFieldVL(sfURI, *uri); @@ -260,10 +315,29 @@ NFTokenMint::doApply() ret != tesSUCCESS) return ret; + if (ctx_.tx.isFieldPresent(sfAmount)) + { + // Rely on the common code shared with NFTokenCreateOffer to create + // the offer. We pass tfSellNFToken as the transaction flags + // because a Mint is only allowed to create a sell offer. + if (TER const ter = nft::tokenOfferCreateApply( + view(), + ctx_.tx[sfAccount], + ctx_.tx[sfAmount], + ctx_.tx[~sfDestination], + ctx_.tx[~sfExpiration], + ctx_.tx.getSeqProxy(), + nftokenID, + mPriorBalance, + j_); + !isTesSuccess(ter)) + return ter; + } + // Only check the reserve if the owner count actually changed. This // allows NFTs to be added to the page (and burn fees) without // requiring the reserve to be met each time. The reserve is - // only managed when a new NFT page is added. + // only managed when a new NFT page or sell offer is added. if (auto const ownerCountAfter = view().read(keylet::account(account_))->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) diff --git a/src/ripple/app/tx/impl/NFTokenMint.h b/src/xrpld/app/tx/detail/NFTokenMint.h similarity index 93% rename from src/ripple/app/tx/impl/NFTokenMint.h rename to src/xrpld/app/tx/detail/NFTokenMint.h index 690843c19ce..c95fd5944e4 100644 --- a/src/ripple/app/tx/impl/NFTokenMint.h +++ b/src/xrpld/app/tx/detail/NFTokenMint.h @@ -20,8 +20,9 @@ #ifndef RIPPLE_TX_NFTTOKENMINT_H_INCLUDED #define RIPPLE_TX_NFTTOKENMINT_H_INCLUDED -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/details/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp similarity index 60% rename from src/ripple/app/tx/impl/details/NFTokenUtils.cpp rename to src/xrpld/app/tx/detail/NFTokenUtils.cpp index 09ff8f13caa..61ff8e200b3 100644 --- a/src/ripple/app/tx/impl/details/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -191,6 +191,7 @@ getPageForToken( : carr[0].getFieldH256(sfNFTokenID); auto np = std::make_shared(keylet::nftpage(base, tokenIDForNewPage)); + assert(np->key() > base.key); np->setFieldArray(sfNFTokens, narr); np->setFieldH256(sfNextPageMin, cp->key()); @@ -428,10 +429,48 @@ removeToken( return tesSUCCESS; } - // The page is empty, so we can just unlink it and then remove it. if (prev) { - // Make our previous page point to our next page: + // With fixNFTokenPageLinks... + // The page is empty and there is a prev. If the last page of the + // directory is empty then we need to: + // 1. Move the contents of the previous page into the last page. + // 2. Fix up the link from prev's previous page. + // 3. Fix up the owner count. + // 4. Erase the previous page. + if (view.rules().enabled(fixNFTokenPageLinks) && + ((curr->key() & nft::pageMask) == pageMask)) + { + // Copy all relevant information from prev to curr. + curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens); + + if (auto const prevLink = prev->at(~sfPreviousPageMin)) + { + curr->at(sfPreviousPageMin) = *prevLink; + + // Also fix up the NextPageMin link in the new Previous. + auto const newPrev = loadPage(curr, sfPreviousPageMin); + newPrev->at(sfNextPageMin) = curr->key(); + view.update(newPrev); + } + else + { + curr->makeFieldAbsent(sfPreviousPageMin); + } + + adjustOwnerCount( + view, + view.peek(keylet::account(owner)), + -1, + beast::Journal{beast::Journal::getNullSink()}); + + view.update(curr); + view.erase(prev); + return tesSUCCESS; + } + + // The page is empty and not the last page, so we can just unlink it + // and then remove it. if (next) prev->setFieldH256(sfNextPageMin, next->key()); else @@ -636,5 +675,370 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) return true; } +bool +repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) +{ + bool didRepair = false; + + auto const last = keylet::nftpage_max(owner); + + std::shared_ptr page = view.peek(Keylet( + ltNFTOKEN_PAGE, + view.succ(keylet::nftpage_min(owner).key, last.key.next()) + .value_or(last.key))); + + if (!page) + return didRepair; + + if (page->key() == last.key) + { + // There's only one page in this entire directory. There should be + // no links on that page. + bool const nextPresent = page->isFieldPresent(sfNextPageMin); + bool const prevPresent = page->isFieldPresent(sfPreviousPageMin); + if (nextPresent || prevPresent) + { + didRepair = true; + if (prevPresent) + page->makeFieldAbsent(sfPreviousPageMin); + if (nextPresent) + page->makeFieldAbsent(sfNextPageMin); + view.update(page); + } + return didRepair; + } + + // First page is not the same as last page. The first page should not + // contain a previous link. + if (page->isFieldPresent(sfPreviousPageMin)) + { + didRepair = true; + page->makeFieldAbsent(sfPreviousPageMin); + view.update(page); + } + + std::shared_ptr nextPage; + while ( + (nextPage = view.peek(Keylet( + ltNFTOKEN_PAGE, + view.succ(page->key().next(), last.key.next()) + .value_or(last.key))))) + { + if (!page->isFieldPresent(sfNextPageMin) || + page->getFieldH256(sfNextPageMin) != nextPage->key()) + { + didRepair = true; + page->setFieldH256(sfNextPageMin, nextPage->key()); + view.update(page); + } + + if (!nextPage->isFieldPresent(sfPreviousPageMin) || + nextPage->getFieldH256(sfPreviousPageMin) != page->key()) + { + didRepair = true; + nextPage->setFieldH256(sfPreviousPageMin, page->key()); + view.update(nextPage); + } + + if (nextPage->key() == last.key) + // We need special handling for the last page. + break; + + page = nextPage; + } + + // When we arrive here, nextPage should have the same index as last. + // If not, then that's something we need to fix. + if (!nextPage) + { + // It turns out that page is the last page for this owner, but + // that last page does not have the expected final index. We need + // to move the contents of the current last page into a page with the + // correct index. + // + // The owner count does not need to change because, even though + // we're adding a page, we'll also remove the page that used to be + // last. + didRepair = true; + nextPage = std::make_shared(last); + + // Copy all relevant information from prev to curr. + nextPage->peekFieldArray(sfNFTokens) = page->peekFieldArray(sfNFTokens); + + if (auto const prevLink = page->at(~sfPreviousPageMin)) + { + nextPage->at(sfPreviousPageMin) = *prevLink; + + // Also fix up the NextPageMin link in the new Previous. + auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink)); + if (!newPrev) + Throw( + "NFTokenPage directory for " + to_string(owner) + + " cannot be repaired. Unexpected link problem."); + newPrev->at(sfNextPageMin) = nextPage->key(); + view.update(newPrev); + } + view.erase(page); + view.insert(nextPage); + return didRepair; + } + + assert(nextPage); + if (nextPage->isFieldPresent(sfNextPageMin)) + { + didRepair = true; + nextPage->makeFieldAbsent(sfNextPageMin); + view.update(nextPage); + } + return didRepair; +} + +NotTEC +tokenOfferCreatePreflight( + AccountID const& acctID, + STAmount const& amount, + std::optional const& dest, + std::optional const& expiration, + std::uint16_t nftFlags, + Rules const& rules, + std::optional const& owner, + std::uint32_t txFlags) +{ + if (amount.negative() && rules.enabled(fixNFTokenNegOffer)) + // An offer for a negative amount makes no sense. + return temBAD_AMOUNT; + + if (!isXRP(amount)) + { + if (nftFlags & nft::flagOnlyXRP) + return temBAD_AMOUNT; + + if (!amount) + return temBAD_AMOUNT; + } + + // If this is an offer to buy, you must offer something; if it's an + // offer to sell, you can ask for nothing. + bool const isSellOffer = txFlags & tfSellNFToken; + if (!isSellOffer && !amount) + return temBAD_AMOUNT; + + if (expiration.has_value() && expiration.value() == 0) + return temBAD_EXPIRATION; + + // The 'Owner' field must be present when offering to buy, but can't + // be present when selling (it's implicit): + if (owner.has_value() == isSellOffer) + return temMALFORMED; + + if (owner && owner == acctID) + return temMALFORMED; + + if (dest) + { + // Some folks think it makes sense for a buy offer to specify a + // specific broker using the Destination field. This change doesn't + // deserve it's own amendment, so we're piggy-backing on + // fixNFTokenNegOffer. + // + // Prior to fixNFTokenNegOffer any use of the Destination field on + // a buy offer was malformed. + if (!isSellOffer && !rules.enabled(fixNFTokenNegOffer)) + return temMALFORMED; + + // The destination can't be the account executing the transaction. + if (dest == acctID) + return temMALFORMED; + } + return tesSUCCESS; +} + +TER +tokenOfferCreatePreclaim( + ReadView const& view, + AccountID const& acctID, + AccountID const& nftIssuer, + STAmount const& amount, + std::optional const& dest, + std::uint16_t nftFlags, + std::uint16_t xferFee, + beast::Journal j, + std::optional const& owner, + std::uint32_t txFlags) +{ + if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() && xferFee) + { + if (!view.exists(keylet::account(nftIssuer))) + return tecNO_ISSUER; + + // If the IOU issuer and the NFToken issuer are the same, then that + // issuer does not need a trust line to accept their fee. + if (view.rules().enabled(featureNFTokenMintOffer)) + { + if (nftIssuer != amount.getIssuer() && + !view.read(keylet::line(nftIssuer, amount.issue()))) + return tecNO_LINE; + } + else if (!view.exists(keylet::line(nftIssuer, amount.issue()))) + { + return tecNO_LINE; + } + + if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer())) + return tecFROZEN; + } + + if (nftIssuer != acctID && !(nftFlags & nft::flagTransferable)) + { + auto const root = view.read(keylet::account(nftIssuer)); + assert(root); + + if (auto minter = (*root)[~sfNFTokenMinter]; minter != acctID) + return tefNFTOKEN_IS_NOT_TRANSFERABLE; + } + + if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer())) + return tecFROZEN; + + // If this is an offer to buy the token, the account must have the + // needed funds at hand; but note that funds aren't reserved and the + // offer may later become unfunded. + if ((txFlags & tfSellNFToken) == 0) + { + // After this amendment, we allow an IOU issuer to make a buy offer + // using their own currency. + if (view.rules().enabled(fixNonFungibleTokensV1_2)) + { + if (accountFunds( + view, acctID, amount, FreezeHandling::fhZERO_IF_FROZEN, j) + .signum() <= 0) + return tecUNFUNDED_OFFER; + } + else if ( + accountHolds( + view, + acctID, + amount.getCurrency(), + amount.getIssuer(), + FreezeHandling::fhZERO_IF_FROZEN, + j) + .signum() <= 0) + return tecUNFUNDED_OFFER; + } + + if (dest) + { + // If a destination is specified, the destination must already be in + // the ledger. + auto const sleDst = view.read(keylet::account(*dest)); + + if (!sleDst) + return tecNO_DST; + + // check if the destination has disallowed incoming offers + if (view.rules().enabled(featureDisallowIncoming)) + { + // flag cannot be set unless amendment is enabled but + // out of an abundance of caution check anyway + + if (sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer) + return tecNO_PERMISSION; + } + } + + if (owner) + { + // Check if the owner (buy offer) has disallowed incoming offers + if (view.rules().enabled(featureDisallowIncoming)) + { + auto const sleOwner = view.read(keylet::account(*owner)); + + // defensively check + // it should not be possible to specify owner that doesn't exist + if (!sleOwner) + return tecNO_TARGET; + + if (sleOwner->getFlags() & lsfDisallowIncomingNFTokenOffer) + return tecNO_PERMISSION; + } + } + + return tesSUCCESS; +} + +TER +tokenOfferCreateApply( + ApplyView& view, + AccountID const& acctID, + STAmount const& amount, + std::optional const& dest, + std::optional const& expiration, + SeqProxy seqProxy, + uint256 const& nftokenID, + XRPAmount const& priorBalance, + beast::Journal j, + std::uint32_t txFlags) +{ + Keylet const acctKeylet = keylet::account(acctID); + if (auto const acct = view.read(acctKeylet); + priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) + return tecINSUFFICIENT_RESERVE; + + auto const offerID = keylet::nftoffer(acctID, seqProxy.value()); + + // Create the offer: + { + // Token offers are always added to the owner's owner directory: + auto const ownerNode = view.dirInsert( + keylet::ownerDir(acctID), offerID, describeOwnerDir(acctID)); + + if (!ownerNode) + return tecDIR_FULL; + + bool const isSellOffer = txFlags & tfSellNFToken; + + // Token offers are also added to the token's buy or sell offer + // directory + auto const offerNode = view.dirInsert( + isSellOffer ? keylet::nft_sells(nftokenID) + : keylet::nft_buys(nftokenID), + offerID, + [&nftokenID, isSellOffer](std::shared_ptr const& sle) { + (*sle)[sfFlags] = + isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers; + (*sle)[sfNFTokenID] = nftokenID; + }); + + if (!offerNode) + return tecDIR_FULL; + + std::uint32_t sleFlags = 0; + + if (isSellOffer) + sleFlags |= lsfSellNFToken; + + auto offer = std::make_shared(offerID); + (*offer)[sfOwner] = acctID; + (*offer)[sfNFTokenID] = nftokenID; + (*offer)[sfAmount] = amount; + (*offer)[sfFlags] = sleFlags; + (*offer)[sfOwnerNode] = *ownerNode; + (*offer)[sfNFTokenOfferNode] = *offerNode; + + if (expiration) + (*offer)[sfExpiration] = *expiration; + + if (dest) + (*offer)[sfDestination] = *dest; + + view.insert(offer); + } + + // Update owner count. + adjustOwnerCount(view, view.peek(acctKeylet), 1, j); + + return tesSUCCESS; +} + } // namespace nft } // namespace ripple diff --git a/src/ripple/app/tx/impl/details/NFTokenUtils.h b/src/xrpld/app/tx/detail/NFTokenUtils.h similarity index 64% rename from src/ripple/app/tx/impl/details/NFTokenUtils.h rename to src/xrpld/app/tx/detail/NFTokenUtils.h index 5242bf38ff3..97d109b8318 100644 --- a/src/ripple/app/tx/impl/details/NFTokenUtils.h +++ b/src/xrpld/app/tx/detail/NFTokenUtils.h @@ -20,12 +20,13 @@ #ifndef RIPPLE_TX_IMPL_DETAILS_NFTOKENUTILS_H_INCLUDED #define RIPPLE_TX_IMPL_DETAILS_NFTOKENUTILS_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -94,9 +95,56 @@ removeToken( bool deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer); +/** Repairs the links in an NFTokenPage directory. + + Returns true if a repair took place, otherwise false. +*/ +bool +repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner); + bool compareTokens(uint256 const& a, uint256 const& b); +/** Preflight checks shared by NFTokenCreateOffer and NFTokenMint */ +NotTEC +tokenOfferCreatePreflight( + AccountID const& acctID, + STAmount const& amount, + std::optional const& dest, + std::optional const& expiration, + std::uint16_t nftFlags, + Rules const& rules, + std::optional const& owner = std::nullopt, + std::uint32_t txFlags = lsfSellNFToken); + +/** Preclaim checks shared by NFTokenCreateOffer and NFTokenMint */ +TER +tokenOfferCreatePreclaim( + ReadView const& view, + AccountID const& acctID, + AccountID const& nftIssuer, + STAmount const& amount, + std::optional const& dest, + std::uint16_t nftFlags, + std::uint16_t xferFee, + beast::Journal j, + std::optional const& owner = std::nullopt, + std::uint32_t txFlags = lsfSellNFToken); + +/** doApply implementation shared by NFTokenCreateOffer and NFTokenMint */ +TER +tokenOfferCreateApply( + ApplyView& view, + AccountID const& acctID, + STAmount const& amount, + std::optional const& dest, + std::optional const& expiration, + SeqProxy seqProxy, + uint256 const& nftokenID, + XRPAmount const& priorBalance, + beast::Journal j, + std::uint32_t txFlags = lsfSellNFToken); + } // namespace nft } // namespace ripple diff --git a/src/ripple/app/tx/impl/Offer.h b/src/xrpld/app/tx/detail/Offer.h similarity index 89% rename from src/ripple/app/tx/impl/Offer.h rename to src/xrpld/app/tx/detail/Offer.h index bdae4d2b155..a6f707ba561 100644 --- a/src/ripple/app/tx/impl/Offer.h +++ b/src/xrpld/app/tx/detail/Offer.h @@ -20,11 +20,12 @@ #ifndef RIPPLE_APP_BOOK_OFFER_H_INCLUDED #define RIPPLE_APP_BOOK_OFFER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -140,11 +141,11 @@ class TOffer : private TOfferBase limitOut( TAmounts const& offrAmt, TOut const& limit, - bool fixReducedOffers, bool roundUp) const; TAmounts - limitIn(TAmounts const& offrAmt, TIn const& limit) const; + limitIn(TAmounts const& offrAmt, TIn const& limit, bool roundUp) + const; template static TER @@ -219,10 +220,10 @@ TAmounts TOffer::limitOut( TAmounts const& offrAmt, TOut const& limit, - bool fixReducedOffers, bool roundUp) const { - if (fixReducedOffers) + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(fixReducedOffersV1)) // It turns out that the ceil_out implementation has some slop in // it. ceil_out_strict removes that slop. But removing that slop // affects transaction outcomes, so the change must be made using @@ -233,9 +234,18 @@ TOffer::limitOut( template TAmounts -TOffer::limitIn(TAmounts const& offrAmt, TIn const& limit) - const +TOffer::limitIn( + TAmounts const& offrAmt, + TIn const& limit, + bool roundUp) const { + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(fixReducedOffersV2)) + // It turns out that the ceil_in implementation has some slop in + // it. ceil_in_strict removes that slop. But removing that slop + // affects transaction outcomes, so the change must be made using + // an amendment. + return quality().ceil_in_strict(offrAmt, limit, roundUp); return m_quality.ceil_in(offrAmt, limit); } diff --git a/src/ripple/app/tx/impl/OfferStream.cpp b/src/xrpld/app/tx/detail/OfferStream.cpp similarity index 99% rename from src/ripple/app/tx/impl/OfferStream.cpp rename to src/xrpld/app/tx/detail/OfferStream.cpp index 5933d9c3838..b963195259a 100644 --- a/src/ripple/app/tx/impl/OfferStream.cpp +++ b/src/xrpld/app/tx/detail/OfferStream.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/OfferStream.h b/src/xrpld/app/tx/detail/OfferStream.h similarity index 95% rename from src/ripple/app/tx/impl/OfferStream.h rename to src/xrpld/app/tx/detail/OfferStream.h index 9472bc4e74d..be224a67b4e 100644 --- a/src/ripple/app/tx/impl/OfferStream.h +++ b/src/xrpld/app/tx/detail/OfferStream.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_BOOK_OFFERSTREAM_H_INCLUDED #define RIPPLE_APP_BOOK_OFFERSTREAM_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp similarity index 93% rename from src/ripple/app/tx/impl/PayChan.cpp rename to src/xrpld/app/tx/detail/PayChan.cpp index 3fe2a28a7cf..b2d4c0c9449 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -17,19 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -403,6 +404,10 @@ PayChanFund::doApply() NotTEC PayChanClaim::preflight(PreflightContext const& ctx) { + if (ctx.tx.isFieldPresent(sfCredentialIDs) && + !ctx.rules.enabled(featureCredentials)) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -453,9 +458,25 @@ PayChanClaim::preflight(PreflightContext const& ctx) return temBAD_SIGNATURE; } + if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + return err; + return preflight2(ctx); } +TER +PayChanClaim::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureCredentials)) + return Transactor::preclaim(ctx); + + if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + !isTesSuccess(err)) + return err; + + return tesSUCCESS; +} + TER PayChanClaim::doApply() { @@ -516,18 +537,11 @@ PayChanClaim::doApply() (txAccount == src && (sled->getFlags() & lsfDisallowXRP))) return tecNO_TARGET; - // Check whether the destination account requires deposit authorization. - if (depositAuth && (sled->getFlags() & lsfDepositAuth)) + if (depositAuth) { - // A destination account that requires authorization has two - // ways to get a Payment Channel Claim into the account: - // 1. If Account == Destination, or - // 2. If Account is deposit preauthorized by destination. - if (txAccount != dst) - { - if (!view().exists(keylet::depositPreauth(dst, txAccount))) - return tecNO_PERMISSION; - } + if (auto err = verifyDepositPreauth(ctx_, txAccount, dst, sled); + !isTesSuccess(err)) + return err; } (*slep)[sfBalance] = ctx_.tx[sfBalance]; diff --git a/src/ripple/app/tx/impl/PayChan.h b/src/xrpld/app/tx/detail/PayChan.h similarity index 91% rename from src/ripple/app/tx/impl/PayChan.h rename to src/xrpld/app/tx/detail/PayChan.h index 9fe4b841944..2e09c473dc0 100644 --- a/src/ripple/app/tx/impl/PayChan.h +++ b/src/xrpld/app/tx/detail/PayChan.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_PAYCHAN_H_INCLUDED #define RIPPLE_TX_PAYCHAN_H_INCLUDED -#include +#include namespace ripple { @@ -46,6 +46,8 @@ class PayChanCreate : public Transactor doApply() override; }; +using PaymentChannelCreate = PayChanCreate; + //------------------------------------------------------------------------------ class PayChanFund : public Transactor @@ -67,6 +69,8 @@ class PayChanFund : public Transactor doApply() override; }; +using PaymentChannelFund = PayChanFund; + //------------------------------------------------------------------------------ class PayChanClaim : public Transactor @@ -81,10 +85,15 @@ class PayChanClaim : public Transactor static NotTEC preflight(PreflightContext const& ctx); + static TER + preclaim(PreclaimContext const& ctx); + TER doApply() override; }; +using PaymentChannelClaim = PayChanClaim; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp similarity index 56% rename from src/ripple/app/tx/impl/Payment.cpp rename to src/xrpld/app/tx/detail/Payment.cpp index 3903aa75045..2be784306cd 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -17,14 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -43,128 +45,158 @@ Payment::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)}; } +STAmount +getMaxSourceAmount( + AccountID const& account, + STAmount const& dstAmount, + std::optional const& sendMax) +{ + if (sendMax) + return *sendMax; + else if (dstAmount.native() || dstAmount.holds()) + return dstAmount; + else + return STAmount( + Issue{dstAmount.get().currency, account}, + dstAmount.mantissa(), + dstAmount.exponent(), + dstAmount < beast::zero); +} + NotTEC Payment::preflight(PreflightContext const& ctx) { + if (ctx.tx.isFieldPresent(sfCredentialIDs) && + !ctx.rules.enabled(featureCredentials)) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; auto& tx = ctx.tx; auto& j = ctx.j; - std::uint32_t const uTxFlags = tx.getFlags(); + STAmount const dstAmount(tx.getFieldAmount(sfAmount)); + bool const mptDirect = dstAmount.holds(); - if (uTxFlags & tfPaymentMask) + if (mptDirect && !ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + std::uint32_t const txFlags = tx.getFlags(); + + std::uint32_t paymentMask = mptDirect ? tfMPTPaymentMask : tfPaymentMask; + + if (txFlags & paymentMask) { - JLOG(j.trace()) << "Malformed transaction: " - << "Invalid flags set."; + JLOG(j.trace()) << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - bool const limitQuality = uTxFlags & tfLimitQuality; - bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); - bool const bPaths = tx.isFieldPresent(sfPaths); - bool const bMax = tx.isFieldPresent(sfSendMax); + if (mptDirect && ctx.tx.isFieldPresent(sfPaths)) + return temMALFORMED; + + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + bool const limitQuality = txFlags & tfLimitQuality; + bool const defaultPathsAllowed = !(txFlags & tfNoRippleDirect); + bool const hasPaths = tx.isFieldPresent(sfPaths); + bool const hasMax = tx.isFieldPresent(sfSendMax); - STAmount const saDstAmount(tx.getFieldAmount(sfAmount)); + auto const deliverMin = tx[~sfDeliverMin]; - STAmount maxSourceAmount; auto const account = tx.getAccountID(sfAccount); + STAmount const maxSourceAmount = + getMaxSourceAmount(account, dstAmount, tx[~sfSendMax]); - if (bMax) - maxSourceAmount = tx.getFieldAmount(sfSendMax); - else if (saDstAmount.native()) - maxSourceAmount = saDstAmount; - else - maxSourceAmount = STAmount( - {saDstAmount.getCurrency(), account}, - saDstAmount.mantissa(), - saDstAmount.exponent(), - saDstAmount < beast::zero); + if ((mptDirect && dstAmount.asset() != maxSourceAmount.asset()) || + (!mptDirect && maxSourceAmount.holds())) + { + JLOG(j.trace()) << "Malformed transaction: inconsistent issues: " + << dstAmount.getFullText() << " " + << maxSourceAmount.getFullText() << " " + << deliverMin.value_or(STAmount{}).getFullText(); + return temMALFORMED; + } - auto const& uSrcCurrency = maxSourceAmount.getCurrency(); - auto const& uDstCurrency = saDstAmount.getCurrency(); + auto const& srcAsset = maxSourceAmount.asset(); + auto const& dstAsset = dstAmount.asset(); - // isZero() is XRP. FIX! - bool const bXRPDirect = uSrcCurrency.isZero() && uDstCurrency.isZero(); + bool const xrpDirect = srcAsset.native() && dstAsset.native(); - if (!isLegalNet(saDstAmount) || !isLegalNet(maxSourceAmount)) + if (!isLegalNet(dstAmount) || !isLegalNet(maxSourceAmount)) return temBAD_AMOUNT; - auto const uDstAccountID = tx.getAccountID(sfDestination); + auto const dstAccountID = tx.getAccountID(sfDestination); - if (!uDstAccountID) + if (!dstAccountID) { JLOG(j.trace()) << "Malformed transaction: " << "Payment destination account not specified."; return temDST_NEEDED; } - if (bMax && maxSourceAmount <= beast::zero) + if (hasMax && maxSourceAmount <= beast::zero) { - JLOG(j.trace()) << "Malformed transaction: " - << "bad max amount: " << maxSourceAmount.getFullText(); + JLOG(j.trace()) << "Malformed transaction: bad max amount: " + << maxSourceAmount.getFullText(); return temBAD_AMOUNT; } - if (saDstAmount <= beast::zero) + if (dstAmount <= beast::zero) { - JLOG(j.trace()) << "Malformed transaction: " - << "bad dst amount: " << saDstAmount.getFullText(); + JLOG(j.trace()) << "Malformed transaction: bad dst amount: " + << dstAmount.getFullText(); return temBAD_AMOUNT; } - if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) + if (badCurrency() == srcAsset || badCurrency() == dstAsset) { - JLOG(j.trace()) << "Malformed transaction: " - << "Bad currency."; + JLOG(j.trace()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } - if (account == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + if (account == dstAccountID && equalTokens(srcAsset, dstAsset) && !hasPaths) { // You're signing yourself a payment. - // If bPaths is true, you might be trying some arbitrage. + // If hasPaths is true, you might be trying some arbitrage. JLOG(j.trace()) << "Malformed transaction: " << "Redundant payment from " << to_string(account) - << " to self without path for " - << to_string(uDstCurrency); + << " to self without path for " << to_string(dstAsset); return temREDUNDANT; } - if (bXRPDirect && bMax) + if (xrpDirect && hasMax) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " << "SendMax specified for XRP to XRP."; return temBAD_SEND_XRP_MAX; } - if (bXRPDirect && bPaths) + if ((xrpDirect || mptDirect) && hasPaths) { // XRP is sent without paths. JLOG(j.trace()) << "Malformed transaction: " - << "Paths specified for XRP to XRP."; + << "Paths specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_PATHS; } - if (bXRPDirect && partialPaymentAllowed) + if (xrpDirect && partialPaymentAllowed) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " << "Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } - if (bXRPDirect && limitQuality) + if ((xrpDirect || mptDirect) && limitQuality) { // Consistent but redundant transaction. - JLOG(j.trace()) << "Malformed transaction: " - << "Limit quality specified for XRP to XRP."; + JLOG(j.trace()) + << "Malformed transaction: " + << "Limit quality specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_LIMIT; } - if (bXRPDirect && !defaultPathsAllowed) + if ((xrpDirect || mptDirect) && !defaultPathsAllowed) { // Consistent but redundant transaction. - JLOG(j.trace()) << "Malformed transaction: " - << "No ripple direct specified for XRP to XRP."; + JLOG(j.trace()) + << "Malformed transaction: " + << "No ripple direct specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_NO_DIRECT; } - auto const deliverMin = tx[~sfDeliverMin]; if (deliverMin) { if (!partialPaymentAllowed) @@ -183,7 +215,7 @@ Payment::preflight(PreflightContext const& ctx) << " amount. " << dMin.getFullText(); return temBAD_AMOUNT; } - if (dMin.issue() != saDstAmount.issue()) + if (dMin.asset() != dstAmount.asset()) { JLOG(j.trace()) << "Malformed transaction: Dst issue differs " @@ -191,7 +223,7 @@ Payment::preflight(PreflightContext const& ctx) << jss::DeliverMin.c_str() << ". " << dMin.getFullText(); return temBAD_AMOUNT; } - if (dMin > saDstAmount) + if (dMin > dstAmount) { JLOG(j.trace()) << "Malformed transaction: Dst amount less than " @@ -200,6 +232,9 @@ Payment::preflight(PreflightContext const& ctx) } } + if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + return err; + return preflight2(ctx); } @@ -207,21 +242,21 @@ TER Payment::preclaim(PreclaimContext const& ctx) { // Ripple if source or destination is non-native or if there are paths. - std::uint32_t const uTxFlags = ctx.tx.getFlags(); - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - auto const paths = ctx.tx.isFieldPresent(sfPaths); + std::uint32_t const txFlags = ctx.tx.getFlags(); + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + auto const hasPaths = ctx.tx.isFieldPresent(sfPaths); auto const sendMax = ctx.tx[~sfSendMax]; - AccountID const uDstAccountID(ctx.tx[sfDestination]); - STAmount const saDstAmount(ctx.tx[sfAmount]); + AccountID const dstAccountID(ctx.tx[sfDestination]); + STAmount const dstAmount(ctx.tx[sfAmount]); - auto const k = keylet::account(uDstAccountID); + auto const k = keylet::account(dstAccountID); auto const sleDst = ctx.view.read(k); if (!sleDst) { // Destination account does not exist. - if (!saDstAmount.native()) + if (!dstAmount.native()) { JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist."; @@ -241,7 +276,7 @@ Payment::preclaim(PreclaimContext const& ctx) // transaction would succeed. return telNO_DST_PARTIAL; } - else if (saDstAmount < STAmount(ctx.view.fees().accountReserve(0))) + else if (dstAmount < STAmount(ctx.view.fees().accountReserve(0))) { // accountReserve is the minimum amount that an account can have. // Reserve is not scaled by load. @@ -271,7 +306,7 @@ Payment::preclaim(PreclaimContext const& ctx) } // Payment with at least one intermediate step and uses transitive balances. - if ((paths || sendMax || !saDstAmount.native()) && ctx.view.open()) + if ((hasPaths || sendMax || !dstAmount.native()) && ctx.view.open()) { STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths); @@ -284,6 +319,10 @@ Payment::preclaim(PreclaimContext const& ctx) } } + if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + !isTesSuccess(err)) + return err; + return tesSUCCESS; } @@ -293,32 +332,24 @@ Payment::doApply() auto const deliverMin = ctx_.tx[~sfDeliverMin]; // Ripple if source or destination is non-native or if there are paths. - std::uint32_t const uTxFlags = ctx_.tx.getFlags(); - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - bool const limitQuality = uTxFlags & tfLimitQuality; - bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); - auto const paths = ctx_.tx.isFieldPresent(sfPaths); + std::uint32_t const txFlags = ctx_.tx.getFlags(); + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + bool const limitQuality = txFlags & tfLimitQuality; + bool const defaultPathsAllowed = !(txFlags & tfNoRippleDirect); + auto const hasPaths = ctx_.tx.isFieldPresent(sfPaths); auto const sendMax = ctx_.tx[~sfSendMax]; - AccountID const uDstAccountID(ctx_.tx.getAccountID(sfDestination)); - STAmount const saDstAmount(ctx_.tx.getFieldAmount(sfAmount)); - STAmount maxSourceAmount; - if (sendMax) - maxSourceAmount = *sendMax; - else if (saDstAmount.native()) - maxSourceAmount = saDstAmount; - else - maxSourceAmount = STAmount( - {saDstAmount.getCurrency(), account_}, - saDstAmount.mantissa(), - saDstAmount.exponent(), - saDstAmount < beast::zero); + AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination)); + STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount)); + bool const mptDirect = dstAmount.holds(); + STAmount const maxSourceAmount = + getMaxSourceAmount(account_, dstAmount, sendMax); JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() - << " saDstAmount=" << saDstAmount.getFullText(); + << " dstAmount=" << dstAmount.getFullText(); // Open a ledger for editing. - auto const k = keylet::account(uDstAccountID); + auto const k = keylet::account(dstAccountID); SLE::pointer sleDst = view().peek(k); if (!sleDst) @@ -329,7 +360,7 @@ Payment::doApply() // Create the account. sleDst = std::make_shared(k); - sleDst->setAccountID(sfAccount, uDstAccountID); + sleDst->setAccountID(sfAccount, dstAccountID); sleDst->setFieldU32(sfSequence, seqno); view().insert(sleDst); @@ -343,35 +374,36 @@ Payment::doApply() } // Determine whether the destination requires deposit authorization. - bool const reqDepositAuth = sleDst->getFlags() & lsfDepositAuth && - view().rules().enabled(featureDepositAuth); + bool const depositAuth = view().rules().enabled(featureDepositAuth); + bool const reqDepositAuth = + sleDst->getFlags() & lsfDepositAuth && depositAuth; bool const depositPreauth = view().rules().enabled(featureDepositPreauth); - bool const bRipple = paths || sendMax || !saDstAmount.native(); + bool const ripple = + (hasPaths || sendMax || !dstAmount.native()) && !mptDirect; // If the destination has lsfDepositAuth set, then only direct XRP // payments (no intermediate steps) are allowed to the destination. - if (!depositPreauth && bRipple && reqDepositAuth) + if (!depositPreauth && ripple && reqDepositAuth) return tecNO_PERMISSION; - if (bRipple) + if (ripple) { // Ripple payment with at least one intermediate step and uses // transitive balances. - if (depositPreauth && reqDepositAuth) + if (depositPreauth && depositAuth) { // If depositPreauth is enabled, then an account that requires // authorization has two ways to get an IOU Payment in: // 1. If Account == Destination, or // 2. If Account is deposit preauthorized by destination. - if (uDstAccountID != account_) - { - if (!view().exists( - keylet::depositPreauth(uDstAccountID, account_))) - return tecNO_PERMISSION; - } + + if (auto err = + verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + !isTesSuccess(err)) + return err; } path::RippleCalc::Input rcInput; @@ -388,8 +420,8 @@ Payment::doApply() rc = path::RippleCalc::rippleCalculate( pv, maxSourceAmount, - saDstAmount, - uDstAccountID, + dstAmount, + dstAccountID, account_, ctx_.tx.getFieldPathSet(sfPaths), ctx_.app.logs(), @@ -402,7 +434,7 @@ Payment::doApply() // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? - if (rc.result() == tesSUCCESS && rc.actualAmountOut != saDstAmount) + if (rc.result() == tesSUCCESS && rc.actualAmountOut != dstAmount) { if (deliverMin && rc.actualAmountOut < *deliverMin) rc.setResult(tecPATH_PARTIAL); @@ -420,8 +452,80 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } + else if (mptDirect) + { + JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText(); + auto const& mptIssue = dstAmount.get(); + + if (auto const ter = requireAuth(view(), mptIssue, account_); + ter != tesSUCCESS) + return ter; + + if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); + ter != tesSUCCESS) + return ter; + + if (auto const ter = + canTransfer(view(), mptIssue, account_, dstAccountID); + ter != tesSUCCESS) + return ter; + + if (auto err = + verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + !isTesSuccess(err)) + return err; + + auto const& issuer = mptIssue.getIssuer(); + + // Transfer rate + Rate rate{QUALITY_ONE}; + // Payment between the holders + if (account_ != issuer && dstAccountID != issuer) + { + // If globally/individually locked then + // - can't send between holders + // - holder can send back to issuer + // - issuer can send to holder + if (isFrozen(view(), account_, mptIssue) || + isFrozen(view(), dstAccountID, mptIssue)) + return tecLOCKED; + + // Get the rate for a payment between the holders. + rate = transferRate(view(), mptIssue.getMptID()); + } - assert(saDstAmount.native()); + // Amount to deliver. + STAmount amountDeliver = dstAmount; + // Factor in the transfer rate. + // No rounding. It'll change once MPT integrated into DEX. + STAmount requiredMaxSourceAmount = multiply(dstAmount, rate); + + // Send more than the account wants to pay or less than + // the account wants to deliver (if no SendMax). + // Adjust the amount to deliver. + if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount) + { + requiredMaxSourceAmount = maxSourceAmount; + // No rounding. It'll change once MPT integrated into DEX. + amountDeliver = divide(maxSourceAmount, rate); + } + + if (requiredMaxSourceAmount > maxSourceAmount || + (deliverMin && amountDeliver < *deliverMin)) + return tecPATH_PARTIAL; + + PaymentSandbox pv(&view()); + auto res = accountSend( + pv, account_, dstAccountID, amountDeliver, ctx_.journal); + if (res == tesSUCCESS) + pv.apply(ctx_.rawView()); + else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY) + res = tecPATH_PARTIAL; + + return res; + } + + assert(dstAmount.native()); // Direct XRP payment. @@ -429,25 +533,25 @@ Payment::doApply() if (!sleSrc) return tefINTERNAL; - // uOwnerCount is the number of entries in this ledger for this + // ownerCount is the number of entries in this ledger for this // account that require a reserve. - auto const uOwnerCount = sleSrc->getFieldU32(sfOwnerCount); + auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); // This is the total reserve in drops. - auto const reserve = view().fees().accountReserve(uOwnerCount); + auto const reserve = view().fees().accountReserve(ownerCount); // mPriorBalance is the balance on the sending account BEFORE the // fees were charged. We want to make sure we have enough reserve // to send. Allow final spend to use reserve for fee. auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()); - if (mPriorBalance < saDstAmount.xrp() + mmm) + if (mPriorBalance < dstAmount.xrp() + mmm) { // Vote no. However the transaction might succeed, if applied in // a different order. JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " - << " " << to_string(mPriorBalance) << " / " - << to_string(saDstAmount.xrp() + mmm) << " (" + << to_string(mPriorBalance) << " / " + << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve) << ")"; return tecUNFUNDED_PAYMENT; @@ -460,7 +564,7 @@ Payment::doApply() // The source account does have enough money. Make sure the // source account has authority to deposit to the destination. - if (reqDepositAuth) + if (depositAuth) { // If depositPreauth is enabled, then an account that requires // authorization has three ways to get an XRP Payment in: @@ -480,24 +584,24 @@ Payment::doApply() // We choose the base reserve as our bound because it is // a small number that seldom changes but is always sufficient // to get the account un-wedged. - if (uDstAccountID != account_) + + // Get the base reserve. + XRPAmount const dstReserve{view().fees().accountReserve(0)}; + + if (dstAmount > dstReserve || + sleDst->getFieldAmount(sfBalance) > dstReserve) { - if (!view().exists(keylet::depositPreauth(uDstAccountID, account_))) - { - // Get the base reserve. - XRPAmount const dstReserve{view().fees().accountReserve(0)}; - - if (saDstAmount > dstReserve || - sleDst->getFieldAmount(sfBalance) > dstReserve) - return tecNO_PERMISSION; - } + if (auto err = + verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + !isTesSuccess(err)) + return err; } } // Do the arithmetic for the transfer and make the ledger change. - sleSrc->setFieldAmount(sfBalance, mSourceBalance - saDstAmount); + sleSrc->setFieldAmount(sfBalance, mSourceBalance - dstAmount); sleDst->setFieldAmount( - sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); + sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags() & lsfPasswordSpent)) diff --git a/src/ripple/app/tx/impl/Payment.h b/src/xrpld/app/tx/detail/Payment.h similarity index 92% rename from src/ripple/app/tx/impl/Payment.h rename to src/xrpld/app/tx/detail/Payment.h index 1d4711198b9..3176ae1c0d3 100644 --- a/src/ripple/app/tx/impl/Payment.h +++ b/src/xrpld/app/tx/detail/Payment.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_PAYMENT_H_INCLUDED #define RIPPLE_TX_PAYMENT_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/xrpld/app/tx/detail/SetAccount.cpp similarity index 98% rename from src/ripple/app/tx/impl/SetAccount.cpp rename to src/xrpld/app/tx/detail/SetAccount.cpp index d43b3b339e5..c0e115c2497 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/xrpld/app/tx/detail/SetAccount.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetAccount.h b/src/xrpld/app/tx/detail/SetAccount.h similarity index 86% rename from src/ripple/app/tx/impl/SetAccount.h rename to src/xrpld/app/tx/detail/SetAccount.h index 1c6bb4b7d80..62cae28e51c 100644 --- a/src/ripple/app/tx/impl/SetAccount.h +++ b/src/xrpld/app/tx/detail/SetAccount.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_TX_SETACCOUNT_H_INCLUDED #define RIPPLE_TX_SETACCOUNT_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -51,6 +51,8 @@ class SetAccount : public Transactor doApply() override; }; +using AccountSet = SetAccount; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp similarity index 97% rename from src/ripple/app/tx/impl/SetOracle.cpp rename to src/xrpld/app/tx/detail/SetOracle.cpp index d0987d26538..055143cc6fd 100644 --- a/src/ripple/app/tx/impl/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetOracle.h b/src/xrpld/app/tx/detail/SetOracle.h similarity index 96% rename from src/ripple/app/tx/impl/SetOracle.h rename to src/xrpld/app/tx/detail/SetOracle.h index 0ab8e603aa5..656b6560192 100644 --- a/src/ripple/app/tx/impl/SetOracle.h +++ b/src/xrpld/app/tx/detail/SetOracle.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_TX_SETORACLE_H_INCLUDED #define RIPPLE_TX_SETORACLE_H_INCLUDED -#include +#include namespace ripple { @@ -52,6 +52,8 @@ class SetOracle : public Transactor doApply() override; }; +using OracleSet = SetOracle; + } // namespace ripple #endif // RIPPLE_TX_SETORACLE_H_INCLUDED diff --git a/src/ripple/app/tx/impl/SetRegularKey.cpp b/src/xrpld/app/tx/detail/SetRegularKey.cpp similarity index 95% rename from src/ripple/app/tx/impl/SetRegularKey.cpp rename to src/xrpld/app/tx/detail/SetRegularKey.cpp index 8b789d75aed..9f165612f3a 100644 --- a/src/ripple/app/tx/impl/SetRegularKey.cpp +++ b/src/xrpld/app/tx/detail/SetRegularKey.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetRegularKey.h b/src/xrpld/app/tx/detail/SetRegularKey.h similarity index 91% rename from src/ripple/app/tx/impl/SetRegularKey.h rename to src/xrpld/app/tx/detail/SetRegularKey.h index 402ee436ed5..89a714342ff 100644 --- a/src/ripple/app/tx/impl/SetRegularKey.h +++ b/src/xrpld/app/tx/detail/SetRegularKey.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_SETREGULARKEY_H_INCLUDED #define RIPPLE_TX_SETREGULARKEY_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp similarity index 96% rename from src/ripple/app/tx/impl/SetSignerList.cpp rename to src/xrpld/app/tx/detail/SetSignerList.cpp index 07cc705bad1..0949fbbe775 100644 --- a/src/ripple/app/tx/impl/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -416,11 +416,11 @@ SetSignerList::writeSignersToSLE( STArray toLedger(signers_.size()); for (auto const& entry : signers_) { - toLedger.emplace_back(sfSignerEntry); + toLedger.push_back(STObject::makeInnerObject(sfSignerEntry)); STObject& obj = toLedger.back(); obj.reserve(2); - obj.setAccountID(sfAccount, entry.account); - obj.setFieldU16(sfSignerWeight, entry.weight); + obj[sfAccount] = entry.account; + obj[sfSignerWeight] = entry.weight; // This is a defensive check to make absolutely sure we will never write // a tag into the ledger while featureExpandedSignerList is not enabled diff --git a/src/ripple/app/tx/impl/SetSignerList.h b/src/xrpld/app/tx/detail/SetSignerList.h similarity index 88% rename from src/ripple/app/tx/impl/SetSignerList.h rename to src/xrpld/app/tx/detail/SetSignerList.h index f8e49e4a7b0..35951645c21 100644 --- a/src/ripple/app/tx/impl/SetSignerList.h +++ b/src/xrpld/app/tx/detail/SetSignerList.h @@ -20,15 +20,15 @@ #ifndef RIPPLE_TX_SETSIGNERLIST_H_INCLUDED #define RIPPLE_TX_SETSIGNERLIST_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -97,6 +97,8 @@ class SetSignerList : public Transactor const; }; +using SignerListSet = SetSignerList; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp similarity index 98% rename from src/ripple/app/tx/impl/SetTrust.cpp rename to src/xrpld/app/tx/detail/SetTrust.cpp index 00a5165221e..954fc6543f1 100644 --- a/src/ripple/app/tx/impl/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -537,7 +537,7 @@ SetTrust::doApply() else { // Zero balance in currency. - STAmount saBalance({currency, noAccount()}); + STAmount saBalance(Issue{currency, noAccount()}); auto const k = keylet::line(account_, uDstAccountID, currency); diff --git a/src/ripple/app/tx/impl/SetTrust.h b/src/xrpld/app/tx/detail/SetTrust.h similarity index 88% rename from src/ripple/app/tx/impl/SetTrust.h rename to src/xrpld/app/tx/detail/SetTrust.h index 259ed077412..6e5a72b3062 100644 --- a/src/ripple/app/tx/impl/SetTrust.h +++ b/src/xrpld/app/tx/detail/SetTrust.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_TX_SETTRUST_H_INCLUDED #define RIPPLE_TX_SETTRUST_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { @@ -47,6 +47,8 @@ class SetTrust : public Transactor doApply() override; }; +using TrustSet = SetTrust; + } // namespace ripple #endif diff --git a/src/ripple/app/tx/impl/SignerEntries.cpp b/src/xrpld/app/tx/detail/SignerEntries.cpp similarity index 92% rename from src/ripple/app/tx/impl/SignerEntries.cpp rename to src/xrpld/app/tx/detail/SignerEntries.cpp index a948b2f902b..cab362a8e3b 100644 --- a/src/ripple/app/tx/impl/SignerEntries.cpp +++ b/src/xrpld/app/tx/detail/SignerEntries.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -30,7 +30,7 @@ Expected, NotTEC> SignerEntries::deserialize( STObject const& obj, beast::Journal journal, - std::string const& annotation) + std::string_view annotation) { std::pair, NotTEC> s; diff --git a/src/ripple/app/tx/impl/SignerEntries.h b/src/xrpld/app/tx/detail/SignerEntries.h similarity index 69% rename from src/ripple/app/tx/impl/SignerEntries.h rename to src/xrpld/app/tx/detail/SignerEntries.h index cf4921ecf4b..2227aa98109 100644 --- a/src/ripple/app/tx/impl/SignerEntries.h +++ b/src/xrpld/app/tx/detail/SignerEntries.h @@ -20,25 +20,34 @@ #ifndef RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED #define RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED -#include // NotTEC -#include // -#include // beast::Journal -#include // Rules -#include // STTx::maxMultiSigners -#include // temMALFORMED -#include // AccountID +#include // NotTEC +#include // +#include // beast::Journal +#include // Rules +#include // STTx::maxMultiSigners +#include // temMALFORMED +#include // AccountID + #include +#include namespace ripple { // Forward declarations class STObject; -// Support for SignerEntries that is needed by a few Transactors +// Support for SignerEntries that is needed by a few Transactors. +// +// SignerEntries is represented as a std::vector. +// There is no direct constructor for SignerEntries. +// +// o A std::vector is a SignerEntries. +// o More commonly, SignerEntries are extracted from an STObject by +// calling SignerEntries::deserialize(). class SignerEntries { public: - explicit SignerEntries() = default; + explicit SignerEntries() = delete; struct SignerEntry { @@ -69,11 +78,15 @@ class SignerEntries }; // Deserialize a SignerEntries array from the network or from the ledger. + // + // obj Contains a SignerEntries field that is an STArray. + // journal For reporting error conditions. + // annotation Source of SignerEntries, like "ledger" or "transaction". static Expected, NotTEC> deserialize( STObject const& obj, beast::Journal journal, - std::string const& annotation); + std::string_view annotation); }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/Taker.cpp b/src/xrpld/app/tx/detail/Taker.cpp similarity index 99% rename from src/ripple/app/tx/impl/Taker.cpp rename to src/xrpld/app/tx/detail/Taker.cpp index f463ce41198..9d335de2846 100644 --- a/src/ripple/app/tx/impl/Taker.cpp +++ b/src/xrpld/app/tx/detail/Taker.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/Taker.h b/src/xrpld/app/tx/detail/Taker.h similarity index 96% rename from src/ripple/app/tx/impl/Taker.h rename to src/xrpld/app/tx/detail/Taker.h index 4a516dccec1..3e64c59b542 100644 --- a/src/ripple/app/tx/impl/Taker.h +++ b/src/xrpld/app/tx/detail/Taker.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_APP_BOOK_TAKER_H_INCLUDED #define RIPPLE_APP_BOOK_TAKER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp similarity index 93% rename from src/ripple/app/tx/impl/Transactor.cpp rename to src/xrpld/app/tx/detail/Transactor.cpp index 7dcf3f15ab7..052a735a2fd 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -17,22 +17,23 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -635,7 +636,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) calcAccountID(PublicKey(makeSlice(spk))); // Verify that the signingAcctID and the signingAcctIDFromPubKey - // belong together. Here is are the rules: + // belong together. Here are the rules: // // 1. "Phantom account": an account that is not in the ledger // A. If signingAcctID == signingAcctIDFromPubKey and the @@ -760,6 +761,19 @@ removeExpiredNFTokenOffers( } } +static void +removeExpiredCredentials( + ApplyView& view, + std::vector const& creds, + beast::Journal viewJ) +{ + for (auto const& index : creds) + { + if (auto const sle = view.peek(keylet::credential(index))) + credentials::deleteSLE(view, sle, viewJ); + } +} + static void removeDeletedTrustLines( ApplyView& view, @@ -825,6 +839,14 @@ Transactor::reset(XRPAmount fee) return {ter, fee}; } +// The sole purpose of this function is to provide a convenient, named +// location to set a breakpoint, to be used when replaying transactions. +void +Transactor::trapTransaction(uint256 txHash) const +{ + JLOG(j_.debug()) << "Transaction trapped: " << txHash; +} + //------------------------------------------------------------------------------ std::pair Transactor::operator()() @@ -854,6 +876,12 @@ Transactor::operator()() } #endif + if (auto const& trap = ctx_.app.trapTxID(); + trap && *trap == ctx_.tx.getTransactionID()) + { + trapTransaction(*trap); + } + auto result = ctx_.preclaimResult; if (result == tesSUCCESS) result = apply(); @@ -893,19 +921,23 @@ Transactor::operator()() std::vector removedOffers; std::vector removedTrustLines; std::vector expiredNFTokenOffers; + std::vector expiredCredentials; bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); bool const doLines = (result == tecINCOMPLETE); bool const doNFTokenOffers = (result == tecEXPIRED); - if (doOffers || doLines || doNFTokenOffers) + bool const doCredentials = (result == tecEXPIRED); + if (doOffers || doLines || doNFTokenOffers || doCredentials) { - ctx_.visit([&doOffers, + ctx_.visit([doOffers, &removedOffers, - &doLines, + doLines, &removedTrustLines, - &doNFTokenOffers, - &expiredNFTokenOffers]( + doNFTokenOffers, + &expiredNFTokenOffers, + doCredentials, + &expiredCredentials]( uint256 const& index, bool isDelete, std::shared_ptr const& before, @@ -932,6 +964,10 @@ Transactor::operator()() if (doNFTokenOffers && before && after && (before->getType() == ltNFTOKEN_OFFER)) expiredNFTokenOffers.push_back(index); + + if (doCredentials && before && after && + (before->getType() == ltCREDENTIAL)) + expiredCredentials.push_back(index); } }); } @@ -958,6 +994,10 @@ Transactor::operator()() removeDeletedTrustLines( view(), removedTrustLines, ctx_.app.journal("View")); + if (result == tecEXPIRED) + removeExpiredCredentials( + view(), expiredCredentials, ctx_.app.journal("View")); + applied = isTecClaim(result); } diff --git a/src/ripple/app/tx/impl/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h similarity index 96% rename from src/ripple/app/tx/impl/Transactor.h rename to src/xrpld/app/tx/detail/Transactor.h index cc280e6141d..c587e5e1994 100644 --- a/src/ripple/app/tx/impl/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED #define RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -198,6 +198,8 @@ class Transactor checkSingleSign(PreclaimContext const& ctx); static NotTEC checkMultiSign(PreclaimContext const& ctx); + + void trapTransaction(uint256) const; }; /** Performs early sanity checks on the txid */ diff --git a/src/ripple/app/tx/impl/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp similarity index 98% rename from src/ripple/app/tx/impl/XChainBridge.cpp rename to src/xrpld/app/tx/detail/XChainBridge.cpp index be315236f2c..f5633903567 100644 --- a/src/ripple/app/tx/impl/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -17,31 +17,31 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/app/tx/impl/XChainBridge.h b/src/xrpld/app/tx/detail/XChainBridge.h similarity index 97% rename from src/ripple/app/tx/impl/XChainBridge.h rename to src/xrpld/app/tx/detail/XChainBridge.h index 4d41c7d1c21..2e5e927d120 100644 --- a/src/ripple/app/tx/impl/XChainBridge.h +++ b/src/xrpld/app/tx/detail/XChainBridge.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_TX_XCHAINBRIDGE_H_INCLUDED #define RIPPLE_TX_XCHAINBRIDGE_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { @@ -67,6 +67,9 @@ class BridgeModify : public Transactor TER doApply() override; }; + +using XChainModifyBridge = BridgeModify; + //------------------------------------------------------------------------------ // Claim funds from a `XChainCommit` transaction. This is normally not needed, @@ -248,6 +251,8 @@ class XChainCreateAccountCommit : public Transactor doApply() override; }; +using XChainAccountCreateCommit = XChainCreateAccountCommit; + //------------------------------------------------------------------------------ } // namespace ripple diff --git a/src/ripple/app/tx/impl/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp similarity index 96% rename from src/ripple/app/tx/impl/apply.cpp rename to src/xrpld/app/tx/detail/apply.cpp index c0704c5c3ae..103ec041074 100644 --- a/src/ripple/app/tx/impl/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp similarity index 62% rename from src/ripple/app/tx/impl/applySteps.cpp rename to src/xrpld/app/tx/detail/applySteps.cpp index 1a1fc343e3c..b3c711084dc 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -17,41 +17,48 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -75,96 +82,18 @@ with_txn_type(TxType txnType, F&& f) { switch (txnType) { - case ttACCOUNT_DELETE: - return f.template operator()(); - case ttACCOUNT_SET: - return f.template operator()(); - case ttCHECK_CANCEL: - return f.template operator()(); - case ttCHECK_CASH: - return f.template operator()(); - case ttCHECK_CREATE: - return f.template operator()(); - case ttDEPOSIT_PREAUTH: - return f.template operator()(); - case ttOFFER_CANCEL: - return f.template operator()(); - case ttOFFER_CREATE: - return f.template operator()(); - case ttESCROW_CREATE: - return f.template operator()(); - case ttESCROW_FINISH: - return f.template operator()(); - case ttESCROW_CANCEL: - return f.template operator()(); - case ttPAYCHAN_CLAIM: - return f.template operator()(); - case ttPAYCHAN_CREATE: - return f.template operator()(); - case ttPAYCHAN_FUND: - return f.template operator()(); - case ttPAYMENT: - return f.template operator()(); - case ttREGULAR_KEY_SET: - return f.template operator()(); - case ttSIGNER_LIST_SET: - return f.template operator()(); - case ttTICKET_CREATE: - return f.template operator()(); - case ttTRUST_SET: - return f.template operator()(); - case ttAMENDMENT: - case ttFEE: - case ttUNL_MODIFY: - return f.template operator()(); - case ttNFTOKEN_MINT: - return f.template operator()(); - case ttNFTOKEN_BURN: - return f.template operator()(); - case ttNFTOKEN_CREATE_OFFER: - return f.template operator()(); - case ttNFTOKEN_CANCEL_OFFER: - return f.template operator()(); - case ttNFTOKEN_ACCEPT_OFFER: - return f.template operator()(); - case ttCLAWBACK: - return f.template operator()(); - case ttAMM_CREATE: - return f.template operator()(); - case ttAMM_DEPOSIT: - return f.template operator()(); - case ttAMM_WITHDRAW: - return f.template operator()(); - case ttAMM_VOTE: - return f.template operator()(); - case ttAMM_BID: - return f.template operator()(); - case ttAMM_DELETE: - return f.template operator()(); - case ttXCHAIN_CREATE_BRIDGE: - return f.template operator()(); - case ttXCHAIN_MODIFY_BRIDGE: - return f.template operator()(); - case ttXCHAIN_CREATE_CLAIM_ID: - return f.template operator()(); - case ttXCHAIN_COMMIT: - return f.template operator()(); - case ttXCHAIN_CLAIM: - return f.template operator()(); - case ttXCHAIN_ADD_CLAIM_ATTESTATION: - return f.template operator()(); - case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION: - return f.template operator()(); - case ttXCHAIN_ACCOUNT_CREATE_COMMIT: - return f.template operator()(); - case ttDID_SET: - return f.template operator()(); - case ttDID_DELETE: - return f.template operator()(); - case ttORACLE_SET: - return f.template operator()(); - case ttORACLE_DELETE: - return f.template operator()(); +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, fields) \ + case tag: \ + return f.template operator()(); + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + default: throw UnknownTxnType(txnType); } diff --git a/src/ripple/conditions/Condition.h b/src/xrpld/conditions/Condition.h similarity index 96% rename from src/ripple/conditions/Condition.h rename to src/xrpld/conditions/Condition.h index 340b7c96124..a3120f1b3ca 100644 --- a/src/ripple/conditions/Condition.h +++ b/src/xrpld/conditions/Condition.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_CONDITIONS_CONDITION_H #define RIPPLE_CONDITIONS_CONDITION_H -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/conditions/Fulfillment.h b/src/xrpld/conditions/Fulfillment.h similarity index 96% rename from src/ripple/conditions/Fulfillment.h rename to src/xrpld/conditions/Fulfillment.h index 075c005100c..e70bcf54d41 100644 --- a/src/ripple/conditions/Fulfillment.h +++ b/src/xrpld/conditions/Fulfillment.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_CONDITIONS_FULFILLMENT_H #define RIPPLE_CONDITIONS_FULFILLMENT_H -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { namespace cryptoconditions { diff --git a/src/ripple/conditions/impl/Condition.cpp b/src/xrpld/conditions/detail/Condition.cpp similarity index 96% rename from src/ripple/conditions/impl/Condition.cpp rename to src/xrpld/conditions/detail/Condition.cpp index 8a1d30916a1..3085ace0a31 100644 --- a/src/ripple/conditions/impl/Condition.cpp +++ b/src/xrpld/conditions/detail/Condition.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/conditions/impl/Fulfillment.cpp b/src/xrpld/conditions/detail/Fulfillment.cpp similarity index 94% rename from src/ripple/conditions/impl/Fulfillment.cpp rename to src/xrpld/conditions/detail/Fulfillment.cpp index ff47e386469..285113ebfb2 100644 --- a/src/ripple/conditions/impl/Fulfillment.cpp +++ b/src/xrpld/conditions/detail/Fulfillment.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/conditions/impl/PreimageSha256.h b/src/xrpld/conditions/detail/PreimageSha256.h similarity index 93% rename from src/ripple/conditions/impl/PreimageSha256.h rename to src/xrpld/conditions/detail/PreimageSha256.h index 316e57ac173..5185ded7a67 100644 --- a/src/ripple/conditions/impl/PreimageSha256.h +++ b/src/xrpld/conditions/detail/PreimageSha256.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_CONDITIONS_PREIMAGE_SHA256_H #define RIPPLE_CONDITIONS_PREIMAGE_SHA256_H -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -137,7 +137,8 @@ class PreimageSha256 final : public Fulfillment return {type(), cost(), fingerprint()}; } - bool validate(Slice) const override + bool + validate(Slice) const override { // Perhaps counterintuitively, the message isn't // relevant. diff --git a/src/ripple/conditions/impl/error.cpp b/src/xrpld/conditions/detail/error.cpp similarity index 98% rename from src/ripple/conditions/impl/error.cpp rename to src/xrpld/conditions/detail/error.cpp index f685a45899e..3594c9e14da 100644 --- a/src/ripple/conditions/impl/error.cpp +++ b/src/xrpld/conditions/detail/error.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/conditions/impl/error.h b/src/xrpld/conditions/detail/error.h similarity index 100% rename from src/ripple/conditions/impl/error.h rename to src/xrpld/conditions/detail/error.h diff --git a/src/ripple/conditions/impl/utils.h b/src/xrpld/conditions/detail/utils.h similarity index 97% rename from src/ripple/conditions/impl/utils.h rename to src/xrpld/conditions/detail/utils.h index e9ab1770a18..35d9c7dea51 100644 --- a/src/ripple/conditions/impl/utils.h +++ b/src/xrpld/conditions/detail/utils.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_CONDITIONS_UTILS_H #define RIPPLE_CONDITIONS_UTILS_H -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/consensus/Consensus.cpp b/src/xrpld/consensus/Consensus.cpp similarity index 98% rename from src/ripple/consensus/Consensus.cpp rename to src/xrpld/consensus/Consensus.cpp index cc1f84270e7..01451c6a255 100644 --- a/src/ripple/consensus/Consensus.cpp +++ b/src/xrpld/consensus/Consensus.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h similarity index 98% rename from src/ripple/consensus/Consensus.h rename to src/xrpld/consensus/Consensus.h index 248bbdc4a1b..06c12b4f150 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -20,15 +20,15 @@ #ifndef RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED #define RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -1060,8 +1060,7 @@ Consensus::checkLedger() { JLOG(j_.warn()) << "View of consensus changed during " << to_string(phase_) << " status=" << to_string(phase_) - << ", " - << " mode=" << to_string(mode_.get()); + << ", " << " mode=" << to_string(mode_.get()); JLOG(j_.warn()) << prevLedgerID_ << " to " << netLgr; JLOG(j_.warn()) << Json::Compact{previousLedger_.getJson()}; JLOG(j_.debug()) << "State on consensus change " @@ -1164,8 +1163,7 @@ Consensus::shouldPause() const << "roundTime: " << result_->roundTime.read().count() << ", " << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", " << "validators: " << totalValidators << ", " - << "laggards: " << laggards << ", " - << "offline: " << offline << ", " + << "laggards: " << laggards << ", " << "offline: " << offline << ", " << "quorum: " << quorum << ")"; if (!ahead || !laggards || !totalValidators || !adaptor_.validator() || @@ -1491,8 +1489,8 @@ Consensus::updateOurPositions() if (!haveCloseTimeConsensus_) { JLOG(j_.debug()) - << "No CT consensus:" - << " Proposers:" << currPeerPositions_.size() + << "No CT consensus:" << " Proposers:" + << currPeerPositions_.size() << " Mode:" << to_string(mode_.get()) << " Thresh:" << threshConsensus << " Pos:" << consensusCloseTime.time_since_epoch().count(); diff --git a/src/ripple/consensus/ConsensusParms.h b/src/xrpld/consensus/ConsensusParms.h similarity index 100% rename from src/ripple/consensus/ConsensusParms.h rename to src/xrpld/consensus/ConsensusParms.h diff --git a/src/ripple/consensus/ConsensusProposal.h b/src/xrpld/consensus/ConsensusProposal.h similarity index 97% rename from src/ripple/consensus/ConsensusProposal.h rename to src/xrpld/consensus/ConsensusProposal.h index c5103cfe0d5..c00bffe0237 100644 --- a/src/ripple/consensus/ConsensusProposal.h +++ b/src/xrpld/consensus/ConsensusProposal.h @@ -19,11 +19,11 @@ #ifndef RIPPLE_CONSENSUS_CONSENSUSPROPOSAL_H_INCLUDED #define RIPPLE_CONSENSUS_CONSENSUSPROPOSAL_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/consensus/ConsensusTypes.h b/src/xrpld/consensus/ConsensusTypes.h similarity index 98% rename from src/ripple/consensus/ConsensusTypes.h rename to src/xrpld/consensus/ConsensusTypes.h index 05d03c8a9c6..da03fc4875a 100644 --- a/src/ripple/consensus/ConsensusTypes.h +++ b/src/xrpld/consensus/ConsensusTypes.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_CONSENSUS_CONSENSUS_TYPES_H_INCLUDED #define RIPPLE_CONSENSUS_CONSENSUS_TYPES_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h similarity index 96% rename from src/ripple/consensus/DisputedTx.h rename to src/xrpld/consensus/DisputedTx.h index ae127197eec..bffb1009323 100644 --- a/src/ripple/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED #define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/consensus/LedgerTiming.h b/src/xrpld/consensus/LedgerTiming.h similarity index 98% rename from src/ripple/consensus/LedgerTiming.h rename to src/xrpld/consensus/LedgerTiming.h index 9320cf07de1..1bb5bfd730f 100644 --- a/src/ripple/consensus/LedgerTiming.h +++ b/src/xrpld/consensus/LedgerTiming.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/consensus/LedgerTrie.h b/src/xrpld/consensus/LedgerTrie.h similarity index 99% rename from src/ripple/consensus/LedgerTrie.h rename to src/xrpld/consensus/LedgerTrie.h index 12cd0d3cbbc..32bae28cc07 100644 --- a/src/ripple/consensus/LedgerTrie.h +++ b/src/xrpld/consensus/LedgerTrie.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED #define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/consensus/README.md b/src/xrpld/consensus/README.md similarity index 100% rename from src/ripple/consensus/README.md rename to src/xrpld/consensus/README.md diff --git a/src/ripple/consensus/Validations.h b/src/xrpld/consensus/Validations.h similarity index 99% rename from src/ripple/consensus/Validations.h rename to src/xrpld/consensus/Validations.h index 1bef76d961c..a1171effb56 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED #define RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -482,8 +482,7 @@ class Validations withTrie(std::lock_guard const& lock, F&& f) { // Call current to flush any stale validations - current( - lock, [](auto) {}, [](auto, auto) {}); + current(lock, [](auto) {}, [](auto, auto) {}); checkAcquired(lock); return f(trie_); } diff --git a/src/ripple/core/ClosureCounter.h b/src/xrpld/core/ClosureCounter.h similarity index 99% rename from src/ripple/core/ClosureCounter.h rename to src/xrpld/core/ClosureCounter.h index 9f5bc3ee617..9b1a69bf95d 100644 --- a/src/ripple/core/ClosureCounter.h +++ b/src/xrpld/core/ClosureCounter.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED #define RIPPLE_CORE_CLOSURE_COUNTER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/core/Config.h b/src/xrpld/core/Config.h similarity index 95% rename from src/ripple/core/Config.h rename to src/xrpld/core/Config.h index 25852615185..16950901f0f 100644 --- a/src/ripple/core/Config.h +++ b/src/xrpld/core/Config.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_CORE_CONFIG_H_INCLUDED #define RIPPLE_CORE_CONFIG_H_INCLUDED -#include -#include -#include -#include -#include -#include // VFALCO Breaks levelization +#include +#include +#include +#include +#include +#include // VFALCO Breaks levelization #include #include // VFALCO FIX: This include should not be here @@ -127,10 +127,6 @@ class Config : public BasicConfig */ bool RUN_STANDALONE = false; - bool RUN_REPORTING = false; - - bool REPORTING_READ_ONLY = false; - bool USE_TX_TABLES = true; /** Determines if the server will sign a tx, given an account's secret seed. @@ -146,7 +142,6 @@ class Config : public BasicConfig public: bool doImport = false; - bool nodeToShard = false; bool ELB_SUPPORT = false; // Entries from [ips] config stanza @@ -162,6 +157,8 @@ class Config : public BasicConfig std::string START_LEDGER; + std::optional TRAP_TX_HASH; + // Network parameters uint32_t NETWORK_ID = 0; @@ -346,11 +343,6 @@ class Config : public BasicConfig { return RUN_STANDALONE; } - bool - reporting() const - { - return RUN_REPORTING; - } bool useTxTables() const @@ -358,18 +350,6 @@ class Config : public BasicConfig return USE_TX_TABLES; } - bool - reportingReadOnly() const - { - return REPORTING_READ_ONLY; - } - - void - setReportingReadOnly(bool b) - { - REPORTING_READ_ONLY = b; - } - bool canSign() const { diff --git a/src/ripple/core/ConfigSections.h b/src/xrpld/core/ConfigSections.h similarity index 96% rename from src/ripple/core/ConfigSections.h rename to src/xrpld/core/ConfigSections.h index b4e460f1cfc..8685d29a4d0 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/xrpld/core/ConfigSections.h @@ -35,11 +35,6 @@ struct ConfigSection return "node_db"; } static std::string - shardDatabase() - { - return "shard_db"; - } - static std::string importNodeDatabase() { return "import_db"; @@ -56,7 +51,6 @@ struct ConfigSection #define SECTION_ELB_SUPPORT "elb_support" #define SECTION_FEE_DEFAULT "fee_default" #define SECTION_FETCH_DEPTH "fetch_depth" -#define SECTION_HISTORICAL_SHARD_PATHS "historical_shard_paths" #define SECTION_INSIGHT "insight" #define SECTION_IO_WORKERS "io_workers" #define SECTION_IPS "ips" diff --git a/src/ripple/core/Coro.ipp b/src/xrpld/core/Coro.ipp similarity index 98% rename from src/ripple/core/Coro.ipp rename to src/xrpld/core/Coro.ipp index 2b07c5a4584..a936dd896ae 100644 --- a/src/ripple/core/Coro.ipp +++ b/src/xrpld/core/Coro.ipp @@ -20,7 +20,7 @@ #ifndef RIPPLE_CORE_COROINL_H_INCLUDED #define RIPPLE_CORE_COROINL_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/core/DatabaseCon.h b/src/xrpld/core/DatabaseCon.h similarity index 92% rename from src/ripple/core/DatabaseCon.h rename to src/xrpld/core/DatabaseCon.h index 91d2a446668..c8c40dec662 100644 --- a/src/ripple/core/DatabaseCon.h +++ b/src/xrpld/core/DatabaseCon.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_APP_DATA_DATABASECON_H_INCLUDED #define RIPPLE_APP_DATA_DATABASECON_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -73,7 +73,8 @@ class LockedSociSession { return session_.get(); } - explicit operator bool() const + explicit + operator bool() const { return bool(session_); } @@ -88,7 +89,6 @@ class DatabaseCon Config::StartUpType startUp = Config::NORMAL; bool standAlone = false; - bool reporting = false; boost::filesystem::path dataDir; // Indicates whether or not to return the `globalPragma` // from commonPragma() @@ -103,6 +103,8 @@ class DatabaseCon } static std::unique_ptr const> globalPragma; + std::array txPragma; + std::array lgrPragma; }; struct CheckpointerSetup @@ -115,13 +117,12 @@ class DatabaseCon DatabaseCon( Setup const& setup, std::string const& dbName, - std::array const& pragma, + std::array const& pragma, std::array const& initSQL, beast::Journal journal) // Use temporary files or regular DB files? : DatabaseCon( - setup.standAlone && !setup.reporting && - setup.startUp != Config::LOAD && + setup.standAlone && setup.startUp != Config::LOAD && setup.startUp != Config::LOAD_FILE && setup.startUp != Config::REPLAY ? "" @@ -138,7 +139,7 @@ class DatabaseCon DatabaseCon( Setup const& setup, std::string const& dbName, - std::array const& pragma, + std::array const& pragma, std::array const& initSQL, CheckpointerSetup const& checkpointerSetup, beast::Journal journal) @@ -151,7 +152,7 @@ class DatabaseCon DatabaseCon( boost::filesystem::path const& dataDir, std::string const& dbName, - std::array const& pragma, + std::array const& pragma, std::array const& initSQL, beast::Journal journal) : DatabaseCon(dataDir / dbName, nullptr, pragma, initSQL, journal) @@ -163,7 +164,7 @@ class DatabaseCon DatabaseCon( boost::filesystem::path const& dataDir, std::string const& dbName, - std::array const& pragma, + std::array const& pragma, std::array const& initSQL, CheckpointerSetup const& checkpointerSetup, beast::Journal journal) @@ -201,13 +202,19 @@ class DatabaseCon DatabaseCon( boost::filesystem::path const& pPath, std::vector const* commonPragma, - std::array const& pragma, + std::array const& pragma, std::array const& initSQL, beast::Journal journal) : session_(std::make_shared()), j_(journal) { open(*session_, "sqlite", pPath.string()); + for (auto const& p : pragma) + { + soci::statement st = session_->prepare << p; + st.execute(true); + } + if (commonPragma) { for (auto const& p : *commonPragma) @@ -216,11 +223,7 @@ class DatabaseCon st.execute(true); } } - for (auto const& p : pragma) - { - soci::statement st = session_->prepare << p; - st.execute(true); - } + for (auto const& sql : initSQL) { soci::statement st = session_->prepare << sql; diff --git a/src/ripple/core/Job.h b/src/xrpld/core/Job.h similarity index 96% rename from src/ripple/core/Job.h rename to src/xrpld/core/Job.h index c4f2eddf35a..76d26c39e72 100644 --- a/src/ripple/core/Job.h +++ b/src/xrpld/core/Job.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_CORE_JOB_H_INCLUDED #define RIPPLE_CORE_JOB_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include @@ -47,7 +47,6 @@ enum JobType { jtCLIENT_FEE_CHANGE, // Subscription for fee change by a client jtCLIENT_CONSENSUS, // Subscription for consensus state change by a client jtCLIENT_ACCT_HIST, // Subscription for account history by a client - jtCLIENT_SHARD, // Client request for shard archiving jtCLIENT_RPC, // Client RPC request jtCLIENT_WEBSOCKET, // Client websocket request jtRPC, // A websocket command from the client diff --git a/src/ripple/core/JobQueue.h b/src/xrpld/core/JobQueue.h similarity index 98% rename from src/ripple/core/JobQueue.h rename to src/xrpld/core/JobQueue.h index a9d541baa75..06f90dd1c4e 100644 --- a/src/ripple/core/JobQueue.h +++ b/src/xrpld/core/JobQueue.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_CORE_JOBQUEUE_H_INCLUDED #define RIPPLE_CORE_JOBQUEUE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include // workaround for boost 1.72 bug #include // workaround for boost 1.72 bug @@ -402,7 +402,7 @@ class JobQueue : private Workers::Callback } // namespace ripple -#include +#include namespace ripple { diff --git a/src/ripple/core/JobTypeData.h b/src/xrpld/core/JobTypeData.h similarity index 96% rename from src/ripple/core/JobTypeData.h rename to src/xrpld/core/JobTypeData.h index 3f0cdb6e939..43d63895d8b 100644 --- a/src/ripple/core/JobTypeData.h +++ b/src/xrpld/core/JobTypeData.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED #define RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/core/JobTypeInfo.h b/src/xrpld/core/JobTypeInfo.h similarity index 98% rename from src/ripple/core/JobTypeInfo.h rename to src/xrpld/core/JobTypeInfo.h index 7ed7c469711..a521980ebe8 100644 --- a/src/ripple/core/JobTypeInfo.h +++ b/src/xrpld/core/JobTypeInfo.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED #define RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/core/JobTypes.h b/src/xrpld/core/JobTypes.h similarity index 98% rename from src/ripple/core/JobTypes.h rename to src/xrpld/core/JobTypes.h index 2803537f115..3b41ce7ff47 100644 --- a/src/ripple/core/JobTypes.h +++ b/src/xrpld/core/JobTypes.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_CORE_JOBTYPES_H_INCLUDED #define RIPPLE_CORE_JOBTYPES_H_INCLUDED -#include -#include +#include +#include #include #include #include @@ -84,7 +84,6 @@ class JobTypes add(jtCLIENT_FEE_CHANGE, "clientFeeChange", maxLimit, 2000ms, 5000ms); add(jtCLIENT_CONSENSUS, "clientConsensus", maxLimit, 2000ms, 5000ms); add(jtCLIENT_ACCT_HIST, "clientAccountHistory", maxLimit, 2000ms, 5000ms); - add(jtCLIENT_SHARD, "clientShardArchive", maxLimit, 2000ms, 5000ms); add(jtCLIENT_RPC, "clientRPC", maxLimit, 2000ms, 5000ms); add(jtCLIENT_WEBSOCKET, "clientWebsocket", maxLimit, 2000ms, 5000ms); add(jtRPC, "RPC", maxLimit, 0ms, 0ms); diff --git a/src/ripple/core/LoadEvent.h b/src/xrpld/core/LoadEvent.h similarity index 100% rename from src/ripple/core/LoadEvent.h rename to src/xrpld/core/LoadEvent.h diff --git a/src/ripple/core/LoadMonitor.h b/src/xrpld/core/LoadMonitor.h similarity index 95% rename from src/ripple/core/LoadMonitor.h rename to src/xrpld/core/LoadMonitor.h index e903bf25e31..e57429da23b 100644 --- a/src/ripple/core/LoadMonitor.h +++ b/src/xrpld/core/LoadMonitor.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED #define RIPPLE_CORE_LOADMONITOR_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/core/SociDB.h b/src/xrpld/core/SociDB.h similarity index 98% rename from src/ripple/core/SociDB.h rename to src/xrpld/core/SociDB.h index 621754098f8..20f210d4e38 100644 --- a/src/ripple/core/SociDB.h +++ b/src/xrpld/core/SociDB.h @@ -33,8 +33,8 @@ #pragma clang diagnostic ignored "-Wdeprecated" #endif -#include -#include +#include +#include #define SOCI_USE_BOOST #include #include diff --git a/src/ripple/core/TimeKeeper.h b/src/xrpld/core/TimeKeeper.h similarity index 98% rename from src/ripple/core/TimeKeeper.h rename to src/xrpld/core/TimeKeeper.h index e239a2f7565..f94a92a6666 100644 --- a/src/ripple/core/TimeKeeper.h +++ b/src/xrpld/core/TimeKeeper.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_CORE_TIMEKEEPER_H_INCLUDED #define RIPPLE_CORE_TIMEKEEPER_H_INCLUDED -#include -#include +#include +#include #include diff --git a/src/ripple/core/impl/Config.cpp b/src/xrpld/core/detail/Config.cpp similarity index 98% rename from src/ripple/core/impl/Config.cpp rename to src/xrpld/core/detail/Config.cpp index 2f26b8ba525..885951111c0 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -382,11 +382,6 @@ Config::setup( // Update default values load(); - if (exists("reporting")) - { - RUN_REPORTING = true; - RUN_STANDALONE = true; - } { // load() may have set a new value for the dataDir std::string const dbPath(legacy("database_path")); diff --git a/src/ripple/core/impl/DatabaseCon.cpp b/src/xrpld/core/detail/DatabaseCon.cpp similarity index 87% rename from src/ripple/core/impl/DatabaseCon.cpp rename to src/xrpld/core/detail/DatabaseCon.cpp index 4f65f7003f6..7000899b1f6 100644 --- a/src/ripple/core/impl/DatabaseCon.cpp +++ b/src/xrpld/core/detail/DatabaseCon.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -109,7 +109,6 @@ setup_DatabaseCon(Config const& c, std::optional j) setup.startUp = c.START_UP; setup.standAlone = c.standalone(); - setup.reporting = c.reporting(); setup.dataDir = c.legacy("database_path"); if (!setup.standAlone && setup.dataDir.empty()) { @@ -176,7 +175,7 @@ setup_DatabaseCon(Config const& c, std::optional j) } { - //#synchronous Valid values : off, normal, full, extra + // #synchronous Valid values : off, normal, full, extra if (set(synchronous, "synchronous", sqlite) && !safety_level.empty()) { @@ -238,6 +237,36 @@ setup_DatabaseCon(Config const& c, std::optional j) } setup.useGlobalPragma = true; + auto setPragma = + [](std::string& pragma, std::string const& key, int64_t value) { + pragma = "PRAGMA " + key + "=" + std::to_string(value) + ";"; + }; + + // Lgr Pragma + setPragma(setup.lgrPragma[0], "journal_size_limit", 1582080); + + // TX Pragma + int64_t page_size = 4096; + int64_t journal_size_limit = 1582080; + if (c.exists("sqlite")) + { + auto& s = c.section("sqlite"); + set(journal_size_limit, "journal_size_limit", s); + set(page_size, "page_size", s); + if (page_size < 512 || page_size > 65536) + Throw( + "Invalid page_size. Must be between 512 and 65536."); + + if (page_size & (page_size - 1)) + Throw( + "Invalid page_size. Must be a power of 2."); + } + + setPragma(setup.txPragma[0], "page_size", page_size); + setPragma(setup.txPragma[1], "journal_size_limit", journal_size_limit); + setPragma(setup.txPragma[2], "max_page_count", 4294967294); + setPragma(setup.txPragma[3], "mmap_size", 17179869184); + return setup; } diff --git a/src/ripple/core/impl/Job.cpp b/src/xrpld/core/detail/Job.cpp similarity index 97% rename from src/ripple/core/impl/Job.cpp rename to src/xrpld/core/detail/Job.cpp index 780a9f49cdf..0782fd05a66 100644 --- a/src/ripple/core/impl/Job.cpp +++ b/src/xrpld/core/detail/Job.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/core/impl/JobQueue.cpp b/src/xrpld/core/detail/JobQueue.cpp similarity index 99% rename from src/ripple/core/impl/JobQueue.cpp rename to src/xrpld/core/detail/JobQueue.cpp index 28947300cc6..1c859d724f4 100644 --- a/src/ripple/core/impl/JobQueue.cpp +++ b/src/xrpld/core/detail/JobQueue.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/core/impl/LoadEvent.cpp b/src/xrpld/core/detail/LoadEvent.cpp similarity index 96% rename from src/ripple/core/impl/LoadEvent.cpp rename to src/xrpld/core/detail/LoadEvent.cpp index 38617e7f541..ca78fdb19cc 100644 --- a/src/ripple/core/impl/LoadEvent.cpp +++ b/src/xrpld/core/detail/LoadEvent.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/core/impl/LoadMonitor.cpp b/src/xrpld/core/detail/LoadMonitor.cpp similarity index 97% rename from src/ripple/core/impl/LoadMonitor.cpp rename to src/xrpld/core/detail/LoadMonitor.cpp index e70b7947a32..80b08bbdfc3 100644 --- a/src/ripple/core/impl/LoadMonitor.cpp +++ b/src/xrpld/core/detail/LoadMonitor.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/core/impl/SociDB.cpp b/src/xrpld/core/detail/SociDB.cpp similarity index 97% rename from src/ripple/core/impl/SociDB.cpp rename to src/xrpld/core/detail/SociDB.cpp index 2d133263607..94f9098d9c0 100644 --- a/src/ripple/core/impl/SociDB.cpp +++ b/src/xrpld/core/detail/SociDB.cpp @@ -22,12 +22,12 @@ #pragma clang diagnostic ignored "-Wdeprecated" #endif -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/core/impl/Workers.cpp b/src/xrpld/core/detail/Workers.cpp similarity index 98% rename from src/ripple/core/impl/Workers.cpp rename to src/xrpld/core/detail/Workers.cpp index 732e6f0ec8a..ff861010d95 100644 --- a/src/ripple/core/impl/Workers.cpp +++ b/src/xrpld/core/detail/Workers.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/core/impl/Workers.h b/src/xrpld/core/detail/Workers.h similarity index 98% rename from src/ripple/core/impl/Workers.h rename to src/xrpld/core/detail/Workers.h index 88ab8fa27e2..3645d4fbcf0 100644 --- a/src/ripple/core/impl/Workers.h +++ b/src/xrpld/core/detail/Workers.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_CORE_WORKERS_H_INCLUDED #define RIPPLE_CORE_WORKERS_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/core/impl/semaphore.h b/src/xrpld/core/detail/semaphore.h similarity index 100% rename from src/ripple/core/impl/semaphore.h rename to src/xrpld/core/detail/semaphore.h diff --git a/src/ripple/ledger/ApplyView.h b/src/xrpld/ledger/ApplyView.h similarity index 99% rename from src/ripple/ledger/ApplyView.h rename to src/xrpld/ledger/ApplyView.h index a37ba6c46a1..f0166cd0b38 100644 --- a/src/ripple/ledger/ApplyView.h +++ b/src/xrpld/ledger/ApplyView.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_APPLYVIEW_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEW_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h similarity index 94% rename from src/ripple/ledger/ApplyViewImpl.h rename to src/xrpld/ledger/ApplyViewImpl.h index a7fb1bd426b..65678c5a6c4 100644 --- a/src/ripple/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/BookDirs.h b/src/xrpld/ledger/BookDirs.h similarity index 98% rename from src/ripple/ledger/BookDirs.h rename to src/xrpld/ledger/BookDirs.h index 86ea67d9166..2c27a5b6596 100644 --- a/src/ripple/ledger/BookDirs.h +++ b/src/xrpld/ledger/BookDirs.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_LEDGER_BOOK_DIRS_H_INCLUDED #define RIPPLE_LEDGER_BOOK_DIRS_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/ledger/CachedSLEs.h b/src/xrpld/ledger/CachedSLEs.h similarity index 91% rename from src/ripple/ledger/CachedSLEs.h rename to src/xrpld/ledger/CachedSLEs.h index d2b04e2cb5c..bc94092e20d 100644 --- a/src/ripple/ledger/CachedSLEs.h +++ b/src/xrpld/ledger/CachedSLEs.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_CACHEDSLES_H_INCLUDED #define RIPPLE_LEDGER_CACHEDSLES_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { using CachedSLEs = TaggedCache; diff --git a/src/ripple/ledger/CachedView.h b/src/xrpld/ledger/CachedView.h similarity index 97% rename from src/ripple/ledger/CachedView.h rename to src/xrpld/ledger/CachedView.h index da483b34948..fd921c63ca9 100644 --- a/src/ripple/ledger/CachedView.h +++ b/src/xrpld/ledger/CachedView.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_CACHEDVIEW_H_INCLUDED #define RIPPLE_LEDGER_CACHEDVIEW_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/ledger/Directory.h b/src/xrpld/ledger/Dir.h similarity index 84% rename from src/ripple/ledger/Directory.h rename to src/xrpld/ledger/Dir.h index 0efcf43e773..0e92d7dbca6 100644 --- a/src/ripple/ledger/Directory.h +++ b/src/xrpld/ledger/Dir.h @@ -20,11 +20,23 @@ #ifndef RIPPLE_LEDGER_DIR_H_INCLUDED #define RIPPLE_LEDGER_DIR_H_INCLUDED -#include -#include +#include +#include namespace ripple { +/** A class that simplifies iterating ledger directory pages + + The Dir class provides a forward iterator for walking through + the uint256 values contained in ledger directories. + + The Dir class also allows accelerated directory walking by + stepping directly from one page to the next using the next_page() + member function. + + As of July 2024, the Dir class is only being used with NFTokenOffer + directories and for unit tests. +*/ class Dir { private: diff --git a/src/ripple/ledger/OpenView.h b/src/xrpld/ledger/OpenView.h similarity index 97% rename from src/ripple/ledger/OpenView.h rename to src/xrpld/ledger/OpenView.h index 98b783e3a48..bd8627a18b2 100644 --- a/src/ripple/ledger/OpenView.h +++ b/src/xrpld/ledger/OpenView.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_LEDGER_OPENVIEW_H_INCLUDED #define RIPPLE_LEDGER_OPENVIEW_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/ledger/PaymentSandbox.h b/src/xrpld/ledger/PaymentSandbox.h similarity index 97% rename from src/ripple/ledger/PaymentSandbox.h rename to src/xrpld/ledger/PaymentSandbox.h index 60e42af1147..d7299b2efbf 100644 --- a/src/ripple/ledger/PaymentSandbox.h +++ b/src/xrpld/ledger/PaymentSandbox.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_LEDGER_PAYMENTSANDBOX_H_INCLUDED #define RIPPLE_LEDGER_PAYMENTSANDBOX_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/ledger/RawView.h b/src/xrpld/ledger/RawView.h similarity index 96% rename from src/ripple/ledger/RawView.h rename to src/xrpld/ledger/RawView.h index 29c9ab9abb9..cd9bcff8761 100644 --- a/src/ripple/ledger/RawView.h +++ b/src/xrpld/ledger/RawView.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_LEDGER_RAWVIEW_H_INCLUDED #define RIPPLE_LEDGER_RAWVIEW_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/ledger/ReadView.h b/src/xrpld/ledger/ReadView.h similarity index 92% rename from src/ripple/ledger/ReadView.h rename to src/xrpld/ledger/ReadView.h index 2ea3ab492ed..50f1a7d98c1 100644 --- a/src/ripple/ledger/ReadView.h +++ b/src/xrpld/ledger/ReadView.h @@ -20,21 +20,21 @@ #ifndef RIPPLE_LEDGER_READVIEW_H_INCLUDED #define RIPPLE_LEDGER_READVIEW_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -283,6 +283,6 @@ makeRulesGivenLedger( } // namespace ripple -#include +#include #endif diff --git a/src/ripple/ledger/Sandbox.h b/src/xrpld/ledger/Sandbox.h similarity index 95% rename from src/ripple/ledger/Sandbox.h rename to src/xrpld/ledger/Sandbox.h index d8610a69ef1..22b8dbecfb2 100644 --- a/src/ripple/ledger/Sandbox.h +++ b/src/xrpld/ledger/Sandbox.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_LEDGER_SANDBOX_H_INCLUDED #define RIPPLE_LEDGER_SANDBOX_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/View.h b/src/xrpld/ledger/View.h similarity index 83% rename from src/ripple/ledger/View.h rename to src/xrpld/ledger/View.h index 5680114a79c..74027752486 100644 --- a/src/ripple/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -20,19 +20,20 @@ #ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED #define RIPPLE_LEDGER_VIEW_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -78,9 +79,18 @@ hasExpired(ReadView const& view, std::optional const& exp); /** Controls the treatment of frozen account balances */ enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN }; +/** Controls the treatment of unauthorized MPT balances */ +enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED }; + [[nodiscard]] bool isGlobalFrozen(ReadView const& view, AccountID const& issuer); +[[nodiscard]] bool +isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue); + +[[nodiscard]] bool +isGlobalFrozen(ReadView const& view, Asset const& asset); + [[nodiscard]] bool isIndividualFrozen( ReadView const& view, @@ -97,6 +107,25 @@ isIndividualFrozen( return isIndividualFrozen(view, account, issue.currency, issue.account); } +[[nodiscard]] bool +isIndividualFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue); + +[[nodiscard]] inline bool +isIndividualFrozen( + ReadView const& view, + AccountID const& account, + Asset const& asset) +{ + return std::visit( + [&](auto const& issue) { + return isIndividualFrozen(view, account, issue); + }, + asset.value()); +} + [[nodiscard]] bool isFrozen( ReadView const& view, @@ -110,6 +139,20 @@ isFrozen(ReadView const& view, AccountID const& account, Issue const& issue) return isFrozen(view, account, issue.currency, issue.account); } +[[nodiscard]] bool +isFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue); + +[[nodiscard]] inline bool +isFrozen(ReadView const& view, AccountID const& account, Asset const& asset) +{ + return std::visit( + [&](auto const& issue) { return isFrozen(view, account, issue); }, + asset.value()); +} + // Returns the amount an account can spend without going into debt. // // <-- saAmount: amount of currency held by account. May be negative. @@ -130,6 +173,15 @@ accountHolds( FreezeHandling zeroIfFrozen, beast::Journal j); +[[nodiscard]] STAmount +accountHolds( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue, + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j); + // Returns the amount an account can spend of the currency type saDefault, or // returns saDefault if this account is the issuer of the currency in // question. Should be used in favor of accountHolds when questioning how much @@ -206,9 +258,22 @@ forEachItemAfter( return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f); } +/** Returns IOU issuer transfer fee as Rate. Rate specifies + * the fee as fractions of 1 billion. For example, 1% transfer rate + * is represented as 1,010,000,000. + * @param issuer The IOU issuer + */ [[nodiscard]] Rate transferRate(ReadView const& view, AccountID const& issuer); +/** Returns MPT transfer fee as Rate. Rate specifies + * the fee as fractions of 1 billion. For example, 1% transfer rate + * is represented as 1,010,000,000. + * @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object + */ +[[nodiscard]] Rate +transferRate(ReadView const& view, MPTID const& issuanceID); + /** Returns `true` if the directory is empty @param key The key of the directory */ @@ -410,21 +475,28 @@ offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j); // - Create trust line of needed. // --> bCheckIssuer : normally require issuer to be involved. // [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles. + +/** Calls static rippleCreditIOU if saAmount represents Issue. + * Calls static rippleCreditMPT if saAmount represents MPTIssue. + */ TER rippleCredit( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, - const STAmount& saAmount, + STAmount const& saAmount, bool bCheckIssuer, beast::Journal j); +/** Calls static accountSendIOU if saAmount represents Issue. + * Calls static accountSendMPT if saAmount represents MPTIssue. + */ [[nodiscard]] TER accountSend( ApplyView& view, AccountID const& from, AccountID const& to, - const STAmount& saAmount, + STAmount const& saAmount, beast::Journal j, WaiveTransferFee waiveFee = WaiveTransferFee::No); @@ -452,12 +524,28 @@ transferXRP( STAmount const& amount, beast::Journal j); -/** Check if the account requires authorization. +/** Check if the account lacks required authorization. * Return tecNO_AUTH or tecNO_LINE if it does * and tesSUCCESS otherwise. */ [[nodiscard]] TER requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); +[[nodiscard]] TER +requireAuth( + ReadView const& view, + MPTIssue const& mptIssue, + AccountID const& account); + +/** Check if the destination account is allowed + * to receive MPT. Return tecNO_AUTH if it doesn't + * and tesSUCCESS otherwise. + */ +[[nodiscard]] TER +canTransfer( + ReadView const& view, + MPTIssue const& mptIssue, + AccountID const& from, + AccountID const& to); /** Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status diff --git a/src/ripple/ledger/impl/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp similarity index 99% rename from src/ripple/ledger/impl/ApplyStateTable.cpp rename to src/xrpld/ledger/detail/ApplyStateTable.cpp index ab2f0ca8cdb..b849365c83f 100644 --- a/src/ripple/ledger/impl/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/ledger/detail/ApplyStateTable.h b/src/xrpld/ledger/detail/ApplyStateTable.h similarity index 93% rename from src/ripple/ledger/detail/ApplyStateTable.h rename to src/xrpld/ledger/detail/ApplyStateTable.h index 3fbc4960973..d1616d095e5 100644 --- a/src/ripple/ledger/detail/ApplyStateTable.h +++ b/src/xrpld/ledger/detail/ApplyStateTable.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED #define RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/ledger/impl/ApplyView.cpp b/src/xrpld/ledger/detail/ApplyView.cpp similarity index 98% rename from src/ripple/ledger/impl/ApplyView.cpp rename to src/xrpld/ledger/detail/ApplyView.cpp index eced521fb5d..735aa9906e0 100644 --- a/src/ripple/ledger/impl/ApplyView.cpp +++ b/src/xrpld/ledger/detail/ApplyView.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { @@ -313,7 +313,7 @@ ApplyView::dirRemove( prev->setFieldU64(sfIndexNext, rootPage); update(prev); - // And the root points to the the last page: + // And the root points to the last page: auto root = peek(keylet::page(directory, rootPage)); if (!root) LogicError("Directory chain: root link broken."); diff --git a/src/ripple/ledger/impl/ApplyViewBase.cpp b/src/xrpld/ledger/detail/ApplyViewBase.cpp similarity index 97% rename from src/ripple/ledger/impl/ApplyViewBase.cpp rename to src/xrpld/ledger/detail/ApplyViewBase.cpp index ac87aabb47f..647927ae965 100644 --- a/src/ripple/ledger/impl/ApplyViewBase.cpp +++ b/src/xrpld/ledger/detail/ApplyViewBase.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace detail { diff --git a/src/ripple/ledger/detail/ApplyViewBase.h b/src/xrpld/ledger/detail/ApplyViewBase.h similarity index 94% rename from src/ripple/ledger/detail/ApplyViewBase.h rename to src/xrpld/ledger/detail/ApplyViewBase.h index 68c210c7b71..8305731b29e 100644 --- a/src/ripple/ledger/detail/ApplyViewBase.h +++ b/src/xrpld/ledger/detail/ApplyViewBase.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_LEDGER_APPLYVIEWBASE_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWBASE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { namespace detail { diff --git a/src/ripple/ledger/impl/ApplyViewImpl.cpp b/src/xrpld/ledger/detail/ApplyViewImpl.cpp similarity index 95% rename from src/ripple/ledger/impl/ApplyViewImpl.cpp rename to src/xrpld/ledger/detail/ApplyViewImpl.cpp index 155ff4df99f..ffbf681cd20 100644 --- a/src/ripple/ledger/impl/ApplyViewImpl.cpp +++ b/src/xrpld/ledger/detail/ApplyViewImpl.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/ledger/impl/BookDirs.cpp b/src/xrpld/ledger/detail/BookDirs.cpp similarity index 96% rename from src/ripple/ledger/impl/BookDirs.cpp rename to src/xrpld/ledger/detail/BookDirs.cpp index f8589d5575c..58527b02ff2 100644 --- a/src/ripple/ledger/impl/BookDirs.cpp +++ b/src/xrpld/ledger/detail/BookDirs.cpp @@ -18,9 +18,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/impl/CachedView.cpp b/src/xrpld/ledger/detail/CachedView.cpp similarity index 80% rename from src/ripple/ledger/impl/CachedView.cpp rename to src/xrpld/ledger/detail/CachedView.cpp index 210031346d3..645a2c79c13 100644 --- a/src/ripple/ledger/impl/CachedView.cpp +++ b/src/xrpld/ledger/detail/CachedView.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { namespace detail { @@ -63,20 +63,17 @@ CachedViewImpl::read(Keylet const& k) const hits.increment(); else misses.increment(); - std::lock_guard lock(mutex_); - auto const er = map_.emplace(k.key, *digest); - bool const inserted = er.second; - if (sle && !k.check(*sle)) + + if (!cacheHit) { - if (!inserted) - { - // On entry, this function did not find this key in map_. Now - // something (another thread?) has inserted the sle into the map and - // it has the wrong type. - LogicError("CachedView::read: wrong type"); - } - return nullptr; + // Avoid acquiring this lock unless necessary. It is only necessary if + // the key was not found in the map_. The lock is needed to add the key + // and digest. + std::lock_guard lock(mutex_); + map_.emplace(k.key, *digest); } + if (!sle || !k.check(*sle)) + return nullptr; return sle; } diff --git a/src/ripple/ledger/impl/Directory.cpp b/src/xrpld/ledger/detail/Dir.cpp similarity index 98% rename from src/ripple/ledger/impl/Directory.cpp rename to src/xrpld/ledger/detail/Dir.cpp index 759b4d71b74..6004a73095c 100644 --- a/src/ripple/ledger/impl/Directory.cpp +++ b/src/xrpld/ledger/detail/Dir.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/ledger/impl/OpenView.cpp b/src/xrpld/ledger/detail/OpenView.cpp similarity index 98% rename from src/ripple/ledger/impl/OpenView.cpp rename to src/xrpld/ledger/detail/OpenView.cpp index 67858ad5667..619006161f8 100644 --- a/src/ripple/ledger/impl/OpenView.cpp +++ b/src/xrpld/ledger/detail/OpenView.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/ledger/impl/PaymentSandbox.cpp b/src/xrpld/ledger/detail/PaymentSandbox.cpp similarity index 97% rename from src/ripple/ledger/impl/PaymentSandbox.cpp rename to src/xrpld/ledger/detail/PaymentSandbox.cpp index bbe2c313491..d182d22b56c 100644 --- a/src/ripple/ledger/impl/PaymentSandbox.cpp +++ b/src/xrpld/ledger/detail/PaymentSandbox.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include @@ -180,7 +180,7 @@ PaymentSandbox::balanceHook( algorithm remembers the original balance, and subtracts the debits. The post-switchover algorithm should be more numerically stable. Consider a large credit with a small initial balance. The pre-switchover algorithm - computes (B+C)-C (where B+C will the the amount passed in). The + computes (B+C)-C (where B+C will the amount passed in). The post-switchover algorithm returns B. When B and C differ by large magnitudes, (B+C)-C may not equal B. */ diff --git a/src/ripple/ledger/impl/RawStateTable.cpp b/src/xrpld/ledger/detail/RawStateTable.cpp similarity index 99% rename from src/ripple/ledger/impl/RawStateTable.cpp rename to src/xrpld/ledger/detail/RawStateTable.cpp index 019ef6156c2..4d3732f8a45 100644 --- a/src/ripple/ledger/impl/RawStateTable.cpp +++ b/src/xrpld/ledger/detail/RawStateTable.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace detail { diff --git a/src/ripple/ledger/detail/RawStateTable.h b/src/xrpld/ledger/detail/RawStateTable.h similarity index 98% rename from src/ripple/ledger/detail/RawStateTable.h rename to src/xrpld/ledger/detail/RawStateTable.h index 2bb38dc49d8..2db37d98333 100644 --- a/src/ripple/ledger/detail/RawStateTable.h +++ b/src/xrpld/ledger/detail/RawStateTable.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_LEDGER_RAWSTATETABLE_H_INCLUDED #define RIPPLE_LEDGER_RAWSTATETABLE_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/ledger/impl/ReadView.cpp b/src/xrpld/ledger/detail/ReadView.cpp similarity index 98% rename from src/ripple/ledger/impl/ReadView.cpp rename to src/xrpld/ledger/detail/ReadView.cpp index 1ce21777297..69a4b5d6a95 100644 --- a/src/ripple/ledger/impl/ReadView.cpp +++ b/src/xrpld/ledger/detail/ReadView.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/ripple/ledger/detail/ReadViewFwdRange.h b/src/xrpld/ledger/detail/ReadViewFwdRange.h similarity index 100% rename from src/ripple/ledger/detail/ReadViewFwdRange.h rename to src/xrpld/ledger/detail/ReadViewFwdRange.h diff --git a/src/ripple/ledger/detail/ReadViewFwdRange.ipp b/src/xrpld/ledger/detail/ReadViewFwdRange.ipp similarity index 100% rename from src/ripple/ledger/detail/ReadViewFwdRange.ipp rename to src/xrpld/ledger/detail/ReadViewFwdRange.ipp diff --git a/src/ripple/ledger/impl/View.cpp b/src/xrpld/ledger/detail/View.cpp similarity index 79% rename from src/ripple/ledger/impl/View.cpp rename to src/xrpld/ledger/detail/View.cpp index b71b4f39e5e..ae4eb095017 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -177,6 +177,27 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer) return false; } +bool +isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue) +{ + if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()))) + return sle->getFlags() & lsfMPTLocked; + return false; +} + +bool +isGlobalFrozen(ReadView const& view, Asset const& asset) +{ + return std::visit( + [&](TIss const& issue) { + if constexpr (std::is_same_v) + return isGlobalFrozen(view, issue.getIssuer()); + else + return isGlobalFrozen(view, issue); + }, + asset.value()); +} + bool isIndividualFrozen( ReadView const& view, @@ -197,6 +218,18 @@ isIndividualFrozen( return false; } +bool +isIndividualFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue) +{ + if (auto const sle = + view.read(keylet::mptoken(mptIssue.getMptID(), account))) + return sle->getFlags() & lsfMPTLocked; + return false; +} + // Can the specified account spend the specified currency issued by // the specified issuer or does the freeze flag prohibit it? bool @@ -222,6 +255,16 @@ isFrozen( return false; } +bool +isFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue) +{ + return isGlobalFrozen(view, mptIssue) || + isIndividualFrozen(view, account, mptIssue); +} + STAmount accountHolds( ReadView const& view, @@ -241,13 +284,13 @@ accountHolds( auto const sle = view.read(keylet::line(account, issuer, currency)); if (!sle) { - amount.clear({currency, issuer}); + amount.clear(Issue{currency, issuer}); } else if ( (zeroIfFrozen == fhZERO_IF_FROZEN) && isFrozen(view, account, currency, issuer)) { - amount.clear(Issue(currency, issuer)); + amount.clear(Issue{currency, issuer}); } else { @@ -259,8 +302,7 @@ accountHolds( } amount.setIssuer(issuer); } - JLOG(j.trace()) << "accountHolds:" - << " account=" << to_string(account) + JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account) << " amount=" << amount.getFullText(); return view.balanceHook(account, issuer, amount); @@ -278,6 +320,46 @@ accountHolds( view, account, issue.currency, issue.account, zeroIfFrozen, j); } +STAmount +accountHolds( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue, + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j) +{ + STAmount amount; + + auto const sleMpt = + view.read(keylet::mptoken(mptIssue.getMptID(), account)); + if (!sleMpt) + amount.clear(mptIssue); + else if ( + zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue)) + amount.clear(mptIssue); + else + { + amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)}; + + // only if auth check is needed, as it needs to do an additional read + // operation + if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED) + { + auto const sleIssuance = + view.read(keylet::mptIssuance(mptIssue.getMptID())); + + // if auth is enabled on the issuance and mpt is not authorized, + // clear amount + if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) && + !sleMpt->isFlag(lsfMPTAuthorized)) + amount.clear(mptIssue); + } + } + + return amount; +} + STAmount accountFunds( ReadView const& view, @@ -369,8 +451,7 @@ xrpLiquid( STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve; - JLOG(j.trace()) << "accountHolds:" - << " account=" << to_string(id) + JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id) << " amount=" << amount.getFullText() << " fullBalance=" << fullBalance.getFullText() << " balance=" << balance.getFullText() @@ -495,6 +576,19 @@ transferRate(ReadView const& view, AccountID const& issuer) return parityRate; } +Rate +transferRate(ReadView const& view, MPTID const& issuanceID) +{ + // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000 + // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000 + // which represents 50% of 1,000,000,000 + if (auto const sle = view.read(keylet::mptIssuance(issuanceID)); + sle && sle->isFieldPresent(sfTransferFee)) + return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)}; + + return parityRate; +} + bool areCompatible( ReadView const& validLedger, @@ -820,9 +914,8 @@ trustCreate( bSetHigh ? sfHighLimit : sfLowLimit, saLimit); sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, - STAmount( - {saBalance.getCurrency(), - bSetDst ? uSrcAccountID : uDstAccountID})); + STAmount(Issue{ + saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID})); if (uQualityIn) sleRippleState->setFieldU32( @@ -946,8 +1039,8 @@ offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) // - Redeeming IOUs and/or sending sender's own IOUs. // - Create trust line if needed. // --> bCheckIssuer : normally require issuer to be involved. -TER -rippleCredit( +static TER +rippleCreditIOU( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -985,7 +1078,7 @@ rippleCredit( saBalance -= saAmount; - JLOG(j.trace()) << "rippleCredit: " << to_string(uSenderID) << " -> " + JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : before=" << saBefore.getFullText() << " amount=" << saAmount.getFullText() @@ -1055,12 +1148,12 @@ rippleCredit( return tesSUCCESS; } - STAmount const saReceiverLimit({currency, uReceiverID}); + STAmount const saReceiverLimit(Issue{currency, uReceiverID}); STAmount saBalance{saAmount}; saBalance.setIssuer(noAccount()); - JLOG(j.debug()) << "rippleCredit: " + JLOG(j.debug()) << "rippleCreditIOU: " "create line: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); @@ -1092,7 +1185,7 @@ rippleCredit( // --> saAmount: Amount/currency/issuer to deliver to receiver. // <-- saActual: Amount actually cost. Sender pays fees. static TER -rippleSend( +rippleSendIOU( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -1110,7 +1203,7 @@ rippleSend( { // Direct send: redeeming IOUs and/or sending own IOUs. auto const ter = - rippleCredit(view, uSenderID, uReceiverID, saAmount, false, j); + rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j); if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS) return ter; saActual = saAmount; @@ -1125,21 +1218,22 @@ rippleSend( ? saAmount : multiply(saAmount, transferRate(view, issuer)); - JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " + JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > " << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - TER terResult = rippleCredit(view, issuer, uReceiverID, saAmount, true, j); + TER terResult = + rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j); if (tesSUCCESS == terResult) - terResult = rippleCredit(view, uSenderID, issuer, saActual, true, j); + terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j); return terResult; } -TER -accountSend( +static TER +accountSendIOU( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -1149,14 +1243,14 @@ accountSend( { if (view.rules().enabled(fixAMMv1_1)) { - if (saAmount < beast::zero) + if (saAmount < beast::zero || saAmount.holds()) { return tecINTERNAL; } } else { - assert(saAmount >= beast::zero); + assert(saAmount >= beast::zero && !saAmount.holds()); } /* If we aren't sending anything or if the sender is the same as the @@ -1169,11 +1263,11 @@ accountSend( { STAmount saActual; - JLOG(j.trace()) << "accountSend: " << to_string(uSenderID) << " -> " + JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); - return rippleSend( + return rippleSendIOU( view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); } @@ -1202,9 +1296,9 @@ accountSend( if (receiver) receiver_bal = receiver->getFieldAmount(sfBalance).getFullText(); - stream << "accountSend> " << to_string(uSenderID) << " (" << sender_bal - << ") -> " << to_string(uReceiverID) << " (" << receiver_bal - << ") : " << saAmount.getFullText(); + stream << "accountSendIOU> " << to_string(uSenderID) << " (" + << sender_bal << ") -> " << to_string(uReceiverID) << " (" + << receiver_bal << ") : " << saAmount.getFullText(); } if (sender) @@ -1248,14 +1342,182 @@ accountSend( if (receiver) receiver_bal = receiver->getFieldAmount(sfBalance).getFullText(); - stream << "accountSend< " << to_string(uSenderID) << " (" << sender_bal - << ") -> " << to_string(uReceiverID) << " (" << receiver_bal - << ") : " << saAmount.getFullText(); + stream << "accountSendIOU< " << to_string(uSenderID) << " (" + << sender_bal << ") -> " << to_string(uReceiverID) << " (" + << receiver_bal << ") : " << saAmount.getFullText(); } return terResult; } +static TER +rippleCreditMPT( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + beast::Journal j) +{ + auto const mptID = keylet::mptIssuance(saAmount.get().getMptID()); + auto const issuer = saAmount.getIssuer(); + auto sleIssuance = view.peek(mptID); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + if (uSenderID == issuer) + { + (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value(); + view.update(sleIssuance); + } + else + { + auto const mptokenID = keylet::mptoken(mptID.key, uSenderID); + if (auto sle = view.peek(mptokenID)) + { + auto const amt = sle->getFieldU64(sfMPTAmount); + auto const pay = saAmount.mpt().value(); + if (amt < pay) + return tecINSUFFICIENT_FUNDS; + (*sle)[sfMPTAmount] = amt - pay; + view.update(sle); + } + else + return tecNO_AUTH; + } + + if (uReceiverID == issuer) + { + auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount); + auto const redeem = saAmount.mpt().value(); + if (outstanding >= redeem) + { + sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem); + view.update(sleIssuance); + } + else + return tecINTERNAL; + } + else + { + auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID); + if (auto sle = view.peek(mptokenID)) + { + (*sle)[sfMPTAmount] += saAmount.mpt().value(); + view.update(sle); + } + else + return tecNO_AUTH; + } + return tesSUCCESS; +} + +static TER +rippleSendMPT( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + STAmount& saActual, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + assert(uSenderID != uReceiverID); + + // Safe to get MPT since rippleSendMPT is only called by accountSendMPT + auto const issuer = saAmount.getIssuer(); + + auto const sle = + view.read(keylet::mptIssuance(saAmount.get().getMptID())); + if (!sle) + return tecOBJECT_NOT_FOUND; + + if (uSenderID == issuer || uReceiverID == issuer) + { + // if sender is issuer, check that the new OutstandingAmount will not + // exceed MaximumAmount + if (uSenderID == issuer) + { + auto const sendAmount = saAmount.mpt().value(); + auto const maximumAmount = + sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); + if (sendAmount > maximumAmount || + sle->getFieldU64(sfOutstandingAmount) > + maximumAmount - sendAmount) + return tecPATH_DRY; + } + + // Direct send: redeeming MPTs and/or sending own MPTs. + auto const ter = + rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j); + if (ter != tesSUCCESS) + return ter; + saActual = saAmount; + return tesSUCCESS; + } + + // Sending 3rd party MPTs: transit. + saActual = (waiveFee == WaiveTransferFee::Yes) + ? saAmount + : multiply( + saAmount, + transferRate(view, saAmount.get().getMptID())); + + JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > " + << to_string(uReceiverID) + << " : deliver=" << saAmount.getFullText() + << " cost=" << saActual.getFullText(); + + if (auto const terResult = + rippleCreditMPT(view, issuer, uReceiverID, saAmount, j); + terResult != tesSUCCESS) + return terResult; + + return rippleCreditMPT(view, uSenderID, issuer, saActual, j); +} + +static TER +accountSendMPT( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + assert(saAmount >= beast::zero && saAmount.holds()); + + /* If we aren't sending anything or if the sender is the same as the + * receiver then we don't need to do anything. + */ + if (!saAmount || (uSenderID == uReceiverID)) + return tesSUCCESS; + + STAmount saActual{saAmount.asset()}; + + return rippleSendMPT( + view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); +} + +TER +accountSend( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + return std::visit( + [&](TIss const& issue) { + if constexpr (std::is_same_v) + return accountSendIOU( + view, uSenderID, uReceiverID, saAmount, j, waiveFee); + else + return accountSendMPT( + view, uSenderID, uReceiverID, saAmount, j, waiveFee); + }, + saAmount.asset().value()); +} + static bool updateTrustLine( ApplyView& view, @@ -1377,7 +1639,7 @@ issueIOU( // NIKB TODO: The limit uses the receiver's account as the issuer and // this is unnecessarily inefficient as copying which could be avoided // is now required. Consider available options. - STAmount const limit({issue.currency, account}); + STAmount const limit(Issue{issue.currency, account}); STAmount final_balance = amount; final_balance.setIssuer(noAccount()); @@ -1537,6 +1799,59 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account) return tesSUCCESS; } +TER +requireAuth( + ReadView const& view, + MPTIssue const& mptIssue, + AccountID const& account) +{ + auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); + auto const sleIssuance = view.read(mptID); + + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + auto const mptIssuer = sleIssuance->getAccountID(sfIssuer); + + // issuer is always "authorized" + if (mptIssuer == account) + return tesSUCCESS; + + auto const mptokenID = keylet::mptoken(mptID.key, account); + auto const sleToken = view.read(mptokenID); + + // if account has no MPToken, fail + if (!sleToken) + return tecNO_AUTH; + + // mptoken must be authorized if issuance enabled requireAuth + if (sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth && + !(sleToken->getFlags() & lsfMPTAuthorized)) + return tecNO_AUTH; + + return tesSUCCESS; +} + +TER +canTransfer( + ReadView const& view, + MPTIssue const& mptIssue, + AccountID const& from, + AccountID const& to) +{ + auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); + auto const sleIssuance = view.read(mptID); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer)) + { + if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer]) + return TER{tecNO_AUTH}; + } + return tesSUCCESS; +} + TER cleanupOnAccountDelete( ApplyView& view, @@ -1662,4 +1977,30 @@ deleteAMMTrustLine( return tesSUCCESS; } +TER +rippleCredit( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + bool bCheckIssuer, + beast::Journal j) +{ + return std::visit( + [&](TIss const& issue) { + if constexpr (std::is_same_v) + { + return rippleCreditIOU( + view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); + } + else + { + assert(!bCheckIssuer); + return rippleCreditMPT( + view, uSenderID, uReceiverID, saAmount, j); + } + }, + saAmount.asset().value()); +} + } // namespace ripple diff --git a/src/ripple/net/AutoSocket.h b/src/xrpld/net/AutoSocket.h similarity index 99% rename from src/ripple/net/AutoSocket.h rename to src/xrpld/net/AutoSocket.h index e64c7a1e5cf..be35c082461 100644 --- a/src/ripple/net/AutoSocket.h +++ b/src/xrpld/net/AutoSocket.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_WEBSOCKET_AUTOSOCKET_AUTOSOCKET_H_INCLUDED #define RIPPLE_WEBSOCKET_AUTOSOCKET_AUTOSOCKET_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/net/HTTPClient.h b/src/xrpld/net/HTTPClient.h similarity index 97% rename from src/ripple/net/HTTPClient.h rename to src/xrpld/net/HTTPClient.h index f133e3ee04a..3b7da776ea4 100644 --- a/src/ripple/net/HTTPClient.h +++ b/src/xrpld/net/HTTPClient.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_NET_HTTPCLIENT_H_INCLUDED #define RIPPLE_NET_HTTPCLIENT_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/net/HTTPClientSSLContext.h b/src/xrpld/net/HTTPClientSSLContext.h similarity index 97% rename from src/ripple/net/HTTPClientSSLContext.h rename to src/xrpld/net/HTTPClientSSLContext.h index b0f6942ae13..64ca2461d23 100644 --- a/src/ripple/net/HTTPClientSSLContext.h +++ b/src/xrpld/net/HTTPClientSSLContext.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_NET_HTTPCLIENTSSLCONTEXT_H_INCLUDED #define RIPPLE_NET_HTTPCLIENTSSLCONTEXT_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/net/InfoSub.h b/src/xrpld/net/InfoSub.h similarity index 97% rename from src/ripple/net/InfoSub.h rename to src/xrpld/net/InfoSub.h index 33441dde632..08fba26ff00 100644 --- a/src/ripple/net/InfoSub.h +++ b/src/xrpld/net/InfoSub.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_NET_INFOSUB_H_INCLUDED #define RIPPLE_NET_INFOSUB_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/net/RPCCall.h b/src/xrpld/net/RPCCall.h similarity index 96% rename from src/ripple/net/RPCCall.h rename to src/xrpld/net/RPCCall.h index 600fad28e58..9c78bb20d25 100644 --- a/src/ripple/net/RPCCall.h +++ b/src/xrpld/net/RPCCall.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_NET_RPCCALL_H_INCLUDED #define RIPPLE_NET_RPCCALL_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/net/RPCSub.h b/src/xrpld/net/RPCSub.h similarity index 96% rename from src/ripple/net/RPCSub.h rename to src/xrpld/net/RPCSub.h index e2193b1ce1a..b2a751d4a19 100644 --- a/src/ripple/net/RPCSub.h +++ b/src/xrpld/net/RPCSub.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_NET_RPCSUB_H_INCLUDED #define RIPPLE_NET_RPCSUB_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/net/RegisterSSLCerts.h b/src/xrpld/net/RegisterSSLCerts.h similarity index 98% rename from src/ripple/net/RegisterSSLCerts.h rename to src/xrpld/net/RegisterSSLCerts.h index 8b09f1031d7..88664139f19 100644 --- a/src/ripple/net/RegisterSSLCerts.h +++ b/src/xrpld/net/RegisterSSLCerts.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NET_REGISTER_SSL_CERTS_H_INCLUDED #define RIPPLE_NET_REGISTER_SSL_CERTS_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/net/impl/HTTPClient.cpp b/src/xrpld/net/detail/HTTPClient.cpp similarity index 98% rename from src/ripple/net/impl/HTTPClient.cpp rename to src/xrpld/net/detail/HTTPClient.cpp index 6ab01317516..61c2875a5fa 100644 --- a/src/ripple/net/impl/HTTPClient.cpp +++ b/src/xrpld/net/detail/HTTPClient.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/net/impl/InfoSub.cpp b/src/xrpld/net/detail/InfoSub.cpp similarity index 99% rename from src/ripple/net/impl/InfoSub.cpp rename to src/xrpld/net/detail/InfoSub.cpp index 5b37cf0759b..3dc891cb5f9 100644 --- a/src/ripple/net/impl/InfoSub.cpp +++ b/src/xrpld/net/detail/InfoSub.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/xrpld/net/detail/RPCCall.cpp similarity index 95% rename from src/ripple/net/impl/RPCCall.cpp rename to src/xrpld/net/detail/RPCCall.cpp index 8537f76b703..b92f4b1a205 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/xrpld/net/detail/RPCCall.cpp @@ -17,27 +17,27 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -189,36 +189,6 @@ class RPCParser return v; } - Json::Value - parseDownloadShard(Json::Value const& jvParams) - { - Json::Value jvResult(Json::objectValue); - unsigned int sz{jvParams.size()}; - unsigned int i{0}; - - // If odd number of params then 'novalidate' may have been specified - if (sz & 1) - { - if (boost::iequals(jvParams[0u].asString(), "novalidate")) - ++i; - else if (!boost::iequals(jvParams[--sz].asString(), "novalidate")) - return rpcError(rpcINVALID_PARAMS); - } - - // Create the 'shards' array - Json::Value shards(Json::arrayValue); - for (; i < sz; i += 2) - { - Json::Value shard(Json::objectValue); - shard[jss::index] = jvParams[i].asUInt(); - shard[jss::url] = jvParams[i + 1].asString(); - shards.append(std::move(shard)); - } - jvResult[jss::shards] = std::move(shards); - - return jvResult; - } - Json::Value parseInternal(Json::Value const& jvParams) { @@ -446,7 +416,8 @@ class RPCParser return jvRequest; } - // deposit_authorized [] + // deposit_authorized + // [ [, ...]] Json::Value parseDepositAuthorized(Json::Value const& jvParams) { @@ -454,9 +425,17 @@ class RPCParser jvRequest[jss::source_account] = jvParams[0u].asString(); jvRequest[jss::destination_account] = jvParams[1u].asString(); - if (jvParams.size() == 3) + if (jvParams.size() >= 3) jvParseLedger(jvRequest, jvParams[2u].asString()); + // 8 credentials max + if ((jvParams.size() >= 4) && (jvParams.size() <= 11)) + { + jvRequest[jss::credentials] = Json::Value(Json::arrayValue); + for (uint32_t i = 3; i < jvParams.size(); ++i) + jvRequest[jss::credentials].append(jvParams[i].asString()); + } + return jvRequest; } @@ -870,15 +849,6 @@ class RPCParser return jvRequest; } - Json::Value - parseNodeToShard(Json::Value const& jvParams) - { - Json::Value jvRequest; - jvRequest[jss::action] = jvParams[0u].asString(); - - return jvRequest; - } - // peer_reservations_add [] Json::Value parsePeerReservationsAdd(Json::Value const& jvParams) @@ -1200,9 +1170,7 @@ class RPCParser {"channel_verify", &RPCParser::parseChannelVerify, 4, 4}, {"connect", &RPCParser::parseConnect, 1, 2}, {"consensus_info", &RPCParser::parseAsIs, 0, 0}, - {"crawl_shards", &RPCParser::parseAsIs, 0, 2}, - {"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 3}, - {"download_shard", &RPCParser::parseDownloadShard, 2, -1}, + {"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 11}, {"feature", &RPCParser::parseFeature, 0, 2}, {"fetch_info", &RPCParser::parseFetchInfo, 0, 1}, {"gateway_balances", &RPCParser::parseGatewayBalances, 1, -1}, @@ -1220,7 +1188,6 @@ class RPCParser {"log_level", &RPCParser::parseLogLevel, 0, 2}, {"logrotate", &RPCParser::parseAsIs, 0, 0}, {"manifest", &RPCParser::parseManifest, 1, 1}, - {"node_to_shard", &RPCParser::parseNodeToShard, 1, 1}, {"owner_info", &RPCParser::parseAccountItems, 1, 3}, {"peers", &RPCParser::parseAsIs, 0, 0}, {"ping", &RPCParser::parseAsIs, 0, 0}, @@ -1351,7 +1318,10 @@ struct RPCCallImp // Receive reply if (strData.empty()) - Throw("no response from server"); + Throw( + "no response from server. Please " + "ensure that the rippled server is running in another " + "process."); // Parse reply JLOG(j.debug()) << "RPC reply: " << strData << std::endl; diff --git a/src/ripple/net/impl/RPCSub.cpp b/src/xrpld/net/detail/RPCSub.cpp similarity index 96% rename from src/ripple/net/impl/RPCSub.cpp rename to src/xrpld/net/detail/RPCSub.cpp index 8b052e817cb..13bdf9119b4 100644 --- a/src/ripple/net/impl/RPCSub.cpp +++ b/src/xrpld/net/detail/RPCSub.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/net/impl/RegisterSSLCerts.cpp b/src/xrpld/net/detail/RegisterSSLCerts.cpp similarity index 98% rename from src/ripple/net/impl/RegisterSSLCerts.cpp rename to src/xrpld/net/detail/RegisterSSLCerts.cpp index b4e3f51a93d..ede42b03af2 100644 --- a/src/ripple/net/impl/RegisterSSLCerts.cpp +++ b/src/xrpld/net/detail/RegisterSSLCerts.cpp @@ -16,7 +16,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include +#include #include #if BOOST_OS_WINDOWS diff --git a/src/ripple/net/images/interrupt_sequence.png b/src/xrpld/net/images/interrupt_sequence.png similarity index 100% rename from src/ripple/net/images/interrupt_sequence.png rename to src/xrpld/net/images/interrupt_sequence.png diff --git a/src/ripple/net/images/states.png b/src/xrpld/net/images/states.png similarity index 100% rename from src/ripple/net/images/states.png rename to src/xrpld/net/images/states.png diff --git a/src/ripple/nodestore/Backend.h b/src/xrpld/nodestore/Backend.h similarity index 85% rename from src/ripple/nodestore/Backend.h rename to src/xrpld/nodestore/Backend.h index d773be74198..29f37553327 100644 --- a/src/ripple/nodestore/Backend.h +++ b/src/xrpld/nodestore/Backend.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_BACKEND_H_INCLUDED #define RIPPLE_NODESTORE_BACKEND_H_INCLUDED -#include +#include #include #include @@ -39,29 +39,6 @@ namespace NodeStore { class Backend { public: - template - struct Counters - { - Counters() = default; - Counters(Counters const&) = default; - - template - Counters(Counters const& other) - : writeDurationUs(other.writeDurationUs) - , writeRetries(other.writeRetries) - , writesDelayed(other.writesDelayed) - , readRetries(other.readRetries) - , readErrors(other.readErrors) - { - } - - T writeDurationUs = {}; - T writeRetries = {}; - T writesDelayed = {}; - T readRetries = {}; - T readErrors = {}; - }; - /** Destroy the backend. All open files are closed and flushed. If there are batched writes @@ -174,17 +151,6 @@ class Backend /** Returns the number of file descriptors the backend expects to need. */ virtual int fdRequired() const = 0; - - /** Returns read and write stats. - - @note The Counters struct is specific to and only used - by CassandraBackend. - */ - virtual std::optional> - counters() const - { - return std::nullopt; - } }; } // namespace NodeStore diff --git a/src/ripple/nodestore/Database.h b/src/xrpld/nodestore/Database.h similarity index 71% rename from src/ripple/nodestore/Database.h rename to src/xrpld/nodestore/Database.h index 0f9e95b23e1..bd25046fee2 100644 --- a/src/ripple/nodestore/Database.h +++ b/src/xrpld/nodestore/Database.h @@ -20,19 +20,17 @@ #ifndef RIPPLE_NODESTORE_DATABASE_H_INCLUDED #define RIPPLE_NODESTORE_DATABASE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include namespace ripple { -class Ledger; - namespace NodeStore { /** Persistency layer for NodeObject @@ -153,7 +151,7 @@ class Database @note This can be called concurrently. @param hash The key of the object to retrieve @param ledgerSeq The sequence of the ledger where the - object is stored, used by the shard store. + object is stored. @param callback Callback function when read completes */ virtual void @@ -162,14 +160,6 @@ class Database std::uint32_t ledgerSeq, std::function const&)>&& callback); - /** Store a ledger from a different database. - - @param srcLedger The ledger to store. - @return true if the operation was successful - */ - virtual bool - storeLedger(std::shared_ptr const& srcLedger) = 0; - /** Remove expired entries from the positive and negative caches. */ virtual void sweep() = 0; @@ -224,14 +214,6 @@ class Database bool isStopping() const; - /** @return The maximum number of ledgers stored in a shard - */ - [[nodiscard]] std::uint32_t - ledgersPerShard() const noexcept - { - return ledgersPerShard_; - } - /** @return The earliest ledger sequence allowed */ [[nodiscard]] std::uint32_t @@ -240,63 +222,6 @@ class Database return earliestLedgerSeq_; } - /** @return The earliest shard index - */ - [[nodiscard]] std::uint32_t - earliestShardIndex() const noexcept - { - return earliestShardIndex_; - } - - /** Calculates the first ledger sequence for a given shard index - - @param shardIndex The shard index considered - @return The first ledger sequence pertaining to the shard index - */ - [[nodiscard]] std::uint32_t - firstLedgerSeq(std::uint32_t shardIndex) const noexcept - { - assert(shardIndex >= earliestShardIndex_); - if (shardIndex <= earliestShardIndex_) - return earliestLedgerSeq_; - return 1 + (shardIndex * ledgersPerShard_); - } - - /** Calculates the last ledger sequence for a given shard index - - @param shardIndex The shard index considered - @return The last ledger sequence pertaining to the shard index - */ - [[nodiscard]] std::uint32_t - lastLedgerSeq(std::uint32_t shardIndex) const noexcept - { - assert(shardIndex >= earliestShardIndex_); - return (shardIndex + 1) * ledgersPerShard_; - } - - /** Calculates the shard index for a given ledger sequence - - @param ledgerSeq ledger sequence - @return The shard index of the ledger sequence - */ - [[nodiscard]] std::uint32_t - seqToShardIndex(std::uint32_t ledgerSeq) const noexcept - { - assert(ledgerSeq >= earliestLedgerSeq_); - return (ledgerSeq - 1) / ledgersPerShard_; - } - - /** Calculates the maximum ledgers for a given shard index - - @param shardIndex The shard index considered - @return The maximum ledgers pertaining to the shard index - - @note The earliest shard may store less if the earliest ledger - sequence truncates its beginning - */ - [[nodiscard]] std::uint32_t - maxLedgers(std::uint32_t shardIndex) const noexcept; - protected: beast::Journal const j_; Scheduler& scheduler_; @@ -305,25 +230,14 @@ class Database std::atomic fetchHitCount_{0}; std::atomic fetchSz_{0}; - // The default is DEFAULT_LEDGERS_PER_SHARD (16384) to match the XRP ledger - // network. Can be set through the configuration file using the - // 'ledgers_per_shard' field under the 'node_db' and 'shard_db' stanzas. - // If specified, the value must be a multiple of 256 and equally assigned - // in both stanzas. Only unit tests or alternate networks should change - // this value. - std::uint32_t const ledgersPerShard_; - // The default is XRP_LEDGER_EARLIEST_SEQ (32570) to match the XRP ledger // network's earliest allowed ledger sequence. Can be set through the // configuration file using the 'earliest_seq' field under the 'node_db' - // and 'shard_db' stanzas. If specified, the value must be greater than zero - // and equally assigned in both stanzas. Only unit tests or alternate + // stanza. If specified, the value must be greater than zero. + // Only unit tests or alternate // networks should change this value. std::uint32_t const earliestLedgerSeq_; - // The earliest shard index - std::uint32_t const earliestShardIndex_; - // The maximum number of requests a thread extracts from the queue in an // attempt to minimize the overhead of mutex acquisition. This is an // advanced tunable, via the config file. The default value is 4. @@ -341,10 +255,6 @@ class Database void importInternal(Backend& dstBackend, Database& srcDB); - // Called by the public storeLedger function - bool - storeLedger(Ledger const& srcLedger, std::shared_ptr dstBackend); - void updateFetchMetrics(uint64_t fetches, uint64_t hits, uint64_t duration) { @@ -392,17 +302,6 @@ class Database virtual void for_each(std::function)> f) = 0; - /** Retrieve backend read and write stats. - - @note The Counters struct is specific to and only used - by CassandraBackend. - */ - virtual std::optional> - getCounters() const - { - return std::nullopt; - } - void threadEntry(); }; diff --git a/src/ripple/nodestore/DatabaseRotating.h b/src/xrpld/nodestore/DatabaseRotating.h similarity index 98% rename from src/ripple/nodestore/DatabaseRotating.h rename to src/xrpld/nodestore/DatabaseRotating.h index c8d7104fe59..10f575c4662 100644 --- a/src/ripple/nodestore/DatabaseRotating.h +++ b/src/xrpld/nodestore/DatabaseRotating.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_DATABASEROTATING_H_INCLUDED #define RIPPLE_NODESTORE_DATABASEROTATING_H_INCLUDED -#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/DummyScheduler.h b/src/xrpld/nodestore/DummyScheduler.h similarity index 97% rename from src/ripple/nodestore/DummyScheduler.h rename to src/xrpld/nodestore/DummyScheduler.h index cfe7c3fd5dd..4e6fab7827d 100644 --- a/src/ripple/nodestore/DummyScheduler.h +++ b/src/xrpld/nodestore/DummyScheduler.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_DUMMYSCHEDULER_H_INCLUDED #define RIPPLE_NODESTORE_DUMMYSCHEDULER_H_INCLUDED -#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/Factory.h b/src/xrpld/nodestore/Factory.h similarity index 95% rename from src/ripple/nodestore/Factory.h rename to src/xrpld/nodestore/Factory.h index 4313b4ef12f..8853a6ce9a5 100644 --- a/src/ripple/nodestore/Factory.h +++ b/src/xrpld/nodestore/Factory.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_NODESTORE_FACTORY_H_INCLUDED #define RIPPLE_NODESTORE_FACTORY_H_INCLUDED -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/nodestore/Manager.h b/src/xrpld/nodestore/Manager.h similarity index 96% rename from src/ripple/nodestore/Manager.h rename to src/xrpld/nodestore/Manager.h index 26fdc8789eb..89ed165b483 100644 --- a/src/ripple/nodestore/Manager.h +++ b/src/xrpld/nodestore/Manager.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_NODESTORE_MANAGER_H_INCLUDED #define RIPPLE_NODESTORE_MANAGER_H_INCLUDED -#include -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/nodestore/NodeObject.h b/src/xrpld/nodestore/NodeObject.h similarity index 96% rename from src/ripple/nodestore/NodeObject.h rename to src/xrpld/nodestore/NodeObject.h index a94e689b34b..f5a1fdb075d 100644 --- a/src/ripple/nodestore/NodeObject.h +++ b/src/xrpld/nodestore/NodeObject.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_NODESTORE_NODEOBJECT_H_INCLUDED #define RIPPLE_NODESTORE_NODEOBJECT_H_INCLUDED -#include -#include -#include +#include +#include +#include // VFALCO NOTE Intentionally not in the NodeStore namespace diff --git a/src/xrpld/nodestore/README.md b/src/xrpld/nodestore/README.md new file mode 100644 index 00000000000..1549c1ef968 --- /dev/null +++ b/src/xrpld/nodestore/README.md @@ -0,0 +1,176 @@ +# Database Documentation +* [NodeStore](#nodestore) +* [Benchmarks](#benchmarks) + +# NodeStore + +## Introduction + +A `NodeObject` is a simple object that the Ledger uses to store entries. It is +comprised of a type, a hash and a blob. It can be uniquely +identified by the hash, which is a 256 bit hash of the blob. The blob is a +variable length block of serialized data. The type identifies what the blob +contains. The fields are as follows: + +* `mType` + + An enumeration that determines what the blob holds. There are four + different types of objects stored. + + * **ledger** + + A ledger header. + + * **transaction** + + A signed transaction. + + * **account node** + + A node in a ledger's account state tree. + + * **transaction node** + + A node in a ledger's transaction tree. + +* `mHash` + + A 256-bit hash of the blob. + +* `mData` + + A blob containing the payload. Stored in the following format. + +|Byte | | | +|:------|:--------------------|:-------------------------| +|0...7 |unused | | +|8 |type |NodeObjectType enumeration| +|9...end|data |body of the object data | +--- +The `NodeStore` provides an interface that stores, in a persistent database, a +collection of NodeObjects that rippled uses as its primary representation of +ledger entries. All ledger entries are stored as NodeObjects and as such, need +to be persisted between launches. If a NodeObject is accessed and is not in +memory, it will be retrieved from the database. + +## Backend + +The `NodeStore` implementation provides the `Backend` abstract interface, +which lets different key/value databases to be chosen at run-time. This allows +experimentation with different engines. Improvements in the performance of the +NodeStore are a constant area of research. The database can be specified in +the configuration file [node_db] section as follows. + +One or more lines of key / value pairs + +Example: +``` +type=RocksDB +path=rocksdb +compression=1 +``` +Choices for 'type' (not case-sensitive) + +* **HyperLevelDB** + + An improved version of LevelDB (preferred). + +* **LevelDB** + + Google's LevelDB database (deprecated). + +* **none** + + Use no backend. + +* **RocksDB** + + Facebook's RocksDB database, builds on LevelDB. + +* **SQLite** + + Use SQLite. + +'path' speficies where the backend will store its data files. + +Choices for 'compression' + +* **0** off + +* **1** on (default) + + +# Benchmarks + +The `NodeStore.Timing` test is used to execute a set of read/write workloads to +compare current available nodestore backends. It can be executed with: + +``` +$rippled --unittest=NodeStoreTiming +``` + +It is also possible to use alternate DB config params by passing config strings +as `--unittest-arg`. + +## Addendum + +The discussion below refers to a `RocksDBQuick` backend that has since been +removed from the code as it was not working and not maintained. That backend +primarily used one of the several rocks `Optimize*` methods to setup the +majority of the DB options/params, whereas the primary RocksDB backend exposes +many of the available config options directly. The code for RocksDBQuick can be +found in versions of this repo 1.2 and earlier if you need to refer back to it. +The conclusions below date from about 2014 and may need revisiting based on +newer versions of RocksDB (TBD). + +## Discussion + +RocksDBQuickFactory is intended to provide a testbed for comparing potential +rocksdb performance with the existing recommended configuration in rippled.cfg. +Through various executions and profiling some conclusions are presented below. + +* If the write ahead log is enabled, insert speed soon clogs up under load. The +BatchWriter class intends to stop this from blocking the main threads by queuing +up writes and running them in a separate thread. However, rocksdb already has +separate threads dedicated to flushing the memtable to disk and the memtable is +itself an in-memory queue. The result is two queues with a guarantee of +durability in between. However if the memtable was used as the sole queue and +the rocksdb::Flush() call was manually triggered at opportune moments, possibly +just after ledger close, then that would provide similar, but more predictable +guarantees. It would also remove an unneeded thread and unnecessary memory +usage. An alternative point of view is that because there will always be many +other rippled instances running there is no need for such guarantees. The nodes +will always be available from another peer. + +* Lookup in a block was previously using binary search. With rippled's use case +it is highly unlikely that two adjacent key/values will ever be requested one +after the other. Therefore hash indexing of blocks makes much more sense. +Rocksdb has a number of options for hash indexing both memtables and blocks and +these need more testing to find the best choice. + +* The current Database implementation has two forms of caching, so the LRU cache +of blocks at Factory level does not make any sense. However, if the hash +indexing and potentially the new [bloom +filter](http://rocksdb.org/blog/1427/new-bloom-filter-format/) can provide +faster lookup for non-existent keys, then potentially the caching could exist at +Factory level. + +* Multiple runs of the benchmarks can yield surprisingly different results. This +can perhaps be attributed to the asynchronous nature of rocksdb's compaction +process. The benchmarks are artifical and create highly unlikely write load to +create the dataset to measure different read access patterns. Therefore multiple +runs of the benchmarks are required to get a feel for the effectiveness of the +changes. This contrasts sharply with the keyvadb benchmarking were highly +repeatable timings were discovered. Also realistically sized datasets are +required to get a correct insight. The number of 2,000,000 key/values (actually +4,000,000 after the two insert benchmarks complete) is too low to get a full +picture. + +* An interesting side effect of running the benchmarks in a profiler was that a +clear pattern of what RocksDB does under the hood was observable. This led to +the decision to trial hash indexing and also the discovery of the native CRC32 +instruction not being used. + +* Important point to note that is if this factory is tested with an existing set +of sst files none of the old sst files will benefit from indexing changes until +they are compacted at a future point in time. diff --git a/src/ripple/nodestore/Scheduler.h b/src/xrpld/nodestore/Scheduler.h similarity index 98% rename from src/ripple/nodestore/Scheduler.h rename to src/xrpld/nodestore/Scheduler.h index c97f75ae59b..236c2260eed 100644 --- a/src/ripple/nodestore/Scheduler.h +++ b/src/xrpld/nodestore/Scheduler.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_SCHEDULER_H_INCLUDED #define RIPPLE_NODESTORE_SCHEDULER_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/nodestore/Task.h b/src/xrpld/nodestore/Task.h similarity index 100% rename from src/ripple/nodestore/Task.h rename to src/xrpld/nodestore/Task.h diff --git a/src/ripple/nodestore/Types.h b/src/xrpld/nodestore/Types.h similarity index 82% rename from src/ripple/nodestore/Types.h rename to src/xrpld/nodestore/Types.h index 6d8583ed9d1..a5792fe7df3 100644 --- a/src/ripple/nodestore/Types.h +++ b/src/xrpld/nodestore/Types.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_NODESTORE_TYPES_H_INCLUDED #define RIPPLE_NODESTORE_TYPES_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { @@ -56,15 +56,6 @@ using Batch = std::vector>; } // namespace NodeStore -/** Shard states. */ -enum class ShardState : std::uint32_t { - acquire, // Acquiring ledgers - complete, // Backend is ledger complete, database is unverified - finalizing, // Verifying database - finalized, // Database verified, shard is immutable - queued // Queued to be finalized -}; - } // namespace ripple #endif diff --git a/src/ripple/nodestore/backend/MemoryFactory.cpp b/src/xrpld/nodestore/backend/MemoryFactory.cpp similarity index 98% rename from src/ripple/nodestore/backend/MemoryFactory.cpp rename to src/xrpld/nodestore/backend/MemoryFactory.cpp index f82c14429ee..767db9d1695 100644 --- a/src/ripple/nodestore/backend/MemoryFactory.cpp +++ b/src/xrpld/nodestore/backend/MemoryFactory.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/nodestore/backend/NuDBFactory.cpp b/src/xrpld/nodestore/backend/NuDBFactory.cpp similarity index 90% rename from src/ripple/nodestore/backend/NuDBFactory.cpp rename to src/xrpld/nodestore/backend/NuDBFactory.cpp index 30b848e82af..14cd84a1ad7 100644 --- a/src/ripple/nodestore/backend/NuDBFactory.cpp +++ b/src/xrpld/nodestore/backend/NuDBFactory.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -38,11 +38,11 @@ namespace NodeStore { class NuDBBackend : public Backend { public: - static constexpr std::uint64_t currentType = 1; - static constexpr std::uint64_t deterministicMask = 0xFFFFFFFF00000000ull; - - /* "SHRD" in ASCII */ - static constexpr std::uint64_t deterministicType = 0x5348524400000000ull; + // "appnum" is an application-defined constant stored in the header of a + // NuDB database. We used it to identify shard databases before that code + // was removed. For now, its only use is a sanity check that the database + // was created by xrpld. + static constexpr std::uint64_t appnum = 1; beast::Journal const j_; size_t const keyBytes_; @@ -149,16 +149,7 @@ class NuDBBackend : public Backend if (ec) Throw(ec); - /** Old value currentType is accepted for appnum in traditional - * databases, new value is used for deterministic shard databases. - * New 64-bit value is constructed from fixed and random parts. - * Fixed part is bounded by bitmask deterministicMask, - * and the value of fixed part is deterministicType. - * Random part depends on the contents of the shard and may be any. - * The contents of appnum field should match either old or new rule. - */ - if (db_.appnum() != currentType && - (db_.appnum() & deterministicMask) != deterministicType) + if (db_.appnum() != appnum) Throw("nodestore: unknown appnum"); db_.set_burst(burstSize_); } @@ -172,7 +163,7 @@ class NuDBBackend : public Backend void open(bool createIfMissing) override { - open(createIfMissing, currentType, nudb::make_uid(), nudb::make_salt()); + open(createIfMissing, appnum, nudb::make_uid(), nudb::make_salt()); } void diff --git a/src/ripple/nodestore/backend/NullFactory.cpp b/src/xrpld/nodestore/backend/NullFactory.cpp similarity index 96% rename from src/ripple/nodestore/backend/NullFactory.cpp rename to src/xrpld/nodestore/backend/NullFactory.cpp index 8509787b318..47254cd50fc 100644 --- a/src/ripple/nodestore/backend/NullFactory.cpp +++ b/src/xrpld/nodestore/backend/NullFactory.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/nodestore/backend/RocksDBFactory.cpp b/src/xrpld/nodestore/backend/RocksDBFactory.cpp similarity index 96% rename from src/ripple/nodestore/backend/RocksDBFactory.cpp rename to src/xrpld/nodestore/backend/RocksDBFactory.cpp index b34560dba89..9ba9fffe1db 100644 --- a/src/ripple/nodestore/backend/RocksDBFactory.cpp +++ b/src/xrpld/nodestore/backend/RocksDBFactory.cpp @@ -17,20 +17,20 @@ */ //============================================================================== -#include +#include #if RIPPLE_ROCKSDB_AVAILABLE -#include -#include -#include -#include -#include // VFALCO Bad dependency -#include -#include -#include -#include -#include +#include // VFALCO Bad dependency +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/nodestore/impl/BatchWriter.cpp b/src/xrpld/nodestore/detail/BatchWriter.cpp similarity index 98% rename from src/ripple/nodestore/impl/BatchWriter.cpp rename to src/xrpld/nodestore/detail/BatchWriter.cpp index 692032016cc..1c5067d5bed 100644 --- a/src/ripple/nodestore/impl/BatchWriter.cpp +++ b/src/xrpld/nodestore/detail/BatchWriter.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/impl/BatchWriter.h b/src/xrpld/nodestore/detail/BatchWriter.h similarity index 96% rename from src/ripple/nodestore/impl/BatchWriter.h rename to src/xrpld/nodestore/detail/BatchWriter.h index 9ce4f120329..c48002f4f80 100644 --- a/src/ripple/nodestore/impl/BatchWriter.h +++ b/src/xrpld/nodestore/detail/BatchWriter.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_NODESTORE_BATCHWRITER_H_INCLUDED #define RIPPLE_NODESTORE_BATCHWRITER_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/nodestore/impl/Database.cpp b/src/xrpld/nodestore/detail/Database.cpp similarity index 67% rename from src/ripple/nodestore/impl/Database.cpp rename to src/xrpld/nodestore/detail/Database.cpp index 70416c873d5..da15088e895 100644 --- a/src/ripple/nodestore/impl/Database.cpp +++ b/src/xrpld/nodestore/detail/Database.cpp @@ -17,13 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -36,21 +35,13 @@ Database::Database( beast::Journal journal) : j_(journal) , scheduler_(scheduler) - , ledgersPerShard_(get( - config, - "ledgers_per_shard", - DEFAULT_LEDGERS_PER_SHARD)) , earliestLedgerSeq_( get(config, "earliest_seq", XRP_LEDGER_EARLIEST_SEQ)) - , earliestShardIndex_((earliestLedgerSeq_ - 1) / ledgersPerShard_) , requestBundle_(get(config, "rq_bundle", 4)) , readThreads_(std::max(1, readThreads)) { assert(readThreads != 0); - if (ledgersPerShard_ == 0 || ledgersPerShard_ % 256 != 0) - Throw("Invalid ledgers_per_shard"); - if (earliestLedgerSeq_ < 1) Throw("Invalid earliest_seq"); @@ -148,19 +139,6 @@ Database::isStopping() const return readStopping_.load(std::memory_order_relaxed); } -std::uint32_t -Database::maxLedgers(std::uint32_t shardIndex) const noexcept -{ - if (shardIndex > earliestShardIndex_) - return ledgersPerShard_; - - if (shardIndex == earliestShardIndex_) - return lastLedgerSeq(shardIndex) - firstLedgerSeq(shardIndex) + 1; - - assert(!"Invalid shard index"); - return 0; -} - void Database::stop() { @@ -275,105 +253,6 @@ Database::fetchNodeObject( return nodeObject; } -bool -Database::storeLedger( - Ledger const& srcLedger, - std::shared_ptr dstBackend) -{ - auto fail = [&](std::string const& msg) { - JLOG(j_.error()) << "Source ledger sequence " << srcLedger.info().seq - << ". " << msg; - return false; - }; - - if (srcLedger.info().hash.isZero()) - return fail("Invalid hash"); - if (srcLedger.info().accountHash.isZero()) - return fail("Invalid account hash"); - - auto& srcDB = const_cast(srcLedger.stateMap().family().db()); - if (&srcDB == this) - return fail("Source and destination databases are the same"); - - Batch batch; - batch.reserve(batchWritePreallocationSize); - auto storeBatch = [&, fname = __func__]() { - std::uint64_t sz{0}; - for (auto const& nodeObject : batch) - sz += nodeObject->getData().size(); - - try - { - dstBackend->storeBatch(batch); - } - catch (std::exception const& e) - { - fail( - std::string("Exception caught in function ") + fname + - ". Error: " + e.what()); - return false; - } - - storeStats(batch.size(), sz); - batch.clear(); - return true; - }; - - // Store ledger header - { - Serializer s(sizeof(std::uint32_t) + sizeof(LedgerInfo)); - s.add32(HashPrefix::ledgerMaster); - addRaw(srcLedger.info(), s); - auto nObj = NodeObject::createObject( - hotLEDGER, std::move(s.modData()), srcLedger.info().hash); - batch.emplace_back(std::move(nObj)); - } - - bool error = false; - auto visit = [&](SHAMapTreeNode& node) { - if (!isStopping()) - { - if (auto nodeObject = srcDB.fetchNodeObject( - node.getHash().as_uint256(), srcLedger.info().seq)) - { - batch.emplace_back(std::move(nodeObject)); - if (batch.size() < batchWritePreallocationSize || storeBatch()) - return true; - } - } - - error = true; - return false; - }; - - // Store the state map - if (srcLedger.stateMap().getHash().isNonZero()) - { - if (!srcLedger.stateMap().isValid()) - return fail("Invalid state map"); - - srcLedger.stateMap().snapShot(false)->visitNodes(visit); - if (error) - return fail("Failed to store state map"); - } - - // Store the transaction map - if (srcLedger.info().txHash.isNonZero()) - { - if (!srcLedger.txMap().isValid()) - return fail("Invalid transaction map"); - - srcLedger.txMap().snapShot(false)->visitNodes(visit); - if (error) - return fail("Failed to store transaction map"); - } - - if (!batch.empty() && !storeBatch()) - return fail("Failed to store"); - - return true; -} - void Database::getCountsJson(Json::Value& obj) { @@ -394,15 +273,6 @@ Database::getCountsJson(Json::Value& obj) obj[jss::node_written_bytes] = std::to_string(storeSz_); obj[jss::node_read_bytes] = std::to_string(fetchSz_); obj[jss::node_reads_duration_us] = std::to_string(fetchDurationUs_); - - if (auto c = getCounters()) - { - obj[jss::node_read_errors] = std::to_string(c->readErrors); - obj[jss::node_read_retries] = std::to_string(c->readRetries); - obj[jss::node_write_retries] = std::to_string(c->writeRetries); - obj[jss::node_writes_delayed] = std::to_string(c->writesDelayed); - obj[jss::node_writes_duration_us] = std::to_string(c->writeDurationUs); - } } } // namespace NodeStore diff --git a/src/ripple/nodestore/impl/DatabaseNodeImp.cpp b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp similarity index 98% rename from src/ripple/nodestore/impl/DatabaseNodeImp.cpp rename to src/xrpld/nodestore/detail/DatabaseNodeImp.cpp index 9ef878bf3be..85e5d3c0da9 100644 --- a/src/ripple/nodestore/impl/DatabaseNodeImp.cpp +++ b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include -#include -#include +#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/impl/DatabaseNodeImp.h b/src/xrpld/nodestore/detail/DatabaseNodeImp.h similarity index 90% rename from src/ripple/nodestore/impl/DatabaseNodeImp.h rename to src/xrpld/nodestore/detail/DatabaseNodeImp.h index 452bd8d27fe..326db38a661 100644 --- a/src/ripple/nodestore/impl/DatabaseNodeImp.h +++ b/src/xrpld/nodestore/detail/DatabaseNodeImp.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_NODESTORE_DATABASENODEIMP_H_INCLUDED #define RIPPLE_NODESTORE_DATABASENODEIMP_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { namespace NodeStore { @@ -106,7 +106,8 @@ class DatabaseNodeImp : public Database store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t) override; - bool isSameDB(std::uint32_t, std::uint32_t) override + bool + isSameDB(std::uint32_t, std::uint32_t) override { // only one database return true; @@ -128,12 +129,6 @@ class DatabaseNodeImp : public Database std::function const&)>&& callback) override; - bool - storeLedger(std::shared_ptr const& srcLedger) override - { - return Database::storeLedger(*srcLedger, backend_); - } - void sweep() override; @@ -156,12 +151,6 @@ class DatabaseNodeImp : public Database { backend_->for_each(f); } - - std::optional> - getCounters() const override - { - return backend_->counters(); - } }; } // namespace NodeStore diff --git a/src/ripple/nodestore/impl/DatabaseRotatingImp.cpp b/src/xrpld/nodestore/detail/DatabaseRotatingImp.cpp similarity index 93% rename from src/ripple/nodestore/impl/DatabaseRotatingImp.cpp rename to src/xrpld/nodestore/detail/DatabaseRotatingImp.cpp index 267b4ee581f..58cc3599dc6 100644 --- a/src/ripple/nodestore/impl/DatabaseRotatingImp.cpp +++ b/src/xrpld/nodestore/detail/DatabaseRotatingImp.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include -#include -#include +#include +#include namespace ripple { namespace NodeStore { @@ -79,17 +78,6 @@ DatabaseRotatingImp::importDatabase(Database& source) importInternal(*backend, source); } -bool -DatabaseRotatingImp::storeLedger(std::shared_ptr const& srcLedger) -{ - auto const backend = [&] { - std::lock_guard lock(mutex_); - return writableBackend_; - }(); - - return Database::storeLedger(*srcLedger, backend); -} - void DatabaseRotatingImp::sync() { diff --git a/src/ripple/nodestore/impl/DatabaseRotatingImp.h b/src/xrpld/nodestore/detail/DatabaseRotatingImp.h similarity index 94% rename from src/ripple/nodestore/impl/DatabaseRotatingImp.h rename to src/xrpld/nodestore/detail/DatabaseRotatingImp.h index 3a9fd302b4f..5183aa1e2e4 100644 --- a/src/ripple/nodestore/impl/DatabaseRotatingImp.h +++ b/src/xrpld/nodestore/detail/DatabaseRotatingImp.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_DATABASEROTATINGIMP_H_INCLUDED #define RIPPLE_NODESTORE_DATABASEROTATINGIMP_H_INCLUDED -#include +#include #include @@ -62,7 +62,8 @@ class DatabaseRotatingImp : public DatabaseRotating void importDatabase(Database& source) override; - bool isSameDB(std::uint32_t, std::uint32_t) override + bool + isSameDB(std::uint32_t, std::uint32_t) override { // rotating store acts as one logical database return true; @@ -75,9 +76,6 @@ class DatabaseRotatingImp : public DatabaseRotating void sync() override; - bool - storeLedger(std::shared_ptr const& srcLedger) override; - void sweep() override; diff --git a/src/ripple/nodestore/impl/DecodedBlob.cpp b/src/xrpld/nodestore/detail/DecodedBlob.cpp similarity index 96% rename from src/ripple/nodestore/impl/DecodedBlob.cpp rename to src/xrpld/nodestore/detail/DecodedBlob.cpp index 13175d36295..b71f2c543b1 100644 --- a/src/ripple/nodestore/impl/DecodedBlob.cpp +++ b/src/xrpld/nodestore/detail/DecodedBlob.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/nodestore/impl/DecodedBlob.h b/src/xrpld/nodestore/detail/DecodedBlob.h similarity index 98% rename from src/ripple/nodestore/impl/DecodedBlob.h rename to src/xrpld/nodestore/detail/DecodedBlob.h index 623e1f18dce..d52f20c4eef 100644 --- a/src/ripple/nodestore/impl/DecodedBlob.h +++ b/src/xrpld/nodestore/detail/DecodedBlob.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_DECODEDBLOB_H_INCLUDED #define RIPPLE_NODESTORE_DECODEDBLOB_H_INCLUDED -#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/impl/DummyScheduler.cpp b/src/xrpld/nodestore/detail/DummyScheduler.cpp similarity index 96% rename from src/ripple/nodestore/impl/DummyScheduler.cpp rename to src/xrpld/nodestore/detail/DummyScheduler.cpp index ee44e02fb81..76cd89610cc 100644 --- a/src/ripple/nodestore/impl/DummyScheduler.cpp +++ b/src/xrpld/nodestore/detail/DummyScheduler.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace NodeStore { diff --git a/src/ripple/nodestore/impl/EncodedBlob.h b/src/xrpld/nodestore/detail/EncodedBlob.h similarity index 98% rename from src/ripple/nodestore/impl/EncodedBlob.h rename to src/xrpld/nodestore/detail/EncodedBlob.h index 77f8fbf3c6c..2b506a0df3f 100644 --- a/src/ripple/nodestore/impl/EncodedBlob.h +++ b/src/xrpld/nodestore/detail/EncodedBlob.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_NODESTORE_ENCODEDBLOB_H_INCLUDED #define RIPPLE_NODESTORE_ENCODEDBLOB_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/nodestore/impl/ManagerImp.cpp b/src/xrpld/nodestore/detail/ManagerImp.cpp similarity index 90% rename from src/ripple/nodestore/impl/ManagerImp.cpp rename to src/xrpld/nodestore/detail/ManagerImp.cpp index d5ea29059df..56dc66ee644 100644 --- a/src/ripple/nodestore/impl/ManagerImp.cpp +++ b/src/xrpld/nodestore/detail/ManagerImp.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include @@ -55,12 +55,6 @@ ManagerImp::make_Backend( auto factory{find(type)}; if (!factory) { -#ifndef RIPPLED_REPORTING - if (boost::iequals(type, "cassandra")) - Throw( - "To use Cassandra as a nodestore, build rippled with " - "-Dreporting=ON"); -#endif missing_backend(); } diff --git a/src/ripple/nodestore/impl/ManagerImp.h b/src/xrpld/nodestore/detail/ManagerImp.h similarity index 98% rename from src/ripple/nodestore/impl/ManagerImp.h rename to src/xrpld/nodestore/detail/ManagerImp.h index 42f8584ef07..92411cc7601 100644 --- a/src/ripple/nodestore/impl/ManagerImp.h +++ b/src/xrpld/nodestore/detail/ManagerImp.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_NODESTORE_MANAGERIMP_H_INCLUDED #define RIPPLE_NODESTORE_MANAGERIMP_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/nodestore/impl/NodeObject.cpp b/src/xrpld/nodestore/detail/NodeObject.cpp similarity index 97% rename from src/ripple/nodestore/impl/NodeObject.cpp rename to src/xrpld/nodestore/detail/NodeObject.cpp index fb476107c8e..f633014b09f 100644 --- a/src/ripple/nodestore/impl/NodeObject.cpp +++ b/src/xrpld/nodestore/detail/NodeObject.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/nodestore/impl/codec.h b/src/xrpld/nodestore/detail/codec.h similarity index 98% rename from src/ripple/nodestore/impl/codec.h rename to src/xrpld/nodestore/detail/codec.h index a6749e0d162..f19672b8123 100644 --- a/src/ripple/nodestore/impl/codec.h +++ b/src/xrpld/nodestore/detail/codec.h @@ -23,11 +23,11 @@ // Disable lz4 deprecation warning due to incompatibility with clang attributes #define LZ4_DISABLE_DEPRECATE_WARNINGS -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/nodestore/impl/varint.h b/src/xrpld/nodestore/detail/varint.h similarity index 100% rename from src/ripple/nodestore/impl/varint.h rename to src/xrpld/nodestore/detail/varint.h diff --git a/src/ripple/overlay/Cluster.h b/src/xrpld/overlay/Cluster.h similarity index 92% rename from src/ripple/overlay/Cluster.h rename to src/xrpld/overlay/Cluster.h index 46d5a6ddb3c..42bda586c0d 100644 --- a/src/ripple/overlay/Cluster.h +++ b/src/xrpld/overlay/Cluster.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_OVERLAY_CLUSTER_H_INCLUDED #define RIPPLE_OVERLAY_CLUSTER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/overlay/ClusterNode.h b/src/xrpld/overlay/ClusterNode.h similarity index 96% rename from src/ripple/overlay/ClusterNode.h rename to src/xrpld/overlay/ClusterNode.h index 7996f8ffd26..1ca78208dd5 100644 --- a/src/ripple/overlay/ClusterNode.h +++ b/src/xrpld/overlay/ClusterNode.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_PEERS_CLUSTERNODESTATUS_H_INCLUDED #define RIPPLE_APP_PEERS_CLUSTERNODESTATUS_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/overlay/Compression.h b/src/xrpld/overlay/Compression.h similarity index 97% rename from src/ripple/overlay/Compression.h rename to src/xrpld/overlay/Compression.h index 6bb94792b43..1cf13fcb185 100644 --- a/src/ripple/overlay/Compression.h +++ b/src/xrpld/overlay/Compression.h @@ -20,8 +20,8 @@ #ifndef RIPPLED_COMPRESSION_H_INCLUDED #define RIPPLED_COMPRESSION_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/overlay/Message.h b/src/xrpld/overlay/Message.h similarity index 96% rename from src/ripple/overlay/Message.h rename to src/xrpld/overlay/Message.h index 0d6479366e8..247e53ec9fd 100644 --- a/src/ripple/overlay/Message.h +++ b/src/xrpld/overlay/Message.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_OVERLAY_MESSAGE_H_INCLUDED #define RIPPLE_OVERLAY_MESSAGE_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h similarity index 93% rename from src/ripple/overlay/Overlay.h rename to src/xrpld/overlay/Overlay.h index 844eafb86cc..b9550ba2ef4 100644 --- a/src/ripple/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_OVERLAY_OVERLAY_H_INCLUDED #define RIPPLE_OVERLAY_OVERLAY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -219,15 +219,6 @@ class Overlay : public beast::PropertyStream::Source virtual std::uint64_t getPeerDisconnectCharges() const = 0; - /** Returns information reported to the crawl shard RPC command. - - @param includePublicKey include peer public keys in the result. - @param hops the maximum jumps the crawler will attempt. - The number of hops achieved is not guaranteed. - */ - virtual Json::Value - crawlShards(bool includePublicKey, std::uint32_t hops) = 0; - /** Returns the ID of the network this server is configured for, if any. The ID is just a numerical identifier, with the IDs 0, 1 and 2 used to diff --git a/src/ripple/overlay/Peer.h b/src/xrpld/overlay/Peer.h similarity index 92% rename from src/ripple/overlay/Peer.h rename to src/xrpld/overlay/Peer.h index ba415974151..82ed2c2481a 100644 --- a/src/ripple/overlay/Peer.h +++ b/src/xrpld/overlay/Peer.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_OVERLAY_PEER_H_INCLUDED #define RIPPLE_OVERLAY_PEER_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { @@ -32,9 +32,6 @@ namespace Resource { class Charge; } -// Maximum hops to relay the peer shard info request -static constexpr std::uint32_t relayLimit = 3; - enum class ProtocolFeature { ValidatorListPropagation, ValidatorList2Propagation, diff --git a/src/ripple/overlay/PeerReservationTable.h b/src/xrpld/overlay/PeerReservationTable.h similarity index 94% rename from src/ripple/overlay/PeerReservationTable.h rename to src/xrpld/overlay/PeerReservationTable.h index e8fd4a29437..fb3add61f70 100644 --- a/src/ripple/overlay/PeerReservationTable.h +++ b/src/xrpld/overlay/PeerReservationTable.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_OVERLAY_PEER_RESERVATION_TABLE_H_INCLUDED #define RIPPLE_OVERLAY_PEER_RESERVATION_TABLE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/overlay/PeerSet.h b/src/xrpld/overlay/PeerSet.h similarity index 93% rename from src/ripple/overlay/PeerSet.h rename to src/xrpld/overlay/PeerSet.h index db21b0c7498..aca6242f9de 100644 --- a/src/ripple/overlay/PeerSet.h +++ b/src/xrpld/overlay/PeerSet.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_APP_PEERS_PEERSET_H_INCLUDED #define RIPPLE_APP_PEERS_PEERSET_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/overlay/README.md b/src/xrpld/overlay/README.md similarity index 100% rename from src/ripple/overlay/README.md rename to src/xrpld/overlay/README.md diff --git a/src/ripple/overlay/ReduceRelayCommon.h b/src/xrpld/overlay/ReduceRelayCommon.h similarity index 100% rename from src/ripple/overlay/ReduceRelayCommon.h rename to src/xrpld/overlay/ReduceRelayCommon.h diff --git a/src/ripple/overlay/Slot.h b/src/xrpld/overlay/Slot.h similarity index 98% rename from src/ripple/overlay/Slot.h rename to src/xrpld/overlay/Slot.h index e58619e66f8..2a8b2146a02 100644 --- a/src/ripple/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -20,15 +20,15 @@ #ifndef RIPPLE_OVERLAY_SLOT_H_INCLUDED #define RIPPLE_OVERLAY_SLOT_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/overlay/Squelch.h b/src/xrpld/overlay/Squelch.h similarity index 95% rename from src/ripple/overlay/Squelch.h rename to src/xrpld/overlay/Squelch.h index 175c80a0af2..2919922da92 100644 --- a/src/ripple/overlay/Squelch.h +++ b/src/xrpld/overlay/Squelch.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_OVERLAY_SQUELCH_H_INCLUDED #define RIPPLE_OVERLAY_SQUELCH_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/overlay/impl/Cluster.cpp b/src/xrpld/overlay/detail/Cluster.cpp similarity index 88% rename from src/ripple/overlay/impl/Cluster.cpp rename to src/xrpld/overlay/detail/Cluster.cpp index 53ec13b7403..9d25248e8df 100644 --- a/src/ripple/overlay/impl/Cluster.cpp +++ b/src/xrpld/overlay/detail/Cluster.cpp @@ -17,17 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include namespace ripple { @@ -113,7 +111,8 @@ Cluster::load(Section const& nodes) return false; } - auto const id = parseBase58(TokenType::NodePublic, match[1]); + auto const id = + parseBase58(TokenType::NodePublic, match[1].str()); if (!id) { diff --git a/src/ripple/overlay/impl/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp similarity index 98% rename from src/ripple/overlay/impl/ConnectAttempt.cpp rename to src/xrpld/overlay/detail/ConnectAttempt.cpp index f1d1c6ffe00..6a3ebdd5b98 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/overlay/impl/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h similarity index 97% rename from src/ripple/overlay/impl/ConnectAttempt.h rename to src/xrpld/overlay/detail/ConnectAttempt.h index 65eac6809f4..474ac3c069f 100644 --- a/src/ripple/overlay/impl/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED #define RIPPLE_OVERLAY_CONNECTATTEMPT_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/overlay/impl/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp similarity index 95% rename from src/ripple/overlay/impl/Handshake.cpp rename to src/xrpld/overlay/detail/Handshake.cpp index 8fe383e1d47..8f1aae8c025 100644 --- a/src/ripple/overlay/impl/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -17,19 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include - +#include +#include +#include +#include +#include +#include +#include #include - #include -#include // VFALCO Shouldn't we have to include the OpenSSL // headers or something for SSL_get_finished? @@ -46,8 +42,8 @@ getFeatureValue( return {}; boost::smatch match; boost::regex rx(feature + "=([^;\\s]+)"); - std::string const value = header->value(); - if (boost::regex_search(value, match, rx)) + std::string const allFeatures(header->value()); + if (boost::regex_search(allFeatures, match, rx)) return {match[1]}; return {}; } @@ -243,7 +239,7 @@ verifyHandshake( { std::uint32_t nid; - if (!beast::lexicalCastChecked(nid, std::string(iter->value()))) + if (!beast::lexicalCastChecked(nid, iter->value())) throw std::runtime_error("Invalid peer network identifier"); if (networkID && nid != *networkID) @@ -252,8 +248,7 @@ verifyHandshake( if (auto const iter = headers.find("Network-Time"); iter != headers.end()) { - auto const netTime = - [str = std::string(iter->value())]() -> TimeKeeper::time_point { + auto const netTime = [str = iter->value()]() -> TimeKeeper::time_point { TimeKeeper::duration::rep val; if (beast::lexicalCastChecked(val, str)) diff --git a/src/ripple/overlay/impl/Handshake.h b/src/xrpld/overlay/detail/Handshake.h similarity index 98% rename from src/ripple/overlay/impl/Handshake.h rename to src/xrpld/overlay/detail/Handshake.h index 2accf5d221e..76c11b287ca 100644 --- a/src/ripple/overlay/impl/Handshake.h +++ b/src/xrpld/overlay/detail/Handshake.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_OVERLAY_HANDSHAKE_H_INCLUDED #define RIPPLE_OVERLAY_HANDSHAKE_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/overlay/impl/Message.cpp b/src/xrpld/overlay/detail/Message.cpp similarity index 96% rename from src/ripple/overlay/impl/Message.cpp rename to src/xrpld/overlay/detail/Message.cpp index b4cb1f192aa..71917db0506 100644 --- a/src/ripple/overlay/impl/Message.cpp +++ b/src/xrpld/overlay/detail/Message.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include namespace ripple { @@ -94,13 +94,9 @@ Message::compress() case protocol::mtSTATUS_CHANGE: case protocol::mtHAVE_SET: case protocol::mtVALIDATION: - case protocol::mtGET_PEER_SHARD_INFO: - case protocol::mtPEER_SHARD_INFO: case protocol::mtPROOF_PATH_REQ: case protocol::mtPROOF_PATH_RESPONSE: case protocol::mtREPLAY_DELTA_REQ: - case protocol::mtGET_PEER_SHARD_INFO_V2: - case protocol::mtPEER_SHARD_INFO_V2: case protocol::mtHAVE_TRANSACTIONS: break; } diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp similarity index 90% rename from src/ripple/overlay/impl/OverlayImpl.cpp rename to src/xrpld/overlay/detail/OverlayImpl.cpp index 1bb9a381edd..e41a08a43d1 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -17,29 +17,27 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include namespace ripple { @@ -493,6 +491,9 @@ OverlayImpl::start() // Pool of servers operated by ISRDC - https://isrdc.in bootstrapIps.push_back("sahyadri.isrdc.in 51235"); + + // Pool of servers operated by @Xrpkuwait - https://xrpkuwait.com + bootstrapIps.push_back("hubs.xrpkuwait.com 51235"); } m_resolver.resolve( @@ -680,103 +681,6 @@ OverlayImpl::reportTraffic( m_traffic.addCount(cat, isInbound, number); } -Json::Value -OverlayImpl::crawlShards(bool includePublicKey, std::uint32_t relays) -{ - using namespace std::chrono; - - Json::Value jv(Json::objectValue); - - // Add shard info from this server to json result - if (auto shardStore = app_.getShardStore()) - { - if (includePublicKey) - jv[jss::public_key] = - toBase58(TokenType::NodePublic, app_.nodeIdentity().first); - - auto const shardInfo{shardStore->getShardInfo()}; - if (!shardInfo->finalized().empty()) - jv[jss::complete_shards] = shardInfo->finalizedToString(); - if (!shardInfo->incomplete().empty()) - jv[jss::incomplete_shards] = shardInfo->incompleteToString(); - } - - if (relays == 0 || size() == 0) - return jv; - - { - protocol::TMGetPeerShardInfoV2 tmGPS; - tmGPS.set_relays(relays); - - // Wait if a request is in progress - std::unique_lock csLock{csMutex_}; - if (!csIDs_.empty()) - csCV_.wait(csLock); - - { - std::lock_guard lock{mutex_}; - for (auto const& id : ids_) - csIDs_.emplace(id.first); - } - - // Request peer shard info - foreach(send_always(std::make_shared( - tmGPS, protocol::mtGET_PEER_SHARD_INFO_V2))); - - if (csCV_.wait_for(csLock, seconds(60)) == std::cv_status::timeout) - { - csIDs_.clear(); - csCV_.notify_all(); - } - } - - // Combine shard info from peers - hash_map peerShardInfo; - for_each([&](std::shared_ptr&& peer) { - auto const psi{peer->getPeerShardInfos()}; - for (auto const& [publicKey, shardInfo] : psi) - { - auto const it{peerShardInfo.find(publicKey)}; - if (it == peerShardInfo.end()) - peerShardInfo.emplace(publicKey, shardInfo); - else if (shardInfo.msgTimestamp() > it->second.msgTimestamp()) - it->second = shardInfo; - } - }); - - // Add shard info to json result - if (!peerShardInfo.empty()) - { - auto& av = jv[jss::peers] = Json::Value(Json::arrayValue); - for (auto const& [publicKey, shardInfo] : peerShardInfo) - { - auto& pv{av.append(Json::Value(Json::objectValue))}; - if (includePublicKey) - { - pv[jss::public_key] = - toBase58(TokenType::NodePublic, publicKey); - } - - if (!shardInfo.finalized().empty()) - pv[jss::complete_shards] = shardInfo.finalizedToString(); - if (!shardInfo.incomplete().empty()) - pv[jss::incomplete_shards] = shardInfo.incompleteToString(); - } - } - - return jv; -} - -void -OverlayImpl::endOfPeerChain(std::uint32_t id) -{ - // Notify threads if all peers have received a reply from all peer chains - std::lock_guard csLock{csMutex_}; - csIDs_.erase(id); - if (csIDs_.empty()) - csCV_.notify_all(); -} - /** The number of active peers on the network Active peers are only those peers that have completed the handshake and are running the Ripple protocol. @@ -826,7 +730,7 @@ OverlayImpl::getOverlayInfo() auto version{sp->getVersion()}; if (!version.empty()) // Could move here if Json::value supported moving from strings - pv[jss::version] = version; + pv[jss::version] = std::string{version}; } std::uint32_t minSeq, maxSeq; @@ -834,17 +738,6 @@ OverlayImpl::getOverlayInfo() if (minSeq != 0 || maxSeq != 0) pv[jss::complete_ledgers] = std::to_string(minSeq) + "-" + std::to_string(maxSeq); - - auto const peerShardInfos{sp->getPeerShardInfos()}; - auto const it{peerShardInfos.find(sp->getNodePublic())}; - if (it != peerShardInfos.end()) - { - auto const& shardInfo{it->second}; - if (!shardInfo.finalized().empty()) - pv[jss::complete_shards] = shardInfo.finalizedToString(); - if (!shardInfo.incomplete().empty()) - pv[jss::incomplete_shards] = shardInfo.incompleteToString(); - } }); return jv; @@ -994,9 +887,9 @@ OverlayImpl::processValidatorList( return true; }; - auto key = req.target().substr(prefix.size()); + std::string_view key = req.target().substr(prefix.size()); - if (auto slash = key.find('/'); slash != boost::string_view::npos) + if (auto slash = key.find('/'); slash != std::string_view::npos) { auto verString = key.substr(0, slash); if (!boost::conversion::try_lexical_convert(verString, version)) @@ -1164,9 +1057,11 @@ OverlayImpl::getActivePeers( disabled = enabledInSkip = 0; ret.reserve(ids_.size()); + // NOTE The purpose of p is to delay the destruction of PeerImp + std::shared_ptr p; for (auto& [id, w] : ids_) { - if (auto p = w.lock()) + if (p = w.lock(); p != nullptr) { bool const reduceRelayEnabled = p->txReduceRelayEnabled(); // tx reduced relay feature disabled @@ -1206,9 +1101,11 @@ std::shared_ptr OverlayImpl::findPeerByPublicKey(PublicKey const& pubKey) { std::lock_guard lock(mutex_); + // NOTE The purpose of peer is to delay the destruction of PeerImp + std::shared_ptr peer; for (auto const& e : ids_) { - if (auto peer = e.second.lock()) + if (peer = e.second.lock(); peer != nullptr) { if (peer->getNodePublic() == pubKey) return peer; diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h similarity index 94% rename from src/ripple/overlay/impl/OverlayImpl.h rename to src/xrpld/overlay/detail/OverlayImpl.h index 2ba7999cbe0..a50dfc5e905 100644 --- a/src/ripple/overlay/impl/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -20,21 +20,21 @@ #ifndef RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED #define RIPPLE_OVERLAY_OVERLAYIMPL_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -119,12 +119,6 @@ class OverlayImpl : public Overlay, public reduce_relay::SquelchHandler std::atomic peerDisconnects_{0}; std::atomic peerDisconnectsCharges_{0}; - // 'cs' = crawl shards - std::mutex csMutex_; - std::condition_variable csCV_; - // Peer IDs expecting to receive a last link notification - std::set csIDs_; - reduce_relay::Slots slots_; // Transaction reduce-relay metrics @@ -392,16 +386,6 @@ class OverlayImpl : public Overlay, public reduce_relay::SquelchHandler return setup_.networkID; } - Json::Value - crawlShards(bool includePublicKey, std::uint32_t relays) override; - - /** Called when the reply from the last peer in a peer chain is received. - - @param id peer id that received the shard info. - */ - void - endOfPeerChain(std::uint32_t id); - /** Updates message count for validator/peer. Sends TMSquelch if the number * of messages for N peers reaches threshold T. A message is counted * if a peer receives the message for the first time and if diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp similarity index 87% rename from src/ripple/overlay/impl/PeerImp.cpp rename to src/xrpld/overlay/detail/PeerImp.cpp index 69ef6061d59..4f5f1470f8e 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -17,32 +17,30 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include + #include #include @@ -161,7 +159,7 @@ PeerImp::run() return post(strand_, std::bind(&PeerImp::run, shared_from_this())); auto parseLedgerHash = - [](std::string const& value) -> std::optional { + [](std::string_view value) -> std::optional { if (uint256 ret; ret.parseHex(value)) return ret; @@ -398,15 +396,15 @@ PeerImp::json() } if (auto const d = domain(); !d.empty()) - ret[jss::server_domain] = domain(); + ret[jss::server_domain] = std::string{d}; if (auto const nid = headers_["Network-ID"]; !nid.empty()) - ret[jss::network_id] = std::string(nid); + ret[jss::network_id] = std::string{nid}; ret[jss::load] = usage_.balance(); if (auto const version = getVersion(); !version.empty()) - ret[jss::version] = version; + ret[jss::version] = std::string{version}; ret[jss::protocol] = to_string(protocol_); @@ -524,17 +522,6 @@ PeerImp::hasLedger(uint256 const& hash, std::uint32_t seq) const recentLedgers_.end()) return true; } - - if (seq >= app_.getNodeStore().earliestLedgerSeq()) - { - std::lock_guard lock{shardInfoMutex_}; - auto const it{shardInfos_.find(publicKey_)}; - if (it != shardInfos_.end()) - { - auto const shardIndex{app_.getNodeStore().seqToShardIndex(seq)}; - return boost::icl::contains(it->second.finalized(), shardIndex); - } - } return false; } @@ -604,7 +591,7 @@ PeerImp::fail(std::string const& reason) return post( strand_, std::bind( - (void (Peer::*)(std::string const&)) & PeerImp::fail, + (void(Peer::*)(std::string const&)) & PeerImp::fail, shared_from_this(), reason)); if (journal_.active(beast::severities::kWarning) && socket_.is_open()) @@ -629,13 +616,6 @@ PeerImp::fail(std::string const& name, error_code ec) close(); } -hash_map const -PeerImp::getPeerShardInfos() const -{ - std::lock_guard l{shardInfoMutex_}; - return shardInfos_; -} - void PeerImp::gracefulClose() { @@ -881,11 +861,6 @@ PeerImp::doProtocolStart() if (auto m = overlay_.getManifestsMessage()) send(m); - // Request shard info from peer - protocol::TMGetPeerShardInfoV2 tmGPS; - tmGPS.set_relays(0); - send(std::make_shared(tmGPS, protocol::mtGET_PEER_SHARD_INFO_V2)); - setTimer(); } @@ -1186,294 +1161,6 @@ PeerImp::onMessage(std::shared_ptr const& m) app_.getFeeTrack().setClusterFee(clusterFee); } -void -PeerImp::onMessage(std::shared_ptr const& m) -{ - // DEPRECATED -} - -void -PeerImp::onMessage(std::shared_ptr const& m) -{ - // DEPRECATED -} - -void -PeerImp::onMessage(std::shared_ptr const& m) -{ - auto badData = [&](std::string msg) { - fee_ = Resource::feeBadData; - JLOG(p_journal_.warn()) << msg; - }; - - // Verify relays - if (m->relays() > relayLimit) - return badData("Invalid relays"); - - // Verify peer chain - // The peer chain should not contain this node's public key - // nor the public key of the sending peer - std::set pubKeyChain; - pubKeyChain.insert(app_.nodeIdentity().first); - pubKeyChain.insert(publicKey_); - - auto const peerChainSz{m->peerchain_size()}; - if (peerChainSz > 0) - { - if (peerChainSz > relayLimit) - return badData("Invalid peer chain size"); - - if (peerChainSz + m->relays() > relayLimit) - return badData("Invalid relays and peer chain size"); - - for (int i = 0; i < peerChainSz; ++i) - { - auto const slice{makeSlice(m->peerchain(i).publickey())}; - - // Verify peer public key - if (!publicKeyType(slice)) - return badData("Invalid peer public key"); - - // Verify peer public key is unique in the peer chain - if (!pubKeyChain.emplace(slice).second) - return badData("Invalid peer public key"); - } - } - - // Reply with shard info this node may have - if (auto shardStore = app_.getShardStore()) - { - auto reply{shardStore->getShardInfo()->makeMessage(app_)}; - if (peerChainSz > 0) - *(reply.mutable_peerchain()) = m->peerchain(); - send(std::make_shared(reply, protocol::mtPEER_SHARD_INFO_V2)); - } - - if (m->relays() == 0) - return; - - // Charge originating peer a fee for requesting relays - if (peerChainSz == 0) - fee_ = Resource::feeMediumBurdenPeer; - - // Add peer to the peer chain - m->add_peerchain()->set_publickey(publicKey_.data(), publicKey_.size()); - - // Relay the request to peers, exclude the peer chain - m->set_relays(m->relays() - 1); - overlay_.foreach(send_if_not( - std::make_shared(*m, protocol::mtGET_PEER_SHARD_INFO_V2), - [&](std::shared_ptr const& peer) { - return pubKeyChain.find(peer->getNodePublic()) != pubKeyChain.end(); - })); -} - -void -PeerImp::onMessage(std::shared_ptr const& m) -{ - // Find the earliest and latest shard indexes - auto const& db{app_.getNodeStore()}; - auto const earliestShardIndex{db.earliestShardIndex()}; - auto const latestShardIndex{[&]() -> std::optional { - auto const curLedgerSeq{app_.getLedgerMaster().getCurrentLedgerIndex()}; - if (curLedgerSeq >= db.earliestLedgerSeq()) - return db.seqToShardIndex(curLedgerSeq); - return std::nullopt; - }()}; - - auto badData = [&](std::string msg) { - fee_ = Resource::feeBadData; - JLOG(p_journal_.warn()) << msg; - }; - - // Used to create a digest and verify the message signature - Serializer s; - s.add32(HashPrefix::shardInfo); - - // Verify message creation time - NodeStore::ShardInfo shardInfo; - { - auto const timestamp{ - NetClock::time_point{std::chrono::seconds{m->timestamp()}}}; - auto const now{app_.timeKeeper().now()}; - if (timestamp > (now + 5s)) - return badData("Invalid timestamp"); - - // Check if stale - using namespace std::chrono_literals; - if (timestamp < (now - 5min)) - return badData("Stale timestamp"); - - s.add32(m->timestamp()); - shardInfo.setMsgTimestamp(timestamp); - } - - // Verify incomplete shards - auto const numIncomplete{m->incomplete_size()}; - if (numIncomplete > 0) - { - if (latestShardIndex && numIncomplete > *latestShardIndex) - return badData("Invalid number of incomplete shards"); - - // Verify each incomplete shard - for (int i = 0; i < numIncomplete; ++i) - { - auto const& incomplete{m->incomplete(i)}; - auto const shardIndex{incomplete.shardindex()}; - - // Verify shard index - if (shardIndex < earliestShardIndex || - (latestShardIndex && shardIndex > latestShardIndex)) - { - return badData("Invalid incomplete shard index"); - } - s.add32(shardIndex); - - // Verify state - auto const state{static_cast(incomplete.state())}; - switch (state) - { - // Incomplete states - case ShardState::acquire: - case ShardState::complete: - case ShardState::finalizing: - case ShardState::queued: - break; - - // case ShardState::finalized: - default: - return badData("Invalid incomplete shard state"); - } - s.add32(incomplete.state()); - - // Verify progress - std::uint32_t progress{0}; - if (incomplete.has_progress()) - { - progress = incomplete.progress(); - if (progress < 1 || progress > 100) - return badData("Invalid incomplete shard progress"); - s.add32(progress); - } - - // Verify each incomplete shard is unique - if (!shardInfo.update(shardIndex, state, progress)) - return badData("Invalid duplicate incomplete shards"); - } - } - - // Verify finalized shards - if (m->has_finalized()) - { - auto const& str{m->finalized()}; - if (str.empty()) - return badData("Invalid finalized shards"); - - if (!shardInfo.setFinalizedFromString(str)) - return badData("Invalid finalized shard indexes"); - - auto const& finalized{shardInfo.finalized()}; - auto const numFinalized{boost::icl::length(finalized)}; - if (numFinalized == 0 || - boost::icl::first(finalized) < earliestShardIndex || - (latestShardIndex && - boost::icl::last(finalized) > latestShardIndex)) - { - return badData("Invalid finalized shard indexes"); - } - - if (latestShardIndex && - (numFinalized + numIncomplete) > *latestShardIndex) - { - return badData("Invalid number of finalized and incomplete shards"); - } - - s.addRaw(str.data(), str.size()); - } - - // Verify public key - auto slice{makeSlice(m->publickey())}; - if (!publicKeyType(slice)) - return badData("Invalid public key"); - - // Verify peer public key isn't this nodes's public key - PublicKey const publicKey(slice); - if (publicKey == app_.nodeIdentity().first) - return badData("Invalid public key"); - - // Verify signature - if (!verify(publicKey, s.slice(), makeSlice(m->signature()), false)) - return badData("Invalid signature"); - - // Forward the message if a peer chain exists - auto const peerChainSz{m->peerchain_size()}; - if (peerChainSz > 0) - { - // Verify peer chain - if (peerChainSz > relayLimit) - return badData("Invalid peer chain size"); - - // The peer chain should not contain this node's public key - // nor the public key of the sending peer - std::set pubKeyChain; - pubKeyChain.insert(app_.nodeIdentity().first); - pubKeyChain.insert(publicKey_); - - for (int i = 0; i < peerChainSz; ++i) - { - // Verify peer public key - slice = makeSlice(m->peerchain(i).publickey()); - if (!publicKeyType(slice)) - return badData("Invalid peer public key"); - - // Verify peer public key is unique in the peer chain - if (!pubKeyChain.emplace(slice).second) - return badData("Invalid peer public key"); - } - - // If last peer in the chain is connected, relay the message - PublicKey const peerPubKey( - makeSlice(m->peerchain(peerChainSz - 1).publickey())); - if (auto peer = overlay_.findPeerByPublicKey(peerPubKey)) - { - m->mutable_peerchain()->RemoveLast(); - peer->send( - std::make_shared(*m, protocol::mtPEER_SHARD_INFO_V2)); - JLOG(p_journal_.trace()) - << "Relayed TMPeerShardInfoV2 from peer IP " - << remote_address_.address().to_string() << " to peer IP " - << peer->getRemoteAddress().to_string(); - } - else - { - // Peer is no longer available so the relay ends - JLOG(p_journal_.info()) << "Unable to relay peer shard info"; - } - } - - JLOG(p_journal_.trace()) - << "Consumed TMPeerShardInfoV2 originating from public key " - << toBase58(TokenType::NodePublic, publicKey) << " finalized shards[" - << ripple::to_string(shardInfo.finalized()) << "] incomplete shards[" - << (shardInfo.incomplete().empty() ? "empty" - : shardInfo.incompleteToString()) - << "]"; - - // Consume the message - { - std::lock_guard lock{shardInfoMutex_}; - auto const it{shardInfos_.find(publicKey_)}; - if (it == shardInfos_.end()) - shardInfos_.emplace(publicKey, std::move(shardInfo)); - else if (shardInfo.msgTimestamp() > it->second.msgTimestamp()) - it->second = std::move(shardInfo); - } - - // Notify overlay a reply was received from the last peer in this chain - if (peerChainSz == 0) - overlay_.endOfPeerChain(id_); -} - void PeerImp::onMessage(std::shared_ptr const& m) { @@ -1539,8 +1226,8 @@ PeerImp::handleTransaction( { // If we've never been in synch, there's nothing we can do // with a transaction - JLOG(p_journal_.debug()) << "Ignoring incoming transaction: " - << "Need network ledger"; + JLOG(p_journal_.debug()) + << "Ignoring incoming transaction: " << "Need network ledger"; return; } @@ -1670,13 +1357,6 @@ PeerImp::onMessage(std::shared_ptr const& m) if (m->has_ledgerseq()) { auto const ledgerSeq{m->ledgerseq()}; - // Verifying the network's earliest ledger only pertains to shards. - if (app_.getShardStore() && - ledgerSeq < app_.getNodeStore().earliestLedgerSeq()) - { - return badData( - "Invalid ledger sequence " + std::to_string(ledgerSeq)); - } // Check if within a reasonable range using namespace std::chrono_literals; @@ -1846,14 +1526,6 @@ PeerImp::onMessage(std::shared_ptr const& m) } else { - // Verifying the network's earliest ledger only pertains to shards. - if (app_.getShardStore() && - ledgerSeq < app_.getNodeStore().earliestLedgerSeq()) - { - return badData( - "Invalid ledger sequence " + std::to_string(ledgerSeq)); - } - // Check if within a reasonable range using namespace std::chrono_literals; if (app_.getLedgerMaster().getValidatedLedgerAge() <= 10s && @@ -2716,14 +2388,6 @@ PeerImp::onMessage(std::shared_ptr const& m) // need to inject the NodeStore interfaces. std::uint32_t seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0}; auto nodeObject{app_.getNodeStore().fetchNodeObject(hash, seq)}; - if (!nodeObject) - { - if (auto shardStore = app_.getShardStore()) - { - if (seq >= shardStore->earliestLedgerSeq()) - nodeObject = shardStore->fetchNodeObject(hash, seq); - } - } if (nodeObject) { protocol::TMIndexedObject& newObj = *reply.add_objects(); @@ -3323,44 +2987,28 @@ PeerImp::getLedger(std::shared_ptr const& m) ledger = app_.getLedgerMaster().getLedgerByHash(ledgerHash); if (!ledger) { - if (m->has_ledgerseq()) + JLOG(p_journal_.trace()) + << "getLedger: Don't have ledger with hash " << ledgerHash; + + if (m->has_querytype() && !m->has_requestcookie()) { - // Attempt to find ledger by sequence in the shard store - if (auto shards = app_.getShardStore()) + // Attempt to relay the request to a peer + if (auto const peer = getPeerWithLedger( + overlay_, + ledgerHash, + m->has_ledgerseq() ? m->ledgerseq() : 0, + this)) { - if (m->ledgerseq() >= shards->earliestLedgerSeq()) - { - ledger = - shards->fetchLedger(ledgerHash, m->ledgerseq()); - } + m->set_requestcookie(id()); + peer->send( + std::make_shared(*m, protocol::mtGET_LEDGER)); + JLOG(p_journal_.debug()) + << "getLedger: Request relayed to peer"; + return ledger; } - } - if (!ledger) - { JLOG(p_journal_.trace()) - << "getLedger: Don't have ledger with hash " << ledgerHash; - - if (m->has_querytype() && !m->has_requestcookie()) - { - // Attempt to relay the request to a peer - if (auto const peer = getPeerWithLedger( - overlay_, - ledgerHash, - m->has_ledgerseq() ? m->ledgerseq() : 0, - this)) - { - m->set_requestcookie(id()); - peer->send(std::make_shared( - *m, protocol::mtGET_LEDGER)); - JLOG(p_journal_.debug()) - << "getLedger: Request relayed to peer"; - return ledger; - } - - JLOG(p_journal_.trace()) - << "getLedger: Failed to find peer to relay request"; - } + << "getLedger: Failed to find peer to relay request"; } } } diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h similarity index 93% rename from src/ripple/overlay/impl/PeerImp.h rename to src/xrpld/overlay/detail/PeerImp.h index 710ab4d74d6..9c76ddb4db8 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -20,22 +20,21 @@ #ifndef RIPPLE_OVERLAY_PEERIMP_H_INCLUDED #define RIPPLE_OVERLAY_PEERIMP_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -163,10 +162,6 @@ class PeerImp : public Peer, // been sent to or received from this peer. hash_map publisherListSequences_; - // Any known shard info from this peer and its sub peers - hash_map shardInfos_; - std::mutex mutable shardInfoMutex_; - Compressed compressionEnabled_ = Compressed::Off; // Queue of transactions' hashes that have not been @@ -415,10 +410,6 @@ class PeerImp : public Peer, void fail(std::string const& reason); - // Return any known shard info from this peer and its sub peers - [[nodiscard]] hash_map const - getPeerShardInfos() const; - bool compressionEnabled() const override { @@ -541,14 +532,6 @@ class PeerImp : public Peer, void onMessage(std::shared_ptr const& m); void - onMessage(std::shared_ptr const& m); - void - onMessage(std::shared_ptr const& m); - void - onMessage(std::shared_ptr const& m); - void - onMessage(std::shared_ptr const& m); - void onMessage(std::shared_ptr const& m); void onMessage(std::shared_ptr const& m); diff --git a/src/ripple/overlay/impl/PeerReservationTable.cpp b/src/xrpld/overlay/detail/PeerReservationTable.cpp similarity index 93% rename from src/ripple/overlay/impl/PeerReservationTable.cpp rename to src/xrpld/overlay/detail/PeerReservationTable.cpp index 6f39d12e99c..6fd0112f46f 100644 --- a/src/ripple/overlay/impl/PeerReservationTable.cpp +++ b/src/xrpld/overlay/detail/PeerReservationTable.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/overlay/impl/PeerSet.cpp b/src/xrpld/overlay/detail/PeerSet.cpp similarity index 97% rename from src/ripple/overlay/impl/PeerSet.cpp rename to src/xrpld/overlay/detail/PeerSet.cpp index de5c3cd9f93..909b20c3079 100644 --- a/src/ripple/overlay/impl/PeerSet.cpp +++ b/src/xrpld/overlay/detail/PeerSet.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/overlay/impl/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h similarity index 92% rename from src/ripple/overlay/impl/ProtocolMessage.h rename to src/xrpld/overlay/detail/ProtocolMessage.h index d6fb14bc78c..8a7512afb31 100644 --- a/src/ripple/overlay/impl/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED #define RIPPLE_OVERLAY_PROTOCOLMESSAGE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -88,10 +88,6 @@ protocolMessageName(int type) return "validator_list_collection"; case protocol::mtVALIDATION: return "validation"; - case protocol::mtGET_PEER_SHARD_INFO: - return "get_peer_shard_info"; - case protocol::mtPEER_SHARD_INFO: - return "peer_shard_info"; case protocol::mtGET_OBJECTS: return "get_objects"; case protocol::mtHAVE_TRANSACTIONS: @@ -108,10 +104,6 @@ protocolMessageName(int type) return "replay_delta_request"; case protocol::mtREPLAY_DELTA_RESPONSE: return "replay_delta_response"; - case protocol::mtGET_PEER_SHARD_INFO_V2: - return "get_peer_shard_info_v2"; - case protocol::mtPEER_SHARD_INFO_V2: - return "peer_shard_info_v2"; default: break; } @@ -436,14 +428,6 @@ invokeProtocolMessage( success = detail::invoke( *header, buffers, handler); break; - case protocol::mtGET_PEER_SHARD_INFO: - success = detail::invoke( - *header, buffers, handler); - break; - case protocol::mtPEER_SHARD_INFO: - success = detail::invoke( - *header, buffers, handler); - break; case protocol::mtVALIDATORLIST: success = detail::invoke( *header, buffers, handler); @@ -484,14 +468,6 @@ invokeProtocolMessage( success = detail::invoke( *header, buffers, handler); break; - case protocol::mtGET_PEER_SHARD_INFO_V2: - success = detail::invoke( - *header, buffers, handler); - break; - case protocol::mtPEER_SHARD_INFO_V2: - success = detail::invoke( - *header, buffers, handler); - break; default: handler.onMessageUnknown(header->message_type); success = true; diff --git a/src/ripple/overlay/impl/ProtocolVersion.cpp b/src/xrpld/overlay/detail/ProtocolVersion.cpp similarity index 97% rename from src/ripple/overlay/impl/ProtocolVersion.cpp rename to src/xrpld/overlay/detail/ProtocolVersion.cpp index fbd48474420..0fecb301f7f 100644 --- a/src/ripple/overlay/impl/ProtocolVersion.cpp +++ b/src/xrpld/overlay/detail/ProtocolVersion.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include #include @@ -45,7 +45,7 @@ constexpr ProtocolVersion const supportedProtocolList[] // ascending order and doesn't contain any duplicates. // FIXME: With C++20 we can use std::is_sorted with an appropriate comparator static_assert( - []() constexpr->bool { + []() constexpr -> bool { auto const len = std::distance( std::begin(supportedProtocolList), std::end(supportedProtocolList)); diff --git a/src/ripple/overlay/impl/ProtocolVersion.h b/src/xrpld/overlay/detail/ProtocolVersion.h similarity index 100% rename from src/ripple/overlay/impl/ProtocolVersion.h rename to src/xrpld/overlay/detail/ProtocolVersion.h diff --git a/src/ripple/overlay/impl/TrafficCount.cpp b/src/xrpld/overlay/detail/TrafficCount.cpp similarity index 95% rename from src/ripple/overlay/impl/TrafficCount.cpp rename to src/xrpld/overlay/detail/TrafficCount.cpp index 9b35d47683c..c64a033e3e3 100644 --- a/src/ripple/overlay/impl/TrafficCount.cpp +++ b/src/xrpld/overlay/detail/TrafficCount.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { @@ -39,12 +39,6 @@ TrafficCount::categorize( if (type == protocol::mtENDPOINTS) return TrafficCount::category::overlay; - if ((type == protocol::mtGET_PEER_SHARD_INFO) || - (type == protocol::mtPEER_SHARD_INFO) || - (type == protocol::mtGET_PEER_SHARD_INFO_V2) || - (type == protocol::mtPEER_SHARD_INFO_V2)) - return TrafficCount::category::shards; - if (type == protocol::mtTRANSACTION) return TrafficCount::category::transaction; diff --git a/src/ripple/overlay/impl/TrafficCount.h b/src/xrpld/overlay/detail/TrafficCount.h similarity index 98% rename from src/ripple/overlay/impl/TrafficCount.h rename to src/xrpld/overlay/detail/TrafficCount.h index 9e212da7917..7dd5cbba901 100644 --- a/src/ripple/overlay/impl/TrafficCount.h +++ b/src/xrpld/overlay/detail/TrafficCount.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_OVERLAY_TRAFFIC_H_INCLUDED #define RIPPLE_OVERLAY_TRAFFIC_H_INCLUDED -#include -#include +#include +#include #include #include @@ -74,7 +74,6 @@ class TrafficCount proposal, validation, validatorlist, - shards, // shard-related traffic // TMHaveSet message: get_set, // transaction sets we try to get @@ -208,7 +207,6 @@ class TrafficCount {"proposals"}, // category::proposal {"validations"}, // category::validation {"validator_lists"}, // category::validatorlist - {"shards"}, // category::shards {"set_get"}, // category::get_set {"set_share"}, // category::share_set {"ledger_data_Transaction_Set_candidate_get"}, // category::ld_tsc_get diff --git a/src/ripple/overlay/impl/Tuning.h b/src/xrpld/overlay/detail/Tuning.h similarity index 100% rename from src/ripple/overlay/impl/Tuning.h rename to src/xrpld/overlay/detail/Tuning.h diff --git a/src/ripple/overlay/impl/TxMetrics.cpp b/src/xrpld/overlay/detail/TxMetrics.cpp similarity index 98% rename from src/ripple/overlay/impl/TxMetrics.cpp rename to src/xrpld/overlay/detail/TxMetrics.cpp index c9b826e9a36..8759e0122e5 100644 --- a/src/ripple/overlay/impl/TxMetrics.cpp +++ b/src/xrpld/overlay/detail/TxMetrics.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include diff --git a/src/ripple/overlay/impl/TxMetrics.h b/src/xrpld/overlay/detail/TxMetrics.h similarity index 98% rename from src/ripple/overlay/impl/TxMetrics.h rename to src/xrpld/overlay/detail/TxMetrics.h index d70cadee1b1..eda7f1bb1b3 100644 --- a/src/ripple/overlay/impl/TxMetrics.h +++ b/src/xrpld/overlay/detail/TxMetrics.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_OVERLAY_TXMETRICS_H_INCLUDED #define RIPPLE_OVERLAY_TXMETRICS_H_INCLUDED -#include -#include +#include +#include #include diff --git a/src/ripple/overlay/impl/ZeroCopyStream.h b/src/xrpld/overlay/detail/ZeroCopyStream.h similarity index 100% rename from src/ripple/overlay/impl/ZeroCopyStream.h rename to src/xrpld/overlay/detail/ZeroCopyStream.h diff --git a/src/ripple/overlay/make_Overlay.h b/src/xrpld/overlay/make_Overlay.h similarity index 91% rename from src/ripple/overlay/make_Overlay.h rename to src/xrpld/overlay/make_Overlay.h index 9a63f5a46af..6532281f3dc 100644 --- a/src/ripple/overlay/make_Overlay.h +++ b/src/xrpld/overlay/make_Overlay.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_OVERLAY_MAKE_OVERLAY_H_INCLUDED #define RIPPLE_OVERLAY_MAKE_OVERLAY_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/overlay/predicates.h b/src/xrpld/overlay/predicates.h similarity index 98% rename from src/ripple/overlay/predicates.h rename to src/xrpld/overlay/predicates.h index 8e403b81fa8..88e7ce6aa4d 100644 --- a/src/ripple/overlay/predicates.h +++ b/src/xrpld/overlay/predicates.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_OVERLAY_PREDICATES_H_INCLUDED #define RIPPLE_OVERLAY_PREDICATES_H_INCLUDED -#include -#include +#include +#include #include diff --git a/src/ripple/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h similarity index 98% rename from src/ripple/peerfinder/PeerfinderManager.h rename to src/xrpld/peerfinder/PeerfinderManager.h index a831cbb707e..10de99abe62 100644 --- a/src/ripple/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PEERFINDER_MANAGER_H_INCLUDED #define RIPPLE_PEERFINDER_MANAGER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/peerfinder/README.md b/src/xrpld/peerfinder/README.md similarity index 100% rename from src/ripple/peerfinder/README.md rename to src/xrpld/peerfinder/README.md diff --git a/src/ripple/peerfinder/Slot.h b/src/xrpld/peerfinder/Slot.h similarity index 97% rename from src/ripple/peerfinder/Slot.h rename to src/xrpld/peerfinder/Slot.h index b43b8075ddc..879c6a53317 100644 --- a/src/ripple/peerfinder/Slot.h +++ b/src/xrpld/peerfinder/Slot.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PEERFINDER_SLOT_H_INCLUDED #define RIPPLE_PEERFINDER_SLOT_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/peerfinder/impl/Bootcache.cpp b/src/xrpld/peerfinder/detail/Bootcache.cpp similarity index 97% rename from src/ripple/peerfinder/impl/Bootcache.cpp rename to src/xrpld/peerfinder/detail/Bootcache.cpp index a07c53417d2..9e94a12e619 100644 --- a/src/ripple/peerfinder/impl/Bootcache.cpp +++ b/src/xrpld/peerfinder/detail/Bootcache.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/Bootcache.h b/src/xrpld/peerfinder/detail/Bootcache.h similarity index 96% rename from src/ripple/peerfinder/impl/Bootcache.h rename to src/xrpld/peerfinder/detail/Bootcache.h index b48f248ae40..07e8869594c 100644 --- a/src/ripple/peerfinder/impl/Bootcache.h +++ b/src/xrpld/peerfinder/detail/Bootcache.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_PEERFINDER_BOOTCACHE_H_INCLUDED #define RIPPLE_PEERFINDER_BOOTCACHE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/peerfinder/impl/Checker.h b/src/xrpld/peerfinder/detail/Checker.h similarity index 99% rename from src/ripple/peerfinder/impl/Checker.h rename to src/xrpld/peerfinder/detail/Checker.h index 3a5ef901328..338bd839541 100644 --- a/src/ripple/peerfinder/impl/Checker.h +++ b/src/xrpld/peerfinder/detail/Checker.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PEERFINDER_CHECKER_H_INCLUDED #define RIPPLE_PEERFINDER_CHECKER_H_INCLUDED -#include +#include #include #include #include diff --git a/src/ripple/peerfinder/impl/Counts.h b/src/xrpld/peerfinder/detail/Counts.h similarity index 98% rename from src/ripple/peerfinder/impl/Counts.h rename to src/xrpld/peerfinder/detail/Counts.h index 5d9e318594f..1ea6ff976de 100644 --- a/src/ripple/peerfinder/impl/Counts.h +++ b/src/xrpld/peerfinder/detail/Counts.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PEERFINDER_COUNTS_H_INCLUDED #define RIPPLE_PEERFINDER_COUNTS_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/ripple/peerfinder/impl/Endpoint.cpp b/src/xrpld/peerfinder/detail/Endpoint.cpp similarity index 93% rename from src/ripple/peerfinder/impl/Endpoint.cpp rename to src/xrpld/peerfinder/detail/Endpoint.cpp index 9740b60206b..53415a8c131 100644 --- a/src/ripple/peerfinder/impl/Endpoint.cpp +++ b/src/xrpld/peerfinder/detail/Endpoint.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/Fixed.h b/src/xrpld/peerfinder/detail/Fixed.h similarity index 97% rename from src/ripple/peerfinder/impl/Fixed.h rename to src/xrpld/peerfinder/detail/Fixed.h index bf7a2b6b35b..464a4d97d9a 100644 --- a/src/ripple/peerfinder/impl/Fixed.h +++ b/src/xrpld/peerfinder/detail/Fixed.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PEERFINDER_FIXED_H_INCLUDED #define RIPPLE_PEERFINDER_FIXED_H_INCLUDED -#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/Handouts.h b/src/xrpld/peerfinder/detail/Handouts.h similarity index 98% rename from src/ripple/peerfinder/impl/Handouts.h rename to src/xrpld/peerfinder/detail/Handouts.h index 00e163bbeaa..0b54dc205fa 100644 --- a/src/ripple/peerfinder/impl/Handouts.h +++ b/src/xrpld/peerfinder/detail/Handouts.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_PEERFINDER_HANDOUTS_H_INCLUDED #define RIPPLE_PEERFINDER_HANDOUTS_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/peerfinder/impl/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h similarity index 98% rename from src/ripple/peerfinder/impl/Livecache.h rename to src/xrpld/peerfinder/detail/Livecache.h index 8ecd68e845e..1583ccd96a3 100644 --- a/src/ripple/peerfinder/impl/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_PEERFINDER_LIVECACHE_H_INCLUDED #define RIPPLE_PEERFINDER_LIVECACHE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/peerfinder/impl/Logic.h b/src/xrpld/peerfinder/detail/Logic.h similarity index 97% rename from src/ripple/peerfinder/impl/Logic.h rename to src/xrpld/peerfinder/detail/Logic.h index 7a2b6a7543a..3bfb9942c3a 100644 --- a/src/ripple/peerfinder/impl/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -20,22 +20,21 @@ #ifndef RIPPLE_PEERFINDER_LOGIC_H_INCLUDED #define RIPPLE_PEERFINDER_LOGIC_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -1110,9 +1109,9 @@ class Logic } else { - JLOG(m_journal.error()) << beast::leftw(18) << "Logic failed " - << "'" << source->name() << "' fetch, " - << results.error.message(); + JLOG(m_journal.error()) + << beast::leftw(18) << "Logic failed " << "'" << source->name() + << "' fetch, " << results.error.message(); } } diff --git a/src/ripple/peerfinder/impl/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp similarity index 97% rename from src/ripple/peerfinder/impl/PeerfinderConfig.cpp rename to src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index 8b90bc97184..3075224189e 100644 --- a/src/ripple/peerfinder/impl/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/PeerfinderManager.cpp b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp similarity index 97% rename from src/ripple/peerfinder/impl/PeerfinderManager.cpp rename to src/xrpld/peerfinder/detail/PeerfinderManager.cpp index e3743c047e4..8baa2b50b9a 100644 --- a/src/ripple/peerfinder/impl/PeerfinderManager.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/peerfinder/impl/SlotImp.cpp b/src/xrpld/peerfinder/detail/SlotImp.cpp similarity index 96% rename from src/ripple/peerfinder/impl/SlotImp.cpp rename to src/xrpld/peerfinder/detail/SlotImp.cpp index c41aeb9d5d8..a5d13aa729e 100644 --- a/src/ripple/peerfinder/impl/SlotImp.cpp +++ b/src/xrpld/peerfinder/detail/SlotImp.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/SlotImp.h b/src/xrpld/peerfinder/detail/SlotImp.h similarity index 96% rename from src/ripple/peerfinder/impl/SlotImp.h rename to src/xrpld/peerfinder/detail/SlotImp.h index 49e5d731ba0..45143a90121 100644 --- a/src/ripple/peerfinder/impl/SlotImp.h +++ b/src/xrpld/peerfinder/detail/SlotImp.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PEERFINDER_SLOTIMP_H_INCLUDED #define RIPPLE_PEERFINDER_SLOTIMP_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/peerfinder/impl/Source.h b/src/xrpld/peerfinder/detail/Source.h similarity index 97% rename from src/ripple/peerfinder/impl/Source.h rename to src/xrpld/peerfinder/detail/Source.h index 5c148438148..a64da7b9e79 100644 --- a/src/ripple/peerfinder/impl/Source.h +++ b/src/xrpld/peerfinder/detail/Source.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PEERFINDER_SOURCE_H_INCLUDED #define RIPPLE_PEERFINDER_SOURCE_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/peerfinder/impl/SourceStrings.cpp b/src/xrpld/peerfinder/detail/SourceStrings.cpp similarity index 97% rename from src/ripple/peerfinder/impl/SourceStrings.cpp rename to src/xrpld/peerfinder/detail/SourceStrings.cpp index 4e5db79c75c..b01fbfd8c21 100644 --- a/src/ripple/peerfinder/impl/SourceStrings.cpp +++ b/src/xrpld/peerfinder/detail/SourceStrings.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace PeerFinder { diff --git a/src/ripple/peerfinder/impl/SourceStrings.h b/src/xrpld/peerfinder/detail/SourceStrings.h similarity index 97% rename from src/ripple/peerfinder/impl/SourceStrings.h rename to src/xrpld/peerfinder/detail/SourceStrings.h index 5d67d0887f3..52d985a7017 100644 --- a/src/ripple/peerfinder/impl/SourceStrings.h +++ b/src/xrpld/peerfinder/detail/SourceStrings.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PEERFINDER_SOURCESTRINGS_H_INCLUDED #define RIPPLE_PEERFINDER_SOURCESTRINGS_H_INCLUDED -#include +#include #include namespace ripple { diff --git a/src/ripple/peerfinder/impl/Store.h b/src/xrpld/peerfinder/detail/Store.h similarity index 100% rename from src/ripple/peerfinder/impl/Store.h rename to src/xrpld/peerfinder/detail/Store.h diff --git a/src/ripple/peerfinder/impl/StoreSqdb.h b/src/xrpld/peerfinder/detail/StoreSqdb.h similarity index 95% rename from src/ripple/peerfinder/impl/StoreSqdb.h rename to src/xrpld/peerfinder/detail/StoreSqdb.h index 879bee83b6f..ad8b886d4a3 100644 --- a/src/ripple/peerfinder/impl/StoreSqdb.h +++ b/src/xrpld/peerfinder/detail/StoreSqdb.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PEERFINDER_STORESQDB_H_INCLUDED #define RIPPLE_PEERFINDER_STORESQDB_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/peerfinder/impl/Tuning.h b/src/xrpld/peerfinder/detail/Tuning.h similarity index 100% rename from src/ripple/peerfinder/impl/Tuning.h rename to src/xrpld/peerfinder/detail/Tuning.h diff --git a/src/ripple/peerfinder/impl/iosformat.h b/src/xrpld/peerfinder/detail/iosformat.h similarity index 100% rename from src/ripple/peerfinder/impl/iosformat.h rename to src/xrpld/peerfinder/detail/iosformat.h diff --git a/src/ripple/peerfinder/make_Manager.h b/src/xrpld/peerfinder/make_Manager.h similarity index 96% rename from src/ripple/peerfinder/make_Manager.h rename to src/xrpld/peerfinder/make_Manager.h index 932fccb9abd..3a4f68d94c9 100644 --- a/src/ripple/peerfinder/make_Manager.h +++ b/src/xrpld/peerfinder/make_Manager.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_PEERFINDER_MAKE_MANAGER_H_INCLUDED #define RIPPLE_PEERFINDER_MAKE_MANAGER_H_INCLUDED -#include +#include #include #include diff --git a/src/ripple/basics/PerfLog.h b/src/xrpld/perflog/PerfLog.h similarity index 98% rename from src/ripple/basics/PerfLog.h rename to src/xrpld/perflog/PerfLog.h index 49ed27338db..a7d66f57ad7 100644 --- a/src/ripple/basics/PerfLog.h +++ b/src/xrpld/perflog/PerfLog.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_BASICS_PERFLOG_H #define RIPPLE_BASICS_PERFLOG_H -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/ripple/perflog/impl/PerfLogImp.cpp b/src/xrpld/perflog/detail/PerfLogImp.cpp similarity index 96% rename from src/ripple/perflog/impl/PerfLogImp.cpp rename to src/xrpld/perflog/detail/PerfLogImp.cpp index 3d07d0ed625..a4773b33e10 100644 --- a/src/ripple/perflog/impl/PerfLogImp.cpp +++ b/src/xrpld/perflog/detail/PerfLogImp.cpp @@ -17,15 +17,14 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include #include #include #include @@ -299,10 +298,7 @@ PerfLogImp::report() report[jss::hostid] = hostname_; report[jss::counters] = counters_.countersJson(); report[jss::nodestore] = Json::objectValue; - if (app_.getShardStore()) - app_.getShardStore()->getCountsJson(report[jss::nodestore]); - else - app_.getNodeStore().getCountsJson(report[jss::nodestore]); + app_.getNodeStore().getCountsJson(report[jss::nodestore]); report[jss::current_activities] = counters_.currentJson(); app_.getOPs().stateAccounting(report); diff --git a/src/ripple/perflog/impl/PerfLogImp.h b/src/xrpld/perflog/detail/PerfLogImp.h similarity index 96% rename from src/ripple/perflog/impl/PerfLogImp.h rename to src/xrpld/perflog/detail/PerfLogImp.h index 4904126d95f..937486f75dc 100644 --- a/src/ripple/perflog/impl/PerfLogImp.h +++ b/src/xrpld/perflog/detail/PerfLogImp.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_BASICS_PERFLOGIMP_H #define RIPPLE_BASICS_PERFLOGIMP_H -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/rpc/BookChanges.h b/src/xrpld/rpc/BookChanges.h similarity index 95% rename from src/ripple/rpc/BookChanges.h rename to src/xrpld/rpc/BookChanges.h index 7d7978d3fe2..c87fa0ccf4e 100644 --- a/src/ripple/rpc/BookChanges.h +++ b/src/xrpld/rpc/BookChanges.h @@ -20,6 +20,15 @@ #ifndef RIPPLE_RPC_BOOKCHANGES_H_INCLUDED #define RIPPLE_RPC_BOOKCHANGES_H_INCLUDED +#include +#include +#include +#include +#include +#include + +#include + namespace Json { class Value; } @@ -171,6 +180,9 @@ computeBookChanges(std::shared_ptr const& lpAccepted) Json::Value jvObj(Json::objectValue); jvObj[jss::type] = "bookChanges"; + + // retrieve validated information from LedgerHeader class + jvObj[jss::validated] = lpAccepted->info().validated; jvObj[jss::ledger_index] = lpAccepted->info().seq; jvObj[jss::ledger_hash] = to_string(lpAccepted->info().hash); jvObj[jss::ledger_time] = Json::Value::UInt( diff --git a/src/ripple/rpc/CTID.h b/src/xrpld/rpc/CTID.h similarity index 99% rename from src/ripple/rpc/CTID.h rename to src/xrpld/rpc/CTID.h index 8f6c64bc028..8cac8d63171 100644 --- a/src/ripple/rpc/CTID.h +++ b/src/xrpld/rpc/CTID.h @@ -63,7 +63,7 @@ decodeCTID(const T ctid) noexcept if (ctidString.length() != 16) return {}; - if (!boost::regex_match(ctidString, boost::regex("^[0-9A-F]+$"))) + if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$"))) return {}; ctidValue = std::stoull(ctidString, nullptr, 16); diff --git a/src/ripple/rpc/Context.h b/src/xrpld/rpc/Context.h similarity index 88% rename from src/ripple/rpc/Context.h rename to src/xrpld/rpc/Context.h index 7a22ed9fe0c..edc5582a401 100644 --- a/src/ripple/rpc/Context.h +++ b/src/xrpld/rpc/Context.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_RPC_CONTEXT_H_INCLUDED #define RIPPLE_RPC_CONTEXT_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include namespace ripple { @@ -57,8 +57,8 @@ struct JsonContext : public Context */ struct Headers { - boost::string_view user; - boost::string_view forwardedFor; + std::string_view user; + std::string_view forwardedFor; }; Json::Value params; diff --git a/src/ripple/rpc/DeliveredAmount.h b/src/xrpld/rpc/DeliveredAmount.h similarity index 96% rename from src/ripple/rpc/DeliveredAmount.h rename to src/xrpld/rpc/DeliveredAmount.h index 94fff68f7bd..2ebadd38752 100644 --- a/src/ripple/rpc/DeliveredAmount.h +++ b/src/xrpld/rpc/DeliveredAmount.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED #define RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/ripple/rpc/GRPCHandlers.h b/src/xrpld/rpc/GRPCHandlers.h similarity index 96% rename from src/ripple/rpc/GRPCHandlers.h rename to src/xrpld/rpc/GRPCHandlers.h index 9fb8d0909fe..679b16fd8ac 100644 --- a/src/ripple/rpc/GRPCHandlers.h +++ b/src/xrpld/rpc/GRPCHandlers.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RPC_GRPCHANDLER_H_INCLUDED #define RIPPLE_RPC_GRPCHANDLER_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/nodestore/impl/TaskQueue.h b/src/xrpld/rpc/MPTokenIssuanceID.h similarity index 52% rename from src/ripple/nodestore/impl/TaskQueue.h rename to src/xrpld/rpc/MPTokenIssuanceID.h index 942bd9c32d8..ef194bd398c 100644 --- a/src/ripple/nodestore/impl/TaskQueue.h +++ b/src/xrpld/rpc/MPTokenIssuanceID.h @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2019 Ripple Labs Inc. + Copyright (c) 2024 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,48 +17,45 @@ */ //============================================================================== -#ifndef RIPPLE_NODESTORE_TASKQUEUE_H_INCLUDED -#define RIPPLE_NODESTORE_TASKQUEUE_H_INCLUDED +#ifndef RIPPLE_RPC_MPTOKENISSUANCEID_H_INCLUDED +#define RIPPLE_RPC_MPTOKENISSUANCEID_H_INCLUDED -#include +#include +#include +#include +#include -#include -#include +#include +#include namespace ripple { -namespace NodeStore { -class TaskQueue : private Workers::Callback -{ -public: - TaskQueue(); +namespace RPC { - void - stop(); +/** + Add a `mpt_issuance_id` field to the `meta` input/output parameter. + The field is only added to successful MPTokenIssuanceCreate transactions. + The mpt_issuance_id is parsed from the sequence and the issuer in the + MPTokenIssuance object. - /** Adds a task to the queue + @{ + */ +bool +canHaveMPTokenIssuanceID( + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta); - @param task std::function with signature void() - */ - void - addTask(std::function task); +std::optional +getIDFromCreatedIssuance(TxMeta const& transactionMeta); - /** Return the queue size - */ - [[nodiscard]] size_t - size() const; +void +insertMPTokenIssuanceID( + Json::Value& response, + std::shared_ptr const& transaction, + TxMeta const& transactionMeta); +/** @} */ -private: - mutable std::mutex mutex_; - Workers workers_; - std::queue> tasks_; - std::uint64_t processing_{0}; - - void - processTask(int instance) override; -}; - -} // namespace NodeStore +} // namespace RPC } // namespace ripple #endif diff --git a/src/ripple/rpc/Output.h b/src/xrpld/rpc/Output.h similarity index 100% rename from src/ripple/rpc/Output.h rename to src/xrpld/rpc/Output.h diff --git a/src/ripple/rpc/README.md b/src/xrpld/rpc/README.md similarity index 100% rename from src/ripple/rpc/README.md rename to src/xrpld/rpc/README.md diff --git a/src/ripple/rpc/RPCHandler.h b/src/xrpld/rpc/RPCHandler.h similarity index 91% rename from src/ripple/rpc/RPCHandler.h rename to src/xrpld/rpc/RPCHandler.h index 45c737783c6..c056ca27eb6 100644 --- a/src/ripple/rpc/RPCHandler.h +++ b/src/xrpld/rpc/RPCHandler.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_RPC_RPCHANDLER_H_INCLUDED #define RIPPLE_RPC_RPCHANDLER_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { namespace RPC { diff --git a/src/ripple/rpc/Request.h b/src/xrpld/rpc/Request.h similarity index 93% rename from src/ripple/rpc/Request.h rename to src/xrpld/rpc/Request.h index 4f9918215a3..ec65e38d4f4 100644 --- a/src/ripple/rpc/Request.h +++ b/src/xrpld/rpc/Request.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_RPC_REQUEST_H_INCLUDED #define RIPPLE_RPC_REQUEST_H_INCLUDED -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/Role.h b/src/xrpld/rpc/Role.h similarity index 89% rename from src/ripple/rpc/Role.h rename to src/xrpld/rpc/Role.h index c4f1f730c02..0ce84e09ef6 100644 --- a/src/ripple/rpc/Role.h +++ b/src/xrpld/rpc/Role.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SERVER_ROLE_H_INCLUDED #define RIPPLE_SERVER_ROLE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -56,15 +56,15 @@ requestRole( Port const& port, Json::Value const& params, beast::IP::Endpoint const& remoteIp, - boost::string_view const& user); + std::string_view user); Resource::Consumer requestInboundEndpoint( Resource::Manager& manager, beast::IP::Endpoint const& remoteAddress, Role const& role, - boost::string_view const& user, - boost::string_view const& forwardedFor); + std::string_view user, + std::string_view forwardedFor); /** * Check if the role entitles the user to unlimited resources. @@ -85,7 +85,7 @@ ipAllowed( std::vector const& nets4, std::vector const& nets6); -boost::string_view +std::string_view forwardedFor(http_request_type const& request); } // namespace ripple diff --git a/src/ripple/rpc/ServerHandler.h b/src/xrpld/rpc/ServerHandler.h similarity index 94% rename from src/ripple/rpc/ServerHandler.h rename to src/xrpld/rpc/ServerHandler.h index 07fb61362a0..a8d4e900fc6 100644 --- a/src/ripple/rpc/ServerHandler.h +++ b/src/xrpld/rpc/ServerHandler.h @@ -20,14 +20,14 @@ #ifndef RIPPLE_RPC_SERVERHANDLER_H_INCLUDED #define RIPPLE_RPC_SERVERHANDLER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -208,8 +208,8 @@ class ServerHandler beast::IP::Endpoint const& remoteIPAddress, Output&&, std::shared_ptr coro, - boost::string_view forwardedFor, - boost::string_view user); + std::string_view forwardedFor, + std::string_view user); Handoff statusResponse(http_request_type const& request) const; diff --git a/src/ripple/rpc/Status.h b/src/xrpld/rpc/Status.h similarity index 98% rename from src/ripple/rpc/Status.h rename to src/xrpld/rpc/Status.h index d930d7ab439..1d94a6837a8 100644 --- a/src/ripple/rpc/Status.h +++ b/src/xrpld/rpc/Status.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RPC_STATUS_H_INCLUDED #define RIPPLE_RPC_STATUS_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/rpc/impl/DeliveredAmount.cpp b/src/xrpld/rpc/detail/DeliveredAmount.cpp similarity index 89% rename from src/ripple/rpc/impl/DeliveredAmount.cpp rename to src/xrpld/rpc/detail/DeliveredAmount.cpp index 59316d91cf5..93af8599146 100644 --- a/src/ripple/rpc/impl/DeliveredAmount.cpp +++ b/src/xrpld/rpc/detail/DeliveredAmount.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -119,20 +119,10 @@ canHaveDeliveredAmount( { // These lambdas are used to compute the values lazily auto const getFix1623Enabled = [&context]() -> bool { - if (context.app.config().reporting()) - { - auto const view = context.ledgerMaster.getValidatedLedger(); - if (!view) - return false; - return view->rules().enabled(fix1623); - } - else - { - auto const view = context.app.openLedger().current(); - if (!view) - return false; - return view->rules().enabled(fix1623); - } + auto const view = context.app.openLedger().current(); + if (!view) + return false; + return view->rules().enabled(fix1623); }; return canHaveDeliveredAmountHelp( diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp similarity index 95% rename from src/ripple/rpc/impl/Handler.cpp rename to src/xrpld/rpc/detail/Handler.cpp index 1fc160dc4db..90dee4475a1 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include @@ -99,20 +99,14 @@ Handler const handlerArray[]{ {"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION}, {"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION}, {"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION}, - {"crawl_shards", byRef(&doCrawlShards), Role::ADMIN, NO_CONDITION}, {"deposit_authorized", byRef(&doDepositAuthorized), Role::USER, NO_CONDITION}, - {"download_shard", byRef(&doDownloadShard), Role::ADMIN, NO_CONDITION}, {"feature", byRef(&doFeature), Role::USER, NO_CONDITION}, {"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER}, {"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION}, -#ifdef RIPPLED_REPORTING - {"gateway_balances", byRef(&doGatewayBalances), Role::ADMIN, NO_CONDITION}, -#else {"gateway_balances", byRef(&doGatewayBalances), Role::USER, NO_CONDITION}, -#endif {"get_counts", byRef(&doGetCounts), Role::ADMIN, NO_CONDITION}, {"get_aggregate_price", byRef(&doGetAggregatePrice), @@ -140,7 +134,6 @@ Handler const handlerArray[]{ {"manifest", byRef(&doManifest), Role::USER, NO_CONDITION}, {"nft_buy_offers", byRef(&doNFTBuyOffers), Role::USER, NO_CONDITION}, {"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION}, - {"node_to_shard", byRef(&doNodeToShard), Role::ADMIN, NO_CONDITION}, {"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION}, {"owner_info", byRef(&doOwnerInfo), Role::USER, NEEDS_CURRENT_LEDGER}, {"peers", byRef(&doPeers), Role::ADMIN, NO_CONDITION}, diff --git a/src/ripple/rpc/impl/Handler.h b/src/xrpld/rpc/detail/Handler.h similarity index 87% rename from src/ripple/rpc/impl/Handler.h rename to src/xrpld/rpc/detail/Handler.h index 28d1ee60c85..cb1a2579ecb 100644 --- a/src/ripple/rpc/impl/Handler.h +++ b/src/xrpld/rpc/detail/Handler.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_RPC_HANDLER_H_INCLUDED #define RIPPLE_RPC_HANDLER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace Json { @@ -81,22 +81,6 @@ template error_code_i conditionMet(Condition condition_required, T& context) { - if (context.app.config().reporting()) - { - if (condition_required == NEEDS_CURRENT_LEDGER) - { - return rpcNO_CURRENT; - } - else if (condition_required == NEEDS_CLOSED_LEDGER) - { - return rpcNO_CLOSED; - } - else - { - return rpcSUCCESS; - } - } - if (context.app.getOPs().isAmendmentBlocked() && (condition_required != NO_CONDITION)) { diff --git a/src/ripple/rpc/impl/LegacyPathFind.cpp b/src/xrpld/rpc/detail/LegacyPathFind.cpp similarity index 89% rename from src/ripple/rpc/impl/LegacyPathFind.cpp rename to src/xrpld/rpc/detail/LegacyPathFind.cpp index 1c13709ead6..35220178bf5 100644 --- a/src/ripple/rpc/impl/LegacyPathFind.cpp +++ b/src/xrpld/rpc/detail/LegacyPathFind.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace RPC { diff --git a/src/ripple/rpc/impl/LegacyPathFind.h b/src/xrpld/rpc/detail/LegacyPathFind.h similarity index 100% rename from src/ripple/rpc/impl/LegacyPathFind.h rename to src/xrpld/rpc/detail/LegacyPathFind.h diff --git a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp new file mode 100644 index 00000000000..721be652622 --- /dev/null +++ b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 + +#include +#include +#include +#include + +namespace ripple { + +namespace RPC { + +bool +canHaveMPTokenIssuanceID( + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta) +{ + if (!serializedTx) + return false; + + TxType const tt = serializedTx->getTxnType(); + if (tt != ttMPTOKEN_ISSUANCE_CREATE) + return false; + + // if the transaction failed nothing could have been delivered. + if (transactionMeta.getResultTER() != tesSUCCESS) + return false; + + return true; +} + +std::optional +getIDFromCreatedIssuance(TxMeta const& transactionMeta) +{ + for (STObject const& node : transactionMeta.getNodes()) + { + if (node.getFieldU16(sfLedgerEntryType) != ltMPTOKEN_ISSUANCE || + node.getFName() != sfCreatedNode) + continue; + + auto const& mptNode = + node.peekAtField(sfNewFields).downcast(); + return makeMptID( + mptNode.getFieldU32(sfSequence), mptNode.getAccountID(sfIssuer)); + } + + return std::nullopt; +} + +void +insertMPTokenIssuanceID( + Json::Value& response, + std::shared_ptr const& transaction, + TxMeta const& transactionMeta) +{ + if (!canHaveMPTokenIssuanceID(transaction, transactionMeta)) + return; + + std::optional result = getIDFromCreatedIssuance(transactionMeta); + if (result) + response[jss::mpt_issuance_id] = to_string(result.value()); +} + +} // namespace RPC +} // namespace ripple diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp similarity index 78% rename from src/ripple/rpc/impl/RPCHandler.cpp rename to src/xrpld/rpc/detail/RPCHandler.cpp index 9c5aa6d465b..19b33709c83 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -17,29 +17,28 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -206,11 +205,6 @@ callMethod( perfLog.rpcFinish(name, curId); return ret; } - catch (ReportingShouldProxy&) - { - result = forwardToP2p(context); - return rpcSUCCESS; - } catch (std::exception& e) { perfLog.rpcError(name, curId); @@ -226,36 +220,9 @@ callMethod( } // namespace -void -injectReportingWarning(RPC::JsonContext& context, Json::Value& result) -{ - if (context.app.config().reporting()) - { - Json::Value warnings{Json::arrayValue}; - Json::Value& w = warnings.append(Json::objectValue); - w[jss::id] = warnRPC_REPORTING; - w[jss::message] = - "This is a reporting server. " - " The default behavior of a reporting server is to only" - " return validated data. If you are looking for not yet" - " validated data, include \"ledger_index : current\"" - " in your request, which will cause this server to forward" - " the request to a p2p node. If the forward is successful" - " the response will include \"forwarded\" : \"true\""; - result[jss::warnings] = std::move(warnings); - } -} - Status doCommand(RPC::JsonContext& context, Json::Value& result) { - if (shouldForwardToP2p(context)) - { - result = forwardToP2p(context); - injectReportingWarning(context, result); - // this return value is ignored - return rpcSUCCESS; - } Handler const* handler = nullptr; if (auto error = fillHandler(context, handler)) { @@ -285,7 +252,6 @@ doCommand(RPC::JsonContext& context, Json::Value& result) else { auto ret = callMethod(context, method, handler->name_, result); - injectReportingWarning(context, result); return ret; } } diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp similarity index 93% rename from src/ripple/rpc/impl/RPCHelpers.cpp rename to src/xrpld/rpc/detail/RPCHelpers.cpp index a6179b04c88..af204eaedf7 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -17,23 +17,25 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include + #include namespace ripple { @@ -158,6 +160,10 @@ getAccountObjects( std::uint32_t const limit, Json::Value& jvResult) { + // check if dirIndex is valid + if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex})) + return false; + auto typeMatchesFilter = [](std::vector const& typeFilter, LedgerEntryType ledgerType) { auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType); @@ -249,8 +255,18 @@ getAccountObjects( if (!dir) { // it's possible the user had nftoken pages but no - // directory entries - return mlimit < limit; + // directory entries. If there's no nftoken page, we will + // give empty array for account_objects. + if (mlimit >= limit) + jvResult[jss::account_objects] = Json::arrayValue; + + // non-zero dirIndex validity was checked in the beginning of this + // function; by this point, it should be zero. This function returns + // true regardless of nftoken page presence; if absent, account_objects + // is already set as an empty array. Notice we will only return false in + // this function when entryIndex can not be found, indicating an invalid + // marker error. + return true; } std::uint32_t i = 0; @@ -329,9 +345,9 @@ getAccountObjects( namespace { bool -isValidatedOld(LedgerMaster& ledgerMaster, bool standaloneOrReporting) +isValidatedOld(LedgerMaster& ledgerMaster, bool standalone) { - if (standaloneOrReporting) + if (standalone) return false; return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge; @@ -371,12 +387,10 @@ ledgerFromRequest(T& ledger, JsonContext& context) auto const index = indexValue.asString(); - if (index == "current" || - (index.empty() && !context.app.config().reporting())) + if (index == "current" || index.empty()) return getLedger(ledger, LedgerShortcut::CURRENT, context); - if (index == "validated" || - (index.empty() && context.app.config().reporting())) + if (index == "validated") return getLedger(ledger, LedgerShortcut::VALIDATED, context); if (index == "closed") @@ -442,13 +456,8 @@ ledgerFromSpecifier( [[fallthrough]]; case LedgerCase::LEDGER_NOT_SET: { auto const shortcut = specifier.shortcut(); - // note, unspecified defaults to validated in reporting mode if (shortcut == - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED || - (shortcut == - org::xrpl::rpc::v1::LedgerSpecifier:: - SHORTCUT_UNSPECIFIED && - context.app.config().reporting())) + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED) { return getLedger(ledger, LedgerShortcut::VALIDATED, context); } @@ -492,8 +501,6 @@ getLedger(T& ledger, uint32_t ledgerIndex, Context& context) ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex); if (ledger == nullptr) { - if (context.app.config().reporting()) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; auto cur = context.ledgerMaster.getCurrentLedger(); if (cur->info().seq == ledgerIndex) { @@ -520,10 +527,7 @@ template Status getLedger(T& ledger, LedgerShortcut shortcut, Context& context) { - if (isValidatedOld( - context.ledgerMaster, - context.app.config().standalone() || - context.app.config().reporting())) + if (isValidatedOld(context.ledgerMaster, context.app.config().standalone())) { if (context.apiVersion == 1) return {rpcNO_NETWORK, "InsufficientNetworkMode"}; @@ -546,18 +550,11 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context& context) { if (shortcut == LedgerShortcut::CURRENT) { - if (context.app.config().reporting()) - return { - rpcLGR_NOT_FOUND, - "Reporting does not track current ledger"}; ledger = context.ledgerMaster.getCurrentLedger(); assert(ledger->open()); } else if (shortcut == LedgerShortcut::CLOSED) { - if (context.app.config().reporting()) - return { - rpcLGR_NOT_FOUND, "Reporting does not track closed ledger"}; ledger = context.ledgerMaster.getClosedLedger(); assert(!ledger->open()); } @@ -934,13 +931,14 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 22> + static constexpr std::array, 25> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, {jss::amm, ltAMM}, {jss::bridge, ltBRIDGE}, {jss::check, ltCHECK}, + {jss::credential, ltCREDENTIAL}, {jss::deposit_preauth, ltDEPOSIT_PREAUTH}, {jss::did, ltDID}, {jss::directory, ltDIR_NODE}, @@ -958,7 +956,9 @@ chooseLedgerEntryType(Json::Value const& params) {jss::ticket, ltTICKET}, {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, {jss::xchain_owned_create_account_claim_id, - ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}}}; + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, + {jss::mpt_issuance, ltMPTOKEN_ISSUANCE}, + {jss::mptoken, ltMPTOKEN}}}; auto const& p = params[jss::type]; if (!p.isString()) @@ -986,6 +986,22 @@ chooseLedgerEntryType(Json::Value const& params) return result; } +bool +isAccountObjectsValidType(LedgerEntryType const& type) +{ + switch (type) + { + case LedgerEntryType::ltAMENDMENTS: + case LedgerEntryType::ltDIR_NODE: + case LedgerEntryType::ltFEE_SETTINGS: + case LedgerEntryType::ltLEDGER_HASHES: + case LedgerEntryType::ltNEGATIVE_UNL: + return false; + default: + return true; + } +} + beast::SemanticVersion const firstVersion("1.0.0"); beast::SemanticVersion const goodVersion("1.0.0"); beast::SemanticVersion const lastVersion("1.0.0"); @@ -1014,9 +1030,6 @@ getAPIVersionNumber(Json::Value const& jv, bool betaEnabled) std::variant, Json::Value> getLedgerByContext(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - auto const hasHash = context.params.isMember(jss::ledger_hash); auto const hasIndex = context.params.isMember(jss::ledger_index); std::uint32_t ledgerIndex = 0; diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h similarity index 92% rename from src/ripple/rpc/impl/RPCHelpers.h rename to src/xrpld/rpc/detail/RPCHelpers.h index e003773e50c..54c426b17c3 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -20,17 +20,17 @@ #ifndef RIPPLE_RPC_RPCHELPERS_H_INCLUDED #define RIPPLE_RPC_RPCHELPERS_H_INCLUDED -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include #include @@ -232,6 +232,15 @@ setVersion(Object& parent, unsigned int apiVersion, bool betaEnabled) std::pair chooseLedgerEntryType(Json::Value const& params); +/** + * Check if the type is a valid filtering type for account_objects method + * + * Since Amendments, DirectoryNode, FeeSettings, LedgerHashes can not be + * owned by an account, this function will return false in these situations. + */ +bool +isAccountObjectsValidType(LedgerEntryType const& type); + /** * Retrieve the api version number from the json value * diff --git a/src/ripple/rpc/impl/Role.cpp b/src/xrpld/rpc/detail/Role.cpp similarity index 91% rename from src/ripple/rpc/impl/Role.cpp rename to src/xrpld/rpc/detail/Role.cpp index 1807ecc20f6..5cfe30b26e6 100644 --- a/src/ripple/rpc/impl/Role.cpp +++ b/src/xrpld/rpc/detail/Role.cpp @@ -17,10 +17,8 @@ */ //============================================================================== -#include -#include +#include #include -#include #include #include #include @@ -96,7 +94,7 @@ requestRole( Port const& port, Json::Value const& params, beast::IP::Endpoint const& remoteIp, - boost::string_view const& user) + std::string_view user) { if (isAdmin(port, params, remoteIp.address())) return Role::ADMIN; @@ -142,8 +140,8 @@ requestInboundEndpoint( Resource::Manager& manager, beast::IP::Endpoint const& remoteAddress, Role const& role, - boost::string_view const& user, - boost::string_view const& forwardedFor) + std::string_view user, + std::string_view forwardedFor) { if (isUnlimited(role)) return manager.newUnlimitedEndpoint(remoteAddress); @@ -152,18 +150,18 @@ requestInboundEndpoint( remoteAddress, role == Role::PROXY, forwardedFor); } -static boost::string_view -extractIpAddrFromField(boost::string_view field) +static std::string_view +extractIpAddrFromField(std::string_view field) { // Lambda to trim leading and trailing spaces on the field. - auto trim = [](boost::string_view str) -> boost::string_view { - boost::string_view ret = str; + auto trim = [](std::string_view str) -> std::string_view { + std::string_view ret = str; // Only do the work if there's at least one leading space. if (!ret.empty() && ret.front() == ' ') { std::size_t const firstNonSpace = ret.find_first_not_of(' '); - if (firstNonSpace == boost::string_view::npos) + if (firstNonSpace == std::string_view::npos) // We know there's at least one leading space. So if we got // npos, then it must be all spaces. Return empty string_view. return {}; @@ -178,7 +176,7 @@ extractIpAddrFromField(boost::string_view field) c == ' ' || c == '\r' || c == '\n') { std::size_t const lastNonSpace = ret.find_last_not_of(" \r\n"); - if (lastNonSpace == boost::string_view::npos) + if (lastNonSpace == std::string_view::npos) // We know there's at least one leading space. So if we // got npos, then it must be all spaces. return {}; @@ -189,7 +187,7 @@ extractIpAddrFromField(boost::string_view field) return ret; }; - boost::string_view ret = trim(field); + std::string_view ret = trim(field); if (ret.empty()) return {}; @@ -251,13 +249,13 @@ extractIpAddrFromField(boost::string_view field) // If there's a port appended to the IP address, strip that by // terminating at the colon. - if (std::size_t colon = ret.find(':'); colon != boost::string_view::npos) + if (std::size_t colon = ret.find(':'); colon != std::string_view::npos) ret = ret.substr(0, colon); return ret; } -boost::string_view +std::string_view forwardedFor(http_request_type const& request) { // Look for the Forwarded field in the request. @@ -286,10 +284,9 @@ forwardedFor(http_request_type const& request) // We found a "for=". Scan for the end of the IP address. std::size_t const pos = [&found, &it]() { - std::size_t pos = - boost::string_view(found, it->value().end() - found) - .find_first_of(",;"); - if (pos != boost::string_view::npos) + std::size_t pos = std::string_view(found, it->value().end() - found) + .find_first_of(",;"); + if (pos != std::string_view::npos) return pos; return it->value().size() - forStr.size(); diff --git a/src/ripple/rpc/impl/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp similarity index 96% rename from src/ripple/rpc/impl/ServerHandler.cpp rename to src/xrpld/rpc/detail/ServerHandler.cpp index d643bdb6331..ccf0c12b5ad 100644 --- a/src/ripple/rpc/impl/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -17,33 +17,33 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -247,6 +247,8 @@ build_map(boost::beast::http::fields const& h) std::map c; for (auto const& e : h) { + // key cannot be a std::string_view because it needs to be used in + // map and along with iterators std::string key(e.name_string()); std::transform(key.begin(), key.end(), key.begin(), [](auto kc) { return std::tolower(static_cast(kc)); @@ -386,9 +388,9 @@ logDuration( beast::Journal& journal) { using namespace std::chrono_literals; - auto const level = (duration >= 10s) - ? journal.error() - : (duration >= 1s) ? journal.warn() : journal.debug(); + auto const level = (duration >= 10s) ? journal.error() + : (duration >= 1s) ? journal.warn() + : journal.debug(); JLOG(level) << "RPC request processing duration = " << std::chrono::duration_cast( @@ -592,8 +594,8 @@ ServerHandler::processRequest( beast::IP::Endpoint const& remoteIPAddress, Output&& output, std::shared_ptr coro, - boost::string_view forwardedFor, - boost::string_view user) + std::string_view forwardedFor, + std::string_view user) { auto rpcJ = app_.journal("RPC"); @@ -847,8 +849,8 @@ ServerHandler::processRequest( */ if (role != Role::IDENTIFIED && role != Role::PROXY) { - forwardedFor.clear(); - user.clear(); + forwardedFor.remove_suffix(forwardedFor.size()); + user.remove_suffix(user.size()); } JLOG(m_journal.debug()) << "Query: " << strMethod << params; diff --git a/src/ripple/rpc/impl/Status.cpp b/src/xrpld/rpc/detail/Status.cpp similarity index 98% rename from src/ripple/rpc/impl/Status.cpp rename to src/xrpld/rpc/detail/Status.cpp index e9e64da7ac0..af212d37f14 100644 --- a/src/ripple/rpc/impl/Status.cpp +++ b/src/xrpld/rpc/detail/Status.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/ripple/rpc/impl/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp similarity index 96% rename from src/ripple/rpc/impl/TransactionSign.cpp rename to src/xrpld/rpc/detail/TransactionSign.cpp index 3f4407c86e7..2f10387bc81 100644 --- a/src/ripple/rpc/impl/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -17,29 +17,29 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include // Validity::Valid -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Validity::Valid +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -213,7 +213,8 @@ checkPayment( if (!dstAccountID) return RPC::invalid_field_error("tx_json.Destination"); - if ((doPath == false) && params.isMember(jss::build_path)) + if (params.isMember(jss::build_path) && + ((doPath == false) || amount.holds())) return RPC::make_error( rpcINVALID_PARAMS, "Field 'build_path' not allowed in this context."); @@ -827,11 +828,7 @@ transactionSign( if (!preprocResult.second) return preprocResult.first; - std::shared_ptr ledger; - if (app.config().reporting()) - ledger = app.getLedgerMaster().getValidatedLedger(); - else - ledger = app.openLedger().current(); + std::shared_ptr ledger = app.openLedger().current(); // Make sure the STTx makes a legitimate Transaction. std::pair txn = transactionConstructImpl(preprocResult.second, ledger->rules(), app); @@ -1051,7 +1048,7 @@ transactionSignFor( auto& sttx = preprocResult.second; { // Make the signer object that we'll inject. - STObject signer(sfSigner); + STObject signer = STObject::makeInnerObject(sfSigner); signer[sfAccount] = *signerAccountID; signer.setFieldVL(sfTxnSignature, signForParams.getSignature()); signer.setFieldVL( diff --git a/src/ripple/rpc/impl/TransactionSign.h b/src/xrpld/rpc/detail/TransactionSign.h similarity index 97% rename from src/ripple/rpc/impl/TransactionSign.h rename to src/xrpld/rpc/detail/TransactionSign.h index 2a38031f50a..36fb60555cf 100644 --- a/src/ripple/rpc/impl/TransactionSign.h +++ b/src/xrpld/rpc/detail/TransactionSign.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_RPC_TRANSACTIONSIGN_H_INCLUDED #define RIPPLE_RPC_TRANSACTIONSIGN_H_INCLUDED -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/impl/Tuning.h b/src/xrpld/rpc/detail/Tuning.h similarity index 100% rename from src/ripple/rpc/impl/Tuning.h rename to src/xrpld/rpc/detail/Tuning.h diff --git a/src/ripple/rpc/impl/WSInfoSub.h b/src/xrpld/rpc/detail/WSInfoSub.h similarity index 91% rename from src/ripple/rpc/impl/WSInfoSub.h rename to src/xrpld/rpc/detail/WSInfoSub.h index 267c8f98f24..2267b9147f4 100644 --- a/src/ripple/rpc/impl/WSInfoSub.h +++ b/src/xrpld/rpc/detail/WSInfoSub.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_RPC_WSINFOSUB_H #define RIPPLE_RPC_WSINFOSUB_H -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include @@ -55,13 +55,13 @@ class WSInfoSub : public InfoSub } } - boost::string_view + std::string_view user() const { return user_; } - boost::string_view + std::string_view forwarded_for() const { return fwdfor_; diff --git a/src/ripple/rpc/handlers/AMMInfo.cpp b/src/xrpld/rpc/handlers/AMMInfo.cpp similarity index 89% rename from src/ripple/rpc/handlers/AMMInfo.cpp rename to src/xrpld/rpc/handlers/AMMInfo.cpp index b240c8c2ae6..9d5b20f1d63 100644 --- a/src/ripple/rpc/handlers/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/AMMInfo.cpp @@ -16,15 +16,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -96,8 +96,15 @@ doAMMInfo(RPC::JsonContext& context) std::optional issue2; std::optional ammID; - if ((params.isMember(jss::asset) != params.isMember(jss::asset2)) || - (params.isMember(jss::asset) == params.isMember(jss::amm_account))) + constexpr auto invalid = [](Json::Value const& params) -> bool { + return (params.isMember(jss::asset) != + params.isMember(jss::asset2)) || + (params.isMember(jss::asset) == + params.isMember(jss::amm_account)); + }; + + // NOTE, identical check for apVersion >= 3 below + if (context.apiVersion < 3 && invalid(params)) return Unexpected(rpcINVALID_PARAMS); if (params.isMember(jss::asset)) @@ -125,12 +132,10 @@ doAMMInfo(RPC::JsonContext& context) if (!sle) return Unexpected(rpcACT_MALFORMED); ammID = sle->getFieldH256(sfAMMID); + if (ammID->isZero()) + return Unexpected(rpcACT_NOT_FOUND); } - assert( - (issue1.has_value() == issue2.has_value()) && - (issue1.has_value() != ammID.has_value())); - if (params.isMember(jss::account)) { accountID = getAccount(params[jss::account], result); @@ -138,6 +143,14 @@ doAMMInfo(RPC::JsonContext& context) return Unexpected(rpcACT_MALFORMED); } + // NOTE, identical check for apVersion < 3 above + if (context.apiVersion >= 3 && invalid(params)) + return Unexpected(rpcINVALID_PARAMS); + + assert( + (issue1.has_value() == issue2.has_value()) && + (issue1.has_value() != ammID.has_value())); + auto const ammKeylet = [&]() { if (issue1 && issue2) return keylet::amm(*issue1, *issue2); diff --git a/src/ripple/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/AccountChannels.cpp similarity index 94% rename from src/ripple/rpc/handlers/AccountChannels.cpp rename to src/xrpld/rpc/handlers/AccountChannels.cpp index da751d0dae2..ad591b04a1c 100644 --- a/src/ripple/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/AccountChannels.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { void diff --git a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp similarity index 91% rename from src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp rename to src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp index 0114e25c712..6c8fe282674 100644 --- a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp +++ b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp similarity index 96% rename from src/ripple/rpc/handlers/AccountInfo.cpp rename to src/xrpld/rpc/handlers/AccountInfo.cpp index b4d31f6187c..dab0274e9dd 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp similarity index 95% rename from src/ripple/rpc/handlers/AccountLines.cpp rename to src/xrpld/rpc/handlers/AccountLines.cpp index 3f7e154690b..64ca95ebe56 100644 --- a/src/ripple/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/xrpld/rpc/handlers/AccountObjects.cpp similarity index 82% rename from src/ripple/rpc/handlers/AccountObjects.cpp rename to src/xrpld/rpc/handlers/AccountObjects.cpp index f228272f640..538b1d79424 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/AccountObjects.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -77,8 +77,9 @@ doAccountNFTs(RPC::JsonContext& context) return *err; uint256 marker; + bool const markerSet = params.isMember(jss::marker); - if (params.isMember(jss::marker)) + if (markerSet) { auto const& m = params[jss::marker]; if (!m.isString()) @@ -100,6 +101,7 @@ doAccountNFTs(RPC::JsonContext& context) // Continue iteration from the current page: bool pastMarker = marker.isZero(); + bool markerFound = false; uint256 const maskedMarker = marker & nft::pageMask; while (cp) { @@ -121,12 +123,23 @@ doAccountNFTs(RPC::JsonContext& context) uint256 const nftokenID = o[sfNFTokenID]; uint256 const maskedNftokenID = nftokenID & nft::pageMask; - if (!pastMarker && maskedNftokenID < maskedMarker) - continue; + if (!pastMarker) + { + if (maskedNftokenID < maskedMarker) + continue; - if (!pastMarker && maskedNftokenID == maskedMarker && - nftokenID <= marker) - continue; + if (maskedNftokenID == maskedMarker && nftokenID < marker) + continue; + + if (nftokenID == marker) + { + markerFound = true; + continue; + } + } + + if (markerSet && !markerFound) + return RPC::invalid_field_error(jss::marker); pastMarker = true; @@ -157,6 +170,9 @@ doAccountNFTs(RPC::JsonContext& context) cp = nullptr; } + if (markerSet && !markerFound) + return RPC::invalid_field_error(jss::marker); + result[jss::account] = toBase58(accountID); context.loadType = Resource::feeMediumBurdenRPC; return result; @@ -206,7 +222,9 @@ doAccountObjects(RPC::JsonContext& context) {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, {jss::xchain_owned_create_account_claim_id, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, - {jss::bridge, ltBRIDGE}}; + {jss::bridge, ltBRIDGE}, + {jss::mpt_issuance, ltMPTOKEN_ISSUANCE}, + {jss::mptoken, ltMPTOKEN}}; typeFilter.emplace(); typeFilter->reserve(std::size(deletionBlockers)); @@ -225,6 +243,9 @@ doAccountObjects(RPC::JsonContext& context) { auto [rpcStatus, type] = RPC::chooseLedgerEntryType(params); + if (!RPC::isAccountObjectsValidType(type)) + return RPC::invalid_field_error(jss::type); + if (rpcStatus) { result.clear(); @@ -249,18 +270,15 @@ doAccountObjects(RPC::JsonContext& context) if (!marker.isString()) return RPC::expected_field_error(jss::marker, "string"); - std::stringstream ss(marker.asString()); - std::string s; - if (!std::getline(ss, s, ',')) - return RPC::invalid_field_error(jss::marker); - - if (!dirIndex.parseHex(s)) + auto const& markerStr = marker.asString(); + auto const& idx = markerStr.find(','); + if (idx == std::string::npos) return RPC::invalid_field_error(jss::marker); - if (!std::getline(ss, s, ',')) + if (!dirIndex.parseHex(markerStr.substr(0, idx))) return RPC::invalid_field_error(jss::marker); - if (!entryIndex.parseHex(s)) + if (!entryIndex.parseHex(markerStr.substr(idx + 1))) return RPC::invalid_field_error(jss::marker); } @@ -272,9 +290,7 @@ doAccountObjects(RPC::JsonContext& context) entryIndex, limit, result)) - { - result[jss::account_objects] = Json::arrayValue; - } + return RPC::invalid_field_error(jss::marker); result[jss::account] = toBase58(accountID); context.loadType = Resource::feeMediumBurdenRPC; diff --git a/src/ripple/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/AccountOffers.cpp similarity index 93% rename from src/ripple/rpc/handlers/AccountOffers.cpp rename to src/xrpld/rpc/handlers/AccountOffers.cpp index 2be50e16880..3c4a4404984 100644 --- a/src/ripple/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/AccountOffers.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/AccountTx.cpp similarity index 93% rename from src/ripple/rpc/handlers/AccountTx.cpp rename to src/xrpld/rpc/handlers/AccountTx.cpp index 89d4f49a519..887694daf21 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/AccountTx.cpp @@ -17,26 +17,25 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -218,16 +217,6 @@ std::pair doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) { context.loadType = Resource::feeMediumBurdenRPC; - if (context.app.config().reporting()) - { - auto const db = dynamic_cast( - &context.app.getRelationalDatabase()); - - if (!db) - Throw("Failed to get relational database"); - - return db->getAccountTx(args); - } AccountTxResult result; @@ -361,6 +350,8 @@ populateJsonResponse( insertDeliveredAmount( jvObj[jss::meta], context, txn, *txnMeta); insertNFTSyntheticInJson(jvObj, sttx, *txnMeta); + RPC::insertMPTokenIssuanceID( + jvObj[jss::meta], sttx, *txnMeta); } else assert(false && "Missing transaction medatata"); @@ -391,8 +382,6 @@ populateJsonResponse( response[jss::marker][jss::ledger] = result.marker->ledgerSeq; response[jss::marker][jss::seq] = result.marker->txnSeq; } - if (context.app.config().reporting()) - response["used_postgres"] = true; } JLOG(context.j.debug()) << __func__ << " : finished"; diff --git a/src/ripple/rpc/handlers/BlackList.cpp b/src/xrpld/rpc/handlers/BlackList.cpp similarity index 89% rename from src/ripple/rpc/handlers/BlackList.cpp rename to src/xrpld/rpc/handlers/BlackList.cpp index 3fe5b99e5a0..3e887ff7309 100644 --- a/src/ripple/rpc/handlers/BlackList.cpp +++ b/src/xrpld/rpc/handlers/BlackList.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/BookOffers.cpp b/src/xrpld/rpc/handlers/BookOffers.cpp similarity index 90% rename from src/ripple/rpc/handlers/BookOffers.cpp rename to src/xrpld/rpc/handlers/BookOffers.cpp index e21d7047f69..dccc03de5be 100644 --- a/src/ripple/rpc/handlers/BookOffers.cpp +++ b/src/xrpld/rpc/handlers/BookOffers.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -204,13 +204,13 @@ doBookOffers(RPC::JsonContext& context) Json::Value doBookChanges(RPC::JsonContext& context) { - auto res = RPC::getLedgerByContext(context); + std::shared_ptr ledger; - if (std::holds_alternative(res)) - return std::get(res); + Json::Value result = RPC::lookupLedger(ledger, context); + if (ledger == nullptr) + return result; - return RPC::computeBookChanges( - std::get>(res)); + return RPC::computeBookChanges(ledger); } } // namespace ripple diff --git a/src/ripple/rpc/handlers/CanDelete.cpp b/src/xrpld/rpc/handlers/CanDelete.cpp similarity index 88% rename from src/ripple/rpc/handlers/CanDelete.cpp rename to src/xrpld/rpc/handlers/CanDelete.cpp index 3127b5c4390..df2301d03e0 100644 --- a/src/ripple/rpc/handlers/CanDelete.cpp +++ b/src/xrpld/rpc/handlers/CanDelete.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -34,9 +34,6 @@ namespace ripple { Json::Value doCanDelete(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return RPC::make_error(rpcREPORTING_UNSUPPORTED); - if (!context.app.getSHAMapStore().advisoryDelete()) return RPC::make_error(rpcNOT_ENABLED); diff --git a/src/ripple/rpc/handlers/Connect.cpp b/src/xrpld/rpc/handlers/Connect.cpp similarity index 83% rename from src/ripple/rpc/handlers/Connect.cpp rename to src/xrpld/rpc/handlers/Connect.cpp index 46fac457cb8..c564319dc8b 100644 --- a/src/ripple/rpc/handlers/Connect.cpp +++ b/src/xrpld/rpc/handlers/Connect.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -37,9 +37,6 @@ namespace ripple { Json::Value doConnect(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - if (context.app.config().standalone()) return "cannot connect in standalone mode"; diff --git a/src/ripple/rpc/handlers/ConsensusInfo.cpp b/src/xrpld/rpc/handlers/ConsensusInfo.cpp similarity index 78% rename from src/ripple/rpc/handlers/ConsensusInfo.cpp rename to src/xrpld/rpc/handlers/ConsensusInfo.cpp index ee7eb10e684..ce727bb4006 100644 --- a/src/ripple/rpc/handlers/ConsensusInfo.cpp +++ b/src/xrpld/rpc/handlers/ConsensusInfo.cpp @@ -17,22 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doConsensusInfo(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - Json::Value ret(Json::objectValue); ret[jss::info] = context.netOps.getConsensusInfo(); diff --git a/src/xrpld/rpc/handlers/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/DepositAuthorized.cpp new file mode 100644 index 00000000000..50aa9ef2898 --- /dev/null +++ b/src/xrpld/rpc/handlers/DepositAuthorized.cpp @@ -0,0 +1,203 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2018 Ripple Labs Inc. + + Permission to use, copy, modify, and/or 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 +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +// { +// source_account : +// destination_account : +// ledger_hash : +// ledger_index : +// credentials : [,...] +// } + +Json::Value +doDepositAuthorized(RPC::JsonContext& context) +{ + Json::Value const& params = context.params; + + // Validate source_account. + if (!params.isMember(jss::source_account)) + return RPC::missing_field_error(jss::source_account); + if (!params[jss::source_account].isString()) + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message(jss::source_account, "a string")); + + auto srcID = parseBase58(params[jss::source_account].asString()); + if (!srcID) + return rpcError(rpcACT_MALFORMED); + auto const srcAcct{std::move(srcID.value())}; + + // Validate destination_account. + if (!params.isMember(jss::destination_account)) + return RPC::missing_field_error(jss::destination_account); + if (!params[jss::destination_account].isString()) + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message(jss::destination_account, "a string")); + + auto dstID = + parseBase58(params[jss::destination_account].asString()); + if (!dstID) + return rpcError(rpcACT_MALFORMED); + auto const dstAcct{std::move(dstID.value())}; + + // Validate ledger. + std::shared_ptr ledger; + Json::Value result = RPC::lookupLedger(ledger, context); + + if (!ledger) + return result; + + // If source account is not in the ledger it can't be authorized. + if (!ledger->exists(keylet::account(srcAcct))) + { + RPC::inject_error(rpcSRC_ACT_NOT_FOUND, result); + return result; + } + + // If destination account is not in the ledger you can't deposit to it, eh? + auto const sleDest = ledger->read(keylet::account(dstAcct)); + if (!sleDest) + { + RPC::inject_error(rpcDST_ACT_NOT_FOUND, result); + return result; + } + + bool const reqAuth = + (sleDest->getFlags() & lsfDepositAuth) && (srcAcct != dstAcct); + bool const credentialsPresent = params.isMember(jss::credentials); + + std::set> sorted; + std::vector> lifeExtender; + if (credentialsPresent) + { + auto const& creds(params[jss::credentials]); + if (!creds.isArray() || !creds) + { + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message( + jss::credentials, + "is non-empty array of CredentialID(hash256)")); + } + else if (creds.size() > maxCredentialsArraySize) + { + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message( + jss::credentials, "array too long")); + } + + lifeExtender.reserve(creds.size()); + for (auto const& jo : creds) + { + if (!jo.isString()) + { + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message( + jss::credentials, "an array of CredentialID(hash256)")); + } + + uint256 credH; + auto const credS = jo.asString(); + if (!credH.parseHex(credS)) + { + return RPC::make_error( + rpcINVALID_PARAMS, + RPC::expected_field_message( + jss::credentials, "an array of CredentialID(hash256)")); + } + + std::shared_ptr sleCred = + ledger->read(keylet::credential(credH)); + if (!sleCred) + { + RPC::inject_error( + rpcBAD_CREDENTIALS, "credentials don't exist", result); + return result; + } + + if (!(sleCred->getFlags() & lsfAccepted)) + { + RPC::inject_error( + rpcBAD_CREDENTIALS, "credentials aren't accepted", result); + return result; + } + + if (credentials::checkExpired( + sleCred, ledger->info().parentCloseTime)) + { + RPC::inject_error( + rpcBAD_CREDENTIALS, "credentials are expired", result); + return result; + } + + if ((*sleCred)[sfSubject] != srcAcct) + { + RPC::inject_error( + rpcBAD_CREDENTIALS, + "credentials doesn't belong to the root account", + result); + return result; + } + + auto [it, ins] = sorted.emplace( + (*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]); + if (!ins) + { + RPC::inject_error( + rpcBAD_CREDENTIALS, "duplicates in credentials", result); + return result; + } + lifeExtender.push_back(std::move(sleCred)); + } + } + + // 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) + depositAuthorized = + ledger->exists(keylet::depositPreauth(dstAcct, srcAcct)) || + (credentialsPresent && + ledger->exists(keylet::depositPreauth(dstAcct, sorted))); + + result[jss::source_account] = params[jss::source_account].asString(); + result[jss::destination_account] = + params[jss::destination_account].asString(); + if (credentialsPresent) + result[jss::credentials] = params[jss::credentials]; + + result[jss::deposit_authorized] = depositAuthorized; + return result; +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/Feature1.cpp b/src/xrpld/rpc/handlers/Feature1.cpp similarity index 83% rename from src/ripple/rpc/handlers/Feature1.cpp rename to src/xrpld/rpc/handlers/Feature1.cpp index 94a205e62f7..75e583a352c 100644 --- a/src/ripple/rpc/handlers/Feature1.cpp +++ b/src/xrpld/rpc/handlers/Feature1.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -35,8 +35,14 @@ namespace ripple { Json::Value doFeature(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); + if (context.params.isMember(jss::feature)) + { + // ensure that the `feature` param is a string + if (!context.params[jss::feature].isString()) + { + return rpcError(rpcINVALID_PARAMS); + } + } bool const isAdmin = context.role == Role::ADMIN; // Get majority amendment status diff --git a/src/ripple/rpc/handlers/Fee1.cpp b/src/xrpld/rpc/handlers/Fee1.cpp similarity index 83% rename from src/ripple/rpc/handlers/Fee1.cpp rename to src/xrpld/rpc/handlers/Fee1.cpp index 89fa9c6ea07..da766fdbb32 100644 --- a/src/ripple/rpc/handlers/Fee1.cpp +++ b/src/xrpld/rpc/handlers/Fee1.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value diff --git a/src/ripple/rpc/handlers/FetchInfo.cpp b/src/xrpld/rpc/handlers/FetchInfo.cpp similarity index 80% rename from src/ripple/rpc/handlers/FetchInfo.cpp rename to src/xrpld/rpc/handlers/FetchInfo.cpp index 79d82b646a0..a4287266e52 100644 --- a/src/ripple/rpc/handlers/FetchInfo.cpp +++ b/src/xrpld/rpc/handlers/FetchInfo.cpp @@ -17,22 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doFetchInfo(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - Json::Value ret(Json::objectValue); if (context.params.isMember(jss::clear) && diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp similarity index 95% rename from src/ripple/rpc/handlers/GatewayBalances.cpp rename to src/xrpld/rpc/handlers/GatewayBalances.cpp index 89be6290f77..8fd13d472cc 100644 --- a/src/ripple/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp similarity index 97% rename from src/ripple/rpc/handlers/GetAggregatePrice.cpp rename to src/xrpld/rpc/handlers/GetAggregatePrice.cpp index 3554ba90c96..7467b94b63f 100644 --- a/src/ripple/rpc/handlers/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/rpc/handlers/GetCounts.cpp b/src/xrpld/rpc/handlers/GetCounts.cpp similarity index 66% rename from src/ripple/rpc/handlers/GetCounts.cpp rename to src/xrpld/rpc/handlers/GetCounts.cpp index 131cf7d3614..690106ebbd2 100644 --- a/src/ripple/rpc/handlers/GetCounts.cpp +++ b/src/xrpld/rpc/handlers/GetCounts.cpp @@ -17,22 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -73,7 +71,7 @@ getCountsJson(Application& app, int minObjectCount) ret[k] = v; } - if (!app.config().reporting() && app.config().useTxTables()) + if (app.config().useTxTables()) { auto const db = dynamic_cast(&app.getRelationalDatabase()); @@ -113,11 +111,11 @@ getCountsJson(Application& app, int minObjectCount) ret[jss::AL_hit_rate] = app.getAcceptedLedgerCache().getHitRate(); ret[jss::fullbelow_size] = - static_cast(app.getNodeFamily().getFullBelowCache(0)->size()); + static_cast(app.getNodeFamily().getFullBelowCache()->size()); ret[jss::treenode_cache_size] = - app.getNodeFamily().getTreeNodeCache(0)->getCacheSize(); + app.getNodeFamily().getTreeNodeCache()->getCacheSize(); ret[jss::treenode_track_size] = - app.getNodeFamily().getTreeNodeCache(0)->getTrackSize(); + app.getNodeFamily().getTreeNodeCache()->getTrackSize(); std::string uptime; auto s = UptimeClock::now(); @@ -129,27 +127,7 @@ getCountsJson(Application& app, int minObjectCount) textTime(uptime, s, "second", 1s); ret[jss::uptime] = uptime; - if (auto shardStore = app.getShardStore()) - { - auto shardFamily{dynamic_cast(app.getShardFamily())}; - auto const [cacheSz, trackSz] = shardFamily->getTreeNodeCacheSize(); - Json::Value& jv = (ret[jss::shards] = Json::objectValue); - - jv[jss::fullbelow_size] = shardFamily->getFullBelowCacheSize(); - jv[jss::treenode_cache_size] = cacheSz; - jv[jss::treenode_track_size] = trackSz; - ret[jss::write_load] = shardStore->getWriteLoad(); - jv[jss::node_writes] = std::to_string(shardStore->getStoreCount()); - jv[jss::node_reads_total] = shardStore->getFetchTotalCount(); - jv[jss::node_reads_hit] = shardStore->getFetchHitCount(); - jv[jss::node_written_bytes] = - std::to_string(shardStore->getStoreSize()); - jv[jss::node_read_bytes] = shardStore->getFetchSize(); - } - else - { - app.getNodeStore().getCountsJson(ret); - } + app.getNodeStore().getCountsJson(ret); return ret; } diff --git a/src/ripple/rpc/handlers/GetCounts.h b/src/xrpld/rpc/handlers/GetCounts.h similarity index 94% rename from src/ripple/rpc/handlers/GetCounts.h rename to src/xrpld/rpc/handlers/GetCounts.h index 28a3e7d0604..dacf93fa910 100644 --- a/src/ripple/rpc/handlers/GetCounts.h +++ b/src/xrpld/rpc/handlers/GetCounts.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RPC_HANDLERS_GETCOUNTS_H_INCLUDED #define RIPPLE_RPC_HANDLERS_GETCOUNTS_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/xrpld/rpc/handlers/Handlers.h similarity index 95% rename from src/ripple/rpc/handlers/Handlers.h rename to src/xrpld/rpc/handlers/Handlers.h index 6c74c5c7e5c..0085f51465a 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/xrpld/rpc/handlers/Handlers.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_RPC_HANDLERS_HANDLERS_H_INCLUDED #define RIPPLE_RPC_HANDLERS_HANDLERS_H_INCLUDED -#include +#include namespace ripple { @@ -61,8 +61,6 @@ doConsensusInfo(RPC::JsonContext&); Json::Value doDepositAuthorized(RPC::JsonContext&); Json::Value -doDownloadShard(RPC::JsonContext&); -Json::Value doFeature(RPC::JsonContext&); Json::Value doFee(RPC::JsonContext&); @@ -101,8 +99,6 @@ doNFTBuyOffers(RPC::JsonContext&); Json::Value doNFTSellOffers(RPC::JsonContext&); Json::Value -doNodeToShard(RPC::JsonContext&); -Json::Value doNoRippleCheck(RPC::JsonContext&); Json::Value doOwnerInfo(RPC::JsonContext&); @@ -139,8 +135,6 @@ doSign(RPC::JsonContext&); Json::Value doSignFor(RPC::JsonContext&); Json::Value -doCrawlShards(RPC::JsonContext&); -Json::Value doStop(RPC::JsonContext&); Json::Value doSubmit(RPC::JsonContext&); diff --git a/src/ripple/rpc/handlers/LedgerAccept.cpp b/src/xrpld/rpc/handlers/LedgerAccept.cpp similarity index 78% rename from src/ripple/rpc/handlers/LedgerAccept.cpp rename to src/xrpld/rpc/handlers/LedgerAccept.cpp index 14177791164..dbd7eb9f1ca 100644 --- a/src/ripple/rpc/handlers/LedgerAccept.cpp +++ b/src/xrpld/rpc/handlers/LedgerAccept.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -36,7 +36,7 @@ doLedgerAccept(RPC::JsonContext& context) { Json::Value jvResult; - if (!context.app.config().standalone() || context.app.config().reporting()) + if (!context.app.config().standalone()) { jvResult[jss::error] = "notStandAlone"; } diff --git a/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp b/src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp similarity index 86% rename from src/ripple/rpc/handlers/LedgerCleanerHandler.cpp rename to src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp index 5b76f3f8b9d..48f024c051e 100644 --- a/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp +++ b/src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerClosed.cpp b/src/xrpld/rpc/handlers/LedgerClosed.cpp similarity index 88% rename from src/ripple/rpc/handlers/LedgerClosed.cpp rename to src/xrpld/rpc/handlers/LedgerClosed.cpp index 66e003e08f5..9bc9315f648 100644 --- a/src/ripple/rpc/handlers/LedgerClosed.cpp +++ b/src/xrpld/rpc/handlers/LedgerClosed.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerCurrent.cpp b/src/xrpld/rpc/handlers/LedgerCurrent.cpp similarity index 85% rename from src/ripple/rpc/handlers/LedgerCurrent.cpp rename to src/xrpld/rpc/handlers/LedgerCurrent.cpp index 3539c800946..7f1d83c17a7 100644 --- a/src/ripple/rpc/handlers/LedgerCurrent.cpp +++ b/src/xrpld/rpc/handlers/LedgerCurrent.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerData.cpp b/src/xrpld/rpc/handlers/LedgerData.cpp similarity index 94% rename from src/ripple/rpc/handlers/LedgerData.cpp rename to src/xrpld/rpc/handlers/LedgerData.cpp index f5433945772..ad26b83b43b 100644 --- a/src/ripple/rpc/handlers/LedgerData.cpp +++ b/src/xrpld/rpc/handlers/LedgerData.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerDiff.cpp b/src/xrpld/rpc/handlers/LedgerDiff.cpp similarity index 97% rename from src/ripple/rpc/handlers/LedgerDiff.cpp rename to src/xrpld/rpc/handlers/LedgerDiff.cpp index 122dcfa31d5..6398be60973 100644 --- a/src/ripple/rpc/handlers/LedgerDiff.cpp +++ b/src/xrpld/rpc/handlers/LedgerDiff.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include namespace ripple { std::pair diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp similarity index 78% rename from src/ripple/rpc/handlers/LedgerEntry.cpp rename to src/xrpld/rpc/handlers/LedgerEntry.cpp index 8985a880824..5d03bbb189d 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -17,23 +17,55 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { +static STArray +parseAuthorizeCredentials(Json::Value const& jv) +{ + STArray arr(sfAuthorizeCredentials, jv.size()); + for (auto const& jo : jv) + { + if (!jo.isObject() || // + !jo.isMember(jss::issuer) || !jo[jss::issuer].isString() || + !jo.isMember(jss::credential_type) || + !jo[jss::credential_type].isString()) + return {}; + + auto const issuer = parseBase58(jo[jss::issuer].asString()); + if (!issuer || !*issuer) + return {}; + + auto const credentialType = + strUnHex(jo[jss::credential_type].asString()); + if (!credentialType || credentialType->empty() || + credentialType->size() > maxCredentialTypeLength) + return {}; + + auto credential = STObject::makeInnerObject(sfCredential); + credential.setAccountID(sfIssuer, *issuer); + credential.setFieldVL(sfCredentialType, *credentialType); + arr.push_back(std::move(credential)); + } + + return arr; +} + // { // ledger_hash : // ledger_index : @@ -84,44 +116,63 @@ doLedgerEntry(RPC::JsonContext& context) else if (context.params.isMember(jss::deposit_preauth)) { expectedType = ltDEPOSIT_PREAUTH; + auto const& dp = context.params[jss::deposit_preauth]; - if (!context.params[jss::deposit_preauth].isObject()) + if (!dp.isObject()) { - if (!context.params[jss::deposit_preauth].isString() || - !uNodeIndex.parseHex( - context.params[jss::deposit_preauth].asString())) + if (!dp.isString() || !uNodeIndex.parseHex(dp.asString())) { uNodeIndex = beast::zero; jvResult[jss::error] = "malformedRequest"; } } + // clang-format off else if ( - !context.params[jss::deposit_preauth].isMember(jss::owner) || - !context.params[jss::deposit_preauth][jss::owner].isString() || - !context.params[jss::deposit_preauth].isMember( - jss::authorized) || - !context.params[jss::deposit_preauth][jss::authorized] - .isString()) + (!dp.isMember(jss::owner) || !dp[jss::owner].isString()) || + (dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials)) || + (dp.isMember(jss::authorized) && !dp[jss::authorized].isString()) || + (dp.isMember(jss::authorized_credentials) && !dp[jss::authorized_credentials].isArray()) + ) + // clang-format on { jvResult[jss::error] = "malformedRequest"; } else { - auto const owner = parseBase58( - context.params[jss::deposit_preauth][jss::owner] - .asString()); - - auto const authorized = parseBase58( - context.params[jss::deposit_preauth][jss::authorized] - .asString()); - + auto const owner = + parseBase58(dp[jss::owner].asString()); if (!owner) + { jvResult[jss::error] = "malformedOwner"; - else if (!authorized) - jvResult[jss::error] = "malformedAuthorized"; + } + else if (dp.isMember(jss::authorized)) + { + auto const authorized = + parseBase58(dp[jss::authorized].asString()); + if (!authorized) + jvResult[jss::error] = "malformedAuthorized"; + else + uNodeIndex = + keylet::depositPreauth(*owner, *authorized).key; + } else - uNodeIndex = - keylet::depositPreauth(*owner, *authorized).key; + { + auto const& ac(dp[jss::authorized_credentials]); + STArray const arr = parseAuthorizeCredentials(ac); + + if (arr.empty() || (arr.size() > maxCredentialsArraySize)) + jvResult[jss::error] = "malformedAuthorizedCredentials"; + else + { + auto sorted = credentials::makeSorted(arr); + if (sorted.empty()) + jvResult[jss::error] = + "malformedAuthorizedCredentials"; + else + uNodeIndex = + keylet::depositPreauth(*owner, sorted).key; + } + } } } else if (context.params.isMember(jss::directory)) @@ -644,6 +695,118 @@ doLedgerEntry(RPC::JsonContext& context) uNodeIndex = keylet::oracle(*account, *documentID).key; } } + else if (context.params.isMember(jss::credential)) + { + expectedType = ltCREDENTIAL; + auto const& cred = context.params[jss::credential]; + + if (cred.isString()) + { + if (!uNodeIndex.parseHex(cred.asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + (!cred.isMember(jss::subject) || + !cred[jss::subject].isString()) || + (!cred.isMember(jss::issuer) || + !cred[jss::issuer].isString()) || + (!cred.isMember(jss::credential_type) || + !cred[jss::credential_type].isString())) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const subject = + parseBase58(cred[jss::subject].asString()); + auto const issuer = + parseBase58(cred[jss::issuer].asString()); + auto const credType = + strUnHex(cred[jss::credential_type].asString()); + if (!subject || subject->isZero() || !issuer || + issuer->isZero() || !credType || credType->empty()) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + uNodeIndex = keylet::credential( + *subject, + *issuer, + Slice(credType->data(), credType->size())) + .key; + } + } + } + else if (context.params.isMember(jss::mpt_issuance)) + { + expectedType = ltMPTOKEN_ISSUANCE; + auto const unparsedMPTIssuanceID = + context.params[jss::mpt_issuance]; + if (unparsedMPTIssuanceID.isString()) + { + uint192 mptIssuanceID; + if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + else + uNodeIndex = keylet::mptIssuance(mptIssuanceID).key; + } + else + { + jvResult[jss::error] = "malformedRequest"; + } + } + else if (context.params.isMember(jss::mptoken)) + { + expectedType = ltMPTOKEN; + auto const& mptJson = context.params[jss::mptoken]; + if (!mptJson.isObject()) + { + if (!uNodeIndex.parseHex(mptJson.asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !mptJson.isMember(jss::mpt_issuance_id) || + !mptJson.isMember(jss::account)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + try + { + auto const mptIssuanceIdStr = + mptJson[jss::mpt_issuance_id].asString(); + + uint192 mptIssuanceID; + if (!mptIssuanceID.parseHex(mptIssuanceIdStr)) + Throw( + "Cannot parse mpt_issuance_id"); + + auto const account = parseBase58( + mptJson[jss::account].asString()); + + if (!account || account->isZero()) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = + keylet::mptoken(mptIssuanceID, *account).key; + } + catch (std::runtime_error const&) + { + jvResult[jss::error] = "malformedRequest"; + } + } + } else { if (context.params.isMember("params") && diff --git a/src/ripple/rpc/handlers/LedgerHandler.cpp b/src/xrpld/rpc/handlers/LedgerHandler.cpp similarity index 96% rename from src/ripple/rpc/handlers/LedgerHandler.cpp rename to src/xrpld/rpc/handlers/LedgerHandler.cpp index 623cb8d75ac..2bf4fb09f94 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.cpp +++ b/src/xrpld/rpc/handlers/LedgerHandler.cpp @@ -17,16 +17,16 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace RPC { @@ -40,8 +40,7 @@ LedgerHandler::check() { auto const& params = context_.params; bool needsLedger = params.isMember(jss::ledger) || - params.isMember(jss::ledger_hash) || - params.isMember(jss::ledger_index) || context_.app.config().reporting(); + params.isMember(jss::ledger_hash) || params.isMember(jss::ledger_index); if (!needsLedger) return Status::OK; diff --git a/src/ripple/rpc/handlers/LedgerHandler.h b/src/xrpld/rpc/handlers/LedgerHandler.h similarity index 87% rename from src/ripple/rpc/handlers/LedgerHandler.h rename to src/xrpld/rpc/handlers/LedgerHandler.h index b0bca8e6635..0a1ba2b427e 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.h +++ b/src/xrpld/rpc/handlers/LedgerHandler.h @@ -20,17 +20,17 @@ #ifndef RIPPLE_RPC_HANDLERS_LEDGER_H_INCLUDED #define RIPPLE_RPC_HANDLERS_LEDGER_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace Json { class Object; diff --git a/src/ripple/rpc/handlers/LedgerHeader.cpp b/src/xrpld/rpc/handlers/LedgerHeader.cpp similarity index 89% rename from src/ripple/rpc/handlers/LedgerHeader.cpp rename to src/xrpld/rpc/handlers/LedgerHeader.cpp index 0f44436569c..b73ee260d4a 100644 --- a/src/ripple/rpc/handlers/LedgerHeader.cpp +++ b/src/xrpld/rpc/handlers/LedgerHeader.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerRequest.cpp b/src/xrpld/rpc/handlers/LedgerRequest.cpp similarity index 78% rename from src/ripple/rpc/handlers/LedgerRequest.cpp rename to src/xrpld/rpc/handlers/LedgerRequest.cpp index 83e7a2184f1..8f79bb04f30 100644 --- a/src/ripple/rpc/handlers/LedgerRequest.cpp +++ b/src/xrpld/rpc/handlers/LedgerRequest.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/rpc/handlers/LogLevel.cpp b/src/xrpld/rpc/handlers/LogLevel.cpp similarity index 91% rename from src/ripple/rpc/handlers/LogLevel.cpp rename to src/xrpld/rpc/handlers/LogLevel.cpp index 20931898d20..bf0a0e1e285 100644 --- a/src/ripple/rpc/handlers/LogLevel.cpp +++ b/src/xrpld/rpc/handlers/LogLevel.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/rpc/handlers/LogRotate.cpp b/src/xrpld/rpc/handlers/LogRotate.cpp similarity index 89% rename from src/ripple/rpc/handlers/LogRotate.cpp rename to src/xrpld/rpc/handlers/LogRotate.cpp index 93e66451f5d..b7a887b0e27 100644 --- a/src/ripple/rpc/handlers/LogRotate.cpp +++ b/src/xrpld/rpc/handlers/LogRotate.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Manifest.cpp b/src/xrpld/rpc/handlers/Manifest.cpp similarity index 88% rename from src/ripple/rpc/handlers/Manifest.cpp rename to src/xrpld/rpc/handlers/Manifest.cpp index 22abfde8a24..1debd48422a 100644 --- a/src/ripple/rpc/handlers/Manifest.cpp +++ b/src/xrpld/rpc/handlers/Manifest.cpp @@ -17,21 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doManifest(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - auto& params = context.params; if (!params.isMember(jss::public_key)) diff --git a/src/ripple/rpc/handlers/NFTOffers.cpp b/src/xrpld/rpc/handlers/NFTOffers.cpp similarity index 92% rename from src/ripple/rpc/handlers/NFTOffers.cpp rename to src/xrpld/rpc/handlers/NFTOffers.cpp index bca862d27e3..bffb3dfbb60 100644 --- a/src/ripple/rpc/handlers/NFTOffers.cpp +++ b/src/xrpld/rpc/handlers/NFTOffers.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/NoRippleCheck.cpp similarity index 94% rename from src/ripple/rpc/handlers/NoRippleCheck.cpp rename to src/xrpld/rpc/handlers/NoRippleCheck.cpp index 6cb206e2530..94830a4f397 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/NoRippleCheck.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/OwnerInfo.cpp b/src/xrpld/rpc/handlers/OwnerInfo.cpp similarity index 87% rename from src/ripple/rpc/handlers/OwnerInfo.cpp rename to src/xrpld/rpc/handlers/OwnerInfo.cpp index 546a2e70980..83bf8b0ef21 100644 --- a/src/ripple/rpc/handlers/OwnerInfo.cpp +++ b/src/xrpld/rpc/handlers/OwnerInfo.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/PathFind.cpp b/src/xrpld/rpc/handlers/PathFind.cpp similarity index 87% rename from src/ripple/rpc/handlers/PathFind.cpp rename to src/xrpld/rpc/handlers/PathFind.cpp index 9c8794b5997..cab14f9b52e 100644 --- a/src/ripple/rpc/handlers/PathFind.cpp +++ b/src/xrpld/rpc/handlers/PathFind.cpp @@ -17,15 +17,15 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/PayChanClaim.cpp b/src/xrpld/rpc/handlers/PayChanClaim.cpp similarity index 91% rename from src/ripple/rpc/handlers/PayChanClaim.cpp rename to src/xrpld/rpc/handlers/PayChanClaim.cpp index 33561463f21..1fecd5f1449 100644 --- a/src/ripple/rpc/handlers/PayChanClaim.cpp +++ b/src/xrpld/rpc/handlers/PayChanClaim.cpp @@ -17,18 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/src/ripple/rpc/handlers/Peers.cpp b/src/xrpld/rpc/handlers/Peers.cpp similarity index 87% rename from src/ripple/rpc/handlers/Peers.cpp rename to src/xrpld/rpc/handlers/Peers.cpp index 4f377da7277..f3be0df558e 100644 --- a/src/ripple/rpc/handlers/Peers.cpp +++ b/src/xrpld/rpc/handlers/Peers.cpp @@ -17,23 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doPeers(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - Json::Value jvResult(Json::objectValue); jvResult[jss::peers] = context.app.overlay().json(); diff --git a/src/ripple/rpc/handlers/Ping.cpp b/src/xrpld/rpc/handlers/Ping.cpp similarity index 84% rename from src/ripple/rpc/handlers/Ping.cpp rename to src/xrpld/rpc/handlers/Ping.cpp index 7bd91d8edc1..cb0dabcb9de 100644 --- a/src/ripple/rpc/handlers/Ping.cpp +++ b/src/xrpld/rpc/handlers/Ping.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { @@ -39,13 +39,13 @@ doPing(RPC::JsonContext& context) break; case Role::IDENTIFIED: ret[jss::role] = "identified"; - ret[jss::username] = context.headers.user.to_string(); + ret[jss::username] = std::string{context.headers.user}; if (context.headers.forwardedFor.size()) - ret[jss::ip] = context.headers.forwardedFor.to_string(); + ret[jss::ip] = std::string{context.headers.forwardedFor}; break; case Role::PROXY: ret[jss::role] = "proxied"; - ret[jss::ip] = context.headers.forwardedFor.to_string(); + ret[jss::ip] = std::string{context.headers.forwardedFor}; default:; } diff --git a/src/ripple/rpc/handlers/Print.cpp b/src/xrpld/rpc/handlers/Print.cpp similarity index 88% rename from src/ripple/rpc/handlers/Print.cpp rename to src/xrpld/rpc/handlers/Print.cpp index 191ecf1ec54..df501240882 100644 --- a/src/ripple/rpc/handlers/Print.cpp +++ b/src/xrpld/rpc/handlers/Print.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Random.cpp b/src/xrpld/rpc/handlers/Random.cpp similarity index 86% rename from src/ripple/rpc/handlers/Random.cpp rename to src/xrpld/rpc/handlers/Random.cpp index 5362969660a..cea83a616c2 100644 --- a/src/ripple/rpc/handlers/Random.cpp +++ b/src/xrpld/rpc/handlers/Random.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Reservations.cpp b/src/xrpld/rpc/handlers/Reservations.cpp similarity index 89% rename from src/ripple/rpc/handlers/Reservations.cpp rename to src/xrpld/rpc/handlers/Reservations.cpp index ffbfd0f98bf..1ff2d506afa 100644 --- a/src/ripple/rpc/handlers/Reservations.cpp +++ b/src/xrpld/rpc/handlers/Reservations.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -34,9 +34,6 @@ namespace ripple { Json::Value doPeerReservationsAdd(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - auto const& params = context.params; if (!params.isMember(jss::public_key)) @@ -90,9 +87,6 @@ doPeerReservationsAdd(RPC::JsonContext& context) Json::Value doPeerReservationsDel(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - auto const& params = context.params; // We repeat much of the parameter parsing from `doPeerReservationsAdd`. @@ -120,9 +114,6 @@ doPeerReservationsDel(RPC::JsonContext& context) Json::Value doPeerReservationsList(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - auto const& reservations = context.app.peerReservations().list(); // Enumerate the reservations in context.app.peerReservations() // as a Json::Value. diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/xrpld/rpc/handlers/RipplePathFind.cpp similarity index 96% rename from src/ripple/rpc/handlers/RipplePathFind.cpp rename to src/xrpld/rpc/handlers/RipplePathFind.cpp index 19bce998494..5e18a1210e9 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/xrpld/rpc/handlers/RipplePathFind.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/ServerInfo.cpp b/src/xrpld/rpc/handlers/ServerInfo.cpp similarity index 90% rename from src/ripple/rpc/handlers/ServerInfo.cpp rename to src/xrpld/rpc/handlers/ServerInfo.cpp index 83c73e20558..ea631491c65 100644 --- a/src/ripple/rpc/handlers/ServerInfo.cpp +++ b/src/xrpld/rpc/handlers/ServerInfo.cpp @@ -17,20 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -81,7 +80,8 @@ ServerDefinitions::translate(std::string const& inp) if (contains("UINT")) { - if (contains("256") || contains("160") || contains("128")) + if (contains("256") || contains("192") || contains("160") || + contains("128")) return replace("UINT", "Hash"); else return replace("UINT", "UInt"); @@ -330,14 +330,6 @@ doServerInfo(RPC::JsonContext& context) context.params.isMember(jss::counters) && context.params[jss::counters].asBool()); - if (context.app.config().reporting()) - { - Json::Value const proxied = forwardToP2p(context); - auto const lf = proxied[jss::result][jss::info][jss::load_factor]; - auto const vq = proxied[jss::result][jss::info][jss::validation_quorum]; - ret[jss::info][jss::validation_quorum] = vq.isNull() ? 1 : vq; - ret[jss::info][jss::load_factor] = lf.isNull() ? 1 : lf; - } return ret; } diff --git a/src/ripple/rpc/handlers/ServerState.cpp b/src/xrpld/rpc/handlers/ServerState.cpp similarity index 84% rename from src/ripple/rpc/handlers/ServerState.cpp rename to src/xrpld/rpc/handlers/ServerState.cpp index 756a3953968..dec18eca119 100644 --- a/src/ripple/rpc/handlers/ServerState.cpp +++ b/src/xrpld/rpc/handlers/ServerState.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/SignFor.cpp b/src/xrpld/rpc/handlers/SignFor.cpp similarity index 89% rename from src/ripple/rpc/handlers/SignFor.cpp rename to src/xrpld/rpc/handlers/SignFor.cpp index 722cf7da157..da8ff869fba 100644 --- a/src/ripple/rpc/handlers/SignFor.cpp +++ b/src/xrpld/rpc/handlers/SignFor.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/SignHandler.cpp b/src/xrpld/rpc/handlers/SignHandler.cpp similarity index 91% rename from src/ripple/rpc/handlers/SignHandler.cpp rename to src/xrpld/rpc/handlers/SignHandler.cpp index 4d89cdcb2e0..ed634161f74 100644 --- a/src/ripple/rpc/handlers/SignHandler.cpp +++ b/src/xrpld/rpc/handlers/SignHandler.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Stop.cpp b/src/xrpld/rpc/handlers/Stop.cpp similarity index 91% rename from src/ripple/rpc/handlers/Stop.cpp rename to src/xrpld/rpc/handlers/Stop.cpp index 9467556969d..ea37a1aaa1b 100644 --- a/src/ripple/rpc/handlers/Stop.cpp +++ b/src/xrpld/rpc/handlers/Stop.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include diff --git a/src/ripple/rpc/handlers/Submit.cpp b/src/xrpld/rpc/handlers/Submit.cpp similarity index 93% rename from src/ripple/rpc/handlers/Submit.cpp rename to src/xrpld/rpc/handlers/Submit.cpp index a151778fbb8..73fdc3822c2 100644 --- a/src/ripple/rpc/handlers/Submit.cpp +++ b/src/xrpld/rpc/handlers/Submit.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/SubmitMultiSigned.cpp b/src/xrpld/rpc/handlers/SubmitMultiSigned.cpp similarity index 87% rename from src/ripple/rpc/handlers/SubmitMultiSigned.cpp rename to src/xrpld/rpc/handlers/SubmitMultiSigned.cpp index 5b9d5b34ac6..9949d3e3212 100644 --- a/src/ripple/rpc/handlers/SubmitMultiSigned.cpp +++ b/src/xrpld/rpc/handlers/SubmitMultiSigned.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Subscribe.cpp b/src/xrpld/rpc/handlers/Subscribe.cpp similarity index 94% rename from src/ripple/rpc/handlers/Subscribe.cpp rename to src/xrpld/rpc/handlers/Subscribe.cpp index 24465a2c3a5..66fe89dea04 100644 --- a/src/ripple/rpc/handlers/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/Subscribe.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -128,8 +128,6 @@ doSubscribe(RPC::JsonContext& context) std::string streamName = it.asString(); if (streamName == "server") { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); context.netOps.subServer( ispSub, jvResult, context.role == Role::ADMIN); } @@ -161,16 +159,12 @@ doSubscribe(RPC::JsonContext& context) } else if (streamName == "peer_status") { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); context.netOps.subPeerStatus(ispSub); } else if (streamName == "consensus") { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); context.netOps.subConsensus(ispSub); } else diff --git a/src/ripple/rpc/handlers/TransactionEntry.cpp b/src/xrpld/rpc/handlers/TransactionEntry.cpp similarity index 93% rename from src/ripple/rpc/handlers/TransactionEntry.cpp rename to src/xrpld/rpc/handlers/TransactionEntry.cpp index 6d157891d1c..e81e0434595 100644 --- a/src/ripple/rpc/handlers/TransactionEntry.cpp +++ b/src/xrpld/rpc/handlers/TransactionEntry.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/Tx.cpp b/src/xrpld/rpc/handlers/Tx.cpp similarity index 66% rename from src/ripple/rpc/handlers/Tx.cpp rename to src/xrpld/rpc/handlers/Tx.cpp index d4c9c95a341..98af3a809bf 100644 --- a/src/ripple/rpc/handlers/Tx.cpp +++ b/src/xrpld/rpc/handlers/Tx.cpp @@ -17,22 +17,23 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -70,129 +71,9 @@ struct TxArgs std::optional> ledgerRange; }; -std::pair -doTxPostgres(RPC::Context& context, TxArgs const& args) -{ - if (!context.app.config().reporting()) - { - assert(false); - Throw( - "Called doTxPostgres yet not in reporting mode"); - } - - TxResult res; - res.searchedAll = TxSearched::unknown; - - if (!args.hash) - return { - res, - {rpcNOT_IMPL, - "Use of CTIDs on reporting mode is not currently supported."}}; - - JLOG(context.j.debug()) << "Fetching from postgres"; - Transaction::Locator locator = - Transaction::locate(*(args.hash), context.app); - - std::pair, std::shared_ptr> - pair; - // database returned the nodestore hash. Fetch the txn directly from the - // nodestore. Don't traverse the transaction SHAMap - if (locator.isFound()) - { - auto start = std::chrono::system_clock::now(); - // The second argument of fetch is ignored when not using shards - if (auto obj = context.app.getNodeFamily().db().fetchNodeObject( - locator.getNodestoreHash(), locator.getLedgerSequence())) - { - auto node = SHAMapTreeNode::makeFromPrefix( - makeSlice(obj->getData()), - SHAMapHash{locator.getNodestoreHash()}); - if (!node) - { - assert(false); - return {res, {rpcINTERNAL, "Error making SHAMap node"}}; - } - auto item = (static_cast(node.get()))->peekItem(); - if (!item) - { - assert(false); - return {res, {rpcINTERNAL, "Error reading SHAMap node"}}; - } - - auto [sttx, meta] = deserializeTxPlusMeta(*item); - JLOG(context.j.debug()) << "Successfully fetched from db"; - - if (!sttx || !meta) - { - assert(false); - return {res, {rpcINTERNAL, "Error deserializing SHAMap node"}}; - } - std::string reason; - res.txn = std::make_shared(sttx, reason, context.app); - res.txn->setLedger(locator.getLedgerSequence()); - res.txn->setStatus(COMMITTED); - if (args.binary) - { - SerialIter it(item->slice()); - it.skip(it.getVLDataLength()); // skip transaction - Blob blob = it.getVL(); - res.meta = std::move(blob); - } - else - { - res.meta = std::make_shared( - *(args.hash), res.txn->getLedger(), *meta); - } - res.validated = true; - - auto const ledgerInfo = - context.app.getRelationalDatabase().getLedgerInfoByIndex( - locator.getLedgerSequence()); - res.closeTime = ledgerInfo->closeTime; - res.ledgerHash = ledgerInfo->hash; - - return {res, rpcSUCCESS}; - } - else - { - JLOG(context.j.error()) << "Failed to fetch from db"; - assert(false); - return {res, {rpcINTERNAL, "Containing SHAMap node not found"}}; - } - auto end = std::chrono::system_clock::now(); - JLOG(context.j.debug()) << "tx flat fetch time : " - << ((end - start).count() / 1000000000.0); - } - // database did not find the transaction, and returned the ledger range - // that was searched - else - { - if (args.ledgerRange) - { - auto range = locator.getLedgerRangeSearched(); - auto min = args.ledgerRange->first; - auto max = args.ledgerRange->second; - if (min >= range.lower() && max <= range.upper()) - { - res.searchedAll = TxSearched::all; - } - else - { - res.searchedAll = TxSearched::some; - } - } - return {res, rpcTXN_NOT_FOUND}; - } - // database didn't return anything. This shouldn't happen - assert(false); - return {res, {rpcINTERNAL, "unexpected Postgres response"}}; -} - std::pair doTxHelp(RPC::Context& context, TxArgs args) { - if (context.app.config().reporting()) - return doTxPostgres(context, args); TxResult result; ClosedInterval range; @@ -345,7 +226,7 @@ populateJsonResponse( } // Note, result.ledgerHash is only set in a closed or validated - // ledger - as seen in `doTxHelp` and `doTxPostgres` + // ledger - as seen in `doTxHelp` if (result.ledgerHash) response[jss::ledger_hash] = to_string(*result.ledgerHash); @@ -385,6 +266,7 @@ populateJsonResponse( insertDeliveredAmount( response[jss::meta], context, result.txn, *meta); insertNFTSyntheticInJson(response, sttx, *meta); + RPC::insertMPTokenIssuanceID(response[jss::meta], sttx, *meta); } } response[jss::validated] = result.validated; diff --git a/src/ripple/rpc/handlers/TxHistory.cpp b/src/xrpld/rpc/handlers/TxHistory.cpp similarity index 76% rename from src/ripple/rpc/handlers/TxHistory.cpp rename to src/xrpld/rpc/handlers/TxHistory.cpp index 8759d4af1b9..1122eab51c3 100644 --- a/src/ripple/rpc/handlers/TxHistory.cpp +++ b/src/xrpld/rpc/handlers/TxHistory.cpp @@ -17,21 +17,20 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -60,8 +59,6 @@ doTxHistory(RPC::JsonContext& context) Json::Value obj; Json::Value& txs = obj[jss::txs]; obj[jss::index] = startIndex; - if (context.app.config().reporting()) - obj["used_postgres"] = true; for (auto const& t : trans) { diff --git a/src/ripple/rpc/handlers/TxReduceRelay.cpp b/src/xrpld/rpc/handlers/TxReduceRelay.cpp similarity index 89% rename from src/ripple/rpc/handlers/TxReduceRelay.cpp rename to src/xrpld/rpc/handlers/TxReduceRelay.cpp index bb883b8dea1..d8c21b3fa46 100644 --- a/src/ripple/rpc/handlers/TxReduceRelay.cpp +++ b/src/xrpld/rpc/handlers/TxReduceRelay.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/UnlList.cpp b/src/xrpld/rpc/handlers/UnlList.cpp similarity index 83% rename from src/ripple/rpc/handlers/UnlList.cpp rename to src/xrpld/rpc/handlers/UnlList.cpp index 0dc4dfc3776..b3394534372 100644 --- a/src/ripple/rpc/handlers/UnlList.cpp +++ b/src/xrpld/rpc/handlers/UnlList.cpp @@ -17,20 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doUnlList(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); Json::Value obj(Json::objectValue); context.app.validators().for_each_listed( diff --git a/src/ripple/rpc/handlers/Unsubscribe.cpp b/src/xrpld/rpc/handlers/Unsubscribe.cpp similarity index 96% rename from src/ripple/rpc/handlers/Unsubscribe.cpp rename to src/xrpld/rpc/handlers/Unsubscribe.cpp index 512790f2a10..bab0d99744c 100644 --- a/src/ripple/rpc/handlers/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/Unsubscribe.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/ValidationCreate.cpp b/src/xrpld/rpc/handlers/ValidationCreate.cpp similarity index 91% rename from src/ripple/rpc/handlers/ValidationCreate.cpp rename to src/xrpld/rpc/handlers/ValidationCreate.cpp index f07cbe9cdfa..ce53f84d951 100644 --- a/src/ripple/rpc/handlers/ValidationCreate.cpp +++ b/src/xrpld/rpc/handlers/ValidationCreate.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/ValidatorInfo.cpp b/src/xrpld/rpc/handlers/ValidatorInfo.cpp similarity index 89% rename from src/ripple/rpc/handlers/ValidatorInfo.cpp rename to src/xrpld/rpc/handlers/ValidatorInfo.cpp index 910c5e9740f..d1a63371980 100644 --- a/src/ripple/rpc/handlers/ValidatorInfo.cpp +++ b/src/xrpld/rpc/handlers/ValidatorInfo.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { Json::Value diff --git a/src/ripple/rpc/handlers/ValidatorListSites.cpp b/src/xrpld/rpc/handlers/ValidatorListSites.cpp similarity index 80% rename from src/ripple/rpc/handlers/ValidatorListSites.cpp rename to src/xrpld/rpc/handlers/ValidatorListSites.cpp index 4800a5e8661..39bc4e36471 100644 --- a/src/ripple/rpc/handlers/ValidatorListSites.cpp +++ b/src/xrpld/rpc/handlers/ValidatorListSites.cpp @@ -17,20 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doValidatorListSites(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - return context.app.validatorSites().getJson(); } diff --git a/src/ripple/rpc/handlers/Validators.cpp b/src/xrpld/rpc/handlers/Validators.cpp similarity index 80% rename from src/ripple/rpc/handlers/Validators.cpp rename to src/xrpld/rpc/handlers/Validators.cpp index 39306612bdf..599e76f847a 100644 --- a/src/ripple/rpc/handlers/Validators.cpp +++ b/src/xrpld/rpc/handlers/Validators.cpp @@ -17,20 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include namespace ripple { Json::Value doValidators(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); - return context.app.validators().getJson(); } diff --git a/src/ripple/rpc/handlers/Version.h b/src/xrpld/rpc/handlers/Version.h similarity index 97% rename from src/ripple/rpc/handlers/Version.h rename to src/xrpld/rpc/handlers/Version.h index 8f33b62f1cf..4efeed24848 100644 --- a/src/ripple/rpc/handlers/Version.h +++ b/src/xrpld/rpc/handlers/Version.h @@ -20,7 +20,7 @@ #ifndef RIPPLED_RIPPLE_RPC_HANDLERS_VERSION_H #define RIPPLED_RIPPLE_RPC_HANDLERS_VERSION_H -#include +#include namespace ripple { namespace RPC { diff --git a/src/ripple/rpc/handlers/WalletPropose.cpp b/src/xrpld/rpc/handlers/WalletPropose.cpp similarity index 92% rename from src/ripple/rpc/handlers/WalletPropose.cpp rename to src/xrpld/rpc/handlers/WalletPropose.cpp index 25aaa3bbb8e..f3300b9ed04 100644 --- a/src/ripple/rpc/handlers/WalletPropose.cpp +++ b/src/xrpld/rpc/handlers/WalletPropose.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/rpc/handlers/WalletPropose.h b/src/xrpld/rpc/handlers/WalletPropose.h similarity index 97% rename from src/ripple/rpc/handlers/WalletPropose.h rename to src/xrpld/rpc/handlers/WalletPropose.h index 9da0c9e412f..d09ae9a4cfb 100644 --- a/src/ripple/rpc/handlers/WalletPropose.h +++ b/src/xrpld/rpc/handlers/WalletPropose.h @@ -20,7 +20,7 @@ #ifndef RIPPLED_RIPPLE_RPC_HANDLERS_WALLETPROPOSE_H #define RIPPLED_RIPPLE_RPC_HANDLERS_WALLETPROPOSE_H -#include +#include namespace ripple { diff --git a/src/ripple/rpc/json_body.h b/src/xrpld/rpc/json_body.h similarity index 97% rename from src/ripple/rpc/json_body.h rename to src/xrpld/rpc/json_body.h index fb5289981c0..5c97da43839 100644 --- a/src/ripple/rpc/json_body.h +++ b/src/xrpld/rpc/json_body.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_RPC_JSON_BODY_H #define RIPPLE_RPC_JSON_BODY_H -#include -#include +#include +#include #include #include diff --git a/src/ripple/shamap/Family.h b/src/xrpld/shamap/Family.h similarity index 74% rename from src/ripple/shamap/Family.h rename to src/xrpld/shamap/Family.h index fea5545d31c..bbb22c273d0 100644 --- a/src/ripple/shamap/Family.h +++ b/src/xrpld/shamap/Family.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SHAMAP_FAMILY_H_INCLUDED #define RIPPLE_SHAMAP_FAMILY_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { @@ -53,31 +53,18 @@ class Family virtual beast::Journal const& journal() = 0; - /** Return a pointer to the Family Full Below Cache - - @param ledgerSeq ledger sequence determines a corresponding shard cache - @note ledgerSeq is used by ShardFamily and ignored by NodeFamily - */ + /** Return a pointer to the Family Full Below Cache */ virtual std::shared_ptr - getFullBelowCache(std::uint32_t ledgerSeq) = 0; - - /** Return a pointer to the Family Tree Node Cache + getFullBelowCache() = 0; - @param ledgerSeq ledger sequence determines a corresponding shard cache - @note ledgerSeq is used by ShardFamily and ignored by NodeFamily - */ + /** Return a pointer to the Family Tree Node Cache */ virtual std::shared_ptr - getTreeNodeCache(std::uint32_t ledgerSeq) = 0; + getTreeNodeCache() = 0; virtual void sweep() = 0; - virtual bool - isShardBacked() const = 0; - /** Acquire ledger that has a missing node by ledger sequence - * - * Throw if in reporting mode. * * @param refNum Sequence of ledger to acquire. * @param nodeHash Hash of missing node to report in throw. diff --git a/src/ripple/shamap/FullBelowCache.h b/src/xrpld/shamap/FullBelowCache.h similarity index 95% rename from src/ripple/shamap/FullBelowCache.h rename to src/xrpld/shamap/FullBelowCache.h index 6d809d3b951..eed7e6294a0 100644 --- a/src/ripple/shamap/FullBelowCache.h +++ b/src/xrpld/shamap/FullBelowCache.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SHAMAP_FULLBELOWCACHE_H_INCLUDED #define RIPPLE_SHAMAP_FULLBELOWCACHE_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/NodeFamily.h b/src/xrpld/shamap/NodeFamily.h similarity index 88% rename from src/ripple/shamap/NodeFamily.h rename to src/xrpld/shamap/NodeFamily.h index f20abccce9d..4062ea23897 100644 --- a/src/ripple/shamap/NodeFamily.h +++ b/src/xrpld/shamap/NodeFamily.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SHAMAP_NODEFAMILY_H_INCLUDED #define RIPPLE_SHAMAP_NODEFAMILY_H_INCLUDED -#include -#include +#include +#include namespace ripple { @@ -60,18 +60,14 @@ class NodeFamily : public Family return j_; } - bool - isShardBacked() const override - { - return false; - } - - std::shared_ptr getFullBelowCache(std::uint32_t) override + std::shared_ptr + getFullBelowCache() override { return fbCache_; } - std::shared_ptr getTreeNodeCache(std::uint32_t) override + std::shared_ptr + getTreeNodeCache() override { return tnCache_; } diff --git a/src/ripple/shamap/README.md b/src/xrpld/shamap/README.md similarity index 100% rename from src/ripple/shamap/README.md rename to src/xrpld/shamap/README.md diff --git a/src/ripple/shamap/SHAMap.h b/src/xrpld/shamap/SHAMap.h similarity index 97% rename from src/ripple/shamap/SHAMap.h rename to src/xrpld/shamap/SHAMap.h index 2d1aa192fc6..a47f1c1c2bc 100644 --- a/src/ripple/shamap/SHAMap.h +++ b/src/xrpld/shamap/SHAMap.h @@ -20,19 +20,19 @@ #ifndef RIPPLE_SHAMAP_SHAMAP_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAP_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/ripple/shamap/SHAMapAccountStateLeafNode.h b/src/xrpld/shamap/SHAMapAccountStateLeafNode.h similarity index 91% rename from src/ripple/shamap/SHAMapAccountStateLeafNode.h rename to src/xrpld/shamap/SHAMapAccountStateLeafNode.h index 45f0c508078..842c1092dd9 100644 --- a/src/ripple/shamap/SHAMapAccountStateLeafNode.h +++ b/src/xrpld/shamap/SHAMapAccountStateLeafNode.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_SHAMAP_SHAMAPACCOUNTSTATELEAFNODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPACCOUNTSTATELEAFNODE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/shamap/SHAMapAddNode.h b/src/xrpld/shamap/SHAMapAddNode.h similarity index 100% rename from src/ripple/shamap/SHAMapAddNode.h rename to src/xrpld/shamap/SHAMapAddNode.h diff --git a/src/ripple/shamap/SHAMapInnerNode.h b/src/xrpld/shamap/SHAMapInnerNode.h similarity index 95% rename from src/ripple/shamap/SHAMapInnerNode.h rename to src/xrpld/shamap/SHAMapInnerNode.h index 44ac05799f2..d2791915c3c 100644 --- a/src/ripple/shamap/SHAMapInnerNode.h +++ b/src/xrpld/shamap/SHAMapInnerNode.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_SHAMAP_SHAMAPINNERNODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPINNERNODE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/SHAMapItem.h b/src/xrpld/shamap/SHAMapItem.h similarity index 96% rename from src/ripple/shamap/SHAMapItem.h rename to src/xrpld/shamap/SHAMapItem.h index 160cc3cb49d..1a1822456e9 100644 --- a/src/ripple/shamap/SHAMapItem.h +++ b/src/xrpld/shamap/SHAMapItem.h @@ -20,11 +20,11 @@ #ifndef RIPPLE_SHAMAP_SHAMAPITEM_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPITEM_H_INCLUDED -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/SHAMapLeafNode.h b/src/xrpld/shamap/SHAMapLeafNode.h similarity index 95% rename from src/ripple/shamap/SHAMapLeafNode.h rename to src/xrpld/shamap/SHAMapLeafNode.h index f24d7053cbe..d5d84d3f003 100644 --- a/src/ripple/shamap/SHAMapLeafNode.h +++ b/src/xrpld/shamap/SHAMapLeafNode.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_SHAMAP_SHAMAPLEAFNODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPLEAFNODE_H_INCLUDED -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/SHAMapMissingNode.h b/src/xrpld/shamap/SHAMapMissingNode.h similarity index 96% rename from src/ripple/shamap/SHAMapMissingNode.h rename to src/xrpld/shamap/SHAMapMissingNode.h index 811fe5f9615..50aa193b2b6 100644 --- a/src/ripple/shamap/SHAMapMissingNode.h +++ b/src/xrpld/shamap/SHAMapMissingNode.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SHAMAP_SHAMAPMISSINGNODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPMISSINGNODE_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/shamap/SHAMapNodeID.h b/src/xrpld/shamap/SHAMapNodeID.h similarity index 98% rename from src/ripple/shamap/SHAMapNodeID.h rename to src/xrpld/shamap/SHAMapNodeID.h index 00ca33ac43f..176553fd7d3 100644 --- a/src/ripple/shamap/SHAMapNodeID.h +++ b/src/xrpld/shamap/SHAMapNodeID.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SHAMAP_SHAMAPNODEID_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPNODEID_H_INCLUDED -#include -#include +#include +#include #include #include #include diff --git a/src/ripple/shamap/SHAMapSyncFilter.h b/src/xrpld/shamap/SHAMapSyncFilter.h similarity index 95% rename from src/ripple/shamap/SHAMapSyncFilter.h rename to src/xrpld/shamap/SHAMapSyncFilter.h index 37eda4fdbc9..b14effac88b 100644 --- a/src/ripple/shamap/SHAMapSyncFilter.h +++ b/src/xrpld/shamap/SHAMapSyncFilter.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_SHAMAP_SHAMAPSYNCFILTER_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPSYNCFILTER_H_INCLUDED -#include -#include +#include +#include #include /** Callback for filtering SHAMap during sync. */ diff --git a/src/ripple/shamap/SHAMapTreeNode.h b/src/xrpld/shamap/SHAMapTreeNode.h similarity index 95% rename from src/ripple/shamap/SHAMapTreeNode.h rename to src/xrpld/shamap/SHAMapTreeNode.h index 8e351cce9de..d6b0ebce9e1 100644 --- a/src/ripple/shamap/SHAMapTreeNode.h +++ b/src/xrpld/shamap/SHAMapTreeNode.h @@ -20,13 +20,13 @@ #ifndef RIPPLE_SHAMAP_SHAMAPTREENODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPTREENODE_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/SHAMapTxLeafNode.h b/src/xrpld/shamap/SHAMapTxLeafNode.h similarity index 91% rename from src/ripple/shamap/SHAMapTxLeafNode.h rename to src/xrpld/shamap/SHAMapTxLeafNode.h index e794a1a8f32..f4d3f21e908 100644 --- a/src/ripple/shamap/SHAMapTxLeafNode.h +++ b/src/xrpld/shamap/SHAMapTxLeafNode.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_SHAMAP_SHAMAPTXLEAFNODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPTXLEAFNODE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/shamap/SHAMapTxPlusMetaLeafNode.h b/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h similarity index 91% rename from src/ripple/shamap/SHAMapTxPlusMetaLeafNode.h rename to src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h index ff32c64e09b..6ea55f4ac46 100644 --- a/src/ripple/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h @@ -20,12 +20,12 @@ #ifndef RIPPLE_SHAMAP_SHAMAPLEAFTXPLUSMETANODE_H_INCLUDED #define RIPPLE_SHAMAP_SHAMAPLEAFTXPLUSMETANODE_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/shamap/TreeNodeCache.h b/src/xrpld/shamap/TreeNodeCache.h similarity index 96% rename from src/ripple/shamap/TreeNodeCache.h rename to src/xrpld/shamap/TreeNodeCache.h index f35c252f460..f59fdc92801 100644 --- a/src/ripple/shamap/TreeNodeCache.h +++ b/src/xrpld/shamap/TreeNodeCache.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_SHAMAP_TREENODECACHE_H_INCLUDED #define RIPPLE_SHAMAP_TREENODECACHE_H_INCLUDED -#include +#include namespace ripple { diff --git a/src/ripple/shamap/impl/NodeFamily.cpp b/src/xrpld/shamap/detail/NodeFamily.cpp similarity index 88% rename from src/ripple/shamap/impl/NodeFamily.cpp rename to src/xrpld/shamap/detail/NodeFamily.cpp index 1752db06a8e..bf95003aef8 100644 --- a/src/ripple/shamap/impl/NodeFamily.cpp +++ b/src/xrpld/shamap/detail/NodeFamily.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ripple { @@ -69,14 +69,6 @@ void NodeFamily::missingNodeAcquireBySeq(std::uint32_t seq, uint256 const& nodeHash) { JLOG(j_.error()) << "Missing node in " << seq; - if (app_.config().reporting()) - { - std::stringstream ss; - ss << "Node not read, likely a Cassandra error in ledger seq " << seq - << " object hash " << nodeHash; - Throw(ss.str()); - } - std::unique_lock lock(maxSeqMutex_); if (maxSeq_ == 0) { diff --git a/src/ripple/shamap/impl/SHAMap.cpp b/src/xrpld/shamap/detail/SHAMap.cpp similarity index 98% rename from src/ripple/shamap/impl/SHAMap.cpp rename to src/xrpld/shamap/detail/SHAMap.cpp index d6348c86c48..d06ba2a153a 100644 --- a/src/ripple/shamap/impl/SHAMap.cpp +++ b/src/xrpld/shamap/detail/SHAMap.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace ripple { @@ -1166,7 +1166,7 @@ SHAMap::dump(bool hash) const std::shared_ptr SHAMap::cacheLookup(SHAMapHash const& hash) const { - auto ret = f_.getTreeNodeCache(ledgerSeq_)->fetch(hash.as_uint256()); + auto ret = f_.getTreeNodeCache()->fetch(hash.as_uint256()); assert(!ret || !ret->cowid()); return ret; } @@ -1180,8 +1180,7 @@ SHAMap::canonicalize( assert(node->cowid() == 0); assert(node->getHash() == hash); - f_.getTreeNodeCache(ledgerSeq_) - ->canonicalize_replace_client(hash.as_uint256(), node); + f_.getTreeNodeCache()->canonicalize_replace_client(hash.as_uint256(), node); } void diff --git a/src/ripple/shamap/impl/SHAMapDelta.cpp b/src/xrpld/shamap/detail/SHAMapDelta.cpp similarity index 99% rename from src/ripple/shamap/impl/SHAMapDelta.cpp rename to src/xrpld/shamap/detail/SHAMapDelta.cpp index ab9e329eb30..0dcb861a63f 100644 --- a/src/ripple/shamap/impl/SHAMapDelta.cpp +++ b/src/xrpld/shamap/detail/SHAMapDelta.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include -#include +#include +#include #include #include diff --git a/src/ripple/shamap/impl/SHAMapInnerNode.cpp b/src/xrpld/shamap/detail/SHAMapInnerNode.cpp similarity index 96% rename from src/ripple/shamap/impl/SHAMapInnerNode.cpp rename to src/xrpld/shamap/detail/SHAMapInnerNode.cpp index c9884955914..99155a6401f 100644 --- a/src/ripple/shamap/impl/SHAMapInnerNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapInnerNode.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/impl/SHAMapLeafNode.cpp b/src/xrpld/shamap/detail/SHAMapLeafNode.cpp similarity index 95% rename from src/ripple/shamap/impl/SHAMapLeafNode.cpp rename to src/xrpld/shamap/detail/SHAMapLeafNode.cpp index 8f634cfad88..972919a9bda 100644 --- a/src/ripple/shamap/impl/SHAMapLeafNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapLeafNode.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { diff --git a/src/ripple/shamap/impl/SHAMapNodeID.cpp b/src/xrpld/shamap/detail/SHAMapNodeID.cpp similarity index 95% rename from src/ripple/shamap/impl/SHAMapNodeID.cpp rename to src/xrpld/shamap/detail/SHAMapNodeID.cpp index bcbdb7f3311..5cbd095e7a9 100644 --- a/src/ripple/shamap/impl/SHAMapNodeID.cpp +++ b/src/xrpld/shamap/detail/SHAMapNodeID.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace ripple { diff --git a/src/ripple/shamap/impl/SHAMapSync.cpp b/src/xrpld/shamap/detail/SHAMapSync.cpp similarity index 97% rename from src/ripple/shamap/impl/SHAMapSync.cpp rename to src/xrpld/shamap/detail/SHAMapSync.cpp index 3f24047eb7a..7235e526560 100644 --- a/src/ripple/shamap/impl/SHAMapSync.cpp +++ b/src/xrpld/shamap/detail/SHAMapSync.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include namespace ripple { @@ -192,8 +192,7 @@ SHAMap::gmn_ProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se) } else if ( !backed_ || - !f_.getFullBelowCache(ledgerSeq_) - ->touch_if_exists(childHash.as_uint256())) + !f_.getFullBelowCache()->touch_if_exists(childHash.as_uint256())) { bool pending = false; auto d = descendAsync( @@ -251,8 +250,7 @@ SHAMap::gmn_ProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se) node->setFullBelowGen(mn.generation_); if (backed_) { - f_.getFullBelowCache(ledgerSeq_) - ->insert(node->getHash().as_uint256()); + f_.getFullBelowCache()->insert(node->getHash().as_uint256()); } } @@ -323,7 +321,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter* filter) max, filter, 512, // number of async reads per pass - f_.getFullBelowCache(ledgerSeq_)->getGeneration()); + f_.getFullBelowCache()->getGeneration()); if (!root_->isInner() || std::static_pointer_cast(root_)->isFullBelow( @@ -580,7 +578,7 @@ SHAMap::addKnownNode( return SHAMapAddNode::duplicate(); } - auto const generation = f_.getFullBelowCache(ledgerSeq_)->getGeneration(); + auto const generation = f_.getFullBelowCache()->getGeneration(); SHAMapNodeID iNodeID; auto iNode = root_.get(); @@ -598,8 +596,7 @@ SHAMap::addKnownNode( } auto childHash = inner->getChildHash(branch); - if (f_.getFullBelowCache(ledgerSeq_) - ->touch_if_exists(childHash.as_uint256())) + if (f_.getFullBelowCache()->touch_if_exists(childHash.as_uint256())) { return SHAMapAddNode::duplicate(); } diff --git a/src/ripple/shamap/impl/SHAMapTreeNode.cpp b/src/xrpld/shamap/detail/SHAMapTreeNode.cpp similarity index 90% rename from src/ripple/shamap/impl/SHAMapTreeNode.cpp rename to src/xrpld/shamap/detail/SHAMapTreeNode.cpp index e7645a16a4e..fe5b5377eee 100644 --- a/src/ripple/shamap/impl/SHAMapTreeNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapTreeNode.cpp @@ -17,19 +17,19 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/ripple/shamap/impl/TaggedPointer.h b/src/xrpld/shamap/detail/TaggedPointer.h similarity index 98% rename from src/ripple/shamap/impl/TaggedPointer.h rename to src/xrpld/shamap/detail/TaggedPointer.h index afc0ef582ae..48534076548 100644 --- a/src/ripple/shamap/impl/TaggedPointer.h +++ b/src/xrpld/shamap/detail/TaggedPointer.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_SHAMAP_TAGGEDPOINTER_H_INCLUDED #define RIPPLE_SHAMAP_TAGGEDPOINTER_H_INCLUDED -#include +#include #include #include @@ -37,7 +37,7 @@ namespace ripple { low bits. When dereferencing the pointer, these low "tag" bits are set to zero. When accessing the tag bits, the high "pointer" bits are set to zero. - The "pointer" part points to to the equivalent to an array of + The "pointer" part points to the equivalent to an array of `SHAMapHash` followed immediately by an array of `shared_ptr`. The sizes of these arrays are determined by the tag. The tag is an index into an array (`boundaries`, diff --git a/src/ripple/shamap/impl/TaggedPointer.ipp b/src/xrpld/shamap/detail/TaggedPointer.ipp similarity index 96% rename from src/ripple/shamap/impl/TaggedPointer.ipp rename to src/xrpld/shamap/detail/TaggedPointer.ipp index 7cdff6b4944..487b88e3461 100644 --- a/src/ripple/shamap/impl/TaggedPointer.ipp +++ b/src/xrpld/shamap/detail/TaggedPointer.ipp @@ -17,9 +17,9 @@ */ //============================================================================== -#include -#include -#include +#include +#include +#include #include #include @@ -55,8 +55,8 @@ constexpr size_t elementSizeBytes = constexpr size_t blockSizeBytes = kilobytes(512); template -constexpr std::array initArrayChunkSizeBytes( - std::index_sequence) +constexpr std::array +initArrayChunkSizeBytes(std::index_sequence) { return std::array{ boundaries[I] * elementSizeBytes..., @@ -66,8 +66,8 @@ constexpr auto arrayChunkSizeBytes = initArrayChunkSizeBytes(std::make_index_sequence{}); template -constexpr std::array initArrayChunksPerBlock( - std::index_sequence) +constexpr std::array +initArrayChunksPerBlock(std::index_sequence) { return std::array{ blockSizeBytes / arrayChunkSizeBytes[I]..., @@ -93,8 +93,8 @@ boundariesIndex(std::uint8_t numChildren) } template -std::array, boundaries.size()> initAllocateArrayFuns( - std::index_sequence) +std::array, boundaries.size()> +initAllocateArrayFuns(std::index_sequence) { return std::array, boundaries.size()>{ boost::singleton_pool< @@ -110,8 +110,8 @@ std::array, boundaries.size()> const allocateArrayFuns = initAllocateArrayFuns(std::make_index_sequence{}); template -std::array, boundaries.size()> initFreeArrayFuns( - std::index_sequence) +std::array, boundaries.size()> +initFreeArrayFuns(std::index_sequence) { return std::array, boundaries.size()>{ static_cast(boost::singleton_pool< @@ -127,8 +127,8 @@ std::array, boundaries.size()> const freeArrayFuns = initFreeArrayFuns(std::make_index_sequence{}); template -std::array, boundaries.size()> initIsFromArrayFuns( - std::index_sequence) +std::array, boundaries.size()> +initIsFromArrayFuns(std::index_sequence) { return std::array, boundaries.size()>{ boost::singleton_pool< @@ -258,7 +258,7 @@ TaggedPointer::getChildIndex(std::uint16_t isBranch, int i) const // of a child in the array is the number of non-empty children // before it. Since `isBranch_` is a bitset of the stored // children, we simply need to mask out (and set to zero) all - // the bits in `isBranch_` equal to to higher than `i` and count + // the bits in `isBranch_` equal to higher than `i` and count // the bits. // mask sets all the bits >=i to zero and all the bits +// #include #include #include #include