diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 2c4135e0cf1..d7a92b3b409 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -10,16 +10,12 @@ inputs: runs: using: composite steps: - - name: dependencies - uses: ./.github/actions/dependencies - with: - configuration: ${{ inputs.configuration }} - name: configure shell: bash run: | cd ${build_dir} cmake \ - ${{ inputs.generator && format('-G {0}', inputs.generator) || '' }} \ + ${{ inputs.generator && format('-G "{0}"', inputs.generator) || '' }} \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \ ${{ inputs.cmake-args }} \ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1ebb9f690bb..120a6ec1782 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,6 +37,10 @@ jobs: run : | conan profile get env.CXXFLAGS default || true conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default + - name: dependencies + uses: ./.github/actions/dependencies + with: + configuration: ${{ matrix.configuration }} - name: build uses: ./.github/actions/build with: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 4198369e78a..fb017d45bf7 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -88,7 +88,7 @@ jobs: # Print the list of dependencies that would need to be built locally. # A non-empty list means we have "failed" to cache binaries remotely. run: | - echo missing=$(conan info . --build missing --json 2>/dev/null | grep '^\[') | tee ${GITHUB_OUTPUT} + echo missing=$(conan info . --build missing --settings build_type=${{ matrix.configuration }} --json 2>/dev/null | grep '^\[') | tee ${GITHUB_OUTPUT} - name: build dependencies if: (steps.binaries.outputs.missing != '[]') uses: ./.github/actions/dependencies @@ -146,6 +146,10 @@ jobs: ls ~/.conan - name: checkout uses: actions/checkout@v3 + - name: dependencies + uses: ./.github/actions/dependencies + with: + configuration: ${{ matrix.configuration }} - name: build uses: ./.github/actions/build with: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0f918a50959..4988e323e2b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,19 +1,11 @@ name: windows -# We have disabled this workflow because it fails in our CI Windows -# environment, but we cannot replicate the failure in our personal Windows -# test environments, nor have we gone through the trouble of setting up an -# interactive CI Windows environment. -# We welcome contributions to diagnose or debug the problems on Windows. Until -# then, we leave this tombstone as a reminder that we have tried (but failed) -# to write a reliable test for Windows. -# on: [push, pull_request] -on: - workflow_dispatch: - push: - branches: - - 'action' - paths: - - '.github/workflow/windows.yml' + +on: [push, pull_request] + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: @@ -25,6 +17,11 @@ jobs: - Visual Studio 16 2019 configuration: - Release + # Github hosted runners tend to hang when running Debug unit tests. + # Instead of trying to work around it, disable the Debug job until + # something beefier (i.e. a heavy self-hosted runner) becomes + # available. + # - Debug runs-on: windows-2019 env: build_dir: .build @@ -37,11 +34,12 @@ jobs: python-version: 3.9 - name: learn Python cache directory id: pip-cache + shell: bash run: | pip install --upgrade pip - echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT + echo "dir=$(pip cache dir)" | tee ${GITHUB_OUTPUT} - name: restore Python cache directory - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-${{ hashFiles('.github/workflows/windows.yml') }} @@ -55,45 +53,48 @@ jobs: cmake --version dir env: - name: configure Conan + shell: bash + env: + CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod run: | conan profile new default --detect - conan profile update settings.compiler.cppstd=20 default - conan profile update settings.compiler.runtime=MT default - conan profile update settings.compiler.toolset=v141 default - - name: learn Conan cache directory - id: conan-cache - run: | - echo "dir=$(conan config get storage.path)" >> $GITHUB_OUTPUT - - name: restore Conan cache directory - uses: actions/cache@v2 - with: - path: ${{ steps.conan-cache.outputs.dir }} - key: ${{ hashFiles('~/.conan/profiles/default', 'conanfile.py', 'external/rocksdb/*', '.github/workflows/windows.yml') }} - - name: export custom recipes + conan profile update settings.compiler.runtime=MT${{ matrix.configuration == 'Debug' && 'd' || '' }} default + # 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: try to authenticate to ripple Conan remote + shell: bash + id: remote run: | - conan export external/snappy snappy/1.1.9@ - conan export external/soci soci/4.0.3@ - - name: install dependencies + echo outcome=$(conan user --remote ripple ${{ secrets.CONAN_USERNAME }} \ + --password ${{ secrets.CONAN_TOKEN }} >&2 && echo success || \ + echo failure) | tee ${GITHUB_OUTPUT} + - name: list missing binaries + id: binaries + shell: bash + # Print the list of dependencies that would need to be built locally. + # A non-empty list means we have "failed" to cache binaries remotely. run: | - mkdir $env:build_dir - cd $env:build_dir - conan install .. --build missing --settings build_type=${{ matrix.configuration }} - - name: configure - run: | - $env:build_dir - cd $env:build_dir - pwd - ls - cmake ` - -G "${{ matrix.generator }}" ` - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake ` - -Dassert=ON ` - -Dreporting=OFF ` - -Dunity=OFF ` - .. + echo missing=$(conan info . --build missing --settings build_type=${{ matrix.configuration }} --json 2>/dev/null | grep '^\[') | tee ${GITHUB_OUTPUT} + - name: build dependencies + uses: ./.github/actions/dependencies + with: + configuration: ${{ matrix.configuration }} + - name: upload dependencies to remote + if: (steps.binaries.outputs.missing != '[]') && (steps.remote.outputs.outcome == 'success') + run: conan upload --remote ripple '*' --all --parallel --confirm - name: build + uses: ./.github/actions/build + with: + generator: '${{ matrix.generator }}' + configuration: ${{ matrix.configuration }} + # Hard code for now. Move to the matrix if varied options are needed + cmake-args: '-Dassert=ON -Dreporting=OFF -Dunity=OFF' + - name: test (permitted to silently fail) + shell: bash + # Github runners are resource limited, which causes unit tests to fail + # (e.g. OOM). To allow forward progress until self-hosted runners are + # up and running reliably, allow the job to succeed even if tests fail. + continue-on-error: true run: | - cmake --build $env:build_dir --target rippled --config ${{ matrix.configuration }} --parallel $env:NUMBER_OF_PROCESSORS - - name: test - run: | - & "$env:build_dir\${{ matrix.configuration }}\rippled.exe" --unittest + ${build_dir}/${{ matrix.configuration }}/rippled --unittest --unittest-jobs $(nproc) diff --git a/.gitignore b/.gitignore index 87d9b3b8e00..e5952e0de1b 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,6 @@ pkg CMakeUserPresets.json bld.rippled/ .vscode + +# Suggested in-tree build directory +/.build/ diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index ae988beeb33..5115eee0855 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -1,28 +1,26 @@ # API Changelog -This changelog is intended to list all updates to the API. +This changelog is intended to list all updates to the [public API methods](https://xrpl.org/public-api-methods.html). -For info about how API versioning works, view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). +For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior. The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `rippled` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade. -For a log of breaking changes, see the **API Version [number]** headings. 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 2 -This version will be supported by `rippled` version 1.12. - -#### V2 account_info response - -`signer_lists` is returned in the root of the response, instead of being nested under `account_data` (as it was in API version 1). ([#3770](https://github.com/XRPLF/rippled/pull/3770)) +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 -This version is supported by `rippled` version 1.11. + +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. ### Idiosyncrasies #### V1 account_info response -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 in this object. However, the docs say that `signer_lists` should be at the root level of the reponse - and this makes sense, since 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).) Thanks to [rippled#3770](https://github.com/XRPLF/rippled/pull/3770), this field will be moved in `api_version: 2`, to the root level of the response. +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. + +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).) + +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) #### server_info - network_id @@ -30,7 +28,7 @@ The `network_id` field was added in the `server_info` response in version 1.5.0 ## XRP Ledger version 1.12.0 -Version 1.12.0 is in development. +[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. ### Additions in 1.12 @@ -76,9 +74,11 @@ Additions are intended to be non-breaking (because they are purely additive). ### Breaking changes in 1.11 -- Added the ability to mark amendments as obsolete. For the `feature` admin API, there is a new possible value for the `vetoed` field. ([#4291](https://github.com/XRPLF/rippled/pull/4291)) -- The API now won't accept seeds or public keys in place of account addresses. ([#4404](https://github.com/XRPLF/rippled/pull/4404)) -- For the `ledger_data` method, when all entries are filtered out, the API now returns an empty list (an empty array, `[]`). (Previously, it would return `null`.) While this is technically a breaking change, the new behavior is consistent with the documentation, so this is considered only a bug fix. ([#4398](https://github.com/XRPLF/rippled/pull/4398)) +- Added the ability to mark amendments as obsolete. For the `feature` admin API, there is a new possible value for the `vetoed` field. (https://github.com/XRPLF/rippled/pull/4291) + - The value of `vetoed` can now be `true`, `false`, or `"Obsolete"`. +- Removed the acceptance of seeds or public keys in place of account addresses. (https://github.com/XRPLF/rippled/pull/4404) + - This simplifies the API and encourages better security practices (i.e. seeds should never be sent over the network). +- For the `ledger_data` method, when all entries are filtered out, the `state` field of the response is now an empty list (in other words, an empty array, `[]`). (Previously, it would return `null`.) While this is technically a breaking change, the new behavior is consistent with the documentation, so this is considered only a bug fix. (https://github.com/XRPLF/rippled/pull/4398) - If and when the `fixNFTokenRemint` amendment activates, there will be a new AccountRoot field, `FirstNFTSequence`. This field is set to the current account sequence when the account issues their first NFT. If an account has not issued any NFTs, then the field is not set. ([#4406](https://github.com/XRPLF/rippled/pull/4406)) - There is a new account deletion restriction: an account can only be deleted if `FirstNFTSequence` + `MintedNFTokens` + `256` is less than the current ledger sequence. - This is potentially a breaking change if clients have logic for determining whether an account can be deleted. @@ -97,3 +97,62 @@ Additions are intended to be non-breaking (because they are purely additive). - Added an `account_flags` object to the `account_info` method response. (https://github.com/XRPLF/rippled/pull/4459) - Added `NFTokenPages` to the `account_objects` RPC. (https://github.com/XRPLF/rippled/pull/4352) - Fixed: `marker` returned from the `account_lines` command would not work on subsequent commands. (https://github.com/XRPLF/rippled/pull/4361) + +## XRP Ledger version 1.10.0 + +[Version 1.10.0](https://github.com/XRPLF/rippled/releases/tag/1.10.0) +was released on Mar 14, 2023. + +### Breaking changes in 1.10 + +- If the `XRPFees` feature is enabled, the `fee_ref` field will be + removed from the [ledger subscription stream](https://xrpl.org/subscribe.html#ledger-stream), because it will no longer + have any meaning. + +# In development + +Changes below this point are in development. + +## API Version 2 + +At the time of writing, this version is expected to be introduced in `rippled` version 2.0. + +Currently (prior to the release of 2.0), it is available as a "beta" version, meaning it can be enabled with a config setting in `rippled.cfg`: +``` +[beta_rpc_api] +1 +``` + +Since `api_version` 2 is in "beta", breaking changes to V2 can currently be made at any time. + +#### Modifications to account_info response in V2 + +- `signer_lists` is returned in the root of the response. Previously, 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 are accepted and may return a normal response - as if `signer_lists` were `true`.) + +#### Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response in V2 + +- 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`. Previously, 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. (https://github.com/XRPLF/rippled/issues/4288) + - Previously, in API version 1, no error was returned. + +##### In progress + +- 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 in V2 + +##### In progress + +- 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: + +The purpose of unit tests is to catch bugs and prevent regressions. In general, it often makes sense to create a test function when there is a breaking change to the API. For APIs that have changed in a new API version, the tests should be modified so that both the prior version and the new version are properly tested. + +To take one example: for `account_info` version 1, WebSocket and JSON-RPC behavior should be tested. The latest API version, i.e. API version 2, should be tested over WebSocket, JSON-RPC, and command line. diff --git a/BUILD.md b/BUILD.md index 3d7223277b2..21341ae9a71 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ -> These instructions assume you have a C++ development environment ready -> with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up -> on Linux, macOS, or Windows, see [our guide](./docs/build/environment.md). -> +| :warning: **WARNING** :warning: +|---| +| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). | + > These instructions also assume a basic familiarity with Conan and CMake. > If you are unfamiliar with Conan, > you can read our [crash course](./docs/build/conan.md) @@ -29,9 +29,12 @@ branch. git checkout develop ``` - ## Minimum Requirements +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) - [CMake 3.16](https://cmake.org/download/) @@ -41,87 +44,111 @@ The [minimum compiler versions][2] required are: | Compiler | Version | |-------------|---------| -| GCC | 10 | +| GCC | 11 | | Clang | 13 | | Apple Clang | 13.1.6 | | MSVC | 19.23 | -We don't recommend Windows for `rippled` production at this time. As of -January 2023, Ubuntu has the highest level of quality assurance, testing, -and support. +### Linux -Windows developers should use Visual Studio 2019. `rippled` isn't -compatible with [Boost](https://www.boost.org/) 1.78 or 1.79, and Conan -can't build earlier Boost versions. +The Ubuntu operating system has received the highest level of +quality assurance, testing, and support. -**Note:** 32-bit Windows development isn't supported. +Here are [sample instructions for setting up a C++ development environment on Linux](./docs/build/environment.md#linux). +### Mac -## Steps +Many rippled engineers use macOS for development. + +Here are [sample instructions for setting up a C++ development environment on macOS](./docs/build/environment.md#macos). +### Windows + +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/ + +## Steps ### Set Up Conan -1. (Optional) If you've never used Conan, use autodetect to set up a default profile. +After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python, Conan, CMake, and a C++ compiler, you may need to set up your Conan profile. + +These instructions assume a basic familiarity with Conan and CMake. + +If you are unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official [Getting Started][3] walkthrough. + +You'll need at least one Conan profile: ``` conan profile new default --detect ``` -2. Update the compiler settings. +Update the compiler settings: ``` conan profile update settings.compiler.cppstd=20 default ``` - 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`), - then you will need to choose the `libstdc++11` ABI. +**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`), +then you will need to choose the `libstdc++11` ABI: ``` conan profile update settings.compiler.libcxx=libstdc++11 default ``` - On Windows, you should 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. +**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. Windows developers must also build `rippled` and its dependencies for the x64 - architecture. + architecture: ``` conan profile update settings.arch=x86_64 default ``` -3. (Optional) If you have multiple compilers installed on your platform, - make sure that Conan and CMake select the one you want to use. - This setting will set the correct variables (`CMAKE__COMPILER`) - in the generated CMake toolchain file. +### Multiple compilers - ``` - conan profile update 'conf.tools.build:compiler_executables={"c": "", "cpp": ""}' default - ``` +When `/usr/bin/g++` exists on a platform, it is the default cpp compiler. This +default works for some users. - It should choose the compiler for dependencies as well, - but not all of them have a Conan recipe that respects this setting (yet). - For the rest, you can set these environment variables: +However, if this compiler cannot build rippled or its dependencies, then you can +install another compiler and set Conan and CMake to use it. +Update the `conf.tools.build:compiler_executables` setting in order to set the correct variables (`CMAKE__COMPILER`) in the +generated CMake toolchain file. +For example, on Ubuntu 20, you may have gcc at `/usr/bin/gcc` and g++ at `/usr/bin/g++`; if that is the case, you can select those compilers with: +``` +conan profile update 'conf.tools.build:compiler_executables={"c": "/usr/bin/gcc", "cpp": "/usr/bin/g++"}' default +``` - ``` - conan profile update env.CC= default - conan profile update env.CXX= default - ``` +Replace `/usr/bin/gcc` and `/usr/bin/g++` with paths to the desired compilers. + +It should choose the compiler for dependencies as well, +but not all of them have a Conan recipe that respects this setting (yet). +For the rest, you can set these environment variables. +Replace `` with paths to the desired compilers: + +- `conan profile update env.CC= default` +- `conan profile update env.CXX= default` -4. Export our [Conan recipe for Snappy](./external/snappy). - It doesn't explicitly link the C++ standard library, - which allows you to statically link it with GCC, if you want. +Export our [Conan recipe for Snappy](./external/snappy). +It does not explicitly link the C++ standard library, +which allows you to statically link it with GCC, if you want. ``` conan export external/snappy snappy/1.1.10@ ``` -5. Export our [Conan recipe for SOCI](./external/soci). - It patches their CMake to correctly import its dependencies. +Export our [Conan recipe for SOCI](./external/soci). +It patches their CMake to correctly import its dependencies. ``` conan export external/soci soci/4.0.3@ @@ -189,7 +216,7 @@ can't build earlier Boost versions. and make sure it matches the `build_type` setting you chose in the previous step. - Multi-config gnerators: + Multi-config generators: ``` cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 5f5dbf5eb0a..8d2ff6cbaef 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -24,6 +24,7 @@ add_library(xrpl::libxrpl ALIAS libxrpl) #]===============================] 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/hash/impl/xxhash.cpp src/ripple/beast/insight/impl/Collector.cpp @@ -55,7 +56,6 @@ target_sources (xrpl_core PRIVATE src/ripple/basics/impl/Log.cpp src/ripple/basics/impl/Number.cpp src/ripple/basics/impl/StringUtilities.cpp - src/ripple/basics/impl/ThreadUtilities.cpp #[===============================[ main sources: subdir: json @@ -103,7 +103,9 @@ target_sources (xrpl_core PRIVATE 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 @@ -201,7 +203,6 @@ install ( src/ripple/basics/tagged_integer.h src/ripple/basics/SubmitSync.h src/ripple/basics/ThreadSafetyAnalysis.h - src/ripple/basics/ThreadUtilities.h src/ripple/basics/ToString.h src/ripple/basics/UnorderedContainers.h src/ripple/basics/UptimeClock.h @@ -243,6 +244,7 @@ install ( 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/KeyType.h src/ripple/protocol/Keylet.h src/ripple/protocol/KnownFormats.h @@ -273,6 +275,8 @@ install ( src/ripple/protocol/STParsedJSON.h src/ripple/protocol/STPathSet.h src/ripple/protocol/STTx.h + src/ripple/protocol/XChainAttestations.h + src/ripple/protocol/STXChainBridge.h src/ripple/protocol/STValidation.h src/ripple/protocol/STVector256.h src/ripple/protocol/SecretKey.h @@ -310,6 +314,7 @@ install ( 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 @@ -528,6 +533,7 @@ target_sources (rippled PRIVATE 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 @@ -562,9 +568,7 @@ target_sources (rippled PRIVATE src/ripple/core/impl/JobQueue.cpp src/ripple/core/impl/LoadEvent.cpp src/ripple/core/impl/LoadMonitor.cpp - src/ripple/core/impl/SNTPClock.cpp src/ripple/core/impl/SociDB.cpp - src/ripple/core/impl/TimeKeeper.cpp src/ripple/core/impl/Workers.cpp src/ripple/core/Pg.cpp #[===============================[ @@ -630,6 +634,7 @@ target_sources (rippled PRIVATE src/ripple/overlay/impl/Cluster.cpp src/ripple/overlay/impl/ConnectAttempt.cpp src/ripple/overlay/impl/Handshake.cpp + src/ripple/overlay/impl/InboundHandoff.cpp src/ripple/overlay/impl/Message.cpp src/ripple/overlay/impl/OverlayImpl.cpp src/ripple/overlay/impl/PeerImp.cpp @@ -809,6 +814,7 @@ if (tests) 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 @@ -839,7 +845,6 @@ if (tests) src/test/basics/Slice_test.cpp src/test/basics/StringUtilities_test.cpp src/test/basics/TaggedCache_test.cpp - src/test/basics/ThreadName_test.cpp src/test/basics/XRPAmount_test.cpp src/test/basics/base64_test.cpp src/test/basics/base_uint_test.cpp @@ -857,6 +862,7 @@ if (tests) 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 @@ -921,12 +927,12 @@ if (tests) src/test/jtx/impl/AMMTest.cpp src/test/jtx/impl/Env.cpp src/test/jtx/impl/JSONRPCClient.cpp - src/test/jtx/impl/ManualTimeKeeper.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 @@ -948,6 +954,7 @@ if (tests) 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 diff --git a/Builds/CMake/deps/Boost.cmake b/Builds/CMake/deps/Boost.cmake index 23ea5e549cc..041c2380e12 100644 --- a/Builds/CMake/deps/Boost.cmake +++ b/Builds/CMake/deps/Boost.cmake @@ -1,4 +1,4 @@ -find_package(Boost 1.70 REQUIRED +find_package(Boost 1.82 REQUIRED COMPONENTS chrono container @@ -6,6 +6,7 @@ find_package(Boost 1.70 REQUIRED coroutine date_time filesystem + json program_options regex system @@ -29,6 +30,7 @@ target_link_libraries(ripple_boost Boost::coroutine Boost::date_time Boost::filesystem + Boost::json Boost::program_options Boost::regex Boost::system diff --git a/Builds/levelization/results/ordering.txt b/Builds/levelization/results/ordering.txt index 7a4f4404321..832b548e5de 100644 --- a/Builds/levelization/results/ordering.txt +++ b/Builds/levelization/results/ordering.txt @@ -173,7 +173,6 @@ test.nodestore > test.unit_test test.overlay > ripple.app test.overlay > ripple.basics test.overlay > ripple.beast -test.overlay > ripple.core test.overlay > ripple.overlay test.overlay > ripple.peerfinder test.overlay > ripple.protocol diff --git a/CMakeLists.txt b/CMakeLists.txt index 5723c831ad4..1bbde549602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # make GIT_COMMIT_HASH define available to all sources find_package(Git) if(Git_FOUND) - execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=40 + execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git describe --always --abbrev=40 OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch) if(gch) set(GIT_COMMIT_HASH "${gch}") @@ -71,7 +71,9 @@ find_package(OpenSSL 1.1.1 REQUIRED) set_target_properties(OpenSSL::SSL PROPERTIES INTERFACE_COMPILE_DEFINITIONS OPENSSL_NO_SSL2 ) +set(SECP256K1_INSTALL TRUE) add_subdirectory(src/secp256k1) +add_library(secp256k1::secp256k1 ALIAS secp256k1) add_subdirectory(src/ed25519-donna) find_package(lz4 REQUIRED) # Target names with :: are not allowed in a generator expression. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2300d476c45..5a40c10cd49 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -30,69 +30,63 @@ If you operate an XRP Ledger server, upgrade to version 1.12.0 by September 20, On supported platforms, see the [instructions on installing or updating `rippled`](https://xrpl.org/install-rippled.html). +The XRPL Foundation publishes portable binaries, which are drop-in replacements for the `rippled` daemon. [See information and downloads for the portable binaries](https://github.com/XRPLF/rippled-portable-builds#portable-builds-of-the-rippled-server). This will work on most distributions, including Ubuntu 16.04, 18.04, 20.04, and 22.04; CentOS; and others. Please test and open issues on GitHub if there are problems. + ## Changelog -### New Features and Amendments +### Amendments, New Features, and Changes +(These are changes which may impact or be useful to end users. For example, you may be able to update your code/workflow to take advantage of these changes.) - **`AMM`**: Introduces an automated market maker (AMM) protocol to the XRP Ledger's decentralized exchange, enabling you to trade assets without a counterparty. For more information about AMMs, see: [Automated Market Maker](https://opensource.ripple.com/docs/xls-30d-amm/amm-uc/). [#4294](https://github.com/XRPLF/rippled/pull/4294) -- **`Clawback`**: Allows issuers to add the `lsfAllowTrustLineClawback` flag to an issuing account. This enables the account to recover, or _claw back_, issued tokens after they're distributed to accounts. For additional documentation on this feature, see: [#4553](https://github.com/XRPLF/rippled/pull/4553). +- **`Clawback`**: Adds a setting, *Allow Clawback*, which lets an issuer recover, or _claw back_, tokens that they previously issued. Issuers cannot enable this setting if they have issued tokens already. For additional documentation on this feature, see: [#4553](https://github.com/XRPLF/rippled/pull/4553). - **`fixReducedOffersV1`**: Reduces the occurrence of order books that are blocked by reduced offers. [#4512](https://github.com/XRPLF/rippled/pull/4512) -- Added binary hardening and linker flags to enhance security during the build process. [#4603](https://github.com/XRPLF/rippled/pull/4603) - -- Updated build dependencies to the most recent versions in Conan Center. [#4595](https://github.com/XRPLF/rippled/pull/4595) - -- Updated Conan recipe for NuDB. [#4615](https://github.com/XRPLF/rippled/pull/4615) - -- Added a pre-commit hook that runs the clang-format linter locally before committing changes. To install this feature, see: [CONTRIBUTING](https://github.com/XRPLF/xrpl-dev-portal/blob/master/CONTRIBUTING.md). [#4599](https://github.com/XRPLF/rippled/pull/4599) - -- Added quality-of-life improvements to workflows, using new [concurrency control](https://docs.github.com/en/actions/using-jobs/using-concurrency) features. [#4597](https://github.com/XRPLF/rippled/pull/4597) - -- Added an Artifactory to the `nix` workflow to improve build times. [#4556](https://github.com/XRPLF/rippled/pull/4556) - - Added WebSocket and RPC port info to `server_info` responses. [#4427](https://github.com/XRPLF/rippled/pull/4427) +- Removed the deprecated `accepted`, `seqNum`, `hash`, and `totalCoins` fields from the `ledger` method. [#4244](https://github.com/XRPLF/rippled/pull/4244) ### Bug Fixes and Performance Improvements +(These are behind-the-scenes improvements, such as internal changes to the code, which are not expected to impact end users.) -- Fixed an incorrect error response when there are missing fields in the API v2 `ledger_entry` method. [#4552](https://github.com/XRPLF/rippled/pull/4552) - -- Updated checkout versions to resolve warnings during Github jobs. [#4598](https://github.com/XRPLF/rippled/pull/4598) +- Added a pre-commit hook that runs the clang-format linter locally before committing changes. To install this feature, see: [CONTRIBUTING](https://github.com/XRPLF/xrpl-dev-portal/blob/master/CONTRIBUTING.md). [#4599](https://github.com/XRPLF/rippled/pull/4599) -- Added an error response to the API v2 `account_info` method when you include an invalid `signer_lists` value. [#4585](https://github.com/XRPLF/rippled/pull/4585) +- In order to make it more straightforward to catch and handle overflows: changed the output type of the `mulDiv()` function from `std::pair` to `std::optional`. [#4243](https://github.com/XRPLF/rippled/pull/4243) -- Fixed an issue with the debug package build. [#4591](https://github.com/XRPLF/rippled/pull/4591) +- Updated `Handler::Condition` enum values to make the code less brittle. [#4239](https://github.com/XRPLF/rippled/pull/4239) -- Added additional error responses to the API v2 `AccountTx` method. [#4571](https://github.com/XRPLF/rippled/pull/4571) +- Renamed `ServerHandlerImp` to `ServerHandler`. [#4516](https://github.com/XRPLF/rippled/pull/4516), [#4592](https://github.com/XRPLF/rippled/pull/4592) -- Fixed build references to deleted `ServerHandlerImp`. [#4592](https://github.com/XRPLF/rippled/pull/4592) +- Replaced hand-rolled code with `std::from_chars` for better maintainability. [#4473](https://github.com/XRPLF/rippled/pull/4473) -- Fixed package definitions for Conan. [#4485](https://github.com/XRPLF/rippled/pull/4485) +- Removed an unused `TypedField` move constructor. [#4567](https://github.com/XRPLF/rippled/pull/4567) -- Changed the output type of the `mulDiv()` function from `std::pair` to `std::optional`. [#4243](https://github.com/XRPLF/rippled/pull/4243) -- Updated `Handler::Condition` enum values to make the code less brittle. [#4239](https://github.com/XRPLF/rippled/pull/4239) +### Docs and Build System -- Renamed `ServerHandlerImp` to `ServerHandler`. [#4516](https://github.com/XRPLF/rippled/pull/4516) +- Updated checkout versions to resolve warnings during GitHub jobs. [#4598](https://github.com/XRPLF/rippled/pull/4598) -- Removed the deprecated `accepted`, `seq`, `hash`, and `totalCoins` fields from the `ledger` method. [#4244](https://github.com/XRPLF/rippled/pull/4244) +- Fixed an issue with the Debian package build. [#4591](https://github.com/XRPLF/rippled/pull/4591) -- Replaced hand-rolled code with `std::from_chars` for better maintainability. [#4473](https://github.com/XRPLF/rippled/pull/4473) +- Updated build instructions with additional steps to take after updating dependencies. [#4623](https://github.com/XRPLF/rippled/pull/4623) -- Removed an unused `TypedField` move constructor. [#4567](https://github.com/XRPLF/rippled/pull/4567) +- Updated contributing doc to clarify that beta releases should also be pushed to the `release` branch. [#4589](https://github.com/XRPLF/rippled/pull/4589) -- Enabled the `BETA_RPC_API` flag in the default unit tests config, making the API v2 available to all unit tests. [#4573](https://github.com/XRPLF/rippled/pull/4573) +- Enabled the `BETA_RPC_API` flag in the default unit tests config, making the API v2 (beta) available to unit tests. [#4573](https://github.com/XRPLF/rippled/pull/4573) +- Conan dependency management. + - Fixed package definitions for Conan. [#4485](https://github.com/XRPLF/rippled/pull/4485) + - Updated build dependencies to the most recent versions in Conan Center. [#4595](https://github.com/XRPLF/rippled/pull/4595) + - Updated Conan recipe for NuDB. [#4615](https://github.com/XRPLF/rippled/pull/4615) -### Docs +- Added binary hardening and linker flags to enhance security during the build process. [#4603](https://github.com/XRPLF/rippled/pull/4603) -- Updated build instructions with additional steps to take after updating dependencies. [#4623](https://github.com/XRPLF/rippled/pull/4623) +- Added an Artifactory to the `nix` workflow to improve build times. [#4556](https://github.com/XRPLF/rippled/pull/4556) -- Updated contributing doc to clarify that beta releases should also be pushed to the `release` branch. [#4589](https://github.com/XRPLF/rippled/pull/4589) +- Added quality-of-life improvements to workflows, using new [concurrency control](https://docs.github.com/en/actions/using-jobs/using-concurrency) features. [#4597](https://github.com/XRPLF/rippled/pull/4597) [Full Commit Log](https://github.com/XRPLF/rippled/compare/1.11.0...1.12.0) diff --git a/SECURITY.md b/SECURITY.md index 15257f21799..4e845735d4c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -37,7 +37,7 @@ Your report should include the following: - The steps to reproduce the vulnerability; - Any other relevant details or artifacts, including code, scripts or patches. -In your mail, please describe of the issue or the potential threat; if possible, please include a "repro" (code that can reproduce the issue) or describe the best way to reproduce and replicate the issue. Please make your report as extensive as possible. +In your email, please describe the issue or potential threat. If possible, include a "repro" (code that can reproduce the issue) or describe the best way to reproduce and replicate the issue. Please make your report as detailed and comprehensive as possible. For more information on responsible disclosure, please read this [Wikipedia article](https://en.wikipedia.org/wiki/Responsible_disclosure). diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 6e1d553a970..a3bcf0056be 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -389,6 +389,21 @@ # # # +# [compression] +# +# true or false +# +# true - enables compression +# false - disables compression [default]. +# +# The rippled server can save bandwidth by compressing its peer-to-peer communications, +# at a cost of greater CPU usage. If you enable link compression, +# the server automatically compresses communications with peer servers +# that also have link compression enabled. +# https://xrpl.org/enable-link-compression.html +# +# +# # [ips] # # List of hostnames or ips where the Ripple protocol is served. A default @@ -463,19 +478,6 @@ # # # -# [sntp_servers] -# -# IP address or domain of NTP servers to use for time synchronization. -# -# These NTP servers are suitable for rippled servers located in the United -# States: -# time.windows.com -# time.apple.com -# time.nist.gov -# pool.ntp.org -# -# -# # [max_transactions] # # Configure the maximum number of transactions to have in the job queue @@ -1704,12 +1706,6 @@ advisory_delete=0 [debug_logfile] /var/log/rippled/debug.log -[sntp_servers] -time.windows.com -time.apple.com -time.nist.gov -pool.ntp.org - # To use the XRP test network # (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), # use the following [ips] section: diff --git a/cfg/rippled-reporting.cfg b/cfg/rippled-reporting.cfg index f09c17ae637..632a8a7800e 100644 --- a/cfg/rippled-reporting.cfg +++ b/cfg/rippled-reporting.cfg @@ -450,19 +450,6 @@ # # # -# [sntp_servers] -# -# IP address or domain of NTP servers to use for time synchronization. -# -# These NTP servers are suitable for rippled servers located in the United -# States: -# time.windows.com -# time.apple.com -# time.nist.gov -# pool.ntp.org -# -# -# # [max_transactions] # # Configure the maximum number of transactions to have in the job queue @@ -1662,12 +1649,6 @@ advisory_delete=0 [debug_logfile] /var/log/rippled-reporting/debug.log -[sntp_servers] -time.windows.com -time.apple.com -time.nist.gov -pool.ntp.org - # To use the XRP test network # (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), # use the following [ips] section: diff --git a/src/ripple/app/ledger/LedgerHistory.h b/src/ripple/app/ledger/LedgerHistory.h index 5733ca76375..092ad7d8371 100644 --- a/src/ripple/app/ledger/LedgerHistory.h +++ b/src/ripple/app/ledger/LedgerHistory.h @@ -95,7 +95,7 @@ class LedgerHistory /** Repair a hash to index mapping @param ledgerIndex The index whose mapping is to be repaired @param ledgerHash The hash it is to be mapped to - @return `true` if the mapping was repaired + @return `false` if the mapping was repaired */ bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash); diff --git a/src/ripple/app/ledger/impl/LedgerCleaner.cpp b/src/ripple/app/ledger/impl/LedgerCleaner.cpp index d63c24476d7..e5ee6409d34 100644 --- a/src/ripple/app/ledger/impl/LedgerCleaner.cpp +++ b/src/ripple/app/ledger/impl/LedgerCleaner.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include namespace ripple { @@ -218,7 +218,7 @@ class LedgerCleanerImp : public LedgerCleaner void run() { - this_thread::set_name("LedgerCleaner"); + beast::setCurrentThreadName("LedgerCleaner"); JLOG(j_.debug()) << "Started"; while (true) diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 82affea3bbd..6f9b5b450eb 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -1182,9 +1182,6 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) // Optionally turn off logging to console. logs_->silent(config_->silent()); - if (!config_->standalone()) - timeKeeper_->run(config_->SNTP_SERVERS); - if (!initRelationalDatabase() || !initNodeStore()) return false; @@ -1286,6 +1283,12 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } } + if (auto const& forcedRange = config().FORCED_LEDGER_RANGE_PRESENT) + { + m_ledgerMaster->setLedgerRangePresent( + forcedRange->first, forcedRange->second); + } + if (!config().reporting()) m_orderBookDB.setup(getLedgerMaster().getCurrentLedger()); @@ -1353,6 +1356,9 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) << "Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]"; return false; } + + // Tell the AmendmentTable who the trusted validators are. + m_amendmentTable->trustChanged(validators_->getQuorumKeys().second); } //---------------------------------------------------------------------- // diff --git a/src/ripple/app/main/BasicApp.cpp b/src/ripple/app/main/BasicApp.cpp index a03d7255b3e..5993df62fa7 100644 --- a/src/ripple/app/main/BasicApp.cpp +++ b/src/ripple/app/main/BasicApp.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include BasicApp::BasicApp(std::size_t numberOfThreads) { @@ -28,7 +28,7 @@ BasicApp::BasicApp(std::size_t numberOfThreads) while (numberOfThreads--) { threads_.emplace_back([this, numberOfThreads]() { - ripple::this_thread::set_name( + beast::setCurrentThreadName( "io svc #" + std::to_string(numberOfThreads)); this->io_service_.run(); }); diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/ripple/app/main/GRPCServer.cpp index eb2d4ce9a18..3a5e96b0ed9 100644 --- a/src/ripple/app/main/GRPCServer.cpp +++ b/src/ripple/app/main/GRPCServer.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -692,8 +692,9 @@ GRPCServer::start() if (running_ = impl_.start(); running_) { thread_ = std::thread([this]() { + beast::setCurrentThreadName("rippled : GRPCServer"); // Start the event loop and begin handling requests - this_thread::set_name("rippled: grpc"); + beast::setCurrentThreadName("rippled: grpc"); this->impl_.handleRpcs(); }); } diff --git a/src/ripple/app/main/LoadManager.cpp b/src/ripple/app/main/LoadManager.cpp index df9d72f599a..5e87063f000 100644 --- a/src/ripple/app/main/LoadManager.cpp +++ b/src/ripple/app/main/LoadManager.cpp @@ -21,8 +21,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -99,7 +99,7 @@ LoadManager::stop() void LoadManager::run() { - this_thread::set_name("LoadManager"); + beast::setCurrentThreadName("LoadManager"); using namespace std::chrono_literals; using clock_type = std::chrono::steady_clock; diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index 0ce8b085971..a998640c01c 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -22,9 +22,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -348,7 +348,8 @@ run(int argc, char** argv) { using namespace std; - this_thread::set_name("main " + BuildInfo::getVersionString()); + beast::setCurrentThreadName( + "rippled: main " + BuildInfo::getVersionString()); po::variables_map vm; @@ -378,8 +379,13 @@ run(int argc, char** argv) "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.")( - "version", "Display the build version."); + "standalone,a", "Run with no peers.")("verbose,v", "Verbose logging.") + + ("force_ledger_present_range", + po::value(), + "Specify the range of present ledgers for testing purposes. Min and " + "max values are comma separated.")( + "version", "Display the build version."); po::options_description data("Ledger/Data Options"); data.add_options()("import", importText.c_str())( @@ -602,6 +608,51 @@ run(int argc, char** argv) return 0; } + if (vm.contains("force_ledger_present_range")) + { + try + { + auto const r = [&vm]() -> std::vector { + std::vector strVec; + boost::split( + strVec, + vm["force_ledger_present_range"].as(), + boost::algorithm::is_any_of(",")); + std::vector result; + for (auto& s : strVec) + { + boost::trim(s); + if (!s.empty()) + result.push_back(std::stoi(s)); + } + return result; + }(); + + if (r.size() == 2) + { + if (r[0] > r[1]) + { + throw std::runtime_error( + "Invalid force_ledger_present_range parameter"); + } + config->FORCED_LEDGER_RANGE_PRESENT.emplace(r[0], r[1]); + } + else + { + throw std::runtime_error( + "Invalid force_ledger_present_range parameter"); + } + } + catch (std::exception const& e) + { + std::cerr << "invalid 'force_ledger_present_range' parameter. The " + "parameter must be two numbers separated by a comma. " + "The first number must be <= the second." + << std::endl; + return -1; + } + } + if (vm.count("start")) { config->START_UP = Config::FRESH; @@ -752,10 +803,8 @@ run(int argc, char** argv) if (vm.count("debug")) setDebugLogSink(logs->makeSink("Debug", beast::severities::kTrace)); - auto timeKeeper = make_TimeKeeper(logs->journal("TimeKeeper")); - auto app = make_Application( - std::move(config), std::move(logs), std::move(timeKeeper)); + std::move(config), std::move(logs), std::make_unique()); if (!app->setup(vm)) return -1; @@ -776,7 +825,7 @@ run(int argc, char** argv) } // We have an RPC command to process: - this_thread::set_name("rippled: rpc"); + beast::setCurrentThreadName("rippled: rpc"); return RPCCall::fromCommandLine( *config, vm["parameters"].as>(), *logs); } diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index 10396d8591f..d7f7c8b26bb 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -111,6 +111,10 @@ class AmendmentTable std::set const& enabled, majorityAmendments_t const& majority) = 0; + // Called when the set of trusted validators changes. + virtual void + trustChanged(hash_set const& allTrusted) = 0; + // Called by the consensus code when we need to // inject pseudo-transactions virtual std::map diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index d606e4b6707..df202cbc2ed 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -607,12 +607,14 @@ class NetworkOPsImp final : public NetworkOPs void pubValidatedTransaction( std::shared_ptr const& ledger, - AcceptedLedgerTx const& transaction); + AcceptedLedgerTx const& transaction, + bool last); void pubAccountTransaction( std::shared_ptr const& ledger, - AcceptedLedgerTx const& transaction); + AcceptedLedgerTx const& transaction, + bool last); void pubProposedAccountTransaction( @@ -737,11 +739,10 @@ class NetworkOPsImp final : public NetworkOPs sPeerStatus, // Peer status changes. sConsensusPhase, // Consensus phase sBookChanges, // Per-ledger order book changes - - sLastEntry = sBookChanges // as this name implies, any new entry - // must be ADDED ABOVE this one + sLastEntry // Any new entry must be ADDED ABOVE this one }; - std::array mStreamMaps; + + std::array mStreamMaps; ServerFeeSummary mLastFeeSummary; @@ -1849,7 +1850,12 @@ NetworkOPsImp::beginConsensus(uint256 const& networkClosed) app_.getHashRouter()); if (!changes.added.empty() || !changes.removed.empty()) + { app_.getValidations().trustChanged(changes.added, changes.removed); + // Update the AmendmentTable so it tracks the current validators. + app_.getAmendmentTable().trustChanged( + app_.validators().getQuorumKeys().second); + } mConsensus.startRound( app_.timeKeeper().closeTime(), @@ -2612,13 +2618,10 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) lpClosed->fees().accountReserve(0).decimalXRP(); l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP(); - auto const nowOffset = app_.timeKeeper().nowOffset(); - if (std::abs(nowOffset.count()) >= 60) - l[jss::system_time_offset] = nowOffset.count(); - - auto const closeOffset = app_.timeKeeper().closeOffset(); - if (std::abs(closeOffset.count()) >= 60) - l[jss::close_time_offset] = closeOffset.count(); + if (auto const closeOffset = app_.timeKeeper().closeOffset(); + std::abs(closeOffset.count()) >= 60) + l[jss::close_time_offset] = + static_cast(closeOffset.count()); #if RIPPLED_REPORTING std::int64_t const dbAge = @@ -3031,7 +3034,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) for (auto const& accTx : *alpAccepted) { JLOG(m_journal.trace()) << "pubAccepted: " << accTx->getJson(); - pubValidatedTransaction(lpAccepted, *accTx); + pubValidatedTransaction( + lpAccepted, *accTx, accTx == *(--alpAccepted->end())); } } @@ -3138,7 +3142,8 @@ NetworkOPsImp::transJson( void NetworkOPsImp::pubValidatedTransaction( std::shared_ptr const& ledger, - const AcceptedLedgerTx& transaction) + const AcceptedLedgerTx& transaction, + bool last) { auto const& stTxn = transaction.getTxn(); @@ -3187,13 +3192,14 @@ NetworkOPsImp::pubValidatedTransaction( if (transaction.getResult() == tesSUCCESS) app_.getOrderBookDB().processTxn(ledger, transaction, jvObj); - pubAccountTransaction(ledger, transaction); + pubAccountTransaction(ledger, transaction, last); } void NetworkOPsImp::pubAccountTransaction( std::shared_ptr const& ledger, - AcceptedLedgerTx const& transaction) + AcceptedLedgerTx const& transaction, + bool last) { hash_set notify; int iProposed = 0; @@ -3301,6 +3307,9 @@ NetworkOPsImp::pubAccountTransaction( for (InfoSub::ref isrListener : notify) isrListener->send(jvObj, true); + if (last) + jvObj[jss::account_history_boundary] = true; + assert(!jvObj.isMember(jss::account_history_tx_stream)); for (auto& info : accountHistoryNotify) { @@ -3699,8 +3708,11 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) auto const& txns = dbResult->first; marker = dbResult->second; - for (auto const& [tx, meta] : txns) + size_t num_txns = txns.size(); + for (size_t i = 0; i < num_txns; ++i) { + auto const& [tx, meta] = txns[i]; + if (!tx || !meta) { JLOG(m_journal.debug()) @@ -3735,6 +3747,11 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) *stTxn, meta->getResultTER(), true, curTxLedger); jvTx[jss::meta] = meta->getJson(JsonOptions::none); jvTx[jss::account_history_tx_index] = txHistoryIndex--; + + if (i + 1 == num_txns || + txns[i + 1].first->getLedger() != tx->getLedger()) + jvTx[jss::account_history_boundary] = true; + RPC::insertDeliveredAmount( jvTx[jss::meta], *curTxLedger, stTxn, *meta); if (isFirstTx(tx, meta)) diff --git a/src/ripple/app/misc/SHAMapStoreImp.cpp b/src/ripple/app/misc/SHAMapStoreImp.cpp index f396144cfd5..d5cb07792dc 100644 --- a/src/ripple/app/misc/SHAMapStoreImp.cpp +++ b/src/ripple/app/misc/SHAMapStoreImp.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -286,7 +286,7 @@ SHAMapStoreImp::run() "Reporting does not support online_delete. Remove " "online_delete info from config"); } - this_thread::set_name("SHAMapStore"); + beast::setCurrentThreadName("SHAMapStore"); LedgerIndex lastRotated = state_db_.getState().lastRotated; netOPs_ = &app_.getOPs(); ledgerMaster_ = &app_.getLedgerMaster(); diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/ripple/app/misc/impl/AmendmentTable.cpp index 6f9ea86fa6c..8f4f0321992 100644 --- a/src/ripple/app/misc/impl/AmendmentTable.cpp +++ b/src/ripple/app/misc/impl/AmendmentTable.cpp @@ -67,6 +67,155 @@ parseSection(Section const& section) return names; } +/** TrustedVotes records the most recent votes from trusted validators. + We keep a record in an effort to avoid "flapping" while amendment voting + is in process. + + If a trusted validator loses synchronization near a flag ledger their + amendment votes may be lost during that round. If the validator is a + bit flaky, then this can cause an amendment to appear to repeatedly + gain and lose support. + + TrustedVotes addresses the problem by holding on to the last vote seen + from every trusted validator. So if any given validator is off line near + a flag ledger we can assume that they did not change their vote. + + If we haven't seen any STValidations from a validator for several hours we + lose confidence that the validator hasn't changed their position. So + there's a timeout. We remove upVotes if they haven't been updated in + several hours. +*/ +class TrustedVotes +{ +private: + static constexpr NetClock::time_point maxTimeout = + NetClock::time_point::max(); + + // Associates each trusted validator with the last votes we saw from them + // and an expiration for that record. + struct UpvotesAndTimeout + { + std::vector upVotes; + NetClock::time_point timeout = maxTimeout; + }; + hash_map recordedVotes_; + +public: + TrustedVotes() = default; + TrustedVotes(TrustedVotes const& rhs) = delete; + TrustedVotes& + operator=(TrustedVotes const& rhs) = delete; + + // Called when the list of trusted validators changes. + // + // Call with AmendmentTable::mutex_ locked. + void + trustChanged( + hash_set const& allTrusted, + std::lock_guard const& lock) + { + decltype(recordedVotes_) newRecordedVotes; + newRecordedVotes.reserve(allTrusted.size()); + + // Make sure every PublicKey in allTrusted is represented in + // recordedVotes_. Also make sure recordedVotes_ contains + // no additional PublicKeys. + for (auto& trusted : allTrusted) + { + if (recordedVotes_.contains(trusted)) + { + // Preserve this validator's previously saved voting state. + newRecordedVotes.insert(recordedVotes_.extract(trusted)); + } + else + { + // New validators have a starting position of no on everything. + // Add the entry with an empty vector and maxTimeout. + newRecordedVotes[trusted]; + } + } + // The votes of any no-longer-trusted validators will be destroyed + // when changedTrustedVotes goes out of scope. + recordedVotes_.swap(newRecordedVotes); + } + + // Called when we receive the latest votes. + // + // Call with AmendmentTable::mutex_ locked. + void + recordVotes( + Rules const& rules, + std::vector> const& valSet, + NetClock::time_point const closeTime, + std::lock_guard const& lock) + { + // When we get an STValidation we save the upVotes it contains, but + // we also set an expiration for those upVotes. The following constant + // controls the timeout. + // + // There really is no "best" timeout to choose for when we finally + // lose confidence that we know how a validator is voting. But part + // of the point of recording validator votes is to avoid flapping of + // amendment votes. A 24h timeout says that we will change the local + // record of a validator's vote to "no" 24h after the last vote seen + // from that validator. So flapping due to that validator being off + // line will happen less frequently than every 24 hours. + using namespace std::chrono_literals; + static constexpr NetClock::duration expiresAfter = 24h; + + // Walk all validations and replace previous votes from trusted + // validators with these newest votes. + for (auto const& val : valSet) + { + // If this validation comes from one of our trusted validators... + if (auto const iter = recordedVotes_.find(val->getSignerPublic()); + iter != recordedVotes_.end()) + { + iter->second.timeout = closeTime + expiresAfter; + if (val->isFieldPresent(sfAmendments)) + { + auto const& choices = val->getFieldV256(sfAmendments); + iter->second.upVotes.assign(choices.begin(), choices.end()); + } + else + { + // This validator does not upVote any amendments right now. + iter->second.upVotes.clear(); + } + } + } + + // Now remove any expired records from recordedVotes_. + std::for_each( + recordedVotes_.begin(), + recordedVotes_.end(), + [&closeTime](decltype(recordedVotes_)::value_type& votes) { + if (closeTime > votes.second.timeout) + { + votes.second.timeout = maxTimeout; + votes.second.upVotes.clear(); + } + }); + } + + // Return the information needed by AmendmentSet to determine votes. + // + // Call with AmendmentTable::mutex_ locked. + [[nodiscard]] std::pair> + getVotes(Rules const& rules, std::lock_guard const& lock) const + { + hash_map ret; + for (auto& validatorVotes : recordedVotes_) + { + for (uint256 const& amendment : validatorVotes.second.upVotes) + { + ret[amendment] += 1; + } + } + return {recordedVotes_.size(), ret}; + } +}; + /** Current state of an amendment. Tells if a amendment is supported, enabled or vetoed. A vetoed amendment means the node will never announce its support. @@ -104,30 +253,9 @@ class AmendmentSet // number of votes needed int threshold_ = 0; -public: - AmendmentSet( - Rules const& rules, - std::vector> const& valSet) - : rules_(rules) + void + computeThreshold(int trustedValidations, Rules const& rules) { - // process validations for ledger before flag ledger - for (auto const& val : valSet) - { - if (val->isTrusted()) - { - if (val->isFieldPresent(sfAmendments)) - { - auto const choices = val->getFieldV256(sfAmendments); - std::for_each( - choices.begin(), - choices.end(), - [&](auto const& amendment) { ++votes_[amendment]; }); - } - - ++trustedValidations_; - } - } - threshold_ = !rules_.enabled(fixAmendmentMajorityCalc) ? std::max( 1L, @@ -143,6 +271,22 @@ class AmendmentSet postFixAmendmentMajorityCalcThreshold.den)); } +public: + AmendmentSet( + Rules const& rules, + TrustedVotes const& trustedVotes, + std::lock_guard const& lock) + : rules_(rules) + { + // process validations for ledger before flag ledger. + auto [trustedCount, newVotes] = trustedVotes.getVotes(rules, lock); + + trustedValidations_ = trustedCount; + votes_.swap(newVotes); + + computeThreshold(trustedValidations_, rules); + } + bool passes(uint256 const& amendment) const { @@ -203,6 +347,9 @@ class AmendmentTableImpl final : public AmendmentTable hash_map amendmentMap_; std::uint32_t lastUpdateSeq_; + // Record of the last votes seen from trusted validators. + TrustedVotes previousTrustedVotes_; + // Time that an amendment must hold a majority for std::chrono::seconds const majorityTime_; @@ -294,6 +441,9 @@ class AmendmentTableImpl final : public AmendmentTable std::set const& enabled, majorityAmendments_t const& majority) override; + void + trustChanged(hash_set const& allTrusted) override; + std::vector doValidation(std::set const& enabledAmendments) const override; @@ -633,8 +783,14 @@ AmendmentTableImpl::doVoting( << ": " << enabledAmendments.size() << ", " << majorityAmendments.size() << ", " << valSet.size(); - auto vote = std::make_unique(rules, valSet); + std::lock_guard lock(mutex_); + + // Keep a record of the votes we received. + previousTrustedVotes_.recordVotes(rules, valSet, closeTime, lock); + // Tally the most recent votes. + auto vote = + std::make_unique(rules, previousTrustedVotes_, lock); JLOG(j_.debug()) << "Received " << vote->trustedValidations() << " trusted validations, threshold is: " << vote->threshold(); @@ -643,8 +799,6 @@ AmendmentTableImpl::doVoting( // the value of the flags in the pseudo-transaction std::map actions; - std::lock_guard lock(mutex_); - // process all amendments we know of for (auto const& entry : amendmentMap_) { @@ -740,6 +894,13 @@ AmendmentTableImpl::doValidatedLedger( firstUnsupportedExpected_ = *firstUnsupportedExpected_ + majorityTime_; } +void +AmendmentTableImpl::trustChanged(hash_set const& allTrusted) +{ + std::lock_guard lock(mutex_); + previousTrustedVotes_.trustChanged(allTrusted, lock); +} + void AmendmentTableImpl::injectJson( Json::Value& v, diff --git a/src/ripple/app/rdb/impl/UnitaryShard.cpp b/src/ripple/app/rdb/impl/UnitaryShard.cpp index 37cbfd55ac3..ab1758b4852 100644 --- a/src/ripple/app/rdb/impl/UnitaryShard.cpp +++ b/src/ripple/app/rdb/impl/UnitaryShard.cpp @@ -175,6 +175,11 @@ updateLedgerDBs( 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)}; @@ -190,11 +195,8 @@ updateLedgerDBs( ":closingTime, :prevClosingTime, :closeTimeRes," ":closeFlags, :accountSetHash, :transSetHash);", soci::use(sHash), soci::use(ledgerSeq), soci::use(sParentHash), - soci::use(sDrops), - soci::use(ledger->info().closeTime.time_since_epoch().count()), - soci::use( - ledger->info().parentCloseTime.time_since_epoch().count()), - soci::use(ledger->info().closeTimeResolution.count()), + soci::use(sDrops), soci::use(closingTime), + soci::use(prevClosingTime), soci::use(closeTimeRes), soci::use(ledger->info().closeFlags), soci::use(sAccountHash), soci::use(sTxHash); diff --git a/src/ripple/app/rdb/impl/Wallet.cpp b/src/ripple/app/rdb/impl/Wallet.cpp index 25a06bbd97a..3715c4c7458 100644 --- a/src/ripple/app/rdb/impl/Wallet.cpp +++ b/src/ripple/app/rdb/impl/Wallet.cpp @@ -205,19 +205,20 @@ insertPeerReservation( PublicKey const& nodeId, std::string const& description) { + auto const sNodeId = toBase58(TokenType::NodePublic, nodeId); session << "INSERT INTO PeerReservations (PublicKey, Description) " "VALUES (:nodeId, :desc) " "ON CONFLICT (PublicKey) DO UPDATE SET " "Description=excluded.Description", - soci::use(toBase58(TokenType::NodePublic, nodeId)), - soci::use(description); + soci::use(sNodeId), soci::use(description); } void deletePeerReservation(soci::session& session, PublicKey const& nodeId) { + auto const sNodeId = toBase58(TokenType::NodePublic, nodeId); session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId", - soci::use(toBase58(TokenType::NodePublic, nodeId)); + soci::use(sNodeId); } bool diff --git a/src/ripple/app/reporting/ETLSource.cpp b/src/ripple/app/reporting/ETLSource.cpp index 213b17737b4..be31f4fdfa9 100644 --- a/src/ripple/app/reporting/ETLSource.cpp +++ b/src/ripple/app/reporting/ETLSource.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include diff --git a/src/ripple/app/reporting/ReportingETL.cpp b/src/ripple/app/reporting/ReportingETL.cpp index 0b68ae1ffa1..d8d6af36881 100644 --- a/src/ripple/app/reporting/ReportingETL.cpp +++ b/src/ripple/app/reporting/ReportingETL.cpp @@ -19,7 +19,8 @@ #include #include -#include + +#include #include #include #include @@ -509,7 +510,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &startSequence, &writeConflict, &transformQueue]() { - this_thread::set_name("ETL extract"); + beast::setCurrentThreadName("rippled: ReportingETL extract"); uint32_t currentSequence = startSequence; // there are two stopping conditions here. @@ -561,7 +562,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &writeConflict, &loadQueue, &transformQueue]() { - this_thread::set_name("ETL transform"); + beast::setCurrentThreadName("rippled: ReportingETL transform"); assert(parent); parent = std::make_shared(*parent, NetClock::time_point{}); @@ -600,7 +601,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &lastPublishedSequence, &loadQueue, &writeConflict]() { - this_thread::set_name("ETL load"); + beast::setCurrentThreadName("rippled: ReportingETL load"); size_t totalTransactions = 0; double totalTime = 0; while (!writeConflict) @@ -824,7 +825,7 @@ void ReportingETL::doWork() { worker_ = std::thread([this]() { - this_thread::set_name("ETL worker"); + beast::setCurrentThreadName("rippled: ReportingETL worker"); if (readOnly_) monitorReadOnly(); else diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 907611f1c9a..7b1ac7d08df 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace ripple { @@ -387,6 +388,9 @@ LedgerEntryTypesMatch::visitEntry( case ltNFTOKEN_PAGE: case ltNFTOKEN_OFFER: case ltAMM: + case ltBRIDGE: + case ltXCHAIN_OWNED_CLAIM_ID: + case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: break; default: invalidTypeAdded_ = true; @@ -487,7 +491,9 @@ ValidNewAccountRoot::finalize( } // From this point on we know exactly one account was created. - if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE) && + if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE || + tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION || + tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) && result == tesSUCCESS) { std::uint32_t const startingSeq{ diff --git a/src/ripple/app/tx/impl/InvariantCheck.h b/src/ripple/app/tx/impl/InvariantCheck.h index 5194a9c34c6..eb606c2ed3b 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.h +++ b/src/ripple/app/tx/impl/InvariantCheck.h @@ -25,6 +25,7 @@ #include #include #include + #include #include #include @@ -300,7 +301,7 @@ class NoZeroEscrow class ValidNewAccountRoot { std::uint32_t accountsCreated_ = 0; - std::uint32_t accountSeq_ = 0; // Only meaningful if accountsCreated_ > 0 + std::uint32_t accountSeq_ = 0; public: void diff --git a/src/ripple/app/tx/impl/SetTrust.cpp b/src/ripple/app/tx/impl/SetTrust.cpp index 7869cc7027d..00a5165221e 100644 --- a/src/ripple/app/tx/impl/SetTrust.cpp +++ b/src/ripple/app/tx/impl/SetTrust.cpp @@ -140,7 +140,20 @@ SetTrust::preclaim(PreclaimContext const& ctx) return tecNO_DST; if (sleDst->getFlags() & lsfDisallowIncomingTrustline) - return tecNO_PERMISSION; + { + // The original implementation of featureDisallowIncoming was + // too restrictive. If + // o fixDisallowIncomingV1 is enabled and + // o The trust line already exists + // Then allow the TrustSet. + if (ctx.view.rules().enabled(fixDisallowIncomingV1) && + ctx.view.exists(keylet::line(id, uDstAccountID, currency))) + { + // pass + } + else + return tecNO_PERMISSION; + } } // If destination is AMM and the trustline doesn't exist then only diff --git a/src/ripple/app/tx/impl/XChainBridge.cpp b/src/ripple/app/tx/impl/XChainBridge.cpp new file mode 100644 index 00000000000..6ef10b0ebfa --- /dev/null +++ b/src/ripple/app/tx/impl/XChainBridge.cpp @@ -0,0 +1,2298 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 + +#include +#include + +namespace ripple { + +/* + Bridges connect two independent ledgers: a "locking chain" and an "issuing + chain". An asset can be moved from the locking chain to the issuing chain by + putting it into trust on the locking chain, and issuing a "wrapped asset" + that represents the locked asset on the issuing chain. + + Note that a bridge is not an exchange. There is no exchange rate: one wrapped + asset on the issuing chain always represents one asset in trust on the + locking chain. The bridge also does not exchange an asset on the locking + chain for an asset on the issuing chain. + + A good model for thinking about bridges is a box that contains an infinite + number of "wrapped tokens". When a token from the locking chain + (locking-chain-token) is put into the box, a wrapped token is taken out of + the box and put onto the issuing chain (issuing-chain-token). No one can use + the locking-chain-token while it remains in the box. When an + issuing-chain-token is returned to the box, one locking-chain-token is taken + out of the box and put back onto the locking chain. + + This requires a way to put assets into trust on one chain (put a + locking-chain-token into the box). A regular XRP account is used for this. + This account is called a "door account". Much in the same way that a door is + used to go from one room to another, a door account is used to move from one + chain to another. This account will be jointly controlled by a set of witness + servers by using the ledger's multi-signature support. The master key will be + disabled. These witness servers are trusted in the sense that if a quorum of + them collude, they can steal the funds put into trust. + + This also requires a way to prove that assets were put into the box - either + a locking-chain-token on the locking chain or returning an + issuing-chain-token on the issuing chain. A set of servers called "witness + servers" fill this role. These servers watch the ledger for these + transactions, and attest that the given events happened on the different + chains by signing messages with the event information. + + There needs to be a way to prevent the attestations from the witness + servers from being used more than once. "Claim ids" fill this role. A claim + id must be acquired on the destination chain before the asset is "put into + the box" on the source chain. This claim id has a unique id, and once it is + destroyed it can never exist again (it's a simple counter). The attestations + reference this claim id, and are accumulated on the claim id. Once a quorum + is reached, funds can move. Once the funds move, the claim id is destroyed. + + Finally, a claim id requires that the sender has an account on the + destination chain. For some chains, this can be a problem - especially if + the wrapped asset represents XRP, and XRP is needed to create an account. + There's a bootstrap problem. To address this, there is a special transaction + used to create accounts. This transaction does not require a claim id. + + See the document "docs/bridge/spec.md" for a full description of how + bridges and their transactions work. +*/ + +namespace { + +// Check that the public key is allowed to sign for the given account. If the +// account does not exist on the ledger, then the public key must be the master +// key for the given account if it existed. Otherwise the key must be an enabled +// master key or a regular key for the existing account. +TER +checkAttestationPublicKey( + ReadView const& view, + std::unordered_map const& signersList, + AccountID const& attestationSignerAccount, + PublicKey const& pk, + beast::Journal j) +{ + if (!signersList.contains(attestationSignerAccount)) + { + return tecNO_PERMISSION; + } + + AccountID const accountFromPK = calcAccountID(pk); + + if (auto const sleAttestationSigningAccount = + view.read(keylet::account(attestationSignerAccount))) + { + if (accountFromPK == attestationSignerAccount) + { + // master key + if (sleAttestationSigningAccount->getFieldU32(sfFlags) & + lsfDisableMaster) + { + JLOG(j.trace()) << "Attempt to add an attestation with " + "disabled master key."; + return tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR; + } + } + else + { + // regular key + if (std::optional regularKey = + (*sleAttestationSigningAccount)[~sfRegularKey]; + regularKey != accountFromPK) + { + if (!regularKey) + { + JLOG(j.trace()) + << "Attempt to add an attestation with " + "account present and non-present regular key."; + } + else + { + JLOG(j.trace()) << "Attempt to add an attestation with " + "account present and mismatched " + "regular key/public key."; + } + return tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR; + } + } + } + else + { + // account does not exist. + if (calcAccountID(pk) != attestationSignerAccount) + { + JLOG(j.trace()) + << "Attempt to add an attestation with non-existant account " + "and mismatched pk/account pair."; + return tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR; + } + } + + return tesSUCCESS; +} + +// If there is a quorum of attestations for the given parameters, then +// return the reward accounts, otherwise return TER for the error. +// Also removes attestations that are no longer part of the signers list. +// +// Note: the dst parameter is what the attestations are attesting to, which +// is not always used (it is used when automatically triggering a transfer +// from an `addAttestation` transaction, it is not used in a `claim` +// transaction). If the `checkDst` parameter is `check`, the attestations +// must attest to this destination, if it is `ignore` then the `dst` of the +// attestations are not checked (as for a `claim` transaction) + +enum class CheckDst { check, ignore }; +template +Expected, TER> +claimHelper( + XChainAttestationsBase& attestations, + ReadView const& view, + typename TAttestation::MatchFields const& toMatch, + CheckDst checkDst, + std::uint32_t quorum, + std::unordered_map const& signersList, + beast::Journal j) +{ + // Remove attestations that are not valid signers. They may be no longer + // part of the signers list, or their master key may have been disabled, + // or their regular key may have changed + attestations.erase_if([&](auto const& a) { + return checkAttestationPublicKey( + view, signersList, a.keyAccount, a.publicKey, j) != + tesSUCCESS; + }); + + // Check if we have quorum for the amount specified on the new claimAtt + std::vector rewardAccounts; + rewardAccounts.reserve(attestations.size()); + std::uint32_t weight = 0; + for (auto const& a : attestations) + { + auto const matchR = a.match(toMatch); + // The dest must match if claimHelper is being run as a result of an add + // attestation transaction. The dst does not need to match if the + // claimHelper is being run using an explicit claim transaction. + using enum AttestationMatch; + if (matchR == nonDstMismatch || + (checkDst == CheckDst::check && matchR != match)) + continue; + auto i = signersList.find(a.keyAccount); + if (i == signersList.end()) + { + assert(0); // should have already been checked + continue; + } + weight += i->second; + rewardAccounts.push_back(a.rewardAccount); + } + + if (weight >= quorum) + return rewardAccounts; + + return Unexpected(tecXCHAIN_CLAIM_NO_QUORUM); +} + +/** + Handle a new attestation event. + + Attempt to add the given attestation and reconcile with the current + signer's list. Attestations that are not part of the current signer's + list will be removed. + + @param claimAtt New attestation to add. It will be added if it is not + already part of the collection, or attests to a larger value. + + @param quorum Min weight required for a quorum + + @param signersList Map from signer's account id (derived from public keys) + to the weight of that key. + + @return optional reward accounts. If after handling the new attestation + there is a quorum for the amount specified on the new attestation, then + return the reward accounts for that amount, otherwise return a nullopt. + Note that if the signer's list changes and there have been `commit` + transactions of different amounts then there may be a different subset that + has reached quorum. However, to "trigger" that subset would require adding + (or re-adding) an attestation that supports that subset. + + The reason for using a nullopt instead of an empty vector when a quorum is + not reached is to allow for an interface where a quorum is reached but no + rewards are distributed. + + @note This function is not called `add` because it does more than just + add the new attestation (in fact, it may not add the attestation at + all). Instead, it handles the event of a new attestation. + */ +struct OnNewAttestationResult +{ + std::optional> rewardAccounts; + // `changed` is true if the attestation collection changed in any way + // (added/removed/changed) + bool changed{false}; +}; + +template +[[nodiscard]] OnNewAttestationResult +onNewAttestations( + XChainAttestationsBase& attestations, + ReadView const& view, + typename TAttestation::TSignedAttestation const* attBegin, + typename TAttestation::TSignedAttestation const* attEnd, + std::uint32_t quorum, + std::unordered_map const& signersList, + beast::Journal j) +{ + bool changed = false; + for (auto att = attBegin; att != attEnd; ++att) + { + if (checkAttestationPublicKey( + view, + signersList, + att->attestationSignerAccount, + att->publicKey, + j) != tesSUCCESS) + { + // The checkAttestationPublicKey is not strictly necessary here (it + // should be checked in a preclaim step), but it would be bad to let + // this slip through if that changes, and the check is relatively + // cheap, so we check again + continue; + } + + auto const& claimSigningAccount = att->attestationSignerAccount; + if (auto i = std::find_if( + attestations.begin(), + attestations.end(), + [&](auto const& a) { + return a.keyAccount == claimSigningAccount; + }); + i != attestations.end()) + { + // existing attestation + // replace old attestation with new attestation + *i = TAttestation{*att}; + changed = true; + } + else + { + attestations.emplace_back(*att); + changed = true; + } + } + + auto r = claimHelper( + attestations, + view, + typename TAttestation::MatchFields{*attBegin}, + CheckDst::check, + quorum, + signersList, + j); + + if (!r.has_value()) + return {std::nullopt, changed}; + + return {std::move(r.value()), changed}; +}; + +// Check if there is a quorurm of attestations for the given amount and +// chain. If so return the reward accounts, if not return the tec code (most +// likely tecXCHAIN_CLAIM_NO_QUORUM) +Expected, TER> +onClaim( + XChainClaimAttestations& attestations, + ReadView const& view, + STAmount const& sendingAmount, + bool wasLockingChainSend, + std::uint32_t quorum, + std::unordered_map const& signersList, + beast::Journal j) +{ + XChainClaimAttestation::MatchFields toMatch{ + sendingAmount, wasLockingChainSend, std::nullopt}; + return claimHelper( + attestations, view, toMatch, CheckDst::ignore, quorum, signersList, j); +} + +enum class CanCreateDstPolicy { no, yes }; + +enum class DepositAuthPolicy { normal, dstCanBypass }; + +// Allow the fee to dip into the reserve. To support this, information about the +// submitting account needs to be fed to the transfer helper. +struct TransferHelperSubmittingAccountInfo +{ + AccountID account; + STAmount preFeeBalance; + STAmount postFeeBalance; +}; + +/** Transfer funds from the src account to the dst account + + @param psb The payment sandbox. + @param src The source of funds. + @param dst The destination for funds. + @param dstTag Integer destination tag. Used to check if funds should be + transferred to an account with a `RequireDstTag` flag set. + @param claimOwner Owner of the claim ledger object. + @param amt Amount to transfer from the src account to the dst account. + @param canCreate Flag to determine if accounts may be created using this + transfer. + @param depositAuthPolicy Flag to determine if dst can bypass deposit auth if + it is also the claim owner. + @param submittingAccountInfo If the transaction is allowed to dip into the + reserve to pay fees, then this optional will be seated ("commit" + transactions support this, other transactions should not). + @param j Log + + @return tesSUCCESS if payment succeeds, otherwise the error code for the + failure reason. + */ + +TER +transferHelper( + PaymentSandbox& psb, + AccountID const& src, + AccountID const& dst, + std::optional const& dstTag, + std::optional const& claimOwner, + STAmount const& amt, + CanCreateDstPolicy canCreate, + DepositAuthPolicy depositAuthPolicy, + std::optional const& + submittingAccountInfo, + beast::Journal j) +{ + if (dst == src) + return tesSUCCESS; + + auto const dstK = keylet::account(dst); + if (auto sleDst = psb.read(dstK)) + { + // Check dst tag and deposit auth + + if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag) + return tecDST_TAG_NEEDED; + + // If the destination is the claim owner, and this is a claim + // transaction, that's the dst account sending funds to itself. It + // can bypass deposit auth. + bool const canBypassDepositAuth = dst == claimOwner && + depositAuthPolicy == DepositAuthPolicy::dstCanBypass; + + if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) && + !psb.exists(keylet::depositPreauth(dst, src))) + { + return tecNO_PERMISSION; + } + } + else if (!amt.native() || canCreate == CanCreateDstPolicy::no) + { + return tecNO_DST; + } + + if (amt.native()) + { + auto const sleSrc = psb.peek(keylet::account(src)); + assert(sleSrc); + if (!sleSrc) + return tecINTERNAL; + + { + auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); + auto const reserve = psb.fees().accountReserve(ownerCount); + + auto const availableBalance = [&]() -> STAmount { + STAmount const curBal = (*sleSrc)[sfBalance]; + // Checking that account == src and postFeeBalance == curBal is + // not strictly nessisary, but helps protect against future + // changes + if (!submittingAccountInfo || + submittingAccountInfo->account != src || + submittingAccountInfo->postFeeBalance != curBal) + return curBal; + return submittingAccountInfo->preFeeBalance; + }(); + + if (availableBalance < amt + reserve) + { + return tecUNFUNDED_PAYMENT; + } + } + + auto sleDst = psb.peek(dstK); + if (!sleDst) + { + if (canCreate == CanCreateDstPolicy::no) + { + // Already checked, but OK to check again + return tecNO_DST; + } + if (amt < psb.fees().accountReserve(0)) + { + JLOG(j.trace()) << "Insufficient payment to create account."; + return tecNO_DST_INSUF_XRP; + } + + // Create the account. + std::uint32_t const seqno{ + psb.rules().enabled(featureDeletableAccounts) ? psb.seq() : 1}; + + sleDst = std::make_shared(dstK); + sleDst->setAccountID(sfAccount, dst); + sleDst->setFieldU32(sfSequence, seqno); + + psb.insert(sleDst); + } + + (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt; + (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt; + psb.update(sleSrc); + psb.update(sleDst); + + return tesSUCCESS; + } + + auto const result = flow( + psb, + amt, + src, + dst, + STPathSet{}, + /*default path*/ true, + /*partial payment*/ false, + /*owner pays transfer fee*/ true, + /*offer crossing*/ false, + /*limit quality*/ std::nullopt, + /*sendmax*/ std::nullopt, + j); + + if (auto const r = result.result(); + isTesSuccess(r) || isTecClaim(r) || isTerRetry(r)) + return r; + return tecXCHAIN_PAYMENT_FAILED; +} + +/** Action to take when the transfer from the door account to the dst fails + + @note This is useful to prevent a failed "create account" transaction from + blocking subsequent "create account" transactions. +*/ +enum class OnTransferFail { + /** Remove the claim even if the transfer fails */ + removeClaim, + /** Keep the claim if the transfer fails */ + keepClaim +}; + +struct FinalizeClaimHelperResult +{ + /// TER for transfering the payment funds + std::optional mainFundsTer; + // TER for transfering the reward funds + std::optional rewardTer; + // TER for removing the sle (if is sle is to be removed) + std::optional rmSleTer; + + // Helper to check for overall success. If there wasn't overall success the + // individual ters can be used to decide what needs to be done. + bool + isTesSuccess() const + { + return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS && + (!rmSleTer || *rmSleTer == tesSUCCESS); + } + + TER + ter() const + { + if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) && + (!rewardTer || *rewardTer == tesSUCCESS) && + (!rmSleTer || *rmSleTer == tesSUCCESS)) + return tesSUCCESS; + + // if any phase return a tecINTERNAL or a tef, prefer returning those + // codes + if (mainFundsTer && + (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL)) + return *mainFundsTer; + if (rewardTer && + (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL)) + return *rewardTer; + if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL)) + return *rmSleTer; + + // Only after the tecINTERNAL and tef are checked, return the first + // non-success error code. + if (mainFundsTer && mainFundsTer != tesSUCCESS) + return *mainFundsTer; + if (rewardTer && rewardTer != tesSUCCESS) + return *rewardTer; + if (rmSleTer && rmSleTer != tesSUCCESS) + return *rmSleTer; + return tesSUCCESS; + } +}; + +/** Transfer funds from the door account to the dst and distribute rewards + + @param psb The payment sandbox. + @param bridgeSpc Bridge + @param dst The destination for funds. + @param dstTag Integer destination tag. Used to check if funds should be + transferred to an account with a `RequireDstTag` flag set. + @param claimOwner Owner of the claim ledger object. + @param sendingAmount Amount that was committed on the source chain. + @param rewardPoolSrc Source of the funds for the reward pool (claim owner). + @param rewardPool Amount to split among the rewardAccounts. + @param rewardAccounts Account to receive the reward pool. + @param srcChain Chain where the commit event occurred. + @param sleClaimID sle for the claim id (may be NULL or XChainClaimID or + XChainCreateAccountClaimID). Don't read fields that aren't in common + with those two types and always check for NULL. Remove on success (if + not null). Remove on fail if the onTransferFail flag is removeClaim. + @param onTransferFail Flag to determine if the claim is removed on transfer + failure. This is used for create account transactions where claims + are removed so they don't block future txns. + @param j Log + + @return FinalizeClaimHelperResult. See the comments in this struct for what + the fields mean. The individual ters need to be returned instead of + an overall ter because the caller needs this information if the + attestation list changed or not. + */ + +FinalizeClaimHelperResult +finalizeClaimHelper( + PaymentSandbox& outerSb, + STXChainBridge const& bridgeSpec, + AccountID const& dst, + std::optional const& dstTag, + AccountID const& claimOwner, + STAmount const& sendingAmount, + AccountID const& rewardPoolSrc, + STAmount const& rewardPool, + std::vector const& rewardAccounts, + STXChainBridge::ChainType const srcChain, + Keylet const& claimIDKeylet, + OnTransferFail onTransferFail, + DepositAuthPolicy depositAuthPolicy, + beast::Journal j) +{ + FinalizeClaimHelperResult result; + + STXChainBridge::ChainType const dstChain = + STXChainBridge::otherChain(srcChain); + STAmount const thisChainAmount = [&] { + STAmount r = sendingAmount; + r.setIssue(bridgeSpec.issue(dstChain)); + return r; + }(); + auto const& thisDoor = bridgeSpec.door(dstChain); + + { + PaymentSandbox innerSb{&outerSb}; + // If distributing the reward pool fails, the mainFunds transfer should + // be rolled back + // + // If the claimid is removed, the rewards should be distributed + // even if the mainFunds fails. + // + // If OnTransferFail::removeClaim, the claim should be removed even if + // the rewards cannot be distributed. + + // transfer funds to the dst + result.mainFundsTer = transferHelper( + innerSb, + thisDoor, + dst, + dstTag, + claimOwner, + thisChainAmount, + CanCreateDstPolicy::yes, + depositAuthPolicy, + std::nullopt, + j); + + if (!isTesSuccess(*result.mainFundsTer) && + onTransferFail == OnTransferFail::keepClaim) + { + return result; + } + + // handle the reward pool + result.rewardTer = [&]() -> TER { + if (rewardAccounts.empty()) + return tesSUCCESS; + + // distribute the reward pool + // if the transfer failed, distribute the pool for "OnTransferFail" + // cases (the attesters did their job) + STAmount const share = [&] { + STAmount const den{rewardAccounts.size()}; + return divide(rewardPool, den, rewardPool.issue()); + }(); + STAmount distributed = rewardPool.zeroed(); + for (auto const& rewardAccount : rewardAccounts) + { + auto const thTer = transferHelper( + innerSb, + rewardPoolSrc, + rewardAccount, + /*dstTag*/ std::nullopt, + // claim owner is not relevant to distributing rewards + /*claimOwner*/ std::nullopt, + share, + CanCreateDstPolicy::no, + DepositAuthPolicy::normal, + std::nullopt, + j); + + if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL) + return thTer; + + if (isTesSuccess(thTer)) + distributed += share; + + // let txn succeed if error distributing rewards (other than + // inability to pay) + } + + if (distributed > rewardPool) + return tecINTERNAL; + + return tesSUCCESS; + }(); + + if (!isTesSuccess(*result.rewardTer) && + (onTransferFail == OnTransferFail::keepClaim || + *result.rewardTer == tecINTERNAL)) + { + return result; + } + + if (!isTesSuccess(*result.mainFundsTer) || + isTesSuccess(*result.rewardTer)) + { + // Note: if the mainFunds transfer succeeds and the result transfer + // fails, we don't apply the inner sandbox (i.e. the mainTransfer is + // rolled back) + innerSb.apply(outerSb); + } + } + + if (auto const sleClaimID = outerSb.peek(claimIDKeylet)) + { + auto const cidOwner = (*sleClaimID)[sfAccount]; + { + // Remove the claim id + auto const sleOwner = outerSb.peek(keylet::account(cidOwner)); + auto const page = (*sleClaimID)[sfOwnerNode]; + if (!outerSb.dirRemove( + keylet::ownerDir(cidOwner), page, sleClaimID->key(), true)) + { + JLOG(j.fatal()) + << "Unable to delete xchain seq number from owner."; + result.rmSleTer = tefBAD_LEDGER; + return result; + } + + // Remove the claim id from the ledger + outerSb.erase(sleClaimID); + + adjustOwnerCount(outerSb, sleOwner, -1, j); + } + } + + return result; +} + +/** Get signers list corresponding to the account that owns the bridge + + @param view View to read the signer's list from. + @param sleBridge Sle of the bridge. + @param j Log + + @return map of the signer's list (AccountIDs and weights), the quorum, and + error code +*/ +std::tuple, std::uint32_t, TER> +getSignersListAndQuorum( + ReadView const& view, + SLE const& sleBridge, + beast::Journal j) +{ + std::unordered_map r; + std::uint32_t q = std::numeric_limits::max(); + + AccountID const thisDoor = sleBridge[sfAccount]; + auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }(); + + if (!sleDoor) + { + return {r, q, tecINTERNAL}; + } + + auto const sleS = view.read(keylet::signers(sleBridge[sfAccount])); + if (!sleS) + { + return {r, q, tecXCHAIN_NO_SIGNERS_LIST}; + } + q = (*sleS)[sfSignerQuorum]; + + auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger"); + + if (!accountSigners) + { + return {r, q, tecINTERNAL}; + } + + for (auto const& as : *accountSigners) + { + r[as.account] = as.weight; + } + + return {std::move(r), q, tesSUCCESS}; +}; + +template +std::shared_ptr +readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec) +{ + auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr { + if (auto r = getter(bridgeSpec, ct)) + { + if ((*r)[sfXChainBridge] == bridgeSpec) + return r; + } + return nullptr; + }; + if (auto r = tryGet(STXChainBridge::ChainType::locking)) + return r; + return tryGet(STXChainBridge::ChainType::issuing); +} + +std::shared_ptr +peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec) +{ + return readOrpeekBridge( + [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) + -> std::shared_ptr { return v.peek(keylet::bridge(b, ct)); }, + bridgeSpec); +} + +std::shared_ptr +readBridge(ReadView const& v, STXChainBridge const& bridgeSpec) +{ + return readOrpeekBridge( + [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) + -> std::shared_ptr { + return v.read(keylet::bridge(b, ct)); + }, + bridgeSpec); +} + +// Precondition: all the claims in the range are consistent. They must sign for +// the same event (amount, sending account, claim id, etc). +template +TER +applyClaimAttestations( + ApplyView& view, + RawView& rawView, + TIter attBegin, + TIter attEnd, + STXChainBridge const& bridgeSpec, + STXChainBridge::ChainType const srcChain, + std::unordered_map const& signersList, + std::uint32_t quorum, + beast::Journal j) +{ + if (attBegin == attEnd) + return tesSUCCESS; + + PaymentSandbox psb(&view); + + auto const claimIDKeylet = + keylet::xChainClaimID(bridgeSpec, attBegin->claimID); + + struct ScopeResult + { + OnNewAttestationResult newAttResult; + STAmount rewardAmount; + AccountID cidOwner; + }; + + auto const scopeResult = [&]() -> Expected { + // This lambda is ugly - admittedly. The purpose of this lambda is to + // limit the scope of sles so they don't overlap with + // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child + // views, it's important that the sle's lifetime doesn't overlap. + auto const sleClaimID = psb.peek(claimIDKeylet); + if (!sleClaimID) + return Unexpected(tecXCHAIN_NO_CLAIM_ID); + + // Add claims that are part of the signer's list to the "claims" vector + std::vector atts; + atts.reserve(std::distance(attBegin, attEnd)); + for (auto att = attBegin; att != attEnd; ++att) + { + if (!signersList.contains(att->attestationSignerAccount)) + continue; + atts.push_back(*att); + } + + if (atts.empty()) + { + return Unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); + } + + AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource]; + if (attBegin->sendingAccount != otherChainSource) + { + return Unexpected(tecXCHAIN_SENDING_ACCOUNT_MISMATCH); + } + + { + STXChainBridge::ChainType const dstChain = + STXChainBridge::otherChain(srcChain); + + STXChainBridge::ChainType const attDstChain = + STXChainBridge::dstChain(attBegin->wasLockingChainSend); + + if (attDstChain != dstChain) + { + return Unexpected(tecXCHAIN_WRONG_CHAIN); + } + } + + XChainClaimAttestations curAtts{ + sleClaimID->getFieldArray(sfXChainClaimAttestations)}; + + auto const newAttResult = onNewAttestations( + curAtts, + view, + &atts[0], + &atts[0] + atts.size(), + quorum, + signersList, + j); + + // update the claim id + sleClaimID->setFieldArray( + sfXChainClaimAttestations, curAtts.toSTArray()); + psb.update(sleClaimID); + + return ScopeResult{ + newAttResult, + (*sleClaimID)[sfSignatureReward], + (*sleClaimID)[sfAccount]}; + }(); + + if (!scopeResult.has_value()) + return scopeResult.error(); + + auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value(); + auto const& [rewardAccounts, attListChanged] = newAttResult; + if (rewardAccounts && attBegin->dst) + { + auto const r = finalizeClaimHelper( + psb, + bridgeSpec, + *attBegin->dst, + /*dstTag*/ std::nullopt, + cidOwner, + attBegin->sendingAmount, + cidOwner, + rewardAmount, + *rewardAccounts, + srcChain, + claimIDKeylet, + OnTransferFail::keepClaim, + DepositAuthPolicy::normal, + j); + + auto const rTer = r.ter(); + + if (!isTesSuccess(rTer) && + (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER)) + return rTer; + } + + psb.apply(rawView); + + return tesSUCCESS; +} + +template +TER +applyCreateAccountAttestations( + ApplyView& view, + RawView& rawView, + TIter attBegin, + TIter attEnd, + AccountID const& doorAccount, + Keylet const& doorK, + STXChainBridge const& bridgeSpec, + Keylet const& bridgeK, + STXChainBridge::ChainType const srcChain, + std::unordered_map const& signersList, + std::uint32_t quorum, + beast::Journal j) +{ + if (attBegin == attEnd) + return tesSUCCESS; + + PaymentSandbox psb(&view); + + auto const claimCountResult = [&]() -> Expected { + auto const sleBridge = psb.peek(bridgeK); + if (!sleBridge) + return Unexpected(tecINTERNAL); + + return (*sleBridge)[sfXChainAccountClaimCount]; + }(); + + if (!claimCountResult.has_value()) + return claimCountResult.error(); + + std::uint64_t const claimCount = claimCountResult.value(); + + if (attBegin->createCount <= claimCount) + { + return tecXCHAIN_ACCOUNT_CREATE_PAST; + } + if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims) + { + // Limit the number of claims on the account + return tecXCHAIN_ACCOUNT_CREATE_TOO_MANY; + } + + { + STXChainBridge::ChainType const dstChain = + STXChainBridge::otherChain(srcChain); + + STXChainBridge::ChainType const attDstChain = + STXChainBridge::dstChain(attBegin->wasLockingChainSend); + + if (attDstChain != dstChain) + { + return tecXCHAIN_WRONG_CHAIN; + } + } + + auto const claimIDKeylet = + keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount); + + struct ScopeResult + { + OnNewAttestationResult newAttResult; + bool createCID; + XChainCreateAccountAttestations curAtts; + }; + + auto const scopeResult = [&]() -> Expected { + // This lambda is ugly - admittedly. The purpose of this lambda is to + // limit the scope of sles so they don't overlap with + // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child + // views, it's important that the sle's lifetime doesn't overlap. + + // sleClaimID may be null. If it's null it isn't created until the end + // of this function (if needed) + auto const sleClaimID = psb.peek(claimIDKeylet); + bool createCID = false; + if (!sleClaimID) + { + createCID = true; + + auto const sleDoor = psb.peek(doorK); + if (!sleDoor) + return Unexpected(tecINTERNAL); + + // Check reserve + auto const balance = (*sleDoor)[sfBalance]; + auto const reserve = + psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); + + if (balance < reserve) + return Unexpected(tecINSUFFICIENT_RESERVE); + } + + std::vector atts; + atts.reserve(std::distance(attBegin, attEnd)); + for (auto att = attBegin; att != attEnd; ++att) + { + if (!signersList.contains(att->attestationSignerAccount)) + continue; + atts.push_back(*att); + } + if (atts.empty()) + { + return Unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); + } + + XChainCreateAccountAttestations curAtts = [&] { + if (sleClaimID) + return XChainCreateAccountAttestations{ + sleClaimID->getFieldArray( + sfXChainCreateAccountAttestations)}; + return XChainCreateAccountAttestations{}; + }(); + + auto const newAttResult = onNewAttestations( + curAtts, + view, + &atts[0], + &atts[0] + atts.size(), + quorum, + signersList, + j); + + if (!createCID) + { + // Modify the object before it's potentially deleted, so the meta + // data will include the new attestations + if (!sleClaimID) + return Unexpected(tecINTERNAL); + sleClaimID->setFieldArray( + sfXChainCreateAccountAttestations, curAtts.toSTArray()); + psb.update(sleClaimID); + } + return ScopeResult{newAttResult, createCID, curAtts}; + }(); + + if (!scopeResult.has_value()) + return scopeResult.error(); + + auto const& [attResult, createCID, curAtts] = scopeResult.value(); + auto const& [rewardAccounts, attListChanged] = attResult; + + // Account create transactions must happen in order + if (rewardAccounts && claimCount + 1 == attBegin->createCount) + { + auto const r = finalizeClaimHelper( + psb, + bridgeSpec, + attBegin->toCreate, + /*dstTag*/ std::nullopt, + doorAccount, + attBegin->sendingAmount, + /*rewardPoolSrc*/ doorAccount, + attBegin->rewardAmount, + *rewardAccounts, + srcChain, + claimIDKeylet, + OnTransferFail::removeClaim, + DepositAuthPolicy::normal, + j); + + auto const rTer = r.ter(); + + if (!isTesSuccess(rTer)) + { + if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT || + isTefFailure(rTer)) + return rTer; + } + // Move past this claim id even if it fails, so it doesn't block + // subsequent claim ids + auto const sleBridge = psb.peek(bridgeK); + if (!sleBridge) + return tecINTERNAL; + (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount; + psb.update(sleBridge); + } + else if (createCID) + { + auto const createdSleClaimID = std::make_shared(claimIDKeylet); + (*createdSleClaimID)[sfAccount] = doorAccount; + (*createdSleClaimID)[sfXChainBridge] = bridgeSpec; + (*createdSleClaimID)[sfXChainAccountCreateCount] = + attBegin->createCount; + createdSleClaimID->setFieldArray( + sfXChainCreateAccountAttestations, curAtts.toSTArray()); + + // Add to owner directory of the door account + auto const page = psb.dirInsert( + keylet::ownerDir(doorAccount), + claimIDKeylet, + describeOwnerDir(doorAccount)); + if (!page) + return tecDIR_FULL; + (*createdSleClaimID)[sfOwnerNode] = *page; + + auto const sleDoor = psb.peek(doorK); + if (!sleDoor) + return tecINTERNAL; + + // Reserve was already checked + adjustOwnerCount(psb, sleDoor, 1, j); + psb.insert(createdSleClaimID); + psb.update(sleDoor); + } + + psb.apply(rawView); + + return tesSUCCESS; +} + +template +std::optional +toClaim(STTx const& tx) +{ + static_assert( + std::is_same_v || + std::is_same_v); + + try + { + STObject o{tx}; + o.setAccountID(sfAccount, o[sfOtherChainSource]); + return TAttestation(o); + } + catch (...) + { + } + return std::nullopt; +} + +template +NotTEC +attestationPreflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + auto const att = toClaim(ctx.tx); + if (!att) + return temMALFORMED; + + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + if (!att->verify(bridgeSpec)) + return temXCHAIN_BAD_PROOF; + if (!att->validAmounts()) + return temXCHAIN_BAD_PROOF; + + if (att->sendingAmount.signum() <= 0) + return temXCHAIN_BAD_PROOF; + auto const expectedIssue = + bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend)); + if (att->sendingAmount.issue() != expectedIssue) + return temXCHAIN_BAD_PROOF; + + return preflight2(ctx); +} + +template +TER +attestationPreclaim(PreclaimContext const& ctx) +{ + auto const att = toClaim(ctx.tx); + if (!att) + return tecINTERNAL; // checked in preflight + + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + auto const sleBridge = readBridge(ctx.view, bridgeSpec); + if (!sleBridge) + { + return tecNO_ENTRY; + } + + AccountID const attestationSignerAccount{ + ctx.tx[sfAttestationSignerAccount]}; + PublicKey const pk{ctx.tx[sfPublicKey]}; + + // signersList is a map from account id to weights + auto const [signersList, quorum, slTer] = + getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j); + + if (!isTesSuccess(slTer)) + return slTer; + + return checkAttestationPublicKey( + ctx.view, signersList, attestationSignerAccount, pk, ctx.j); +} + +template +TER +attestationDoApply(ApplyContext& ctx) +{ + auto const att = toClaim(ctx.tx); + if (!att) + // Should already be checked in preflight + return tecINTERNAL; + + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + + struct ScopeResult + { + STXChainBridge::ChainType srcChain; + std::unordered_map signersList; + std::uint32_t quorum; + AccountID thisDoor; + Keylet bridgeK; + }; + + auto const scopeResult = [&]() -> Expected { + // This lambda is ugly - admittedly. The purpose of this lambda is to + // limit the scope of sles so they don't overlap with + // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child + // views, it's important that the sle's lifetime doesn't overlap. + auto sleBridge = readBridge(ctx.view(), bridgeSpec); + if (!sleBridge) + { + return Unexpected(tecNO_ENTRY); + } + Keylet const bridgeK{ltBRIDGE, sleBridge->key()}; + AccountID const thisDoor = (*sleBridge)[sfAccount]; + + STXChainBridge::ChainType dstChain = STXChainBridge::ChainType::locking; + { + if (thisDoor == bridgeSpec.lockingChainDoor()) + dstChain = STXChainBridge::ChainType::locking; + else if (thisDoor == bridgeSpec.issuingChainDoor()) + dstChain = STXChainBridge::ChainType::issuing; + else + return Unexpected(tecINTERNAL); + } + STXChainBridge::ChainType const srcChain = + STXChainBridge::otherChain(dstChain); + + // signersList is a map from account id to weights + auto [signersList, quorum, slTer] = + getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal); + + if (!isTesSuccess(slTer)) + return Unexpected(slTer); + + return ScopeResult{ + srcChain, std::move(signersList), quorum, thisDoor, bridgeK}; + }(); + + if (!scopeResult.has_value()) + return scopeResult.error(); + + auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] = + scopeResult.value(); + + static_assert( + std::is_same_v || + std::is_same_v); + + if constexpr (std::is_same_v) + { + return applyClaimAttestations( + ctx.view(), + ctx.rawView(), + &*att, + &*att + 1, + bridgeSpec, + srcChain, + signersList, + quorum, + ctx.journal); + } + else if constexpr (std::is_same_v< + TAttestation, + Attestations::AttestationCreateAccount>) + { + return applyCreateAccountAttestations( + ctx.view(), + ctx.rawView(), + &*att, + &*att + 1, + thisDoor, + keylet::account(thisDoor), + bridgeSpec, + bridgeK, + srcChain, + signersList, + quorum, + ctx.journal); + } +} + +} // namespace +//------------------------------------------------------------------------------ + +NotTEC +XChainCreateBridge::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + auto const account = ctx.tx[sfAccount]; + auto const reward = ctx.tx[sfSignatureReward]; + auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + // Doors must be distinct to help prevent transaction replay attacks + if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor()) + { + return temXCHAIN_EQUAL_DOOR_ACCOUNTS; + } + + if (bridgeSpec.lockingChainDoor() != account && + bridgeSpec.issuingChainDoor() != account) + { + return temXCHAIN_BRIDGE_NONDOOR_OWNER; + } + + if (isXRP(bridgeSpec.lockingChainIssue()) != + isXRP(bridgeSpec.issuingChainIssue())) + { + // Because ious and xrp have different numeric ranges, both the src and + // dst issues must be both XRP or both IOU. + return temXCHAIN_BRIDGE_BAD_ISSUES; + } + + if (!isXRP(reward) || reward.signum() < 0) + { + return temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT; + } + + if (minAccountCreate && + ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) || + !isXRP(bridgeSpec.lockingChainIssue()) || + !isXRP(bridgeSpec.issuingChainIssue()))) + { + return temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT; + } + + if (isXRP(bridgeSpec.issuingChainIssue())) + { + // Issuing account must be the root account for XRP (which presumably + // owns all the XRP). This is done so the issuing account can't "run + // out" of wrapped tokens. + static auto const rootAccount = calcAccountID( + generateKeyPair( + KeyType::secp256k1, generateSeed("masterpassphrase")) + .first); + if (bridgeSpec.issuingChainDoor() != rootAccount) + { + return temXCHAIN_BRIDGE_BAD_ISSUES; + } + } + else + { + // Issuing account must be the issuer for non-XRP. This is done so the + // issuing account can't "run out" of wrapped tokens. + if (bridgeSpec.issuingChainDoor() != + bridgeSpec.issuingChainIssue().account) + { + return temXCHAIN_BRIDGE_BAD_ISSUES; + } + } + + if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account) + { + // If the locking chain door is locking their own asset, in some sense + // nothing is being locked. Disallow this. + return temXCHAIN_BRIDGE_BAD_ISSUES; + } + + return preflight2(ctx); +} + +TER +XChainCreateBridge::preclaim(PreclaimContext const& ctx) +{ + auto const account = ctx.tx[sfAccount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor()); + + { + auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool { + return ctx.view.exists(keylet::bridge(bridgeSpec, ct)); + }; + + if (hasBridge(STXChainBridge::ChainType::issuing) || + hasBridge(STXChainBridge::ChainType::locking)) + { + return tecDUPLICATE; + } + } + + if (!isXRP(bridgeSpec.issue(chainType))) + { + auto const sleIssuer = + ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account)); + + if (!sleIssuer) + return tecNO_ISSUER; + + // Allowing clawing back funds would break the bridge's invariant that + // wrapped funds are always backed by locked funds + if (sleIssuer->getFlags() & lsfAllowTrustLineClawback) + return tecNO_PERMISSION; + } + + { + // Check reserve + auto const sleAcc = ctx.view.read(keylet::account(account)); + if (!sleAcc) + return terNO_ACCOUNT; + + auto const balance = (*sleAcc)[sfBalance]; + auto const reserve = + ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); + + if (balance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + return tesSUCCESS; +} + +TER +XChainCreateBridge::doApply() +{ + auto const account = ctx_.tx[sfAccount]; + auto const bridgeSpec = ctx_.tx[sfXChainBridge]; + auto const reward = ctx_.tx[sfSignatureReward]; + auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount]; + + auto const sleAcct = ctx_.view().peek(keylet::account(account)); + if (!sleAcct) + return tecINTERNAL; + + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor()); + + Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType); + auto const sleBridge = std::make_shared(bridgeKeylet); + + (*sleBridge)[sfAccount] = account; + (*sleBridge)[sfSignatureReward] = reward; + if (minAccountCreate) + (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate; + (*sleBridge)[sfXChainBridge] = bridgeSpec; + (*sleBridge)[sfXChainClaimID] = 0; + (*sleBridge)[sfXChainAccountCreateCount] = 0; + (*sleBridge)[sfXChainAccountClaimCount] = 0; + + // Add to owner directory + { + auto const page = ctx_.view().dirInsert( + keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account)); + if (!page) + return tecDIR_FULL; + (*sleBridge)[sfOwnerNode] = *page; + } + + adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + + ctx_.view().insert(sleBridge); + ctx_.view().update(sleAcct); + + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +NotTEC +BridgeModify::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfBridgeModifyMask) + return temINVALID_FLAG; + + auto const account = ctx.tx[sfAccount]; + auto const reward = ctx.tx[~sfSignatureReward]; + auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + bool const clearAccountCreate = + ctx.tx.getFlags() & tfClearAccountCreateAmount; + + if (!reward && !minAccountCreate && !clearAccountCreate) + { + // Must change something + return temMALFORMED; + } + + if (minAccountCreate && clearAccountCreate) + { + // Can't both clear and set account create in the same txn + return temMALFORMED; + } + + if (bridgeSpec.lockingChainDoor() != account && + bridgeSpec.issuingChainDoor() != account) + { + return temXCHAIN_BRIDGE_NONDOOR_OWNER; + } + + if (reward && (!isXRP(*reward) || reward->signum() < 0)) + { + return temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT; + } + + if (minAccountCreate && + ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) || + !isXRP(bridgeSpec.lockingChainIssue()) || + !isXRP(bridgeSpec.issuingChainIssue()))) + { + return temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT; + } + + return preflight2(ctx); +} + +TER +BridgeModify::preclaim(PreclaimContext const& ctx) +{ + auto const account = ctx.tx[sfAccount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor()); + + if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType))) + { + return tecNO_ENTRY; + } + + return tesSUCCESS; +} + +TER +BridgeModify::doApply() +{ + auto const account = ctx_.tx[sfAccount]; + auto const bridgeSpec = ctx_.tx[sfXChainBridge]; + auto const reward = ctx_.tx[~sfSignatureReward]; + auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount]; + bool const clearAccountCreate = + ctx_.tx.getFlags() & tfClearAccountCreateAmount; + + auto const sleAcct = ctx_.view().peek(keylet::account(account)); + if (!sleAcct) + return tecINTERNAL; + + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor()); + + auto const sleBridge = + ctx_.view().peek(keylet::bridge(bridgeSpec, chainType)); + if (!sleBridge) + return tecINTERNAL; + + if (reward) + (*sleBridge)[sfSignatureReward] = *reward; + if (minAccountCreate) + { + (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate; + } + if (clearAccountCreate && + sleBridge->isFieldPresent(sfMinAccountCreateAmount)) + { + sleBridge->makeFieldAbsent(sfMinAccountCreateAmount); + } + ctx_.view().update(sleBridge); + + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +NotTEC +XChainClaim::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + auto const amount = ctx.tx[sfAmount]; + + if (amount.signum() <= 0 || + (amount.issue() != bridgeSpec.lockingChainIssue() && + amount.issue() != bridgeSpec.issuingChainIssue())) + { + return temBAD_AMOUNT; + } + + return preflight2(ctx); +} + +TER +XChainClaim::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx[sfAccount]; + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + STAmount const& thisChainAmount = ctx.tx[sfAmount]; + auto const claimID = ctx.tx[sfXChainClaimID]; + + auto const sleBridge = readBridge(ctx.view, bridgeSpec); + if (!sleBridge) + { + return tecNO_ENTRY; + } + + if (!ctx.view.read(keylet::account(ctx.tx[sfDestination]))) + { + return tecNO_DST; + } + + auto const thisDoor = (*sleBridge)[sfAccount]; + bool isLockingChain = false; + { + if (thisDoor == bridgeSpec.lockingChainDoor()) + isLockingChain = true; + else if (thisDoor == bridgeSpec.issuingChainDoor()) + isLockingChain = false; + else + return tecINTERNAL; + } + + { + // Check that the amount specified matches the expected issue + + if (isLockingChain) + { + if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + } + else + { + if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + } + } + + if (isXRP(bridgeSpec.lockingChainIssue()) != + isXRP(bridgeSpec.issuingChainIssue())) + { + // Should have been caught when creating the bridge + // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP + // and the numeric issues that need to be addressed with that. + return tecINTERNAL; + } + + auto const otherChainAmount = [&]() -> STAmount { + STAmount r(thisChainAmount); + if (isLockingChain) + r.setIssue(bridgeSpec.issuingChainIssue()); + else + r.setIssue(bridgeSpec.lockingChainIssue()); + return r; + }(); + + auto const sleClaimID = + ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID)); + { + // Check that the sequence number is owned by the sender of this + // transaction + if (!sleClaimID) + { + return tecXCHAIN_NO_CLAIM_ID; + } + + if ((*sleClaimID)[sfAccount] != account) + { + // Sequence number isn't owned by the sender of this transaction + return tecXCHAIN_BAD_CLAIM_ID; + } + } + + // quorum is checked in `doApply` + return tesSUCCESS; +} + +TER +XChainClaim::doApply() +{ + PaymentSandbox psb(&ctx_.view()); + + AccountID const account = ctx_.tx[sfAccount]; + auto const dst = ctx_.tx[sfDestination]; + STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge]; + STAmount const& thisChainAmount = ctx_.tx[sfAmount]; + auto const claimID = ctx_.tx[sfXChainClaimID]; + auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID); + + struct ScopeResult + { + std::vector rewardAccounts; + AccountID rewardPoolSrc; + STAmount sendingAmount; + STXChainBridge::ChainType srcChain; + STAmount signatureReward; + }; + + auto const scopeResult = [&]() -> Expected { + // This lambda is ugly - admittedly. The purpose of this lambda is to + // limit the scope of sles so they don't overlap with + // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child + // views, it's important that the sle's lifetime doesn't overlap. + + auto const sleAcct = psb.peek(keylet::account(account)); + auto const sleBridge = peekBridge(psb, bridgeSpec); + auto const sleClaimID = psb.peek(claimIDKeylet); + + if (!(sleBridge && sleClaimID && sleAcct)) + return Unexpected(tecINTERNAL); + + AccountID const thisDoor = (*sleBridge)[sfAccount]; + + STXChainBridge::ChainType dstChain = STXChainBridge::ChainType::locking; + { + if (thisDoor == bridgeSpec.lockingChainDoor()) + dstChain = STXChainBridge::ChainType::locking; + else if (thisDoor == bridgeSpec.issuingChainDoor()) + dstChain = STXChainBridge::ChainType::issuing; + else + return Unexpected(tecINTERNAL); + } + STXChainBridge::ChainType const srcChain = + STXChainBridge::otherChain(dstChain); + + auto const sendingAmount = [&]() -> STAmount { + STAmount r(thisChainAmount); + r.setIssue(bridgeSpec.issue(srcChain)); + return r; + }(); + + auto const [signersList, quorum, slTer] = + getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal); + + if (!isTesSuccess(slTer)) + return Unexpected(slTer); + + XChainClaimAttestations curAtts{ + sleClaimID->getFieldArray(sfXChainClaimAttestations)}; + + auto const claimR = onClaim( + curAtts, + psb, + sendingAmount, + /*wasLockingChainSend*/ srcChain == + STXChainBridge::ChainType::locking, + quorum, + signersList, + ctx_.journal); + if (!claimR.has_value()) + return Unexpected(claimR.error()); + + return ScopeResult{ + claimR.value(), + (*sleClaimID)[sfAccount], + sendingAmount, + srcChain, + (*sleClaimID)[sfSignatureReward], + }; + }(); + + if (!scopeResult.has_value()) + return scopeResult.error(); + + auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] = + scopeResult.value(); + std::optional const dstTag = ctx_.tx[~sfDestinationTag]; + + auto const r = finalizeClaimHelper( + psb, + bridgeSpec, + dst, + dstTag, + /*claimOwner*/ account, + sendingAmount, + rewardPoolSrc, + signatureReward, + rewardAccounts, + srcChain, + claimIDKeylet, + OnTransferFail::keepClaim, + DepositAuthPolicy::dstCanBypass, + ctx_.journal); + if (!r.isTesSuccess()) + return r.ter(); + + psb.apply(ctx_.rawView()); + + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +TxConsequences +XChainCommit::makeTxConsequences(PreflightContext const& ctx) +{ + auto const maxSpend = [&] { + auto const amount = ctx.tx[sfAmount]; + if (amount.native() && amount.signum() > 0) + return amount.xrp(); + return XRPAmount{beast::zero}; + }(); + + return TxConsequences{ctx.tx, maxSpend}; +} + +NotTEC +XChainCommit::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + auto const amount = ctx.tx[sfAmount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + + if (amount.signum() <= 0 || !isLegalNet(amount)) + return temBAD_AMOUNT; + + if (amount.issue() != bridgeSpec.lockingChainIssue() && + amount.issue() != bridgeSpec.issuingChainIssue()) + return temBAD_ISSUER; + + return preflight2(ctx); +} + +TER +XChainCommit::preclaim(PreclaimContext const& ctx) +{ + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + auto const amount = ctx.tx[sfAmount]; + + auto const sleBridge = readBridge(ctx.view, bridgeSpec); + if (!sleBridge) + { + return tecNO_ENTRY; + } + + AccountID const thisDoor = (*sleBridge)[sfAccount]; + AccountID const account = ctx.tx[sfAccount]; + + if (thisDoor == account) + { + // Door account can't lock funds onto itself + return tecXCHAIN_SELF_COMMIT; + } + + bool isLockingChain = false; + { + if (thisDoor == bridgeSpec.lockingChainDoor()) + isLockingChain = true; + else if (thisDoor == bridgeSpec.issuingChainDoor()) + isLockingChain = false; + else + return tecINTERNAL; + } + + if (isLockingChain) + { + if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + } + else + { + if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + } + + return tesSUCCESS; +} + +TER +XChainCommit::doApply() +{ + PaymentSandbox psb(&ctx_.view()); + + auto const account = ctx_.tx[sfAccount]; + auto const amount = ctx_.tx[sfAmount]; + auto const bridgeSpec = ctx_.tx[sfXChainBridge]; + + if (!psb.read(keylet::account(account))) + return tecINTERNAL; + + auto const sleBridge = readBridge(psb, bridgeSpec); + if (!sleBridge) + return tecINTERNAL; + + auto const dst = (*sleBridge)[sfAccount]; + + // Support dipping into reserves to pay the fee + TransferHelperSubmittingAccountInfo submittingAccountInfo{ + account_, mPriorBalance, mSourceBalance}; + + auto const thTer = transferHelper( + psb, + account, + dst, + /*dstTag*/ std::nullopt, + /*claimOwner*/ std::nullopt, + amount, + CanCreateDstPolicy::no, + DepositAuthPolicy::normal, + submittingAccountInfo, + ctx_.journal); + + if (!isTesSuccess(thTer)) + return thTer; + + psb.apply(ctx_.rawView()); + + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +NotTEC +XChainCreateClaimID::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + auto const reward = ctx.tx[sfSignatureReward]; + + if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward)) + return temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT; + + return preflight2(ctx); +} + +TER +XChainCreateClaimID::preclaim(PreclaimContext const& ctx) +{ + auto const account = ctx.tx[sfAccount]; + auto const bridgeSpec = ctx.tx[sfXChainBridge]; + auto const sleBridge = readBridge(ctx.view, bridgeSpec); + + if (!sleBridge) + { + return tecNO_ENTRY; + } + + // Check that the reward matches + auto const reward = ctx.tx[sfSignatureReward]; + + if (reward != (*sleBridge)[sfSignatureReward]) + { + return tecXCHAIN_REWARD_MISMATCH; + } + + { + // Check reserve + auto const sleAcc = ctx.view.read(keylet::account(account)); + if (!sleAcc) + return terNO_ACCOUNT; + + auto const balance = (*sleAcc)[sfBalance]; + auto const reserve = + ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); + + if (balance < reserve) + return tecINSUFFICIENT_RESERVE; + } + + return tesSUCCESS; +} + +TER +XChainCreateClaimID::doApply() +{ + auto const account = ctx_.tx[sfAccount]; + auto const bridgeSpec = ctx_.tx[sfXChainBridge]; + auto const reward = ctx_.tx[sfSignatureReward]; + auto const otherChainSrc = ctx_.tx[sfOtherChainSource]; + + auto const sleAcct = ctx_.view().peek(keylet::account(account)); + if (!sleAcct) + return tecINTERNAL; + + auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec); + if (!sleBridge) + return tecINTERNAL; + + std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1; + if (claimID == 0) + return tecINTERNAL; // overflow + + (*sleBridge)[sfXChainClaimID] = claimID; + + Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID); + if (ctx_.view().exists(claimIDKeylet)) + return tecINTERNAL; // already checked out!?! + + auto const sleClaimID = std::make_shared(claimIDKeylet); + + (*sleClaimID)[sfAccount] = account; + (*sleClaimID)[sfXChainBridge] = bridgeSpec; + (*sleClaimID)[sfXChainClaimID] = claimID; + (*sleClaimID)[sfOtherChainSource] = otherChainSrc; + (*sleClaimID)[sfSignatureReward] = reward; + sleClaimID->setFieldArray( + sfXChainClaimAttestations, STArray{sfXChainClaimAttestations}); + + // Add to owner directory + { + auto const page = ctx_.view().dirInsert( + keylet::ownerDir(account), + claimIDKeylet, + describeOwnerDir(account)); + if (!page) + return tecDIR_FULL; + (*sleClaimID)[sfOwnerNode] = *page; + } + + adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + + ctx_.view().insert(sleClaimID); + ctx_.view().update(sleBridge); + ctx_.view().update(sleAcct); + + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +NotTEC +XChainAddClaimAttestation::preflight(PreflightContext const& ctx) +{ + return attestationPreflight(ctx); +} + +TER +XChainAddClaimAttestation::preclaim(PreclaimContext const& ctx) +{ + return attestationPreclaim(ctx); +} + +TER +XChainAddClaimAttestation::doApply() +{ + return attestationDoApply(ctx_); +} + +//------------------------------------------------------------------------------ + +NotTEC +XChainAddAccountCreateAttestation::preflight(PreflightContext const& ctx) +{ + return attestationPreflight(ctx); +} + +TER +XChainAddAccountCreateAttestation::preclaim(PreclaimContext const& ctx) +{ + return attestationPreclaim(ctx); +} + +TER +XChainAddAccountCreateAttestation::doApply() +{ + return attestationDoApply(ctx_); +} + +//------------------------------------------------------------------------------ + +NotTEC +XChainCreateAccountCommit::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureXChainBridge)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfUniversalMask) + return temINVALID_FLAG; + + auto const amount = ctx.tx[sfAmount]; + + if (amount.signum() <= 0 || !amount.native()) + return temBAD_AMOUNT; + + auto const reward = ctx.tx[sfSignatureReward]; + if (reward.signum() < 0 || !reward.native()) + return temBAD_AMOUNT; + + if (reward.issue() != amount.issue()) + return temBAD_AMOUNT; + + return preflight2(ctx); +} + +TER +XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx) +{ + STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; + STAmount const amount = ctx.tx[sfAmount]; + STAmount const reward = ctx.tx[sfSignatureReward]; + + auto const sleBridge = readBridge(ctx.view, bridgeSpec); + if (!sleBridge) + { + return tecNO_ENTRY; + } + + if (reward != (*sleBridge)[sfSignatureReward]) + { + return tecXCHAIN_REWARD_MISMATCH; + } + + std::optional const minCreateAmount = + (*sleBridge)[~sfMinAccountCreateAmount]; + + if (!minCreateAmount) + return tecXCHAIN_CREATE_ACCOUNT_DISABLED; + + if (amount < *minCreateAmount) + return tecXCHAIN_INSUFF_CREATE_AMOUNT; + + if (minCreateAmount->issue() != amount.issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + + AccountID const thisDoor = (*sleBridge)[sfAccount]; + AccountID const account = ctx.tx[sfAccount]; + if (thisDoor == account) + { + // Door account can't lock funds onto itself + return tecXCHAIN_SELF_COMMIT; + } + + STXChainBridge::ChainType srcChain = STXChainBridge::ChainType::locking; + { + if (thisDoor == bridgeSpec.lockingChainDoor()) + srcChain = STXChainBridge::ChainType::locking; + else if (thisDoor == bridgeSpec.issuingChainDoor()) + srcChain = STXChainBridge::ChainType::issuing; + else + return tecINTERNAL; + } + STXChainBridge::ChainType const dstChain = + STXChainBridge::otherChain(srcChain); + + if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue()) + return tecXCHAIN_BAD_TRANSFER_ISSUE; + + if (!isXRP(bridgeSpec.issue(dstChain))) + return tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE; + + return tesSUCCESS; +} + +TER +XChainCreateAccountCommit::doApply() +{ + PaymentSandbox psb(&ctx_.view()); + + AccountID const account = ctx_.tx[sfAccount]; + STAmount const amount = ctx_.tx[sfAmount]; + STAmount const reward = ctx_.tx[sfSignatureReward]; + STXChainBridge const bridge = ctx_.tx[sfXChainBridge]; + + auto const sle = psb.peek(keylet::account(account)); + if (!sle) + return tecINTERNAL; + + auto const sleBridge = peekBridge(psb, bridge); + if (!sleBridge) + return tecINTERNAL; + + auto const dst = (*sleBridge)[sfAccount]; + + // Support dipping into reserves to pay the fee + TransferHelperSubmittingAccountInfo submittingAccountInfo{ + account_, mPriorBalance, mSourceBalance}; + STAmount const toTransfer = amount + reward; + auto const thTer = transferHelper( + psb, + account, + dst, + /*dstTag*/ std::nullopt, + /*claimOwner*/ std::nullopt, + toTransfer, + CanCreateDstPolicy::yes, + DepositAuthPolicy::normal, + submittingAccountInfo, + ctx_.journal); + + if (!isTesSuccess(thTer)) + return thTer; + + (*sleBridge)[sfXChainAccountCreateCount] = + (*sleBridge)[sfXChainAccountCreateCount] + 1; + psb.update(sleBridge); + + psb.apply(ctx_.rawView()); + + return tesSUCCESS; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/XChainBridge.h b/src/ripple/app/tx/impl/XChainBridge.h new file mode 100644 index 00000000000..4d41c7d1c21 --- /dev/null +++ b/src/ripple/app/tx/impl/XChainBridge.h @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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_XCHAINBRIDGE_H_INCLUDED +#define RIPPLE_TX_XCHAINBRIDGE_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +constexpr size_t xbridgeMaxAccountCreateClaims = 128; + +// Attach a new bridge to a door account. Once this is done, the cross-chain +// transfer transactions may be used to transfer funds from this account. +class XChainCreateBridge : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit XChainCreateBridge(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +class BridgeModify : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit BridgeModify(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; +//------------------------------------------------------------------------------ + +// Claim funds from a `XChainCommit` transaction. This is normally not needed, +// but may be used to handle transaction failures or if the destination account +// was not specified in the `XChainCommit` transaction. It may only be used +// after a quorum of signatures have been sent from the witness servers. +// +// If the transaction succeeds in moving funds, the referenced `XChainClaimID` +// ledger object will be destroyed. This prevents transaction replay. If the +// transaction fails, the `XChainClaimID` will not be destroyed and the +// transaction may be re-run with different parameters. +class XChainClaim : public Transactor +{ +public: + // Blocker since we cannot accurately calculate the consequences + static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker}; + + explicit XChainClaim(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +// Put assets into trust on the locking-chain so they may be wrapped on the +// issuing-chain, or return wrapped assets on the issuing-chain so they can be +// unlocked on the locking-chain. The second step in a cross-chain transfer. +class XChainCommit : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + explicit XChainCommit(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +// Create a new claim id owned by the account. This is the first step in a +// cross-chain transfer. The claim id must be created on the destination chain +// before the `XChainCommit` transaction (which must reference this number) can +// be sent on the source chain. The account that will send the `XChainCommit` on +// the source chain must be specified in this transaction (see note on the +// `SourceAccount` field in the `XChainClaimID` ledger object for +// justification). The actual sequence number must be retrieved from a validated +// ledger. +class XChainCreateClaimID : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit XChainCreateClaimID(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +// Provide attestations from a witness server attesting to events on +// the other chain. The signatures must be from one of the keys on the door's +// signer's list at the time the signature was provided. However, if the +// signature list changes between the time the signature was submitted and the +// quorum is reached, the new signature set is used and some of the currently +// collected signatures may be removed. Also note the reward is only sent to +// accounts that have keys on the current list. +class XChainAddClaimAttestation : public Transactor +{ +public: + // Blocker since we cannot accurately calculate the consequences + static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker}; + + explicit XChainAddClaimAttestation(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +class XChainAddAccountCreateAttestation : public Transactor +{ +public: + // Blocker since we cannot accurately calculate the consequences + static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker}; + + explicit XChainAddAccountCreateAttestation(ApplyContext& ctx) + : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +//------------------------------------------------------------------------------ + +// This is a special transaction used for creating accounts through a +// cross-chain transfer. A normal cross-chain transfer requires a "chain claim +// id" (which requires an existing account on the destination chain). One +// purpose of the "chain claim id" is to prevent transaction replay. For this +// transaction, we use a different mechanism: the accounts must be claimed on +// the destination chain in the same order that the `XChainCreateAccountCommit` +// transactions occurred on the source chain. +// +// This transaction can only be used for XRP to XRP bridges. +// +// IMPORTANT: This transaction should only be enabled if the witness +// attestations will be reliably delivered to the destination chain. If the +// signatures are not delivered (for example, the chain relies on user wallets +// to collect signatures) then account creation would be blocked for all +// transactions that happened after the one waiting on attestations. This could +// be used maliciously. To disable this transaction on XRP to XRP bridges, the +// bridge's `MinAccountCreateAmount` should not be present. +// +// Note: If this account already exists, the XRP is transferred to the existing +// account. However, note that unlike the `XChainCommit` transaction, there is +// no error handling mechanism. If the claim transaction fails, there is no +// mechanism for refunds. The funds are permanently lost. This transaction +// should still only be used for account creation. +class XChainCreateAccountCommit : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit XChainCreateAccountCommit(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/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index fdb84c271a0..4c882f3fb8a 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -47,322 +47,243 @@ #include #include #include +#include +#include + +#include namespace ripple { -// Templates so preflight does the right thing with T::ConsequencesFactory. -// -// This could be done more easily using if constexpr, but Visual Studio -// 2017 doesn't handle if constexpr correctly. So once we're no longer -// building with Visual Studio 2017 we can consider replacing the four -// templates with a single template function that uses if constexpr. -// -// For Transactor::Normal -template < - class T, - std::enable_if_t = 0> -TxConsequences -consequences_helper(PreflightContext const& ctx) -{ - return TxConsequences(ctx.tx); -}; +namespace { -// For Transactor::Blocker -template < - class T, - std::enable_if_t = 0> -TxConsequences -consequences_helper(PreflightContext const& ctx) +struct UnknownTxnType : std::exception { - return TxConsequences(ctx.tx, TxConsequences::blocker); + TxType txnType; + UnknownTxnType(TxType t) : txnType{t} + { + } }; -// For Transactor::Custom -template < - class T, - std::enable_if_t = 0> -TxConsequences -consequences_helper(PreflightContext const& ctx) +// Call a lambda with the concrete transaction type as a template parameter +// throw an "UnknownTxnType" exception on error +template +auto +with_txn_type(TxType txnType, F&& f) { - return T::makeTxConsequences(ctx); -}; - -template -std::pair -invoke_preflight_helper(PreflightContext const& ctx) -{ - auto const tec = T::preflight(ctx); - return { - tec, - isTesSuccess(tec) ? consequences_helper(ctx) : TxConsequences{tec}}; -} - -static std::pair -invoke_preflight(PreflightContext const& ctx) -{ - switch (ctx.tx.getTxnType()) + switch (txnType) { case ttACCOUNT_DELETE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttACCOUNT_SET: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttCHECK_CANCEL: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttCHECK_CASH: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttCHECK_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttDEPOSIT_PREAUTH: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttOFFER_CANCEL: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttOFFER_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttESCROW_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttESCROW_FINISH: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttESCROW_CANCEL: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttPAYCHAN_CLAIM: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttPAYCHAN_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttPAYCHAN_FUND: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttPAYMENT: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttREGULAR_KEY_SET: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttSIGNER_LIST_SET: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttTICKET_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttTRUST_SET: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMENDMENT: case ttFEE: case ttUNL_MODIFY: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttNFTOKEN_MINT: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttNFTOKEN_BURN: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttNFTOKEN_CREATE_OFFER: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttNFTOKEN_CANCEL_OFFER: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttNFTOKEN_ACCEPT_OFFER: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttCLAWBACK: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_CREATE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_DEPOSIT: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_WITHDRAW: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_VOTE: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_BID: - return invoke_preflight_helper(ctx); + return f.template operator()(); case ttAMM_DELETE: - return invoke_preflight_helper(ctx); + 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()(); default: - assert(false); - return {temUNKNOWN, TxConsequences{temUNKNOWN}}; + throw UnknownTxnType(txnType); } } +} // namespace -/* invoke_preclaim uses name hiding to accomplish - compile-time polymorphism of (presumably) static - class functions for Transactor and derived classes. -*/ +// Templates so preflight does the right thing with T::ConsequencesFactory. +// +// This could be done more easily using if constexpr, but Visual Studio +// 2017 doesn't handle if constexpr correctly. So once we're no longer +// building with Visual Studio 2017 we can consider replacing the four +// templates with a single template function that uses if constexpr. +// +// For Transactor::Normal +// + +// clang-format off +// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses +template +requires(T::ConsequencesFactory == Transactor::Normal) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return TxConsequences(ctx.tx); +}; + +// For Transactor::Blocker +template +requires(T::ConsequencesFactory == Transactor::Blocker) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return TxConsequences(ctx.tx, TxConsequences::blocker); +}; + +// For Transactor::Custom template +requires(T::ConsequencesFactory == Transactor::Custom) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return T::makeTxConsequences(ctx); +}; +// clang-format on + +static std::pair +invoke_preflight(PreflightContext const& ctx) +{ + try + { + return with_txn_type(ctx.tx.getTxnType(), [&]() { + auto const tec = T::preflight(ctx); + return std::make_pair( + tec, + isTesSuccess(tec) ? consequences_helper(ctx) + : TxConsequences{tec}); + }); + } + catch (UnknownTxnType const& e) + { + // Should never happen + JLOG(ctx.j.fatal()) + << "Unknown transaction type in preflight: " << e.txnType; + assert(false); + return {temUNKNOWN, TxConsequences{temUNKNOWN}}; + } +} + static TER invoke_preclaim(PreclaimContext const& ctx) { - // If the transactor requires a valid account and the transaction doesn't - // list one, preflight will have already a flagged a failure. - auto const id = ctx.tx.getAccountID(sfAccount); - - if (id != beast::zero) + try { - TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); + // use name hiding to accomplish compile-time polymorphism of static + // class functions for Transactor and derived classes. + return with_txn_type(ctx.tx.getTxnType(), [&]() { + // If the transactor requires a valid account and the transaction + // doesn't list one, preflight will have already a flagged a + // failure. + auto const id = ctx.tx.getAccountID(sfAccount); - if (result != tesSUCCESS) - return result; + if (id != beast::zero) + { + TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); - result = T::checkPriorTxAndLastLedger(ctx); + if (result != tesSUCCESS) + return result; - if (result != tesSUCCESS) - return result; + result = T::checkPriorTxAndLastLedger(ctx); - result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); + if (result != tesSUCCESS) + return result; - if (result != tesSUCCESS) - return result; + result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); - result = T::checkSign(ctx); + if (result != tesSUCCESS) + return result; - if (result != tesSUCCESS) - return result; - } + result = T::checkSign(ctx); - return T::preclaim(ctx); -} + if (result != tesSUCCESS) + return result; + } -static TER -invoke_preclaim(PreclaimContext const& ctx) -{ - switch (ctx.tx.getTxnType()) + return T::preclaim(ctx); + }); + } + catch (UnknownTxnType const& e) { - case ttACCOUNT_DELETE: - return invoke_preclaim(ctx); - case ttACCOUNT_SET: - return invoke_preclaim(ctx); - case ttCHECK_CANCEL: - return invoke_preclaim(ctx); - case ttCHECK_CASH: - return invoke_preclaim(ctx); - case ttCHECK_CREATE: - return invoke_preclaim(ctx); - case ttDEPOSIT_PREAUTH: - return invoke_preclaim(ctx); - case ttOFFER_CANCEL: - return invoke_preclaim(ctx); - case ttOFFER_CREATE: - return invoke_preclaim(ctx); - case ttESCROW_CREATE: - return invoke_preclaim(ctx); - case ttESCROW_FINISH: - return invoke_preclaim(ctx); - case ttESCROW_CANCEL: - return invoke_preclaim(ctx); - case ttPAYCHAN_CLAIM: - return invoke_preclaim(ctx); - case ttPAYCHAN_CREATE: - return invoke_preclaim(ctx); - case ttPAYCHAN_FUND: - return invoke_preclaim(ctx); - case ttPAYMENT: - return invoke_preclaim(ctx); - case ttREGULAR_KEY_SET: - return invoke_preclaim(ctx); - case ttSIGNER_LIST_SET: - return invoke_preclaim(ctx); - case ttTICKET_CREATE: - return invoke_preclaim(ctx); - case ttTRUST_SET: - return invoke_preclaim(ctx); - case ttAMENDMENT: - case ttFEE: - case ttUNL_MODIFY: - return invoke_preclaim(ctx); - case ttNFTOKEN_MINT: - return invoke_preclaim(ctx); - case ttNFTOKEN_BURN: - return invoke_preclaim(ctx); - case ttNFTOKEN_CREATE_OFFER: - return invoke_preclaim(ctx); - case ttNFTOKEN_CANCEL_OFFER: - return invoke_preclaim(ctx); - case ttNFTOKEN_ACCEPT_OFFER: - return invoke_preclaim(ctx); - case ttCLAWBACK: - return invoke_preclaim(ctx); - case ttAMM_CREATE: - return invoke_preclaim(ctx); - case ttAMM_DEPOSIT: - return invoke_preclaim(ctx); - case ttAMM_WITHDRAW: - return invoke_preclaim(ctx); - case ttAMM_VOTE: - return invoke_preclaim(ctx); - case ttAMM_BID: - return invoke_preclaim(ctx); - case ttAMM_DELETE: - return invoke_preclaim(ctx); - default: - assert(false); - return temUNKNOWN; + // Should never happen + JLOG(ctx.j.fatal()) + << "Unknown transaction type in preclaim: " << e.txnType; + assert(false); + return temUNKNOWN; } } static XRPAmount invoke_calculateBaseFee(ReadView const& view, STTx const& tx) { - switch (tx.getTxnType()) + try { - case ttACCOUNT_DELETE: - return DeleteAccount::calculateBaseFee(view, tx); - case ttACCOUNT_SET: - return SetAccount::calculateBaseFee(view, tx); - case ttCHECK_CANCEL: - return CancelCheck::calculateBaseFee(view, tx); - case ttCHECK_CASH: - return CashCheck::calculateBaseFee(view, tx); - case ttCHECK_CREATE: - return CreateCheck::calculateBaseFee(view, tx); - case ttDEPOSIT_PREAUTH: - return DepositPreauth::calculateBaseFee(view, tx); - case ttOFFER_CANCEL: - return CancelOffer::calculateBaseFee(view, tx); - case ttOFFER_CREATE: - return CreateOffer::calculateBaseFee(view, tx); - case ttESCROW_CREATE: - return EscrowCreate::calculateBaseFee(view, tx); - case ttESCROW_FINISH: - return EscrowFinish::calculateBaseFee(view, tx); - case ttESCROW_CANCEL: - return EscrowCancel::calculateBaseFee(view, tx); - case ttPAYCHAN_CLAIM: - return PayChanClaim::calculateBaseFee(view, tx); - case ttPAYCHAN_CREATE: - return PayChanCreate::calculateBaseFee(view, tx); - case ttPAYCHAN_FUND: - return PayChanFund::calculateBaseFee(view, tx); - case ttPAYMENT: - return Payment::calculateBaseFee(view, tx); - case ttREGULAR_KEY_SET: - return SetRegularKey::calculateBaseFee(view, tx); - case ttSIGNER_LIST_SET: - return SetSignerList::calculateBaseFee(view, tx); - case ttTICKET_CREATE: - return CreateTicket::calculateBaseFee(view, tx); - case ttTRUST_SET: - return SetTrust::calculateBaseFee(view, tx); - case ttAMENDMENT: - case ttFEE: - case ttUNL_MODIFY: - return Change::calculateBaseFee(view, tx); - case ttNFTOKEN_MINT: - return NFTokenMint::calculateBaseFee(view, tx); - case ttNFTOKEN_BURN: - return NFTokenBurn::calculateBaseFee(view, tx); - case ttNFTOKEN_CREATE_OFFER: - return NFTokenCreateOffer::calculateBaseFee(view, tx); - case ttNFTOKEN_CANCEL_OFFER: - return NFTokenCancelOffer::calculateBaseFee(view, tx); - case ttNFTOKEN_ACCEPT_OFFER: - return NFTokenAcceptOffer::calculateBaseFee(view, tx); - case ttCLAWBACK: - return Clawback::calculateBaseFee(view, tx); - case ttAMM_CREATE: - return AMMCreate::calculateBaseFee(view, tx); - case ttAMM_DEPOSIT: - return AMMDeposit::calculateBaseFee(view, tx); - case ttAMM_WITHDRAW: - return AMMWithdraw::calculateBaseFee(view, tx); - case ttAMM_VOTE: - return AMMVote::calculateBaseFee(view, tx); - case ttAMM_BID: - return AMMBid::calculateBaseFee(view, tx); - case ttAMM_DELETE: - return AMMDelete::calculateBaseFee(view, tx); - default: - assert(false); - return XRPAmount{0}; + return with_txn_type(tx.getTxnType(), [&]() { + return T::calculateBaseFee(view, tx); + }); + } + catch (UnknownTxnType const& e) + { + assert(false); + return XRPAmount{0}; } } @@ -408,141 +329,20 @@ TxConsequences::TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed) static std::pair invoke_apply(ApplyContext& ctx) { - switch (ctx.tx.getTxnType()) + try { - case ttACCOUNT_DELETE: { - DeleteAccount p(ctx); - return p(); - } - case ttACCOUNT_SET: { - SetAccount p(ctx); - return p(); - } - case ttCHECK_CANCEL: { - CancelCheck p(ctx); - return p(); - } - case ttCHECK_CASH: { - CashCheck p(ctx); - return p(); - } - case ttCHECK_CREATE: { - CreateCheck p(ctx); - return p(); - } - case ttDEPOSIT_PREAUTH: { - DepositPreauth p(ctx); - return p(); - } - case ttOFFER_CANCEL: { - CancelOffer p(ctx); - return p(); - } - case ttOFFER_CREATE: { - CreateOffer p(ctx); - return p(); - } - case ttESCROW_CREATE: { - EscrowCreate p(ctx); - return p(); - } - case ttESCROW_FINISH: { - EscrowFinish p(ctx); - return p(); - } - case ttESCROW_CANCEL: { - EscrowCancel p(ctx); - return p(); - } - case ttPAYCHAN_CLAIM: { - PayChanClaim p(ctx); - return p(); - } - case ttPAYCHAN_CREATE: { - PayChanCreate p(ctx); - return p(); - } - case ttPAYCHAN_FUND: { - PayChanFund p(ctx); + return with_txn_type(ctx.tx.getTxnType(), [&]() { + T p(ctx); return p(); - } - case ttPAYMENT: { - Payment p(ctx); - return p(); - } - case ttREGULAR_KEY_SET: { - SetRegularKey p(ctx); - return p(); - } - case ttSIGNER_LIST_SET: { - SetSignerList p(ctx); - return p(); - } - case ttTICKET_CREATE: { - CreateTicket p(ctx); - return p(); - } - case ttTRUST_SET: { - SetTrust p(ctx); - return p(); - } - case ttAMENDMENT: - case ttFEE: - case ttUNL_MODIFY: { - Change p(ctx); - return p(); - } - case ttNFTOKEN_MINT: { - NFTokenMint p(ctx); - return p(); - } - case ttNFTOKEN_BURN: { - NFTokenBurn p(ctx); - return p(); - } - case ttNFTOKEN_CREATE_OFFER: { - NFTokenCreateOffer p(ctx); - return p(); - } - case ttNFTOKEN_CANCEL_OFFER: { - NFTokenCancelOffer p(ctx); - return p(); - } - case ttNFTOKEN_ACCEPT_OFFER: { - NFTokenAcceptOffer p(ctx); - return p(); - } - case ttCLAWBACK: { - Clawback p(ctx); - return p(); - } - case ttAMM_CREATE: { - AMMCreate p(ctx); - return p(); - } - case ttAMM_DEPOSIT: { - AMMDeposit p(ctx); - return p(); - } - case ttAMM_WITHDRAW: { - AMMWithdraw p(ctx); - return p(); - } - case ttAMM_VOTE: { - AMMVote p(ctx); - return p(); - } - case ttAMM_BID: { - AMMBid p(ctx); - return p(); - } - case ttAMM_DELETE: { - AMMDelete p(ctx); - return p(); - } - default: - assert(false); - return {temUNKNOWN, false}; + }); + } + catch (UnknownTxnType const& e) + { + // Should never happen + JLOG(ctx.journal.fatal()) + << "Unknown transaction type in apply: " << e.txnType; + assert(false); + return {temUNKNOWN, false}; } } diff --git a/src/ripple/basics/impl/ThreadUtilities.cpp b/src/ripple/basics/impl/ThreadUtilities.cpp deleted file mode 100644 index be1745c40a7..00000000000 --- a/src/ripple/basics/impl/ThreadUtilities.cpp +++ /dev/null @@ -1,140 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2022 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 { - -#ifdef __APPLE__ - -#include - -std::string -get_name(std::thread::native_handle_type t) -{ - char buffer[64]; - if (pthread_getname_np(t, buffer, sizeof(buffer)) != 0) - throw std::runtime_error("get_name failed\n"); - return buffer; -} - -namespace this_thread { - -std::string -get_name() -{ - return ripple::get_name(pthread_self()); -} - -void -set_name(std::string s) -{ - s.resize(15); - if (pthread_setname_np(s.data()) != 0) - throw std::runtime_error("this_thread::set_name failed\n"); -} - -} // namespace this_thread - -#endif // __APPLE__ - -#ifdef __linux__ - -#include - -std::string -get_name(std::thread::native_handle_type t) -{ - char buffer[64]; - if (pthread_getname_np(t, buffer, sizeof(buffer)) != 0) - throw std::runtime_error("get_name failed\n"); - return buffer; -} - -namespace this_thread { - -std::string -get_name() -{ - return ripple::get_name(pthread_self()); -} - -void -set_name(std::string s) -{ - s.resize(15); - if (pthread_setname_np(pthread_self(), s.data()) != 0) - throw std::runtime_error("this_thread::set_name failed\n"); -} - -} // namespace this_thread - -#endif // __linux__ - -#ifdef _WIN64 - -#define WIN32_LEAN_AND_MEAN - -#include -#include -#include - -std::string -get_name(std::thread::native_handle_type t) -{ - wchar_t* unhandled_data{}; - HRESULT r = GetThreadDescription(t, &unhandled_data); - std::unique_ptr data{ - unhandled_data, LocalFree}; - if (FAILED(r)) - throw std::runtime_error("get_name failed\n"); - std::string s; - auto p = data.get(); - while (*p) - s.push_back(static_cast(*p++)); - return s; -} - -namespace this_thread { - -std::string -get_name() -{ - return ripple::get_name(GetCurrentThread()); -} - -void -set_name(std::string s) -{ - assert(s.size() <= 15); - s.resize(15); - std::wstring ws; - for (auto c : s) - ws += c; - HRESULT r = SetThreadDescription(GetCurrentThread(), ws.data()); - if (FAILED(r)) - throw std::runtime_error("this_thread::set_name failed\n"); -} - -} // namespace this_thread - -#endif // __WINDOWS__ - -} // namespace ripple diff --git a/src/ripple/beast/clock/abstract_clock.h b/src/ripple/beast/clock/abstract_clock.h index e543d6d7bb4..128ab82b4bd 100644 --- a/src/ripple/beast/clock/abstract_clock.h +++ b/src/ripple/beast/clock/abstract_clock.h @@ -70,7 +70,7 @@ class abstract_clock abstract_clock(abstract_clock const&) = default; /** Returns the current time. */ - virtual time_point + [[nodiscard]] virtual time_point now() const = 0; }; diff --git a/src/ripple/beast/core/CurrentThreadName.cpp b/src/ripple/beast/core/CurrentThreadName.cpp new file mode 100644 index 00000000000..80d275a1fff --- /dev/null +++ b/src/ripple/beast/core/CurrentThreadName.cpp @@ -0,0 +1,125 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com + + 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 + +//------------------------------------------------------------------------------ + +#if BOOST_OS_WINDOWS +#include +#include + +namespace beast::detail { + +inline void +setCurrentThreadNameImpl(std::string_view name) +{ +#if DEBUG && BOOST_COMP_MSVC + // This technique is documented by Microsoft and works for all versions + // of Windows and Visual Studio provided that the process is being run + // under the Visual Studio debugger. For more details, see: + // https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code + +#pragma pack(push, 8) + struct THREADNAME_INFO + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + }; +#pragma pack(pop) + + THREADNAME_INFO ni; + + ni.dwType = 0x1000; + ni.szName = name.data(); + ni.dwThreadID = GetCurrentThreadId(); + ni.dwFlags = 0; + +#pragma warning(push) +#pragma warning(disable : 6320 6322) + __try + { + RaiseException( + 0x406d1388, 0, sizeof(ni) / sizeof(ULONG_PTR), (ULONG_PTR*)&ni); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + { + } +#pragma warning(pop) +#endif +} + +} // namespace beast::detail +#endif // BOOST_OS_WINDOWS + +#if BOOST_OS_MACOS +#include + +namespace beast::detail { + +inline void +setCurrentThreadNameImpl(std::string_view name) +{ + pthread_setname_np(name.data()); +} + +} // namespace beast::detail +#endif // BOOST_OS_MACOS + +#if BOOST_OS_LINUX +#include + +namespace beast::detail { + +inline void +setCurrentThreadNameImpl(std::string_view name) +{ + pthread_setname_np(pthread_self(), name.data()); +} + +} // namespace beast::detail +#endif // BOOST_OS_LINUX + +namespace beast { + +namespace detail { +thread_local std::string threadName; +} // namespace detail + +std::string +getCurrentThreadName() +{ + return detail::threadName; +} + +void +setCurrentThreadName(std::string_view name) +{ + detail::threadName = name; + detail::setCurrentThreadNameImpl(name); +} + +} // namespace beast diff --git a/src/ripple/basics/ThreadUtilities.h b/src/ripple/beast/core/CurrentThreadName.h similarity index 51% rename from src/ripple/basics/ThreadUtilities.h rename to src/ripple/beast/core/CurrentThreadName.h index f91dbd118e4..5adbb210880 100644 --- a/src/ripple/basics/ThreadUtilities.h +++ b/src/ripple/beast/core/CurrentThreadName.h @@ -1,7 +1,11 @@ //------------------------------------------------------------------------------ /* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2022 Ripple Labs Inc. + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Portions of this file are from JUCE. + Copyright (c) 2013 - Raw Material Software Ltd. + Please visit http://www.juce.com 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,34 +21,31 @@ */ //============================================================================== -#ifndef RIPPLE_BASICS_THREADUTILITIES_H_INCLUDED -#define RIPPLE_BASICS_THREADUTILITIES_H_INCLUDED +#ifndef BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED +#define BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED #include -#include +#include -namespace ripple { +namespace beast { -std::string -get_name(std::thread::native_handle_type t); +/** Changes the name of the caller thread. + Different OSes may place different length or content limits on this name. +*/ +void +setCurrentThreadName(std::string_view newThreadName); -template -inline auto -get_name(Thread& t) -> decltype(t.native_handle(), t.join(), std::string{}) -{ - return get_name(t.native_handle()); -} +/** Returns the name of the caller thread. -namespace this_thread { + The name returned is the name as set by a call to setCurrentThreadName(). + If the thread name is set by an external force, then that name change + will not be reported. + If no name has ever been set, then the empty string is returned. +*/ std::string -get_name(); - -void -set_name(std::string s); - -} // namespace this_thread +getCurrentThreadName(); -} // namespace ripple +} // namespace beast #endif diff --git a/src/ripple/beast/unit_test/match.hpp b/src/ripple/beast/unit_test/match.hpp index 859eb1f71ca..689b189567d 100644 --- a/src/ripple/beast/unit_test/match.hpp +++ b/src/ripple/beast/unit_test/match.hpp @@ -94,6 +94,14 @@ selector::operator()(suite_info const& s) return ! s.manual(); } + // check start of name + if (s.name().starts_with(pat_) || s.full_name().starts_with(pat_)) + { + // Don't change the mode so that the partial pattern can match + // more than once + return !s.manual(); + } + return false; case suite: diff --git a/src/ripple/beast/utility/Journal.h b/src/ripple/beast/utility/Journal.h index 333a743a658..0738748b6c5 100644 --- a/src/ripple/beast/utility/Journal.h +++ b/src/ripple/beast/utility/Journal.h @@ -134,7 +134,6 @@ class Journal class Stream; -private: /* Scoped ostream-based container for writing messages to a Journal. */ class ScopedStream { diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 6236f89fc52..a9acd4c6b2b 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -26,9 +26,11 @@ #include #include #include // VFALCO Breaks levelization + #include #include // VFALCO FIX: This include should not be here #include + #include #include #include @@ -37,6 +39,7 @@ #include #include #include +#include #include namespace ripple { @@ -146,9 +149,11 @@ class Config : public BasicConfig bool nodeToShard = false; bool ELB_SUPPORT = false; - std::vector IPS; // Peer IPs from rippled.cfg. - std::vector IPS_FIXED; // Fixed Peer IPs from rippled.cfg. - std::vector SNTP_SERVERS; // SNTP servers from rippled.cfg. + // Entries from [ips] config stanza + std::vector IPS; + + // Entries from [ips_fixed] config stanza + std::vector IPS_FIXED; enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK }; StartUpType START_UP = NORMAL; @@ -295,6 +300,14 @@ class Config : public BasicConfig // First, attempt to load the latest ledger directly from disk. bool FAST_LOAD = false; + // When starting rippled with existing database it do not know it has those + // ledgers locally until the server naturally tries to backfill. This makes + // is difficult to test some functionality (in particular performance + // testing sidechains). With this variable the user is able to force rippled + // to consider the ledger range to be present. It should be used for testing + // only. + std::optional> + FORCED_LEDGER_RANGE_PRESENT; public: Config(); diff --git a/src/ripple/core/TimeKeeper.h b/src/ripple/core/TimeKeeper.h index ebc6c1f1ab0..55970ec8227 100644 --- a/src/ripple/core/TimeKeeper.h +++ b/src/ripple/core/TimeKeeper.h @@ -22,73 +22,99 @@ #include #include -#include -#include -#include +#include namespace ripple { /** Manages various times used by the server. */ class TimeKeeper : public beast::abstract_clock { -public: - virtual ~TimeKeeper() = default; - - /** Launch the internal thread. +private: + std::atomic closeOffset_{}; - The internal thread synchronizes local network time - using the provided list of SNTP servers. - */ - virtual void - run(std::vector const& servers) = 0; - - /** Returns the estimate of wall time, in network time. + // Adjust system_clock::time_point for NetClock epoch + static constexpr time_point + adjust(std::chrono::system_clock::time_point when) + { + return time_point(std::chrono::duration_cast( + when.time_since_epoch() - days(10957))); + } - The network time is wall time adjusted for the Ripple - epoch, the beginning of January 1st, 2000 UTC. Each server - can compute a different value for network time. Other - servers value for network time is not directly observable, - but good guesses can be made by looking at validators' - positions on close times. +public: + virtual ~TimeKeeper() = default; - Servers compute network time by adjusting a local wall - clock using SNTP and then adjusting for the epoch. - */ - virtual time_point - now() const override = 0; + /** Returns the current time, using the server's clock. - /** Returns the close time, in network time. + It's possible for servers to have a different value for network + time, especially if they do not use some external mechanism for + time synchronization (e.g. NTP or SNTP). This is fine. - Close time is the time the network would agree that - a ledger closed, if a ledger closed right now. + This estimate is not directly visible to other servers over the + protocol, but it is possible for them to make an educated guess + if this server publishes proposals or validations. - The close time represents the notional "center" - of the network. Each server assumes its clock - is correct, and tries to pull the close time towards - its measure of network time. + @note The network time is adjusted for the "Ripple epoch" which + was arbitrarily defined as 2000-01-01T00:00:00Z by Arthur + Britto and David Schwartz during early development of the + code. No rationale has been provided for this curious and + annoying, but otherwise unimportant, choice. */ - virtual time_point - closeTime() const = 0; + [[nodiscard]] time_point + now() const override + { + return adjust(std::chrono::system_clock::now()); + } - /** Adjust the close time. + /** Returns the predicted close time, in network time. - This is called in response to received validations. + The predicted close time represents the notional "center" of the + network. Each server assumes that its clock is correct and tries + to pull the close time towards its measure of network time. */ - virtual void - adjustCloseTime(std::chrono::duration amount) = 0; + [[nodiscard]] time_point + closeTime() const + { + return now() + closeOffset_.load(); + } // This may return a negative value - virtual std::chrono::duration - nowOffset() const = 0; - - // This may return a negative value - virtual std::chrono::duration - closeOffset() const = 0; + [[nodiscard]] std::chrono::seconds + closeOffset() const + { + return closeOffset_.load(); + } + + /** Adjust the close time, based on the network's view of time. */ + std::chrono::seconds + adjustCloseTime(std::chrono::seconds by) + { + using namespace std::chrono_literals; + + auto offset = closeOffset_.load(); + + if (by == 0s && offset == 0s) + return offset; + + // The close time adjustment is serialized externally to this + // code. The compare/exchange only serves as a weak check and + // should not fail. Even if it does, it's safe to simply just + // skip the adjustment. + closeOffset_.compare_exchange_strong(offset, [by, offset]() { + // Ignore small offsets and push the close time + // towards our wall time. + if (by > 1s) + return offset + ((by + 3s) / 4); + + if (by < -1s) + return offset + ((by - 3s) / 4); + + return (offset * 3) / 4; + }()); + + return closeOffset_.load(); + } }; -extern std::unique_ptr -make_TimeKeeper(beast::Journal j); - } // namespace ripple #endif diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index f835ca8df04..2f26b8ba525 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -206,14 +206,10 @@ parseIniFile(std::string const& strInput, const bool bTrim) IniFileSections::mapped_type* getIniFileSection(IniFileSections& secSource, std::string const& strSection) { - IniFileSections::iterator it; - IniFileSections::mapped_type* smtResult; - it = secSource.find(strSection); - if (it == secSource.end()) - smtResult = nullptr; - else - smtResult = &(it->second); - return smtResult; + if (auto it = secSource.find(strSection); it != secSource.end()) + return &(it->second); + + return nullptr; } bool @@ -223,22 +219,21 @@ getSingleSection( std::string& strValue, beast::Journal j) { - IniFileSections::mapped_type* pmtEntries = - getIniFileSection(secSource, strSection); - bool bSingle = pmtEntries && 1 == pmtEntries->size(); + auto const pmtEntries = getIniFileSection(secSource, strSection); - if (bSingle) + if (pmtEntries && pmtEntries->size() == 1) { strValue = (*pmtEntries)[0]; + return true; } - else if (pmtEntries) + + if (pmtEntries) { - JLOG(j.warn()) << boost::str( - boost::format("Section [%s]: requires 1 line not %d lines.") % - strSection % pmtEntries->size()); + JLOG(j.warn()) << "Section '" << strSection << "': requires 1 line not " + << pmtEntries->size() << " lines."; } - return bSingle; + return false; } //------------------------------------------------------------------------------ @@ -461,9 +456,6 @@ Config::loadFromString(std::string const& fileContents) if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED)) IPS_FIXED = *s; - if (auto s = getIniFileSection(secConfig, SECTION_SNTP)) - SNTP_SERVERS = *s; - // if the user has specified ip:port then replace : with a space. { auto replaceColons = [](std::vector& strVec) { diff --git a/src/ripple/core/impl/Job.cpp b/src/ripple/core/impl/Job.cpp index 952d513dc16..780a9f49cdf 100644 --- a/src/ripple/core/impl/Job.cpp +++ b/src/ripple/core/impl/Job.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include @@ -61,7 +61,7 @@ Job::queue_time() const void Job::doJob() { - this_thread::set_name("doJob: " + mName); + beast::setCurrentThreadName("doJob: " + mName); m_loadEvent->start(); m_loadEvent->setName(mName); diff --git a/src/ripple/core/impl/SNTPClock.cpp b/src/ripple/core/impl/SNTPClock.cpp deleted file mode 100644 index 43bd35166df..00000000000 --- a/src/ripple/core/impl/SNTPClock.cpp +++ /dev/null @@ -1,491 +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 -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -static uint8_t SNTPQueryData[48] = { - 0x1B, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -using namespace std::chrono_literals; -// NTP query frequency - 4 minutes -auto constexpr NTP_QUERY_FREQUENCY = 4min; - -// NTP minimum interval to query same servers - 3 minutes -auto constexpr NTP_MIN_QUERY = 3min; - -// NTP sample window (should be odd) -#define NTP_SAMPLE_WINDOW 9 - -// NTP timestamp constant -auto constexpr NTP_UNIX_OFFSET = 0x83AA7E80s; - -// NTP timestamp validity -auto constexpr NTP_TIMESTAMP_VALID = (NTP_QUERY_FREQUENCY + NTP_MIN_QUERY) * 2; - -// SNTP packet offsets -#define NTP_OFF_INFO 0 -#define NTP_OFF_ROOTDELAY 1 -#define NTP_OFF_ROOTDISP 2 -#define NTP_OFF_REFERENCEID 3 -#define NTP_OFF_REFTS_INT 4 -#define NTP_OFF_REFTS_FRAC 5 -#define NTP_OFF_ORGTS_INT 6 -#define NTP_OFF_ORGTS_FRAC 7 -#define NTP_OFF_RECVTS_INT 8 -#define NTP_OFF_RECVTS_FRAC 9 -#define NTP_OFF_XMITTS_INT 10 -#define NTP_OFF_XMITTS_FRAC 11 - -class SNTPClientImp : public SNTPClock -{ -private: - template - using sys_time = std::chrono::time_point; - - using sys_seconds = sys_time; - - struct Query - { - bool replied; - sys_seconds sent; - std::uint32_t nonce; - - explicit Query(sys_seconds j = sys_seconds::max()) - : replied(false), sent(j) - { - } - }; - - beast::Journal const j_; - std::mutex mutable mutex_; - std::thread thread_; - boost::asio::io_service io_service_; - std::optional work_; - - std::map queries_; - boost::asio::ip::udp::socket socket_; - boost::asio::basic_waitable_timer timer_; - boost::asio::ip::udp::resolver resolver_; - std::vector> servers_; - std::chrono::seconds offset_; - sys_seconds lastUpdate_; - std::deque offsets_; - std::vector buf_; - boost::asio::ip::udp::endpoint ep_; - -public: - using error_code = boost::system::error_code; - - explicit SNTPClientImp(beast::Journal j) - : j_(j) - , work_(io_service_) - , socket_(io_service_) - , timer_(io_service_) - , resolver_(io_service_) - , offset_(0) - , lastUpdate_(sys_seconds::max()) - , buf_(256) - { - } - - ~SNTPClientImp() override - { - if (thread_.joinable()) - { - error_code ec; - timer_.cancel(ec); - socket_.cancel(ec); - work_ = std::nullopt; - thread_.join(); - } - } - - //-------------------------------------------------------------------------- - - void - run(const std::vector& servers) override - { - std::vector::const_iterator it = servers.begin(); - - if (it == servers.end()) - { - JLOG(j_.info()) << "SNTP: no server specified"; - return; - } - - { - std::lock_guard lock(mutex_); - for (auto const& item : servers) - servers_.emplace_back(item, sys_seconds::max()); - } - queryAll(); - - using namespace boost::asio; - socket_.open(ip::udp::v4()); - socket_.bind(ep_); - socket_.async_receive_from( - buffer(buf_, 256), - ep_, - std::bind( - &SNTPClientImp::onRead, - this, - std::placeholders::_1, - std::placeholders::_2)); - timer_.expires_from_now(NTP_QUERY_FREQUENCY); - timer_.async_wait( - std::bind(&SNTPClientImp::onTimer, this, std::placeholders::_1)); - - thread_ = std::thread(&SNTPClientImp::doRun, this); - } - - time_point - now() const override - { - std::lock_guard lock(mutex_); - using namespace std::chrono; - auto const when = time_point_cast(clock_type::now()); - if ((lastUpdate_ == sys_seconds::max()) || - ((lastUpdate_ + NTP_TIMESTAMP_VALID) < - time_point_cast(clock_type::now()))) - return when; - return when + offset_; - } - - duration - offset() const override - { - std::lock_guard lock(mutex_); - return offset_; - } - - //-------------------------------------------------------------------------- - - void - doRun() - { - this_thread::set_name("SNTPClock"); - io_service_.run(); - } - - void - onTimer(error_code const& ec) - { - using namespace boost::asio; - if (ec == error::operation_aborted) - return; - if (ec) - { - JLOG(j_.error()) << "SNTPClock::onTimer: " << ec.message(); - return; - } - - doQuery(); - timer_.expires_from_now(NTP_QUERY_FREQUENCY); - timer_.async_wait( - std::bind(&SNTPClientImp::onTimer, this, std::placeholders::_1)); - } - - void - onRead(error_code const& ec, std::size_t bytes_xferd) - { - using namespace boost::asio; - using namespace std::chrono; - if (ec == error::operation_aborted) - return; - - // VFALCO Should we return on any error? - /* - if (ec) - return; - */ - - if (!ec) - { - JLOG(j_.trace()) << "SNTP: Packet from " << ep_; - std::lock_guard lock(mutex_); - auto const query = queries_.find(ep_); - if (query == queries_.end()) - { - JLOG(j_.debug()) << "SNTP: Reply from " << ep_ - << " found without matching query"; - } - else if (query->second.replied) - { - JLOG(j_.debug()) << "SNTP: Duplicate response from " << ep_; - } - else - { - query->second.replied = true; - - if (time_point_cast(clock_type::now()) > - (query->second.sent + 1s)) - { - JLOG(j_.warn()) << "SNTP: Late response from " << ep_; - } - else if (bytes_xferd < 48) - { - JLOG(j_.warn()) << "SNTP: Short reply from " << ep_ << " (" - << bytes_xferd << ") " << buf_.size(); - } - else if ( - reinterpret_cast( - &buf_[0])[NTP_OFF_ORGTS_FRAC] != query->second.nonce) - { - JLOG(j_.warn()) - << "SNTP: Reply from " << ep_ << "had wrong nonce"; - } - else - { - processReply(); - } - } - } - - socket_.async_receive_from( - buffer(buf_, 256), - ep_, - std::bind( - &SNTPClientImp::onRead, - this, - std::placeholders::_1, - std::placeholders::_2)); - } - - //-------------------------------------------------------------------------- - - void - addServer(std::string const& server) - { - std::lock_guard lock(mutex_); - servers_.push_back(std::make_pair(server, sys_seconds::max())); - } - - void - queryAll() - { - while (doQuery()) - { - } - } - - bool - doQuery() - { - std::lock_guard lock(mutex_); - auto best = servers_.end(); - - for (auto iter = servers_.begin(), end = best; iter != end; ++iter) - if ((best == end) || (iter->second == sys_seconds::max()) || - (iter->second < best->second)) - best = iter; - - if (best == servers_.end()) - { - JLOG(j_.trace()) << "SNTP: No server to query"; - return false; - } - - using namespace std::chrono; - auto now = time_point_cast(clock_type::now()); - - if ((best->second != sys_seconds::max()) && - ((best->second + NTP_MIN_QUERY) >= now)) - { - JLOG(j_.trace()) << "SNTP: All servers recently queried"; - return false; - } - - best->second = now; - - boost::asio::ip::udp::resolver::query query( - boost::asio::ip::udp::v4(), best->first, "ntp"); - resolver_.async_resolve( - query, - std::bind( - &SNTPClientImp::resolveComplete, - this, - std::placeholders::_1, - std::placeholders::_2)); - JLOG(j_.trace()) << "SNTPClock: Resolve pending for " << best->first; - return true; - } - - void - resolveComplete( - error_code const& ec, - boost::asio::ip::udp::resolver::iterator it) - { - using namespace boost::asio; - if (ec == error::operation_aborted) - return; - if (ec) - { - JLOG(j_.trace()) << "SNTPClock::resolveComplete: " << ec.message(); - return; - } - - assert(it != ip::udp::resolver::iterator()); - - auto sel = it; - int i = 1; - - while (++it != ip::udp::resolver::iterator()) - { - if (rand_int(i++) == 0) - sel = it; - } - - if (sel != ip::udp::resolver::iterator()) - { - std::lock_guard lock(mutex_); - Query& query = queries_[*sel]; - using namespace std::chrono; - auto now = time_point_cast(clock_type::now()); - - if ((query.sent == now) || ((query.sent + 1s) == now)) - { - // This can happen if the same IP address is reached through - // multiple names - JLOG(j_.trace()) << "SNTP: Redundant query suppressed"; - return; - } - - query.replied = false; - query.sent = now; - query.nonce = rand_int(); - // The following line of code will overflow at 2036-02-07 06:28:16 - // UTC - // due to the 32 bit cast. - reinterpret_cast( - SNTPQueryData)[NTP_OFF_XMITTS_INT] = - static_cast( - (time_point_cast(clock_type::now()) + - NTP_UNIX_OFFSET) - .time_since_epoch() - .count()); - reinterpret_cast( - SNTPQueryData)[NTP_OFF_XMITTS_FRAC] = query.nonce; - socket_.async_send_to( - buffer(SNTPQueryData, 48), - *sel, - std::bind( - &SNTPClientImp::onSend, - this, - std::placeholders::_1, - std::placeholders::_2)); - } - } - - void - onSend(error_code const& ec, std::size_t) - { - if (ec == boost::asio::error::operation_aborted) - return; - - if (ec) - { - JLOG(j_.warn()) << "SNTPClock::onSend: " << ec.message(); - return; - } - } - - void - processReply() - { - using namespace std::chrono; - assert(buf_.size() >= 48); - std::uint32_t* recvBuffer = - reinterpret_cast(&buf_.front()); - - unsigned info = ntohl(recvBuffer[NTP_OFF_INFO]); - auto timev = seconds{ntohl(recvBuffer[NTP_OFF_RECVTS_INT])}; - unsigned stratum = (info >> 16) & 0xff; - - if ((info >> 30) == 3) - { - JLOG(j_.info()) << "SNTP: Alarm condition " << ep_; - return; - } - - if ((stratum == 0) || (stratum > 14)) - { - JLOG(j_.info()) << "SNTP: Unreasonable stratum (" << stratum - << ") from " << ep_; - return; - } - - using namespace std::chrono; - auto now = time_point_cast(clock_type::now()); - timev -= now.time_since_epoch(); - timev -= NTP_UNIX_OFFSET; - - // add offset to list, replacing oldest one if appropriate - offsets_.push_back(timev); - - if (offsets_.size() >= NTP_SAMPLE_WINDOW) - offsets_.pop_front(); - - lastUpdate_ = now; - - // select median time - auto offsetList = offsets_; - std::sort(offsetList.begin(), offsetList.end()); - auto j = offsetList.size(); - auto it = std::next(offsetList.begin(), j / 2); - offset_ = *it; - - if ((j % 2) == 0) - offset_ = (offset_ + (*--it)) / 2; - - // debounce: small corrections likely - // do more harm than good - if ((offset_ == -1s) || (offset_ == 1s)) - offset_ = 0s; - - if (timev != 0s || offset_ != 0s) - { - JLOG(j_.trace()) << "SNTP: Offset is " << timev.count() - << ", new system offset is " << offset_.count(); - } - } -}; - -//------------------------------------------------------------------------------ - -std::unique_ptr -make_SNTPClock(beast::Journal j) -{ - return std::make_unique(j); -} - -} // namespace ripple diff --git a/src/ripple/core/impl/SNTPClock.h b/src/ripple/core/impl/SNTPClock.h deleted file mode 100644 index b63cac53da5..00000000000 --- a/src/ripple/core/impl/SNTPClock.h +++ /dev/null @@ -1,47 +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_NET_SNTPCLOCK_H_INCLUDED -#define RIPPLE_NET_SNTPCLOCK_H_INCLUDED - -#include -#include -#include -#include -#include -#include - -namespace ripple { - -/** A clock based on system_clock and adjusted for SNTP. */ -class SNTPClock : public beast::abstract_clock -{ -public: - virtual void - run(std::vector const& servers) = 0; - - virtual duration - offset() const = 0; -}; - -extern std::unique_ptr make_SNTPClock(beast::Journal); - -} // namespace ripple - -#endif diff --git a/src/ripple/core/impl/TimeKeeper.cpp b/src/ripple/core/impl/TimeKeeper.cpp deleted file mode 100644 index d1b07f53f44..00000000000 --- a/src/ripple/core/impl/TimeKeeper.cpp +++ /dev/null @@ -1,124 +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 { - -class TimeKeeperImpl : public TimeKeeper -{ -private: - beast::Journal const j_; - std::mutex mutable mutex_; - std::chrono::duration closeOffset_; - std::unique_ptr clock_; - - // Adjust system_clock::time_point for NetClock epoch - static time_point - adjust(std::chrono::system_clock::time_point when) - { - return time_point(std::chrono::duration_cast( - when.time_since_epoch() - days(10957))); - } - -public: - explicit TimeKeeperImpl(beast::Journal j) - : j_(j), closeOffset_{}, clock_(make_SNTPClock(j)) - { - } - - void - run(std::vector const& servers) override - { - clock_->run(servers); - } - - time_point - now() const override - { - std::lock_guard lock(mutex_); - return adjust(clock_->now()); - } - - time_point - closeTime() const override - { - std::lock_guard lock(mutex_); - return adjust(clock_->now()) + closeOffset_; - } - - void - adjustCloseTime(std::chrono::duration amount) override - { - using namespace std::chrono; - auto const s = amount.count(); - std::lock_guard lock(mutex_); - // Take large offsets, ignore small offsets, - // push the close time towards our wall time. - if (s > 1) - closeOffset_ += seconds((s + 3) / 4); - else if (s < -1) - closeOffset_ += seconds((s - 3) / 4); - else - closeOffset_ = (closeOffset_ * 3) / 4; - if (closeOffset_.count() != 0) - { - if (std::abs(closeOffset_.count()) < 60) - { - JLOG(j_.info()) << "TimeKeeper: Close time offset now " - << closeOffset_.count(); - } - else - { - JLOG(j_.warn()) << "TimeKeeper: Large close time offset = " - << closeOffset_.count(); - } - } - } - - std::chrono::duration - nowOffset() const override - { - using namespace std::chrono; - using namespace std; - lock_guard lock(mutex_); - return duration_cast>(clock_->offset()); - } - - std::chrono::duration - closeOffset() const override - { - std::lock_guard lock(mutex_); - return closeOffset_; - } -}; - -//------------------------------------------------------------------------------ - -std::unique_ptr -make_TimeKeeper(beast::Journal j) -{ - return std::make_unique(j); -} - -} // namespace ripple diff --git a/src/ripple/core/impl/Workers.cpp b/src/ripple/core/impl/Workers.cpp index aac2bf1716a..732e6f0ec8a 100644 --- a/src/ripple/core/impl/Workers.cpp +++ b/src/ripple/core/impl/Workers.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -206,7 +206,7 @@ Workers::Worker::run() for (;;) { // Put the name back in case the callback changed it - this_thread::set_name(threadName_); + beast::setCurrentThreadName(threadName_); // Acquire a task or "internal task." // @@ -259,7 +259,7 @@ Workers::Worker::run() } // Set inactive thread name. - this_thread::set_name("(" + threadName_ + ")"); + beast::setCurrentThreadName("(" + threadName_ + ")"); // [1] We will be here when the paused list is popped // diff --git a/src/ripple/json/impl/Writer.cpp b/src/ripple/json/impl/Writer.cpp index 98c8debe96a..44d106aa336 100644 --- a/src/ripple/json/impl/Writer.cpp +++ b/src/ripple/json/impl/Writer.cpp @@ -47,8 +47,6 @@ const char openBrace = '{'; const char openBracket = '['; const char quote = '"'; -const std::string none; - static auto const integralFloatsBecomeInts = false; size_t diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index df758646540..1242fdc2a68 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -121,7 +121,13 @@ class RPCParser static Json::Value jvParseCurrencyIssuer(std::string const& strCurrencyIssuer) { - static boost::regex reCurIss("\\`([[:alpha:]]{3})(?:/(.+))?\\'"); + // Matches a sequence of 3 characters from + // `ripple::detail::isoCharSet` (the currency), + // optionally followed by a forward slash and some other characters + // (the issuer). + // https://www.boost.org/doc/libs/1_82_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html + static boost::regex reCurIss( + "\\`([][:alnum:]<>(){}[|?!@#$%^&*]{3})(?:/(.+))?\\'"); boost::smatch smMatch; diff --git a/src/ripple/nodestore/backend/RocksDBFactory.cpp b/src/ripple/nodestore/backend/RocksDBFactory.cpp index 11979ed2019..b34560dba89 100644 --- a/src/ripple/nodestore/backend/RocksDBFactory.cpp +++ b/src/ripple/nodestore/backend/RocksDBFactory.cpp @@ -22,9 +22,9 @@ #if RIPPLE_ROCKSDB_AVAILABLE #include -#include #include #include +#include #include // VFALCO Bad dependency #include #include @@ -67,7 +67,7 @@ class RocksDBEnv : public rocksdb::EnvWrapper std::size_t const id(++n); std::stringstream ss; ss << "rocksdb #" << id; - this_thread::set_name(ss.str()); + beast::setCurrentThreadName(ss.str()); (*f)(a); } diff --git a/src/ripple/nodestore/impl/Database.cpp b/src/ripple/nodestore/impl/Database.cpp index e0c82d00d03..70416c873d5 100644 --- a/src/ripple/nodestore/impl/Database.cpp +++ b/src/ripple/nodestore/impl/Database.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include #include +#include #include #include #include @@ -63,7 +63,8 @@ Database::Database( [this](int i) { runningThreads_++; - this_thread::set_name("prefetch " + std::to_string(i)); + beast::setCurrentThreadName( + "db prefetch #" + std::to_string(i)); decltype(read_) read; diff --git a/src/ripple/overlay/Message.h b/src/ripple/overlay/Message.h index 0d6479366e8..6cb6900c639 100644 --- a/src/ripple/overlay/Message.h +++ b/src/ripple/overlay/Message.h @@ -100,6 +100,14 @@ class Message : public std::enable_shared_from_this return validatorKey_; } + /** Get the message type from the payload header. + * First four bytes are the compression/algorithm flag and the payload size. + * Next two bytes are the message type + * @return Message type + */ + int + getType() const; + private: std::vector buffer_; std::vector bufferCompressed_; @@ -129,15 +137,6 @@ class Message : public std::enable_shared_from_this */ void compress(); - - /** Get the message type from the payload header. - * First four bytes are the compression/algorithm flag and the payload size. - * Next two bytes are the message type - * @param in Payload header pointer - * @return Message type - */ - int - getType(std::uint8_t const* in) const; }; } // namespace ripple diff --git a/src/ripple/overlay/Peer.h b/src/ripple/overlay/Peer.h index ba415974151..dbe5416e590 100644 --- a/src/ripple/overlay/Peer.h +++ b/src/ripple/overlay/Peer.h @@ -39,6 +39,7 @@ enum class ProtocolFeature { ValidatorListPropagation, ValidatorList2Propagation, LedgerReplay, + StartProtocol }; /** Represents a peer connection in the overlay. */ diff --git a/src/ripple/overlay/PeerReservationTable.h b/src/ripple/overlay/PeerReservationTable.h index 3242ee68a8d..e8fd4a29437 100644 --- a/src/ripple/overlay/PeerReservationTable.h +++ b/src/ripple/overlay/PeerReservationTable.h @@ -26,9 +26,6 @@ #include #include -#define SOCI_USE_BOOST -#include - #include #include #include diff --git a/src/ripple/overlay/README.md b/src/ripple/overlay/README.md index 8be890ef75f..bfead075135 100644 --- a/src/ripple/overlay/README.md +++ b/src/ripple/overlay/README.md @@ -343,10 +343,11 @@ messages for the local and remote endpoints, and combine them to generate a uniq "fingerprint". By design, this fingerprint should be the same for both SSL/TLS endpoints. -That fingerprint, which is never shared over the wire (since each endpoint will -calculate it independently), is then signed by each server using its public -**`secp256k1`** node identity and the signature is transferred over the SSL/TLS -encrypted link during the protocol handshake phase. +That fingerprint is calculated by each endpoint independently, so the +fingerprint is never transmitted over the network. Each server then utilizes its +private key to sign the fingerprint. This is the same keypair that determines +the server's public `secp256k1` node identity. The signature is transferred over +the secure SSL/TLS encrypted link during the protocol's initial handshake phase. Each side of the link will verify that the provided signature is from the claimed public key against the session's unique fingerprint. If this signature check fails @@ -364,6 +365,50 @@ transferred between A and B and will not be able to intelligently tamper with th message stream between Alice and Bob, although she may be still be able to inject delays or terminate the link. +## Peer Connection Sequence + +The _PeerImp_ object can be constructed as either an outbound or an inbound peer. +The outbound peer is constructed by the _ConnectAttempt_ - the client side of +the connection. The inbound peer is constructed by the _InboundHandoff_ - +the server side of the connection. This differentiation of the peers matters only +in terms of the object construction. Once constructed, both inbound and outbound +peer play the same role. + +### Outbound Peer + +An outbound connection is initiated once a second by +the _OverlayImpl::Timer::on_timer()_ method. This method calls +_OverlayImpl::autoConnect()_, which in turn calls _OverlayImpl::connect()_ for +every outbound endpoint generated by _PeerFinder::autoconnect()_. _connect()_ +method constructs _ConnectAttempt_ object. _ConnectAttempt_ attempts to connect +to the provided endpoint and on a successful connection executes the client side +of the handshake protocol described above. If the handshake is successful then +the outbound _PeerImp_ object is constructed and passed to the overlay manager +_OverlayImpl_, which adds the object to the list of peers and children. The latter +maintains a list of objects which might be executing an asynchronous operation +and therefore have to be stopped on shutdown. The outbound _PeerImp_ sends +_TMStartProtocol_ message on start to instruct the connected inbound peer that +the outbound peer is ready to receive the protocol messages. + +### Inbound Peer + +Construction of the inbound peer is more involved. A multi protocol-server, +_ServerImpl_ located in _src/ripple/server_ module, maintains multiple configured +listening ports. Each listening port allows for multiple protocols including HTTP, +HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. For simplicity this +sequence describes only the Peer protocol. _ServerImpl_ constructs +_Door_ object for each configured protocol. Each instance of the _Door_ object +accepts connections on the configured port. On a successful connection the _Door_ +constructs _SSLHTTPPeer_ object since the Peer protocol always uses SSL +connection. _SSLHTTPPeer_ executes the SSL handshake. If the handshake is successful +then a server handler, _ServerHandlerImpl_ located in _src/ripple/src/impl_, hands off +the connection to the _OverlayImpl::onHandoff()_ method. _onHandoff()_ method +validates the client's HTTP handshake request described above. If the request is +valid then the _InboundHandoff_ object is constructed. _InboundHandoff_ sends +HTTP response to the connected client, constructs the inbound _PeerImp_ object, +and passes it to the overlay manager _OverlayImpl_, which adds the object to +the list of peers and children. Once the inbound _PeerImp_ receives +_TMStartProtocol_ message, it starts sending the protocol messages. # Ripple Clustering # diff --git a/src/ripple/overlay/ReduceRelayCommon.h b/src/ripple/overlay/ReduceRelayCommon.h index 3b87c3c8c13..8289e467e65 100644 --- a/src/ripple/overlay/ReduceRelayCommon.h +++ b/src/ripple/overlay/ReduceRelayCommon.h @@ -24,6 +24,10 @@ namespace ripple { +// Blog post explaining the rationale behind reduction of flooding gossip +// protocol: +// https://xrpl.org/blog/2021/message-routing-optimizations-pt-1-proposal-validation-relaying.html + namespace reduce_relay { // Peer's squelch is limited in time to diff --git a/src/ripple/overlay/impl/InboundHandoff.cpp b/src/ripple/overlay/impl/InboundHandoff.cpp new file mode 100644 index 00000000000..1f45e1d37a7 --- /dev/null +++ b/src/ripple/overlay/impl/InboundHandoff.cpp @@ -0,0 +1,185 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-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 { + +InboundHandoff::InboundHandoff( + Application& app, + id_t id, + std::shared_ptr const& slot, + http_request_type&& request, + PublicKey const& publicKey, + ProtocolVersion protocol, + Resource::Consumer consumer, + std::unique_ptr&& stream_ptr, + OverlayImpl& overlay) + : OverlayImpl::Child(overlay) + , app_(app) + , id_(id) + , sink_( + app_.journal("Peer"), + [id]() { + std::stringstream ss; + ss << "[" << std::setfill('0') << std::setw(3) << id << "] "; + return ss.str(); + }()) + , journal_(sink_) + , stream_ptr_(std::move(stream_ptr)) + , strand_(stream_ptr_->next_layer().socket().get_executor()) + , remote_address_(slot->remote_endpoint()) + , protocol_(protocol) + , publicKey_(publicKey) + , usage_(consumer) + , slot_(slot) + , request_(std::move(request)) +{ +} + +void +InboundHandoff::run() +{ + if (!strand_.running_in_this_thread()) + return post( + strand_, std::bind(&InboundHandoff::run, shared_from_this())); + sendResponse(); +} + +void +InboundHandoff::stop() +{ + if (!strand_.running_in_this_thread()) + return post( + strand_, std::bind(&InboundHandoff::stop, shared_from_this())); + if (stream_ptr_->next_layer().socket().is_open()) + { + JLOG(journal_.debug()) << "Stop"; + } + close(); +} + +void +InboundHandoff::sendResponse() +{ + auto const sharedValue = makeSharedValue(*stream_ptr_, journal_); + // This shouldn't fail since we already computed + // the shared value successfully in OverlayImpl + if (!sharedValue) + return fail("makeSharedValue: Unexpected failure"); + + JLOG(journal_.info()) << "Protocol: " << to_string(protocol_); + JLOG(journal_.info()) << "Public Key: " + << toBase58(TokenType::NodePublic, publicKey_); + + auto write_buffer = std::make_shared(); + + boost::beast::ostream(*write_buffer) << makeResponse( + !overlay_.peerFinder().config().peerPrivate, + request_, + overlay_.setup().public_ip, + remote_address_.address(), + *sharedValue, + overlay_.setup().networkID, + protocol_, + app_); + + // Write the whole buffer and only start protocol when that's done. + boost::asio::async_write( + *stream_ptr_, + write_buffer->data(), + boost::asio::transfer_all(), + bind_executor( + strand_, + [this, write_buffer, self = shared_from_this()]( + error_code ec, std::size_t bytes_transferred) { + if (!stream_ptr_->next_layer().socket().is_open()) + return; + if (ec == boost::asio::error::operation_aborted) + return; + if (ec) + return fail("onWriteResponse", ec); + if (write_buffer->size() == bytes_transferred) + return createPeer(); + return fail("Failed to write header"); + })); +} + +void +InboundHandoff::fail(std::string const& name, error_code const& ec) +{ + if (socket().is_open()) + { + JLOG(journal_.warn()) + << name << " from " << toBase58(TokenType::NodePublic, publicKey_) + << " at " << remote_address_.to_string() << ": " << ec.message(); + } + close(); +} + +void +InboundHandoff::fail(std::string const& reason) +{ + if (journal_.active(beast::severities::kWarning) && socket().is_open()) + { + auto const n = app_.cluster().member(publicKey_); + JLOG(journal_.warn()) + << (n ? remote_address_.to_string() : *n) << " failed: " << reason; + } + close(); +} + +void +InboundHandoff::close() +{ + if (socket().is_open()) + { + socket().close(); + JLOG(journal_.debug()) << "Closed"; + } +} + +void +InboundHandoff::createPeer() +{ + auto peer = std::make_shared( + app_, + id_, + slot_, + std::move(request_), + publicKey_, + protocol_, + usage_, + std::move(stream_ptr_), + overlay_); + + overlay_.add_active(peer); +} + +InboundHandoff::socket_type& +InboundHandoff::socket() const +{ + return stream_ptr_->next_layer().socket(); +} + +} // namespace ripple \ No newline at end of file diff --git a/src/ripple/overlay/impl/InboundHandoff.h b/src/ripple/overlay/impl/InboundHandoff.h new file mode 100644 index 00000000000..3f3154c3a8f --- /dev/null +++ b/src/ripple/overlay/impl/InboundHandoff.h @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-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_OVERLAY_INBOUNDHANDOFF_H_INCLUDED +#define RIPPLE_OVERLAY_INBOUNDHANDOFF_H_INCLUDED + +#include + +namespace ripple { + +/** Sends HTTP response. Instantiates the inbound peer + * once the response is sent. Maintains all data members + * required for the inbound peer instantiation. + */ +class InboundHandoff : public OverlayImpl::Child, + public std::enable_shared_from_this +{ +private: + using error_code = boost::system::error_code; + using socket_type = boost::asio::ip::tcp::socket; + using middle_type = boost::beast::tcp_stream; + using stream_type = boost::beast::ssl_stream; + using id_t = Peer::id_t; + Application& app_; + id_t const id_; + beast::WrappedSink sink_; + beast::Journal const journal_; + std::unique_ptr stream_ptr_; + boost::asio::strand strand_; + beast::IP::Endpoint const remote_address_; + ProtocolVersion protocol_; + PublicKey const publicKey_; + Resource::Consumer usage_; + std::shared_ptr const slot_; + http_request_type request_; + +public: + virtual ~InboundHandoff() override = default; + + InboundHandoff( + Application& app, + id_t id, + std::shared_ptr const& slot, + http_request_type&& request, + PublicKey const& publicKey, + ProtocolVersion protocol, + Resource::Consumer consumer, + std::unique_ptr&& stream_ptr, + OverlayImpl& overlay); + + // This class isn't meant to be copied + InboundHandoff(InboundHandoff const&) = delete; + InboundHandoff& + operator=(InboundHandoff const&) = delete; + + /** Start the handshake */ + void + run(); + /** Stop the child */ + void + stop() override; + +private: + /** Send upgrade response to the client */ + void + sendResponse(); + /** Instantiate and run the overlay peer */ + void + createPeer(); + /** Log and close */ + void + fail(std::string const& name, error_code const& ec); + /** Log and close */ + void + fail(std::string const& reason); + /** Close connection */ + void + close(); + /** Get underlying socket */ + socket_type& + socket() const; +}; + +} // namespace ripple + +#endif // RIPPLE_OVERLAY_INBOUNDHANDOFF_H_INCLUDED diff --git a/src/ripple/overlay/impl/Message.cpp b/src/ripple/overlay/impl/Message.cpp index b4cb1f192aa..1b434225501 100644 --- a/src/ripple/overlay/impl/Message.cpp +++ b/src/ripple/overlay/impl/Message.cpp @@ -70,7 +70,7 @@ Message::compress() using namespace ripple::compression; auto const messageBytes = buffer_.size() - headerBytes; - auto type = getType(buffer_.data()); + auto type = getType(); bool const compressible = [&] { if (messageBytes <= 70) @@ -221,9 +221,10 @@ Message::getBuffer(Compressed tryCompressed) } int -Message::getType(std::uint8_t const* in) const +Message::getType() const { - int type = (static_cast(*(in + 4)) << 8) + *(in + 5); + int type = + (static_cast(*(buffer_.data() + 4)) << 8) + *(buffer_.data() + 5); return type; } diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 6ed046f0403..c48ab378cb3 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -279,7 +280,7 @@ OverlayImpl::onHandoff( } } - auto const peer = std::make_shared( + auto const ih = std::make_shared( app_, id, slot, @@ -290,18 +291,10 @@ OverlayImpl::onHandoff( std::move(stream_ptr), *this); { - // As we are not on the strand, run() must be called - // while holding the lock, otherwise new I/O can be - // queued after a call to stop(). std::lock_guard lock(mutex_); - { - auto const result = m_peers.emplace(peer->slot(), peer); - assert(result.second); - (void)result.second; - } - list_.emplace(peer.get(), peer); + list_.emplace(ih.get(), ih); - peer->run(); + ih->run(); } handoff.moved = true; return handoff; diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index 0d9b9fc549b..dc23f3325f8 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -66,6 +66,30 @@ std::chrono::milliseconds constexpr peerHighLatency{300}; std::chrono::seconds constexpr peerTimerInterval{60}; } // namespace +std::string +closeReasonToString(protocol::TMCloseReason reason) +{ + switch (reason) + { + case protocol::TMCloseReason::crCHARGE_RESOURCES: + return "Charge: Resources"; + case protocol::TMCloseReason::crMALFORMED_HANDSHAKE1: + return "Malformed handshake data (1)"; + case protocol::TMCloseReason::crMALFORMED_HANDSHAKE2: + return "Malformed handshake data (2)"; + case protocol::TMCloseReason::crMALFORMED_HANDSHAKE3: + return "Malformed handshake data (3)"; + case protocol::TMCloseReason::crLARGE_SENDQUEUE: + return "Large send queue"; + case protocol::TMCloseReason::crNOT_USEFUL: + return "Not useful"; + case protocol::TMCloseReason::crPING_TIMEOUT: + return "Ping timeout"; + default: + return "Unknown reason"; + } +} + PeerImp::PeerImp( Application& app, id_t id, @@ -132,6 +156,11 @@ PeerImp::PeerImp( << " tx reduce-relay enabled " << txReduceRelayEnabled_ << " on " << remote_address_ << " " << id_; + if (auto member = app_.cluster().member(publicKey_)) + { + name_ = *member; + JLOG(journal_.info()) << "Cluster name: " << *member; + } } PeerImp::~PeerImp() @@ -182,7 +211,7 @@ PeerImp::run() closed = parseLedgerHash(iter->value()); if (!closed) - fail("Malformed handshake data (1)"); + fail(protocol::TMCloseReason::crMALFORMED_HANDSHAKE1); } if (auto const iter = headers_.find("Previous-Ledger"); @@ -191,11 +220,11 @@ PeerImp::run() previous = parseLedgerHash(iter->value()); if (!previous) - fail("Malformed handshake data (2)"); + fail(protocol::TMCloseReason::crMALFORMED_HANDSHAKE2); } if (previous && !closed) - fail("Malformed handshake data (3)"); + fail(protocol::TMCloseReason::crMALFORMED_HANDSHAKE3); { std::lock_guard sl(recentLock_); @@ -205,10 +234,7 @@ PeerImp::run() previousLedgerHash_ = *previous; } - if (inbound_) - doAccept(); - else - doProtocolStart(); + doProtocolStart(); // Anything else that needs to be done with the connection should be // done in doProtocolStart @@ -350,7 +376,7 @@ PeerImp::charge(Resource::Charge const& fee) { // Sever the connection overlay_.incPeerDisconnectCharges(); - fail("charge: Resources"); + fail(protocol::TMCloseReason::crCHARGE_RESOURCES); } } @@ -508,6 +534,8 @@ PeerImp::supportsFeature(ProtocolFeature f) const return protocol_ >= make_protocol(2, 2); case ProtocolFeature::LedgerReplay: return ledgerReplayEnabled_; + case ProtocolFeature::StartProtocol: + return protocol_ >= make_protocol(2, 3); } return false; } @@ -600,22 +628,34 @@ PeerImp::close() } void -PeerImp::fail(std::string const& reason) +PeerImp::fail(protocol::TMCloseReason reason) { if (!strand_.running_in_this_thread()) return post( strand_, std::bind( - (void (Peer::*)(std::string const&)) & PeerImp::fail, + (void (Peer::*)(protocol::TMCloseReason)) & PeerImp::fail, shared_from_this(), reason)); if (journal_.active(beast::severities::kWarning) && socket_.is_open()) { std::string const n = name(); JLOG(journal_.warn()) << (n.empty() ? remote_address_.to_string() : n) - << " failed: " << reason; + << " failed: " << closeReasonToString(reason); } - close(); + + // erase all outstanding messages except for the one + // currently being executed + if (send_queue_.size() > 1) + { + decltype(send_queue_) q({send_queue_.front()}); + send_queue_.swap(q); + } + + closeOnWriteComplete_ = true; + protocol::TMGracefulClose tmGC; + tmGC.set_reason(reason); + send(std::make_shared(tmGC, protocol::mtGRACEFUL_CLOSE)); } void @@ -707,7 +747,7 @@ PeerImp::onTimer(error_code const& ec) if (large_sendq_++ >= Tuning::sendqIntervals) { - fail("Large send queue"); + fail(protocol::TMCloseReason::crLARGE_SENDQUEUE); return; } @@ -726,7 +766,7 @@ PeerImp::onTimer(error_code const& ec) (duration > app_.config().MAX_UNKNOWN_TIME))) { overlay_.peerFinder().on_failure(slot_); - fail("Not useful"); + fail(protocol::TMCloseReason::crLARGE_SENDQUEUE); return; } } @@ -734,7 +774,7 @@ PeerImp::onTimer(error_code const& ec) // Already waiting for PONG if (lastPingSeq_) { - fail("Ping Timeout"); + fail(protocol::TMCloseReason::crPING_TIMEOUT); return; } @@ -766,71 +806,6 @@ PeerImp::onShutdown(error_code ec) } //------------------------------------------------------------------------------ -void -PeerImp::doAccept() -{ - assert(read_buffer_.size() == 0); - - JLOG(journal_.debug()) << "doAccept: " << remote_address_; - - auto const sharedValue = makeSharedValue(*stream_ptr_, journal_); - - // This shouldn't fail since we already computed - // the shared value successfully in OverlayImpl - if (!sharedValue) - return fail("makeSharedValue: Unexpected failure"); - - JLOG(journal_.info()) << "Protocol: " << to_string(protocol_); - JLOG(journal_.info()) << "Public Key: " - << toBase58(TokenType::NodePublic, publicKey_); - - if (auto member = app_.cluster().member(publicKey_)) - { - { - std::unique_lock lock{nameMutex_}; - name_ = *member; - } - JLOG(journal_.info()) << "Cluster name: " << *member; - } - - overlay_.activate(shared_from_this()); - - // XXX Set timer: connection is in grace period to be useful. - // XXX Set timer: connection idle (idle may vary depending on connection - // type.) - - auto write_buffer = std::make_shared(); - - boost::beast::ostream(*write_buffer) << makeResponse( - !overlay_.peerFinder().config().peerPrivate, - request_, - overlay_.setup().public_ip, - remote_address_.address(), - *sharedValue, - overlay_.setup().networkID, - protocol_, - app_); - - // Write the whole buffer and only start protocol when that's done. - boost::asio::async_write( - stream_, - write_buffer->data(), - boost::asio::transfer_all(), - bind_executor( - strand_, - [this, write_buffer, self = shared_from_this()]( - error_code ec, std::size_t bytes_transferred) { - if (!socket_.is_open()) - return; - if (ec == boost::asio::error::operation_aborted) - return; - if (ec) - return fail("onWriteResponse", ec); - if (write_buffer->size() == bytes_transferred) - return doProtocolStart(); - return fail("Failed to write header"); - })); -} std::string PeerImp::name() const @@ -854,39 +829,49 @@ PeerImp::doProtocolStart() { onReadMessage(error_code(), 0); - // Send all the validator lists that have been loaded - if (inbound_ && supportsFeature(ProtocolFeature::ValidatorListPropagation)) + bool supportedProtocol = supportsFeature(ProtocolFeature::StartProtocol); + + if (!inbound_) { - app_.validators().for_each_available( - [&](std::string const& manifest, - std::uint32_t version, - std::map const& blobInfos, - PublicKey const& pubKey, - std::size_t maxSequence, - uint256 const& hash) { - ValidatorList::sendValidatorList( - *this, - 0, - pubKey, - maxSequence, - version, - manifest, - blobInfos, - app_.getHashRouter(), - p_journal_); + // Instruct connected inbound peer to start sending + // protocol messages + if (supportedProtocol) + { + JLOG(journal_.debug()) + << "doProtocolStart(): outbound sending mtSTART_PROTOCOL to " + << remote_address_; + protocol::TMStartProtocol tmPS; + tmPS.set_starttime(std::chrono::duration_cast( + clock_type::now().time_since_epoch()) + .count()); + send(std::make_shared(tmPS, protocol::mtSTART_PROTOCOL)); + } + else + { + JLOG(journal_.debug()) << "doProtocolStart(): outbound connected " + "to an older protocol on " + << remote_address_ << " " << protocol_.first + << " " << protocol_.second; + } - // Don't send it next time. - app_.getHashRouter().addSuppressionPeer(hash, id_); - }); - } + if (auto m = overlay_.getManifestsMessage()) + send(m); - 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)); + // Request shard info from peer + protocol::TMGetPeerShardInfoV2 tmGPS; + tmGPS.set_relays(0); + send(std::make_shared( + tmGPS, protocol::mtGET_PEER_SHARD_INFO_V2)); + } + // Backward compatibility with the older protocols + else if (!supportedProtocol) + { + JLOG(journal_.debug()) + << "doProtocolStart(): inbound handling of an older protocol on " + << remote_address_ << " " << protocol_.first << " " + << protocol_.second; + onStartProtocol(); + } setTimer(); } @@ -954,7 +939,11 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytes_transferred) if (!socket_.is_open()) return; if (ec == boost::asio::error::operation_aborted) + { + if (closeOnWriteComplete_) + close(); return; + } if (ec) return fail("onWriteMessage", ec); if (auto stream = journal_.trace()) @@ -968,6 +957,11 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytes_transferred) metrics_.sent.add_message(bytes_transferred); assert(!send_queue_.empty()); + if (send_queue_.front()->getType() == protocol::mtGRACEFUL_CLOSE) + { + close(); + return; + } send_queue_.pop(); if (!send_queue_.empty()) { @@ -2947,6 +2941,69 @@ PeerImp::onMessage(std::shared_ptr const& m) << "onMessage: TMSquelch " << slice << " " << id() << " " << duration; } +void +PeerImp::onStartProtocol() +{ + JLOG(journal_.debug()) << "onStartProtocol(): " << remote_address_; + // Send all the validator lists that have been loaded + if (supportsFeature(ProtocolFeature::ValidatorListPropagation)) + { + app_.validators().for_each_available( + [&](std::string const& manifest, + std::uint32_t version, + std::map const& blobInfos, + PublicKey const& pubKey, + std::size_t maxSequence, + uint256 const& hash) { + ValidatorList::sendValidatorList( + *this, + 0, + pubKey, + maxSequence, + version, + manifest, + blobInfos, + app_.getHashRouter(), + p_journal_); + + // Don't send it next time. + app_.getHashRouter().addSuppressionPeer(hash, id_); + }); + } + + 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)); +} + +void +PeerImp::onMessage(std::shared_ptr const& m) +{ + JLOG(journal_.debug()) << "onMessage(TMStartProtocol): " << remote_address_; + onStartProtocol(); +} + +void +PeerImp::onMessage(const std::shared_ptr& m) +{ + using on_message_fn = + void (PeerImp::*)(std::shared_ptr const&); + if (!strand_.running_in_this_thread()) + return post( + strand_, + std::bind( + (on_message_fn)&PeerImp::onMessage, shared_from_this(), m)); + + JLOG(journal_.info()) << "got graceful close from: " << remote_address_ + << " reason: " << closeReasonToString(m->reason()); + + close(); +} + //-------------------------------------------------------------------------- void diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h index 710ab4d74d6..d922e757946 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/ripple/overlay/impl/PeerImp.h @@ -180,6 +180,8 @@ class PeerImp : public Peer, bool vpReduceRelayEnabled_ = false; bool ledgerReplayEnabled_ = false; LedgerReplayMsgHandler ledgerReplayMsgHandler_; + // close connection when async write is complete + bool closeOnWriteComplete_ = false; friend class OverlayImpl; @@ -235,7 +237,7 @@ class PeerImp : public Peer, /** Create outgoing, handshaked peer. */ // VFALCO legacyPublicKey should be implied by the Slot - template + template PeerImp( Application& app, std::unique_ptr&& stream_ptr, @@ -413,7 +415,7 @@ class PeerImp : public Peer, isHighLatency() const override; void - fail(std::string const& reason); + fail(protocol::TMCloseReason reason); // Return any known shard info from this peer and its sub peers [[nodiscard]] hash_map const @@ -458,9 +460,6 @@ class PeerImp : public Peer, void onShutdown(error_code ec); - void - doAccept(); - std::string name() const; @@ -584,6 +583,10 @@ class PeerImp : public Peer, 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); private: //-------------------------------------------------------------------------- @@ -642,6 +645,9 @@ class PeerImp : public Peer, void processLedgerRequest(std::shared_ptr const& m); + + void + onStartProtocol(); }; //------------------------------------------------------------------------------ diff --git a/src/ripple/overlay/impl/ProtocolMessage.h b/src/ripple/overlay/impl/ProtocolMessage.h index d6fb14bc78c..6071a621db5 100644 --- a/src/ripple/overlay/impl/ProtocolMessage.h +++ b/src/ripple/overlay/impl/ProtocolMessage.h @@ -112,6 +112,10 @@ protocolMessageName(int type) return "get_peer_shard_info_v2"; case protocol::mtPEER_SHARD_INFO_V2: return "peer_shard_info_v2"; + case protocol::mtSTART_PROTOCOL: + return "start_protocol"; + case protocol::mtGRACEFUL_CLOSE: + return "graceful_close"; default: break; } @@ -492,6 +496,14 @@ invokeProtocolMessage( success = detail::invoke( *header, buffers, handler); break; + case protocol::mtSTART_PROTOCOL: + success = detail::invoke( + *header, buffers, handler); + break; + case protocol::mtGRACEFUL_CLOSE: + 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/ripple/overlay/impl/ProtocolVersion.cpp index fbd48474420..8325f6d32fb 100644 --- a/src/ripple/overlay/impl/ProtocolVersion.cpp +++ b/src/ripple/overlay/impl/ProtocolVersion.cpp @@ -37,7 +37,8 @@ namespace ripple { constexpr ProtocolVersion const supportedProtocolList[] { {2, 1}, - {2, 2} + {2, 2}, + {2, 3} }; // clang-format on diff --git a/src/ripple/perflog/impl/PerfLogImp.cpp b/src/ripple/perflog/impl/PerfLogImp.cpp index 4fe6eda2fc5..db5a188fc3e 100644 --- a/src/ripple/perflog/impl/PerfLogImp.cpp +++ b/src/ripple/perflog/impl/PerfLogImp.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -255,7 +255,7 @@ PerfLogImp::openLog() void PerfLogImp::run() { - this_thread::set_name("perflog"); + beast::setCurrentThreadName("perflog"); lastLog_ = system_clock::now(); while (true) diff --git a/src/ripple/proto/ripple.proto b/src/ripple/proto/ripple.proto index d116b992a90..5ea0fcba450 100644 --- a/src/ripple/proto/ripple.proto +++ b/src/ripple/proto/ripple.proto @@ -33,6 +33,8 @@ enum MessageType mtPEER_SHARD_INFO_V2 = 62; mtHAVE_TRANSACTIONS = 63; mtTRANSACTIONS = 64; + mtSTART_PROTOCOL = 65; + mtGRACEFUL_CLOSE = 66; } // token, iterations, target, challenge = issue demand for proof of work @@ -452,3 +454,24 @@ message TMHaveTransactions repeated bytes hashes = 1; } +message TMStartProtocol +{ + required uint64 startTime = 1; +} + +enum TMCloseReason +{ + crMALFORMED_HANDSHAKE1 = 1; + crMALFORMED_HANDSHAKE2 = 2; + crMALFORMED_HANDSHAKE3 = 3; + crCHARGE_RESOURCES = 4; + crLARGE_SENDQUEUE = 5; + crNOT_USEFUL = 6; + crPING_TIMEOUT = 7; +} + +message TMGracefulClose +{ + required TMCloseReason reason = 1; +} + diff --git a/src/ripple/protocol/AccountID.h b/src/ripple/protocol/AccountID.h index 79768eefd7d..27e1f452293 100644 --- a/src/ripple/protocol/AccountID.h +++ b/src/ripple/protocol/AccountID.h @@ -26,6 +26,8 @@ #include #include #include +#include + #include #include #include @@ -123,6 +125,21 @@ initAccountIdCache(std::size_t count); } // namespace ripple +//------------------------------------------------------------------------------ +namespace Json { +template <> +inline ripple::AccountID +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + + std::string const b58 = getOrThrow(v, field); + if (auto const r = parseBase58(b58)) + return *r; + Throw(field.getJsonName(), "AccountID"); +} +} // namespace Json + //------------------------------------------------------------------------------ namespace std { diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index 87323b0dea8..8319b69c8c2 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -78,7 +78,7 @@ enum error_code_i { // unused 27, // unused 28, rpcTXN_NOT_FOUND = 29, - // unused 30, + rpcINVALID_HOTWALLET = 30, // Malformed command rpcINVALID_PARAMS = 31, diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 626b99b8cdb..17aca813f71 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,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 = 61; +static constexpr std::size_t numFeatures = 63; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -348,6 +348,8 @@ 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; } // namespace ripple diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 014ff82ef1b..0c83f7765a4 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -270,6 +271,15 @@ amm(Issue const& issue1, Issue const& issue2) noexcept; Keylet amm(uint256 const& amm) noexcept; +Keylet +bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType); + +Keylet +xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq); + +Keylet +xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq); + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index b9205e7888a..e907e299f52 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -91,6 +91,13 @@ enum LedgerEntryType : std::uint16_t */ 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 @@ -109,6 +116,18 @@ enum LedgerEntryType : std::uint16_t */ 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. diff --git a/src/ripple/protocol/PublicKey.h b/src/ripple/protocol/PublicKey.h index 1173d3416d9..35d214746d2 100644 --- a/src/ripple/protocol/PublicKey.h +++ b/src/ripple/protocol/PublicKey.h @@ -24,7 +24,9 @@ #include #include #include +#include #include + #include #include #include @@ -296,4 +298,27 @@ calcAccountID(PublicKey const& pk); } // namespace ripple +//------------------------------------------------------------------------------ + +namespace Json { +template <> +inline ripple::PublicKey +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + std::string const b58 = getOrThrow(v, field); + if (auto pubKeyBlob = strUnHex(b58); publicKeyType(makeSlice(*pubKeyBlob))) + { + return PublicKey{makeSlice(*pubKeyBlob)}; + } + for (auto const tokenType : + {TokenType::NodePublic, TokenType::AccountPublic}) + { + if (auto const pk = parseBase58(tokenType, b58)) + return *pk; + } + Throw(field.getJsonName(), "PublicKey"); +} +} // namespace Json + #endif diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index e1180bc1c93..7c802a64e1d 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -49,6 +49,7 @@ template class STBitString; template class STInteger; +class STXChainBridge; class STVector256; enum SerializedTypeID { @@ -79,6 +80,7 @@ enum SerializedTypeID { STI_UINT384 = 22, STI_UINT512 = 23, STI_ISSUE = 24, + STI_XCHAIN_BRIDGE = 25, // high level types // cannot be serialized inside other types @@ -318,6 +320,7 @@ using SF_AMOUNT = TypedField; using SF_ISSUE = TypedField; using SF_VL = TypedField; using SF_VECTOR256 = TypedField; +using SF_XCHAIN_BRIDGE = TypedField; //------------------------------------------------------------------------------ @@ -332,6 +335,7 @@ extern SField const sfMetadata; extern SF_UINT8 const sfCloseResolution; extern SF_UINT8 const sfMethod; extern SF_UINT8 const sfTransactionResult; +extern SF_UINT8 const sfWasLockingChainSend; // 8-bit integers (uncommon) extern SF_UINT8 const sfTickSize; @@ -424,6 +428,9 @@ 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; // 128-bit extern SF_UINT128 const sfEmailHash; @@ -499,6 +506,8 @@ extern SF_AMOUNT const sfLPTokenIn; 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; @@ -541,6 +550,12 @@ 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; @@ -548,6 +563,11 @@ extern SField const sfPaths; // 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; @@ -582,6 +602,10 @@ 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 @@ -604,6 +628,8 @@ extern SField const sfDisabledValidators; extern SField const sfHookExecutions; extern SField const sfHookParameters; extern SField const sfHookGrants; +extern SField const sfXChainClaimAttestations; +extern SField const sfXChainCreateAccountAttestations; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/STAccount.h b/src/ripple/protocol/STAccount.h index fb6d1c81bfe..c622a7c5eef 100644 --- a/src/ripple/protocol/STAccount.h +++ b/src/ripple/protocol/STAccount.h @@ -64,7 +64,7 @@ class STAccount final : public STBase STAccount& operator=(AccountID const& value); - AccountID + AccountID const& value() const noexcept; void @@ -86,7 +86,7 @@ STAccount::operator=(AccountID const& value) return *this; } -inline AccountID +inline AccountID const& STAccount::value() const noexcept { return value_; @@ -99,6 +99,36 @@ STAccount::setValue(AccountID const& v) default_ = false; } +inline bool +operator==(STAccount const& lhs, STAccount const& rhs) +{ + return lhs.value() == rhs.value(); +} + +inline auto +operator<(STAccount const& lhs, STAccount const& rhs) +{ + return lhs.value() < rhs.value(); +} + +inline bool +operator==(STAccount const& lhs, AccountID const& rhs) +{ + return lhs.value() == rhs; +} + +inline auto +operator<(STAccount const& lhs, AccountID const& rhs) +{ + return lhs.value() < rhs; +} + +inline auto +operator<(AccountID const& lhs, STAccount const& rhs) +{ + return lhs < rhs.value(); +} + } // namespace ripple #endif diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index 63f97bb48fe..1de1568ae03 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace ripple { @@ -125,6 +126,8 @@ class STAmount final : public STBase, public CountedObject explicit STAmount(std::uint64_t mantissa = 0, bool negative = false); + explicit STAmount(SField const& name, STAmount const& amt); + STAmount( Issue const& issue, std::uint64_t mantissa = 0, @@ -582,4 +585,18 @@ class STAmountSO } // namespace ripple +//------------------------------------------------------------------------------ +namespace Json { +template <> +inline ripple::STAmount +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + Json::StaticString const& key = field.getJsonName(); + if (!v.isMember(key)) + Throw(key); + Json::Value const& inner = v[key]; + return amountFromJson(field, inner); +} +} // namespace Json #endif diff --git a/src/ripple/protocol/STArray.h b/src/ripple/protocol/STArray.h index 9501307c2c9..8c3833d3fc8 100644 --- a/src/ripple/protocol/STArray.h +++ b/src/ripple/protocol/STArray.h @@ -61,7 +61,7 @@ class STArray final : public STBase, public CountedObject STArray& operator=(STArray&&); - STArray(SField const& f, int n); + STArray(SField const& f, std::size_t n); STArray(SerialIter& sit, SField const& f, int depth = 0); explicit STArray(int n); explicit STArray(SField const& f); diff --git a/src/ripple/protocol/STIssue.h b/src/ripple/protocol/STIssue.h index c24cf6a30b5..80a37b305fc 100644 --- a/src/ripple/protocol/STIssue.h +++ b/src/ripple/protocol/STIssue.h @@ -100,6 +100,10 @@ STIssue::value() const noexcept inline void STIssue::setIssue(Issue const& issue) { + if (isXRP(issue_.currency) != isXRP(issue_.account)) + Throw( + "invalid issue: currency and account native mismatch"); + issue_ = issue; } diff --git a/src/ripple/protocol/STXChainBridge.h b/src/ripple/protocol/STXChainBridge.h new file mode 100644 index 00000000000..44cd6a480f7 --- /dev/null +++ b/src/ripple/protocol/STXChainBridge.h @@ -0,0 +1,235 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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_STXCHAINBRIDGE_H_INCLUDED +#define RIPPLE_PROTOCOL_STXCHAINBRIDGE_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +class Serializer; +class STObject; + +class STXChainBridge final : public STBase +{ + STAccount lockingChainDoor_{sfLockingChainDoor}; + STIssue lockingChainIssue_{sfLockingChainIssue}; + STAccount issuingChainDoor_{sfIssuingChainDoor}; + STIssue issuingChainIssue_{sfIssuingChainIssue}; + +public: + using value_type = STXChainBridge; + + enum class ChainType { locking, issuing }; + + static ChainType + otherChain(ChainType ct); + + static ChainType + srcChain(bool wasLockingChainSend); + + static ChainType + dstChain(bool wasLockingChainSend); + + STXChainBridge(); + + explicit STXChainBridge(SField const& name); + + STXChainBridge(STXChainBridge const& rhs) = default; + + STXChainBridge(STObject const& o); + + STXChainBridge( + AccountID const& srcChainDoor, + Issue const& srcChainIssue, + AccountID const& dstChainDoor, + Issue const& dstChainIssue); + + explicit STXChainBridge(Json::Value const& v); + + explicit STXChainBridge(SField const& name, Json::Value const& v); + + explicit STXChainBridge(SerialIter& sit, SField const& name); + + STXChainBridge& + operator=(STXChainBridge const& rhs) = default; + + std::string + getText() const override; + + STObject + toSTObject() const; + + AccountID const& + lockingChainDoor() const; + + Issue const& + lockingChainIssue() const; + + AccountID const& + issuingChainDoor() const; + + Issue const& + issuingChainIssue() const; + + AccountID const& + door(ChainType ct) const; + + Issue const& + issue(ChainType ct) const; + + SerializedTypeID + getSType() const override; + + Json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(const STBase& t) const override; + + bool + isDefault() const override; + + value_type const& + value() const noexcept; + +private: + static std::unique_ptr + construct(SerialIter&, SField const& name); + + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend bool + operator==(STXChainBridge const& lhs, STXChainBridge const& rhs); + + friend bool + operator<(STXChainBridge const& lhs, STXChainBridge const& rhs); +}; + +inline bool +operator==(STXChainBridge const& lhs, STXChainBridge const& rhs) +{ + return std::tie( + lhs.lockingChainDoor_, + lhs.lockingChainIssue_, + lhs.issuingChainDoor_, + lhs.issuingChainIssue_) == + std::tie( + rhs.lockingChainDoor_, + rhs.lockingChainIssue_, + rhs.issuingChainDoor_, + rhs.issuingChainIssue_); +} + +inline bool +operator<(STXChainBridge const& lhs, STXChainBridge const& rhs) +{ + return std::tie( + lhs.lockingChainDoor_, + lhs.lockingChainIssue_, + lhs.issuingChainDoor_, + lhs.issuingChainIssue_) < + std::tie( + rhs.lockingChainDoor_, + rhs.lockingChainIssue_, + rhs.issuingChainDoor_, + rhs.issuingChainIssue_); +} + +inline AccountID const& +STXChainBridge::lockingChainDoor() const +{ + return lockingChainDoor_.value(); +}; + +inline Issue const& +STXChainBridge::lockingChainIssue() const +{ + return lockingChainIssue_.value(); +}; + +inline AccountID const& +STXChainBridge::issuingChainDoor() const +{ + return issuingChainDoor_.value(); +}; + +inline Issue const& +STXChainBridge::issuingChainIssue() const +{ + return issuingChainIssue_.value(); +}; + +inline STXChainBridge::value_type const& +STXChainBridge::value() const noexcept +{ + return *this; +} + +inline AccountID const& +STXChainBridge::door(ChainType ct) const +{ + if (ct == ChainType::locking) + return lockingChainDoor(); + return issuingChainDoor(); +} + +inline Issue const& +STXChainBridge::issue(ChainType ct) const +{ + if (ct == ChainType::locking) + return lockingChainIssue(); + return issuingChainIssue(); +} + +inline STXChainBridge::ChainType +STXChainBridge::otherChain(ChainType ct) +{ + if (ct == ChainType::locking) + return ChainType::issuing; + return ChainType::locking; +} + +inline STXChainBridge::ChainType +STXChainBridge::srcChain(bool wasLockingChainSend) +{ + if (wasLockingChainSend) + return ChainType::locking; + return ChainType::issuing; +} + +inline STXChainBridge::ChainType +STXChainBridge::dstChain(bool wasLockingChainSend) +{ + if (wasLockingChainSend) + return ChainType::issuing; + return ChainType::locking; +} + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index edae58d83c9..4cabac1dd13 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -125,6 +125,13 @@ enum TEMcodes : TERUnderlyingType { temBAD_NFTOKEN_TRANSFER_FEE, temBAD_AMM_TOKENS, + + temXCHAIN_EQUAL_DOOR_ACCOUNTS, + temXCHAIN_BAD_PROOF, + temXCHAIN_BRIDGE_BAD_ISSUES, + temXCHAIN_BRIDGE_NONDOOR_OWNER, + temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT, + temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, }; //------------------------------------------------------------------------------ @@ -303,7 +310,24 @@ enum TECcodes : TERUnderlyingType { tecAMM_EMPTY = 166, tecAMM_NOT_EMPTY = 167, tecAMM_ACCOUNT = 168, - tecINCOMPLETE = 169 + tecINCOMPLETE = 169, + tecXCHAIN_BAD_TRANSFER_ISSUE = 170, + tecXCHAIN_NO_CLAIM_ID = 171, + tecXCHAIN_BAD_CLAIM_ID = 172, + tecXCHAIN_CLAIM_NO_QUORUM = 173, + tecXCHAIN_PROOF_UNKNOWN_KEY = 174, + tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE = 175, + tecXCHAIN_WRONG_CHAIN = 176, + tecXCHAIN_REWARD_MISMATCH = 177, + tecXCHAIN_NO_SIGNERS_LIST = 178, + tecXCHAIN_SENDING_ACCOUNT_MISMATCH = 179, + tecXCHAIN_INSUFF_CREATE_AMOUNT = 180, + tecXCHAIN_ACCOUNT_CREATE_PAST = 181, + tecXCHAIN_ACCOUNT_CREATE_TOO_MANY = 182, + tecXCHAIN_PAYMENT_FAILED = 183, + tecXCHAIN_SELF_COMMIT = 184, + tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 185, + tecXCHAIN_CREATE_ACCOUNT_DISABLED = 186, }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 39680e41d95..ba2b97562db 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -181,6 +181,10 @@ constexpr std::uint32_t tfDepositSubTx = constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx); constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx); +// BridgeModify flags: +constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; +constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); + // clang-format on } // namespace ripple diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 2d7ba40c44c..d8785f3ea1d 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -160,6 +160,31 @@ enum TxType : std::uint16_t /** 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 system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html diff --git a/src/ripple/protocol/XChainAttestations.h b/src/ripple/protocol/XChainAttestations.h new file mode 100644 index 00000000000..b99a0b59a4b --- /dev/null +++ b/src/ripple/protocol/XChainAttestations.h @@ -0,0 +1,504 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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_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 + +namespace ripple { + +namespace Attestations { + +struct AttestationBase +{ + // Account associated with the public key + AccountID attestationSignerAccount; + // Public key from the witness server attesting to the event + PublicKey publicKey; + // Signature from the witness server attesting to the event + Buffer signature; + // Account on the sending chain that triggered the event (sent the + // transaction) + AccountID sendingAccount; + // Amount transfered on the sending chain + STAmount sendingAmount; + // Account on the destination chain that collects a share of the attestation + // reward + AccountID rewardAccount; + // Amount was transfered on the locking chain + bool wasLockingChainSend; + + explicit AttestationBase( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_); + + AttestationBase(AttestationBase const&) = default; + + virtual ~AttestationBase() = default; + + AttestationBase& + operator=(AttestationBase const&) = default; + + // verify that the signature attests to the data. + bool + verify(STXChainBridge const& bridge) const; + +protected: + explicit AttestationBase(STObject const& o); + explicit AttestationBase(Json::Value const& v); + + [[nodiscard]] static bool + equalHelper(AttestationBase const& lhs, AttestationBase const& rhs); + + [[nodiscard]] static bool + sameEventHelper(AttestationBase const& lhs, AttestationBase const& rhs); + + void + addHelper(STObject& o) const; + +private: + [[nodiscard]] virtual std::vector + message(STXChainBridge const& bridge) const = 0; +}; + +// Attest to a regular cross-chain transfer +struct AttestationClaim : AttestationBase +{ + std::uint64_t claimID; + std::optional dst; + + explicit AttestationClaim( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t claimID_, + std::optional const& dst_); + + explicit AttestationClaim( + STXChainBridge const& bridge, + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + SecretKey const& secretKey_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t claimID_, + std::optional const& dst_); + + explicit AttestationClaim(STObject const& o); + explicit AttestationClaim(Json::Value const& v); + + [[nodiscard]] STObject + toSTObject() const; + + // return true if the two attestations attest to the same thing + [[nodiscard]] bool + sameEvent(AttestationClaim const& rhs) const; + + [[nodiscard]] static std::vector + message( + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst); + + [[nodiscard]] bool + validAmounts() const; + +private: + [[nodiscard]] std::vector + message(STXChainBridge const& bridge) const override; + + friend bool + operator==(AttestationClaim const& lhs, AttestationClaim const& rhs); +}; + +struct CmpByClaimID +{ + bool + operator()(AttestationClaim const& lhs, AttestationClaim const& rhs) const + { + return lhs.claimID < rhs.claimID; + } +}; + +// Attest to a cross-chain transfer that creates an account +struct AttestationCreateAccount : AttestationBase +{ + // createCount on the sending chain. This is the value of the `CreateCount` + // field of the bridge on the sending chain when the transaction was + // executed. + std::uint64_t createCount; + // Account to create on the destination chain + AccountID toCreate; + // Total amount of the reward pool + STAmount rewardAmount; + + explicit AttestationCreateAccount(STObject const& o); + + explicit AttestationCreateAccount(Json::Value const& v); + + explicit AttestationCreateAccount( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t createCount_, + AccountID const& toCreate_); + + explicit AttestationCreateAccount( + STXChainBridge const& bridge, + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + SecretKey const& secretKey_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t createCount_, + AccountID const& toCreate_); + + [[nodiscard]] STObject + toSTObject() const; + + // return true if the two attestations attest to the same thing + [[nodiscard]] bool + sameEvent(AttestationCreateAccount const& rhs) const; + + friend bool + operator==( + AttestationCreateAccount const& lhs, + AttestationCreateAccount const& rhs); + + [[nodiscard]] static std::vector + message( + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + STAmount const& rewardAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + AccountID const& dst); + + [[nodiscard]] bool + validAmounts() const; + +private: + [[nodiscard]] std::vector + message(STXChainBridge const& bridge) const override; +}; + +struct CmpByCreateCount +{ + bool + operator()( + AttestationCreateAccount const& lhs, + AttestationCreateAccount const& rhs) const + { + return lhs.createCount < rhs.createCount; + } +}; + +}; // namespace Attestations + +// Result when checking when two attestation match. +enum class AttestationMatch { + // One of the fields doesn't match, and it isn't the dst field + nonDstMismatch, + // all of the fields match, except the dst field + matchExceptDst, + // all of the fields match + match +}; + +struct XChainClaimAttestation +{ + using TSignedAttestation = Attestations::AttestationClaim; + static SField const& ArrayFieldName; + + AccountID keyAccount; + PublicKey publicKey; + STAmount amount; + AccountID rewardAccount; + bool wasLockingChainSend; + std::optional dst; + + struct MatchFields + { + STAmount amount; + bool wasLockingChainSend; + std::optional dst; + MatchFields(TSignedAttestation const& att); + MatchFields( + STAmount const& a, + bool b, + std::optional const& d) + : amount{a}, wasLockingChainSend{b}, dst{d} + { + } + }; + + explicit XChainClaimAttestation( + AccountID const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::optional const& dst); + + explicit XChainClaimAttestation( + STAccount const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + STAccount const& rewardAccount_, + bool wasLockingChainSend_, + std::optional const& dst); + + explicit XChainClaimAttestation(TSignedAttestation const& claimAtt); + + explicit XChainClaimAttestation(STObject const& o); + + explicit XChainClaimAttestation(Json::Value const& v); + + AttestationMatch + match(MatchFields const& rhs) const; + + [[nodiscard]] STObject + toSTObject() const; + + friend bool + operator==( + XChainClaimAttestation const& lhs, + XChainClaimAttestation const& rhs); +}; + +struct XChainCreateAccountAttestation +{ + using TSignedAttestation = Attestations::AttestationCreateAccount; + static SField const& ArrayFieldName; + + AccountID keyAccount; + PublicKey publicKey; + STAmount amount; + STAmount rewardAmount; + AccountID rewardAccount; + bool wasLockingChainSend; + AccountID dst; + + struct MatchFields + { + STAmount amount; + STAmount rewardAmount; + bool wasLockingChainSend; + AccountID dst; + + MatchFields(TSignedAttestation const& att); + }; + + explicit XChainCreateAccountAttestation( + AccountID const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + AccountID const& dst_); + + explicit XChainCreateAccountAttestation(TSignedAttestation const& claimAtt); + + explicit XChainCreateAccountAttestation(STObject const& o); + + explicit XChainCreateAccountAttestation(Json::Value const& v); + + [[nodiscard]] STObject + toSTObject() const; + + AttestationMatch + match(MatchFields const& rhs) const; + + friend bool + operator==( + XChainCreateAccountAttestation const& lhs, + XChainCreateAccountAttestation const& rhs); +}; + +// Attestations from witness servers for a particular claimid and bridge. +// Only one attestation per signature is allowed. +template +class XChainAttestationsBase +{ +public: + using AttCollection = std::vector; + +private: + // Set a max number of allowed attestations to limit the amount of memory + // allocated and processing time. This number is much larger than the actual + // number of attestation a server would ever expect. + static constexpr std::uint32_t maxAttestations = 256; + AttCollection attestations_; + +protected: + // Prevent slicing to the base class + ~XChainAttestationsBase() = default; + +public: + XChainAttestationsBase() = default; + XChainAttestationsBase(XChainAttestationsBase const& rhs) = default; + XChainAttestationsBase& + operator=(XChainAttestationsBase const& rhs) = default; + + explicit XChainAttestationsBase(AttCollection&& sigs); + + explicit XChainAttestationsBase(Json::Value const& v); + + explicit XChainAttestationsBase(STArray const& arr); + + [[nodiscard]] STArray + toSTArray() const; + + typename AttCollection::const_iterator + begin() const; + + typename AttCollection::const_iterator + end() const; + + typename AttCollection::iterator + begin(); + + typename AttCollection::iterator + end(); + + template + std::size_t + erase_if(F&& f); + + std::size_t + size() const; + + bool + empty() const; + + AttCollection const& + attestations() const; + + template + void + emplace_back(T&& att); +}; + +template +[[nodiscard]] inline bool +operator==( + XChainAttestationsBase const& lhs, + XChainAttestationsBase const& rhs) +{ + return lhs.attestations() == rhs.attestations(); +} + +template +inline typename XChainAttestationsBase::AttCollection const& +XChainAttestationsBase::attestations() const +{ + return attestations_; +}; + +template +template +inline void +XChainAttestationsBase::emplace_back(T&& att) +{ + attestations_.emplace_back(std::forward(att)); +}; + +template +template +inline std::size_t +XChainAttestationsBase::erase_if(F&& f) +{ + return std::erase_if(attestations_, std::forward(f)); +} + +template +inline std::size_t +XChainAttestationsBase::size() const +{ + return attestations_.size(); +} + +template +inline bool +XChainAttestationsBase::empty() const +{ + return attestations_.empty(); +} + +class XChainClaimAttestations final + : public XChainAttestationsBase +{ + using TBase = XChainAttestationsBase; + using TBase::TBase; +}; + +class XChainCreateAccountAttestations final + : public XChainAttestationsBase +{ + using TBase = XChainAttestationsBase; + using TBase::TBase; +}; + +} // namespace ripple + +#endif // STXCHAINATTESTATIONS_H_ diff --git a/src/ripple/protocol/impl/BuildInfo.cpp b/src/ripple/protocol/impl/BuildInfo.cpp index f6ca21e43e4..a0f43df8b64 100644 --- a/src/ripple/protocol/impl/BuildInfo.cpp +++ b/src/ripple/protocol/impl/BuildInfo.cpp @@ -33,7 +33,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "1.12.0" +char const* const versionString = "2.0.0-b2" // clang-format on #if defined(DEBUG) || defined(SANITIZER) diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 603888827b2..319bd8e28c2 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -76,6 +76,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcINTERNAL, "internal", "Internal error.", 500}, {rpcINVALID_LGR_RANGE, "invalidLgrRange", "Ledger range is invalid.", 400}, {rpcINVALID_PARAMS, "invalidParams", "Invalid parameters.", 400}, + {rpcINVALID_HOTWALLET, "invalidHotWallet", "Invalid hotwallet.", 400}, {rpcISSUE_MALFORMED, "issueMalformed", "Issue is malformed.", 400}, {rpcJSON_RPC, "json_rpc", "JSON-RPC transport error.", 500}, {rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid.", 400}, diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index b9710ebbc69..6a3430f4f50 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -455,6 +455,8 @@ REGISTER_FIX (fixNFTokenRemint, Supported::yes, VoteBehavior::De 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); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index 1140c42d3ef..3fef856b365 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -18,9 +18,13 @@ //============================================================================== #include +#include +#include +#include #include #include #include + #include #include @@ -64,6 +68,9 @@ enum class LedgerNameSpace : std::uint16_t { NFTOKEN_BUY_OFFERS = 'h', NFTOKEN_SELL_OFFERS = 'i', AMM = 'A', + BRIDGE = 'H', + XCHAIN_CLAIM_ID = 'Q', + XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. @@ -389,6 +396,47 @@ amm(uint256 const& id) noexcept return {ltAMM, id}; } +Keylet +bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType) +{ + // A door account can support multiple bridges. On the locking chain + // there can only be one bridge per lockingChainCurrency. On the issuing + // chain there can only be one bridge per issuingChainCurrency. + auto const& issue = bridge.issue(chainType); + return { + ltBRIDGE, + indexHash( + LedgerNameSpace::BRIDGE, bridge.door(chainType), issue.currency)}; +} + +Keylet +xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq) +{ + return { + ltXCHAIN_OWNED_CLAIM_ID, + indexHash( + LedgerNameSpace::XCHAIN_CLAIM_ID, + bridge.lockingChainDoor(), + bridge.lockingChainIssue(), + bridge.issuingChainDoor(), + bridge.issuingChainIssue(), + seq)}; +} + +Keylet +xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq) +{ + return { + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, + indexHash( + LedgerNameSpace::XCHAIN_CREATE_ACCOUNT_CLAIM_ID, + bridge.lockingChainDoor(), + bridge.lockingChainIssue(), + bridge.issuingChainDoor(), + bridge.issuingChainIssue(), + seq)}; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index ba1a40a87ee..58f4392f536 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -18,6 +18,8 @@ //============================================================================== #include +#include +#include namespace ripple { @@ -70,12 +72,62 @@ InnerObjectFormats::InnerObjectFormats() add(sfAuctionSlot.jsonName.c_str(), sfAuctionSlot.getCode(), + {{sfAccount, soeREQUIRED}, + {sfExpiration, soeREQUIRED}, + {sfDiscountedFee, soeDEFAULT}, + {sfPrice, soeREQUIRED}, + {sfAuthAccounts, soeOPTIONAL}}); + + add(sfXChainClaimAttestationCollectionElement.jsonName.c_str(), + sfXChainClaimAttestationCollectionElement.getCode(), { + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfSignature, soeREQUIRED}, + {sfAmount, soeREQUIRED}, {sfAccount, soeREQUIRED}, - {sfExpiration, soeREQUIRED}, - {sfDiscountedFee, soeDEFAULT}, - {sfPrice, soeREQUIRED}, - {sfAuthAccounts, soeOPTIONAL}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + {sfXChainClaimID, soeREQUIRED}, + {sfDestination, soeOPTIONAL}, + }); + + add(sfXChainCreateAccountAttestationCollectionElement.jsonName.c_str(), + sfXChainCreateAccountAttestationCollectionElement.getCode(), + { + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfSignature, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfAccount, soeREQUIRED}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + {sfXChainAccountCreateCount, soeREQUIRED}, + {sfDestination, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + }); + + add(sfXChainClaimProofSig.jsonName.c_str(), + sfXChainClaimProofSig.getCode(), + { + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + {sfDestination, soeOPTIONAL}, + }); + + add(sfXChainCreateAccountProofSig.jsonName.c_str(), + sfXChainCreateAccountProofSig.getCode(), + { + {sfAttestationSignerAccount, soeREQUIRED}, + {sfPublicKey, soeREQUIRED}, + {sfAmount, soeREQUIRED}, + {sfSignatureReward, soeREQUIRED}, + {sfAttestationRewardAccount, soeREQUIRED}, + {sfWasLockingChainSend, soeREQUIRED}, + {sfDestination, soeREQUIRED}, }); add(sfAuthAccount.jsonName.c_str(), diff --git a/src/ripple/protocol/impl/Issue.cpp b/src/ripple/protocol/impl/Issue.cpp index 6d0be069a8a..623ce24bb15 100644 --- a/src/ripple/protocol/impl/Issue.cpp +++ b/src/ripple/protocol/impl/Issue.cpp @@ -79,8 +79,8 @@ issueFromJson(Json::Value const& v) { if (!v.isObject()) { - Throw( - "issueFromJson can only be specified with a 'object' Json value"); + Throw( + "issueFromJson can only be specified with an 'object' Json value"); } Json::Value const curStr = v[jss::currency]; diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index d9e7ca178c0..e5313a8c1f9 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -283,6 +283,49 @@ LedgerFormats::LedgerFormats() }, 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); // clang-format on } diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 95b6d123941..517971dbf07 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -96,6 +96,7 @@ CONSTRUCT_TYPED_SFIELD(sfTransactionResult, "TransactionResult", UINT8, 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); @@ -180,10 +181,13 @@ CONSTRUCT_TYPED_SFIELD(sfNFTokenOfferNode, "NFTokenOfferNode", UINT64, 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(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); // 128-bit CONSTRUCT_TYPED_SFIELD(sfEmailHash, "EmailHash", UINT128, 1); @@ -263,7 +267,8 @@ CONSTRUCT_TYPED_SFIELD(sfLPTokenOut, "LPTokenOut", AMOUNT, CONSTRUCT_TYPED_SFIELD(sfLPTokenIn, "LPTokenIn", AMOUNT, 26); CONSTRUCT_TYPED_SFIELD(sfEPrice, "EPrice", AMOUNT, 27); CONSTRUCT_TYPED_SFIELD(sfPrice, "Price", AMOUNT, 28); -// 29 and 30 are reserved for side-chains +CONSTRUCT_TYPED_SFIELD(sfSignatureReward, "SignatureReward", AMOUNT, 29); +CONSTRUCT_TYPED_SFIELD(sfMinAccountCreateAmount, "MinAccountCreateAmount", AMOUNT, 30); CONSTRUCT_TYPED_SFIELD(sfLPTokenBalance, "LPTokenBalance", AMOUNT, 31); // variable length (common) @@ -308,6 +313,12 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT, // 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); @@ -319,9 +330,14 @@ CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR25 CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1); // 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); @@ -351,6 +367,16 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 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); // array of objects // ARRAY/1 is reserved for end of array @@ -372,7 +398,13 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18); CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19); CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20); -// 21-24 is reserved for side-chains +CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimAttestations, + "XChainClaimAttestations", + ARRAY, 21); +CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestations, + "XChainCreateAccountAttestations", + ARRAY, 22); +// 23 and 24 are unused and available for use CONSTRUCT_UNTYPED_SFIELD(sfAuthAccounts, "AuthAccounts", ARRAY, 25); // clang-format on diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/ripple/protocol/impl/STAmount.cpp index 877a19813cf..201bcb6b681 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/ripple/protocol/impl/STAmount.cpp @@ -239,6 +239,17 @@ STAmount::STAmount( canonicalize(); } +STAmount::STAmount(SField const& name, STAmount const& from) + : STBase(name) + , mIssue(from.mIssue) + , mValue(from.mValue) + , mOffset(from.mOffset) + , mIsNegative(from.mIsNegative) +{ + assert(mValue <= std::numeric_limits::max()); + canonicalize(); +} + //------------------------------------------------------------------------------ STAmount::STAmount(std::uint64_t mantissa, bool negative) diff --git a/src/ripple/protocol/impl/STArray.cpp b/src/ripple/protocol/impl/STArray.cpp index 7b1e5d9f249..7ee3da1ff1a 100644 --- a/src/ripple/protocol/impl/STArray.cpp +++ b/src/ripple/protocol/impl/STArray.cpp @@ -46,7 +46,7 @@ STArray::STArray(SField const& f) : STBase(f) { } -STArray::STArray(SField const& f, int n) : STBase(f) +STArray::STArray(SField const& f, std::size_t n) : STBase(f) { v_.reserve(n); } diff --git a/src/ripple/protocol/impl/STIssue.cpp b/src/ripple/protocol/impl/STIssue.cpp index 19b9ec207c1..7b12247337c 100644 --- a/src/ripple/protocol/impl/STIssue.cpp +++ b/src/ripple/protocol/impl/STIssue.cpp @@ -19,7 +19,18 @@ #include +#include #include +#include +#include + +#include +#include +#include + +#include +#include +#include namespace ripple { diff --git a/src/ripple/protocol/impl/STParsedJSON.cpp b/src/ripple/protocol/impl/STParsedJSON.cpp index 6c38e377c8f..fb960e6f11e 100644 --- a/src/ripple/protocol/impl/STParsedJSON.cpp +++ b/src/ripple/protocol/impl/STParsedJSON.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -33,10 +34,13 @@ #include #include #include +#include #include #include #include +#include #include + #include #include #include @@ -742,6 +746,20 @@ parseLeaf( return ret; } break; + + case STI_XCHAIN_BRIDGE: + try + { + ret = detail::make_stvar( + STXChainBridge(field, value)); + } + catch (std::exception const&) + { + error = invalid_data(json_name, fieldName); + return ret; + } + break; + default: error = bad_type(json_name, fieldName); return ret; @@ -951,6 +969,7 @@ parseArray( if (ret->getFName().fieldType != STI_OBJECT) { + ss << "Field type: " << ret->getFName().fieldType << " "; error = non_object_in_array(ss.str(), i); return std::nullopt; } diff --git a/src/ripple/protocol/impl/STVar.cpp b/src/ripple/protocol/impl/STVar.cpp index ee0a6cb17a4..2ec55ccaf03 100644 --- a/src/ripple/protocol/impl/STVar.cpp +++ b/src/ripple/protocol/impl/STVar.cpp @@ -17,6 +17,8 @@ */ //============================================================================== +#include + #include #include #include @@ -29,7 +31,8 @@ #include #include #include -#include +#include +#include namespace ripple { namespace detail { @@ -161,6 +164,9 @@ STVar::STVar(SerialIter& sit, SField const& name, int depth) case STI_ISSUE: construct(sit, name); return; + case STI_XCHAIN_BRIDGE: + construct(sit, name); + return; default: Throw("Unknown object type"); } @@ -219,6 +225,9 @@ STVar::STVar(SerializedTypeID id, SField const& name) case STI_ISSUE: construct(name); return; + case STI_XCHAIN_BRIDGE: + construct(name); + return; default: Throw("Unknown object type"); } diff --git a/src/ripple/protocol/impl/STVar.h b/src/ripple/protocol/impl/STVar.h index b156534f827..73863edbbe0 100644 --- a/src/ripple/protocol/impl/STVar.h +++ b/src/ripple/protocol/impl/STVar.h @@ -125,7 +125,7 @@ class STVar void construct(Args&&... args) { - if (sizeof(T) > max_size) + if constexpr (sizeof(T) > max_size) p_ = new T(std::forward(args)...); else p_ = new (&d_) T(std::forward(args)...); diff --git a/src/ripple/protocol/impl/STXChainBridge.cpp b/src/ripple/protocol/impl/STXChainBridge.cpp new file mode 100644 index 00000000000..8ff19ca7e3b --- /dev/null +++ b/src/ripple/protocol/impl/STXChainBridge.cpp @@ -0,0 +1,227 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 { + +STXChainBridge::STXChainBridge() : STBase{sfXChainBridge} +{ +} + +STXChainBridge::STXChainBridge(SField const& name) : STBase{name} +{ +} + +STXChainBridge::STXChainBridge( + AccountID const& srcChainDoor, + Issue const& srcChainIssue, + AccountID const& dstChainDoor, + Issue const& dstChainIssue) + : STBase{sfXChainBridge} + , lockingChainDoor_{sfLockingChainDoor, srcChainDoor} + , lockingChainIssue_{sfLockingChainIssue, srcChainIssue} + , issuingChainDoor_{sfIssuingChainDoor, dstChainDoor} + , issuingChainIssue_{sfIssuingChainIssue, dstChainIssue} +{ +} + +STXChainBridge::STXChainBridge(STObject const& o) + : STBase{sfXChainBridge} + , lockingChainDoor_{sfLockingChainDoor, o[sfLockingChainDoor]} + , lockingChainIssue_{sfLockingChainIssue, o[sfLockingChainIssue]} + , issuingChainDoor_{sfIssuingChainDoor, o[sfIssuingChainDoor]} + , issuingChainIssue_{sfIssuingChainIssue, o[sfIssuingChainIssue]} +{ +} + +STXChainBridge::STXChainBridge(Json::Value const& v) + : STXChainBridge{sfXChainBridge, v} +{ +} + +STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v) + : STBase{name} +{ + if (!v.isObject()) + { + Throw( + "STXChainBridge can only be specified with a 'object' Json value"); + } + + auto checkExtra = [](Json::Value const& v) { + static auto const jbridge = + ripple::STXChainBridge().getJson(ripple::JsonOptions::none); + for (auto it = v.begin(); it != v.end(); ++it) + { + std::string const name = it.memberName(); + if (!jbridge.isMember(name)) + { + Throw( + "STXChainBridge extra field detected: " + name); + } + } + return true; + }; + checkExtra(v); + + Json::Value const& lockingChainDoorStr = + v[sfLockingChainDoor.getJsonName()]; + Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()]; + Json::Value const& issuingChainDoorStr = + v[sfIssuingChainDoor.getJsonName()]; + Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()]; + + if (!lockingChainDoorStr.isString()) + { + Throw( + "STXChainBridge LockingChainDoor must be a string Json value"); + } + if (!issuingChainDoorStr.isString()) + { + Throw( + "STXChainBridge IssuingChainDoor must be a string Json value"); + } + + auto const lockingChainDoor = + parseBase58(lockingChainDoorStr.asString()); + auto const issuingChainDoor = + parseBase58(issuingChainDoorStr.asString()); + if (!lockingChainDoor) + { + Throw( + "STXChainBridge LockingChainDoor must be a valid account"); + } + if (!issuingChainDoor) + { + Throw( + "STXChainBridge IssuingChainDoor must be a valid account"); + } + + lockingChainDoor_ = STAccount{sfLockingChainDoor, *lockingChainDoor}; + lockingChainIssue_ = + STIssue{sfLockingChainIssue, issueFromJson(lockingChainIssue)}; + issuingChainDoor_ = STAccount{sfIssuingChainDoor, *issuingChainDoor}; + issuingChainIssue_ = + STIssue{sfIssuingChainIssue, issueFromJson(issuingChainIssue)}; +} + +STXChainBridge::STXChainBridge(SerialIter& sit, SField const& name) + : STBase{name} + , lockingChainDoor_{sit, sfLockingChainDoor} + , lockingChainIssue_{sit, sfLockingChainIssue} + , issuingChainDoor_{sit, sfIssuingChainDoor} + , issuingChainIssue_{sit, sfIssuingChainIssue} +{ +} + +void +STXChainBridge::add(Serializer& s) const +{ + lockingChainDoor_.add(s); + lockingChainIssue_.add(s); + issuingChainDoor_.add(s); + issuingChainIssue_.add(s); +} + +Json::Value +STXChainBridge::getJson(JsonOptions jo) const +{ + Json::Value v; + v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo); + v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo); + v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo); + v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo); + return v; +} + +std::string +STXChainBridge::getText() const +{ + return str( + boost::format("{ %s = %s, %s = %s, %s = %s, %s = %s }") % + sfLockingChainDoor.getName() % lockingChainDoor_.getText() % + sfLockingChainIssue.getName() % lockingChainIssue_.getText() % + sfIssuingChainDoor.getName() % issuingChainDoor_.getText() % + sfIssuingChainIssue.getName() % issuingChainIssue_.getText()); +} + +STObject +STXChainBridge::toSTObject() const +{ + STObject o{sfXChainBridge}; + o[sfLockingChainDoor] = lockingChainDoor_; + o[sfLockingChainIssue] = lockingChainIssue_; + o[sfIssuingChainDoor] = issuingChainDoor_; + o[sfIssuingChainIssue] = issuingChainIssue_; + return o; +} + +SerializedTypeID +STXChainBridge::getSType() const +{ + return STI_XCHAIN_BRIDGE; +} + +bool +STXChainBridge::isEquivalent(const STBase& t) const +{ + const STXChainBridge* v = dynamic_cast(&t); + return v && (*v == *this); +} + +bool +STXChainBridge::isDefault() const +{ + return lockingChainDoor_.isDefault() && lockingChainIssue_.isDefault() && + issuingChainDoor_.isDefault() && issuingChainIssue_.isDefault(); +} + +std::unique_ptr +STXChainBridge::construct(SerialIter& sit, SField const& name) +{ + return std::make_unique(sit, name); +} + +STBase* +STXChainBridge::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STXChainBridge::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} +} // namespace ripple diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index 9da1bc70757..87dae362598 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -96,6 +96,23 @@ transResults() MAKE_ERROR(tecOBJECT_NOT_FOUND, "A requested object could not be located."), MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."), MAKE_ERROR(tecINCOMPLETE, "Some work was completed, but more submissions required to finish."), + MAKE_ERROR(tecXCHAIN_BAD_TRANSFER_ISSUE, "Bad xchain transfer issue."), + MAKE_ERROR(tecXCHAIN_NO_CLAIM_ID, "No such xchain claim id."), + MAKE_ERROR(tecXCHAIN_BAD_CLAIM_ID, "Bad xchain claim id."), + MAKE_ERROR(tecXCHAIN_CLAIM_NO_QUORUM, "Quorum was not reached on the xchain claim."), + MAKE_ERROR(tecXCHAIN_PROOF_UNKNOWN_KEY, "Unknown key for the xchain proof."), + MAKE_ERROR(tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE, "Only XRP may be used for xchain create account."), + MAKE_ERROR(tecXCHAIN_WRONG_CHAIN, "XChain Transaction was submitted to the wrong chain."), + MAKE_ERROR(tecXCHAIN_REWARD_MISMATCH, "The reward amount must match the reward specified in the xchain bridge."), + MAKE_ERROR(tecXCHAIN_NO_SIGNERS_LIST, "The account did not have a signers list."), + MAKE_ERROR(tecXCHAIN_SENDING_ACCOUNT_MISMATCH,"The sending account did not match the expected sending account."), + MAKE_ERROR(tecXCHAIN_INSUFF_CREATE_AMOUNT, "Insufficient amount to create an account."), + MAKE_ERROR(tecXCHAIN_ACCOUNT_CREATE_PAST, "The account create count has already passed."), + MAKE_ERROR(tecXCHAIN_ACCOUNT_CREATE_TOO_MANY, "There are too many pending account create transactions to submit a new one."), + MAKE_ERROR(tecXCHAIN_PAYMENT_FAILED, "Failed to transfer funds in a xchain transaction."), + MAKE_ERROR(tecXCHAIN_SELF_COMMIT, "Account cannot commit funds to itself."), + MAKE_ERROR(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR, "Bad public key account pair in an xchain transaction."), + MAKE_ERROR(tecXCHAIN_CREATE_ACCOUNT_DISABLED, "This bridge does not support account creation."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -175,6 +192,12 @@ transResults() MAKE_ERROR(temINVALID_COUNT, "Malformed: Count field outside valid range."), MAKE_ERROR(temSEQ_AND_TICKET, "Transaction contains a TicketSequence and a non-zero Sequence."), MAKE_ERROR(temBAD_NFTOKEN_TRANSFER_FEE, "Malformed: The NFToken transfer fee must be between 1 and 5000, inclusive."), + MAKE_ERROR(temXCHAIN_EQUAL_DOOR_ACCOUNTS, "Malformed: Bridge must have unique door accounts."), + MAKE_ERROR(temXCHAIN_BAD_PROOF, "Malformed: Bad cross-chain claim proof."), + MAKE_ERROR(temXCHAIN_BRIDGE_BAD_ISSUES, "Malformed: Bad bridge issues."), + MAKE_ERROR(temXCHAIN_BRIDGE_NONDOOR_OWNER, "Malformed: Bridge owner must be one of the door accounts."), + MAKE_ERROR(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT, "Malformed: Bad min account create amount."), + MAKE_ERROR(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, "Malformed: Bad reward amount."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 58e25f0b8f9..755401bda92 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -18,48 +18,35 @@ //============================================================================== #include + +#include +#include #include namespace ripple { TxFormats::TxFormats() { -#pragma push_macro("PSEUDO_TXN_COMMON_FIELDS") - - // clang-format off - - #define PSEUDO_TXN_COMMON_FIELDS \ - {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}, \ - {sfTxnSignature, soeOPTIONAL}, \ - {sfSigners, soeOPTIONAL}, /* submit_multisigned */ \ - {sfNetworkID, soeOPTIONAL} - - // clang-format on - - // Fields shared by all pseudo-transaction txFormats: - static const std::initializer_list pseudoCommonFields{ - PSEUDO_TXN_COMMON_FIELDS, - }; - - // Fields shared by all normal transaction txFormats: + // Fields shared by all txFormats: static const std::initializer_list commonFields{ - PSEUDO_TXN_COMMON_FIELDS, + {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 pop_macro("PSEUDO_TXN_COMMON_FIELDS") - add(jss::AccountSet, ttACCOUNT_SET, { @@ -220,7 +207,7 @@ TxFormats::TxFormats() {sfLedgerSequence, soeREQUIRED}, {sfAmendment, soeREQUIRED}, }, - pseudoCommonFields); + commonFields); add(jss::SetFee, ttFEE, @@ -236,7 +223,7 @@ TxFormats::TxFormats() {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, }, - pseudoCommonFields); + commonFields); add(jss::UNLModify, ttUNL_MODIFY, @@ -245,7 +232,7 @@ TxFormats::TxFormats() {sfLedgerSequence, soeREQUIRED}, {sfUNLModifyValidator, soeREQUIRED}, }, - pseudoCommonFields); + commonFields); add(jss::TicketCreate, ttTICKET_CREATE, @@ -390,6 +377,101 @@ TxFormats::TxFormats() {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); } TxFormats const& diff --git a/src/ripple/protocol/impl/UintTypes.cpp b/src/ripple/protocol/impl/UintTypes.cpp index ff2644b085b..821e81238b0 100644 --- a/src/ripple/protocol/impl/UintTypes.cpp +++ b/src/ripple/protocol/impl/UintTypes.cpp @@ -93,14 +93,8 @@ to_currency(Currency& currency, std::string const& code) currency = beast::zero; - std::transform( - code.begin(), - code.end(), - currency.begin() + detail::isoCodeOffset, - [](auto c) { - return static_cast( - ::toupper(static_cast(c))); - }); + std::copy( + code.begin(), code.end(), currency.begin() + detail::isoCodeOffset); return true; } diff --git a/src/ripple/protocol/impl/XChainAttestations.cpp b/src/ripple/protocol/impl/XChainAttestations.cpp new file mode 100644 index 00000000000..591b20ad5a0 --- /dev/null +++ b/src/ripple/protocol/impl/XChainAttestations.cpp @@ -0,0 +1,759 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 + +namespace ripple { +namespace Attestations { + +AttestationBase::AttestationBase( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_) + : attestationSignerAccount{attestationSignerAccount_} + , publicKey{publicKey_} + , signature{std::move(signature_)} + , sendingAccount{sendingAccount_} + , sendingAmount{sendingAmount_} + , rewardAccount{rewardAccount_} + , wasLockingChainSend{wasLockingChainSend_} +{ +} + +bool +AttestationBase::equalHelper( + AttestationBase const& lhs, + AttestationBase const& rhs) +{ + return std::tie( + lhs.attestationSignerAccount, + lhs.publicKey, + lhs.signature, + lhs.sendingAccount, + lhs.sendingAmount, + lhs.rewardAccount, + lhs.wasLockingChainSend) == + std::tie( + rhs.attestationSignerAccount, + rhs.publicKey, + rhs.signature, + rhs.sendingAccount, + rhs.sendingAmount, + rhs.rewardAccount, + rhs.wasLockingChainSend); +} + +bool +AttestationBase::sameEventHelper( + AttestationBase const& lhs, + AttestationBase const& rhs) +{ + return std::tie( + lhs.sendingAccount, + lhs.sendingAmount, + lhs.wasLockingChainSend) == + std::tie( + rhs.sendingAccount, rhs.sendingAmount, rhs.wasLockingChainSend); +} + +bool +AttestationBase::verify(STXChainBridge const& bridge) const +{ + std::vector msg = message(bridge); + return ripple::verify(publicKey, makeSlice(msg), signature); +} + +AttestationBase::AttestationBase(STObject const& o) + : attestationSignerAccount{o[sfAttestationSignerAccount]} + , publicKey{o[sfPublicKey]} + , signature{o[sfSignature]} + , sendingAccount{o[sfAccount]} + , sendingAmount{o[sfAmount]} + , rewardAccount{o[sfAttestationRewardAccount]} + , wasLockingChainSend{bool(o[sfWasLockingChainSend])} +{ +} + +AttestationBase::AttestationBase(Json::Value const& v) + : attestationSignerAccount{Json::getOrThrow( + v, + sfAttestationSignerAccount)} + , publicKey{Json::getOrThrow(v, sfPublicKey)} + , signature{Json::getOrThrow(v, sfSignature)} + , sendingAccount{Json::getOrThrow(v, sfAccount)} + , sendingAmount{Json::getOrThrow(v, sfAmount)} + , rewardAccount{Json::getOrThrow(v, sfAttestationRewardAccount)} + , wasLockingChainSend{Json::getOrThrow(v, sfWasLockingChainSend)} +{ +} + +void +AttestationBase::addHelper(STObject& o) const +{ + o[sfAttestationSignerAccount] = attestationSignerAccount; + o[sfPublicKey] = publicKey; + o[sfSignature] = signature; + o[sfAmount] = sendingAmount; + o[sfAccount] = sendingAccount; + o[sfAttestationRewardAccount] = rewardAccount; + o[sfWasLockingChainSend] = wasLockingChainSend; +} + +AttestationClaim::AttestationClaim( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t claimID_, + std::optional const& dst_) + : AttestationBase( + attestationSignerAccount_, + publicKey_, + std::move(signature_), + sendingAccount_, + sendingAmount_, + rewardAccount_, + wasLockingChainSend_) + , claimID{claimID_} + , dst{dst_} +{ +} + +AttestationClaim::AttestationClaim( + STXChainBridge const& bridge, + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + SecretKey const& secretKey_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t claimID_, + std::optional const& dst_) + : AttestationClaim{ + attestationSignerAccount_, + publicKey_, + Buffer{}, + sendingAccount_, + sendingAmount_, + rewardAccount_, + wasLockingChainSend_, + claimID_, + dst_} +{ + auto const toSign = message(bridge); + signature = sign(publicKey_, secretKey_, makeSlice(toSign)); +} + +AttestationClaim::AttestationClaim(STObject const& o) + : AttestationBase(o), claimID{o[sfXChainClaimID]}, dst{o[~sfDestination]} +{ +} + +AttestationClaim::AttestationClaim(Json::Value const& v) + : AttestationBase{v} + , claimID{Json::getOrThrow(v, sfXChainClaimID)} +{ + if (v.isMember(sfDestination.getJsonName())) + dst = Json::getOrThrow(v, sfDestination); +} + +STObject +AttestationClaim::toSTObject() const +{ + STObject o{sfXChainClaimAttestationCollectionElement}; + addHelper(o); + o[sfXChainClaimID] = claimID; + if (dst) + o[sfDestination] = *dst; + return o; +} + +std::vector +AttestationClaim::message( + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst) +{ + STObject o{sfGeneric}; + // Serialize in SField order to make python serializers easier to write + o[sfXChainClaimID] = claimID; + o[sfAmount] = sendingAmount; + if (dst) + o[sfDestination] = *dst; + o[sfOtherChainSource] = sendingAccount; + o[sfAttestationRewardAccount] = rewardAccount; + o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0; + o[sfXChainBridge] = bridge; + + Serializer s; + o.add(s); + + return std::move(s.modData()); +} + +std::vector +AttestationClaim::message(STXChainBridge const& bridge) const +{ + return AttestationClaim::message( + bridge, + sendingAccount, + sendingAmount, + rewardAccount, + wasLockingChainSend, + claimID, + dst); +} + +bool +AttestationClaim::validAmounts() const +{ + return isLegalNet(sendingAmount); +} + +bool +AttestationClaim::sameEvent(AttestationClaim const& rhs) const +{ + return AttestationClaim::sameEventHelper(*this, rhs) && + tie(claimID, dst) == tie(rhs.claimID, rhs.dst); +} + +bool +operator==(AttestationClaim const& lhs, AttestationClaim const& rhs) +{ + return AttestationClaim::equalHelper(lhs, rhs) && + tie(lhs.claimID, lhs.dst) == tie(rhs.claimID, rhs.dst); +} + +AttestationCreateAccount::AttestationCreateAccount(STObject const& o) + : AttestationBase(o) + , createCount{o[sfXChainAccountCreateCount]} + , toCreate{o[sfDestination]} + , rewardAmount{o[sfSignatureReward]} +{ +} + +AttestationCreateAccount::AttestationCreateAccount(Json::Value const& v) + : AttestationBase{v} + , createCount{Json::getOrThrow( + v, + sfXChainAccountCreateCount)} + , toCreate{Json::getOrThrow(v, sfDestination)} + , rewardAmount{Json::getOrThrow(v, sfSignatureReward)} +{ +} + +AttestationCreateAccount::AttestationCreateAccount( + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + Buffer signature_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t createCount_, + AccountID const& toCreate_) + : AttestationBase( + attestationSignerAccount_, + publicKey_, + std::move(signature_), + sendingAccount_, + sendingAmount_, + rewardAccount_, + wasLockingChainSend_) + , createCount{createCount_} + , toCreate{toCreate_} + , rewardAmount{rewardAmount_} +{ +} + +AttestationCreateAccount::AttestationCreateAccount( + STXChainBridge const& bridge, + AccountID attestationSignerAccount_, + PublicKey const& publicKey_, + SecretKey const& secretKey_, + AccountID const& sendingAccount_, + STAmount const& sendingAmount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::uint64_t createCount_, + AccountID const& toCreate_) + : AttestationCreateAccount{ + attestationSignerAccount_, + publicKey_, + Buffer{}, + sendingAccount_, + sendingAmount_, + rewardAmount_, + rewardAccount_, + wasLockingChainSend_, + createCount_, + toCreate_} +{ + auto const toSign = message(bridge); + signature = sign(publicKey_, secretKey_, makeSlice(toSign)); +} + +STObject +AttestationCreateAccount::toSTObject() const +{ + STObject o{sfXChainCreateAccountAttestationCollectionElement}; + addHelper(o); + + o[sfXChainAccountCreateCount] = createCount; + o[sfDestination] = toCreate; + o[sfSignatureReward] = rewardAmount; + + return o; +} + +std::vector +AttestationCreateAccount::message( + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + STAmount const& rewardAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + AccountID const& dst) +{ + STObject o{sfGeneric}; + // Serialize in SField order to make python serializers easier to write + o[sfXChainAccountCreateCount] = createCount; + o[sfAmount] = sendingAmount; + o[sfSignatureReward] = rewardAmount; + o[sfDestination] = dst; + o[sfOtherChainSource] = sendingAccount; + o[sfAttestationRewardAccount] = rewardAccount; + o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0; + o[sfXChainBridge] = bridge; + + Serializer s; + o.add(s); + + return std::move(s.modData()); +} + +std::vector +AttestationCreateAccount::message(STXChainBridge const& bridge) const +{ + return AttestationCreateAccount::message( + bridge, + sendingAccount, + sendingAmount, + rewardAmount, + rewardAccount, + wasLockingChainSend, + createCount, + toCreate); +} + +bool +AttestationCreateAccount::validAmounts() const +{ + return isLegalNet(rewardAmount) && isLegalNet(sendingAmount); +} + +bool +AttestationCreateAccount::sameEvent(AttestationCreateAccount const& rhs) const +{ + return AttestationCreateAccount::sameEventHelper(*this, rhs) && + std::tie(createCount, toCreate, rewardAmount) == + std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount); +} + +bool +operator==( + AttestationCreateAccount const& lhs, + AttestationCreateAccount const& rhs) +{ + return AttestationCreateAccount::equalHelper(lhs, rhs) && + std::tie(lhs.createCount, lhs.toCreate, lhs.rewardAmount) == + std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount); +} + +} // namespace Attestations + +SField const& XChainClaimAttestation::ArrayFieldName{sfXChainClaimAttestations}; +SField const& XChainCreateAccountAttestation::ArrayFieldName{ + sfXChainCreateAccountAttestations}; + +XChainClaimAttestation::XChainClaimAttestation( + AccountID const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + std::optional const& dst_) + : keyAccount(keyAccount_) + , publicKey(publicKey_) + , amount(sfAmount, amount_) + , rewardAccount(rewardAccount_) + , wasLockingChainSend(wasLockingChainSend_) + , dst(dst_) +{ +} + +XChainClaimAttestation::XChainClaimAttestation( + STAccount const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + STAccount const& rewardAccount_, + bool wasLockingChainSend_, + std::optional const& dst_) + : XChainClaimAttestation{ + keyAccount_.value(), + publicKey_, + amount_, + rewardAccount_.value(), + wasLockingChainSend_, + dst_ ? std::optional{dst_->value()} : std::nullopt} +{ +} + +XChainClaimAttestation::XChainClaimAttestation(STObject const& o) + : XChainClaimAttestation{ + o[sfAttestationSignerAccount], + PublicKey{o[sfPublicKey]}, + o[sfAmount], + o[sfAttestationRewardAccount], + o[sfWasLockingChainSend] != 0, + o[~sfDestination]} {}; + +XChainClaimAttestation::XChainClaimAttestation(Json::Value const& v) + : XChainClaimAttestation{ + Json::getOrThrow(v, sfAttestationSignerAccount), + Json::getOrThrow(v, sfPublicKey), + Json::getOrThrow(v, sfAmount), + Json::getOrThrow(v, sfAttestationRewardAccount), + Json::getOrThrow(v, sfWasLockingChainSend), + std::nullopt} +{ + if (v.isMember(sfDestination.getJsonName())) + dst = Json::getOrThrow(v, sfDestination); +}; + +XChainClaimAttestation::XChainClaimAttestation( + XChainClaimAttestation::TSignedAttestation const& claimAtt) + : XChainClaimAttestation{ + claimAtt.attestationSignerAccount, + claimAtt.publicKey, + claimAtt.sendingAmount, + claimAtt.rewardAccount, + claimAtt.wasLockingChainSend, + claimAtt.dst} +{ +} + +STObject +XChainClaimAttestation::toSTObject() const +{ + STObject o{sfXChainClaimProofSig}; + o[sfAttestationSignerAccount] = + STAccount{sfAttestationSignerAccount, keyAccount}; + o[sfPublicKey] = publicKey; + o[sfAmount] = STAmount{sfAmount, amount}; + o[sfAttestationRewardAccount] = + STAccount{sfAttestationRewardAccount, rewardAccount}; + o[sfWasLockingChainSend] = wasLockingChainSend; + if (dst) + o[sfDestination] = STAccount{sfDestination, *dst}; + return o; +} + +bool +operator==(XChainClaimAttestation const& lhs, XChainClaimAttestation const& rhs) +{ + return std::tie( + lhs.keyAccount, + lhs.publicKey, + lhs.amount, + lhs.rewardAccount, + lhs.wasLockingChainSend, + lhs.dst) == + std::tie( + rhs.keyAccount, + rhs.publicKey, + rhs.amount, + rhs.rewardAccount, + rhs.wasLockingChainSend, + rhs.dst); +} + +XChainClaimAttestation::MatchFields::MatchFields( + XChainClaimAttestation::TSignedAttestation const& att) + : amount{att.sendingAmount} + , wasLockingChainSend{att.wasLockingChainSend} + , dst{att.dst} +{ +} + +AttestationMatch +XChainClaimAttestation::match( + XChainClaimAttestation::MatchFields const& rhs) const +{ + if (std::tie(amount, wasLockingChainSend) != + std::tie(rhs.amount, rhs.wasLockingChainSend)) + return AttestationMatch::nonDstMismatch; + if (dst != rhs.dst) + return AttestationMatch::matchExceptDst; + return AttestationMatch::match; +} + +//------------------------------------------------------------------------------ + +XChainCreateAccountAttestation::XChainCreateAccountAttestation( + AccountID const& keyAccount_, + PublicKey const& publicKey_, + STAmount const& amount_, + STAmount const& rewardAmount_, + AccountID const& rewardAccount_, + bool wasLockingChainSend_, + AccountID const& dst_) + : keyAccount(keyAccount_) + , publicKey(publicKey_) + , amount(sfAmount, amount_) + , rewardAmount(sfSignatureReward, rewardAmount_) + , rewardAccount(rewardAccount_) + , wasLockingChainSend(wasLockingChainSend_) + , dst(dst_) +{ +} + +XChainCreateAccountAttestation::XChainCreateAccountAttestation( + STObject const& o) + : XChainCreateAccountAttestation{ + o[sfAttestationSignerAccount], + PublicKey{o[sfPublicKey]}, + o[sfAmount], + o[sfSignatureReward], + o[sfAttestationRewardAccount], + o[sfWasLockingChainSend] != 0, + o[sfDestination]} {}; + +XChainCreateAccountAttestation ::XChainCreateAccountAttestation( + Json::Value const& v) + : XChainCreateAccountAttestation{ + Json::getOrThrow(v, sfAttestationSignerAccount), + Json::getOrThrow(v, sfPublicKey), + Json::getOrThrow(v, sfAmount), + Json::getOrThrow(v, sfSignatureReward), + Json::getOrThrow(v, sfAttestationRewardAccount), + Json::getOrThrow(v, sfWasLockingChainSend), + Json::getOrThrow(v, sfDestination)} +{ +} + +XChainCreateAccountAttestation::XChainCreateAccountAttestation( + XChainCreateAccountAttestation::TSignedAttestation const& createAtt) + : XChainCreateAccountAttestation{ + createAtt.attestationSignerAccount, + createAtt.publicKey, + createAtt.sendingAmount, + createAtt.rewardAmount, + createAtt.rewardAccount, + createAtt.wasLockingChainSend, + createAtt.toCreate} +{ +} + +STObject +XChainCreateAccountAttestation::toSTObject() const +{ + STObject o{sfXChainCreateAccountProofSig}; + + o[sfAttestationSignerAccount] = + STAccount{sfAttestationSignerAccount, keyAccount}; + o[sfPublicKey] = publicKey; + o[sfAmount] = STAmount{sfAmount, amount}; + o[sfSignatureReward] = STAmount{sfSignatureReward, rewardAmount}; + o[sfAttestationRewardAccount] = + STAccount{sfAttestationRewardAccount, rewardAccount}; + o[sfWasLockingChainSend] = wasLockingChainSend; + o[sfDestination] = STAccount{sfDestination, dst}; + + return o; +} + +XChainCreateAccountAttestation::MatchFields::MatchFields( + XChainCreateAccountAttestation::TSignedAttestation const& att) + : amount{att.sendingAmount} + , rewardAmount(att.rewardAmount) + , wasLockingChainSend{att.wasLockingChainSend} + , dst{att.toCreate} +{ +} + +AttestationMatch +XChainCreateAccountAttestation::match( + XChainCreateAccountAttestation::MatchFields const& rhs) const +{ + if (std::tie(amount, rewardAmount, wasLockingChainSend) != + std::tie(rhs.amount, rhs.rewardAmount, rhs.wasLockingChainSend)) + return AttestationMatch::nonDstMismatch; + if (dst != rhs.dst) + return AttestationMatch::matchExceptDst; + return AttestationMatch::match; +} + +bool +operator==( + XChainCreateAccountAttestation const& lhs, + XChainCreateAccountAttestation const& rhs) +{ + return std::tie( + lhs.keyAccount, + lhs.publicKey, + lhs.amount, + lhs.rewardAmount, + lhs.rewardAccount, + lhs.wasLockingChainSend, + lhs.dst) == + std::tie( + rhs.keyAccount, + rhs.publicKey, + rhs.amount, + rhs.rewardAmount, + rhs.rewardAccount, + rhs.wasLockingChainSend, + rhs.dst); +} + +//------------------------------------------------------------------------------ +// +template +XChainAttestationsBase::XChainAttestationsBase( + XChainAttestationsBase::AttCollection&& atts) + : attestations_{std::move(atts)} +{ +} + +template +typename XChainAttestationsBase::AttCollection::const_iterator +XChainAttestationsBase::begin() const +{ + return attestations_.begin(); +} + +template +typename XChainAttestationsBase::AttCollection::const_iterator +XChainAttestationsBase::end() const +{ + return attestations_.end(); +} + +template +typename XChainAttestationsBase::AttCollection::iterator +XChainAttestationsBase::begin() +{ + return attestations_.begin(); +} + +template +typename XChainAttestationsBase::AttCollection::iterator +XChainAttestationsBase::end() +{ + return attestations_.end(); +} + +template +XChainAttestationsBase::XChainAttestationsBase( + Json::Value const& v) +{ + if (!v.isObject()) + { + Throw( + "XChainAttestationsBase can only be specified with an 'object' " + "Json value"); + } + + attestations_ = [&] { + auto const jAtts = v[jss::attestations]; + + if (jAtts.size() > maxAttestations) + Throw( + "XChainAttestationsBase exceeded max number of attestations"); + + std::vector r; + r.reserve(jAtts.size()); + for (auto const& a : jAtts) + r.emplace_back(a); + return r; + }(); +} + +template +XChainAttestationsBase::XChainAttestationsBase(STArray const& arr) +{ + if (arr.size() > maxAttestations) + Throw( + "XChainAttestationsBase exceeded max number of attestations"); + + attestations_.reserve(arr.size()); + for (auto const& o : arr) + attestations_.emplace_back(o); +} + +template +STArray +XChainAttestationsBase::toSTArray() const +{ + STArray r{TAttestation::ArrayFieldName, attestations_.size()}; + for (auto const& e : attestations_) + r.emplace_back(e.toSTObject()); + return r; +} + +template class XChainAttestationsBase; +template class XChainAttestationsBase; + +} // namespace ripple diff --git a/src/ripple/protocol/json_get_or_throw.h b/src/ripple/protocol/json_get_or_throw.h new file mode 100644 index 00000000000..86bd5924d3e --- /dev/null +++ b/src/ripple/protocol/json_get_or_throw.h @@ -0,0 +1,159 @@ +#ifndef PROTOCOL_GET_OR_THROW_H_ +#define PROTOCOL_GET_OR_THROW_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Json { +struct JsonMissingKeyError : std::exception +{ + char const* const key; + mutable std::string msg; + JsonMissingKeyError(Json::StaticString const& k) : key{k.c_str()} + { + } + const char* + what() const noexcept override + { + if (msg.empty()) + { + msg = std::string("Missing json key: ") + key; + } + return msg.c_str(); + } +}; + +struct JsonTypeMismatchError : std::exception +{ + char const* const key; + std::string const expectedType; + mutable std::string msg; + JsonTypeMismatchError(Json::StaticString const& k, std::string et) + : key{k.c_str()}, expectedType{std::move(et)} + { + } + const char* + what() const noexcept override + { + if (msg.empty()) + { + msg = std::string("Type mismatch on json key: ") + key + + "; expected type: " + expectedType; + } + return msg.c_str(); + } +}; + +template +T +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + static_assert(sizeof(T) == -1, "This function must be specialized"); +} + +template <> +inline std::string +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + Json::StaticString const& key = field.getJsonName(); + if (!v.isMember(key)) + Throw(key); + + Json::Value const& inner = v[key]; + if (!inner.isString()) + Throw(key, "string"); + return inner.asString(); +} + +// Note, this allows integer numeric fields to act as bools +template <> +inline bool +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + Json::StaticString const& key = field.getJsonName(); + if (!v.isMember(key)) + Throw(key); + Json::Value const& inner = v[key]; + if (inner.isBool()) + return inner.asBool(); + if (!inner.isIntegral()) + Throw(key, "bool"); + + return inner.asInt() != 0; +} + +template <> +inline std::uint64_t +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + Json::StaticString const& key = field.getJsonName(); + if (!v.isMember(key)) + Throw(key); + Json::Value const& inner = v[key]; + if (inner.isUInt()) + return inner.asUInt(); + if (inner.isInt()) + { + auto const r = inner.asInt(); + if (r < 0) + Throw(key, "uint64"); + return r; + } + if (inner.isString()) + { + auto const s = inner.asString(); + // parse as hex + std::uint64_t val; + + auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), val, 16); + + if (ec != std::errc() || (p != s.data() + s.size())) + Throw(key, "uint64"); + return val; + } + Throw(key, "uint64"); +} + +template <> +inline ripple::Buffer +getOrThrow(Json::Value const& v, ripple::SField const& field) +{ + using namespace ripple; + std::string const hex = getOrThrow(v, field); + if (auto const r = strUnHex(hex)) + { + // TODO: mismatch between a buffer and a blob + return Buffer{r->data(), r->size()}; + } + Throw(field.getJsonName(), "Buffer"); +} + +// This function may be used by external projects (like the witness server). +template +std::optional +getOptional(Json::Value const& v, ripple::SField const& field) +{ + try + { + return getOrThrow(v, field); + } + catch (...) + { + } + return {}; +} + +} // namespace Json + +#endif // PROTOCOL_GET_OR_THROW_H_ diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index eaf0ffa74c3..e31a1cb3bf8 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -62,6 +62,7 @@ JSS(Asset); // in: AMM Asset1 JSS(Asset2); // in: AMM Asset2 JSS(AuthAccount); // in: AMM Auction Slot JSS(AuthAccounts); // in: AMM Auction Slot +JSS(Bridge); // ledger type. JSS(Check); // ledger type. JSS(CheckCancel); // transaction type. JSS(CheckCash); // transaction type. @@ -107,31 +108,41 @@ 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(RippleState); // ledger type. -JSS(SLE_hit_rate); // out: GetCounts. -JSS(SetFee); // transaction type. -JSS(UNLModify); // transaction type. -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(VoteSlots); // out: AMM Vote -JSS(aborted); // out: InboundLedger +JSS(PaymentChannelCreate); // transaction type. +JSS(PaymentChannelFund); // transaction type. +JSS(RippleState); // ledger type. +JSS(SLE_hit_rate); // out: GetCounts. +JSS(SetFee); // transaction type. +JSS(UNLModify); // transaction type. +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(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 @@ -147,96 +158,102 @@ 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(accounts); // in: LedgerEntry, Subscribe, - // handlers/Ledger, Unsubscribe -JSS(accounts_proposed); // in: Subscribe, Unsubscribe + +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(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_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(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_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(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_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_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 @@ -298,7 +315,7 @@ 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 +JSS(fee_ref); // out: NetworkOPs, DEPRECATED JSS(fetch_pack); // out: NetworkOPs JSS(first); // out: rpc/Version JSS(firstSequence); // out: NodeToShardStatus @@ -698,8 +715,10 @@ JSS(vote_weight); // out: amm_info JSS(warning); // rpc: JSS(warnings); // out: server_info, server_state JSS(workers); -JSS(write_load); // out: GetCounts -JSS(NegativeUNL); // out: ValidatorList; ledger type +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 diff --git a/src/ripple/resource/impl/ResourceManager.cpp b/src/ripple/resource/impl/ResourceManager.cpp index 137bbf36eb7..1a7e74ec1f8 100644 --- a/src/ripple/resource/impl/ResourceManager.cpp +++ b/src/ripple/resource/impl/ResourceManager.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include #include +#include #include #include #include @@ -149,7 +149,7 @@ class ManagerImp : public Manager void run() { - this_thread::set_name("Resrc::Manager"); + beast::setCurrentThreadName("Resource::Manager"); for (;;) { logic_.periodicActivity(); diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index 8c85670af6a..bd2184f49a3 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -134,10 +134,12 @@ doAccountInfo(RPC::JsonContext& context) result[jss::account_flags] = std::move(acctFlags); - // The document states that signer_lists is a bool, however - // assigning any string value works. Do not allow this. - // This check is for api Version 2 onwards only - if (!params[jss::signer_lists].isBool() && context.apiVersion > 1) + // The document[https://xrpl.org/account_info.html#account_info] states + // that signer_lists is a bool, however assigning any string value + // works. Do not allow this. This check is for api Version 2 onwards + // only + if (context.apiVersion > 1u && params.isMember(jss::signer_lists) && + !params[jss::signer_lists].isBool()) { RPC::inject_error(rpcINVALID_PARAMS, result); return result; diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/ripple/rpc/handlers/AccountObjects.cpp index 65cd12f2d41..bbf5b6e126a 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/ripple/rpc/handlers/AccountObjects.cpp @@ -197,7 +197,11 @@ doAccountObjects(RPC::JsonContext& context) {jss::escrow, ltESCROW}, {jss::nft_page, ltNFTOKEN_PAGE}, {jss::payment_channel, ltPAYCHAN}, - {jss::state, ltRIPPLE_STATE}}; + {jss::state, ltRIPPLE_STATE}, + {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}}; typeFilter.emplace(); typeFilter->reserve(std::size(deletionBlockers)); diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index 84d087939e7..cee8a629c75 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -58,7 +58,7 @@ parseLedgerArgs(RPC::Context& context, Json::Value const& params) Json::Value response; // if ledger_index_min or max is specified, then ledger_hash or ledger_index // should not be specified. Error out if it is - if (context.apiVersion > 1) + if (context.apiVersion > 1u) { if ((params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max)) && @@ -162,7 +162,7 @@ getLedgerRange( // if ledger_index_min or ledger_index_max is out of // valid ledger range, error out. exclude -1 as // it is a valid input - if (context.apiVersion > 1) + if (context.apiVersion > 1u) { if ((ls.max > uValidatedMax && ls.max != -1) || (ls.min < uValidatedMin && ls.min != 0)) @@ -389,6 +389,21 @@ doAccountTxJson(RPC::JsonContext& context) AccountTxArgs args; Json::Value response; + // The document[https://xrpl.org/account_tx.html#account_tx] states that + // binary and forward params are both boolean values, however, assigning any + // string value works. Do not allow this. This check is for api Version 2 + // onwards only + if (context.apiVersion > 1u && params.isMember(jss::binary) && + !params[jss::binary].isBool()) + { + return rpcError(rpcINVALID_PARAMS); + } + if (context.apiVersion > 1u && params.isMember(jss::forward) && + !params[jss::forward].isBool()) + { + return rpcError(rpcINVALID_PARAMS); + } + args.limit = params.isMember(jss::limit) ? params[jss::limit].asUInt() : 0; args.binary = params.isMember(jss::binary) && params[jss::binary].asBool(); args.forward = diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/ripple/rpc/handlers/GatewayBalances.cpp index 77cec496ed0..fc6a7b49fd8 100644 --- a/src/ripple/rpc/handlers/GatewayBalances.cpp +++ b/src/ripple/rpc/handlers/GatewayBalances.cpp @@ -78,6 +78,12 @@ doGatewayBalances(RPC::JsonContext& context) result[jss::account] = toBase58(accountID); + if (context.apiVersion > 1u && !ledger->exists(keylet::account(accountID))) + { + RPC::inject_error(rpcACT_NOT_FOUND, result); + return result; + } + // Parse the specified hotwallet(s), if any std::set hotWallets; @@ -116,7 +122,18 @@ doGatewayBalances(RPC::JsonContext& context) if (!valid) { - result[jss::error] = "invalidHotWallet"; + // The documentation states that invalidParams is used when + // One or more fields are specified incorrectly. + // invalidHotwallet should be used when the account exists, but does + // not have currency issued by the account from the request. + if (context.apiVersion < 2u) + { + RPC::inject_error(rpcINVALID_HOTWALLET, result); + } + else + { + RPC::inject_error(rpcINVALID_PARAMS, result); + } return result; } } diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index 44bf1c1ab45..7f40d3ee3be 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -390,6 +391,203 @@ doLedgerEntry(RPC::JsonContext& context) } } } + else if (context.params.isMember(jss::bridge)) + { + expectedType = ltBRIDGE; + + // return the keylet for the specified bridge or nullopt if the + // request is malformed + auto const maybeKeylet = [&]() -> std::optional { + try + { + if (!context.params.isMember(jss::bridge_account)) + return std::nullopt; + + auto const& jsBridgeAccount = + context.params[jss::bridge_account]; + if (!jsBridgeAccount.isString()) + { + return std::nullopt; + } + auto const account = + parseBase58(jsBridgeAccount.asString()); + if (!account || account->isZero()) + { + return std::nullopt; + } + + // This may throw and is the reason for the `try` block. The + // try block has a larger scope so the `bridge` variable + // doesn't need to be an optional. + STXChainBridge const bridge(context.params[jss::bridge]); + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain( + account == bridge.lockingChainDoor()); + if (account != bridge.door(chainType)) + return std::nullopt; + + return keylet::bridge(bridge, chainType); + } + catch (...) + { + return std::nullopt; + } + }(); + + if (maybeKeylet) + { + uNodeIndex = maybeKeylet->key; + } + else + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if (context.params.isMember(jss::xchain_owned_claim_id)) + { + expectedType = ltXCHAIN_OWNED_CLAIM_ID; + auto& claim_id = context.params[jss::xchain_owned_claim_id]; + if (claim_id.isString()) + { + // we accept a node id as specifier of a xchain claim id + if (!uNodeIndex.parseHex(claim_id.asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !claim_id.isObject() || + !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && + claim_id[sfIssuingChainDoor.getJsonName()].isString()) || + !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && + claim_id[sfLockingChainDoor.getJsonName()].isString()) || + !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || + !claim_id.isMember(sfLockingChainIssue.getJsonName()) || + !claim_id.isMember(jss::xchain_owned_claim_id)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + // if not specified with a node id, a claim_id is specified by + // four strings defining the bridge (locking_chain_door, + // locking_chain_issue, issuing_chain_door, issuing_chain_issue) + // and the claim id sequence number. + auto lockingChainDoor = parseBase58( + claim_id[sfLockingChainDoor.getJsonName()].asString()); + auto issuingChainDoor = parseBase58( + claim_id[sfIssuingChainDoor.getJsonName()].asString()); + Issue lockingChainIssue, issuingChainIssue; + bool valid = lockingChainDoor && issuingChainDoor; + if (valid) + { + try + { + lockingChainIssue = issueFromJson( + claim_id[sfLockingChainIssue.getJsonName()]); + issuingChainIssue = issueFromJson( + claim_id[sfIssuingChainIssue.getJsonName()]); + } + catch (std::runtime_error const& ex) + { + valid = false; + jvResult[jss::error] = "malformedRequest"; + } + } + + if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral()) + { + auto seq = claim_id[jss::xchain_owned_claim_id].asUInt(); + + STXChainBridge bridge_spec( + *lockingChainDoor, + lockingChainIssue, + *issuingChainDoor, + issuingChainIssue); + Keylet keylet = keylet::xChainClaimID(bridge_spec, seq); + uNodeIndex = keylet.key; + } + } + } + else if (context.params.isMember( + jss::xchain_owned_create_account_claim_id)) + { + // see object definition in LedgerFormats.cpp + expectedType = ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID; + auto& claim_id = + context.params[jss::xchain_owned_create_account_claim_id]; + if (claim_id.isString()) + { + // we accept a node id as specifier of a xchain create account + // claim_id + if (!uNodeIndex.parseHex(claim_id.asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !claim_id.isObject() || + !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && + claim_id[sfIssuingChainDoor.getJsonName()].isString()) || + !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && + claim_id[sfLockingChainDoor.getJsonName()].isString()) || + !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || + !claim_id.isMember(sfLockingChainIssue.getJsonName()) || + !claim_id.isMember(jss::xchain_owned_create_account_claim_id)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + // if not specified with a node id, a create account claim_id is + // specified by four strings defining the bridge + // (locking_chain_door, locking_chain_issue, issuing_chain_door, + // issuing_chain_issue) and the create account claim id sequence + // number. + auto lockingChainDoor = parseBase58( + claim_id[sfLockingChainDoor.getJsonName()].asString()); + auto issuingChainDoor = parseBase58( + claim_id[sfIssuingChainDoor.getJsonName()].asString()); + Issue lockingChainIssue, issuingChainIssue; + bool valid = lockingChainDoor && issuingChainDoor; + if (valid) + { + try + { + lockingChainIssue = issueFromJson( + claim_id[sfLockingChainIssue.getJsonName()]); + issuingChainIssue = issueFromJson( + claim_id[sfIssuingChainIssue.getJsonName()]); + } + catch (std::runtime_error const& ex) + { + valid = false; + jvResult[jss::error] = "malformedRequest"; + } + } + + if (valid && + claim_id[jss::xchain_owned_create_account_claim_id] + .isIntegral()) + { + auto seq = + claim_id[jss::xchain_owned_create_account_claim_id] + .asUInt(); + + STXChainBridge bridge_spec( + *lockingChainDoor, + lockingChainIssue, + *issuingChainDoor, + issuingChainIssue); + Keylet keylet = + keylet::xChainCreateAccountClaimID(bridge_spec, seq); + uNodeIndex = keylet.key; + } + } + } else { if (context.params.isMember("params") && diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/ripple/rpc/handlers/NoRippleCheck.cpp index 20137c985c9..1942372f3e3 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/ripple/rpc/handlers/NoRippleCheck.cpp @@ -83,6 +83,16 @@ doNoRippleCheck(RPC::JsonContext& context) if (params.isMember(jss::transactions)) transactions = params["transactions"].asBool(); + // The document[https://xrpl.org/noripple_check.html#noripple_check] states + // that transactions params is a boolean value, however, assigning any + // string value works. Do not allow this. This check is for api Version 2 + // onwards only + if (context.apiVersion > 1u && params.isMember(jss::transactions) && + !params[jss::transactions].isBool()) + { + return rpcError(rpcINVALID_PARAMS); + } + std::shared_ptr ledger; auto result = RPC::lookupLedger(ledger, context); if (!ledger) diff --git a/src/ripple/rpc/handlers/PayChanClaim.cpp b/src/ripple/rpc/handlers/PayChanClaim.cpp index 820ccd92648..ca3e8c63c6d 100644 --- a/src/ripple/rpc/handlers/PayChanClaim.cpp +++ b/src/ripple/rpc/handlers/PayChanClaim.cpp @@ -56,7 +56,7 @@ doChannelAuthorize(RPC::JsonContext& context) Json::Value result; std::optional> const keyPair = - RPC::keypairForSignature(params, result); + RPC::keypairForSignature(params, result, context.apiVersion); assert(keyPair || RPC::contains_error(result)); if (!keyPair || RPC::contains_error(result)) diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 6b5e37a5f7e..adf8a04c385 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -847,7 +847,10 @@ getSeedFromRPC(Json::Value const& params, Json::Value& error) } std::optional> -keypairForSignature(Json::Value const& params, Json::Value& error) +keypairForSignature( + Json::Value const& params, + Json::Value& error, + unsigned int apiVersion) { bool const has_key_type = params.isMember(jss::key_type); @@ -901,7 +904,10 @@ keypairForSignature(Json::Value const& params, Json::Value& error) if (!keyType) { - error = RPC::invalid_field_error(jss::key_type); + if (apiVersion > 1u) + error = RPC::make_error(rpcBAD_KEY_TYPE); + else + error = RPC::invalid_field_error(jss::key_type); return {}; } @@ -982,7 +988,7 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 16> + static constexpr std::array, 19> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, @@ -999,7 +1005,11 @@ chooseLedgerEntryType(Json::Value const& params) {jss::ticket, ltTICKET}, {jss::nft_offer, ltNFTOKEN_OFFER}, {jss::nft_page, ltNFTOKEN_PAGE}, - {jss::amm, ltAMM}}}; + {jss::amm, ltAMM}, + {jss::bridge, ltBRIDGE}, + {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, + {jss::xchain_owned_create_account_claim_id, + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}}}; auto const& p = params[jss::type]; if (!p.isString()) @@ -1103,11 +1113,13 @@ getLedgerByContext(RPC::JsonContext& context) return RPC::make_param_error("Ledger index too small"); auto const j = context.app.journal("RPCHandler"); - // Try to get the hash of the desired ledger from the validated ledger + // Try to get the hash of the desired ledger from the validated + // ledger auto neededHash = hashOfSeq(*ledger, ledgerIndex, j); if (!neededHash) { - // Find a ledger more likely to have the hash of the desired ledger + // Find a ledger more likely to have the hash of the desired + // ledger auto const refIndex = getCandidateLedger(ledgerIndex); auto refHash = hashOfSeq(*ledger, refIndex, j); assert(refHash); @@ -1115,8 +1127,8 @@ getLedgerByContext(RPC::JsonContext& context) ledger = ledgerMaster.getLedgerByHash(*refHash); if (!ledger) { - // We don't have the ledger we need to figure out which ledger - // they want. Try to get it. + // We don't have the ledger we need to figure out which + // ledger they want. Try to get it. if (auto il = context.app.getInboundLedgers().acquire( *refHash, refIndex, InboundLedger::Reason::GENERIC)) diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index c2bf8273c78..aca34f33b47 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -208,9 +208,6 @@ getSeedFromRPC(Json::Value const& params, Json::Value& error); std::optional parseRippleLibSeed(Json::Value const& params); -std::optional> -keypairForSignature(Json::Value const& params, Json::Value& error); - /** * API version numbers used in API version 1 */ @@ -295,6 +292,12 @@ getAPIVersionNumber(const Json::Value& value, bool betaEnabled); std::variant, Json::Value> getLedgerByContext(RPC::JsonContext& context); +std::optional> +keypairForSignature( + Json::Value const& params, + Json::Value& error, + unsigned int apiVersion = apiVersionIfUnspecified); + /** Helper to parse submit_mode parameter to RPC submit. * * @param params RPC parameters diff --git a/src/secp256k1/.travis.yml b/src/secp256k1/.travis.yml deleted file mode 100644 index 24395292426..00000000000 --- a/src/secp256k1/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -language: c -sudo: false -addons: - apt: - packages: libgmp-dev -compiler: - - clang - - gcc -cache: - directories: - - src/java/guava/ -env: - global: - - FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no - - GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar - matrix: - - SCALAR=32bit RECOVERY=yes - - SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes - - SCALAR=64bit - - FIELD=64bit RECOVERY=yes - - FIELD=64bit ENDOMORPHISM=yes - - FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes - - FIELD=64bit ASM=x86_64 - - FIELD=64bit ENDOMORPHISM=yes ASM=x86_64 - - FIELD=32bit ENDOMORPHISM=yes - - BIGNUM=no - - BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes - - BIGNUM=no STATICPRECOMPUTATION=no - - BUILD=distcheck - - EXTRAFLAGS=CPPFLAGS=-DDETERMINISTIC - - EXTRAFLAGS=CFLAGS=-O0 - - BUILD=check-java ECDH=yes EXPERIMENTAL=yes -matrix: - fast_finish: true - include: - - compiler: clang - env: HOST=i686-linux-gnu ENDOMORPHISM=yes - addons: - apt: - packages: - - gcc-multilib - - libgmp-dev:i386 - - compiler: clang - env: HOST=i686-linux-gnu - addons: - apt: - packages: - - gcc-multilib - - compiler: gcc - env: HOST=i686-linux-gnu ENDOMORPHISM=yes - addons: - apt: - packages: - - gcc-multilib - - compiler: gcc - env: HOST=i686-linux-gnu - addons: - apt: - packages: - - gcc-multilib - - libgmp-dev:i386 -before_install: mkdir -p `dirname $GUAVA_JAR` -install: if [ ! -f $GUAVA_JAR ]; then wget $GUAVA_URL -O $GUAVA_JAR; fi -before_script: ./autogen.sh -script: - - if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi - - if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi - - ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY $EXTRAFLAGS $USE_HOST && make -j2 $BUILD -os: linux diff --git a/src/secp256k1/TODO b/src/secp256k1/TODO deleted file mode 100644 index a300e1c5eb9..00000000000 --- a/src/secp256k1/TODO +++ /dev/null @@ -1,3 +0,0 @@ -* Unit tests for fieldelem/groupelem, including ones intended to - trigger fieldelem's boundary cases. -* Complete constant-time operations for signing/keygen diff --git a/src/secp256k1/build-aux/m4/ax_jni_include_dir.m4 b/src/secp256k1/build-aux/m4/ax_jni_include_dir.m4 deleted file mode 100644 index 1fc36276144..00000000000 --- a/src/secp256k1/build-aux/m4/ax_jni_include_dir.m4 +++ /dev/null @@ -1,140 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_jni_include_dir.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_JNI_INCLUDE_DIR -# -# DESCRIPTION -# -# AX_JNI_INCLUDE_DIR finds include directories needed for compiling -# programs using the JNI interface. -# -# JNI include directories are usually in the Java distribution. This is -# deduced from the value of $JAVA_HOME, $JAVAC, or the path to "javac", in -# that order. When this macro completes, a list of directories is left in -# the variable JNI_INCLUDE_DIRS. -# -# Example usage follows: -# -# AX_JNI_INCLUDE_DIR -# -# for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS -# do -# CPPFLAGS="$CPPFLAGS -I$JNI_INCLUDE_DIR" -# done -# -# If you want to force a specific compiler: -# -# - at the configure.in level, set JAVAC=yourcompiler before calling -# AX_JNI_INCLUDE_DIR -# -# - at the configure level, setenv JAVAC -# -# Note: This macro can work with the autoconf M4 macros for Java programs. -# This particular macro is not part of the original set of macros. -# -# LICENSE -# -# Copyright (c) 2008 Don Anderson -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 10 - -AU_ALIAS([AC_JNI_INCLUDE_DIR], [AX_JNI_INCLUDE_DIR]) -AC_DEFUN([AX_JNI_INCLUDE_DIR],[ - -JNI_INCLUDE_DIRS="" - -if test "x$JAVA_HOME" != x; then - _JTOPDIR="$JAVA_HOME" -else - if test "x$JAVAC" = x; then - JAVAC=javac - fi - AC_PATH_PROG([_ACJNI_JAVAC], [$JAVAC], [no]) - if test "x$_ACJNI_JAVAC" = xno; then - AC_MSG_WARN([cannot find JDK; try setting \$JAVAC or \$JAVA_HOME]) - fi - _ACJNI_FOLLOW_SYMLINKS("$_ACJNI_JAVAC") - _JTOPDIR=`echo "$_ACJNI_FOLLOWED" | sed -e 's://*:/:g' -e 's:/[[^/]]*$::'` -fi - -case "$host_os" in - darwin*) _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` - _JINC="$_JTOPDIR/Headers";; - *) _JINC="$_JTOPDIR/include";; -esac -_AS_ECHO_LOG([_JTOPDIR=$_JTOPDIR]) -_AS_ECHO_LOG([_JINC=$_JINC]) - -# On Mac OS X 10.6.4, jni.h is a symlink: -# /System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/jni.h -# -> ../../CurrentJDK/Headers/jni.h. - -AC_CACHE_CHECK(jni headers, ac_cv_jni_header_path, -[ -if test -f "$_JINC/jni.h"; then - ac_cv_jni_header_path="$_JINC" - JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" -else - _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` - if test -f "$_JTOPDIR/include/jni.h"; then - ac_cv_jni_header_path="$_JTOPDIR/include" - JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" - else - ac_cv_jni_header_path=none - fi -fi -]) - - - -# get the likely subdirectories for system specific java includes -case "$host_os" in -bsdi*) _JNI_INC_SUBDIRS="bsdos";; -darwin*) _JNI_INC_SUBDIRS="darwin";; -freebsd*) _JNI_INC_SUBDIRS="freebsd";; -linux*) _JNI_INC_SUBDIRS="linux genunix";; -osf*) _JNI_INC_SUBDIRS="alpha";; -solaris*) _JNI_INC_SUBDIRS="solaris";; -mingw*) _JNI_INC_SUBDIRS="win32";; -cygwin*) _JNI_INC_SUBDIRS="win32";; -*) _JNI_INC_SUBDIRS="genunix";; -esac - -if test "x$ac_cv_jni_header_path" != "xnone"; then - # add any subdirectories that are present - for JINCSUBDIR in $_JNI_INC_SUBDIRS - do - if test -d "$_JTOPDIR/include/$JINCSUBDIR"; then - JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $_JTOPDIR/include/$JINCSUBDIR" - fi - done -fi -]) - -# _ACJNI_FOLLOW_SYMLINKS -# Follows symbolic links on , -# finally setting variable _ACJNI_FOLLOWED -# ---------------------------------------- -AC_DEFUN([_ACJNI_FOLLOW_SYMLINKS],[ -# find the include directory relative to the javac executable -_cur="$1" -while ls -ld "$_cur" 2>/dev/null | grep " -> " >/dev/null; do - AC_MSG_CHECKING([symlink for $_cur]) - _slink=`ls -ld "$_cur" | sed 's/.* -> //'` - case "$_slink" in - /*) _cur="$_slink";; - # 'X' avoids triggering unwanted echo options. - *) _cur=`echo "X$_cur" | sed -e 's/^X//' -e 's:[[^/]]*$::'`"$_slink";; - esac - AC_MSG_RESULT([$_cur]) -done -_ACJNI_FOLLOWED="$_cur" -])# _ACJNI diff --git a/src/secp256k1/build-aux/m4/ax_prog_cc_for_build.m4 b/src/secp256k1/build-aux/m4/ax_prog_cc_for_build.m4 deleted file mode 100644 index 77fd346a79a..00000000000 --- a/src/secp256k1/build-aux/m4/ax_prog_cc_for_build.m4 +++ /dev/null @@ -1,125 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_prog_cc_for_build.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PROG_CC_FOR_BUILD -# -# DESCRIPTION -# -# This macro searches for a C compiler that generates native executables, -# that is a C compiler that surely is not a cross-compiler. This can be -# useful if you have to generate source code at compile-time like for -# example GCC does. -# -# The macro sets the CC_FOR_BUILD and CPP_FOR_BUILD macros to anything -# needed to compile or link (CC_FOR_BUILD) and preprocess (CPP_FOR_BUILD). -# The value of these variables can be overridden by the user by specifying -# a compiler with an environment variable (like you do for standard CC). -# -# It also sets BUILD_EXEEXT and BUILD_OBJEXT to the executable and object -# file extensions for the build platform, and GCC_FOR_BUILD to `yes' if -# the compiler we found is GCC. All these variables but GCC_FOR_BUILD are -# substituted in the Makefile. -# -# LICENSE -# -# Copyright (c) 2008 Paolo Bonzini -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 8 - -AU_ALIAS([AC_PROG_CC_FOR_BUILD], [AX_PROG_CC_FOR_BUILD]) -AC_DEFUN([AX_PROG_CC_FOR_BUILD], [dnl -AC_REQUIRE([AC_PROG_CC])dnl -AC_REQUIRE([AC_PROG_CPP])dnl -AC_REQUIRE([AC_EXEEXT])dnl -AC_REQUIRE([AC_CANONICAL_HOST])dnl - -dnl Use the standard macros, but make them use other variable names -dnl -pushdef([ac_cv_prog_CPP], ac_cv_build_prog_CPP)dnl -pushdef([ac_cv_prog_gcc], ac_cv_build_prog_gcc)dnl -pushdef([ac_cv_prog_cc_works], ac_cv_build_prog_cc_works)dnl -pushdef([ac_cv_prog_cc_cross], ac_cv_build_prog_cc_cross)dnl -pushdef([ac_cv_prog_cc_g], ac_cv_build_prog_cc_g)dnl -pushdef([ac_cv_exeext], ac_cv_build_exeext)dnl -pushdef([ac_cv_objext], ac_cv_build_objext)dnl -pushdef([ac_exeext], ac_build_exeext)dnl -pushdef([ac_objext], ac_build_objext)dnl -pushdef([CC], CC_FOR_BUILD)dnl -pushdef([CPP], CPP_FOR_BUILD)dnl -pushdef([CFLAGS], CFLAGS_FOR_BUILD)dnl -pushdef([CPPFLAGS], CPPFLAGS_FOR_BUILD)dnl -pushdef([LDFLAGS], LDFLAGS_FOR_BUILD)dnl -pushdef([host], build)dnl -pushdef([host_alias], build_alias)dnl -pushdef([host_cpu], build_cpu)dnl -pushdef([host_vendor], build_vendor)dnl -pushdef([host_os], build_os)dnl -pushdef([ac_cv_host], ac_cv_build)dnl -pushdef([ac_cv_host_alias], ac_cv_build_alias)dnl -pushdef([ac_cv_host_cpu], ac_cv_build_cpu)dnl -pushdef([ac_cv_host_vendor], ac_cv_build_vendor)dnl -pushdef([ac_cv_host_os], ac_cv_build_os)dnl -pushdef([ac_cpp], ac_build_cpp)dnl -pushdef([ac_compile], ac_build_compile)dnl -pushdef([ac_link], ac_build_link)dnl - -save_cross_compiling=$cross_compiling -save_ac_tool_prefix=$ac_tool_prefix -cross_compiling=no -ac_tool_prefix= - -AC_PROG_CC -AC_PROG_CPP -AC_EXEEXT - -ac_tool_prefix=$save_ac_tool_prefix -cross_compiling=$save_cross_compiling - -dnl Restore the old definitions -dnl -popdef([ac_link])dnl -popdef([ac_compile])dnl -popdef([ac_cpp])dnl -popdef([ac_cv_host_os])dnl -popdef([ac_cv_host_vendor])dnl -popdef([ac_cv_host_cpu])dnl -popdef([ac_cv_host_alias])dnl -popdef([ac_cv_host])dnl -popdef([host_os])dnl -popdef([host_vendor])dnl -popdef([host_cpu])dnl -popdef([host_alias])dnl -popdef([host])dnl -popdef([LDFLAGS])dnl -popdef([CPPFLAGS])dnl -popdef([CFLAGS])dnl -popdef([CPP])dnl -popdef([CC])dnl -popdef([ac_objext])dnl -popdef([ac_exeext])dnl -popdef([ac_cv_objext])dnl -popdef([ac_cv_exeext])dnl -popdef([ac_cv_prog_cc_g])dnl -popdef([ac_cv_prog_cc_cross])dnl -popdef([ac_cv_prog_cc_works])dnl -popdef([ac_cv_prog_gcc])dnl -popdef([ac_cv_prog_CPP])dnl - -dnl Finally, set Makefile variables -dnl -BUILD_EXEEXT=$ac_build_exeext -BUILD_OBJEXT=$ac_build_objext -AC_SUBST(BUILD_EXEEXT)dnl -AC_SUBST(BUILD_OBJEXT)dnl -AC_SUBST([CFLAGS_FOR_BUILD])dnl -AC_SUBST([CPPFLAGS_FOR_BUILD])dnl -AC_SUBST([LDFLAGS_FOR_BUILD])dnl -]) diff --git a/src/secp256k1/obj/.gitignore b/src/secp256k1/obj/.gitignore deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/secp256k1/sage/secp256k1.sage b/src/secp256k1/sage/secp256k1.sage deleted file mode 100644 index a97e732f7fa..00000000000 --- a/src/secp256k1/sage/secp256k1.sage +++ /dev/null @@ -1,306 +0,0 @@ -# Test libsecp256k1' group operation implementations using prover.sage - -import sys - -load("group_prover.sage") -load("weierstrass_prover.sage") - -def formula_secp256k1_gej_double_var(a): - """libsecp256k1's secp256k1_gej_double_var, used by various addition functions""" - rz = a.Z * a.Y - rz = rz * 2 - t1 = a.X^2 - t1 = t1 * 3 - t2 = t1^2 - t3 = a.Y^2 - t3 = t3 * 2 - t4 = t3^2 - t4 = t4 * 2 - t3 = t3 * a.X - rx = t3 - rx = rx * 4 - rx = -rx - rx = rx + t2 - t2 = -t2 - t3 = t3 * 6 - t3 = t3 + t2 - ry = t1 * t3 - t2 = -t4 - ry = ry + t2 - return jacobianpoint(rx, ry, rz) - -def formula_secp256k1_gej_add_var(branch, a, b): - """libsecp256k1's secp256k1_gej_add_var""" - if branch == 0: - return (constraints(), constraints(nonzero={a.Infinity : 'a_infinite'}), b) - if branch == 1: - return (constraints(), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a) - z22 = b.Z^2 - z12 = a.Z^2 - u1 = a.X * z22 - u2 = b.X * z12 - s1 = a.Y * z22 - s1 = s1 * b.Z - s2 = b.Y * z12 - s2 = s2 * a.Z - h = -u1 - h = h + u2 - i = -s1 - i = i + s2 - if branch == 2: - r = formula_secp256k1_gej_double_var(a) - return (constraints(), constraints(zero={h : 'h=0', i : 'i=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}), r) - if branch == 3: - return (constraints(), constraints(zero={h : 'h=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 - h2 = h^2 - h3 = h2 * h - h = h * b.Z - rz = a.Z * h - t = u1 * h2 - rx = t - rx = rx * 2 - rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i - h3 = h3 * s1 - h3 = -h3 - ry = ry + h3 - return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) - -def formula_secp256k1_gej_add_ge_var(branch, a, b): - """libsecp256k1's secp256k1_gej_add_ge_var, which assume bz==1""" - if branch == 0: - return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(nonzero={a.Infinity : 'a_infinite'}), b) - if branch == 1: - return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a) - z12 = a.Z^2 - u1 = a.X - u2 = b.X * z12 - s1 = a.Y - s2 = b.Y * z12 - s2 = s2 * a.Z - h = -u1 - h = h + u2 - i = -s1 - i = i + s2 - if (branch == 2): - r = formula_secp256k1_gej_double_var(a) - return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) - if (branch == 3): - return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 - h2 = h^2 - h3 = h * h2 - rz = a.Z * h - t = u1 * h2 - rx = t - rx = rx * 2 - rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i - h3 = h3 * s1 - h3 = -h3 - ry = ry + h3 - return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) - -def formula_secp256k1_gej_add_zinv_var(branch, a, b): - """libsecp256k1's secp256k1_gej_add_zinv_var""" - bzinv = b.Z^(-1) - if branch == 0: - return (constraints(), constraints(nonzero={b.Infinity : 'b_infinite'}), a) - if branch == 1: - bzinv2 = bzinv^2 - bzinv3 = bzinv2 * bzinv - rx = b.X * bzinv2 - ry = b.Y * bzinv3 - rz = 1 - return (constraints(), constraints(zero={b.Infinity : 'b_finite'}, nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz)) - azz = a.Z * bzinv - z12 = azz^2 - u1 = a.X - u2 = b.X * z12 - s1 = a.Y - s2 = b.Y * z12 - s2 = s2 * azz - h = -u1 - h = h + u2 - i = -s1 - i = i + s2 - if branch == 2: - r = formula_secp256k1_gej_double_var(a) - return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) - if branch == 3: - return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 - h2 = h^2 - h3 = h * h2 - rz = a.Z - rz = rz * h - t = u1 * h2 - rx = t - rx = rx * 2 - rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i - h3 = h3 * s1 - h3 = -h3 - ry = ry + h3 - return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) - -def formula_secp256k1_gej_add_ge(branch, a, b): - """libsecp256k1's secp256k1_gej_add_ge""" - zeroes = {} - nonzeroes = {} - a_infinity = False - if (branch & 4) != 0: - nonzeroes.update({a.Infinity : 'a_infinite'}) - a_infinity = True - else: - zeroes.update({a.Infinity : 'a_finite'}) - zz = a.Z^2 - u1 = a.X - u2 = b.X * zz - s1 = a.Y - s2 = b.Y * zz - s2 = s2 * a.Z - t = u1 - t = t + u2 - m = s1 - m = m + s2 - rr = t^2 - m_alt = -u2 - tt = u1 * m_alt - rr = rr + tt - degenerate = (branch & 3) == 3 - if (branch & 1) != 0: - zeroes.update({m : 'm_zero'}) - else: - nonzeroes.update({m : 'm_nonzero'}) - if (branch & 2) != 0: - zeroes.update({rr : 'rr_zero'}) - else: - nonzeroes.update({rr : 'rr_nonzero'}) - rr_alt = s1 - rr_alt = rr_alt * 2 - m_alt = m_alt + u1 - if not degenerate: - rr_alt = rr - m_alt = m - n = m_alt^2 - q = n * t - n = n^2 - if degenerate: - n = m - t = rr_alt^2 - rz = a.Z * m_alt - infinity = False - if (branch & 8) != 0: - if not a_infinity: - infinity = True - zeroes.update({rz : 'r.z=0'}) - else: - nonzeroes.update({rz : 'r.z!=0'}) - rz = rz * 2 - q = -q - t = t + q - rx = t - t = t * 2 - t = t + q - t = t * rr_alt - t = t + n - ry = -t - rx = rx * 4 - ry = ry * 4 - if a_infinity: - rx = b.X - ry = b.Y - rz = 1 - if infinity: - return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), point_at_infinity()) - return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), jacobianpoint(rx, ry, rz)) - -def formula_secp256k1_gej_add_ge_old(branch, a, b): - """libsecp256k1's old secp256k1_gej_add_ge, which fails when ay+by=0 but ax!=bx""" - a_infinity = (branch & 1) != 0 - zero = {} - nonzero = {} - if a_infinity: - nonzero.update({a.Infinity : 'a_infinite'}) - else: - zero.update({a.Infinity : 'a_finite'}) - zz = a.Z^2 - u1 = a.X - u2 = b.X * zz - s1 = a.Y - s2 = b.Y * zz - s2 = s2 * a.Z - z = a.Z - t = u1 - t = t + u2 - m = s1 - m = m + s2 - n = m^2 - q = n * t - n = n^2 - rr = t^2 - t = u1 * u2 - t = -t - rr = rr + t - t = rr^2 - rz = m * z - infinity = False - if (branch & 2) != 0: - if not a_infinity: - infinity = True - else: - return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(nonzero={z : 'conflict_a'}, zero={z : 'conflict_b'}), point_at_infinity()) - zero.update({rz : 'r.z=0'}) - else: - nonzero.update({rz : 'r.z!=0'}) - rz = rz * (0 if a_infinity else 2) - rx = t - q = -q - rx = rx + q - q = q * 3 - t = t * 2 - t = t + q - t = t * rr - t = t + n - ry = -t - rx = rx * (0 if a_infinity else 4) - ry = ry * (0 if a_infinity else 4) - t = b.X - t = t * (1 if a_infinity else 0) - rx = rx + t - t = b.Y - t = t * (1 if a_infinity else 0) - ry = ry + t - t = (1 if a_infinity else 0) - rz = rz + t - if infinity: - return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), point_at_infinity()) - return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), jacobianpoint(rx, ry, rz)) - -if __name__ == "__main__": - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old) - - if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive": - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43) diff --git a/src/secp256k1/src/basic-config.h b/src/secp256k1/src/basic-config.h deleted file mode 100644 index c4c16eb7ca7..00000000000 --- a/src/secp256k1/src/basic-config.h +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#ifndef _SECP256K1_BASIC_CONFIG_ -#define _SECP256K1_BASIC_CONFIG_ - -#ifdef USE_BASIC_CONFIG - -#undef USE_ASM_X86_64 -#undef USE_ENDOMORPHISM -#undef USE_FIELD_10X26 -#undef USE_FIELD_5X52 -#undef USE_FIELD_INV_BUILTIN -#undef USE_FIELD_INV_NUM -#undef USE_NUM_GMP -#undef USE_NUM_NONE -#undef USE_SCALAR_4X64 -#undef USE_SCALAR_8X32 -#undef USE_SCALAR_INV_BUILTIN -#undef USE_SCALAR_INV_NUM - -#define USE_NUM_NONE 1 -#define USE_FIELD_INV_BUILTIN 1 -#define USE_SCALAR_INV_BUILTIN 1 -#define USE_FIELD_10X26 1 -#define USE_SCALAR_8X32 1 - -#endif // USE_BASIC_CONFIG -#endif // _SECP256K1_BASIC_CONFIG_ diff --git a/src/secp256k1/src/bench_ecdh.c b/src/secp256k1/src/bench_ecdh.c deleted file mode 100644 index e290de32609..00000000000 --- a/src/secp256k1/src/bench_ecdh.c +++ /dev/null @@ -1,54 +0,0 @@ -/********************************************************************** - * Copyright (c) 2015 Pieter Wuille, Andrew Poelstra * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#include - -#include "bench.h" -#include "secp256k1.h" -#include "secp256k1_ecdh.h" -#include "util.h" - -typedef struct { - secp256k1_context *ctx; - secp256k1_pubkey point; - unsigned char scalar[32]; -} bench_ecdh_t; - -static void bench_ecdh_setup(void* arg) { - int i; - bench_ecdh_t *data = (bench_ecdh_t*)arg; - const unsigned char point[] = { - 0x03, - 0x54, 0x94, 0xc1, 0x5d, 0x32, 0x09, 0x97, 0x06, - 0xc2, 0x39, 0x5f, 0x94, 0x34, 0x87, 0x45, 0xfd, - 0x75, 0x7c, 0xe3, 0x0e, 0x4e, 0x8c, 0x90, 0xfb, - 0xa2, 0xba, 0xd1, 0x84, 0xf8, 0x83, 0xc6, 0x9f - }; - - /* create a context with no capabilities */ - data->ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT); - for (i = 0; i < 32; i++) { - data->scalar[i] = i + 1; - } - CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->point, point, sizeof(point)) == 1); -} - -static void bench_ecdh(void* arg) { - int i; - unsigned char res[32]; - bench_ecdh_t *data = (bench_ecdh_t*)arg; - - for (i = 0; i < 20000; i++) { - CHECK(secp256k1_ecdh(data->ctx, res, &data->point, data->scalar) == 1); - } -} - -int main(void) { - bench_ecdh_t data; - - run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, 20000); - return 0; -} diff --git a/src/secp256k1/src/bench_recover.c b/src/secp256k1/src/bench_recover.c deleted file mode 100644 index 010d7798049..00000000000 --- a/src/secp256k1/src/bench_recover.c +++ /dev/null @@ -1,60 +0,0 @@ -/********************************************************************** - * Copyright (c) 2014-2015 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#include "bench.h" -#include "secp256k1.h" -#include "secp256k1_recovery.h" -#include "util.h" - -typedef struct { - secp256k1_context *ctx; - unsigned char msg[32]; - unsigned char sig[64]; -} bench_recover_t; - -void bench_recover(void* arg) { - int i; - bench_recover_t *data = (bench_recover_t*)arg; - secp256k1_pubkey pubkey; - unsigned char pubkeyc[33]; - - for (i = 0; i < 20000; i++) { - int j; - size_t pubkeylen = 33; - secp256k1_ecdsa_recoverable_signature sig; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(data->ctx, &sig, data->sig, i % 2)); - CHECK(secp256k1_ecdsa_recover(data->ctx, &pubkey, &sig, data->msg)); - CHECK(secp256k1_ec_pubkey_serialize(data->ctx, pubkeyc, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED)); - for (j = 0; j < 32; j++) { - data->sig[j + 32] = data->msg[j]; /* Move former message to S. */ - data->msg[j] = data->sig[j]; /* Move former R to message. */ - data->sig[j] = pubkeyc[j + 1]; /* Move recovered pubkey X coordinate to R (which must be a valid X coordinate). */ - } - } -} - -void bench_recover_setup(void* arg) { - int i; - bench_recover_t *data = (bench_recover_t*)arg; - - for (i = 0; i < 32; i++) { - data->msg[i] = 1 + i; - } - for (i = 0; i < 64; i++) { - data->sig[i] = 65 + i; - } -} - -int main(void) { - bench_recover_t data; - - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - - run_benchmark("ecdsa_recover", bench_recover, bench_recover_setup, NULL, &data, 10, 20000); - - secp256k1_context_destroy(data.ctx); - return 0; -} diff --git a/src/secp256k1/src/bench_schnorr_verify.c b/src/secp256k1/src/bench_schnorr_verify.c deleted file mode 100644 index a7d321b7523..00000000000 --- a/src/secp256k1/src/bench_schnorr_verify.c +++ /dev/null @@ -1,73 +0,0 @@ -/********************************************************************** - * Copyright (c) 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#include -#include - -#include "bench.h" -#include "secp256k1.h" -#include "secp256k1_schnorr.h" -#include "util.h" - -typedef struct { - unsigned char key[32]; - unsigned char sig[64]; - unsigned char pubkey[33]; - size_t pubkeylen; -} benchmark_schnorr_sig_t; - -typedef struct { - secp256k1_context *ctx; - unsigned char msg[32]; - benchmark_schnorr_sig_t sigs[64]; - int numsigs; -} benchmark_schnorr_verify_t; - -static void benchmark_schnorr_init(void* arg) { - int i, k; - benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; - - for (i = 0; i < 32; i++) { - data->msg[i] = 1 + i; - } - for (k = 0; k < data->numsigs; k++) { - secp256k1_pubkey pubkey; - for (i = 0; i < 32; i++) { - data->sigs[k].key[i] = 33 + i + k; - } - secp256k1_schnorr_sign(data->ctx, data->sigs[k].sig, data->msg, data->sigs[k].key, NULL, NULL); - data->sigs[k].pubkeylen = 33; - CHECK(secp256k1_ec_pubkey_create(data->ctx, &pubkey, data->sigs[k].key)); - CHECK(secp256k1_ec_pubkey_serialize(data->ctx, data->sigs[k].pubkey, &data->sigs[k].pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED)); - } -} - -static void benchmark_schnorr_verify(void* arg) { - int i; - benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; - - for (i = 0; i < 20000 / data->numsigs; i++) { - secp256k1_pubkey pubkey; - data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); - CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->sigs[0].pubkey, data->sigs[0].pubkeylen)); - CHECK(secp256k1_schnorr_verify(data->ctx, data->sigs[0].sig, data->msg, &pubkey) == ((i & 0xFF) == 0)); - data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); - } -} - - - -int main(void) { - benchmark_schnorr_verify_t data; - - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - - data.numsigs = 1; - run_benchmark("schnorr_verify", benchmark_schnorr_verify, benchmark_schnorr_init, NULL, &data, 10, 20000); - - secp256k1_context_destroy(data.ctx); - return 0; -} diff --git a/src/secp256k1/src/bench_sign.c b/src/secp256k1/src/bench_sign.c deleted file mode 100644 index 435216f4aa5..00000000000 --- a/src/secp256k1/src/bench_sign.c +++ /dev/null @@ -1,56 +0,0 @@ -/********************************************************************** - * Copyright (c) 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#include "bench.h" -#include "secp256k1.h" -#include "util.h" - -typedef struct { - secp256k1_context* ctx; - unsigned char msg[32]; - unsigned char key[32]; -} bench_sign_t; - -static void bench_sign_setup(void* arg) { - int i; - bench_sign_t *data = (bench_sign_t*)arg; - - for (i = 0; i < 32; i++) { - data->msg[i] = i + 1; - } - for (i = 0; i < 32; i++) { - data->key[i] = i + 65; - } -} - -static void bench_sign(void* arg) { - int i; - bench_sign_t *data = (bench_sign_t*)arg; - - unsigned char sig[74]; - for (i = 0; i < 20000; i++) { - size_t siglen = 74; - int j; - secp256k1_ecdsa_signature signature; - CHECK(secp256k1_ecdsa_sign(data->ctx, &signature, data->msg, data->key, NULL, NULL)); - CHECK(secp256k1_ecdsa_signature_serialize_der(data->ctx, sig, &siglen, &signature)); - for (j = 0; j < 32; j++) { - data->msg[j] = sig[j]; - data->key[j] = sig[j + 32]; - } - } -} - -int main(void) { - bench_sign_t data; - - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - - run_benchmark("ecdsa_sign", bench_sign, bench_sign_setup, NULL, &data, 10, 20000); - - secp256k1_context_destroy(data.ctx); - return 0; -} diff --git a/src/secp256k1/src/bench_verify.c b/src/secp256k1/src/bench_verify.c deleted file mode 100644 index 05c6929b2a8..00000000000 --- a/src/secp256k1/src/bench_verify.c +++ /dev/null @@ -1,112 +0,0 @@ -/********************************************************************** - * Copyright (c) 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#include -#include - -#include "bench.h" -#include "secp256k1.h" -#include "util.h" - -#ifdef ENABLE_OPENSSL_TESTS -#include -#include -#include -#endif - -typedef struct { - secp256k1_context *ctx; - unsigned char msg[32]; - unsigned char key[32]; - unsigned char sig[72]; - size_t siglen; - unsigned char pubkey[33]; - size_t pubkeylen; -#ifdef ENABLE_OPENSSL_TESTS - EC_GROUP* ec_group; -#endif -} benchmark_verify_t; - -static void benchmark_verify(void* arg) { - int i; - benchmark_verify_t* data = (benchmark_verify_t*)arg; - - for (i = 0; i < 20000; i++) { - secp256k1_pubkey pubkey; - secp256k1_ecdsa_signature sig; - data->sig[data->siglen - 1] ^= (i & 0xFF); - data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); - data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); - CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->pubkey, data->pubkeylen) == 1); - CHECK(secp256k1_ecdsa_signature_parse_der(data->ctx, &sig, data->sig, data->siglen) == 1); - CHECK(secp256k1_ecdsa_verify(data->ctx, &sig, data->msg, &pubkey) == (i == 0)); - data->sig[data->siglen - 1] ^= (i & 0xFF); - data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); - data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); - } -} - -#ifdef ENABLE_OPENSSL_TESTS -static void benchmark_verify_openssl(void* arg) { - int i; - benchmark_verify_t* data = (benchmark_verify_t*)arg; - - for (i = 0; i < 20000; i++) { - data->sig[data->siglen - 1] ^= (i & 0xFF); - data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); - data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); - { - EC_KEY *pkey = EC_KEY_new(); - const unsigned char *pubkey = &data->pubkey[0]; - int result; - - CHECK(pkey != NULL); - result = EC_KEY_set_group(pkey, data->ec_group); - CHECK(result); - result = (o2i_ECPublicKey(&pkey, &pubkey, data->pubkeylen)) != NULL; - CHECK(result); - result = ECDSA_verify(0, &data->msg[0], sizeof(data->msg), &data->sig[0], data->siglen, pkey) == (i == 0); - CHECK(result); - EC_KEY_free(pkey); - } - data->sig[data->siglen - 1] ^= (i & 0xFF); - data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); - data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); - } -} -#endif - -int main(void) { - int i; - secp256k1_pubkey pubkey; - secp256k1_ecdsa_signature sig; - benchmark_verify_t data; - - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - - for (i = 0; i < 32; i++) { - data.msg[i] = 1 + i; - } - for (i = 0; i < 32; i++) { - data.key[i] = 33 + i; - } - data.siglen = 72; - CHECK(secp256k1_ecdsa_sign(data.ctx, &sig, data.msg, data.key, NULL, NULL)); - CHECK(secp256k1_ecdsa_signature_serialize_der(data.ctx, data.sig, &data.siglen, &sig)); - CHECK(secp256k1_ec_pubkey_create(data.ctx, &pubkey, data.key)); - data.pubkeylen = 33; - CHECK(secp256k1_ec_pubkey_serialize(data.ctx, data.pubkey, &data.pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED) == 1); - - run_benchmark("ecdsa_verify", benchmark_verify, NULL, NULL, &data, 10, 20000); -#ifdef ENABLE_OPENSSL_TESTS - data.ec_group = EC_GROUP_new_by_curve_name(NID_secp256k1); - run_benchmark("ecdsa_verify_openssl", benchmark_verify_openssl, NULL, NULL, &data, 10, 20000); - EC_GROUP_free(data.ec_group); -#endif - - secp256k1_context_destroy(data.ctx); - return 0; -} diff --git a/src/secp256k1/src/gen_context.c b/src/secp256k1/src/gen_context.c deleted file mode 100644 index cd2be2a3223..00000000000 --- a/src/secp256k1/src/gen_context.c +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014, 2015 Thomas Daede, Cory Fields * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#define USE_BASIC_CONFIG 1 - -#include "basic-config.h" -#include "ecmult_gen_impl.h" -#include "field_impl.h" -#include "group_impl.h" -#include "scalar_impl.h" -#include "secp256k1.h" - -static void default_error_callback_fn(const char* str, void* data) { - (void)data; - fprintf(stderr, "[libsecp256k1] internal consistency check failed: %s\n", str); - abort(); -} - -static const secp256k1_callback default_error_callback = { - default_error_callback_fn, - NULL -}; - -int main(int argc, char **argv) { - secp256k1_ecmult_gen_context ctx; - int inner; - int outer; - FILE* fp; - - (void)argc; - (void)argv; - - fp = fopen("src/ecmult_static_context.h","w"); - if (fp == NULL) { - fprintf(stderr, "Could not open src/ecmult_static_context.h for writing!\n"); - return -1; - } - - fprintf(fp, "#ifndef _SECP256K1_ECMULT_STATIC_CONTEXT_\n"); - fprintf(fp, "#define _SECP256K1_ECMULT_STATIC_CONTEXT_\n"); - fprintf(fp, "#include \"group.h\"\n"); - fprintf(fp, "#define SC SECP256K1_GE_STORAGE_CONST\n"); - fprintf(fp, "static const secp256k1_ge_storage secp256k1_ecmult_static_context[64][16] = {\n"); - - secp256k1_ecmult_gen_context_init(&ctx); - secp256k1_ecmult_gen_context_build(&ctx, &default_error_callback); - for(outer = 0; outer != 64; outer++) { - fprintf(fp,"{\n"); - for(inner = 0; inner != 16; inner++) { - fprintf(fp," SC(%uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu)", SECP256K1_GE_STORAGE_CONST_GET((*ctx.prec)[outer][inner])); - if (inner != 15) { - fprintf(fp,",\n"); - } else { - fprintf(fp,"\n"); - } - } - if (outer != 63) { - fprintf(fp,"},\n"); - } else { - fprintf(fp,"}\n"); - } - } - fprintf(fp,"};\n"); - secp256k1_ecmult_gen_context_clear(&ctx); - - fprintf(fp, "#undef SC\n"); - fprintf(fp, "#endif\n"); - fclose(fp); - - return 0; -} diff --git a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java deleted file mode 100644 index 1c67802fba8..00000000000 --- a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * Copyright 2014-2016 the libsecp256k1 contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bitcoin; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import java.math.BigInteger; -import com.google.common.base.Preconditions; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import static org.bitcoin.NativeSecp256k1Util.*; - -/** - *

This class holds native methods to handle ECDSA verification.

- * - *

You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1

- * - *

To build secp256k1 for use with bitcoinj, run - * `./configure --enable-jni --enable-experimental --enable-module-ecdh` - * and `make` then copy `.libs/libsecp256k1.so` to your system library path - * or point the JVM to the folder containing it with -Djava.library.path - *

- */ -public class NativeSecp256k1 { - - private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - private static final Lock r = rwl.readLock(); - private static final Lock w = rwl.writeLock(); - private static ThreadLocal nativeECDSABuffer = new ThreadLocal(); - /** - * Verifies the given secp256k1 signature in native code. - * Calling when enabled == false is undefined (probably library not loaded) - * - * @param data The data which was signed, must be exactly 32 bytes - * @param signature The signature - * @param pub The public key which did the signing - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{ - Preconditions.checkArgument(data.length == 32 && signature.length <= 520 && pub.length <= 520); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < 520) { - byteBuff = ByteBuffer.allocateDirect(520); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(data); - byteBuff.put(signature); - byteBuff.put(pub); - - byte[][] retByteArray; - - r.lock(); - try { - return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.length, pub.length) == 1; - } finally { - r.unlock(); - } - } - - /** - * libsecp256k1 Create an ECDSA signature. - * - * @param data Message hash, 32 bytes - * @param key Secret key, 32 bytes - * - * Return values - * @param sig byte array of signature - */ - public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ - Preconditions.checkArgument(data.length == 32 && sec.length <= 32); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < 32 + 32) { - byteBuff = ByteBuffer.allocateDirect(32 + 32); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(data); - byteBuff.put(sec); - - byte[][] retByteArray; - - r.lock(); - try { - retByteArray = secp256k1_ecdsa_sign(byteBuff, Secp256k1Context.getContext()); - } finally { - r.unlock(); - } - - byte[] sigArr = retByteArray[0]; - int sigLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(sigArr.length, sigLen, "Got bad signature length."); - - return retVal == 0 ? new byte[0] : sigArr; - } - - /** - * libsecp256k1 Seckey Verify - returns 1 if valid, 0 if invalid - * - * @param seckey ECDSA Secret key, 32 bytes - */ - public static boolean secKeyVerify(byte[] seckey) { - Preconditions.checkArgument(seckey.length == 32); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < seckey.length) { - byteBuff = ByteBuffer.allocateDirect(seckey.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(seckey); - - r.lock(); - try { - return secp256k1_ec_seckey_verify(byteBuff,Secp256k1Context.getContext()) == 1; - } finally { - r.unlock(); - } - } - - - /** - * libsecp256k1 Compute Pubkey - computes public key from secret key - * - * @param seckey ECDSA Secret key, 32 bytes - * - * Return values - * @param pubkey ECDSA Public key, 33 or 65 bytes - */ - //TODO add a 'compressed' arg - public static byte[] computePubkey(byte[] seckey) throws AssertFailException{ - Preconditions.checkArgument(seckey.length == 32); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < seckey.length) { - byteBuff = ByteBuffer.allocateDirect(seckey.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(seckey); - - byte[][] retByteArray; - - r.lock(); - try { - retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext()); - } finally { - r.unlock(); - } - - byte[] pubArr = retByteArray[0]; - int pubLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); - - return retVal == 0 ? new byte[0]: pubArr; - } - - /** - * libsecp256k1 Cleanup - This destroys the secp256k1 context object - * This should be called at the end of the program for proper cleanup of the context. - */ - public static synchronized void cleanup() { - w.lock(); - try { - secp256k1_destroy_context(Secp256k1Context.getContext()); - } finally { - w.unlock(); - } - } - - public static long cloneContext() { - r.lock(); - try { - return secp256k1_ctx_clone(Secp256k1Context.getContext()); - } finally { r.unlock(); } - } - - /** - * libsecp256k1 PrivKey Tweak-Mul - Tweak privkey by multiplying to it - * - * @param tweak some bytes to tweak with - * @param seckey 32-byte seckey - */ - public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(privkey.length == 32); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { - byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(privkey); - byteBuff.put(tweak); - - byte[][] retByteArray; - r.lock(); - try { - retByteArray = secp256k1_privkey_tweak_mul(byteBuff,Secp256k1Context.getContext()); - } finally { - r.unlock(); - } - - byte[] privArr = retByteArray[0]; - - int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(privArr.length, privLen, "Got bad pubkey length."); - - assertEquals(retVal, 1, "Failed return value check."); - - return privArr; - } - - /** - * libsecp256k1 PrivKey Tweak-Add - Tweak privkey by adding to it - * - * @param tweak some bytes to tweak with - * @param seckey 32-byte seckey - */ - public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(privkey.length == 32); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { - byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(privkey); - byteBuff.put(tweak); - - byte[][] retByteArray; - r.lock(); - try { - retByteArray = secp256k1_privkey_tweak_add(byteBuff,Secp256k1Context.getContext()); - } finally { - r.unlock(); - } - - byte[] privArr = retByteArray[0]; - - int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(privArr.length, privLen, "Got bad pubkey length."); - - assertEquals(retVal, 1, "Failed return value check."); - - return privArr; - } - - /** - * libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it - * - * @param tweak some bytes to tweak with - * @param pubkey 32-byte seckey - */ - public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { - byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(pubkey); - byteBuff.put(tweak); - - byte[][] retByteArray; - r.lock(); - try { - retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length); - } finally { - r.unlock(); - } - - byte[] pubArr = retByteArray[0]; - - int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); - - assertEquals(retVal, 1, "Failed return value check."); - - return pubArr; - } - - /** - * libsecp256k1 PubKey Tweak-Mul - Tweak pubkey by multiplying to it - * - * @param tweak some bytes to tweak with - * @param pubkey 32-byte seckey - */ - public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFailException{ - Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { - byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(pubkey); - byteBuff.put(tweak); - - byte[][] retByteArray; - r.lock(); - try { - retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length); - } finally { - r.unlock(); - } - - byte[] pubArr = retByteArray[0]; - - int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; - int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); - - assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); - - assertEquals(retVal, 1, "Failed return value check."); - - return pubArr; - } - - /** - * libsecp256k1 create ECDH secret - constant time ECDH calculation - * - * @param seckey byte array of secret key used in exponentiaion - * @param pubkey byte array of public key used in exponentiaion - */ - public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws AssertFailException{ - Preconditions.checkArgument(seckey.length <= 32 && pubkey.length <= 65); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < 32 + pubkey.length) { - byteBuff = ByteBuffer.allocateDirect(32 + pubkey.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(seckey); - byteBuff.put(pubkey); - - byte[][] retByteArray; - r.lock(); - try { - retByteArray = secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.length); - } finally { - r.unlock(); - } - - byte[] resArr = retByteArray[0]; - int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); - - assertEquals(resArr.length, 32, "Got bad result length."); - assertEquals(retVal, 1, "Failed return value check."); - - return resArr; - } - - /** - * libsecp256k1 randomize - updates the context randomization - * - * @param seed 32-byte random seed - */ - public static synchronized boolean randomize(byte[] seed) throws AssertFailException{ - Preconditions.checkArgument(seed.length == 32 || seed == null); - - ByteBuffer byteBuff = nativeECDSABuffer.get(); - if (byteBuff == null || byteBuff.capacity() < seed.length) { - byteBuff = ByteBuffer.allocateDirect(seed.length); - byteBuff.order(ByteOrder.nativeOrder()); - nativeECDSABuffer.set(byteBuff); - } - byteBuff.rewind(); - byteBuff.put(seed); - - w.lock(); - try { - return secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1; - } finally { - w.unlock(); - } - } - - private static native long secp256k1_ctx_clone(long context); - - private static native int secp256k1_context_randomize(ByteBuffer byteBuff, long context); - - private static native byte[][] secp256k1_privkey_tweak_add(ByteBuffer byteBuff, long context); - - private static native byte[][] secp256k1_privkey_tweak_mul(ByteBuffer byteBuff, long context); - - private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen); - - private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen); - - private static native void secp256k1_destroy_context(long context); - - private static native int secp256k1_ecdsa_verify(ByteBuffer byteBuff, long context, int sigLen, int pubLen); - - private static native byte[][] secp256k1_ecdsa_sign(ByteBuffer byteBuff, long context); - - private static native int secp256k1_ec_seckey_verify(ByteBuffer byteBuff, long context); - - private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context); - - private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); - - private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); - -} diff --git a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java deleted file mode 100644 index c00d08899b9..00000000000 --- a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.bitcoin; - -import com.google.common.io.BaseEncoding; -import java.util.Arrays; -import java.math.BigInteger; -import javax.xml.bind.DatatypeConverter; -import static org.bitcoin.NativeSecp256k1Util.*; - -/** - * This class holds test cases defined for testing this library. - */ -public class NativeSecp256k1Test { - - //TODO improve comments/add more tests - /** - * This tests verify() for a valid signature - */ - public static void testVerifyPos() throws AssertFailException{ - boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - - result = NativeSecp256k1.verify( data, sig, pub); - assertEquals( result, true , "testVerifyPos"); - } - - /** - * This tests verify() for a non-valid signature - */ - public static void testVerifyNeg() throws AssertFailException{ - boolean result = false; - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()); //sha256hash of "testing" - byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - - result = NativeSecp256k1.verify( data, sig, pub); - //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals( result, false , "testVerifyNeg"); - } - - /** - * This tests secret key verify() for a valid secretkey - */ - public static void testSecKeyVerifyPos() throws AssertFailException{ - boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - - result = NativeSecp256k1.secKeyVerify( sec ); - //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals( result, true , "testSecKeyVerifyPos"); - } - - /** - * This tests secret key verify() for a invalid secretkey - */ - public static void testSecKeyVerifyNeg() throws AssertFailException{ - boolean result = false; - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); - - result = NativeSecp256k1.secKeyVerify( sec ); - //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals( result, false , "testSecKeyVerifyNeg"); - } - - /** - * This tests public key create() for a valid secretkey - */ - public static void testPubKeyCreatePos() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - - byte[] resultArr = NativeSecp256k1.computePubkey( sec); - String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( pubkeyString , "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6" , "testPubKeyCreatePos"); - } - - /** - * This tests public key create() for a invalid secretkey - */ - public static void testPubKeyCreateNeg() throws AssertFailException{ - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); - - byte[] resultArr = NativeSecp256k1.computePubkey( sec); - String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( pubkeyString, "" , "testPubKeyCreateNeg"); - } - - /** - * This tests sign() for a valid secretkey - */ - public static void testSignPos() throws AssertFailException{ - - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - - byte[] resultArr = NativeSecp256k1.sign(data, sec); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString, "30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9" , "testSignPos"); - } - - /** - * This tests sign() for a invalid secretkey - */ - public static void testSignNeg() throws AssertFailException{ - byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" - byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); - - byte[] resultArr = NativeSecp256k1.sign(data, sec); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString, "" , "testSignNeg"); - } - - /** - * This tests private key tweak-add - */ - public static void testPrivKeyTweakAdd_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" - - byte[] resultArr = NativeSecp256k1.privKeyTweakAdd( sec , data ); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString , "A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3" , "testPrivKeyAdd_1"); - } - - /** - * This tests private key tweak-mul - */ - public static void testPrivKeyTweakMul_1() throws AssertFailException { - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" - - byte[] resultArr = NativeSecp256k1.privKeyTweakMul( sec , data ); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString , "97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC" , "testPrivKeyMul_1"); - } - - /** - * This tests private key tweak-add uncompressed - */ - public static void testPrivKeyTweakAdd_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" - - byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data ); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString , "0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF" , "testPrivKeyAdd_2"); - } - - /** - * This tests private key tweak-mul uncompressed - */ - public static void testPrivKeyTweakMul_2() throws AssertFailException { - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" - - byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data ); - String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( sigString , "04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589" , "testPrivKeyMul_2"); - } - - /** - * This tests seed randomization - */ - public static void testRandomize() throws AssertFailException { - byte[] seed = BaseEncoding.base16().lowerCase().decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()); //sha256hash of "random" - boolean result = NativeSecp256k1.randomize(seed); - assertEquals( result, true, "testRandomize"); - } - - public static void testCreateECDHSecret() throws AssertFailException{ - - byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); - byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); - - byte[] resultArr = NativeSecp256k1.createECDHSecret(sec, pub); - String ecdhString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); - assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret"); - } - - public static void main(String[] args) throws AssertFailException{ - - - System.out.println("\n libsecp256k1 enabled: " + Secp256k1Context.isEnabled() + "\n"); - - assertEquals( Secp256k1Context.isEnabled(), true, "isEnabled" ); - - //Test verify() success/fail - testVerifyPos(); - testVerifyNeg(); - - //Test secKeyVerify() success/fail - testSecKeyVerifyPos(); - testSecKeyVerifyNeg(); - - //Test computePubkey() success/fail - testPubKeyCreatePos(); - testPubKeyCreateNeg(); - - //Test sign() success/fail - testSignPos(); - testSignNeg(); - - //Test privKeyTweakAdd() 1 - testPrivKeyTweakAdd_1(); - - //Test privKeyTweakMul() 2 - testPrivKeyTweakMul_1(); - - //Test privKeyTweakAdd() 3 - testPrivKeyTweakAdd_2(); - - //Test privKeyTweakMul() 4 - testPrivKeyTweakMul_2(); - - //Test randomize() - testRandomize(); - - //Test ECDH - testCreateECDHSecret(); - - NativeSecp256k1.cleanup(); - - System.out.println(" All tests passed." ); - - } -} diff --git a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java b/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java deleted file mode 100644 index 04732ba0443..00000000000 --- a/src/secp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2014-2016 the libsecp256k1 contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bitcoin; - -public class NativeSecp256k1Util{ - - public static void assertEquals( int val, int val2, String message ) throws AssertFailException{ - if( val != val2 ) - throw new AssertFailException("FAIL: " + message); - } - - public static void assertEquals( boolean val, boolean val2, String message ) throws AssertFailException{ - if( val != val2 ) - throw new AssertFailException("FAIL: " + message); - else - System.out.println("PASS: " + message); - } - - public static void assertEquals( String val, String val2, String message ) throws AssertFailException{ - if( !val.equals(val2) ) - throw new AssertFailException("FAIL: " + message); - else - System.out.println("PASS: " + message); - } - - public static class AssertFailException extends Exception { - public AssertFailException(String message) { - super( message ); - } - } -} diff --git a/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java b/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java deleted file mode 100644 index 216c986a8b5..00000000000 --- a/src/secp256k1/src/java/org/bitcoin/Secp256k1Context.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2014-2016 the libsecp256k1 contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bitcoin; - -/** - * This class holds the context reference used in native methods - * to handle ECDSA operations. - */ -public class Secp256k1Context { - private static final boolean enabled; //true if the library is loaded - private static final long context; //ref to pointer to context obj - - static { //static initializer - boolean isEnabled = true; - long contextRef = -1; - try { - System.loadLibrary("secp256k1"); - contextRef = secp256k1_init_context(); - } catch (UnsatisfiedLinkError e) { - System.out.println("UnsatisfiedLinkError: " + e.toString()); - isEnabled = false; - } - enabled = isEnabled; - context = contextRef; - } - - public static boolean isEnabled() { - return enabled; - } - - public static long getContext() { - if(!enabled) return -1; //sanity check - return context; - } - - private static native long secp256k1_init_context(); -} diff --git a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c deleted file mode 100644 index e0936c4e1aa..00000000000 --- a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c +++ /dev/null @@ -1,376 +0,0 @@ -#include "org_bitcoin_NativeSecp256k1.h" -#include "secp256k1.h" -#include "secp256k1_ecdh.h" -#include "secp256k1_recovery.h" -#include -#include -#include - -SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone - (JNIEnv* env, jclass classObject, jlong ctx_l) -{ - const secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - - jlong ctx_clone_l = (uintptr_t) secp256k1_context_clone(ctx); - - (void)classObject;(void)env; - - return ctx_clone_l; - -} - -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1context_1randomize - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - - const unsigned char* seed = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - - (void)classObject; - - return secp256k1_context_randomize(ctx, seed); - -} - -SECP256K1_API void JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1destroy_1context - (JNIEnv* env, jclass classObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - - secp256k1_context_destroy(ctx); - - (void)classObject;(void)env; -} - -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint siglen, jint publen) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - - unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* sigdata = { (unsigned char*) (data + 32) }; - const unsigned char* pubdata = { (unsigned char*) (data + siglen + 32) }; - - secp256k1_ecdsa_signature sig; - secp256k1_pubkey pubkey; - - int ret = secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigdata, siglen); - - if( ret ) { - ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen); - - if( ret ) { - ret = secp256k1_ecdsa_verify(ctx, &sig, data, &pubkey); - } - } - - (void)classObject; - - return ret; -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - unsigned char* secKey = (unsigned char*) (data + 32); - - jobjectArray retArray; - jbyteArray sigArray, intsByteArray; - unsigned char intsarray[2]; - - secp256k1_ecdsa_signature sig[72]; - - int ret = secp256k1_ecdsa_sign(ctx, sig, data, secKey, NULL, NULL ); - - unsigned char outputSer[72]; - size_t outputLen = 72; - - if( ret ) { - int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx,outputSer, &outputLen, sig ); (void)ret2; - } - - intsarray[0] = outputLen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - sigArray = (*env)->NewByteArray(env, outputLen); - (*env)->SetByteArrayRegion(env, sigArray, 0, outputLen, (jbyte*)outputSer); - (*env)->SetObjectArrayElement(env, retArray, 0, sigArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} - -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* secKey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - - (void)classObject; - - return secp256k1_ec_seckey_verify(ctx, secKey); -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - const unsigned char* secKey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - - secp256k1_pubkey pubkey; - - jobjectArray retArray; - jbyteArray pubkeyArray, intsByteArray; - unsigned char intsarray[2]; - - int ret = secp256k1_ec_pubkey_create(ctx, &pubkey, secKey); - - unsigned char outputSer[65]; - size_t outputLen = 65; - - if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; - } - - intsarray[0] = outputLen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - pubkeyArray = (*env)->NewByteArray(env, outputLen); - (*env)->SetByteArrayRegion(env, pubkeyArray, 0, outputLen, (jbyte*)outputSer); - (*env)->SetObjectArrayElement(env, retArray, 0, pubkeyArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; - -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1add - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* privkey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* tweak = (unsigned char*) (privkey + 32); - - jobjectArray retArray; - jbyteArray privArray, intsByteArray; - unsigned char intsarray[2]; - - int privkeylen = 32; - - int ret = secp256k1_ec_privkey_tweak_add(ctx, privkey, tweak); - - intsarray[0] = privkeylen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - privArray = (*env)->NewByteArray(env, privkeylen); - (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte*)privkey); - (*env)->SetObjectArrayElement(env, retArray, 0, privArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1mul - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* privkey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* tweak = (unsigned char*) (privkey + 32); - - jobjectArray retArray; - jbyteArray privArray, intsByteArray; - unsigned char intsarray[2]; - - int privkeylen = 32; - - int ret = secp256k1_ec_privkey_tweak_mul(ctx, privkey, tweak); - - intsarray[0] = privkeylen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - privArray = (*env)->NewByteArray(env, privkeylen); - (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte*)privkey); - (*env)->SetObjectArrayElement(env, retArray, 0, privArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; -/* secp256k1_pubkey* pubkey = (secp256k1_pubkey*) (*env)->GetDirectBufferAddress(env, byteBufferObject);*/ - unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* tweak = (unsigned char*) (pkey + publen); - - jobjectArray retArray; - jbyteArray pubArray, intsByteArray; - unsigned char intsarray[2]; - unsigned char outputSer[65]; - size_t outputLen = 65; - - secp256k1_pubkey pubkey; - int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); - - if( ret ) { - ret = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, tweak); - } - - if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; - } - - intsarray[0] = outputLen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - pubArray = (*env)->NewByteArray(env, outputLen); - (*env)->SetByteArrayRegion(env, pubArray, 0, outputLen, (jbyte*)outputSer); - (*env)->SetObjectArrayElement(env, retArray, 0, pubArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1mul - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* tweak = (unsigned char*) (pkey + publen); - - jobjectArray retArray; - jbyteArray pubArray, intsByteArray; - unsigned char intsarray[2]; - unsigned char outputSer[65]; - size_t outputLen = 65; - - secp256k1_pubkey pubkey; - int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); - - if ( ret ) { - ret = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, tweak); - } - - if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; - } - - intsarray[0] = outputLen; - intsarray[1] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - pubArray = (*env)->NewByteArray(env, outputLen); - (*env)->SetByteArrayRegion(env, pubArray, 0, outputLen, (jbyte*)outputSer); - (*env)->SetObjectArrayElement(env, retArray, 0, pubArray); - - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} - -SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1pubkey_1combine - (JNIEnv * env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint numkeys) -{ - (void)classObject;(void)env;(void)byteBufferObject;(void)ctx_l;(void)numkeys; - - return 0; -} - -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) -{ - secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - const unsigned char* secdata = (*env)->GetDirectBufferAddress(env, byteBufferObject); - const unsigned char* pubdata = (const unsigned char*) (secdata + 32); - - jobjectArray retArray; - jbyteArray outArray, intsByteArray; - unsigned char intsarray[1]; - secp256k1_pubkey pubkey; - unsigned char nonce_res[32]; - size_t outputLen = 32; - - int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen); - - if (ret) { - ret = secp256k1_ecdh( - ctx, - nonce_res, - &pubkey, - secdata - ); - } - - intsarray[0] = ret; - - retArray = (*env)->NewObjectArray(env, 2, - (*env)->FindClass(env, "[B"), - (*env)->NewByteArray(env, 1)); - - outArray = (*env)->NewByteArray(env, outputLen); - (*env)->SetByteArrayRegion(env, outArray, 0, 32, (jbyte*)nonce_res); - (*env)->SetObjectArrayElement(env, retArray, 0, outArray); - - intsByteArray = (*env)->NewByteArray(env, 1); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 1, (jbyte*)intsarray); - (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); - - (void)classObject; - - return retArray; -} diff --git a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h b/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h deleted file mode 100644 index e2574b85ff5..00000000000 --- a/src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h +++ /dev/null @@ -1,119 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include "secp256k1.h" -#include -/* Header for class org_bitcoin_NativeSecp256k1 */ - -#ifndef _Included_org_bitcoin_NativeSecp256k1 -#define _Included_org_bitcoin_NativeSecp256k1 -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ctx_clone - * Signature: (J)J - */ -SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone - (JNIEnv *, jclass, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_context_randomize - * Signature: (Ljava/nio/ByteBuffer;J)I - */ -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1context_1randomize - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_privkey_tweak_add - * Signature: (Ljava/nio/ByteBuffer;J)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1add - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_privkey_tweak_mul - * Signature: (Ljava/nio/ByteBuffer;J)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1mul - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_pubkey_tweak_add - * Signature: (Ljava/nio/ByteBuffer;JI)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add - (JNIEnv *, jclass, jobject, jlong, jint); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_pubkey_tweak_mul - * Signature: (Ljava/nio/ByteBuffer;JI)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1mul - (JNIEnv *, jclass, jobject, jlong, jint); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_destroy_context - * Signature: (J)V - */ -SECP256K1_API void JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1destroy_1context - (JNIEnv *, jclass, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ecdsa_verify - * Signature: (Ljava/nio/ByteBuffer;JII)I - */ -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify - (JNIEnv *, jclass, jobject, jlong, jint, jint); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ecdsa_sign - * Signature: (Ljava/nio/ByteBuffer;J)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ec_seckey_verify - * Signature: (Ljava/nio/ByteBuffer;J)I - */ -SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ec_pubkey_create - * Signature: (Ljava/nio/ByteBuffer;J)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create - (JNIEnv *, jclass, jobject, jlong); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ec_pubkey_parse - * Signature: (Ljava/nio/ByteBuffer;JI)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse - (JNIEnv *, jclass, jobject, jlong, jint); - -/* - * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ecdh - * Signature: (Ljava/nio/ByteBuffer;JI)[[B - */ -SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen); - - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.c b/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.c deleted file mode 100644 index 403facfb40e..00000000000 --- a/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "org_bitcoin_Secp256k1Context.h" -#include "secp256k1.h" -#include -#include - -SECP256K1_API jlong JNICALL Java_org_bitcoin_Secp256k1Context_secp256k1_1init_1context - (JNIEnv* env, jclass classObject) -{ - secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - - (void)classObject;(void)env; - - return (uintptr_t)ctx; -} - diff --git a/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.h b/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.h deleted file mode 100644 index 9a0ec222ec9..00000000000 --- a/src/secp256k1/src/java/org_bitcoin_Secp256k1Context.h +++ /dev/null @@ -1,22 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include "secp256k1.h" -#include -/* Header for class org_bitcoin_Secp256k1Context */ - -#ifndef _Included_org_bitcoin_Secp256k1Context -#define _Included_org_bitcoin_Secp256k1Context -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_bitcoin_Secp256k1Context - * Method: secp256k1_init_context - * Signature: ()J - */ -SECP256K1_API jlong JNICALL Java_org_bitcoin_Secp256k1Context_secp256k1_1init_1context - (JNIEnv *, jclass); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/secp256k1/src/num.h b/src/secp256k1/src/num.h deleted file mode 100644 index 7bb9c5be8cf..00000000000 --- a/src/secp256k1/src/num.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#ifndef _SECP256K1_NUM_ -#define _SECP256K1_NUM_ - -#ifndef USE_NUM_NONE - -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - -#if defined(USE_NUM_GMP) -#include "num_gmp.h" -#else -#error "Please select num implementation" -#endif - -/** Copy a number. */ -static void secp256k1_num_copy(secp256k1_num *r, const secp256k1_num *a); - -/** Convert a number's absolute value to a binary big-endian string. - * There must be enough place. */ -static void secp256k1_num_get_bin(unsigned char *r, unsigned int rlen, const secp256k1_num *a); - -/** Set a number to the value of a binary big-endian string. */ -static void secp256k1_num_set_bin(secp256k1_num *r, const unsigned char *a, unsigned int alen); - -/** Compute a modular inverse. The input must be less than the modulus. */ -static void secp256k1_num_mod_inverse(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *m); - -/** Compute the jacobi symbol (a|b). b must be positive and odd. */ -static int secp256k1_num_jacobi(const secp256k1_num *a, const secp256k1_num *b); - -/** Compare the absolute value of two numbers. */ -static int secp256k1_num_cmp(const secp256k1_num *a, const secp256k1_num *b); - -/** Test whether two number are equal (including sign). */ -static int secp256k1_num_eq(const secp256k1_num *a, const secp256k1_num *b); - -/** Add two (signed) numbers. */ -static void secp256k1_num_add(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); - -/** Subtract two (signed) numbers. */ -static void secp256k1_num_sub(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); - -/** Multiply two (signed) numbers. */ -static void secp256k1_num_mul(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); - -/** Replace a number by its remainder modulo m. M's sign is ignored. The result is a number between 0 and m-1, - even if r was negative. */ -static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m); - -/** Right-shift the passed number by bits bits. */ -static void secp256k1_num_shift(secp256k1_num *r, int bits); - -/** Check whether a number is zero. */ -static int secp256k1_num_is_zero(const secp256k1_num *a); - -/** Check whether a number is one. */ -static int secp256k1_num_is_one(const secp256k1_num *a); - -/** Check whether a number is strictly negative. */ -static int secp256k1_num_is_neg(const secp256k1_num *a); - -/** Change a number's sign. */ -static void secp256k1_num_negate(secp256k1_num *r); - -#endif - -#endif diff --git a/src/secp256k1/src/num_gmp.h b/src/secp256k1/src/num_gmp.h deleted file mode 100644 index 7dd813088af..00000000000 --- a/src/secp256k1/src/num_gmp.h +++ /dev/null @@ -1,20 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#ifndef _SECP256K1_NUM_REPR_ -#define _SECP256K1_NUM_REPR_ - -#include - -#define NUM_LIMBS ((256+GMP_NUMB_BITS-1)/GMP_NUMB_BITS) - -typedef struct { - mp_limb_t data[2*NUM_LIMBS]; - int neg; - int limbs; -} secp256k1_num; - -#endif diff --git a/src/secp256k1/src/num_gmp_impl.h b/src/secp256k1/src/num_gmp_impl.h deleted file mode 100644 index 3a46495eeac..00000000000 --- a/src/secp256k1/src/num_gmp_impl.h +++ /dev/null @@ -1,288 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#ifndef _SECP256K1_NUM_REPR_IMPL_H_ -#define _SECP256K1_NUM_REPR_IMPL_H_ - -#include -#include -#include - -#include "util.h" -#include "num.h" - -#ifdef VERIFY -static void secp256k1_num_sanity(const secp256k1_num *a) { - VERIFY_CHECK(a->limbs == 1 || (a->limbs > 1 && a->data[a->limbs-1] != 0)); -} -#else -#define secp256k1_num_sanity(a) do { } while(0) -#endif - -static void secp256k1_num_copy(secp256k1_num *r, const secp256k1_num *a) { - *r = *a; -} - -static void secp256k1_num_get_bin(unsigned char *r, unsigned int rlen, const secp256k1_num *a) { - unsigned char tmp[65]; - int len = 0; - int shift = 0; - if (a->limbs>1 || a->data[0] != 0) { - len = mpn_get_str(tmp, 256, (mp_limb_t*)a->data, a->limbs); - } - while (shift < len && tmp[shift] == 0) shift++; - VERIFY_CHECK(len-shift <= (int)rlen); - memset(r, 0, rlen - len + shift); - if (len > shift) { - memcpy(r + rlen - len + shift, tmp + shift, len - shift); - } - memset(tmp, 0, sizeof(tmp)); -} - -static void secp256k1_num_set_bin(secp256k1_num *r, const unsigned char *a, unsigned int alen) { - int len; - VERIFY_CHECK(alen > 0); - VERIFY_CHECK(alen <= 64); - len = mpn_set_str(r->data, a, alen, 256); - if (len == 0) { - r->data[0] = 0; - len = 1; - } - VERIFY_CHECK(len <= NUM_LIMBS*2); - r->limbs = len; - r->neg = 0; - while (r->limbs > 1 && r->data[r->limbs-1]==0) { - r->limbs--; - } -} - -static void secp256k1_num_add_abs(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { - mp_limb_t c = mpn_add(r->data, a->data, a->limbs, b->data, b->limbs); - r->limbs = a->limbs; - if (c != 0) { - VERIFY_CHECK(r->limbs < 2*NUM_LIMBS); - r->data[r->limbs++] = c; - } -} - -static void secp256k1_num_sub_abs(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { - mp_limb_t c = mpn_sub(r->data, a->data, a->limbs, b->data, b->limbs); - (void)c; - VERIFY_CHECK(c == 0); - r->limbs = a->limbs; - while (r->limbs > 1 && r->data[r->limbs-1]==0) { - r->limbs--; - } -} - -static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m) { - secp256k1_num_sanity(r); - secp256k1_num_sanity(m); - - if (r->limbs >= m->limbs) { - mp_limb_t t[2*NUM_LIMBS]; - mpn_tdiv_qr(t, r->data, 0, r->data, r->limbs, m->data, m->limbs); - memset(t, 0, sizeof(t)); - r->limbs = m->limbs; - while (r->limbs > 1 && r->data[r->limbs-1]==0) { - r->limbs--; - } - } - - if (r->neg && (r->limbs > 1 || r->data[0] != 0)) { - secp256k1_num_sub_abs(r, m, r); - r->neg = 0; - } -} - -static void secp256k1_num_mod_inverse(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *m) { - int i; - mp_limb_t g[NUM_LIMBS+1]; - mp_limb_t u[NUM_LIMBS+1]; - mp_limb_t v[NUM_LIMBS+1]; - mp_size_t sn; - mp_size_t gn; - secp256k1_num_sanity(a); - secp256k1_num_sanity(m); - - /** mpn_gcdext computes: (G,S) = gcdext(U,V), where - * * G = gcd(U,V) - * * G = U*S + V*T - * * U has equal or more limbs than V, and V has no padding - * If we set U to be (a padded version of) a, and V = m: - * G = a*S + m*T - * G = a*S mod m - * Assuming G=1: - * S = 1/a mod m - */ - VERIFY_CHECK(m->limbs <= NUM_LIMBS); - VERIFY_CHECK(m->data[m->limbs-1] != 0); - for (i = 0; i < m->limbs; i++) { - u[i] = (i < a->limbs) ? a->data[i] : 0; - v[i] = m->data[i]; - } - sn = NUM_LIMBS+1; - gn = mpn_gcdext(g, r->data, &sn, u, m->limbs, v, m->limbs); - (void)gn; - VERIFY_CHECK(gn == 1); - VERIFY_CHECK(g[0] == 1); - r->neg = a->neg ^ m->neg; - if (sn < 0) { - mpn_sub(r->data, m->data, m->limbs, r->data, -sn); - r->limbs = m->limbs; - while (r->limbs > 1 && r->data[r->limbs-1]==0) { - r->limbs--; - } - } else { - r->limbs = sn; - } - memset(g, 0, sizeof(g)); - memset(u, 0, sizeof(u)); - memset(v, 0, sizeof(v)); -} - -static int secp256k1_num_jacobi(const secp256k1_num *a, const secp256k1_num *b) { - int ret; - mpz_t ga, gb; - secp256k1_num_sanity(a); - secp256k1_num_sanity(b); - VERIFY_CHECK(!b->neg && (b->limbs > 0) && (b->data[0] & 1)); - - mpz_inits(ga, gb, NULL); - - mpz_import(gb, b->limbs, -1, sizeof(mp_limb_t), 0, 0, b->data); - mpz_import(ga, a->limbs, -1, sizeof(mp_limb_t), 0, 0, a->data); - if (a->neg) { - mpz_neg(ga, ga); - } - - ret = mpz_jacobi(ga, gb); - - mpz_clears(ga, gb, NULL); - - return ret; -} - -static int secp256k1_num_is_one(const secp256k1_num *a) { - return (a->limbs == 1 && a->data[0] == 1); -} - -static int secp256k1_num_is_zero(const secp256k1_num *a) { - return (a->limbs == 1 && a->data[0] == 0); -} - -static int secp256k1_num_is_neg(const secp256k1_num *a) { - return (a->limbs > 1 || a->data[0] != 0) && a->neg; -} - -static int secp256k1_num_cmp(const secp256k1_num *a, const secp256k1_num *b) { - if (a->limbs > b->limbs) { - return 1; - } - if (a->limbs < b->limbs) { - return -1; - } - return mpn_cmp(a->data, b->data, a->limbs); -} - -static int secp256k1_num_eq(const secp256k1_num *a, const secp256k1_num *b) { - if (a->limbs > b->limbs) { - return 0; - } - if (a->limbs < b->limbs) { - return 0; - } - if ((a->neg && !secp256k1_num_is_zero(a)) != (b->neg && !secp256k1_num_is_zero(b))) { - return 0; - } - return mpn_cmp(a->data, b->data, a->limbs) == 0; -} - -static void secp256k1_num_subadd(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b, int bneg) { - if (!(b->neg ^ bneg ^ a->neg)) { /* a and b have the same sign */ - r->neg = a->neg; - if (a->limbs >= b->limbs) { - secp256k1_num_add_abs(r, a, b); - } else { - secp256k1_num_add_abs(r, b, a); - } - } else { - if (secp256k1_num_cmp(a, b) > 0) { - r->neg = a->neg; - secp256k1_num_sub_abs(r, a, b); - } else { - r->neg = b->neg ^ bneg; - secp256k1_num_sub_abs(r, b, a); - } - } -} - -static void secp256k1_num_add(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { - secp256k1_num_sanity(a); - secp256k1_num_sanity(b); - secp256k1_num_subadd(r, a, b, 0); -} - -static void secp256k1_num_sub(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { - secp256k1_num_sanity(a); - secp256k1_num_sanity(b); - secp256k1_num_subadd(r, a, b, 1); -} - -static void secp256k1_num_mul(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { - mp_limb_t tmp[2*NUM_LIMBS+1]; - secp256k1_num_sanity(a); - secp256k1_num_sanity(b); - - VERIFY_CHECK(a->limbs + b->limbs <= 2*NUM_LIMBS+1); - if ((a->limbs==1 && a->data[0]==0) || (b->limbs==1 && b->data[0]==0)) { - r->limbs = 1; - r->neg = 0; - r->data[0] = 0; - return; - } - if (a->limbs >= b->limbs) { - mpn_mul(tmp, a->data, a->limbs, b->data, b->limbs); - } else { - mpn_mul(tmp, b->data, b->limbs, a->data, a->limbs); - } - r->limbs = a->limbs + b->limbs; - if (r->limbs > 1 && tmp[r->limbs - 1]==0) { - r->limbs--; - } - VERIFY_CHECK(r->limbs <= 2*NUM_LIMBS); - mpn_copyi(r->data, tmp, r->limbs); - r->neg = a->neg ^ b->neg; - memset(tmp, 0, sizeof(tmp)); -} - -static void secp256k1_num_shift(secp256k1_num *r, int bits) { - if (bits % GMP_NUMB_BITS) { - /* Shift within limbs. */ - mpn_rshift(r->data, r->data, r->limbs, bits % GMP_NUMB_BITS); - } - if (bits >= GMP_NUMB_BITS) { - int i; - /* Shift full limbs. */ - for (i = 0; i < r->limbs; i++) { - int index = i + (bits / GMP_NUMB_BITS); - if (index < r->limbs && index < 2*NUM_LIMBS) { - r->data[i] = r->data[index]; - } else { - r->data[i] = 0; - } - } - } - while (r->limbs>1 && r->data[r->limbs-1]==0) { - r->limbs--; - } -} - -static void secp256k1_num_negate(secp256k1_num *r) { - r->neg ^= 1; -} - -#endif diff --git a/src/secp256k1/src/num_impl.h b/src/secp256k1/src/num_impl.h deleted file mode 100644 index 0b0e3a072a1..00000000000 --- a/src/secp256k1/src/num_impl.h +++ /dev/null @@ -1,24 +0,0 @@ -/********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or http://www.opensource.org/licenses/mit-license.php.* - **********************************************************************/ - -#ifndef _SECP256K1_NUM_IMPL_H_ -#define _SECP256K1_NUM_IMPL_H_ - -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - -#include "num.h" - -#if defined(USE_NUM_GMP) -#include "num_gmp_impl.h" -#elif defined(USE_NUM_NONE) -/* Nothing. */ -#else -#error "Please select num implementation" -#endif - -#endif diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 4284190a18a..64e6a038653 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -481,30 +481,37 @@ class AmendmentTable_test final : public beast::unit_test::suite } } + // Make a list of trusted validators. + // Register the validators with AmendmentTable and return the list. std::vector> - makeValidators(int num) + makeValidators(int num, std::unique_ptr const& table) { std::vector> ret; ret.reserve(num); + hash_set trustedValidators; + trustedValidators.reserve(num); for (int i = 0; i < num; ++i) { - ret.emplace_back(randomKeyPair(KeyType::secp256k1)); + auto const& back = + ret.emplace_back(randomKeyPair(KeyType::secp256k1)); + trustedValidators.insert(back.first); } + table->trustChanged(trustedValidators); return ret; } static NetClock::time_point - weekTime(weeks w) + hourTime(std::chrono::hours h) { - return NetClock::time_point{w}; + return NetClock::time_point{h}; } // Execute a pretend consensus round for a flag ledger void doRound( - uint256 const& feat, + Rules const& rules, AmendmentTable& table, - weeks week, + std::chrono::hours hour, std::vector> const& validators, std::vector> const& votes, std::vector& ourVotes, @@ -522,7 +529,7 @@ class AmendmentTable_test final : public beast::unit_test::suite // enabled: In/out enabled amendments // majority: In/our majority amendments (and when they got a majority) - auto const roundTime = weekTime(week); + auto const roundTime = hourTime(hour); // Build validations std::vector> validations; @@ -536,7 +543,8 @@ class AmendmentTable_test final : public beast::unit_test::suite for (auto const& [hash, nVotes] : votes) { - if (feat == fixAmendmentMajorityCalc ? nVotes >= i : nVotes > i) + if (rules.enabled(fixAmendmentMajorityCalc) ? nVotes >= i + : nVotes > i) { // We vote yes on this amendment field.push_back(hash); @@ -560,8 +568,8 @@ class AmendmentTable_test final : public beast::unit_test::suite ourVotes = table.doValidation(enabled); - auto actions = table.doVoting( - Rules({feat}), roundTime, enabled, majority, validations); + auto actions = + table.doVoting(rules, roundTime, enabled, majority, validations); for (auto const& [hash, action] : actions) { // This code assumes other validators do as we do @@ -600,24 +608,25 @@ class AmendmentTable_test final : public beast::unit_test::suite // No vote on unknown amendment void - testNoOnUnknown(uint256 const& feat) + testNoOnUnknown(FeatureBitset const& feat) { testcase("Vote NO on unknown"); auto const testAmendment = amendmentId("TestAmendment"); - auto const validators = makeValidators(10); - test::jtx::Env env{*this}; + test::jtx::Env env{*this, feat}; auto table = makeTable(env, weeks(2), emptyYes_, emptySection_, emptySection_); + auto const validators = makeValidators(10, table); + std::vector> votes; std::vector ourVotes; std::set enabled; majorityAmendments_t majority; doRound( - feat, + env.current()->rules(), *table, weeks{1}, validators, @@ -632,7 +641,7 @@ class AmendmentTable_test final : public beast::unit_test::suite votes.emplace_back(testAmendment, validators.size()); doRound( - feat, + env.current()->rules(), *table, weeks{2}, validators, @@ -643,12 +652,12 @@ class AmendmentTable_test final : public beast::unit_test::suite BEAST_EXPECT(ourVotes.empty()); BEAST_EXPECT(enabled.empty()); - majority[testAmendment] = weekTime(weeks{1}); + majority[testAmendment] = hourTime(weeks{1}); // Note that the simulation code assumes others behave as we do, // so the amendment won't get enabled doRound( - feat, + env.current()->rules(), *table, weeks{5}, validators, @@ -662,13 +671,13 @@ class AmendmentTable_test final : public beast::unit_test::suite // No vote on vetoed amendment void - testNoOnVetoed(uint256 const& feat) + testNoOnVetoed(FeatureBitset const& feat) { testcase("Vote NO on vetoed"); auto const testAmendment = amendmentId("vetoedAmendment"); - test::jtx::Env env{*this}; + test::jtx::Env env{*this, feat}; auto table = makeTable( env, weeks(2), @@ -676,7 +685,7 @@ class AmendmentTable_test final : public beast::unit_test::suite emptySection_, makeSection(testAmendment)); - auto const validators = makeValidators(10); + auto const validators = makeValidators(10, table); std::vector> votes; std::vector ourVotes; @@ -684,7 +693,7 @@ class AmendmentTable_test final : public beast::unit_test::suite majorityAmendments_t majority; doRound( - feat, + env.current()->rules(), *table, weeks{1}, validators, @@ -699,7 +708,7 @@ class AmendmentTable_test final : public beast::unit_test::suite votes.emplace_back(testAmendment, validators.size()); doRound( - feat, + env.current()->rules(), *table, weeks{2}, validators, @@ -710,10 +719,10 @@ class AmendmentTable_test final : public beast::unit_test::suite BEAST_EXPECT(ourVotes.empty()); BEAST_EXPECT(enabled.empty()); - majority[testAmendment] = weekTime(weeks{1}); + majority[testAmendment] = hourTime(weeks{1}); doRound( - feat, + env.current()->rules(), *table, weeks{5}, validators, @@ -727,15 +736,16 @@ class AmendmentTable_test final : public beast::unit_test::suite // Vote on and enable known, not-enabled amendment void - testVoteEnable(uint256 const& feat) + testVoteEnable(FeatureBitset const& feat) { testcase("voteEnable"); - test::jtx::Env env{*this}; + test::jtx::Env env{*this, feat}; auto table = makeTable( env, weeks(2), makeDefaultYes(yes_), emptySection_, emptySection_); - auto const validators = makeValidators(10); + auto const validators = makeValidators(10, table); + std::vector> votes; std::vector ourVotes; std::set enabled; @@ -743,7 +753,7 @@ class AmendmentTable_test final : public beast::unit_test::suite // Week 1: We should vote for all known amendments not enabled doRound( - feat, + env.current()->rules(), *table, weeks{1}, validators, @@ -762,7 +772,7 @@ class AmendmentTable_test final : public beast::unit_test::suite // Week 2: We should recognize a majority doRound( - feat, + env.current()->rules(), *table, weeks{2}, validators, @@ -774,11 +784,11 @@ class AmendmentTable_test final : public beast::unit_test::suite BEAST_EXPECT(enabled.empty()); for (auto const& i : yes_) - BEAST_EXPECT(majority[amendmentId(i)] == weekTime(weeks{2})); + BEAST_EXPECT(majority[amendmentId(i)] == hourTime(weeks{2})); // Week 5: We should enable the amendment doRound( - feat, + env.current()->rules(), *table, weeks{5}, validators, @@ -790,7 +800,7 @@ class AmendmentTable_test final : public beast::unit_test::suite // Week 6: We should remove it from our votes and from having a majority doRound( - feat, + env.current()->rules(), *table, weeks{6}, validators, @@ -806,12 +816,12 @@ class AmendmentTable_test final : public beast::unit_test::suite // Detect majority at 80%, enable later void - testDetectMajority(uint256 const& feat) + testDetectMajority(FeatureBitset const& feat) { testcase("detectMajority"); auto const testAmendment = amendmentId("detectMajority"); - test::jtx::Env env{*this}; + test::jtx::Env env{*this, feat}; auto table = makeTable( env, weeks(2), @@ -819,7 +829,7 @@ class AmendmentTable_test final : public beast::unit_test::suite emptySection_, emptySection_); - auto const validators = makeValidators(16); + auto const validators = makeValidators(16, table); std::set enabled; majorityAmendments_t majority; @@ -833,7 +843,7 @@ class AmendmentTable_test final : public beast::unit_test::suite votes.emplace_back(testAmendment, i); doRound( - feat, + env.current()->rules(), *table, weeks{i}, validators, @@ -875,14 +885,13 @@ class AmendmentTable_test final : public beast::unit_test::suite // Detect loss of majority void - testLostMajority(uint256 const& feat) + testLostMajority(FeatureBitset const& feat) { testcase("lostMajority"); auto const testAmendment = amendmentId("lostMajority"); - auto const validators = makeValidators(16); - test::jtx::Env env{*this}; + test::jtx::Env env{*this, feat}; auto table = makeTable( env, weeks(8), @@ -890,6 +899,8 @@ class AmendmentTable_test final : public beast::unit_test::suite emptySection_, emptySection_); + auto const validators = makeValidators(16, table); + std::set enabled; majorityAmendments_t majority; @@ -901,7 +912,7 @@ class AmendmentTable_test final : public beast::unit_test::suite votes.emplace_back(testAmendment, validators.size()); doRound( - feat, + env.current()->rules(), *table, weeks{1}, validators, @@ -923,7 +934,7 @@ class AmendmentTable_test final : public beast::unit_test::suite votes.emplace_back(testAmendment, validators.size() - i); doRound( - feat, + env.current()->rules(), *table, weeks{i + 1}, validators, @@ -949,6 +960,258 @@ class AmendmentTable_test final : public beast::unit_test::suite } } + // Exercise the UNL changing while voting is in progress. + void + testChangedUNL(FeatureBitset const& feat) + { + // This test doesn't work without fixAmendmentMajorityCalc enabled. + if (!feat[fixAmendmentMajorityCalc]) + return; + + testcase("changedUNL"); + + auto const testAmendment = amendmentId("changedUNL"); + test::jtx::Env env{*this, feat}; + auto table = makeTable( + env, + weeks(8), + makeDefaultYes(testAmendment), + emptySection_, + emptySection_); + + std::vector> validators = + makeValidators(10, table); + + std::set enabled; + majorityAmendments_t majority; + + { + // 10 validators with 2 voting against won't get majority. + std::vector> votes; + std::vector ourVotes; + + votes.emplace_back(testAmendment, validators.size() - 2); + + doRound( + env.current()->rules(), + *table, + weeks{1}, + validators, + votes, + ourVotes, + enabled, + majority); + + BEAST_EXPECT(enabled.empty()); + BEAST_EXPECT(majority.empty()); + } + + // Add one new validator to the UNL. + validators.emplace_back(randomKeyPair(KeyType::secp256k1)); + + // A lambda that updates the AmendmentTable with the latest + // trusted validators. + auto callTrustChanged = + [](std::vector> const& validators, + std::unique_ptr const& table) { + // We need a hash_set to pass to trustChanged. + hash_set trustedValidators; + trustedValidators.reserve(validators.size()); + std::for_each( + validators.begin(), + validators.end(), + [&trustedValidators](auto const& val) { + trustedValidators.insert(val.first); + }); + + // Tell the AmendmentTable that the UNL changed. + table->trustChanged(trustedValidators); + }; + + // Tell the table that there's been a change in trusted validators. + callTrustChanged(validators, table); + + { + // 11 validators with 2 voting against gains majority. + std::vector> votes; + std::vector ourVotes; + + votes.emplace_back(testAmendment, validators.size() - 2); + + doRound( + env.current()->rules(), + *table, + weeks{2}, + validators, + votes, + ourVotes, + enabled, + majority); + + BEAST_EXPECT(enabled.empty()); + BEAST_EXPECT(!majority.empty()); + } + { + // One of the validators goes flaky and doesn't send validations + // (without the UNL changing) so the amendment loses majority. + std::pair const savedValidator = + validators.front(); + validators.erase(validators.begin()); + + std::vector> votes; + std::vector ourVotes; + + votes.emplace_back(testAmendment, validators.size() - 2); + + doRound( + env.current()->rules(), + *table, + weeks{3}, + validators, + votes, + ourVotes, + enabled, + majority); + + BEAST_EXPECT(enabled.empty()); + BEAST_EXPECT(majority.empty()); + + // Simulate the validator re-syncing to the network by adding it + // back to the validators vector + validators.insert(validators.begin(), savedValidator); + + votes.front().second = validators.size() - 2; + + doRound( + env.current()->rules(), + *table, + weeks{4}, + validators, + votes, + ourVotes, + enabled, + majority); + + BEAST_EXPECT(enabled.empty()); + BEAST_EXPECT(!majority.empty()); + + // Finally, remove one validator from the UNL and see that majority + // is lost. + validators.erase(validators.begin()); + + // Tell the table that there's been a change in trusted validators. + callTrustChanged(validators, table); + + votes.front().second = validators.size() - 2; + + doRound( + env.current()->rules(), + *table, + weeks{5}, + validators, + votes, + ourVotes, + enabled, + majority); + + BEAST_EXPECT(enabled.empty()); + BEAST_EXPECT(majority.empty()); + } + } + + // Exercise a validator losing connectivity and then regaining it after + // extended delays. Depending on how long that delay is an amendment + // either will or will not go live. + void + testValidatorFlapping(FeatureBitset const& feat) + { + // This test doesn't work without fixAmendmentMajorityCalc enabled. + if (!feat[fixAmendmentMajorityCalc]) + return; + + testcase("validatorFlapping"); + + // We run a test where a validator flaps on and off every 23 hours + // and another one one where it flaps on and off every 25 hours. + // + // Since the local validator vote record expires after 24 hours, + // with 23 hour flapping the amendment will go live. But with 25 + // hour flapping the amendment will not go live. + for (int flapRateHours : {23, 25}) + { + test::jtx::Env env{*this, feat}; + auto const testAmendment = amendmentId("validatorFlapping"); + auto table = makeTable( + env, + weeks(1), + makeDefaultYes(testAmendment), + emptySection_, + emptySection_); + + // Make two lists of validators, one with a missing validator, to + // make it easy to simulate validator flapping. + auto const allValidators = makeValidators(11, table); + decltype(allValidators) const mostValidators( + allValidators.begin() + 1, allValidators.end()); + BEAST_EXPECT(allValidators.size() == mostValidators.size() + 1); + + std::set enabled; + majorityAmendments_t majority; + + std::vector> votes; + std::vector ourVotes; + + votes.emplace_back(testAmendment, allValidators.size() - 2); + + int delay = flapRateHours; + // Loop for 1 week plus a day. + for (int hour = 1; hour < (24 * 8); ++hour) + { + decltype(allValidators) const& thisHoursValidators = + (delay < flapRateHours) ? mostValidators : allValidators; + delay = delay == flapRateHours ? 0 : delay + 1; + + votes.front().second = thisHoursValidators.size() - 2; + + using namespace std::chrono; + doRound( + env.current()->rules(), + *table, + hours(hour), + thisHoursValidators, + votes, + ourVotes, + enabled, + majority); + + if (hour <= (24 * 7) || flapRateHours > 24) + { + // The amendment should not be enabled under any + // circumstance until one week has elapsed. + BEAST_EXPECT(enabled.empty()); + + // If flapping is less than 24 hours, there should be + // no flapping. Otherwise we should only have majority + // if allValidators vote -- which means there are no + // missing validators. + bool const expectMajority = (delay <= 24) + ? true + : &thisHoursValidators == &allValidators; + BEAST_EXPECT(majority.empty() != expectMajority); + } + else + { + // We're... + // o Past one week, and + // o AmendmentFlapping was less than 24 hours. + // The amendment should be enabled. + BEAST_EXPECT(!enabled.empty()); + BEAST_EXPECT(majority.empty()); + } + } + } + } + void testHasUnsupported() { @@ -993,25 +1256,30 @@ class AmendmentTable_test final : public beast::unit_test::suite } void - testFeature(uint256 const& feat) + testFeature(FeatureBitset const& feat) { testNoOnUnknown(feat); testNoOnVetoed(feat); testVoteEnable(feat); testDetectMajority(feat); testLostMajority(feat); + testChangedUNL(feat); + testValidatorFlapping(feat); } void run() override { + FeatureBitset const all{test::jtx::supported_amendments()}; + FeatureBitset const fixMajorityCalc{fixAmendmentMajorityCalc}; + testConstruct(); testGet(); testBadConfig(); testEnableVeto(); testHasUnsupported(); - testFeature({}); - testFeature(fixAmendmentMajorityCalc); + testFeature(all - fixMajorityCalc); + testFeature(all); } }; diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index 75c32385acf..2bd7074b2a8 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -26,7 +26,7 @@ namespace ripple { -class NFTokenBurn_test : public beast::unit_test::suite +class NFTokenBurn0_test : public beast::unit_test::suite { // Helper function that returns the owner count of an account root. static std::uint32_t @@ -786,22 +786,68 @@ class NFTokenBurn_test : public beast::unit_test::suite testBurnTooManyOffers(features); } +protected: + void + run(std::uint32_t instance, bool last = false) + { + using namespace test::jtx; + static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; + + static std::array const feats{ + all - fixNonFungibleTokensV1_2 - fixNFTDir - fixNFTokenRemint, + all - fixNonFungibleTokensV1_2 - fixNFTokenRemint, + all - fixNFTokenRemint, + all}; + + if (BEAST_EXPECT(instance < feats.size())) + { + testWithFeats(feats[instance]); + } + BEAST_EXPECT(!last || instance == feats.size() - 1); + } + public: void run() override { - using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; - FeatureBitset const fixNFTDir{fixNFTokenDirV1}; - - testWithFeats( - all - fixNonFungibleTokensV1_2 - fixNFTDir - fixNFTokenRemint); - testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTokenRemint); - testWithFeats(all - fixNFTokenRemint); - testWithFeats(all); + run(0); + } +}; + +class NFTokenBurn1_test : public NFTokenBurn0_test +{ +public: + void + run() override + { + NFTokenBurn0_test::run(1); + } +}; + +class NFTokenBurn2_test : public NFTokenBurn0_test +{ +public: + void + run() override + { + NFTokenBurn0_test::run(2); + } +}; + +class NFTokenBurn3_test : public NFTokenBurn0_test +{ +public: + void + run() override + { + NFTokenBurn0_test::run(3, true); } }; -BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn, tx, ripple, 3); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn0, tx, ripple, 3); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn1, tx, ripple, 3); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn2, tx, ripple, 3); +BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn3, tx, ripple, 3); } // namespace ripple diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 1f3636e4e39..0539d2abd93 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -27,7 +27,7 @@ namespace ripple { -class NFToken_test : public beast::unit_test::suite +class NFToken0_test : public beast::unit_test::suite { FeatureBitset const disallowIncoming{featureDisallowIncoming}; @@ -6835,23 +6835,74 @@ class NFToken_test : public beast::unit_test::suite public: void - run() override + run(std::uint32_t instance, bool last = false) { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; - FeatureBitset const fixNFTDir{fixNFTokenDirV1}; + static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; - testWithFeats( - all - fixNFTDir - fixNonFungibleTokensV1_2 - fixNFTokenRemint); - testWithFeats( + static std::array const feats{ + all - fixNFTDir - fixNonFungibleTokensV1_2 - fixNFTokenRemint, all - disallowIncoming - fixNonFungibleTokensV1_2 - - fixNFTokenRemint); - testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTokenRemint); - testWithFeats(all - fixNFTokenRemint); - testWithFeats(all); + fixNFTokenRemint, + all - fixNonFungibleTokensV1_2 - fixNFTokenRemint, + all - fixNFTokenRemint, + all}; + + if (BEAST_EXPECT(instance < feats.size())) + { + testWithFeats(feats[instance]); + } + BEAST_EXPECT(!last || instance == feats.size() - 1); + } + + void + run() override + { + run(0); + } +}; + +class NFToken1_test : public NFToken0_test +{ + void + run() override + { + NFToken0_test::run(1); + } +}; + +class NFToken2_test : public NFToken0_test +{ + void + run() override + { + NFToken0_test::run(2); + } +}; + +class NFToken3_test : public NFToken0_test +{ + void + run() override + { + NFToken0_test::run(3); + } +}; + +class NFToken4_test : public NFToken0_test +{ + void + run() override + { + NFToken0_test::run(4, true); } }; -BEAST_DEFINE_TESTSUITE_PRIO(NFToken, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFToken0, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFToken1, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFToken2, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFToken3, tx, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(NFToken4, tx, ripple, 2); } // namespace ripple diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 1c8e544af30..fbf9cc890dc 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -27,7 +27,7 @@ namespace ripple { namespace test { -class Offer_test : public beast::unit_test::suite +class Offer0_test : public beast::unit_test::suite { XRPAmount reserve(jtx::Env& env, std::uint32_t count) @@ -5145,25 +5145,76 @@ class Offer_test : public beast::unit_test::suite } void - run() override + run(std::uint32_t instance, bool last = false) { using namespace jtx; - FeatureBitset const all{supported_amendments()}; - FeatureBitset const flowCross{featureFlowCross}; - FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; - FeatureBitset const rmSmallIncreasedQOffers{fixRmSmallIncreasedQOffers}; - FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled}; + static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const flowCross{featureFlowCross}; + static FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; + static FeatureBitset const rmSmallIncreasedQOffers{ + fixRmSmallIncreasedQOffers}; + static FeatureBitset const immediateOfferKilled{ + featureImmediateOfferKilled}; + + static std::array const feats{ + all - takerDryOffer - immediateOfferKilled, + all - flowCross - takerDryOffer - immediateOfferKilled, + all - flowCross - immediateOfferKilled, + all - rmSmallIncreasedQOffers - immediateOfferKilled, + all}; + + if (BEAST_EXPECT(instance < feats.size())) + { + testAll(feats[instance]); + } + BEAST_EXPECT(!last || instance == feats.size() - 1); + } - testAll(all - takerDryOffer - immediateOfferKilled); - testAll(all - flowCross - takerDryOffer - immediateOfferKilled); - testAll(all - flowCross - immediateOfferKilled); - testAll(all - rmSmallIncreasedQOffers - immediateOfferKilled); - testAll(all); + void + run() override + { + run(0); testFalseAssert(); } }; -class Offer_manual_test : public Offer_test +class Offer1_test : public Offer0_test +{ + void + run() override + { + Offer0_test::run(1); + } +}; + +class Offer2_test : public Offer0_test +{ + void + run() override + { + Offer0_test::run(2); + } +}; + +class Offer3_test : public Offer0_test +{ + void + run() override + { + Offer0_test::run(3); + } +}; + +class Offer4_test : public Offer0_test +{ + void + run() override + { + Offer0_test::run(4, true); + } +}; + +class Offer_manual_test : public Offer0_test { void run() override @@ -5184,7 +5235,11 @@ class Offer_manual_test : public Offer_test } }; -BEAST_DEFINE_TESTSUITE_PRIO(Offer, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(Offer0, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(Offer1, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(Offer2, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(Offer3, tx, ripple, 4); +BEAST_DEFINE_TESTSUITE_PRIO(Offer4, tx, ripple, 4); BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, tx, ripple, 20); } // namespace test diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index f8162dd107d..02a13de5c21 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -24,9 +24,9 @@ #include #include #include -#include - +#include #include +#include namespace ripple { namespace test { @@ -1063,6 +1063,47 @@ struct PayChan_test : public beast::unit_test::suite bob.human()); } + void + testAccountChannelAuthorize(FeatureBitset features) + { + using namespace jtx; + using namespace std::literals::chrono_literals; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const charlie = Account("charlie", KeyType::ed25519); + env.fund(XRP(10000), alice, bob, charlie); + auto const pk = alice.pk(); + auto const settleDelay = 3600s; + auto const channelFunds = XRP(1000); + auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); + env(create(alice, bob, channelFunds, settleDelay, pk)); + env.close(); + + Json::Value args{Json::objectValue}; + args[jss::channel_id] = chan1Str; + args[jss::key_type] = "ed255191"; + args[jss::seed] = "snHq1rzQoN2qiUkC3XF5RyxBzUtN"; + args[jss::amount] = 51110000; + + // test for all api versions + for (auto apiVersion = RPC::apiMinimumSupportedVersion; + apiVersion <= RPC::apiBetaVersion; + ++apiVersion) + { + testcase( + "PayChan Channel_Auth RPC Api " + std::to_string(apiVersion)); + args[jss::api_version] = apiVersion; + auto const rs = env.rpc( + "json", + "channel_authorize", + args.toStyledString())[jss::result]; + auto const error = apiVersion < 2u ? "invalidParams" : "badKeyType"; + BEAST_EXPECT(rs[jss::error] == error); + } + } + void testAuthVerifyRPC(FeatureBitset features) { @@ -2042,6 +2083,7 @@ struct PayChan_test : public beast::unit_test::suite testAccountChannelsRPC(features); testAccountChannelsRPCMarkers(features); testAccountChannelsRPCSenderOnly(features); + testAccountChannelAuthorize(features); testAuthVerifyRPC(features); testOptionalFields(features); testMalformedPK(features); diff --git a/src/test/app/SetTrust_test.cpp b/src/test/app/SetTrust_test.cpp index fce9c4295c2..599ac1917f9 100644 --- a/src/test/app/SetTrust_test.cpp +++ b/src/test/app/SetTrust_test.cpp @@ -275,6 +275,48 @@ class SetTrust_test : public beast::unit_test::suite BEAST_EXPECT(!(flags & lsfDisallowIncomingTrustline)); } + // fixDisallowIncomingV1 + { + for (bool const withFix : {true, false}) + { + auto const amend = withFix + ? features | disallowIncoming + : (features | disallowIncoming) - fixDisallowIncomingV1; + + Env env{*this, amend}; + auto const dist = Account("dist"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + auto const distUSD = dist["USD"]; + + env.fund(XRP(1000), gw, dist); + env.close(); + + env(fset(gw, asfRequireAuth)); + env.close(); + + env(fset(dist, asfDisallowIncomingTrustline)); + env.close(); + + env(trust(dist, USD(10000))); + env.close(); + + // withFix: can set trustline + // withOutFix: cannot set trustline + auto const trustResult = + withFix ? ter(tesSUCCESS) : ter(tecNO_PERMISSION); + env(trust(gw, distUSD(10000)), + txflags(tfSetfAuth), + trustResult); + env.close(); + + auto const txResult = + withFix ? ter(tesSUCCESS) : ter(tecPATH_DRY); + env(pay(gw, dist, USD(1000)), txResult); + env.close(); + } + } + Env env{*this, features | disallowIncoming}; auto const gw = Account{"gateway"}; diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 4bc0040f867..ac6bf56f06c 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -4843,13 +4843,13 @@ class TxQ1_test : public beast::unit_test::suite drops[jss::base_fee] == "0"); BEAST_EXPECT( drops.isMember(jss::median_fee) && - drops[jss::base_fee] == "0"); + drops[jss::median_fee] == "0"); BEAST_EXPECT( drops.isMember(jss::minimum_fee) && - drops[jss::base_fee] == "0"); + drops[jss::minimum_fee] == "0"); BEAST_EXPECT( drops.isMember(jss::open_ledger_fee) && - drops[jss::base_fee] == "0"); + drops[jss::open_ledger_fee] == "0"); } } diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp new file mode 100644 index 00000000000..9b8aaf397dc --- /dev/null +++ b/src/test/app/XChain_test.cpp @@ -0,0 +1,5133 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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::test { + +// SEnv class - encapsulate jtx::Env to make it more user-friendly, +// for example having APIs that return a *this reference so that calls can be +// chained (fluent interface) allowing to create an environment and use it +// without encapsulating it in a curly brace block. +// --------------------------------------------------------------------------- +template +struct SEnv +{ + jtx::Env env_; + + SEnv( + T& s, + std::unique_ptr config, + FeatureBitset features, + std::unique_ptr logs = nullptr, + beast::severities::Severity thresh = beast::severities::kError) + : env_(s, std::move(config), features, std::move(logs), thresh) + { + } + + SEnv& + close() + { + env_.close(); + return *this; + } + + SEnv& + enableFeature(uint256 const feature) + { + env_.enableFeature(feature); + return *this; + } + + SEnv& + disableFeature(uint256 const feature) + { + env_.app().config().features.erase(feature); + return *this; + } + + template + SEnv& + fund(STAmount const& amount, Arg const& arg, Args const&... args) + { + env_.fund(amount, arg, args...); + return *this; + } + + template + SEnv& + tx(JsonValue&& jv, FN const&... fN) + { + env_(std::forward(jv), fN...); + return *this; + } + + template + SEnv& + multiTx(jtx::JValueVec&& jvv, FN const&... fN) + { + for (auto const& jv : jvv) + env_(jv, fN...); + return *this; + } + + TER + ter() const + { + return env_.ter(); + } + + STAmount + balance(jtx::Account const& account) const + { + return env_.balance(account).value(); + } + + STAmount + balance(jtx::Account const& account, Issue const& issue) const + { + return env_.balance(account, issue).value(); + } + + XRPAmount + reserve(std::uint32_t count) + { + return env_.current()->fees().accountReserve(count); + } + + XRPAmount + txFee() + { + return env_.current()->fees().base; + } + + std::shared_ptr + account(jtx::Account const& account) + { + return env_.le(account); + } + + std::shared_ptr + bridge(Json::Value const& jvb) + { + STXChainBridge b(jvb); + + auto tryGet = + [&](STXChainBridge::ChainType ct) -> std::shared_ptr { + if (auto r = env_.le(keylet::bridge(b, ct))) + { + if ((*r)[sfXChainBridge] == b) + return r; + } + return nullptr; + }; + if (auto r = tryGet(STXChainBridge::ChainType::locking)) + return r; + return tryGet(STXChainBridge::ChainType::issuing); + } + + std::uint64_t + claimCount(Json::Value const& jvb) + { + return (*bridge(jvb))[sfXChainAccountClaimCount]; + } + + std::uint64_t + claimID(Json::Value const& jvb) + { + return (*bridge(jvb))[sfXChainClaimID]; + } + + std::shared_ptr + claimID(Json::Value const& jvb, std::uint64_t seq) + { + return env_.le(keylet::xChainClaimID(STXChainBridge(jvb), seq)); + } + + std::shared_ptr + caClaimID(Json::Value const& jvb, std::uint64_t seq) + { + return env_.le( + keylet::xChainCreateAccountClaimID(STXChainBridge(jvb), seq)); + } +}; + +// XEnv class used for XChain tests. The only difference with SEnv is that it +// funds some default accounts, and that it enables `supported_amendments() | +// FeatureBitset{featureXChainBridge}` by default. +// ----------------------------------------------------------------------------- +template +struct XEnv : public jtx::XChainBridgeObjects, public SEnv +{ + XEnv(T& s, bool side = false) + : SEnv( + s, + jtx::envconfig(jtx::port_increment, side ? 3 : 0), + features) + { + using namespace jtx; + STAmount xrp_funds{XRP(10000)}; + + if (!side) + { + this->fund(xrp_funds, mcDoor, mcAlice, mcBob, mcCarol, mcGw); + + // Signer's list must match the attestation signers + // env_(jtx::signers(mcDoor, quorum, signers)); + for (auto& s : signers) + this->fund(xrp_funds, s.account); + } + else + { + this->fund( + xrp_funds, + scDoor, + scAlice, + scBob, + scCarol, + scGw, + scAttester, + scReward); + + for (auto& ra : payees) + this->fund(xrp_funds, ra); + + for (auto& s : signers) + this->fund(xrp_funds, s.account); + + // Signer's list must match the attestation signers + // env_(jtx::signers(Account::master, quorum, signers)); + } + this->close(); + } +}; + +// Tracks the xrp balance for one account +template +struct Balance +{ + jtx::Account const& account_; + T& env_; + STAmount startAmount; + + Balance(T& env, jtx::Account const& account) : account_(account), env_(env) + { + startAmount = env_.balance(account_); + } + + STAmount + diff() const + { + return env_.balance(account_) - startAmount; + } +}; + +// Tracks the xrp balance for multiple accounts involved in a crosss-chain +// transfer +template +struct BalanceTransfer +{ + using balance = Balance; + + balance from_; + balance to_; + balance payor_; // pays the rewards + std::vector reward_accounts; // receives the reward + XRPAmount txFees_; + + BalanceTransfer( + T& env, + jtx::Account const& from_acct, + jtx::Account const& to_acct, + jtx::Account const& payor, + jtx::Account const* payees, + size_t num_payees, + bool withClaim) + : from_(env, from_acct) + , to_(env, to_acct) + , payor_(env, payor) + , reward_accounts([&]() { + std::vector r; + r.reserve(num_payees); + for (size_t i = 0; i < num_payees; ++i) + r.emplace_back(env, payees[i]); + return r; + }()) + , txFees_(withClaim ? env.env_.current()->fees().base : XRPAmount(0)) + { + } + + BalanceTransfer( + T& env, + jtx::Account const& from_acct, + jtx::Account const& to_acct, + jtx::Account const& payor, + std::vector const& payees, + bool withClaim) + : BalanceTransfer( + env, + from_acct, + to_acct, + payor, + &payees[0], + payees.size(), + withClaim) + { + } + + bool + payees_received(STAmount const& reward) const + { + return std::all_of( + reward_accounts.begin(), + reward_accounts.end(), + [&](const balance& b) { return b.diff() == reward; }); + } + + bool + check_most_balances(STAmount const& amt, STAmount const& reward) + { + return from_.diff() == -amt && to_.diff() == amt && + payees_received(reward); + } + + bool + has_happened( + STAmount const& amt, + STAmount const& reward, + bool check_payer = true) + { + auto reward_cost = + multiply(reward, STAmount(reward_accounts.size()), reward.issue()); + return check_most_balances(amt, reward) && + (!check_payer || payor_.diff() == -(reward_cost + txFees_)); + } + + bool + has_not_happened() + { + return check_most_balances(STAmount(0), STAmount(0)) && + payor_.diff() <= txFees_; // could have paid fee for failed claim + } +}; + +struct BridgeDef +{ + jtx::Account doorA; + Issue issueA; + jtx::Account doorB; + Issue issueB; + STAmount reward; + STAmount minAccountCreate; + uint32_t quorum; + std::vector const& signers; + Json::Value jvb; + + template + void + initBridge(ENV& mcEnv, ENV& scEnv) + { + jvb = bridge(doorA, issueA, doorB, issueB); + + auto const optAccountCreate = [&]() -> std::optional { + if (issueA != xrpIssue() || issueB != xrpIssue()) + return {}; + return minAccountCreate; + }(); + mcEnv.tx(bridge_create(doorA, jvb, reward, optAccountCreate)) + .tx(jtx::signers(doorA, quorum, signers)) + .close(); + + scEnv.tx(bridge_create(doorB, jvb, reward, optAccountCreate)) + .tx(jtx::signers(doorB, quorum, signers)) + .close(); + } +}; + +struct XChain_test : public beast::unit_test::suite, + public jtx::XChainBridgeObjects +{ + XRPAmount + reserve(std::uint32_t count) + { + return XEnv(*this).env_.current()->fees().accountReserve(count); + } + + XRPAmount + txFee() + { + return XEnv(*this).env_.current()->fees().base; + } + + void + testXChainBridgeExtraFields() + { + auto jBridge = create_bridge(mcDoor)[sfXChainBridge.jsonName]; + bool exceptionPresent = false; + try + { + exceptionPresent = false; + [[maybe_unused]] STXChainBridge testBridge1(jBridge); + } + catch (std::exception& ec) + { + exceptionPresent = true; + } + + BEAST_EXPECT(!exceptionPresent); + + try + { + exceptionPresent = false; + jBridge["Extra"] = 1; + [[maybe_unused]] STXChainBridge testBridge2(jBridge); + } + catch ([[maybe_unused]] std::exception& ec) + { + exceptionPresent = true; + } + + BEAST_EXPECT(exceptionPresent); + } + + void + testXChainCreateBridge() + { + XRPAmount res1 = reserve(1); + + using namespace jtx; + testcase("Create Bridge"); + + // Normal create_bridge => should succeed + XEnv(*this).tx(create_bridge(mcDoor)).close(); + + // Bridge not owned by one of the door account. + XEnv(*this).tx( + create_bridge(mcBob), ter(temXCHAIN_BRIDGE_NONDOOR_OWNER)); + + // Create twice on the same account + XEnv(*this) + .tx(create_bridge(mcDoor)) + .close() + .tx(create_bridge(mcDoor), ter(tecDUPLICATE)); + + // Create USD bridge Alice -> Bob ... should succeed + XEnv(*this).tx( + create_bridge( + mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])), + ter(tesSUCCESS)); + + // Create USD bridge, Alice is both the locking door and locking issue, + // ... should fail. + XEnv(*this).tx( + create_bridge( + mcAlice, bridge(mcAlice, mcAlice["USD"], mcBob, mcBob["USD"])), + ter(temXCHAIN_BRIDGE_BAD_ISSUES)); + + // Bridge where the two door accounts are equal. + XEnv(*this).tx( + create_bridge( + mcBob, bridge(mcBob, mcGw["USD"], mcBob, mcGw["USD"])), + ter(temXCHAIN_EQUAL_DOOR_ACCOUNTS)); + + // Both door accounts are on the same chain. This is not allowed. + // Although it doesn't violate any invariants, it's not a useful thing + // to do and it complicates the "add claim" transactions. + XEnv(*this) + .tx(create_bridge( + mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"]))) + .close() + .tx(create_bridge( + mcBob, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])), + ter(tecDUPLICATE)) + .close(); + + // Create a bridge on an account with exactly enough balance to + // meet the new reserve should succeed + XEnv(*this) + .fund(res1, mcuDoor) // exact reserve for account + 1 object + .close() + .tx(create_bridge(mcuDoor, jvub), ter(tesSUCCESS)); + + // Create a bridge on an account with no enough balance to meet the + // new reserve + XEnv(*this) + .fund(res1 - 1, mcuDoor) // just short of required reserve + .close() + .tx(create_bridge(mcuDoor, jvub), ter(tecINSUFFICIENT_RESERVE)); + + // Reward amount is non-xrp + XEnv(*this).tx( + create_bridge(mcDoor, jvb, mcUSD(1)), + ter(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT)); + + // Reward amount is XRP and negative + XEnv(*this).tx( + create_bridge(mcDoor, jvb, XRP(-1)), + ter(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT)); + + // Reward amount is 1 xrp => should succeed + XEnv(*this).tx(create_bridge(mcDoor, jvb, XRP(1)), ter(tesSUCCESS)); + + // Min create amount is 1 xrp, mincreate is 1 xrp => should succeed + XEnv(*this).tx( + create_bridge(mcDoor, jvb, XRP(1), XRP(1)), ter(tesSUCCESS)); + + // Min create amount is non-xrp + XEnv(*this).tx( + create_bridge(mcDoor, jvb, XRP(1), mcUSD(100)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // Min create amount is zero (should fail, currently succeeds) + XEnv(*this).tx( + create_bridge(mcDoor, jvb, XRP(1), XRP(0)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // Min create amount is negative + XEnv(*this).tx( + create_bridge(mcDoor, jvb, XRP(1), XRP(-1)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // coverage test: BridgeCreate::preflight() - create bridge when feature + // disabled. + { + Env env(*this, supported_amendments() - featureXChainBridge); + env(create_bridge(Account::master, jvb), ter(temDISABLED)); + } + + // coverage test: BridgeCreate::preclaim() returns tecNO_ISSUER. + XEnv(*this).tx( + create_bridge( + mcAlice, bridge(mcAlice, mcuAlice["USD"], mcBob, mcBob["USD"])), + ter(tecNO_ISSUER)); + + // coverage test: create_bridge transaction with incorrect flag + XEnv(*this).tx( + create_bridge(mcAlice, jvb), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)); + + // coverage test: create_bridge transaction with xchain feature disabled + XEnv(*this) + .disableFeature(featureXChainBridge) + .tx(create_bridge(mcAlice, jvb), ter(temDISABLED)); + } + + void + testXChainBridgeCreateConstraints() + { + /** + * Bridge create constraints tests. + * + * Define the door's bridge asset collection as the collection of all + * the issuing assets for which the door account is on the issuing chain + * and all the locking assets for which the door account is on the + * locking chain. (note: a door account can simultaneously be on an + * issuing and locking chain). A new bridge is not a duplicate as long + * as the new bridge asset collection does not contain any duplicate + * currencies (even if the issuers differ). + * + * Create bridges: + * + *| Owner | Locking | Issuing | Comment | + *| a1 | a1 USD/GW | USD/B | | + *| a2 | a2 USD/GW | USD/B | Same locking & issuing assets | + *| | | | | + *| a3 | a3 USD/GW | USD/a4 | | + *| a4 | a4 USD/GW | USD/a4 | Same bridge, different accounts | + *| | | | | + *| B | A USD/GW | USD/B | | + *| B | A EUR/GW | USD/B | Fail: Same issuing asset | + *| | | | | + *| A | A USD/B | USD/C | | + *| A | A USD/B | EUR/B | Fail: Same locking asset | + *| A | A USD/C | EUR/B | Fail: Same locking asset currency | + *| | | | | + *| A | A USD/GW | USD/B | Fail: Same bridge not allowed | + *| A | B USD/GW | USD/A | Fail: "A" has USD already | + *| B | A EUR/GW | USD/B | Fail: | + * + * Note that, now from sidechain's point of view, A is both + * a local locking door and a foreign locking door on different + * bridges. Txns such as commits specify bridge spec, but not the + * local door account. So we test the transactors can figure out + * the correct local door account from bridge spec. + * + * Commit to sidechain door accounts: + * | bridge spec | result + * case 6 | A -> B | B's balance increase + * case 7 | C <- A | A's balance increase + * + * We also test ModifyBridge txns modify correct bridges. + */ + + using namespace jtx; + testcase("Bridge create constraints"); + XEnv env(*this, true); + auto& A = scAlice; + auto& B = scBob; + auto& C = scCarol; + auto AUSD = A["USD"]; + auto BUSD = B["USD"]; + auto CUSD = C["USD"]; + auto GUSD = scGw["USD"]; + auto AEUR = A["EUR"]; + auto BEUR = B["EUR"]; + auto CEUR = C["EUR"]; + auto GEUR = scGw["EUR"]; + + // Accounts to own single brdiges + Account const a1("a1"); + Account const a2("a2"); + Account const a3("a3"); + Account const a4("a4"); + Account const a5("a5"); + Account const a6("a6"); + + env.fund(XRP(10000), a1, a2, a3, a4, a5, a6); + env.close(); + + // Add a bridge on two different accounts with the same locking and + // issuing assets + env.tx(create_bridge(a1, bridge(a1, GUSD, B, BUSD))).close(); + env.tx(create_bridge(a2, bridge(a2, GUSD, B, BUSD))).close(); + + // Add the exact same bridge to two different accounts (one locking + // account and one issuing) + env.tx(create_bridge(a3, bridge(a3, GUSD, a4, a4["USD"]))).close(); + env.tx(create_bridge(a4, bridge(a3, GUSD, a4, a4["USD"])), + ter(tecDUPLICATE)) + .close(); + + // Add the exact same bridge to two different accounts (one issuing + // account and one locking - opposite order from the test above) + env.tx(create_bridge(a5, bridge(a6, GUSD, a5, a5["USD"]))).close(); + env.tx(create_bridge(a6, bridge(a6, GUSD, a5, a5["USD"])), + ter(tecDUPLICATE)) + .close(); + + // Test case 1 ~ 5, create bridges + auto const goodBridge1 = bridge(A, GUSD, B, BUSD); + auto const goodBridge2 = bridge(A, BUSD, C, CUSD); + env.tx(create_bridge(B, goodBridge1)).close(); + // Issuing asset is the same, this is a duplicate + env.tx(create_bridge(B, bridge(A, GEUR, B, BUSD)), ter(tecDUPLICATE)) + .close(); + env.tx(create_bridge(A, goodBridge2), ter(tesSUCCESS)).close(); + // Locking asset is the same - this is a duplicate + env.tx(create_bridge(A, bridge(A, BUSD, B, BEUR)), ter(tecDUPLICATE)) + .close(); + // Locking asset is USD - this is a duplicate even tho it has a + // different issuer + env.tx(create_bridge(A, bridge(A, CUSD, B, BEUR)), ter(tecDUPLICATE)) + .close(); + + // Test case 6 and 7, commits + env.tx(trust(C, BUSD(1000))) + .tx(trust(A, BUSD(1000))) + .close() + .tx(pay(B, C, BUSD(1000))) + .close(); + auto const aBalanceStart = env.balance(A, BUSD); + auto const cBalanceStart = env.balance(C, BUSD); + env.tx(xchain_commit(C, goodBridge1, 1, BUSD(50))).close(); + BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(0)); + BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50)); + env.tx(xchain_commit(C, goodBridge2, 1, BUSD(60))).close(); + BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(60)); + BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50 - 60)); + + // bridge modify test cases + env.tx(bridge_modify(B, goodBridge1, XRP(33), std::nullopt)).close(); + BEAST_EXPECT((*env.bridge(goodBridge1))[sfSignatureReward] == XRP(33)); + env.tx(bridge_modify(A, goodBridge2, XRP(44), std::nullopt)).close(); + BEAST_EXPECT((*env.bridge(goodBridge2))[sfSignatureReward] == XRP(44)); + } + + void + testXChainCreateBridgeMatrix() + { + using namespace jtx; + testcase("Create Bridge Matrix"); + + // Test all combinations of the following:` + // -------------------------------------- + // - Locking chain is IOU with locking chain door account as issuer + // - Locking chain is IOU with issuing chain door account that + // exists on the locking chain as issuer + // - Locking chain is IOU with issuing chain door account that does + // not exists on the locking chain as issuer + // - Locking chain is IOU with non-door account (that exists on the + // locking chain ledger) as issuer + // - Locking chain is IOU with non-door account (that does not exist + // exists on the locking chain ledger) as issuer + // - Locking chain is XRP + // --------------------------------------------------------------------- + // - Issuing chain is IOU with issuing chain door account as the + // issuer + // - Issuing chain is IOU with locking chain door account (that + // exists on the issuing chain ledger) as the issuer + // - Issuing chain is IOU with locking chain door account (that does + // not exist on the issuing chain ledger) as the issuer + // - Issuing chain is IOU with non-door account (that exists on the + // issuing chain ledger) as the issuer + // - Issuing chain is IOU with non-door account (that does not + // exists on the issuing chain ledger) as the issuer + // - Issuing chain is XRP and issuing chain door account is not the + // root account + // - Issuing chain is XRP and issuing chain door account is the root + // account + // --------------------------------------------------------------------- + // That's 42 combinations. The only combinations that should succeed + // are: + // - Locking chain is any IOU, + // - Issuing chain is IOU with issuing chain door account as the + // issuer + // Locking chain is XRP, + // - Issuing chain is XRP with issuing chain is the root account. + // --------------------------------------------------------------------- + Account a, b; + Issue ia, ib; + + std::tuple lcs{ + std::make_pair( + "Locking chain is IOU(locking chain door)", + [&](auto& env, bool) { + a = mcDoor; + ia = mcDoor["USD"]; + }), + std::make_pair( + "Locking chain is IOU(issuing chain door funded on locking " + "chain)", + [&](auto& env, bool shouldFund) { + a = mcDoor; + ia = scDoor["USD"]; + if (shouldFund) + env.fund(XRP(10000), scDoor); + }), + std::make_pair( + "Locking chain is IOU(issuing chain door account unfunded " + "on locking chain)", + [&](auto& env, bool) { + a = mcDoor; + ia = scDoor["USD"]; + }), + std::make_pair( + "Locking chain is IOU(bob funded on locking chain)", + [&](auto& env, bool) { + a = mcDoor; + ia = mcGw["USD"]; + }), + std::make_pair( + "Locking chain is IOU(bob unfunded on locking chain)", + [&](auto& env, bool) { + a = mcDoor; + ia = mcuGw["USD"]; + }), + std::make_pair("Locking chain is XRP", [&](auto& env, bool) { + a = mcDoor; + ia = xrpIssue(); + })}; + + std::tuple ics{ + std::make_pair( + "Issuing chain is IOU(issuing chain door account)", + [&](auto& env, bool) { + b = scDoor; + ib = scDoor["USD"]; + }), + std::make_pair( + "Issuing chain is IOU(locking chain door funded on issuing " + "chain)", + [&](auto& env, bool shouldFund) { + b = scDoor; + ib = mcDoor["USD"]; + if (shouldFund) + env.fund(XRP(10000), mcDoor); + }), + std::make_pair( + "Issuing chain is IOU(locking chain door unfunded on " + "issuing chain)", + [&](auto& env, bool) { + b = scDoor; + ib = mcDoor["USD"]; + }), + std::make_pair( + "Issuing chain is IOU(bob funded on issuing chain)", + [&](auto& env, bool) { + b = scDoor; + ib = mcGw["USD"]; + }), + std::make_pair( + "Issuing chain is IOU(bob unfunded on issuing chain)", + [&](auto& env, bool) { + b = scDoor; + ib = mcuGw["USD"]; + }), + std::make_pair( + "Issuing chain is XRP and issuing chain door account is " + "not the root account", + [&](auto& env, bool) { + b = scDoor; + ib = xrpIssue(); + }), + std::make_pair( + "Issuing chain is XRP and issuing chain door account is " + "the root account ", + [&](auto& env, bool) { + b = Account::master; + ib = xrpIssue(); + })}; + + std::vector> expected_result{ + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {tesSUCCESS, tesSUCCESS}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {tecNO_ISSUER, tesSUCCESS}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {tesSUCCESS, tesSUCCESS}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {tecNO_ISSUER, tesSUCCESS}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {temXCHAIN_BRIDGE_BAD_ISSUES, temXCHAIN_BRIDGE_BAD_ISSUES}, + {tesSUCCESS, tesSUCCESS}}; + + std::vector> test_result; + + auto testcase = [&](auto const& lc, auto const& ic) { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + lc.second(mcEnv, true); + lc.second(scEnv, false); + + ic.second(mcEnv, false); + ic.second(scEnv, true); + + auto const& expected = expected_result[test_result.size()]; + + mcEnv.tx( + create_bridge(a, bridge(a, ia, b, ib)), + ter(TER::fromInt(expected.first))); + TER mcTER = mcEnv.env_.ter(); + + scEnv.tx( + create_bridge(b, bridge(a, ia, b, ib)), + ter(TER::fromInt(expected.second))); + TER scTER = scEnv.env_.ter(); + + bool pass = mcTER == tesSUCCESS && scTER == tesSUCCESS; + + test_result.emplace_back(mcTER, scTER, pass); + }; + + auto apply_ics = [&](auto const& lc, auto const& ics) { + std::apply( + [&](auto const&... ic) { (testcase(lc, ic), ...); }, ics); + }; + + std::apply([&](auto const&... lc) { (apply_ics(lc, ics), ...); }, lcs); + +#if GENERATE_MTX_OUTPUT + // optional output of matrix results in markdown format + // ---------------------------------------------------- + std::string fname{std::tmpnam(nullptr)}; + fname += ".md"; + std::cout << "Markdown output for matrix test: " << fname << "\n"; + + auto print_res = [](auto tup) -> std::string { + std::string status = std::string(transToken(std::get<0>(tup))) + + " / " + transToken(std::get<1>(tup)); + + if (std::get<2>(tup)) + return status; + else + { + // red + return std::string("`") + status + "`"; + } + }; + + auto output_table = [&](auto print_res) { + size_t test_idx = 0; + std::string res; + res.reserve(10000); // should be enough :-) + + // first two header lines + res += "| `issuing ->` | "; + std::apply( + [&](auto const&... ic) { + ((res += ic.first, res += " | "), ...); + }, + ics); + res += "\n"; + + res += "| :--- | "; + std::apply( + [&](auto const&... ic) { + (((void)ic.first, res += ":---: | "), ...); + }, + ics); + res += "\n"; + + auto output = [&](auto const& lc, auto const& ic) { + res += print_res(test_result[test_idx]); + res += " | "; + ++test_idx; + }; + + auto output_ics = [&](auto const& lc, auto const& ics) { + res += "| "; + res += lc.first; + res += " | "; + std::apply( + [&](auto const&... ic) { (output(lc, ic), ...); }, ics); + res += "\n"; + }; + + std::apply( + [&](auto const&... lc) { (output_ics(lc, ics), ...); }, lcs); + + return res; + }; + + std::ofstream(fname) << output_table(print_res); + + std::string ter_fname{std::tmpnam(nullptr)}; + std::cout << "ter output for matrix test: " << ter_fname << "\n"; + + std::ofstream ofs(ter_fname); + for (auto& t : test_result) + { + ofs << "{ " << std::string(transToken(std::get<0>(t))) << ", " + << std::string(transToken(std::get<1>(t))) << "}\n,"; + } +#endif + } + + void + testXChainModifyBridge() + { + using namespace jtx; + testcase("Modify Bridge"); + + // Changing a non-existent bridge should fail + XEnv(*this).tx( + bridge_modify( + mcAlice, + bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"]), + XRP(2), + std::nullopt), + ter(tecNO_ENTRY)); + + // must change something + // XEnv(*this) + // .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1))) + // .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(1)), + // ter(temMALFORMED)); + + // must change something + XEnv(*this) + .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1))) + .close() + .tx(bridge_modify(mcDoor, jvb, {}, {}), ter(temMALFORMED)); + + // Reward amount is non-xrp + XEnv(*this).tx( + bridge_modify(mcDoor, jvb, mcUSD(2), XRP(10)), + ter(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT)); + + // Reward amount is XRP and negative + XEnv(*this).tx( + bridge_modify(mcDoor, jvb, XRP(-2), XRP(10)), + ter(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT)); + + // Min create amount is non-xrp + XEnv(*this).tx( + bridge_modify(mcDoor, jvb, XRP(2), mcUSD(10)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // Min create amount is zero + XEnv(*this).tx( + bridge_modify(mcDoor, jvb, XRP(2), XRP(0)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // Min create amount is negative + XEnv(*this).tx( + bridge_modify(mcDoor, jvb, XRP(2), XRP(-10)), + ter(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT)); + + // First check the regular claim process (without bridge_modify) + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + } + + // Check that the reward paid from a claim Id was the reward when + // the claim id was created, not the reward since the bridge was + // modified. + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + // Now modify the reward on the bridge + mcEnv.tx(bridge_modify(mcDoor, jvb, XRP(2), XRP(10))).close(); + scEnv.tx(bridge_modify(Account::master, jvb, XRP(2), XRP(10))) + .close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + // make sure the reward accounts indeed received the original + // split reward (1 split 5 ways) instead of the updated 2 XRP. + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + } + + // Check that the signatures used to verify attestations and decide + // if there is a quorum are the current signer's list on the door + // account, not the signer's list that was in effect when the claim + // id was created. + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + // change signers - claim should not be processed is the batch + // is signed by original signers + scEnv.tx(jtx::signers(Account::master, quorum, alt_signers)) + .close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + // submit claim using outdated signers - should fail + scEnv + .multiTx( + claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers), + ter(tecNO_PERMISSION)) + .close(); + if (withClaim) + { + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + // make sure transfer has not happened as we sent attestations + // using outdated signers + BEAST_EXPECT(transfer.has_not_happened()); + + // submit claim using current signers - should succeed + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + alt_signers)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + // make sure the transfer went through as we sent attestations + // using new signers + BEAST_EXPECT( + transfer.has_happened(amt, split_reward_quorum, false)); + } + + // coverage test: bridge_modify transaction with incorrect flag + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .close() + .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(2)), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)); + + // coverage test: bridge_modify transaction with xchain feature + // disabled + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .disableFeature(featureXChainBridge) + .close() + .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(2)), ter(temDISABLED)); + + // coverage test: bridge_modify return temSIDECHAIN_NONDOOR_OWNER; + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .close() + .tx(bridge_modify(mcAlice, jvb, XRP(1), XRP(2)), + ter(temXCHAIN_BRIDGE_NONDOOR_OWNER)); + + /** + * test tfClearAccountCreateAmount flag in BridgeModify tx + * -- tx has both minAccountCreateAmount and the flag, temMALFORMED + * -- tx has the flag and also modifies signature reward, tesSUCCESS + * -- XChainCreateAccountCommit tx fail after previous step + */ + XEnv(*this) + .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))) + .close() + .tx(sidechain_xchain_account_create( + mcAlice, jvb, scuAlice, XRP(100), reward)) + .close() + .tx(bridge_modify(mcDoor, jvb, {}, XRP(2)), + txflags(tfClearAccountCreateAmount), + ter(temMALFORMED)) + .close() + .tx(bridge_modify(mcDoor, jvb, XRP(3), {}), + txflags(tfClearAccountCreateAmount)) + .close() + .tx(sidechain_xchain_account_create( + mcAlice, jvb, scuBob, XRP(100), XRP(3)), + ter(tecXCHAIN_CREATE_ACCOUNT_DISABLED)) + .close(); + } + + void + testXChainCreateClaimID() + { + using namespace jtx; + XRPAmount res1 = reserve(1); + XRPAmount tx_fee = txFee(); + + testcase("Create ClaimID"); + + // normal bridge create for sanity check with the exact necessary + // account balance + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .fund(res1, scuAlice) // acct reserve + 1 object + .close() + .tx(xchain_create_claim_id(scuAlice, jvb, reward, mcAlice)) + .close(); + + // check reward not deducted when claim id is created + { + XEnv xenv(*this, true); + + Balance scAlice_bal(xenv, scAlice); + + xenv.tx(create_bridge(Account::master, jvb)) + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + BEAST_EXPECT(scAlice_bal.diff() == -tx_fee); + } + + // Non-existent bridge + XEnv(*this, true) + .tx(xchain_create_claim_id( + scAlice, + bridge(mcAlice, mcAlice["USD"], scBob, scBob["USD"]), + reward, + mcAlice), + ter(tecNO_ENTRY)) + .close(); + + // Creating the new object would put the account below the reserve + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .fund(res1 - xrp_dust, scuAlice) // barely not enough + .close() + .tx(xchain_create_claim_id(scuAlice, jvb, reward, mcAlice), + ter(tecINSUFFICIENT_RESERVE)) + .close(); + + // The specified reward doesn't match the reward on the bridge (test + // by giving the reward amount for the other side, as well as a + // completely non-matching reward) + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .close() + .tx(xchain_create_claim_id( + scAlice, jvb, split_reward_quorum, mcAlice), + ter(tecXCHAIN_REWARD_MISMATCH)) + .close(); + + // A reward amount that isn't XRP + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, mcUSD(1), mcAlice), + ter(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT)) + .close(); + + // coverage test: xchain_create_claim_id transaction with incorrect + // flag + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)) + .close(); + + // coverage test: xchain_create_claim_id transaction with xchain + // feature disabled + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .disableFeature(featureXChainBridge) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice), + ter(temDISABLED)) + .close(); + } + + void + testXChainCommit() + { + using namespace jtx; + XRPAmount res0 = reserve(0); + XRPAmount tx_fee = txFee(); + + testcase("Commit"); + + // Commit to a non-existent bridge + XEnv(*this).tx( + xchain_commit(mcAlice, jvb, 1, one_xrp, scBob), ter(tecNO_ENTRY)); + + // check that reward not deducted when doing the commit + { + XEnv xenv(*this); + + Balance alice_bal(xenv, mcAlice); + auto const amt = XRP(1000); + + xenv.tx(create_bridge(mcDoor, jvb)) + .close() + .tx(xchain_commit(mcAlice, jvb, 1, amt, scBob)) + .close(); + + STAmount claim_cost = amt; + BEAST_EXPECT(alice_bal.diff() == -(claim_cost + tx_fee)); + } + + // Commit a negative amount + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .close() + .tx(xchain_commit(mcAlice, jvb, 1, XRP(-1), scBob), + ter(temBAD_AMOUNT)); + + // Commit an amount whose issue that does not match the expected + // issue on the bridge (either LockingChainIssue or + // IssuingChainIssue, depending on the chain). + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .close() + .tx(xchain_commit(mcAlice, jvb, 1, mcUSD(100), scBob), + ter(temBAD_ISSUER)); + + // Commit an amount that would put the sender below the required + // reserve (if XRP) + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .fund(res0 + one_xrp - xrp_dust, mcuAlice) // barely not enough + .close() + .tx(xchain_commit(mcuAlice, jvb, 1, one_xrp, scBob), + ter(tecUNFUNDED_PAYMENT)); + + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .fund( + res0 + one_xrp + xrp_dust, // "xrp_dust" for tx fees + mcuAlice) // exactly enough => should succeed + .close() + .tx(xchain_commit(mcuAlice, jvb, 1, one_xrp, scBob)); + + // Commit an amount above the account's balance (for both XRP and + // IOUs) + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .fund(res0, mcuAlice) // barely not enough + .close() + .tx(xchain_commit(mcuAlice, jvb, 1, res0 + one_xrp, scBob), + ter(tecUNFUNDED_PAYMENT)); + + auto jvb_USD = bridge(mcDoor, mcUSD, scGw, scUSD); + + // commit sent from iou issuer (mcGw) succeeds - should it? + XEnv(*this) + .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline + .tx(create_bridge(mcDoor, jvb_USD)) + .close() + .tx(xchain_commit(mcGw, jvb_USD, 1, mcUSD(1), scBob)); + + // commit to a door account from the door account. This should fail. + XEnv(*this) + .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline + .tx(create_bridge(mcDoor, jvb_USD)) + .close() + .tx(xchain_commit(mcDoor, jvb_USD, 1, mcUSD(1), scBob), + ter(tecXCHAIN_SELF_COMMIT)); + + // commit sent from mcAlice which has no IOU balance => should fail + XEnv(*this) + .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline + .tx(create_bridge(mcDoor, jvb_USD)) + .close() + .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scBob), + ter(terNO_LINE)); + + // commit sent from mcAlice which has no IOU balance => should fail + // just changed the destination to scGw (which is the door account + // and may not make much sense) + XEnv(*this) + .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline + .tx(create_bridge(mcDoor, jvb_USD)) + .close() + .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scGw), + ter(terNO_LINE)); + + // commit sent from mcAlice which has a IOU balance => should + // succeed + XEnv(*this) + .tx(trust(mcDoor, mcUSD(10000))) + .tx(trust(mcAlice, mcUSD(10000))) + .close() + .tx(pay(mcGw, mcAlice, mcUSD(10))) + .tx(create_bridge(mcDoor, jvb_USD)) + .close() + //.tx(pay(mcAlice, mcDoor, mcUSD(10))); + .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(10), scAlice)); + + // coverage test: xchain_commit transaction with incorrect flag + XEnv(*this) + .tx(create_bridge(mcDoor)) + .close() + .tx(xchain_commit(mcAlice, jvb, 1, one_xrp, scBob), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)); + + // coverage test: xchain_commit transaction with xchain feature + // disabled + XEnv(*this) + .tx(create_bridge(mcDoor)) + .disableFeature(featureXChainBridge) + .close() + .tx(xchain_commit(mcAlice, jvb, 1, one_xrp, scBob), + ter(temDISABLED)); + } + + void + testXChainAddAttestation() + { + using namespace jtx; + + testcase("Add Attestation"); + XRPAmount res0 = reserve(0); + XRPAmount tx_fee = txFee(); + + auto multiTtxFee = [&](std::uint32_t m) -> STAmount { + return multiply(tx_fee, STAmount(m), xrpIssue()); + }; + + // Add an attestation to a claim id that has already reached quorum. + // This should succeed and share in the reward. + // note: this is true only when either: + // 1. dest account is not specified, so transfer requires a claim + // 2. or the extra attestation is sent in the same batch as the + // one reaching quorum + for (auto withClaim : {true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + std::uint32_t const claimID = 1; + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers, + UT_XCHAIN_DEFAULT_QUORUM)) + .close(); + scEnv + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[UT_XCHAIN_DEFAULT_QUORUM], + true, + claimID, + dst, + signers[UT_XCHAIN_DEFAULT_QUORUM])) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted + BEAST_EXPECT(scEnv.claimID(jvb) == claimID); + } + + BEAST_EXPECT(transfer.has_happened(amt, split_reward_everyone)); + } + + // Test that signature weights are correctly handled. Assign + // signature weights of 1,2,4,4 and a quorum of 7. Check that the + // 4,4 signatures reach a quorum, the 1,2,4, reach a quorum, but the + // 4,2, 4,1 and 1,2 do not. + + // 1,2,4 => should succeed + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + std::uint32_t const quorum_7 = 7; + std::vector const signers_ = [] { + constexpr int numSigners = 4; + std::uint32_t weights[] = {1, 2, 4, 4}; + + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account("signer_"s + std::to_string(i)); + result.emplace_back(a, weights[i]); + } + return result; + }(); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum_7, signers_)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + std::uint32_t const claimID = 1; + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + 3, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers_, + 3)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + BEAST_EXPECT(!scEnv.claimID(jvb, 1)); // claim id deleted + + BEAST_EXPECT(transfer.has_happened( + amt, divide(reward, STAmount(3), reward.issue()))); + } + + // 4,4 => should succeed + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + std::uint32_t const quorum_7 = 7; + std::vector const signers_ = [] { + constexpr int numSigners = 4; + std::uint32_t weights[] = {1, 2, 4, 4}; + + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account("signer_"s + std::to_string(i)); + result.emplace_back(a, weights[i]); + } + return result; + }(); + STAmount const split_reward_ = + divide(reward, STAmount(signers_.size()), reward.issue()); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum_7, signers_)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + std::uint32_t const claimID = 1; + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[2], + 2, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers_, + 2, + 2)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted + + BEAST_EXPECT(transfer.has_happened( + amt, divide(reward, STAmount(2), reward.issue()))); + } + + // 1,2 => should fail + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + std::uint32_t const quorum_7 = 7; + std::vector const signers_ = [] { + constexpr int numSigners = 4; + std::uint32_t weights[] = {1, 2, 4, 4}; + + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account("signer_"s + std::to_string(i)); + result.emplace_back(a, weights[i]); + } + return result; + }(); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum_7, signers_)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + std::uint32_t const claimID = 1; + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + 2, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers_, + 2)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + BEAST_EXPECT( + !!scEnv.claimID(jvb, claimID)); // claim id still present + BEAST_EXPECT(transfer.has_not_happened()); + } + + // 2,4 => should fail + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + std::uint32_t const quorum_7 = 7; + std::vector const signers_ = [] { + constexpr int numSigners = 4; + std::uint32_t weights[] = {1, 2, 4, 4}; + + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account("signer_"s + std::to_string(i)); + result.emplace_back(a, weights[i]); + } + return result; + }(); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum_7, signers_)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + std::uint32_t const claimID = 1; + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[1], + 2, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers_, + 2, + 1)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + BEAST_EXPECT( + !!scEnv.claimID(jvb, claimID)); // claim id still present + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Confirm that account create transactions happen in the correct + // order. If they reach quorum out of order they should not execute + // until all the previous created transactions have occurred. + // Re-adding an attestation should move funds. + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + auto const amt = XRP(1000); + auto const amt_plus_reward = amt + reward; + + { + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))) + .close() + .tx(sidechain_xchain_account_create( + mcAlice, jvb, scuAlice, amt, reward)) + .tx(sidechain_xchain_account_create( + mcBob, jvb, scuBob, amt, reward)) + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuCarol, amt, reward)) + .close(); + + BEAST_EXPECT( + door.diff() == + (multiply(amt_plus_reward, STAmount(3), xrpIssue()) - + tx_fee)); + BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee)); + } + + scEnv.tx(create_bridge(Account::master, jvb, reward, XRP(20))) + .tx(jtx::signers(Account::master, quorum, signers)) + .close(); + + { + // send first batch of account create attest for all 3 + // account create + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2)) + .multiTx(att_create_acct_vec(3, amt, scuCarol, 2)) + .multiTx(att_create_acct_vec(2, amt, scuBob, 2)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + // att_create_acct_vec return vectors of size 2, so 2*3 txns + BEAST_EXPECT(attester.diff() == -multiTtxFee(6)); + + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // ca claim id present + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count still 0 + } + + { + // complete attestations for 2nd account create => should + // not complete + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 3, 2)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + // att_create_acct_vec return vectors of size 3, so 3 txns + BEAST_EXPECT(attester.diff() == -multiTtxFee(3)); + + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count still 0 + } + + { + // complete attestations for 3rd account create => should + // not complete + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + // att_create_acct_vec return vectors of size 3, so 3 txns + BEAST_EXPECT(attester.diff() == -multiTtxFee(3)); + + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count still 0 + } + + { + // complete attestations for 1st account create => account + // should be created + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 3, 1)) + .close(); + + BEAST_EXPECT(door.diff() == -amt_plus_reward); + // att_create_acct_vec return vectors of size 3, so 3 txns + BEAST_EXPECT(attester.diff() == -multiTtxFee(3)); + BEAST_EXPECT(scEnv.balance(scuAlice) == amt); + + BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id 1 deleted + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present + BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count now 1 + } + + { + // resend attestations for 3rd account create => still + // should not complete + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + // att_create_acct_vec return vectors of size 3, so 3 txns + BEAST_EXPECT(attester.diff() == -multiTtxFee(3)); + + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 1); // claim count still 1 + } + + { + // resend attestations for 2nd account create => account + // should be created + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1)).close(); + + BEAST_EXPECT(door.diff() == -amt_plus_reward); + BEAST_EXPECT(attester.diff() == -tx_fee); + BEAST_EXPECT(scEnv.balance(scuBob) == amt); + + BEAST_EXPECT(!scEnv.caClaimID(jvb, 2)); // claim id 2 deleted + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present + BEAST_EXPECT(scEnv.claimCount(jvb) == 2); // claim count now 2 + } + { + // resend attestations for 3rc account create => account + // should be created + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1)).close(); + + BEAST_EXPECT(door.diff() == -amt_plus_reward); + BEAST_EXPECT(attester.diff() == -tx_fee); + BEAST_EXPECT(scEnv.balance(scuCarol) == amt); + + BEAST_EXPECT(!scEnv.caClaimID(jvb, 3)); // claim id 3 deleted + BEAST_EXPECT(scEnv.claimCount(jvb) == 3); // claim count now 3 + } + } + + // Check that creating an account with less than the minimum reserve + // fails. + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + auto const amt = res0 - XRP(1); + auto const amt_plus_reward = amt + reward; + + mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close(); + + { + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, amt, reward)) + .close(); + + BEAST_EXPECT(door.diff() == amt_plus_reward); + BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee)); + } + + scEnv.tx(create_bridge(Account::master, jvb, reward, XRP(20))) + .tx(jtx::signers(Account::master, quorum, signers)) + .close(); + + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2)).close(); + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count is one less + + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2, 2)).close(); + BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted + BEAST_EXPECT( + scEnv.claimCount(jvb) == 1); // claim count was incremented + + BEAST_EXPECT(attester.diff() == -multiTtxFee(4)); + BEAST_EXPECT(door.diff() == -reward); + BEAST_EXPECT(!scEnv.account(scuAlice)); + } + + // Check that sending funds with an account create txn to an + // existing account works. + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + auto const amt = XRP(111); + auto const amt_plus_reward = amt + reward; + + mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close(); + + { + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scAlice, amt, reward)) + .close(); + + BEAST_EXPECT(door.diff() == amt_plus_reward); + BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee)); + } + + scEnv.tx(create_bridge(Account::master, jvb, reward, XRP(20))) + .tx(jtx::signers(Account::master, quorum, signers)) + .close(); + + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + Balance alice(scEnv, scAlice); + + scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close(); + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count is one less + + scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close(); + BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted + BEAST_EXPECT( + scEnv.claimCount(jvb) == 1); // claim count was incremented + + BEAST_EXPECT(door.diff() == -amt_plus_reward); + BEAST_EXPECT(attester.diff() == -multiTtxFee(4)); + BEAST_EXPECT(alice.diff() == amt); + } + + // Check that sending funds to an existing account with deposit auth + // set fails for account create transactions. + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + auto const amt = XRP(1000); + auto const amt_plus_reward = amt + reward; + + mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close(); + + { + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scAlice, amt, reward)) + .close(); + + BEAST_EXPECT(door.diff() == amt_plus_reward); + BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee)); + } + + scEnv.tx(create_bridge(Account::master, jvb, reward, XRP(20))) + .tx(jtx::signers(Account::master, quorum, signers)) + .tx(fset("scAlice", asfDepositAuth)) // set deposit auth + .close(); + + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + Balance alice(scEnv, scAlice); + + scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close(); + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count is one less + + scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close(); + BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted + BEAST_EXPECT( + scEnv.claimCount(jvb) == 1); // claim count was incremented + + BEAST_EXPECT(door.diff() == -reward); + BEAST_EXPECT(attester.diff() == -multiTtxFee(4)); + BEAST_EXPECT(alice.diff() == STAmount(0)); + } + + // If an account is unable to pay the reserve, check that it fails. + // [greg todo] I don't know what this should test?? + + // If an attestation already exists for that server and claim id, + // the new attestation should replace the old attestation + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + auto const amt = XRP(1000); + auto const amt_plus_reward = amt + reward; + + { + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))) + .close() + .tx(sidechain_xchain_account_create( + mcAlice, jvb, scuAlice, amt, reward)) + .close() // make sure Alice gets claim #1 + .tx(sidechain_xchain_account_create( + mcBob, jvb, scuBob, amt, reward)) + .close() // make sure Bob gets claim #2 + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuCarol, amt, reward)) + .close(); // and Carol will get claim #3 + + BEAST_EXPECT( + door.diff() == + (multiply(amt_plus_reward, STAmount(3), xrpIssue()) - + tx_fee)); + BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee)); + } + + std::uint32_t const red_quorum = 2; + scEnv.tx(create_bridge(Account::master, jvb, reward, XRP(20))) + .tx(jtx::signers(Account::master, red_quorum, signers)) + .close(); + + { + Balance attester(scEnv, scAttester); + Balance door(scEnv, Account::master); + auto const bad_amt = XRP(10); + std::uint32_t txCount = 0; + + // send attestations with incorrect amounts to for all 3 + // AccountCreate. They will be replaced later + scEnv.multiTx(att_create_acct_vec(1, bad_amt, scuAlice, 1)) + .multiTx(att_create_acct_vec(2, bad_amt, scuBob, 1, 2)) + .multiTx(att_create_acct_vec(3, bad_amt, scuCarol, 1, 1)) + .close(); + txCount += 3; + + BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 1), "claim id 1 created"); + BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 2), "claim id 2 created"); + BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 created"); + + // note: if we send inconsistent attestations in the same + // batch, the transaction errors. + + // from now on we send correct attestations + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 0)) + .multiTx(att_create_acct_vec(2, amt, scuBob, 1, 2)) + .multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 4)) + .close(); + txCount += 3; + + BEAST_EXPECTS( + !!scEnv.caClaimID(jvb, 1), "claim id 1 still there"); + BEAST_EXPECTS( + !!scEnv.caClaimID(jvb, 2), "claim id 2 still there"); + BEAST_EXPECTS( + !!scEnv.caClaimID(jvb, 3), "claim id 3 still there"); + BEAST_EXPECTS( + scEnv.claimCount(jvb) == 0, "No account created yet"); + + scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 1)) + .close(); + txCount += 1; + + BEAST_EXPECTS( + !!scEnv.caClaimID(jvb, 3), "claim id 3 still there"); + BEAST_EXPECTS( + scEnv.claimCount(jvb) == 0, "No account created yet"); + + scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 2)) + .close(); + txCount += 1; + + BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 deleted"); + BEAST_EXPECTS(scEnv.claimCount(jvb) == 1, "scuAlice created"); + + scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1, 3)) + .multiTx( + att_create_acct_vec(1, amt, scuAlice, 1, 3), + ter(tecXCHAIN_ACCOUNT_CREATE_PAST)) + .close(); + txCount += 2; + + BEAST_EXPECTS(!scEnv.caClaimID(jvb, 2), "claim id 2 deleted"); + BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 not added"); + BEAST_EXPECTS( + scEnv.claimCount(jvb) == 2, "scuAlice & scuBob created"); + + scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 0)) + .close(); + txCount += 1; + + BEAST_EXPECTS(!scEnv.caClaimID(jvb, 3), "claim id 3 deleted"); + BEAST_EXPECTS( + scEnv.claimCount(jvb) == 3, "All 3 accounts created"); + + // because of the division of the rewards among attesters, + // sometimes a couple drops are left over unspent in the + // door account (here 2 drops) + BEAST_EXPECT( + multiply(amt_plus_reward, STAmount(3), xrpIssue()) + + door.diff() < + drops(3)); + BEAST_EXPECT(attester.diff() == -multiTtxFee(txCount)); + BEAST_EXPECT(scEnv.balance(scuAlice) == amt); + BEAST_EXPECT(scEnv.balance(scuBob) == amt); + BEAST_EXPECT(scEnv.balance(scuCarol) == amt); + } + } + + // If attestation moves funds, confirm the claim ledger objects are + // removed (for both account create and "regular" transactions) + // [greg] we do this in all attestation tests + + // coverage test: add_attestation transaction with incorrect flag + { + XEnv scEnv(*this, true); + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + XRP(1000), + payees[0], + true, + 1, + {}, + signers[0]), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)) + .close(); + } + + // coverage test: add_attestation with xchain feature + // disabled + { + XEnv scEnv(*this, true); + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .disableFeature(featureXChainBridge) + .close() + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + XRP(1000), + payees[0], + true, + 1, + {}, + signers[0]), + ter(temDISABLED)) + .close(); + } + } + + void + testXChainAddClaimNonBatchAttestation() + { + using namespace jtx; + + testcase("Add Non Batch Claim Attestation"); + + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + std::uint32_t const claimID = 1; + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present + + Account const dst{scBob}; + auto const amt = XRP(1000); + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + auto const dstStartBalance = scEnv.env_.balance(dst); + + for (int i = 0; i < signers.size(); ++i) + { + auto const att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[i], + true, + claimID, + dst, + signers[i]); + + TER const expectedTER = + i < quorum ? tesSUCCESS : TER{tecXCHAIN_NO_CLAIM_ID}; + if (i + 1 == quorum) + scEnv.tx(att, ter(expectedTER)).close(); + else + scEnv.tx(att, ter(expectedTER)).close(); + + if (i + 1 < quorum) + BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst)); + else + BEAST_EXPECT( + dstStartBalance + amt == scEnv.env_.balance(dst)); + } + BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst)); + } + + { + /** + * sfAttestationSignerAccount related cases. + * + * Good cases: + * --G1: master key + * --G2: regular key + * --G3: public key and non-exist (unfunded) account match + * + * Bad cases: + * --B1: disabled master key + * --B2: single item signer list + * --B3: public key and non-exist (unfunded) account mismatch + * --B4: not on signer list + * --B5: missing sfAttestationSignerAccount field + */ + + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + + for (auto i = 0; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS - 2; ++i) + scEnv.fund(amt, alt_signers[i].account); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, alt_signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + Account const dst{scBob}; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + auto const dstStartBalance = scEnv.env_.balance(dst); + + { + // G1: master key + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + alt_signers[0]); + scEnv.tx(att).close(); + } + { + // G2: regular key + // alt_signers[0] is the regular key of alt_signers[1] + // There should be 2 attestations after the transaction + scEnv + .tx(jtx::regkey( + alt_signers[1].account, alt_signers[0].account)) + .close(); + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[1], + true, + claimID, + dst, + alt_signers[0]); + att[sfAttestationSignerAccount.getJsonName()] = + alt_signers[1].account.human(); + scEnv.tx(att).close(); + } + { + // B3: public key and non-exist (unfunded) account mismatch + // G3: public key and non-exist (unfunded) account match + auto const unfundedSigner1 = + alt_signers[UT_XCHAIN_DEFAULT_NUM_SIGNERS - 1]; + auto const unfundedSigner2 = + alt_signers[UT_XCHAIN_DEFAULT_NUM_SIGNERS - 2]; + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[UT_XCHAIN_DEFAULT_NUM_SIGNERS - 1], + true, + claimID, + dst, + unfundedSigner1); + att[sfAttestationSignerAccount.getJsonName()] = + unfundedSigner2.account.human(); + scEnv.tx(att, ter(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR)) + .close(); + att[sfAttestationSignerAccount.getJsonName()] = + unfundedSigner1.account.human(); + scEnv.tx(att).close(); + } + { + // B2: single item signer list + std::vector tempSignerList = {signers[0]}; + scEnv.tx( + jtx::signers(alt_signers[2].account, 1, tempSignerList)); + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[2], + true, + claimID, + dst, + tempSignerList.front()); + att[sfAttestationSignerAccount.getJsonName()] = + alt_signers[2].account.human(); + scEnv.tx(att, ter(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR)) + .close(); + } + { + // B1: disabled master key + scEnv.tx(fset(alt_signers[2].account, asfDisableMaster, 0)); + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[2], + true, + claimID, + dst, + alt_signers[2]); + scEnv.tx(att, ter(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR)) + .close(); + } + { + // --B4: not on signer list + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + signers[0]); + scEnv.tx(att, ter(tecNO_PERMISSION)).close(); + } + { + // --B5: missing sfAttestationSignerAccount field + // Then submit the one with the field. Should rearch quorum. + auto att = claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[3], + true, + claimID, + dst, + alt_signers[3]); + att.removeMember(sfAttestationSignerAccount.getJsonName()); + scEnv.tx(att, ter(temMALFORMED)).close(); + BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst)); + att[sfAttestationSignerAccount.getJsonName()] = + alt_signers[3].account.human(); + scEnv.tx(att).close(); + BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst)); + } + } + } + + void + testXChainAddAccountCreateNonBatchAttestation() + { + using namespace jtx; + + testcase("Add Non Batch Account Create Attestation"); + + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + XRPAmount tx_fee = mcEnv.txFee(); + + Account a{"a"}; + Account doorA{"doorA"}; + + STAmount funds{XRP(10000)}; + mcEnv.fund(funds, a); + mcEnv.fund(funds, doorA); + + Account ua{"ua"}; // unfunded account we want to create + + BridgeDef xrp_b{ + doorA, + xrpIssue(), + Account::master, + xrpIssue(), + XRP(1), // reward + XRP(20), // minAccountCreate + 4, // quorum + signers, + Json::nullValue}; + + xrp_b.initBridge(mcEnv, scEnv); + + auto const amt = XRP(777); + auto const amt_plus_reward = amt + xrp_b.reward; + { + Balance bal_doorA(mcEnv, doorA); + Balance bal_a(mcEnv, a); + + mcEnv + .tx(sidechain_xchain_account_create( + a, xrp_b.jvb, ua, amt, xrp_b.reward)) + .close(); + + BEAST_EXPECT(bal_doorA.diff() == amt_plus_reward); + BEAST_EXPECT(bal_a.diff() == -(amt_plus_reward + tx_fee)); + } + + for (int i = 0; i < signers.size(); ++i) + { + auto const att = create_account_attestation( + signers[0].account, + xrp_b.jvb, + a, + amt, + xrp_b.reward, + signers[i].account, + true, + 1, + ua, + signers[i]); + TER const expectedTER = i < xrp_b.quorum + ? tesSUCCESS + : TER{tecXCHAIN_ACCOUNT_CREATE_PAST}; + + scEnv.tx(att, ter(expectedTER)).close(); + if (i + 1 < xrp_b.quorum) + BEAST_EXPECT(!scEnv.env_.le(ua)); + else + BEAST_EXPECT(scEnv.env_.le(ua)); + } + BEAST_EXPECT(scEnv.env_.le(ua)); + } + + void + testXChainClaim() + { + using namespace jtx; + + XRPAmount res0 = reserve(0); + XRPAmount tx_fee = txFee(); + + testcase("Claim"); + + // Claim where the amount matches what is attested to, to an account + // that exists, and there are enough attestations to reach a quorum + // => should succeed + // ----------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + } + + // Claim with just one attestation signed by the Master key + // => should not succeed + // ----------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv + .tx(create_bridge(Account::master, jvb)) + //.tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + 1, + withClaim); + + jtx::signer master_signer(Account::master); + scEnv + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + master_signer), + ter(tecXCHAIN_NO_SIGNERS_LIST)) + .close(); + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim with just one attestation signed by a regular key + // associated to the master account + // => should not succeed + // ----------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv + .tx(create_bridge(Account::master, jvb)) + //.tx(jtx::signers(Account::master, quorum, signers)) + .tx(jtx::regkey(Account::master, payees[0])) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + 1, + withClaim); + + jtx::signer master_signer(payees[0]); + scEnv + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + master_signer), + ter(tecXCHAIN_NO_SIGNERS_LIST)) + .close(); + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim against non-existent bridge + // --------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + auto jvb_unknown = + bridge(mcBob, xrpIssue(), Account::master, xrpIssue()); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id( + scAlice, jvb_unknown, reward, mcAlice), + ter(tecNO_ENTRY)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv + .tx(xchain_commit(mcAlice, jvb_unknown, claimID, amt, dst), + ter(tecNO_ENTRY)) + .close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + scEnv + .tx(claim_attestation( + scAttester, + jvb_unknown, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + signers[0]), + ter(tecNO_ENTRY)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb_unknown, claimID, amt, scBob), + ter(tecNO_ENTRY)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim against non-existent claim id + // ----------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + + // attest using non-existent claim id + scEnv + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + 999, + dst, + signers[0]), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // claim using non-existent claim id + scEnv + .tx(xchain_claim(scAlice, jvb, 999, amt, scBob), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim against a claim id owned by another account + // ------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // submit a claim transaction with the wrong account (scGw + // instead of scAlice) + scEnv + .tx(xchain_claim(scGw, jvb, claimID, amt, scBob), + ter(tecXCHAIN_BAD_CLAIM_ID)) + .close(); + BEAST_EXPECT(transfer.has_not_happened()); + } + else + { + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + } + } + + // Claim against a claim id with no attestations + // --------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + + // don't send any attestations + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim against a claim id with attestations, but not enough to + // make a quorum + // -------------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + + auto tooFew = quorum - 1; + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers, + tooFew)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim id of zero + // ---------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scAlice, payees, withClaim); + + scEnv + .multiTx( + claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + 0, + dst, + signers), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, 0, amt, scBob), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim issue that does not match the expected issue on the bridge + // (either LockingChainIssue or IssuingChainIssue, depending on the + // chain). The claim id should already have enough attestations to + // reach a quorum for this amount (for a different issuer). + // --------------------------------------------------------------------- + for (auto withClaim : {true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, scUSD(1000), scBob), + ter(temBAD_AMOUNT)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim to a destination that does not already exist on the chain + // ----------------------------------------------------------------- + for (auto withClaim : {true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scuBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scuBob), + ter(tecNO_DST)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim where the claim id owner does not have enough XRP to pay + // the reward + // ------------------------------------------------------------------ + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + STAmount huge_reward{XRP(20000)}; + BEAST_EXPECT(huge_reward > scEnv.balance(scAlice)); + + scEnv.tx(create_bridge(Account::master, jvb, huge_reward)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, huge_reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + + if (withClaim) + { + scEnv + .multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)) + .close(); + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecUNFUNDED_PAYMENT)) + .close(); + } + else + { + auto txns = claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers); + for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i) + { + scEnv.tx(txns[i]).close(); + } + scEnv.tx(txns.back()); + scEnv.close(); + // The attestation should succeed, because it adds an + // attestation, but the claim should fail with insufficient + // funds + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecUNFUNDED_PAYMENT)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Claim where the claim id owner has enough XRP to pay the reward, + // but it would put his balance below the reserve + // -------------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .fund( + res0 + reward, + scuAlice) // just not enough because of fees + .close() + .tx(xchain_create_claim_id(scuAlice, jvb, reward, mcAlice), + ter(tecINSUFFICIENT_RESERVE)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, Account::master, scBob, scuAlice, payees, withClaim); + + scEnv + .tx(claim_attestation( + scAttester, + jvb, + mcAlice, + amt, + payees[0], + true, + claimID, + dst, + signers[0]), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scuAlice, jvb, claimID, amt, scBob), + ter(tecXCHAIN_NO_CLAIM_ID)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Pay to an account with deposit auth set + // --------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .tx(fset("scBob", asfDepositAuth)) // set deposit auth + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + auto txns = claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers); + for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i) + { + scEnv.tx(txns[i]).close(); + } + if (withClaim) + { + scEnv.tx(txns.back()).close(); + + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecNO_PERMISSION)) + .close(); + + // the transfer failed, but check that we can still use the + // claimID with a different account + Balance scCarol_bal(scEnv, scCarol); + + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol)) + .close(); + BEAST_EXPECT(scCarol_bal.diff() == amt); + } + else + { + scEnv.tx(txns.back()).close(); + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecNO_PERMISSION)) + .close(); + // A way would be to remove deposit auth and resubmit the + // attestations (even though the witness servers won't do + // it) + scEnv + .tx(fset("scBob", 0, asfDepositAuth)) // clear deposit auth + .close(); + + Balance scBob_bal(scEnv, scBob); + scEnv.tx(txns.back()).close(); + BEAST_EXPECT(scBob_bal.diff() == amt); + } + } + + // Pay to an account with Destination Tag set + // ------------------------------------------ + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .tx(fset("scBob", asfRequireDest)) // set dest tag + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + auto txns = claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers); + for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i) + { + scEnv.tx(txns[i]).close(); + } + if (withClaim) + { + scEnv.tx(txns.back()).close(); + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecDST_TAG_NEEDED)) + .close(); + + // the transfer failed, but check that we can still use the + // claimID with a different account + Balance scCarol_bal(scEnv, scCarol); + + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol)) + .close(); + BEAST_EXPECT(scCarol_bal.diff() == amt); + } + else + { + scEnv.tx(txns.back()).close(); + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob), + ter(tecDST_TAG_NEEDED)) + .close(); + // A way would be to remove the destination tag requirement + // and resubmit the attestations (even though the witness + // servers won't do it) + scEnv + .tx(fset("scBob", 0, asfRequireDest)) // clear dest tag + .close(); + + Balance scBob_bal(scEnv, scBob); + + scEnv.tx(txns.back()).close(); + BEAST_EXPECT(scBob_bal.diff() == amt); + } + } + + // Pay to an account with deposit auth set. Check that the attestations + // are still validated and that we can used the claimID to transfer the + // funds to a different account (which doesn't have deposit auth set) + // -------------------------------------------------------------------- + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .tx(fset("scBob", asfDepositAuth)) // set deposit auth + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + // we should be able to submit the attestations, but the transfer + // should not occur because dest account has deposit auth set + Balance scBob_bal(scEnv, scBob); + + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)); + BEAST_EXPECT(scBob_bal.diff() == STAmount(0)); + + // Check that check that we still can use the claimID to transfer + // the amount to a different account + Balance scCarol_bal(scEnv, scCarol); + + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol)).close(); + BEAST_EXPECT(scCarol_bal.diff() == amt); + } + + // Claim where the amount different from what is attested to + // --------------------------------------------------------- + for (auto withClaim : {true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)); + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // claim wrong amount + scEnv + .tx(xchain_claim(scAlice, jvb, claimID, one_xrp, scBob), + ter(tecXCHAIN_CLAIM_NO_QUORUM)) + .close(); + } + + BEAST_EXPECT(transfer.has_not_happened()); + } + + // Verify that rewards are paid from the account that owns the claim + // id + // -------------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + Balance scAlice_bal(scEnv, scAlice); + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)); + + STAmount claim_cost = reward; + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + claim_cost += tx_fee; + } + + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + BEAST_EXPECT( + scAlice_bal.diff() == -claim_cost); // because reward % 4 == 0 + } + + // Verify that if a reward is not evenly divisible among the reward + // accounts, the remaining amount goes to the claim id owner. + // ---------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb, tiny_reward)).close(); + + scEnv.tx(create_bridge(Account::master, jvb, tiny_reward)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, tiny_reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM, + withClaim); + Balance scAlice_bal(scEnv, scAlice); + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)); + STAmount claim_cost = tiny_reward; + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + claim_cost += tx_fee; + } + + BEAST_EXPECT(transfer.has_happened(amt, tiny_reward_split)); + BEAST_EXPECT( + scAlice_bal.diff() == -(claim_cost - tiny_reward_remainder)); + } + + // If a reward distribution fails for one of the reward accounts + // (the reward account doesn't exist or has deposit auth set), then + // the txn should still succeed, but that portion should go to the + // claim id owner. + // ------------------------------------------------------------------- + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + + std::vector alt_payees{payees.begin(), payees.end() - 1}; + alt_payees.back() = Account("inexistent"); + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM - 1, + withClaim); + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + alt_payees, + true, + claimID, + dst, + signers)); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + // this also checks that only 3 * split_reward was deducted from + // scAlice (the payor account), since we passed alt_payees to + // BalanceTransfer + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + } + + for (auto withClaim : {false, true}) + { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + mcEnv.tx(create_bridge(mcDoor, jvb)).close(); + auto& unpaid = payees[UT_XCHAIN_DEFAULT_QUORUM - 1]; + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .tx(fset(unpaid, asfDepositAuth)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + auto dst(withClaim ? std::nullopt : std::optional{scBob}); + auto const amt = XRP(1000); + std::uint32_t const claimID = 1; + mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close(); + + // balance of last signer should not change (has deposit auth) + Balance last_signer(scEnv, unpaid); + + // make sure all signers except the last one get the + // split_reward + + BalanceTransfer transfer( + scEnv, + Account::master, + scBob, + scAlice, + &payees[0], + UT_XCHAIN_DEFAULT_QUORUM - 1, + withClaim); + scEnv.multiTx(claim_attestations( + scAttester, + jvb, + mcAlice, + amt, + payees, + true, + claimID, + dst, + signers)); + + if (withClaim) + { + BEAST_EXPECT(transfer.has_not_happened()); + + // need to submit a claim transactions + scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob)) + .close(); + } + + // this also checks that only 3 * split_reward was deducted from + // scAlice (the payor account), since we passed payees.size() - + // 1 to BalanceTransfer + BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); + + // and make sure the account with deposit auth received nothing + BEAST_EXPECT(last_signer.diff() == STAmount(0)); + } + + // coverage test: xchain_claim transaction with incorrect flag + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .close() + .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)) + .close(); + + // coverage test: xchain_claim transaction with xchain feature + // disabled + XEnv(*this, true) + .tx(create_bridge(Account::master, jvb)) + .disableFeature(featureXChainBridge) + .close() + .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob), + ter(temDISABLED)) + .close(); + + // coverage test: XChainClaim::preclaim - isLockingChain = true; + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .close() + .tx(xchain_claim(mcAlice, jvb, 1, XRP(1000), mcBob), + ter(tecXCHAIN_NO_CLAIM_ID)); + } + + void + testXChainCreateAccount() + { + using namespace jtx; + + testcase("Bridge Create Account"); + XRPAmount tx_fee = txFee(); + + // coverage test: transferHelper() - dst == src + { + XEnv scEnv(*this, true); + + auto const amt = XRP(111); + auto const amt_plus_reward = amt + reward; + + scEnv.tx(create_bridge(Account::master, jvb)) + .tx(jtx::signers(Account::master, quorum, signers)) + .close(); + + Balance door(scEnv, Account::master); + + // scEnv.tx(att_create_acct_batch1(1, amt, + // Account::master)).close(); + scEnv.multiTx(att_create_acct_vec(1, amt, Account::master, 2)) + .close(); + BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present + BEAST_EXPECT( + scEnv.claimCount(jvb) == 0); // claim count is one less + + // scEnv.tx(att_create_acct_batch2(1, amt, + // Account::master)).close(); + scEnv.multiTx(att_create_acct_vec(1, amt, Account::master, 2, 2)) + .close(); + BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted + BEAST_EXPECT( + scEnv.claimCount(jvb) == 1); // claim count was incremented + + BEAST_EXPECT(door.diff() == -reward); + } + + // Check that creating an account with less than the minimum create + // amount fails. + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + Balance carol(mcEnv, mcCarol); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(19), reward), + ter(tecXCHAIN_INSUFF_CREATE_AMOUNT)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + BEAST_EXPECT(carol.diff() == -tx_fee); + } + + // Check that creating an account with invalid flags fails. + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(20), reward), + txflags(tfFillOrKill), + ter(temINVALID_FLAG)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + } + + // Check that creating an account with the XChainBridge feature + // disabled fails. + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv.disableFeature(featureXChainBridge) + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(20), reward), + ter(temDISABLED)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + } + + // Check that creating an account with a negative amount fails + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(-20), reward), + ter(temBAD_AMOUNT)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + } + + // Check that creating an account with a negative reward fails + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(20), XRP(-1)), + ter(temBAD_AMOUNT)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + } + + // Check that door account can't lock funds onto itself + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv + .tx(sidechain_xchain_account_create( + mcDoor, jvb, scuAlice, XRP(20), XRP(1)), + ter(tecXCHAIN_SELF_COMMIT)) + .close(); + + BEAST_EXPECT(door.diff() == -tx_fee); + } + + // Check that reward matches the amount specified in bridge + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close(); + + Balance door(mcEnv, mcDoor); + + mcEnv + .tx(sidechain_xchain_account_create( + mcCarol, jvb, scuAlice, XRP(20), XRP(2)), + ter(tecXCHAIN_REWARD_MISMATCH)) + .close(); + + BEAST_EXPECT(door.diff() == STAmount(0)); + } + } + + void + testFeeDipsIntoReserve() + { + using namespace jtx; + XRPAmount res0 = reserve(0); + XRPAmount tx_fee = txFee(); + + testcase("Fee dips into reserve"); + + // commit where the fee dips into the reserve, this should succeed + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .fund(res0 + one_xrp + tx_fee - drops(1), mcuAlice) + .close() + .tx(xchain_commit(mcuAlice, jvb, 1, one_xrp, scBob), + ter(tesSUCCESS)); + + // commit where the commit amount drips into the reserve, this should + // fail + XEnv(*this) + .tx(create_bridge(mcDoor, jvb)) + .fund(res0 + one_xrp - drops(1), mcuAlice) + .close() + .tx(xchain_commit(mcuAlice, jvb, 1, one_xrp, scBob), + ter(tecUNFUNDED_PAYMENT)); + + auto const minAccountCreate = XRP(20); + + // account create commit where the fee dips into the reserve, + // this should succeed + XEnv(*this) + .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate)) + .fund( + res0 + tx_fee + minAccountCreate + reward - drops(1), mcuAlice) + .close() + .tx(sidechain_xchain_account_create( + mcuAlice, jvb, scuAlice, minAccountCreate, reward), + ter(tesSUCCESS)); + + // account create commit where the commit dips into the reserve, + // this should fail + XEnv(*this) + .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate)) + .fund(res0 + minAccountCreate + reward - drops(1), mcuAlice) + .close() + .tx(sidechain_xchain_account_create( + mcuAlice, jvb, scuAlice, minAccountCreate, reward), + ter(tecUNFUNDED_PAYMENT)); + } + + void + testXChainDeleteDoor() + { + using namespace jtx; + + testcase("Bridge Delete Door Account"); + + auto const acctDelFee{ + drops(XEnv(*this).env_.current()->fees().increment)}; + + // Deleting an account that owns bridge should fail + { + XEnv mcEnv(*this); + + mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1))).close(); + + // We don't allow an account to be deleted if its sequence + // number is within 256 of the current ledger. + for (size_t i = 0; i < 256; ++i) + mcEnv.close(); + + // try to delete mcDoor, send funds to mcAlice + mcEnv.tx( + acctdelete(mcDoor, mcAlice), + fee(acctDelFee), + ter(tecHAS_OBLIGATIONS)); + } + + // Deleting an account that owns a claim id should fail + { + XEnv scEnv(*this, true); + + scEnv.tx(create_bridge(Account::master, jvb)) + .close() + .tx(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)) + .close(); + + // We don't allow an account to be deleted if its sequence + // number is within 256 of the current ledger. + for (size_t i = 0; i < 256; ++i) + scEnv.close(); + + // try to delete scAlice, send funds to scBob + scEnv.tx( + acctdelete(scAlice, scBob), + fee(acctDelFee), + ter(tecHAS_OBLIGATIONS)); + } + } + + void + run() override + { + testXChainBridgeExtraFields(); + testXChainCreateBridge(); + testXChainBridgeCreateConstraints(); + testXChainCreateBridgeMatrix(); + testXChainModifyBridge(); + testXChainCreateClaimID(); + testXChainCommit(); + testXChainAddAttestation(); + testXChainAddClaimNonBatchAttestation(); + testXChainAddAccountCreateNonBatchAttestation(); + testXChainClaim(); + testXChainCreateAccount(); + testFeeDipsIntoReserve(); + testXChainDeleteDoor(); + } +}; + +// ----------------------------------------------------------- +// ----------------------------------------------------------- +struct XChainSim_test : public beast::unit_test::suite, + public jtx::XChainBridgeObjects +{ +private: + static constexpr size_t num_signers = 5; + + // -------------------------------------------------- + enum class WithClaim { no, yes }; + struct Transfer + { + jtx::Account from; + jtx::Account to; + jtx::Account finaldest; + STAmount amt; + bool a2b; // direction of transfer + WithClaim with_claim{WithClaim::no}; + uint32_t claim_id{0}; + std::array attested{}; + }; + + struct AccountCreate + { + jtx::Account from; + jtx::Account to; + STAmount amt; + STAmount reward; + bool a2b; + uint32_t claim_id{0}; + std::array attested{}; + }; + + using ENV = XEnv; + using BridgeID = BridgeDef const*; + + // tracking chain state + // -------------------- + struct AccountStateTrack + { + STAmount startAmount{0}; + STAmount expectedDiff{0}; + + void + init(ENV& env, jtx::Account const& acct) + { + startAmount = env.balance(acct); + expectedDiff = STAmount(0); + } + + bool + verify(ENV& env, jtx::Account const& acct) const + { + STAmount diff{env.balance(acct) - startAmount}; + bool check = diff == expectedDiff; + return check; + } + }; + + // -------------------------------------------------- + struct ChainStateTrack + { + using ClaimVec = jtx::JValueVec; + using CreateClaimVec = jtx::JValueVec; + using CreateClaimMap = std::map; + + ChainStateTrack(ENV& env) + : env(env), tx_fee(env.env_.current()->fees().base) + { + } + + void + sendAttestations(size_t signer_idx, BridgeID bridge, ClaimVec& claims) + { + for (auto const& c : claims) + { + env.tx(c).close(); + spendFee(bridge->signers[signer_idx].account); + } + claims.clear(); + } + + uint32_t + sendCreateAttestations( + size_t signer_idx, + BridgeID bridge, + CreateClaimVec& claims) + { + size_t num_successful = 0; + for (auto const& c : claims) + { + env.tx(c).close(); + if (env.ter() == tesSUCCESS) + { + counters[bridge].signers.push_back(signer_idx); + num_successful++; + } + spendFee(bridge->signers[signer_idx].account); + } + claims.clear(); + return num_successful; + } + + void + sendAttestations() + { + bool callback_called; + + // we have this "do {} while" loop because we want to process + // all the account create which can reach quorum at this time + // stamp. + do + { + callback_called = false; + for (size_t i = 0; i < signers_attns.size(); ++i) + { + for (auto& [bridge, claims] : signers_attns[i]) + { + sendAttestations(i, bridge, claims.xfer_claims); + + auto& c = counters[bridge]; + auto& create_claims = + claims.create_claims[c.claim_count]; + auto num_attns = create_claims.size(); + if (num_attns) + { + c.num_create_attn_sent += sendCreateAttestations( + i, bridge, create_claims); + } + assert(claims.create_claims[c.claim_count].empty()); + } + } + for (auto& [bridge, c] : counters) + { + if (c.num_create_attn_sent >= bridge->quorum) + { + callback_called = true; + c.create_callbacks[c.claim_count](c.signers); + ++c.claim_count; + c.num_create_attn_sent = 0; + c.signers.clear(); + } + } + } while (callback_called); + } + + void + init(jtx::Account const& acct) + { + accounts[acct].init(env, acct); + } + + void + receive( + jtx::Account const& acct, + STAmount amt, + std::uint64_t divisor = 1) + { + if (amt.issue() != xrpIssue()) + return; + auto it = accounts.find(acct); + if (it == accounts.end()) + { + accounts[acct].init(env, acct); + // we just looked up the account, so expectedDiff == 0 + } + else + { + it->second.expectedDiff += + (divisor == 1 ? amt + : divide( + amt, + STAmount(amt.issue(), divisor), + amt.issue())); + } + } + + void + spend(jtx::Account const& acct, STAmount amt, std::uint64_t times = 1) + { + if (amt.issue() != xrpIssue()) + return; + receive( + acct, + times == 1 + ? -amt + : -multiply( + amt, STAmount(amt.issue(), times), amt.issue())); + } + + void + transfer(jtx::Account const& from, jtx::Account const& to, STAmount amt) + { + spend(from, amt); + receive(to, amt); + } + + void + spendFee(jtx::Account const& acct, size_t times = 1) + { + spend(acct, tx_fee, times); + } + + bool + verify() const + { + for (auto const& [acct, state] : accounts) + if (!state.verify(env, acct)) + return false; + return true; + } + + struct BridgeCounters + { + using complete_cb = + std::function const& signers)>; + + uint32_t claim_id{0}; + uint32_t create_count{0}; // for account create. First should be 1 + uint32_t claim_count{ + 0}; // for account create. Increments after quorum for + // current create_count (starts at 1) is reached. + + uint32_t num_create_attn_sent{0}; // for current claim_count + std::vector signers; + std::vector create_callbacks; + }; + + struct Claims + { + ClaimVec xfer_claims; + CreateClaimMap create_claims; + }; + + using SignerAttns = std::unordered_map; + using SignersAttns = std::array; + + ENV& env; + std::map accounts; + SignersAttns signers_attns; + std::map counters; + STAmount tx_fee; + }; + + struct ChainStateTracker + { + ChainStateTracker(ENV& a_env, ENV& b_env) : a_(a_env), b_(b_env) + { + } + + bool + verify() const + { + return a_.verify() && b_.verify(); + } + + void + sendAttestations() + { + a_.sendAttestations(); + b_.sendAttestations(); + } + + void + init(jtx::Account const& acct) + { + a_.init(acct); + b_.init(acct); + } + + ChainStateTrack a_; + ChainStateTrack b_; + }; + + enum SmState { + st_initial, + st_claimid_created, + st_attesting, + st_attested, + st_completed, + st_closed, + }; + + enum Act_Flags { af_a2b = 1 << 0 }; + + // -------------------------------------------------- + template + class SmBase + { + public: + SmBase( + const std::shared_ptr& chainstate, + const BridgeDef& bridge) + : bridge_(bridge), st_(chainstate) + { + } + + ChainStateTrack& + srcState() + { + return static_cast(*this).a2b() ? st_->a_ : st_->b_; + } + + ChainStateTrack& + destState() + { + return static_cast(*this).a2b() ? st_->b_ : st_->a_; + } + + jtx::Account const& + srcDoor() + { + return static_cast(*this).a2b() ? bridge_.doorA : bridge_.doorB; + } + + jtx::Account const& + dstDoor() + { + return static_cast(*this).a2b() ? bridge_.doorB : bridge_.doorA; + } + + protected: + const BridgeDef& bridge_; + std::shared_ptr st_; + }; + + // -------------------------------------------------- + class SmCreateAccount : public SmBase + { + public: + using Base = SmBase; + + SmCreateAccount( + const std::shared_ptr& chainstate, + const BridgeDef& bridge, + AccountCreate create) + : Base(chainstate, bridge) + , sm_state(st_initial) + , cr(std::move(create)) + { + } + + bool + a2b() const + { + return cr.a2b; + } + + uint32_t + issue_account_create() + { + ChainStateTrack& st = srcState(); + jtx::Account const& srcdoor = srcDoor(); + + st.env + .tx(sidechain_xchain_account_create( + cr.from, bridge_.jvb, cr.to, cr.amt, cr.reward)) + .close(); // needed for claim_id sequence to be correct' + st.spendFee(cr.from); + st.transfer(cr.from, srcdoor, cr.amt); + st.transfer(cr.from, srcdoor, cr.reward); + + return ++st.counters[&bridge_].create_count; + } + + void + attest(uint64_t time, uint32_t rnd) + { + ChainStateTrack& st = destState(); + + // check all signers, but start at a random one + size_t i; + for (i = 0; i < num_signers; ++i) + { + size_t signer_idx = (rnd + i) % num_signers; + + if (!(cr.attested[signer_idx])) + { + // enqueue one attestation for this signer + cr.attested[signer_idx] = true; + + st.signers_attns[signer_idx][&bridge_] + .create_claims[cr.claim_id - 1] + .emplace_back(create_account_attestation( + bridge_.signers[signer_idx].account, + bridge_.jvb, + cr.from, + cr.amt, + cr.reward, + bridge_.signers[signer_idx].account, + cr.a2b, + cr.claim_id, + cr.to, + bridge_.signers[signer_idx])); + break; + } + } + + if (i == num_signers) + return; // did not attest + + auto& counters = st.counters[&bridge_]; + if (counters.create_callbacks.size() < cr.claim_id) + counters.create_callbacks.resize(cr.claim_id); + + auto complete_cb = [&](std::vector const& signers) { + auto num_attestors = signers.size(); + st.env.close(); + assert( + num_attestors <= + std::count(cr.attested.begin(), cr.attested.end(), true)); + assert(num_attestors >= bridge_.quorum); + assert(cr.claim_id - 1 == counters.claim_count); + + auto r = cr.reward; + auto reward = divide(r, STAmount(num_attestors), r.issue()); + + for (auto i : signers) + st.receive(bridge_.signers[i].account, reward); + + st.spend(dstDoor(), reward, num_attestors); + st.transfer(dstDoor(), cr.to, cr.amt); + st.env.env_.memoize(cr.to); + sm_state = st_completed; + }; + + counters.create_callbacks[cr.claim_id - 1] = std::move(complete_cb); + } + + SmState + advance(uint64_t time, uint32_t rnd) + { + switch (sm_state) + { + case st_initial: + cr.claim_id = issue_account_create(); + sm_state = st_attesting; + break; + + case st_attesting: + attest(time, rnd); + break; + + default: + assert(0); + break; + + case st_completed: + break; // will get this once + } + return sm_state; + } + + private: + SmState sm_state; + AccountCreate cr; + }; + + // -------------------------------------------------- + class SmTransfer : public SmBase + { + public: + using Base = SmBase; + + SmTransfer( + const std::shared_ptr& chainstate, + const BridgeDef& bridge, + Transfer xfer) + : Base(chainstate, bridge) + , xfer(std::move(xfer)) + , sm_state(st_initial) + { + } + + bool + a2b() const + { + return xfer.a2b; + } + + uint32_t + create_claim_id() + { + ChainStateTrack& st = destState(); + + st.env + .tx(xchain_create_claim_id( + xfer.to, bridge_.jvb, bridge_.reward, xfer.from)) + .close(); // needed for claim_id sequence to be + // correct' + st.spendFee(xfer.to); + return ++st.counters[&bridge_].claim_id; + } + + void + commit() + { + ChainStateTrack& st = srcState(); + jtx::Account const& srcdoor = srcDoor(); + + if (xfer.amt.issue() != xrpIssue()) + { + st.env.tx(pay(srcdoor, xfer.from, xfer.amt)); + st.spendFee(srcdoor); + } + st.env.tx(xchain_commit( + xfer.from, + bridge_.jvb, + xfer.claim_id, + xfer.amt, + xfer.with_claim == WithClaim::yes + ? std::nullopt + : std::optional(xfer.finaldest))); + st.spendFee(xfer.from); + st.transfer(xfer.from, srcdoor, xfer.amt); + } + + void + distribute_reward(ChainStateTrack& st) + { + auto r = bridge_.reward; + auto reward = divide(r, STAmount(bridge_.quorum), r.issue()); + + for (size_t i = 0; i < num_signers; ++i) + { + if (xfer.attested[i]) + st.receive(bridge_.signers[i].account, reward); + } + st.spend(xfer.to, reward, bridge_.quorum); + } + + bool + attest(uint64_t time, uint32_t rnd) + { + ChainStateTrack& st = destState(); + + // check all signers, but start at a random one + for (size_t i = 0; i < num_signers; ++i) + { + size_t signer_idx = (rnd + i) % num_signers; + if (!(xfer.attested[signer_idx])) + { + // enqueue one attestation for this signer + xfer.attested[signer_idx] = true; + + st.signers_attns[signer_idx][&bridge_] + .xfer_claims.emplace_back(claim_attestation( + bridge_.signers[signer_idx].account, + bridge_.jvb, + xfer.from, + xfer.amt, + bridge_.signers[signer_idx].account, + xfer.a2b, + xfer.claim_id, + xfer.with_claim == WithClaim::yes + ? std::nullopt + : std::optional(xfer.finaldest), + bridge_.signers[signer_idx])); + break; + } + } + + // return true if quorum was reached, false otherwise + bool quorum = + std::count(xfer.attested.begin(), xfer.attested.end(), true) >= + bridge_.quorum; + if (quorum && xfer.with_claim == WithClaim::no) + { + distribute_reward(st); + st.transfer(dstDoor(), xfer.finaldest, xfer.amt); + } + return quorum; + } + + void + claim() + { + ChainStateTrack& st = destState(); + st.env.tx(xchain_claim( + xfer.to, bridge_.jvb, xfer.claim_id, xfer.amt, xfer.finaldest)); + distribute_reward(st); + st.transfer(dstDoor(), xfer.finaldest, xfer.amt); + st.spendFee(xfer.to); + } + + SmState + advance(uint64_t time, uint32_t rnd) + { + switch (sm_state) + { + case st_initial: + xfer.claim_id = create_claim_id(); + sm_state = st_claimid_created; + break; + + case st_claimid_created: + commit(); + sm_state = st_attesting; + break; + + case st_attesting: + sm_state = attest(time, rnd) + ? (xfer.with_claim == WithClaim::yes ? st_attested + : st_completed) + : st_attesting; + break; + + case st_attested: + assert(xfer.with_claim == WithClaim::yes); + claim(); + sm_state = st_completed; + break; + + default: + case st_completed: + assert(0); // should have been removed + break; + } + return sm_state; + } + + private: + Transfer xfer; + SmState sm_state; + }; + + // -------------------------------------------------- + using Sm = std::variant; + using SmCont = std::list>; + + SmCont sm_; + + void + xfer( + uint64_t time, + const std::shared_ptr& chainstate, + BridgeDef const& bridge, + Transfer transfer) + { + sm_.emplace_back( + time, SmTransfer(chainstate, bridge, std::move(transfer))); + } + + void + ac(uint64_t time, + const std::shared_ptr& chainstate, + BridgeDef const& bridge, + AccountCreate ac) + { + sm_.emplace_back( + time, SmCreateAccount(chainstate, bridge, std::move(ac))); + } + +public: + void + runSimulation( + std::shared_ptr const& st, + bool verify_balances = true) + { + using namespace jtx; + uint64_t time = 0; + std::mt19937 gen(27); // Standard mersenne_twister_engine + std::uniform_int_distribution distrib(0, 9); + + while (!sm_.empty()) + { + ++time; + for (auto it = sm_.begin(); it != sm_.end();) + { + auto vis = [&](auto& sm) { + uint32_t rnd = distrib(gen); + return sm.advance(time, rnd); + }; + auto& [t, sm] = *it; + if (t <= time && std::visit(vis, sm) == st_completed) + it = sm_.erase(it); + else + ++it; + } + + // send attestations + st->sendAttestations(); + + // make sure all transactions have been applied + st->a_.env.close(); + st->b_.env.close(); + + if (verify_balances) + { + BEAST_EXPECT(st->verify()); + } + } + } + + void + testXChainSimulation() + { + using namespace jtx; + + testcase("Bridge usage simulation"); + + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + auto st = std::make_shared(mcEnv, scEnv); + + // create 10 accounts + door funded on both chains, and store + // in ChainStateTracker the initial amount of these accounts + Account doorXRPLocking, doorUSDLocking, doorUSDIssuing; + + constexpr size_t num_acct = 10; + auto a = [&doorXRPLocking, &doorUSDLocking, &doorUSDIssuing]() { + using namespace std::literals; + std::vector result; + result.reserve(num_acct); + for (int i = 0; i < num_acct; ++i) + result.emplace_back( + "a"s + std::to_string(i), + (i % 2) ? KeyType::ed25519 : KeyType::secp256k1); + result.emplace_back("doorXRPLocking"); + doorXRPLocking = result.back(); + result.emplace_back("doorUSDLocking"); + doorUSDLocking = result.back(); + result.emplace_back("doorUSDIssuing"); + doorUSDIssuing = result.back(); + return result; + }(); + + for (auto& acct : a) + { + STAmount amt{XRP(100000)}; + + mcEnv.fund(amt, acct); + scEnv.fund(amt, acct); + } + Account USDLocking{"USDLocking"}; + IOU usdLocking{USDLocking["USD"]}; + IOU usdIssuing{doorUSDIssuing["USD"]}; + + mcEnv.fund(XRP(100000), USDLocking); + mcEnv.close(); + mcEnv.tx(trust(doorUSDLocking, usdLocking(100000))); + mcEnv.close(); + mcEnv.tx(pay(USDLocking, doorUSDLocking, usdLocking(50000))); + + for (int i = 0; i < a.size(); ++i) + { + auto& acct{a[i]}; + if (i < num_acct) + { + mcEnv.tx(trust(acct, usdLocking(100000))); + scEnv.tx(trust(acct, usdIssuing(100000))); + } + st->init(acct); + } + for (auto& s : signers) + st->init(s.account); + + st->b_.init(Account::master); + + // also create some unfunded accounts + constexpr size_t num_ua = 20; + auto ua = []() { + using namespace std::literals; + std::vector result; + result.reserve(num_ua); + for (int i = 0; i < num_ua; ++i) + result.emplace_back( + "ua"s + std::to_string(i), + (i % 2) ? KeyType::ed25519 : KeyType::secp256k1); + return result; + }(); + + // initialize a bridge from a BridgeDef + auto initBridge = [&mcEnv, &scEnv, &st](BridgeDef& bd) { + bd.initBridge(mcEnv, scEnv); + st->a_.spendFee(bd.doorA, 2); + st->b_.spendFee(bd.doorB, 2); + }; + + // create XRP -> XRP bridge + // ------------------------ + BridgeDef xrp_b{ + doorXRPLocking, + xrpIssue(), + Account::master, + xrpIssue(), + XRP(1), + XRP(20), + quorum, + signers, + Json::nullValue}; + + initBridge(xrp_b); + + // create USD -> USD bridge + // ------------------------ + BridgeDef usd_b{ + doorUSDLocking, + usdLocking, + doorUSDIssuing, + usdIssuing, + XRP(1), + XRP(20), + quorum, + signers, + Json::nullValue}; + + initBridge(usd_b); + + // try a single account create + transfer to validate the simulation + // engine. Do the transfer 8 time steps after the account create, to + // give time enough for ua[0] to be funded now so it can reserve + // the claimID + // ----------------------------------------------------------------- + ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, true}); + xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), true}); + runSimulation(st); + + // try the same thing in the other direction + // ----------------------------------------- + ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, false}); + xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), false}); + runSimulation(st); + + // run multiple XRP transfers + // -------------------------- + xfer(0, st, xrp_b, {a[0], a[0], a[1], XRP(6), true, WithClaim::no}); + xfer(1, st, xrp_b, {a[0], a[0], a[1], XRP(8), false, WithClaim::no}); + xfer(1, st, xrp_b, {a[1], a[1], a[1], XRP(1), true}); + xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(3), false}); + xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(5), false}); + xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(7), false, WithClaim::no}); + xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(9), true}); + runSimulation(st); + + // run one USD transfer + // -------------------- + xfer(0, st, usd_b, {a[0], a[1], a[2], usdLocking(3), true}); + runSimulation(st); + + // run multiple USD transfers + // -------------------------- + xfer(0, st, usd_b, {a[0], a[0], a[1], usdLocking(6), true}); + xfer(1, st, usd_b, {a[0], a[0], a[1], usdIssuing(8), false}); + xfer(1, st, usd_b, {a[1], a[1], a[1], usdLocking(1), true}); + xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(3), false}); + xfer(2, st, usd_b, {a[1], a[1], a[1], usdIssuing(5), false}); + xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(7), false}); + xfer(2, st, usd_b, {a[1], a[1], a[1], usdLocking(9), true}); + runSimulation(st); + + // run mixed transfers + // ------------------- + xfer(0, st, xrp_b, {a[0], a[0], a[0], XRP(1), true}); + xfer(0, st, usd_b, {a[1], a[3], a[3], usdIssuing(3), false}); + xfer(0, st, usd_b, {a[3], a[2], a[1], usdIssuing(5), false}); + + xfer(1, st, xrp_b, {a[0], a[0], a[0], XRP(4), false}); + xfer(1, st, xrp_b, {a[1], a[1], a[0], XRP(8), true}); + xfer(1, st, usd_b, {a[4], a[1], a[1], usdLocking(7), true}); + + xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(7), true}); + xfer(3, st, xrp_b, {a[0], a[4], a[3], XRP(2), false}); + xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(9), true}); + xfer(3, st, usd_b, {a[3], a[1], a[1], usdIssuing(11), false}); + runSimulation(st); + + // run multiple account create to stress attestation batching + // ---------------------------------------------------------- + ac(0, st, xrp_b, {a[0], ua[1], XRP(301), xrp_b.reward, true}); + ac(0, st, xrp_b, {a[1], ua[2], XRP(302), xrp_b.reward, true}); + ac(1, st, xrp_b, {a[0], ua[3], XRP(303), xrp_b.reward, true}); + ac(2, st, xrp_b, {a[1], ua[4], XRP(304), xrp_b.reward, true}); + ac(3, st, xrp_b, {a[0], ua[5], XRP(305), xrp_b.reward, true}); + ac(4, st, xrp_b, {a[1], ua[6], XRP(306), xrp_b.reward, true}); + ac(6, st, xrp_b, {a[0], ua[7], XRP(307), xrp_b.reward, true}); + ac(7, st, xrp_b, {a[2], ua[8], XRP(308), xrp_b.reward, true}); + ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true}); + ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true}); + ac(10, st, xrp_b, {a[0], ua[10], XRP(310), xrp_b.reward, true}); + ac(12, st, xrp_b, {a[0], ua[11], XRP(311), xrp_b.reward, true}); + ac(12, st, xrp_b, {a[3], ua[12], XRP(312), xrp_b.reward, true}); + ac(12, st, xrp_b, {a[4], ua[13], XRP(313), xrp_b.reward, true}); + ac(12, st, xrp_b, {a[3], ua[14], XRP(314), xrp_b.reward, true}); + ac(12, st, xrp_b, {a[6], ua[15], XRP(315), xrp_b.reward, true}); + ac(13, st, xrp_b, {a[7], ua[16], XRP(316), xrp_b.reward, true}); + ac(15, st, xrp_b, {a[3], ua[17], XRP(317), xrp_b.reward, true}); + runSimulation(st, true); // balances verification working now. + } + + void + run() override + { + testXChainSimulation(); + } +}; + +BEAST_DEFINE_TESTSUITE(XChain, app, ripple); +BEAST_DEFINE_TESTSUITE(XChainSim, app, ripple); + +} // namespace ripple::test diff --git a/src/test/basics/ThreadName_test.cpp b/src/test/beast/beast_CurrentThreadName_test.cpp similarity index 81% rename from src/test/basics/ThreadName_test.cpp rename to src/test/beast/beast_CurrentThreadName_test.cpp index 5cc2c1d609f..6e46808f4b2 100644 --- a/src/test/basics/ThreadName_test.cpp +++ b/src/test/beast/beast_CurrentThreadName_test.cpp @@ -17,13 +17,15 @@ */ //============================================================================== -#include +#include #include +#include +#include namespace ripple { namespace test { -class ThreadName_test : public beast::unit_test::suite +class CurrentThreadName_test : public beast::unit_test::suite { private: static void @@ -32,19 +34,26 @@ class ThreadName_test : public beast::unit_test::suite std::atomic* stop, std::atomic* state) { + // Verify that upon creation a thread has no name. + auto const initialThreadName = beast::getCurrentThreadName(); + // Set the new name. - this_thread::set_name(myName); + beast::setCurrentThreadName(myName); // Indicate to caller that the name is set. *state = 1; + // If there is an initial thread name then we failed. + if (!initialThreadName.empty()) + return; + // Wait until all threads have their names. while (!*stop) ; // Make sure the thread name that we set before is still there // (not overwritten by, for instance, another thread). - if (this_thread::get_name() == myName) + if (beast::getCurrentThreadName() == myName) *state = 2; } @@ -77,7 +86,7 @@ class ThreadName_test : public beast::unit_test::suite } }; -BEAST_DEFINE_TESTSUITE(ThreadName, basics, ripple); +BEAST_DEFINE_TESTSUITE(CurrentThreadName, core, beast); } // namespace test } // namespace ripple diff --git a/src/test/jtx/ManualTimeKeeper.h b/src/test/jtx/ManualTimeKeeper.h index 838f2c1398f..f3adb29b5f0 100644 --- a/src/test/jtx/ManualTimeKeeper.h +++ b/src/test/jtx/ManualTimeKeeper.h @@ -21,45 +21,30 @@ #define RIPPLE_TEST_MANUALTIMEKEEPER_H_INCLUDED #include -#include +#include namespace ripple { namespace test { class ManualTimeKeeper : public TimeKeeper { -public: - ManualTimeKeeper(); - - void - run(std::vector const& servers) override; +private: + std::atomic now_{}; - time_point - now() const override; +public: + ManualTimeKeeper() = default; time_point - closeTime() const override; + now() const override + { + return now_.load(); + } void - adjustCloseTime(std::chrono::duration amount) override; - - std::chrono::duration - nowOffset() const override; - - std::chrono::duration - closeOffset() const override; - - void - set(time_point now); - -private: - // Adjust system_clock::time_point for NetClock epoch - static time_point - adjust(std::chrono::system_clock::time_point when); - - std::mutex mutable mutex_; - std::chrono::duration closeOffset_; - time_point now_; + set(time_point now) + { + now_.store(now); + } }; } // namespace test diff --git a/src/test/jtx/attester.h b/src/test/jtx/attester.h new file mode 100644 index 00000000000..7741991b752 --- /dev/null +++ b/src/test/jtx/attester.h @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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_ATTESTER_H_INCLUDED +#define RIPPLE_TEST_JTX_ATTESTER_H_INCLUDED + +#include +#include + +#include +#include + +namespace ripple { + +class PublicKey; +class SecretKey; +class STXChainBridge; +class STAmount; + +namespace test { +namespace jtx { + +Buffer +sign_claim_attestation( + PublicKey const& pk, + SecretKey const& sk, + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst); + +Buffer +sign_create_account_attestation( + PublicKey const& pk, + SecretKey const& sk, + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + STAmount const& rewardAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + AccountID const& dst); +} // namespace jtx +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/jtx/impl/ManualTimeKeeper.cpp b/src/test/jtx/impl/ManualTimeKeeper.cpp deleted file mode 100644 index 72ceaa30bc0..00000000000 --- a/src/test/jtx/impl/ManualTimeKeeper.cpp +++ /dev/null @@ -1,95 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2015 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 { - -using namespace std::chrono_literals; - -ManualTimeKeeper::ManualTimeKeeper() : closeOffset_{}, now_(0s) -{ -} - -void -ManualTimeKeeper::run(std::vector const& servers) -{ -} - -auto -ManualTimeKeeper::now() const -> time_point -{ - std::lock_guard lock(mutex_); - return now_; -} - -auto -ManualTimeKeeper::closeTime() const -> time_point -{ - std::lock_guard lock(mutex_); - return now_ + closeOffset_; -} - -void -ManualTimeKeeper::adjustCloseTime(std::chrono::duration amount) -{ - // Copied from TimeKeeper::adjustCloseTime - using namespace std::chrono; - auto const s = amount.count(); - std::lock_guard lock(mutex_); - // Take large offsets, ignore small offsets, - // push the close time towards our wall time. - if (s > 1) - closeOffset_ += seconds((s + 3) / 4); - else if (s < -1) - closeOffset_ += seconds((s - 3) / 4); - else - closeOffset_ = (closeOffset_ * 3) / 4; -} - -std::chrono::duration -ManualTimeKeeper::nowOffset() const -{ - return {}; -} - -std::chrono::duration -ManualTimeKeeper::closeOffset() const -{ - std::lock_guard lock(mutex_); - return closeOffset_; -} - -void -ManualTimeKeeper::set(time_point now) -{ - std::lock_guard lock(mutex_); - now_ = now; -} - -auto -ManualTimeKeeper::adjust(std::chrono::system_clock::time_point when) - -> time_point -{ - return time_point(std::chrono::duration_cast( - when.time_since_epoch() - days(10957))); -} -} // namespace test -} // namespace ripple diff --git a/src/test/jtx/impl/attester.cpp b/src/test/jtx/impl/attester.cpp new file mode 100644 index 00000000000..dd00f536af8 --- /dev/null +++ b/src/test/jtx/impl/attester.cpp @@ -0,0 +1,82 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 { +namespace test { +namespace jtx { + +Buffer +sign_claim_attestation( + PublicKey const& pk, + SecretKey const& sk, + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst) +{ + auto const toSign = Attestations::AttestationClaim::message( + bridge, + sendingAccount, + sendingAmount, + rewardAccount, + wasLockingChainSend, + claimID, + dst); + return sign(pk, sk, makeSlice(toSign)); +} + +Buffer +sign_create_account_attestation( + PublicKey const& pk, + SecretKey const& sk, + STXChainBridge const& bridge, + AccountID const& sendingAccount, + STAmount const& sendingAmount, + STAmount const& rewardAmount, + AccountID const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + AccountID const& dst) +{ + auto const toSign = Attestations::AttestationCreateAccount::message( + bridge, + sendingAccount, + sendingAmount, + rewardAmount, + rewardAccount, + wasLockingChainSend, + createCount, + dst); + return sign(pk, sk, makeSlice(toSign)); +} + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp new file mode 100644 index 00000000000..0b81ccdcd91 --- /dev/null +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -0,0 +1,516 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 + +namespace ripple { +namespace test { +namespace jtx { + +// use this for creating a bridge for a transaction +Json::Value +bridge( + Account const& lockingChainDoor, + Issue const& lockingChainIssue, + Account const& issuingChainDoor, + Issue const& issuingChainIssue) +{ + Json::Value jv; + jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human(); + jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue); + jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human(); + jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue); + return jv; +} + +// use this for creating a bridge for a rpc query +Json::Value +bridge_rpc( + Account const& lockingChainDoor, + Issue const& lockingChainIssue, + Account const& issuingChainDoor, + Issue const& issuingChainIssue) +{ + Json::Value jv; + jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human(); + jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue); + jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human(); + jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue); + return jv; +} + +Json::Value +bridge_create( + Account const& acc, + Json::Value const& bridge, + STAmount const& reward, + std::optional const& minAccountCreate) +{ + Json::Value jv; + + jv[jss::Account] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + jv[sfSignatureReward.getJsonName()] = reward.getJson(JsonOptions::none); + if (minAccountCreate) + jv[sfMinAccountCreateAmount.getJsonName()] = + minAccountCreate->getJson(JsonOptions::none); + + jv[jss::TransactionType] = jss::XChainCreateBridge; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +bridge_modify( + Account const& acc, + Json::Value const& bridge, + std::optional const& reward, + std::optional const& minAccountCreate) +{ + Json::Value jv; + + jv[jss::Account] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + if (reward) + jv[sfSignatureReward.getJsonName()] = + reward->getJson(JsonOptions::none); + if (minAccountCreate) + jv[sfMinAccountCreateAmount.getJsonName()] = + minAccountCreate->getJson(JsonOptions::none); + + jv[jss::TransactionType] = jss::XChainModifyBridge; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +xchain_create_claim_id( + Account const& acc, + Json::Value const& bridge, + STAmount const& reward, + Account const& otherChainSource) +{ + Json::Value jv; + + jv[jss::Account] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + jv[sfSignatureReward.getJsonName()] = reward.getJson(JsonOptions::none); + jv[sfOtherChainSource.getJsonName()] = otherChainSource.human(); + + jv[jss::TransactionType] = jss::XChainCreateClaimID; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +xchain_commit( + Account const& acc, + Json::Value const& bridge, + std::uint32_t claimID, + AnyAmount const& amt, + std::optional const& dst) +{ + Json::Value jv; + + jv[jss::Account] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + jv[sfXChainClaimID.getJsonName()] = claimID; + jv[jss::Amount] = amt.value.getJson(JsonOptions::none); + if (dst) + jv[sfOtherChainDestination.getJsonName()] = dst->human(); + + jv[jss::TransactionType] = jss::XChainCommit; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +xchain_claim( + Account const& acc, + Json::Value const& bridge, + std::uint32_t claimID, + AnyAmount const& amt, + Account const& dst) +{ + Json::Value jv; + + jv[sfAccount.getJsonName()] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + jv[sfXChainClaimID.getJsonName()] = claimID; + jv[sfDestination.getJsonName()] = dst.human(); + jv[sfAmount.getJsonName()] = amt.value.getJson(JsonOptions::none); + + jv[jss::TransactionType] = jss::XChainClaim; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +sidechain_xchain_account_create( + Account const& acc, + Json::Value const& bridge, + Account const& dst, + AnyAmount const& amt, + AnyAmount const& reward) +{ + Json::Value jv; + + jv[sfAccount.getJsonName()] = acc.human(); + jv[sfXChainBridge.getJsonName()] = bridge; + jv[sfDestination.getJsonName()] = dst.human(); + jv[sfAmount.getJsonName()] = amt.value.getJson(JsonOptions::none); + jv[sfSignatureReward.getJsonName()] = + reward.value.getJson(JsonOptions::none); + + jv[jss::TransactionType] = jss::XChainAccountCreateCommit; + jv[jss::Flags] = tfUniversal; + return jv; +} + +Json::Value +claim_attestation( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::Account const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst, + jtx::signer const& signer) +{ + STXChainBridge const stBridge(jvBridge); + + auto const& pk = signer.account.pk(); + auto const& sk = signer.account.sk(); + auto const sig = sign_claim_attestation( + pk, + sk, + stBridge, + sendingAccount, + sendingAmount.value, + rewardAccount, + wasLockingChainSend, + claimID, + dst); + + Json::Value result; + + result[sfAccount.getJsonName()] = submittingAccount.human(); + result[sfXChainBridge.getJsonName()] = jvBridge; + + result[sfAttestationSignerAccount.getJsonName()] = signer.account.human(); + result[sfPublicKey.getJsonName()] = strHex(pk.slice()); + result[sfSignature.getJsonName()] = strHex(sig); + result[sfOtherChainSource.getJsonName()] = toBase58(sendingAccount); + result[sfAmount.getJsonName()] = + sendingAmount.value.getJson(JsonOptions::none); + result[sfAttestationRewardAccount.getJsonName()] = toBase58(rewardAccount); + result[sfWasLockingChainSend.getJsonName()] = wasLockingChainSend ? 1 : 0; + + result[sfXChainClaimID.getJsonName()] = + STUInt64{claimID}.getJson(JsonOptions::none); + if (dst) + result[sfDestination.getJsonName()] = toBase58(*dst); + + result[jss::TransactionType] = jss::XChainAddClaimAttestation; + result[jss::Flags] = tfUniversal; + + return result; +} + +Json::Value +create_account_attestation( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::AnyAmount const& rewardAmount, + jtx::Account const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + jtx::Account const& dst, + jtx::signer const& signer) +{ + STXChainBridge const stBridge(jvBridge); + + auto const& pk = signer.account.pk(); + auto const& sk = signer.account.sk(); + auto const sig = jtx::sign_create_account_attestation( + pk, + sk, + stBridge, + sendingAccount, + sendingAmount.value, + rewardAmount.value, + rewardAccount, + wasLockingChainSend, + createCount, + dst); + + Json::Value result; + + result[sfAccount.getJsonName()] = submittingAccount.human(); + result[sfXChainBridge.getJsonName()] = jvBridge; + + result[sfAttestationSignerAccount.getJsonName()] = signer.account.human(); + result[sfPublicKey.getJsonName()] = strHex(pk.slice()); + result[sfSignature.getJsonName()] = strHex(sig); + result[sfOtherChainSource.getJsonName()] = toBase58(sendingAccount); + result[sfAmount.getJsonName()] = + sendingAmount.value.getJson(JsonOptions::none); + result[sfAttestationRewardAccount.getJsonName()] = toBase58(rewardAccount); + result[sfWasLockingChainSend.getJsonName()] = wasLockingChainSend ? 1 : 0; + + result[sfXChainAccountCreateCount.getJsonName()] = + STUInt64{createCount}.getJson(JsonOptions::none); + result[sfDestination.getJsonName()] = toBase58(dst); + result[sfSignatureReward.getJsonName()] = + rewardAmount.value.getJson(JsonOptions::none); + + result[jss::TransactionType] = jss::XChainAddAccountCreateAttestation; + result[jss::Flags] = tfUniversal; + + return result; +} + +JValueVec +claim_attestations( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + std::vector const& rewardAccounts, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst, + std::vector const& signers, + std::size_t const numAtts, + std::size_t const fromIdx) +{ + assert(fromIdx + numAtts <= rewardAccounts.size()); + assert(fromIdx + numAtts <= signers.size()); + JValueVec vec; + vec.reserve(numAtts); + for (auto i = fromIdx; i < fromIdx + numAtts; ++i) + vec.emplace_back(claim_attestation( + submittingAccount, + jvBridge, + sendingAccount, + sendingAmount, + rewardAccounts[i], + wasLockingChainSend, + claimID, + dst, + signers[i])); + return vec; +} + +JValueVec +create_account_attestations( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::AnyAmount const& rewardAmount, + std::vector const& rewardAccounts, + bool wasLockingChainSend, + std::uint64_t createCount, + jtx::Account const& dst, + std::vector const& signers, + std::size_t const numAtts, + std::size_t const fromIdx) +{ + assert(fromIdx + numAtts <= rewardAccounts.size()); + assert(fromIdx + numAtts <= signers.size()); + JValueVec vec; + vec.reserve(numAtts); + for (auto i = fromIdx; i < fromIdx + numAtts; ++i) + vec.emplace_back(create_account_attestation( + submittingAccount, + jvBridge, + sendingAccount, + sendingAmount, + rewardAmount, + rewardAccounts[i], + wasLockingChainSend, + createCount, + dst, + signers[i])); + return vec; +} + +XChainBridgeObjects::XChainBridgeObjects() + : mcDoor("mcDoor") + , mcAlice("mcAlice") + , mcBob("mcBob") + , mcCarol("mcCarol") + , mcGw("mcGw") + , scDoor("scDoor") + , scAlice("scAlice") + , scBob("scBob") + , scCarol("scCarol") + , scGw("scGw") + , scAttester("scAttester") + , scReward("scReward") + , mcuDoor("mcuDoor") + , mcuAlice("mcuAlice") + , mcuBob("mcuBob") + , mcuCarol("mcuCarol") + , mcuGw("mcuGw") + , scuDoor("scuDoor") + , scuAlice("scuAlice") + , scuBob("scuBob") + , scuCarol("scuCarol") + , scuGw("scuGw") + , mcUSD(mcGw["USD"]) + , scUSD(scGw["USD"]) + , jvXRPBridgeRPC( + bridge_rpc(mcDoor, xrpIssue(), Account::master, xrpIssue())) + , jvb(bridge(mcDoor, xrpIssue(), Account::master, xrpIssue())) + , jvub(bridge(mcuDoor, xrpIssue(), Account::master, xrpIssue())) + , features(supported_amendments() | FeatureBitset{featureXChainBridge}) + , signers([] { + constexpr int numSigners = UT_XCHAIN_DEFAULT_NUM_SIGNERS; + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account( + "signer_"s + std::to_string(i), + (i % 2) ? KeyType::ed25519 : KeyType::secp256k1); + result.emplace_back(a); + } + return result; + }()) + , alt_signers([] { + constexpr int numSigners = UT_XCHAIN_DEFAULT_NUM_SIGNERS; + std::vector result; + result.reserve(numSigners); + for (int i = 0; i < numSigners; ++i) + { + using namespace std::literals; + auto const a = Account( + "alt_signer_"s + std::to_string(i), + (i % 2) ? KeyType::ed25519 : KeyType::secp256k1); + result.emplace_back(a); + } + return result; + }()) + , payee([&] { + std::vector r; + r.reserve(signers.size()); + for (int i = 0, e = signers.size(); i != e; ++i) + { + r.push_back(scReward); + } + return r; + }()) + , payees([&] { + std::vector r; + r.reserve(signers.size()); + for (int i = 0, e = signers.size(); i != e; ++i) + { + using namespace std::literals; + auto const a = Account("reward_"s + std::to_string(i)); + r.push_back(a); + } + return r; + }()) + , quorum(UT_XCHAIN_DEFAULT_QUORUM) + , reward(XRP(1)) + , split_reward_quorum( + divide(reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), reward.issue())) + , split_reward_everyone(divide( + reward, + STAmount(UT_XCHAIN_DEFAULT_NUM_SIGNERS), + reward.issue())) + , tiny_reward(drops(37)) + , tiny_reward_split((divide( + tiny_reward, + STAmount(UT_XCHAIN_DEFAULT_QUORUM), + tiny_reward.issue()))) + , tiny_reward_remainder( + tiny_reward - + multiply( + tiny_reward_split, + STAmount(UT_XCHAIN_DEFAULT_QUORUM), + tiny_reward.issue())) + , one_xrp(XRP(1)) + , xrp_dust(divide(one_xrp, STAmount(10000), one_xrp.issue())) +{ +} + +void +XChainBridgeObjects::createMcBridgeObjects(Env& mcEnv) +{ + STAmount xrp_funds{XRP(10000)}; + mcEnv.fund(xrp_funds, mcDoor, mcAlice, mcBob, mcCarol, mcGw); + + // Signer's list must match the attestation signers + mcEnv(jtx::signers(mcDoor, signers.size(), signers)); + + // create XRP bridges in both direction + auto const reward = XRP(1); + STAmount const minCreate = XRP(20); + + mcEnv(bridge_create(mcDoor, jvb, reward, minCreate)); + mcEnv.close(); +} + +void +XChainBridgeObjects::createScBridgeObjects(Env& scEnv) +{ + STAmount xrp_funds{XRP(10000)}; + scEnv.fund( + xrp_funds, scDoor, scAlice, scBob, scCarol, scGw, scAttester, scReward); + + // Signer's list must match the attestation signers + scEnv(jtx::signers(Account::master, signers.size(), signers)); + + // create XRP bridges in both direction + auto const reward = XRP(1); + STAmount const minCreate = XRP(20); + + scEnv(bridge_create(Account::master, jvb, reward, minCreate)); + scEnv.close(); +} + +void +XChainBridgeObjects::createBridgeObjects(Env& mcEnv, Env& scEnv) +{ + createMcBridgeObjects(mcEnv); + createScBridgeObjects(scEnv); +} +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/xchain_bridge.h b/src/test/jtx/xchain_bridge.h new file mode 100644 index 00000000000..8a398bc6f20 --- /dev/null +++ b/src/test/jtx/xchain_bridge.h @@ -0,0 +1,260 @@ +//------------------------------------------------------------------------------ +/* + 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_TEST_JTX_XCHAINBRIDGE_H_INCLUDED +#define RIPPLE_TEST_JTX_XCHAINBRIDGE_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +using JValueVec = std::vector; + +constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS = 5; +constexpr std::size_t UT_XCHAIN_DEFAULT_QUORUM = 4; + +Json::Value +bridge( + Account const& lockingChainDoor, + Issue const& lockingChainIssue, + Account const& issuingChainDoor, + Issue const& issuingChainIssue); + +Json::Value +bridge_create( + Account const& acc, + Json::Value const& bridge, + STAmount const& reward, + std::optional const& minAccountCreate = std::nullopt); + +Json::Value +bridge_modify( + Account const& acc, + Json::Value const& bridge, + std::optional const& reward, + std::optional const& minAccountCreate = std::nullopt); + +Json::Value +xchain_create_claim_id( + Account const& acc, + Json::Value const& bridge, + STAmount const& reward, + Account const& otherChainSource); + +Json::Value +xchain_commit( + Account const& acc, + Json::Value const& bridge, + std::uint32_t claimID, + AnyAmount const& amt, + std::optional const& dst = std::nullopt); + +Json::Value +xchain_claim( + Account const& acc, + Json::Value const& bridge, + std::uint32_t claimID, + AnyAmount const& amt, + Account const& dst); + +Json::Value +sidechain_xchain_account_create( + Account const& acc, + Json::Value const& bridge, + Account const& dst, + AnyAmount const& amt, + AnyAmount const& xChainFee); + +Json::Value +sidechain_xchain_account_claim( + Account const& acc, + Json::Value const& bridge, + Account const& dst, + AnyAmount const& amt); + +Json::Value +claim_attestation( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::Account const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst, + jtx::signer const& signer); + +Json::Value +create_account_attestation( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::AnyAmount const& rewardAmount, + jtx::Account const& rewardAccount, + bool wasLockingChainSend, + std::uint64_t createCount, + jtx::Account const& dst, + jtx::signer const& signer); + +JValueVec +claim_attestations( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + std::vector const& rewardAccounts, + bool wasLockingChainSend, + std::uint64_t claimID, + std::optional const& dst, + std::vector const& signers, + std::size_t const numAtts = UT_XCHAIN_DEFAULT_QUORUM, + std::size_t const fromIdx = 0); + +JValueVec +create_account_attestations( + jtx::Account const& submittingAccount, + Json::Value const& jvBridge, + jtx::Account const& sendingAccount, + jtx::AnyAmount const& sendingAmount, + jtx::AnyAmount const& rewardAmount, + std::vector const& rewardAccounts, + bool wasLockingChainSend, + std::uint64_t createCount, + jtx::Account const& dst, + std::vector const& signers, + std::size_t const numAtts = UT_XCHAIN_DEFAULT_QUORUM, + std::size_t const fromIdx = 0); + +struct XChainBridgeObjects +{ + // funded accounts + Account const mcDoor; + Account const mcAlice; + Account const mcBob; + Account const mcCarol; + Account const mcGw; + Account const scDoor; + Account const scAlice; + Account const scBob; + Account const scCarol; + Account const scGw; + Account const scAttester; + Account const scReward; + + // unfunded accounts + Account const mcuDoor; + Account const mcuAlice; + Account const mcuBob; + Account const mcuCarol; + Account const mcuGw; + Account const scuDoor; + Account const scuAlice; + Account const scuBob; + Account const scuCarol; + Account const scuGw; + + IOU const mcUSD; + IOU const scUSD; + + Json::Value const jvXRPBridgeRPC; + Json::Value jvb; // standard xrp bridge def for tx + Json::Value jvub; // standard xrp bridge def for tx, unfunded accounts + + FeatureBitset const features; + std::vector const signers; + std::vector const alt_signers; + std::vector const payee; + std::vector const payees; + std::uint32_t const quorum; + + STAmount const reward; // 1 xrp + STAmount const split_reward_quorum; // 250,000 drops + STAmount const split_reward_everyone; // 200,000 drops + + const STAmount tiny_reward; // 37 drops + const STAmount tiny_reward_split; // 9 drops + const STAmount tiny_reward_remainder; // 1 drops + + const STAmount one_xrp; + const STAmount xrp_dust; + + static constexpr int drop_per_xrp = 1000000; + + XChainBridgeObjects(); + + void + createMcBridgeObjects(Env& mcEnv); + + void + createScBridgeObjects(Env& scEnv); + + void + createBridgeObjects(Env& mcEnv, Env& scEnv); + + JValueVec + att_create_acct_vec( + std::uint64_t createCount, + jtx::AnyAmount const& amt, + jtx::Account const& dst, + std::size_t const numAtts, + std::size_t const fromIdx = 0) + { + return create_account_attestations( + scAttester, + jvb, + mcCarol, + amt, + reward, + payees, + true, + createCount, + dst, + signers, + numAtts, + fromIdx); + } + + Json::Value + create_bridge( + Account const& acc, + Json::Value const& bridge = Json::nullValue, + STAmount const& _reward = XRP(1), + std::optional const& minAccountCreate = std::nullopt) + { + return bridge_create( + acc, + bridge == Json::nullValue ? jvb : bridge, + _reward, + minAccountCreate); + } +}; + +} // namespace jtx +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/overlay/ProtocolVersion_test.cpp b/src/test/overlay/ProtocolVersion_test.cpp index a5a26fe74ec..3bfba5099f4 100644 --- a/src/test/overlay/ProtocolVersion_test.cpp +++ b/src/test/overlay/ProtocolVersion_test.cpp @@ -88,7 +88,7 @@ class ProtocolVersion_test : public beast::unit_test::suite BEAST_EXPECT( negotiateProtocolVersion( "RTXP/1.2, XRPL/2.2, XRPL/2.3, XRPL/999.999") == - make_protocol(2, 2)); + make_protocol(2, 3)); BEAST_EXPECT( negotiateProtocolVersion("XRPL/999.999, WebSocket/1.0") == std::nullopt); diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index 3b61b2b3a09..81f21258e30 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -227,8 +227,7 @@ class compression_test : public beast::unit_test::suite auto transaction = std::make_shared(); transaction->set_rawtransaction(usdTxBlob); transaction->set_status(protocol::tsNEW); - auto tk = make_TimeKeeper(logs.journal("TimeKeeper")); - transaction->set_receivetimestamp(tk->now().time_since_epoch().count()); + transaction->set_receivetimestamp(rand_int()); transaction->set_deferred(true); return transaction; @@ -263,19 +262,23 @@ class compression_test : public beast::unit_test::suite ledgerData->set_error(protocol::TMReplyError::reNO_LEDGER); ledgerData->mutable_nodes()->Reserve(n); uint256 parentHash(0); + + NetClock::duration const resolution{10}; + NetClock::time_point ct{resolution}; + for (int i = 0; i < n; i++) { LedgerInfo info; - auto tk = make_TimeKeeper(logs.journal("TimeKeeper")); info.seq = i; - info.parentCloseTime = tk->now(); + info.parentCloseTime = ct; info.hash = ripple::sha512Half(i); info.txHash = ripple::sha512Half(i + 1); info.accountHash = ripple::sha512Half(i + 2); info.parentHash = parentHash; info.drops = XRPAmount(10); - info.closeTimeResolution = tk->now().time_since_epoch(); - info.closeTime = tk->now(); + info.closeTimeResolution = resolution; + info.closeTime = ct; + ct += resolution; parentHash = ledgerHash(info); Serializer nData; ripple::addRaw(info, nData); @@ -341,7 +344,7 @@ class compression_test : public beast::unit_test::suite Serializer s1; st.add(s1); list->set_signature(s1.data(), s1.size()); - list->set_blob(strHex(s.getString())); + list->set_blob(strHex(s.slice())); return list; } @@ -375,7 +378,7 @@ class compression_test : public beast::unit_test::suite st.add(s1); auto& blob = *list->add_blobs(); blob.set_signature(s1.data(), s1.size()); - blob.set_blob(strHex(s.getString())); + blob.set_blob(strHex(s.slice())); return list; } diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index cfcf6642d94..434b4100852 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include #include +#include #include #include @@ -630,7 +630,7 @@ class short_read_test : public beast::unit_test::suite short_read_test() : work_(io_context_.get_executor()) , thread_(std::thread([this]() { - this_thread::set_name("io_context"); + beast::setCurrentThreadName("io_context"); this->io_context_.run(); })) , context_(make_SSLContext("")) diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 90d4e54684f..e38c7c029b7 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -550,7 +551,9 @@ class AccountObjects_test : public beast::unit_test::suite Account const gw{"gateway"}; auto const USD = gw["USD"]; - Env env(*this); + auto const features = + supported_amendments() | FeatureBitset{featureXChainBridge}; + Env env(*this, features); // Make a lambda we can use to get "account_objects" easily. auto acct_objs = [&env]( @@ -676,6 +679,142 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human()); BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000); } + { + // Create a bridge + test::jtx::XChainBridgeObjects x; + Env scEnv(*this, envconfig(port_increment, 3), features); + x.createScBridgeObjects(scEnv); + + auto scenv_acct_objs = [&](Account const& acct, char const* type) { + Json::Value params; + params[jss::account] = acct.human(); + params[jss::type] = type; + params[jss::ledger_index] = "validated"; + return scEnv.rpc("json", "account_objects", to_string(params)); + }; + + Json::Value const resp = + scenv_acct_objs(Account::master, jss::bridge); + + BEAST_EXPECT(acct_objs_is_size(resp, 1)); + auto const& acct_bridge = + resp[jss::result][jss::account_objects][0u]; + BEAST_EXPECT( + acct_bridge[sfAccount.jsonName] == Account::master.human()); + BEAST_EXPECT( + acct_bridge[sfLedgerEntryType.getJsonName()] == "Bridge"); + BEAST_EXPECT( + acct_bridge[sfXChainClaimID.getJsonName()].asUInt() == 0); + BEAST_EXPECT( + acct_bridge[sfXChainAccountClaimCount.getJsonName()].asUInt() == + 0); + BEAST_EXPECT( + acct_bridge[sfXChainAccountCreateCount.getJsonName()] + .asUInt() == 0); + BEAST_EXPECT( + acct_bridge[sfMinAccountCreateAmount.getJsonName()].asUInt() == + 20000000); + BEAST_EXPECT( + acct_bridge[sfSignatureReward.getJsonName()].asUInt() == + 1000000); + BEAST_EXPECT(acct_bridge[sfXChainBridge.getJsonName()] == x.jvb); + } + { + // Alice and Bob create a xchain sequence number that we can look + // for in the ledger. + test::jtx::XChainBridgeObjects x; + Env scEnv(*this, envconfig(port_increment, 3), features); + x.createScBridgeObjects(scEnv); + + scEnv( + xchain_create_claim_id(x.scAlice, x.jvb, x.reward, x.mcAlice)); + scEnv.close(); + 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) { + Json::Value params; + params[jss::account] = acct.human(); + params[jss::type] = type; + params[jss::ledger_index] = "validated"; + return scEnv.rpc("json", "account_objects", to_string(params)); + }; + + { + // 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)); + + auto const& xchain_seq = + resp[jss::result][jss::account_objects][0u]; + BEAST_EXPECT( + xchain_seq[sfAccount.jsonName] == x.scAlice.human()); + BEAST_EXPECT( + xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 1); + } + { + // 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)); + + auto const& xchain_seq = + resp[jss::result][jss::account_objects][0u]; + BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scBob.human()); + BEAST_EXPECT( + xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 2); + } + } + { + test::jtx::XChainBridgeObjects x; + Env scEnv(*this, envconfig(port_increment, 3), features); + x.createScBridgeObjects(scEnv); + auto const amt = XRP(1000); + + // send first batch of account create attestations, so the + // xchain_create_account_claim_id should be present on the door + // account (Account::master) to collect the signatures until a + // quorum is reached + scEnv(test::jtx::create_account_attestation( + x.scAttester, + x.jvb, + x.mcCarol, + amt, + x.reward, + x.payees[0], + true, + 1, + x.scuAlice, + x.signers[0])); + scEnv.close(); + + auto scenv_acct_objs = [&](Account const& acct, char const* type) { + Json::Value params; + params[jss::account] = acct.human(); + params[jss::type] = type; + params[jss::ledger_index] = "validated"; + return scEnv.rpc("json", "account_objects", to_string(params)); + }; + + { + // Find the xchain_create_account_claim_id + Json::Value const resp = scenv_acct_objs( + Account::master, jss::xchain_owned_create_account_claim_id); + BEAST_EXPECT(acct_objs_is_size(resp, 1)); + + auto const& xchain_create_account_claim_id = + resp[jss::result][jss::account_objects][0u]; + BEAST_EXPECT( + xchain_create_account_claim_id[sfAccount.jsonName] == + Account::master.human()); + BEAST_EXPECT( + xchain_create_account_claim_id[sfXChainAccountCreateCount + .getJsonName()] + .asUInt() == 1); + } + } + // gw creates an offer that we can look for in the ledger. env(offer(gw, USD(7), XRP(14))); env.close(); @@ -690,7 +829,8 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7); } { - // Create a payment channel from qw to alice that we can look for. + // Create a payment channel from qw to alice that we can look + // for. Json::Value jvPayChan; jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate; jvPayChan[jss::Flags] = tfUniversal; @@ -715,7 +855,7 @@ class AccountObjects_test : public beast::unit_test::suite payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60); } // Make gw multisigning by adding a signerList. - env(signers(gw, 6, {{alice, 7}})); + env(jtx::signers(gw, 6, {{alice, 7}})); env.close(); { // Find the signer list. diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 05baf869eee..24f6737917b 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -144,176 +144,192 @@ class AccountTx_test : public beast::unit_test::suite Json::Value jParms; jParms[jss::api_version] = apiVersion; - if (apiVersion < 2) - { - BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), - rpcINVALID_PARAMS)); + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(jParms)), + rpcINVALID_PARAMS)); - jParms[jss::account] = "0xDEADBEEF"; + jParms[jss::account] = "0xDEADBEEF"; - BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), - rpcACT_MALFORMED)); + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(jParms)), + rpcACT_MALFORMED)); - jParms[jss::account] = A1.human(); - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(jParms)))); + jParms[jss::account] = A1.human(); + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(jParms)))); - // Ledger min/max index - { - Json::Value p{jParms}; - p[jss::ledger_index_min] = -1; - p[jss::ledger_index_max] = -1; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); - - p[jss::ledger_index_min] = 0; - p[jss::ledger_index_max] = 100; + // Ledger min/max index + { + Json::Value p{jParms}; + p[jss::ledger_index_min] = -1; + p[jss::ledger_index_max] = -1; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); + + p[jss::ledger_index_min] = 0; + p[jss::ledger_index_max] = 100; + if (apiVersion < 2u) BEAST_EXPECT( hasTxs(env.rpc("json", "account_tx", to_string(p)))); + else + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcLGR_IDX_MALFORMED)); - p[jss::ledger_index_min] = 1; - p[jss::ledger_index_max] = 2; + p[jss::ledger_index_min] = 1; + p[jss::ledger_index_max] = 2; + if (apiVersion < 2u) BEAST_EXPECT( noTxs(env.rpc("json", "account_tx", to_string(p)))); - - p[jss::ledger_index_min] = 2; - p[jss::ledger_index_max] = 1; + else BEAST_EXPECT(isErr( env.rpc("json", "account_tx", to_string(p)), - (RPC::apiMaximumSupportedVersion == 1 - ? rpcLGR_IDXS_INVALID - : rpcINVALID_LGR_RANGE))); - } + rpcLGR_IDX_MALFORMED)); - // Ledger index min only - { - Json::Value p{jParms}; - p[jss::ledger_index_min] = -1; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + p[jss::ledger_index_min] = 2; + p[jss::ledger_index_max] = 1; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + (apiVersion == 1 ? rpcLGR_IDXS_INVALID + : rpcINVALID_LGR_RANGE))); + } + // Ledger index min only + { + Json::Value p{jParms}; + p[jss::ledger_index_min] = -1; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); - p[jss::ledger_index_min] = 1; + p[jss::ledger_index_min] = 1; + if (apiVersion < 2u) BEAST_EXPECT( hasTxs(env.rpc("json", "account_tx", to_string(p)))); - - p[jss::ledger_index_min] = env.current()->info().seq; + else BEAST_EXPECT(isErr( env.rpc("json", "account_tx", to_string(p)), - (RPC::apiMaximumSupportedVersion == 1 - ? rpcLGR_IDXS_INVALID - : rpcINVALID_LGR_RANGE))); - } + rpcLGR_IDX_MALFORMED)); - // Ledger index max only - { - Json::Value p{jParms}; - p[jss::ledger_index_max] = -1; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + p[jss::ledger_index_min] = env.current()->info().seq; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + (apiVersion == 1 ? rpcLGR_IDXS_INVALID + : rpcINVALID_LGR_RANGE))); + } - p[jss::ledger_index_max] = env.current()->info().seq; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + // Ledger index max only + { + Json::Value p{jParms}; + p[jss::ledger_index_max] = -1; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); - p[jss::ledger_index_max] = 3; + p[jss::ledger_index_max] = env.current()->info().seq; + if (apiVersion < 2u) BEAST_EXPECT( hasTxs(env.rpc("json", "account_tx", to_string(p)))); + else + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcLGR_IDX_MALFORMED)); - p[jss::ledger_index_max] = env.closed()->info().seq; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + p[jss::ledger_index_max] = 3; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); - p[jss::ledger_index_max] = env.closed()->info().seq - 1; - BEAST_EXPECT( - noTxs(env.rpc("json", "account_tx", to_string(p)))); - } + p[jss::ledger_index_max] = env.closed()->info().seq; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); - // Ledger Sequence - { - Json::Value p{jParms}; + p[jss::ledger_index_max] = env.closed()->info().seq - 1; + BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p)))); + } - p[jss::ledger_index] = env.closed()->info().seq; - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + // Ledger Sequence + { + Json::Value p{jParms}; - p[jss::ledger_index] = env.closed()->info().seq - 1; - BEAST_EXPECT( - noTxs(env.rpc("json", "account_tx", to_string(p)))); + p[jss::ledger_index] = env.closed()->info().seq; + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); - p[jss::ledger_index] = env.current()->info().seq; - BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(p)), - rpcLGR_NOT_VALIDATED)); + p[jss::ledger_index] = env.closed()->info().seq - 1; + BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p)))); - p[jss::ledger_index] = env.current()->info().seq + 1; - BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(p)), - rpcLGR_NOT_FOUND)); - } + p[jss::ledger_index] = env.current()->info().seq; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcLGR_NOT_VALIDATED)); - // Ledger Hash - { - Json::Value p{jParms}; + p[jss::ledger_index] = env.current()->info().seq + 1; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND)); + } - p[jss::ledger_hash] = to_string(env.closed()->info().hash); - BEAST_EXPECT( - hasTxs(env.rpc("json", "account_tx", to_string(p)))); + // Ledger Hash + { + Json::Value p{jParms}; - p[jss::ledger_hash] = - to_string(env.closed()->info().parentHash); - BEAST_EXPECT( - noTxs(env.rpc("json", "account_tx", to_string(p)))); - } + p[jss::ledger_hash] = to_string(env.closed()->info().hash); + BEAST_EXPECT(hasTxs(env.rpc("json", "account_tx", to_string(p)))); + + p[jss::ledger_hash] = to_string(env.closed()->info().parentHash); + BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p)))); } - else + + // Ledger index max/min/index all specified + // ERRORS out with invalid Parenthesis { - // Ledger index max/min/index all specified - // ERRORS out with invalid Parenthesis - { - jParms[jss::account] = "0xDEADBEEF"; - jParms[jss::account] = A1.human(); - Json::Value p{jParms}; + jParms[jss::account] = "0xDEADBEEF"; + jParms[jss::account] = A1.human(); + Json::Value p{jParms}; - p[jss::ledger_index_max] = -1; - p[jss::ledger_index_min] = -1; - p[jss::ledger_index] = -1; + p[jss::ledger_index_max] = -1; + p[jss::ledger_index_min] = -1; + p[jss::ledger_index] = -1; + if (apiVersion < 2u) + BEAST_EXPECT( + hasTxs(env.rpc("json", "account_tx", to_string(p)))); + else BEAST_EXPECT(isErr( env.rpc("json", "account_tx", to_string(p)), rpcINVALID_PARAMS)); - } - - // Ledger index min/max only - { - Json::Value p{jParms}; - p[jss::ledger_index_max] = 100; - p[jss::ledger_index_min] = 0; - BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(p)), - rpcLGR_IDX_MALFORMED)); + } - p[jss::ledger_index_max] = -1; - p[jss::ledger_index_min] = -1; + // Ledger index max only + { + Json::Value p{jParms}; + p[jss::ledger_index_max] = env.current()->info().seq; + if (apiVersion < 2u) BEAST_EXPECT( hasTxs(env.rpc("json", "account_tx", to_string(p)))); - - p[jss::ledger_index_min] = 2; - p[jss::ledger_index_max] = 1; + else BEAST_EXPECT(isErr( env.rpc("json", "account_tx", to_string(p)), - rpcINVALID_LGR_RANGE)); + rpcLGR_IDX_MALFORMED)); + } + // test binary and forward for bool/non bool values + { + Json::Value p{jParms}; + p[jss::binary] = "asdf"; + if (apiVersion < 2u) + { + Json::Value result{env.rpc("json", "account_tx", to_string(p))}; + BEAST_EXPECT(result[jss::result][jss::status] == "success"); } + else + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcINVALID_PARAMS)); - // Ledger index max only - { - Json::Value p{jParms}; - p[jss::ledger_index_max] = env.current()->info().seq; + p[jss::binary] = true; + Json::Value result{env.rpc("json", "account_tx", to_string(p))}; + BEAST_EXPECT(result[jss::result][jss::status] == "success"); + + p[jss::forward] = "true"; + if (apiVersion < 2u) + BEAST_EXPECT(result[jss::result][jss::status] == "success"); + else BEAST_EXPECT(isErr( env.rpc("json", "account_tx", to_string(p)), - rpcLGR_IDX_MALFORMED)); - } + rpcINVALID_PARAMS)); + + p[jss::forward] = false; + result = env.rpc("json", "account_tx", to_string(p)); + BEAST_EXPECT(result[jss::result][jss::status] == "success"); } } diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index c14ec0f043c..091b9f51686 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -34,117 +35,155 @@ class GatewayBalances_test : public beast::unit_test::suite using namespace jtx; Env env(*this, features); + { + // Gateway account and assets + Account const alice{"alice"}; + env.fund(XRP(10000), "alice"); + auto USD = alice["USD"]; + auto CNY = alice["CNY"]; + auto JPY = alice["JPY"]; + + // Create a hotwallet + Account const hw{"hw"}; + env.fund(XRP(10000), "hw"); + env(trust(hw, USD(10000))); + env(trust(hw, JPY(10000))); + env(pay(alice, hw, USD(5000))); + env(pay(alice, hw, JPY(5000))); + + // Create some clients + Account const bob{"bob"}; + env.fund(XRP(10000), "bob"); + env(trust(bob, USD(100))); + env(trust(bob, CNY(100))); + env(pay(alice, bob, USD(50))); + + Account const charley{"charley"}; + env.fund(XRP(10000), "charley"); + env(trust(charley, CNY(500))); + env(trust(charley, JPY(500))); + env(pay(alice, charley, CNY(250))); + env(pay(alice, charley, JPY(250))); + + Account const dave{"dave"}; + env.fund(XRP(10000), "dave"); + env(trust(dave, CNY(100))); + env(pay(alice, dave, CNY(30))); + + // give the gateway an asset + env(trust(alice, charley["USD"](50))); + env(pay(charley, alice, USD(10))); + + // freeze dave + env(trust(alice, dave["CNY"](0), dave, tfSetFreeze)); + + env.close(); + + auto wsc = makeWSClient(env.app().config()); + + Json::Value qry; + qry[jss::account] = alice.human(); + qry[jss::hotwallet] = hw.human(); + + auto jv = wsc->invoke("gateway_balances", qry); + expect(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + expect(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + expect( + jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + expect(jv.isMember(jss::id) && jv[jss::id] == 5); + } + + auto const& result = jv[jss::result]; + expect(result[jss::account] == alice.human()); + expect(result[jss::status] == "success"); + + { + auto const& balances = result[jss::balances]; + expect(balances.isObject(), "balances is not an object"); + expect(balances.size() == 1, "balances size is not 1"); + + auto const& hwBalance = balances[hw.human()]; + expect(hwBalance.isArray(), "hwBalance is not an array"); + expect(hwBalance.size() == 2); + auto c1 = hwBalance[0u][jss::currency]; + auto c2 = hwBalance[1u][jss::currency]; + expect(c1 == "USD" || c2 == "USD"); + expect(c1 == "JPY" || c2 == "JPY"); + expect( + hwBalance[0u][jss::value] == "5000" && + hwBalance[1u][jss::value] == "5000"); + } + + { + auto const& fBalances = result[jss::frozen_balances]; + expect(fBalances.isObject()); + expect(fBalances.size() == 1); + + auto const& fBal = fBalances[dave.human()]; + expect(fBal.isArray()); + expect(fBal.size() == 1); + expect(fBal[0u].isObject()); + expect(fBal[0u][jss::currency] == "CNY"); + expect(fBal[0u][jss::value] == "30"); + } + + { + auto const& assets = result[jss::assets]; + expect(assets.isObject(), "assets it not an object"); + expect(assets.size() == 1, "assets size is not 1"); + + auto const& cAssets = assets[charley.human()]; + expect(cAssets.isArray()); + expect(cAssets.size() == 1); + expect(cAssets[0u][jss::currency] == "USD"); + expect(cAssets[0u][jss::value] == "10"); + } + + { + auto const& obligations = result[jss::obligations]; + expect(obligations.isObject(), "obligations is not an object"); + expect(obligations.size() == 3); + expect(obligations["CNY"] == "250"); + expect(obligations["JPY"] == "250"); + expect(obligations["USD"] == "50"); + } + } + } + + void + testGWBApiVersions(FeatureBitset features) + { + using namespace std::chrono_literals; + using namespace jtx; + Env env(*this, features); + // Gateway account and assets Account const alice{"alice"}; - env.fund(XRP(10000), "alice"); - auto USD = alice["USD"]; - auto CNY = alice["CNY"]; - auto JPY = alice["JPY"]; - - // Create a hotwallet + env.fund(XRP(10000), alice); Account const hw{"hw"}; - env.fund(XRP(10000), "hw"); - env(trust(hw, USD(10000))); - env(trust(hw, JPY(10000))); - env(pay(alice, hw, USD(5000))); - env(pay(alice, hw, JPY(5000))); - - // Create some clients - Account const bob{"bob"}; - env.fund(XRP(10000), "bob"); - env(trust(bob, USD(100))); - env(trust(bob, CNY(100))); - env(pay(alice, bob, USD(50))); - - Account const charley{"charley"}; - env.fund(XRP(10000), "charley"); - env(trust(charley, CNY(500))); - env(trust(charley, JPY(500))); - env(pay(alice, charley, CNY(250))); - env(pay(alice, charley, JPY(250))); - - Account const dave{"dave"}; - env.fund(XRP(10000), "dave"); - env(trust(dave, CNY(100))); - env(pay(alice, dave, CNY(30))); - - // give the gateway an asset - env(trust(alice, charley["USD"](50))); - env(pay(charley, alice, USD(10))); - - // freeze dave - env(trust(alice, dave["CNY"](0), dave, tfSetFreeze)); - + env.fund(XRP(10000), hw); env.close(); auto wsc = makeWSClient(env.app().config()); - Json::Value qry; - qry[jss::account] = alice.human(); - qry[jss::hotwallet] = hw.human(); - - auto jv = wsc->invoke("gateway_balances", qry); - expect(jv[jss::status] == "success"); - if (wsc->version() == 2) - { - expect(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); - expect(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); - expect(jv.isMember(jss::id) && jv[jss::id] == 5); - } - - auto const& result = jv[jss::result]; - expect(result[jss::account] == alice.human()); - expect(result[jss::status] == "success"); - - { - auto const& balances = result[jss::balances]; - expect(balances.isObject(), "balances is not an object"); - expect(balances.size() == 1, "balances size is not 1"); - - auto const& hwBalance = balances[hw.human()]; - expect(hwBalance.isArray(), "hwBalance is not an array"); - expect(hwBalance.size() == 2); - auto c1 = hwBalance[0u][jss::currency]; - auto c2 = hwBalance[1u][jss::currency]; - expect(c1 == "USD" || c2 == "USD"); - expect(c1 == "JPY" || c2 == "JPY"); - expect( - hwBalance[0u][jss::value] == "5000" && - hwBalance[1u][jss::value] == "5000"); - } - - { - auto const& fBalances = result[jss::frozen_balances]; - expect(fBalances.isObject()); - expect(fBalances.size() == 1); - - auto const& fBal = fBalances[dave.human()]; - expect(fBal.isArray()); - expect(fBal.size() == 1); - expect(fBal[0u].isObject()); - expect(fBal[0u][jss::currency] == "CNY"); - expect(fBal[0u][jss::value] == "30"); - } + Json::Value qry2; + qry2[jss::account] = alice.human(); + qry2[jss::hotwallet] = "asdf"; + for (auto apiVersion = RPC::apiMinimumSupportedVersion; + apiVersion <= RPC::apiBetaVersion; + ++apiVersion) { - auto const& assets = result[jss::assets]; - expect(assets.isObject(), "assets it not an object"); - expect(assets.size() == 1, "assets size is not 1"); - - auto const& cAssets = assets[charley.human()]; - expect(cAssets.isArray()); - expect(cAssets.size() == 1); - expect(cAssets[0u][jss::currency] == "USD"); - expect(cAssets[0u][jss::value] == "10"); - } - - { - auto const& obligations = result[jss::obligations]; - expect(obligations.isObject(), "obligations is not an object"); - expect(obligations.size() == 3); - expect(obligations["CNY"] == "250"); - expect(obligations["JPY"] == "250"); - expect(obligations["USD"] == "50"); + qry2[jss::api_version] = apiVersion; + auto jv = wsc->invoke("gateway_balances", qry2); + expect(jv[jss::status] == "error"); + + auto response = jv[jss::result]; + auto const error = + apiVersion < 2u ? "invalidHotWallet" : "invalidParams"; + BEAST_EXPECT(response[jss::error] == error); } } @@ -207,8 +246,11 @@ class GatewayBalances_test : public beast::unit_test::suite { using namespace jtx; auto const sa = supported_amendments(); - testGWB(sa - featureFlowCross); - testGWB(sa); + for (auto feature : {sa - featureFlowCross, sa}) + { + testGWB(feature); + testGWBApiVersions(feature); + } testGWBOverflow(); } diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 905af6aceb2..960ac3a86ee 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -21,12 +21,331 @@ #include #include #include +#include #include +#include #include #include +#include +#include +#include namespace ripple { +class LedgerRPC_XChain_test : public beast::unit_test::suite, + public test::jtx::XChainBridgeObjects +{ + void + checkErrorValue( + Json::Value const& jv, + std::string const& err, + std::string const& msg) + { + if (BEAST_EXPECT(jv.isMember(jss::status))) + BEAST_EXPECT(jv[jss::status] == "error"); + if (BEAST_EXPECT(jv.isMember(jss::error))) + BEAST_EXPECT(jv[jss::error] == err); + if (msg.empty()) + { + BEAST_EXPECT( + jv[jss::error_message] == Json::nullValue || + jv[jss::error_message] == ""); + } + else if (BEAST_EXPECT(jv.isMember(jss::error_message))) + BEAST_EXPECT(jv[jss::error_message] == msg); + } + + void + testLedgerEntryBridge() + { + testcase("ledger_entry: bridge"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(port_increment, 3), features); + + createBridgeObjects(mcEnv, scEnv); + + std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)}; + std::string bridge_index; + Json::Value mcBridge; + { + // request the bridge via RPC + Json::Value jvParams; + jvParams[jss::bridge_account] = mcDoor.human(); + jvParams[jss::bridge] = jvb; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == mcDoor.human()); + + BEAST_EXPECT(r.isMember(jss::Flags)); + + BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName)); + BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge); + + // we not created an account yet + BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0); + + // we have not claimed a locking chain tx yet + BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0); + + BEAST_EXPECT(r.isMember(jss::index)); + bridge_index = r[jss::index].asString(); + mcBridge = r; + } + { + // request the bridge via RPC by index + Json::Value jvParams; + jvParams[jss::index] = bridge_index; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + BEAST_EXPECT(jrr[jss::node] == mcBridge); + } + { + // swap door accounts and make sure we get an error value + Json::Value jvParams; + // Sidechain door account is "master", not scDoor + jvParams[jss::bridge_account] = Account::master.human(); + jvParams[jss::bridge] = jvb; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // create two claim ids and verify that the bridge counter was + // incremented + mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice)); + mcEnv.close(); + mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob)); + mcEnv.close(); + + // request the bridge via RPC + Json::Value jvParams; + jvParams[jss::bridge_account] = mcDoor.human(); + jvParams[jss::bridge] = jvb; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + + // we executed two create claim id txs + BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName)); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); + } + } + + void + testLedgerEntryClaimID() + { + testcase("ledger_entry: xchain_claim_id"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(port_increment, 3), features); + + createBridgeObjects(mcEnv, scEnv); + + scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)); + scEnv.close(); + scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob)); + scEnv.close(); + + std::string bridge_index; + { + // request the xchain_claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; + jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = + 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == scAlice.human()); + BEAST_EXPECT( + r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1); + BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); + } + + { + // request the xchain_claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; + jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = + 2; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == scBob.human()); + BEAST_EXPECT( + r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); + BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); + } + } + + void + testLedgerEntryCreateAccountClaimID() + { + testcase("ledger_entry: xchain_create_account_claim_id"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(port_increment, 3), features); + + // note: signers.size() and quorum are both 5 in createBridgeObjects + createBridgeObjects(mcEnv, scEnv); + + auto scCarol = + Account("scCarol"); // Don't fund it - it will be created with the + // xchain transaction + auto const amt = XRP(1000); + mcEnv(sidechain_xchain_account_create( + mcAlice, jvb, scCarol, amt, reward)); + mcEnv.close(); + + // send less than quorum of attestations (otherwise funds are + // immediately transferred and no "claim" object is created) + size_t constexpr num_attest = 3; + auto attestations = create_account_attestations( + scAttester, + jvb, + mcAlice, + amt, + reward, + payee, + /*wasLockingChainSend*/ true, + 1, + scCarol, + signers, + UT_XCHAIN_DEFAULT_NUM_SIGNERS); + for (size_t i = 0; i < num_attest; ++i) + { + scEnv(attestations[i]); + } + scEnv.close(); + + { + // request the create account claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_create_account_claim_id] = + jvXRPBridgeRPC; + jvParams[jss::xchain_owned_create_account_claim_id] + [jss::xchain_owned_create_account_claim_id] = 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + // std::cout << to_string(jrr) << '\n'; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == Account::master.human()); + + BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1); + + BEAST_EXPECT( + r.isMember(sfXChainCreateAccountAttestations.jsonName)); + auto attest = r[sfXChainCreateAccountAttestations.jsonName]; + BEAST_EXPECT(attest.isArray()); + BEAST_EXPECT(attest.size() == 3); + BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember( + sfXChainCreateAccountProofSig.jsonName)); + Json::Value a[num_attest]; + for (size_t i = 0; i < num_attest; ++i) + { + a[i] = attest[Json::Value::UInt(0)] + [sfXChainCreateAccountProofSig.jsonName]; + BEAST_EXPECT( + a[i].isMember(jss::Amount) && + a[i][jss::Amount].asInt() == 1000 * drop_per_xrp); + BEAST_EXPECT( + a[i].isMember(jss::Destination) && + a[i][jss::Destination] == scCarol.human()); + BEAST_EXPECT( + a[i].isMember(sfAttestationSignerAccount.jsonName) && + std::any_of( + signers.begin(), signers.end(), [&](signer const& s) { + return a[i][sfAttestationSignerAccount.jsonName] == + s.account.human(); + })); + BEAST_EXPECT( + a[i].isMember(sfAttestationRewardAccount.jsonName) && + std::any_of( + payee.begin(), + payee.end(), + [&](Account const& account) { + return a[i][sfAttestationRewardAccount.jsonName] == + account.human(); + })); + BEAST_EXPECT( + a[i].isMember(sfWasLockingChainSend.jsonName) && + a[i][sfWasLockingChainSend.jsonName] == 1); + BEAST_EXPECT( + a[i].isMember(sfSignatureReward.jsonName) && + a[i][sfSignatureReward.jsonName].asInt() == + 1 * drop_per_xrp); + } + } + + // complete attestations quorum - CreateAccountClaimID should not be + // present anymore + for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i) + { + scEnv(attestations[i]); + } + scEnv.close(); + { + // request the create account claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_create_account_claim_id] = + jvXRPBridgeRPC; + jvParams[jss::xchain_owned_create_account_claim_id] + [jss::xchain_owned_create_account_claim_id] = 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + +public: + void + run() override + { + testLedgerEntryBridge(); + testLedgerEntryClaimID(); + testLedgerEntryCreateAccountClaimID(); + } +}; + class LedgerRPC_test : public beast::unit_test::suite { void @@ -1940,5 +2259,6 @@ class LedgerRPC_test : public beast::unit_test::suite }; BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple); +BEAST_DEFINE_TESTSUITE(LedgerRPC_XChain, app, ripple); } // namespace ripple diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index 472706f0b73..3077b06f8a3 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include namespace ripple { @@ -202,9 +203,12 @@ class NoRipple_test : public beast::unit_test::suite } void - testDefaultRipple(FeatureBitset features) + testDefaultRipple(FeatureBitset features, unsigned int apiVersion) { - testcase("Set default ripple on an account and check new trustlines"); + testcase( + "Set default ripple on an account and check new trustlines " + "Version " + + std::to_string(apiVersion)); using namespace jtx; Env env(*this, features); @@ -221,9 +225,10 @@ class NoRipple_test : public beast::unit_test::suite env(trust(gw, USD(100), alice, 0)); env(trust(gw, USD(100), bob, 0)); + Json::Value params; + params[jss::api_version] = apiVersion; { - Json::Value params; params[jss::account] = gw.human(); params[jss::peer] = alice.human(); @@ -232,7 +237,6 @@ class NoRipple_test : public beast::unit_test::suite BEAST_EXPECT(line0[jss::no_ripple_peer].asBool() == true); } { - Json::Value params; params[jss::account] = alice.human(); params[jss::peer] = gw.human(); @@ -241,7 +245,6 @@ class NoRipple_test : public beast::unit_test::suite BEAST_EXPECT(line0[jss::no_ripple].asBool() == true); } { - Json::Value params; params[jss::account] = gw.human(); params[jss::peer] = bob.human(); @@ -250,7 +253,6 @@ class NoRipple_test : public beast::unit_test::suite BEAST_EXPECT(line0[jss::no_ripple].asBool() == false); } { - Json::Value params; params[jss::account] = bob.human(); params[jss::peer] = gw.human(); @@ -258,6 +260,22 @@ class NoRipple_test : public beast::unit_test::suite auto const& line0 = lines[jss::result][jss::lines][0u]; BEAST_EXPECT(line0[jss::no_ripple_peer].asBool() == false); } + { + // test for transactions + { + params[jss::account] = bob.human(); + params[jss::role] = "gateway"; + params[jss::transactions] = "asdf"; + + auto lines = + env.rpc("json", "noripple_check", to_string(params)); + if (apiVersion < 2u) + BEAST_EXPECT(lines[jss::result][jss::status] == "success"); + else + BEAST_EXPECT( + lines[jss::result][jss::error] == "invalidParams"); + } + } } void @@ -266,9 +284,14 @@ class NoRipple_test : public beast::unit_test::suite testSetAndClear(); auto withFeatsTests = [this](FeatureBitset features) { + for (auto testVersion = RPC::apiMinimumSupportedVersion; + testVersion <= RPC::apiBetaVersion; + ++testVersion) + { + testDefaultRipple(features, testVersion); + } testNegativeBalance(features); testPairwise(features); - testDefaultRipple(features); }; using namespace jtx; auto const sa = supported_amendments(); diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 3bb0cce611c..5322319907f 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace ripple { namespace test { @@ -743,7 +744,7 @@ class Subscribe_test : public beast::unit_test::suite using namespace std::chrono_literals; using namespace jtx; - using IdxHashVec = std::vector>; + using IdxHashVec = std::vector>; Account alice("alice"); Account bob("bob"); @@ -781,11 +782,14 @@ class Subscribe_test : public beast::unit_test::suite idx = r[jss::account_history_tx_index].asInt(); if (r.isMember(jss::account_history_tx_first)) first_flag = true; + bool boundary = r.isMember(jss::account_history_boundary); + int ledger_idx = r[jss::ledger_index].asInt(); if (r.isMember(jss::transaction) && r[jss::transaction].isMember(jss::hash)) { + auto t{r[jss::transaction]}; v.emplace_back( - idx, r[jss::transaction][jss::hash].asString()); + idx, t[jss::hash].asString(), boundary, ledger_idx); continue; } } @@ -838,13 +842,13 @@ class Subscribe_test : public beast::unit_test::suite hash_map txHistoryMap; for (auto const& tx : txHistoryVec) { - txHistoryMap.emplace(tx.second, tx.first); + txHistoryMap.emplace(std::get<1>(tx), std::get<0>(tx)); } auto getHistoryIndex = [&](std::size_t i) -> std::optional { if (i >= accountVec.size()) return {}; - auto it = txHistoryMap.find(accountVec[i].second); + auto it = txHistoryMap.find(std::get<1>(accountVec[i])); if (it == txHistoryMap.end()) return {}; return it->second; @@ -862,6 +866,48 @@ class Subscribe_test : public beast::unit_test::suite return true; }; + // example of vector created from the return of `subscribe` rpc + // with jss::accounts + // boundary == true on last tx of ledger + // ------------------------------------------------------------ + // (0, "E5B8B...", false, 4 + // (0, "39E1C...", false, 4 + // (0, "14EF1...", false, 4 + // (0, "386E6...", false, 4 + // (0, "00F3B...", true, 4 + // (0, "1DCDC...", false, 5 + // (0, "BD02A...", false, 5 + // (0, "D3E16...", false, 5 + // (0, "CB593...", false, 5 + // (0, "8F28B...", true, 5 + // + // example of vector created from the return of `subscribe` rpc + // with jss::account_history_tx_stream. + // boundary == true on first tx of ledger + // ------------------------------------------------------------ + // (-1, "8F28B...", false, 5 + // (-2, "CB593...", false, 5 + // (-3, "D3E16...", false, 5 + // (-4, "BD02A...", false, 5 + // (-5, "1DCDC...", true, 5 + // (-6, "00F3B...", false, 4 + // (-7, "386E6...", false, 4 + // (-8, "14EF1...", false, 4 + // (-9, "39E1C...", false, 4 + // (-10, "E5B8B...", true, 4 + + auto checkBoundary = [](IdxHashVec const& vec, bool /* forward */) { + size_t num_tx = vec.size(); + for (size_t i = 0; i < num_tx; ++i) + { + auto [idx, hash, boundary, ledger] = vec[i]; + if ((i + 1 == num_tx || ledger != std::get<3>(vec[i + 1])) != + boundary) + return false; + } + return true; + }; + /////////////////////////////////////////////////////////////////// { @@ -880,6 +926,7 @@ class Subscribe_test : public beast::unit_test::suite auto jv = wscTxHistory->invoke("subscribe", request); if (!BEAST_EXPECT(goodSubRPC(jv))) return; + jv = wscTxHistory->invoke("subscribe", request); BEAST_EXPECT(!goodSubRPC(jv)); @@ -911,7 +958,6 @@ class Subscribe_test : public beast::unit_test::suite r = getTxHash(*wscTxHistory, vec, 1); BEAST_EXPECT(!r.first); } - { /* * subscribe genesis account tx history without txns @@ -950,8 +996,8 @@ class Subscribe_test : public beast::unit_test::suite if (!BEAST_EXPECT(r.first && r.second)) return; BEAST_EXPECT( - bobFullHistoryVec.back().second == - genesisFullHistoryVec.back().second); + std::get<1>(bobFullHistoryVec.back()) == + std::get<1>(genesisFullHistoryVec.back())); /* * unsubscribe to prepare next test @@ -987,8 +1033,8 @@ class Subscribe_test : public beast::unit_test::suite jv = wscTxHistory->invoke("unsubscribe", request); BEAST_EXPECT( - bobFullHistoryVec.back().second == - genesisFullHistoryVec.back().second); + std::get<1>(bobFullHistoryVec.back()) == + std::get<1>(genesisFullHistoryVec.back())); } { @@ -1030,11 +1076,17 @@ class Subscribe_test : public beast::unit_test::suite if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true))) return; + // check boundary tags + // only account_history_tx_stream has ledger boundary information. + if (!BEAST_EXPECT(checkBoundary(txHistoryVec, false))) + return; + { // take out all history txns from stream to prepare next test IdxHashVec initFundTxns; if (!BEAST_EXPECT( - getTxHash(*wscTxHistory, initFundTxns, 10).second)) + getTxHash(*wscTxHistory, initFundTxns, 10).second) || + !BEAST_EXPECT(checkBoundary(initFundTxns, false))) return; } @@ -1046,6 +1098,12 @@ class Subscribe_test : public beast::unit_test::suite return; if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true))) return; + + // check boundary tags + // only account_history_tx_stream has ledger boundary information. + if (!BEAST_EXPECT(checkBoundary(txHistoryVec, false))) + return; + wscTxHistory->invoke("unsubscribe", request); wscAccount->invoke("unsubscribe", stream); }