diff --git a/.clang-tidy b/.clang-tidy index 88dd6f4e57..68fc9e75fc 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -56,32 +56,17 @@ Checks: "-*, readability-*, -readability-avoid-const-params-in-decls, - -readability-avoid-unconditional-preprocessor-if, -readability-container-data-pointer, - -readability-delete-null-pointer, -readability-function-cognitive-complexity, - -readability-function-size, -readability-identifier-length, -readability-inconsistent-declaration-parameter-name, -readability-isolate-declaration, -readability-magic-numbers, - -readability-misplaced-array-index, -readability-named-parameter, - -readability-operators-representation, -readability-qualified-auto, -readability-redundant-access-specifiers, - -readability-redundant-control-flow, - -readability-redundant-function-ptr-dereference, - -readability-redundant-preprocessor, - -readability-redundant-smartptr-get, - -readability-redundant-string-cstr, - -readability-simplify-subscript-expr, -readability-static-accessed-through-instance, - -readability-string-compare, - -readability-uniqueptr-delete-release, - -readability-uppercase-literal-suffix, - -readability-use-anyofallof, - -readability-use-concise-preprocessor-directives + -readability-uppercase-literal-suffix " # --- # bugprone-narrowing-conversions, # This will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs diff --git a/.cspell.config.yaml b/.cspell.config.yaml index c558fc0984..13da132b90 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -65,6 +65,7 @@ words: - Btrfs - Buildx - canonicality + - canonicalised - changespq - checkme - choco @@ -72,6 +73,7 @@ words: - citardauq - clawback - clawbacks + - clippy - cmaketoolchain - coeffs - coldwallet @@ -259,6 +261,7 @@ words: - rocksdb - Rohrs - roundings + - rustc - sahyadri - Satoshi - scons @@ -279,6 +282,8 @@ words: - sles - soci - socidb + - sponsee + - sponsees - SRPMS - sslws - statsd @@ -300,6 +305,7 @@ words: - takerpays - ters - TMEndpointv2 + - tparam - trixie - tx - txid @@ -327,6 +333,7 @@ words: - unserviced - unshareable - unshares + - unsponsored - unsquelch - unsquelched - unsquelching diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index aee6f4c579..b31e6ae961 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -159,6 +159,7 @@ test.peerfinder > xrpl.protocol test.protocol > test.jtx test.protocol > test.unit_test test.protocol > xrpl.basics +test.protocol > xrpld.core test.protocol > xrpl.json test.protocol > xrpl.protocol test.rpc > test.jtx diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index a269cb25d4..c783f32fb7 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -25,24 +25,16 @@ def get_cmake_args(build_type: str, extra_args: str) -> str: return " ".join(args) -def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool: - """Whether a config should run for the current event. - - 'exclude_event_types' is a list of GitHub event names (e.g. - ["pull_request"]) on which the config should NOT run; an empty list means - the config runs on every event. When no event is given (event is None), no - filtering is applied. - """ - if event is None: - return True - return event not in exclude_event_types - - # --------------------------------------------------------------------------- # Input types — shapes of the JSON config files # --------------------------------------------------------------------------- +# Every config must declare 'minimal'. Minimal configs form the reduced matrix +# built for pull requests by default; the full matrix adds the rest. Packaging +# configs declare it too, but packaging is gated in the workflow, not by it. + + @dataclasses.dataclass class LinuxConfig: """One entry in linux.json's 'configs' or 'package_configs' arrays.""" @@ -50,13 +42,11 @@ class LinuxConfig: compiler: list[str] build_type: list[str] arch: list[str] + minimal: bool sanitizers: list[str] = dataclasses.field(default_factory=list) suffix: str = "" extra_cmake_args: str = "" image: str = "" # only used by package_configs entries - # List of GitHub event names (e.g. "pull_request") on which this config - # should NOT run. Empty means it runs on every event. - exclude_event_types: list[str] = dataclasses.field(default_factory=list) @dataclasses.dataclass @@ -89,11 +79,9 @@ class PlatformConfig: """One entry in macos.json's or windows.json's 'configs' array.""" build_type: list[str] + minimal: bool build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug) extra_cmake_args: str = "" - # List of GitHub event names (e.g. "pull_request") on which this config - # should NOT run. Empty means it runs on every event. - exclude_event_types: list[str] = dataclasses.field(default_factory=list) def __post_init__(self) -> None: if isinstance(self.build_type, str): @@ -168,20 +156,18 @@ _ARCHS: dict[str, Architecture] = { } -def expand_linux_matrix( - linux: LinuxFile, event: str | None = None -) -> list[MatrixEntry]: +def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]: """Expand a LinuxFile into a flat list of matrix entries. Each config entry is expanded over the cross-product of its - compiler, build_type, sanitizers, and architecture lists. Configs that - exclude the current event are skipped. + compiler, build_type, sanitizers, and architecture lists. When 'minimal' is + true, only configs flagged as minimal are included. """ entries: list[MatrixEntry] = [] for distro, configs in linux.configs.items(): for cfg in configs: - if not runs_on_event(cfg.exclude_event_types, event): + if minimal and not cfg.minimal: continue # An empty sanitizers list means "one entry with no sanitizer". effective_sanitizers = cfg.sanitizers or [""] @@ -240,19 +226,17 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]: return entries -def expand_platform_matrix( - pf: PlatformFile, event: str | None = None -) -> list[MatrixEntry]: +def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]: """Expand a PlatformFile (macOS or Windows) into matrix entries. - Configs that exclude the current event are skipped. + When 'minimal' is true, only configs flagged as minimal are included. """ platform_name, arch = pf.platform.split("/") is_windows = platform_name == "windows" entries: list[MatrixEntry] = [] for cfg in pf.configs: - if not runs_on_event(cfg.exclude_event_types, event): + if minimal and not cfg.minimal: continue for build_type in cfg.build_type: entries.append( @@ -292,12 +276,12 @@ if __name__ == "__main__": action="store_true", ) parser.add_argument( - "-e", - "--event", - help="The GitHub event name that triggered the workflow (e.g. 'push', " - "'pull_request'). Configs are filtered by their 'event_type'. If " - "omitted, no filtering is applied.", - default=None, + "-m", + "--minimal", + help="Emit only the minimal matrix (the configs flagged 'minimal'), " + "used for pull requests by default. If omitted, the full matrix is " + "emitted.", + action="store_true", ) args = parser.parse_args() @@ -308,15 +292,15 @@ if __name__ == "__main__": else: if args.config in ("linux", None): matrix += expand_linux_matrix( - LinuxFile.load(THIS_DIR / "linux.json"), args.event + LinuxFile.load(THIS_DIR / "linux.json"), args.minimal ) if args.config in ("macos", None): matrix += expand_platform_matrix( - PlatformFile.load(THIS_DIR / "macos.json"), args.event + PlatformFile.load(THIS_DIR / "macos.json"), args.minimal ) if args.config in ("windows", None): matrix += expand_platform_matrix( - PlatformFile.load(THIS_DIR / "windows.json"), args.event + PlatformFile.load(THIS_DIR / "windows.json"), args.minimal ) print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}") diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 863b910dda..03ac1c6334 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -2,16 +2,30 @@ "image_tag": "sha-e29b523", "configs": { "ubuntu": [ + { + "compiler": ["clang"], + "build_type": ["Release"], + "arch": ["amd64"], + "minimal": true + }, + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"], + "minimal": false + }, { "compiler": ["gcc", "clang"], "build_type": ["Debug", "Release"], - "arch": ["amd64", "arm64"] + "arch": ["arm64"], + "minimal": false }, { "compiler": ["gcc", "clang"], "build_type": ["Debug", "Release"], "arch": ["amd64"], + "minimal": false, "sanitizers": ["address", "undefinedbehavior"] }, @@ -19,6 +33,7 @@ "compiler": ["gcc"], "build_type": ["Debug"], "arch": ["amd64"], + "minimal": true, "suffix": "coverage", "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0" }, @@ -26,6 +41,7 @@ "compiler": ["clang"], "build_type": ["Debug"], "arch": ["amd64"], + "minimal": false, "suffix": "voidstar", "extra_cmake_args": "-Dvoidstar=ON" }, @@ -33,6 +49,7 @@ "compiler": ["clang"], "build_type": ["Release"], "arch": ["amd64"], + "minimal": false, "suffix": "reffee", "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000" }, @@ -40,9 +57,9 @@ "compiler": ["gcc"], "build_type": ["Debug"], "arch": ["amd64"], + "minimal": false, "suffix": "unity", - "extra_cmake_args": "-Dunity=ON", - "exclude_event_types": ["pull_request"] + "extra_cmake_args": "-Dunity=ON" } ], @@ -50,7 +67,8 @@ { "compiler": ["gcc"], "build_type": ["Release"], - "arch": ["amd64"] + "arch": ["amd64"], + "minimal": false } ], @@ -58,7 +76,8 @@ { "compiler": ["gcc"], "build_type": ["Release"], - "arch": ["amd64"] + "arch": ["amd64"], + "minimal": false } ] }, @@ -68,6 +87,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], + "minimal": false, "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745" } ], @@ -77,6 +97,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], + "minimal": false, "image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745" } ] diff --git a/.github/scripts/strategy-matrix/macos.json b/.github/scripts/strategy-matrix/macos.json index 2d3cc75c7b..98e0f13141 100644 --- a/.github/scripts/strategy-matrix/macos.json +++ b/.github/scripts/strategy-matrix/macos.json @@ -4,13 +4,14 @@ "configs": [ { "build_type": "Release", - "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" + "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", + "minimal": true }, { "build_type": "Debug", "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", "build_only": true, - "exclude_event_types": ["pull_request"] + "minimal": false } ] } diff --git a/.github/scripts/strategy-matrix/windows.json b/.github/scripts/strategy-matrix/windows.json index 370e9f5bc7..6b926e85f5 100644 --- a/.github/scripts/strategy-matrix/windows.json +++ b/.github/scripts/strategy-matrix/windows.json @@ -2,11 +2,11 @@ "platform": "windows/amd64", "runner": ["self-hosted", "Windows", "dev-box-windows-2026"], "configs": [ - { "build_type": "Release" }, + { "build_type": "Release", "minimal": true }, { "build_type": "Debug", "build_only": true, - "exclude_event_types": ["pull_request"] + "minimal": false } ] } diff --git a/.github/workflows/conflicting-pr.yml b/.github/workflows/conflicting-pr.yml index 772d46fd7d..cf65640954 100644 --- a/.github/workflows/conflicting-pr.yml +++ b/.github/workflows/conflicting-pr.yml @@ -14,6 +14,7 @@ permissions: jobs: main: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'IgnoreConflicts') }} runs-on: ubuntu-latest steps: - name: Check if PRs are dirty diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 19fb170b92..bbf6f8c39e 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -1,7 +1,11 @@ -# This workflow runs all workflows to check, build and test the project on -# various Linux flavors, as well as on MacOS and Windows, on every push to a -# user branch. However, it will not run if the pull request is a draft unless it -# has the 'DraftRunCI' label. For commits to PRs that target a release branch, +# This workflow runs workflows to check, build and test the project +# on every meaningful change on pull_request. +# However, it will not run if the PR is a draft +# unless it has the 'DraftRunCI' or 'Full CI build' label. +# +# By default a PR builds only a minimal matrix. +# The full matrix runs once the PR is labeled "Ready to merge" or "Full CI build". +# For commits to PRs that target a release branch, # it also uploads the libxrpl recipe to the Conan remote. name: PR @@ -15,9 +19,24 @@ on: - reopened - synchronize - ready_for_review + # Trigger on label changes so toggling "Ready to merge" or "Full CI build" + # switches between the minimal and full matrix without needing a new push. + - labeled + - unlabeled concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # Use a per-ref group so a newer run (a push, or a change to a label below) + # supersedes the in-progress one for that ref. Label events we don't act on get + # their own unique group (per run id) instead, keeping them out of the shared + # group so real builds keep running. Keep this list in sync with `should-run`. + group: >- + ${{ github.workflow }}-${{ github.ref }}${{ + ((github.event.action == 'labeled' || github.event.action == 'unlabeled') + && github.event.label.name != 'Ready to merge' + && github.event.label.name != 'DraftRunCI' + && github.event.label.name != 'Full CI build') + && format('-{0}', github.run_id) || '' + }} cancel-in-progress: true defaults: @@ -26,10 +45,21 @@ defaults: jobs: # This job determines whether the rest of the workflow should run. It runs - # when the PR is not a draft (which should also cover merge-group) or - # has the 'DraftRunCI' label. + # when the PR is not a draft (which should also cover merge-group) or has the + # 'DraftRunCI' or 'Full CI build' label. For label events it only runs when the + # label added or removed is one we act on ('Ready to merge', 'DraftRunCI' or + # 'Full CI build'), so unrelated label changes do not trigger a redundant run. should-run: - if: ${{ !github.event.pull_request.draft || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }} + if: >- + ${{ + ((github.event.action != 'labeled' && github.event.action != 'unlabeled') + || github.event.label.name == 'Ready to merge' + || github.event.label.name == 'DraftRunCI' + || github.event.label.name == 'Full CI build') + && (!github.event.pull_request.draft + || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') + || contains(github.event.pull_request.labels.*.name, 'Full CI build')) + }} runs-on: ubuntu-latest steps: - name: Checkout repository @@ -91,15 +121,17 @@ jobs: # least one of: # * Any of the files checked in the `changes` step were modified # * The PR is NOT a draft and is labeled "Ready to merge" + # * The PR is labeled "Full CI build" (draft or not) # * The workflow is running from the merge queue id: go env: FILES: ${{ steps.changes.outputs.any_changed }} DRAFT: ${{ github.event.pull_request.draft }} READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }} + FULL: ${{ contains(github.event.pull_request.labels.*.name, 'Full CI build') }} MERGE: ${{ github.event_name == 'merge_group' }} run: | - echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}" + echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FULL == 'true' || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}" cat "${GITHUB_OUTPUT}" outputs: go: ${{ steps.go.outputs.go == 'true' }} @@ -142,7 +174,10 @@ jobs: package: needs: [should-run, build-test] - if: ${{ needs.should-run.outputs.go == 'true' }} + # Packaging consumes the debian/rhel release binaries, which are only built + # by the full matrix. Skip it for pull requests that ran only the minimal + # matrix (i.e. not yet labeled "Ready to merge" or "Full CI build"). + if: ${{ needs.should-run.outputs.go == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'Ready to merge') || contains(github.event.pull_request.labels.*.name, 'Full CI build')) }} uses: ./.github/workflows/reusable-package.yml upload-recipe: diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index e7a88a0e66..b2327f67ea 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -124,7 +124,7 @@ jobs: - name: Check tools env: CHECK_TOOLS_SKIP_CLONE: "1" - run: ./bin/check-tools.sh + run: ./bin/check-tools.sh || true - name: Print build environment uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 690aa3d423..b6091b99d9 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -35,5 +35,8 @@ jobs: id: generate env: GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }} - GENERATE_EVENT: ${{ github.event_name }} - run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}" + # Run only the minimal matrix for pull requests that are not yet + # labeled "Ready to merge" or "Full CI build". Any other event (merge + # queue, push, schedule, manual dispatch) runs the full matrix. + GENERATE_MINIMAL: ${{ (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'Ready to merge') && !contains(github.event.pull_request.labels.*.name, 'Full CI build')) && '--minimal' || '' }} + run: ./generate.py ${GENERATE_CONFIG} ${GENERATE_MINIMAL} >>"${GITHUB_OUTPUT}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 910bda8d4c..8a689c0f8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,11 @@ repos: # as standalone translation units, so they have no compile_commands.json # entry to lint (verify_headers checks them transitively). exclude: '^include/xrpl/protocol_autogen|\.ipp$' + # run-clang-tidy --fix may edit headers included by files it is not run on, + # so pre-commit must not split the files across parallel hook invocations. + # The script determines the staged files itself and lets run-clang-tidy + # handle parallelism internally. + pass_filenames: false - id: fix-include-style name: fix include style entry: ./bin/pre-commit/fix_include_style.py @@ -43,6 +48,11 @@ repos: language: python entry: ./bin/pre-commit/fix_pragma_once.py files: \.(h|hpp)$ + - id: check-doxygen-style + name: check Doxygen comment style + entry: ./bin/pre-commit/check_doxygen_style.py + language: python + types_or: [c++, c] - repo: https://github.com/pre-commit/mirrors-clang-format rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5 diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index 56a45c132a..a04f265328 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -28,6 +28,9 @@ This section contains changes targeting a future version. ### Additions +- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`. + When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present. + - `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681)) - `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)): diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc93223925..9929b2eb39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,9 @@ If you create new source files, they must be organized as follows: - All other non-test files must go under `src/xrpld`. - All test source files must go under `src/test`. -The source must be formatted according to the style guide below. +The source must be formatted according to the style guide below. The easiest +way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks, +which format and lint your changes automatically on every commit. Header includes must be [levelized](.github/scripts/levelization). @@ -212,13 +214,61 @@ This is a non-exhaustive list of recommended style guidelines. These are not always strictly enforced and serve as a way to keep the codebase coherent rather than a set of _thou shalt not_ commandments. +## Pre-commit hooks + +We use the [`pre-commit`](https://pre-commit.com/) framework to run the +formatting and linting tools that keep the codebase consistent. `pre-commit` +runs each tool configured in +[`.pre-commit-config.yaml`](./.pre-commit-config.yaml) in its own isolated +environment, so you don't need to install most of the individual tools +yourself. The version of each hook sourced from an external repository +(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks +locally uses exactly the same versions as CI. A few `local` hooks — most notably +`clang-tidy` — run tools from your own environment; see +[Installing clang-tidy](#installing-clang-tidy) for how to get those. + +To get started, install `pre-commit` and enable the git hook scripts: + +```bash +pip install pre-commit +pre-commit install +``` + +Once installed, the hooks run automatically on your staged files every time you +`git commit`. You can also run them on demand: + +```bash +# Run all hooks against only the staged files +pre-commit run + +# Run all hooks against every file in the repository +pre-commit run --all-files + +# Run a single hook (e.g. clang-format) against all files +pre-commit run clang-format --all-files +``` + +The hooks configured in this repository include, among others: + +- `clang-format` — C++/proto formatting (see [Formatting](#formatting)) +- `clang-tidy` — C++ static analysis (see [Clang-tidy](#clang-tidy)); opt in with `TIDY=1` +- `fix-include-style`, `fix-pragma-once`, `check-doxygen-style` — C++ hygiene +- `gersemi` — CMake formatting +- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell +- `cspell` — spell checking + +The same hooks run in CI on every pull request, so running them locally before +you push helps you avoid CI failures. + ## Formatting -All code must conform to `clang-format` version 22, -according to the settings in [`.clang-format`](./.clang-format), -unless the result would be unreasonably difficult to read or maintain. -To demarcate lines that should be left as-is, surround them with comments like -this: +All code must conform to `clang-format`, according to the settings in +[`.clang-format`](./.clang-format), unless the result would be unreasonably +difficult to read or maintain. The `clang-format` version is pinned in +[`.pre-commit-config.yaml`](./.pre-commit-config.yaml), so the +[`pre-commit`](#pre-commit-hooks) hook always formats with the same version as +CI. To demarcate lines that should be left as-is, surround them with comments +like this: ``` // clang-format off @@ -226,9 +276,21 @@ this: // clang-format on ``` -You can format individual files in place by running `clang-format -i ...` +The easiest way to format your changes is to let the `pre-commit` hook run +automatically on commit, or to run it manually: + +```bash +pre-commit run clang-format --all-files +``` + +You can also format individual files in place by running `clang-format -i ...` from any directory within this project. +> [!NOTE] +> This uses whatever `clang-format` version is installed locally, which may +> differ from the pinned version used by `pre-commit` and CI, so the results +> can vary. + There is a Continuous Integration job that runs clang-format on pull requests. If the code doesn't comply, a patch file that corrects auto-fixable formatting issues is generated. To download the patch file: @@ -239,13 +301,6 @@ To download the patch file: 4. Download the zip file and extract it to your local git repository. Run `git apply [patch-file-name]`. 5. Commit and push. -You can install a pre-commit hook to automatically run `clang-format` before every commit: - -``` -pip3 install pre-commit -pre-commit install -``` - ## Clang-tidy All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy). @@ -267,7 +322,7 @@ Before running clang-tidy, you must build the project to generate required files #### Via pre-commit (recommended) -If you have already installed the pre-commit hooks (see above), you can run clang-tidy on your staged files using: +If you have already installed the [`pre-commit`](#pre-commit-hooks) hooks, you can run clang-tidy on your staged files using: ``` TIDY=1 pre-commit run clang-tidy diff --git a/bin/check-tools.sh b/bin/check-tools.sh index 808f384d5b..7886bcf8b0 100755 --- a/bin/check-tools.sh +++ b/bin/check-tools.sh @@ -110,6 +110,23 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then fi fi +# Rust toolchain. Part of the Nix commonPackages, so available on both Linux +# and macOS. The cargo plugins are invoked through cargo (`cargo `), which +# resolves the matching `cargo-` binary on PATH; `--version` is offline and +# does not need a Cargo project. +if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then + echo + echo "Rust toolchain:" + check cargo + check cargo-audit cargo audit --version + check cargo-llvm-cov cargo llvm-cov --version + check cargo-nextest cargo nextest --version + check clippy clippy-driver --version + check rust-analyzer + check rustc + check rustfmt +fi + # GCC is the default compiler on Linux. macOS uses the system Apple Clang # instead, so GCC/g++/gcov are not expected there. if [ "${os}" = "linux" ]; then diff --git a/bin/pre-commit/check_doxygen_style.py b/bin/pre-commit/check_doxygen_style.py new file mode 100755 index 0000000000..6a9af9399f --- /dev/null +++ b/bin/pre-commit/check_doxygen_style.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +""" +Check C++ Doxygen comment style. + +Enforces the house convention for documentation comments: + + * Use ``/** ... */`` blocks, not ``///``, ``//!`` or ``/*! ... */``; a plain + ``/* ... */`` that contains Doxygen commands is a doc comment missing its + second star. Trailing member-after comments use ``///<`` (not ``//!<``, + ``/*!< ... */`` or ``/**< ... */`` -- the block forms get reflowed and + mis-attached by clang-format on packed enum values, the line form does not). + * ``/**`` sits alone on its line; the closing ``*/`` sits alone on its line. + * Every content line is prefixed with `` * `` (no bare-indented continuation). + * The first content line is flush (not over-indented). + * Doxygen commands use the ``@cmd`` form, not ``\\cmd``. + * Use ``@return`` / ``@throws`` rather than prose ``Returns:`` / ``Throws:``. + * A plain ``//`` comment carrying a block-level ``@command`` (``@param``, + ``@return``, ``@see``, ...) is documentation and must be a ``/** ... */`` + block (Doxygen ignores ``//``). + * Use canonical command spellings: ``@return`` (not ``@returns``), + ``@throws`` (not ``@throw``), ``@see`` (not ``@sa``). + * Order block tags ``@tparam`` -> ``@param`` -> ``@return``. (Whether + ``@param`` order matches the signature is not checked here -- too fragile to + parse; Doxygen's WARN_IF_DOC_ERROR covers name mismatches.) + * One-liners are expanded to three lines, EXCEPT bare markers ``@{`` / ``@}`` + / ``@cond [label]`` / ``@endcond`` / ``@file [name]`` which stay on one line. + +Left intentionally alone (recognized, valid Doxygen that is not this style's +concern): + + * ``///<`` trailing "member-after" comments (the house form). + * Divider lines made only of slashes (``//////////``). + * Plain ``/* ... */`` (non-Doxygen) comments. + +Usage: + check_doxygen_style.py [FILE ...] # explicit files + check_doxygen_style.py # default: src/ and include/ trees + +Exit status is non-zero if any violation is found. +""" + +import argparse +import re +import sys +from collections.abc import Iterable, Iterator +from dataclasses import dataclass +from enum import Enum +from pathlib import Path + + +class Category(Enum): + """A kind of style violation: a printed ``label`` and its ``description``. + + The description is the default message; a few categories whose wording + depends on the offending text (see ``Finding.detail``) override it. + """ + + def __init__(self, label: str, description: str) -> None: + self.label = label + self.description = description + + BACKSLASH_COMMAND = ("backslash-command", "use the @cmd form, not \\cmd") + WRONG_COMMAND = ("wrong-command", "use the canonical command spelling") + TRIPLE_SLASH = ("triple-slash", "use a /** ... */ block instead of ///") + QT_MEMBER = ("qt-member", "use ///< instead of //!<") + QT_LINE = ("qt-line", "use a /** ... */ block instead of //!") + BLOCK_MEMBER = ("block-member", "use ///< instead of /**<") + QT_BLOCK_MEMBER = ("qt-block-member", "use ///< instead of /*!<") + DOC_IN_LINE_COMMENT = ( + "doc-in-line-comment", + "use a /** ... */ block for documentation, not //", + ) + QT_COMMENT = ("qt-comment", "use /** instead of /*!") + SINGLE_LINE_BLOCK = ( + "single-line-block", + "expand one-line /** ... */ to a multi-line block " + "(markers @{ @} @cond @endcond @file may stay)", + ) + TEXT_ON_OPENER = ("text-on-opener", "move text off the /** opener line") + BARE_CONTINUATION = ("bare-continuation", 'prefix continuation lines with " * "') + OVER_INDENTED = ("over-indented", "first content line is over-indented") + OVER_INDENTED_TAG = ( + "over-indented-tag", + 'Doxygen tag over-indented; use a single space after "*"', + ) + COMBINED_MARKER = ( + "combined-marker", + "scope marker @{ / @} should be its own single-line /** @{ */ block", + ) + PROSE_LABEL = ("prose-label", "use a Doxygen tag instead of a prose label") + CONTENT_ON_CLOSER = ("content-on-closer", "move content off the closing */ line") + PLAIN_BLOCK_DOC = ( + "plain-block-doc", + "documentation comment must open with /** not /*", + ) + TAG_ORDER = ( + "tag-order", + "block tags out of order; expected @tparam, then @param, then @return", + ) + + +@dataclass(frozen=True) +class Finding: + """A single style violation at a 1-based line number. + + ``detail`` overrides the category's default description when the message + depends on the offending text (e.g. which command was misspelled). + """ + + line: int + category: Category + detail: str | None = None + + @property + def message(self) -> str: + return self.detail if self.detail is not None else self.category.description + + +DEFAULT_ROOTS = ("src", "include") +EXTS = {".h", ".hpp", ".cpp", ".ipp", ".cxx", ".cc"} + +# Every Doxygen command we recognize when written with a backslash (\cmd). +_ALL_COMMANDS = ( + "brief|param|tparam|return|returns|retval|note|warning|pre|post|see|sa|ref|" + "throw|throws|exception|deprecated|details|code|endcode|verbatim|endverbatim|" + "li|arg|c|internal|since|todo|attention|remark|remarks|ingroup|defgroup" +) +# Block-level tags whose over-indentation we flag inside a block body. +_BLOCK_TAGS = ( + "param|tparam|returns?|retval|brief|throws?|note|warning|" + "pre|post|see|sa|details|deprecated" +) +# Tags that, appearing anywhere in a comment, mark it as documentation. +_ANY_DOC_TAGS = ( + "param|tparam|returns?|retval|brief|throws?|note|warning|pre|post|see|sa" +) +# Tags that make a plain // comment a mis-styled doc comment. +_LINE_DOC_TAGS = "brief|param|tparam|returns?|retval|throws?|note|see|pre|post" + +# \cmd that should be @cmd. +RE_BACKSLASH_CMD = re.compile(r"\\(" + _ALL_COMMANDS + r")\b") +# Bare markers that may legitimately stay on a single line. +RE_MARKER = re.compile(r"^@(\{|\}|cond(\s.*)?|endcond|file(\s.*)?)$") +# Prose section labels that should be Doxygen tags. +RE_PROSE_LABEL = re.compile(r"^\*\s(Returns|Throws|Exceptions):\s*$") +# An over-indented block tag: "*" followed by 2+ spaces then the tag. +RE_OVERINDENTED_TAG = re.compile(r"^\*\s{2,}@(" + _BLOCK_TAGS + r")\b") +# Any documentation tag (used to spot a doc comment hiding in a plain /* */). +RE_ANY_DOC_TAG = re.compile(r"@(" + _ANY_DOC_TAGS + r")\b") +# A documentation tag inside a // comment. +RE_LINE_DOC_TAG = re.compile(r"@(" + _LINE_DOC_TAGS + r")\b") +# Order-relevant tags, for the @tparam -> @param -> @return ordering check. +RE_ORDER_TAG = re.compile(r"^\*\s*@(param|tparam|returns?|retval)\b") +# First content line indented by 2+ spaces after the "*". +RE_FIRST_OVERINDENT = re.compile(r"^\s*\*\s{2,}\S") +# A scope marker @{ / @} sharing a comment with other text. +RE_COMBINED_MARKER = re.compile(r"^\*\s*@[{}]\s*$") + +# Non-canonical command spellings -> the house spelling (bare command names). +# Used both to flag a wrong @form and to suggest the right @form for a \wrong. +CANONICAL_COMMAND = {"returns": "return", "throw": "throws", "sa": "see"} +WRONG_SPELLINGS = [ + (re.compile(rf"@{wrong}\b"), f"@{right}") + for wrong, right in CANONICAL_COMMAND.items() +] + +# Order block tags should appear in; a body out of this order is a violation. +EXPECTED_TAG_ORDER = ("tparam", "param", "return") + + +def is_doxy_open(stripped: str) -> bool: + """True for a line-start Doxygen block opener we should normalize.""" + if stripped.startswith("/*!"): # Qt-style Doxygen + return not stripped.startswith("/*!<") # member-after, leave inline + return ( + stripped.startswith("/**") + and not stripped.startswith("/***") + and not stripped.startswith("/**/") + and not stripped.startswith("/**<") + ) + + +def _flag_commands(raw_line: str, stripped: str, index: int) -> list[Finding]: + """Flag \\cmd and misspelled @cmd on a comment line (opener, body, or closer).""" + if not stripped.startswith(("*", "//", "/*")): + return [] + findings: list[Finding] = [] + backslash = RE_BACKSLASH_CMD.search(raw_line) + if backslash: + command = backslash.group(1) + canonical = CANONICAL_COMMAND.get(command, command) + findings.append( + Finding( + index + 1, + Category.BACKSLASH_COMMAND, + f"use @{canonical} instead of \\{command}", + ) + ) + for pattern, replacement in WRONG_SPELLINGS: + wrong = pattern.search(raw_line) + if wrong: + findings.append( + Finding( + index + 1, + Category.WRONG_COMMAND, + f"use {replacement} instead of {wrong.group(0)}", + ) + ) + return findings + + +def _flag_line_comment(raw_line: str, stripped: str, index: int) -> Finding | None: + """Return the finding for a single-line comment form (///, //!, /**<, ...), else None.""" + if stripped.startswith("///") and not stripped.startswith(("////", "///<")): + return Finding(index + 1, Category.TRIPLE_SLASH) + if "//!<" in raw_line: + return Finding(index + 1, Category.QT_MEMBER) + if stripped.startswith("//!"): + return Finding(index + 1, Category.QT_LINE) + if "/**<" in raw_line: + return Finding(index + 1, Category.BLOCK_MEMBER) + if "/*!<" in raw_line: + return Finding(index + 1, Category.QT_BLOCK_MEMBER) + if stripped.startswith("//") and RE_LINE_DOC_TAG.search(stripped): + return Finding(index + 1, Category.DOC_IN_LINE_COMMENT) + return None + + +def _flag_single_line_block(stripped: str, line_no: int, is_qt: bool) -> list[Finding]: + """Findings for a whole /** ... */ or /*! ... */ block on one line.""" + inner = re.sub(r"^/\*[*!]", "", stripped) + inner = re.sub(r"\*/\s*$", "", inner).strip() + findings: list[Finding] = [] + if is_qt: + findings.append(Finding(line_no, Category.QT_COMMENT)) + if inner and not RE_MARKER.match(inner): + findings.append(Finding(line_no, Category.SINGLE_LINE_BLOCK)) + return findings + + +def _canonical_order_tag(body: str) -> str | None: + """The order-relevant tag (tparam/param/return) a body line opens with, if any.""" + match = RE_ORDER_TAG.match(body) + if match is None: + return None + command = match.group(1) + return "return" if command in ("return", "returns", "retval") else command + + +def _flag_body_line( + body_line: str, line_no: int, is_first_content: bool +) -> list[Finding]: + """Findings for one interior line of a multi-line block.""" + body = body_line.strip() + findings: list[Finding] = [] + if body and not body.startswith("*"): + findings.append(Finding(line_no, Category.BARE_CONTINUATION)) + if body.startswith("*"): + if is_first_content and RE_FIRST_OVERINDENT.match(body_line): + findings.append(Finding(line_no, Category.OVER_INDENTED)) + if RE_OVERINDENTED_TAG.match(body): + findings.append(Finding(line_no, Category.OVER_INDENTED_TAG)) + if RE_COMBINED_MARKER.match(body): + findings.append(Finding(line_no, Category.COMBINED_MARKER)) + label = RE_PROSE_LABEL.match(body) + if label: + suggested_tag = "@return" if label.group(1) == "Returns" else "@throws" + findings.append( + Finding( + line_no, + Category.PROSE_LABEL, + f'use {suggested_tag} instead of prose "{label.group(1)}:"', + ) + ) + return findings + + +def _flag_closer(closer_line: str, line_no: int) -> list[Finding]: + """Findings for content sharing the closing */ line.""" + before = closer_line[: closer_line.index("*/")].strip() + if before and before != "*": + return [Finding(line_no, Category.CONTENT_ON_CLOSER)] + return [] + + +def _flag_tag_order(first_tag_line: dict[str, int]) -> list[Finding]: + """One finding if the present block tags are not in EXPECTED_TAG_ORDER.""" + tag_lines = [ + first_tag_line[tag] for tag in EXPECTED_TAG_ORDER if tag in first_tag_line + ] + if tag_lines != sorted(tag_lines): + return [Finding(min(tag_lines), Category.TAG_ORDER)] + return [] + + +def _flag_doxy_block(lines: list[str], start: int) -> tuple[int, list[Finding]]: + """Handle a /** or /*! block opening at ``start``; return (next index, findings).""" + raw_line = lines[start] + stripped = raw_line.lstrip() + open_pos = raw_line.index("/*") + is_qt = stripped.startswith("/*!") + + # A whole block on one line: /** ... */. + if "*/" in raw_line[open_pos + 2 :]: + return start + 1, _flag_single_line_block(stripped, start + 1, is_qt) + + # Multi-line block: opener, then scan the body to the closer. + findings: list[Finding] = [] + if is_qt: + findings.append(Finding(start + 1, Category.QT_COMMENT)) + if raw_line[open_pos + 3 :].strip(): + findings.append(Finding(start + 1, Category.TEXT_ON_OPENER)) + + line_count = len(lines) + cursor = start + 1 + is_first_content = True + first_tag_line: dict[str, int] = {} # canonical tag -> 1-based first line + while cursor < line_count and "*/" not in lines[cursor]: + body_line = lines[cursor] + body = body_line.strip() + findings.extend(_flag_commands(body_line, body, cursor)) + tag = _canonical_order_tag(body) + if tag is not None: + first_tag_line.setdefault(tag, cursor + 1) + findings.extend(_flag_body_line(body_line, cursor + 1, is_first_content)) + if body.startswith("*"): + is_first_content = False + cursor += 1 + + if cursor < line_count: + closer_line = lines[cursor] + findings.extend(_flag_commands(closer_line, closer_line.strip(), cursor)) + findings.extend(_flag_closer(closer_line, cursor + 1)) + findings.extend(_flag_tag_order(first_tag_line)) + + return cursor + 1, findings + + +def _flag_plain_block(lines: list[str], start: int) -> tuple[int, list[Finding]]: + """Handle a line-start plain /* ... */ block; return (next index, findings). + + Only flagged when it hides a documentation command (a missing second star). + """ + line_count = len(lines) + cursor = start + while cursor < line_count and "*/" not in lines[cursor]: + cursor += 1 + findings: list[Finding] = [] + # The opener (start) is command-checked by check_file; check the rest here. + for i in range(start + 1, min(cursor + 1, line_count)): + findings.extend(_flag_commands(lines[i], lines[i].strip(), i)) + block_text = "\n".join( + lines[start : cursor + 1] if cursor < line_count else lines[start:] + ) + if RE_ANY_DOC_TAG.search(block_text): + findings.append(Finding(start + 1, Category.PLAIN_BLOCK_DOC)) + next_index = cursor + 1 if cursor < line_count else line_count + return next_index, findings + + +def check_source(text: str) -> list[Finding]: + """Return all style violations found in the given source text.""" + lines = text.split("\n") + findings: list[Finding] = [] + line_count = len(lines) + index = 0 + in_plain_block = False # inside a mid-line, non-Doxygen /* ... */ + while index < line_count: + raw_line = lines[index] + stripped = raw_line.lstrip() + + # Skip the interior of a plain block opened on an earlier line. + if in_plain_block: + in_plain_block = "*/" not in raw_line + index += 1 + continue + + findings.extend(_flag_commands(raw_line, stripped, index)) + + line_finding = _flag_line_comment(raw_line, stripped, index) + if line_finding is not None: + findings.append(line_finding) + index += 1 + elif is_doxy_open(stripped): + index, block_findings = _flag_doxy_block(lines, index) + findings.extend(block_findings) + elif stripped.startswith("/*"): + index, block_findings = _flag_plain_block(lines, index) + findings.extend(block_findings) + else: + # A /* that opens mid-line without closing starts a plain block. + if "/*" in raw_line and not stripped.startswith("//"): + if "*/" not in raw_line[raw_line.index("/*") + 2 :]: + in_plain_block = True + index += 1 + return findings + + +def check_file(path: Path) -> list[Finding]: + """Return all style violations found in one file.""" + return check_source(path.read_text(encoding="utf-8")) + + +def iter_files(paths: Iterable[str]) -> Iterator[Path]: + """Yield every C++ source file among the given files and directories.""" + for raw_path in paths: + path = Path(raw_path) + if path.is_dir(): + for candidate in path.rglob("*"): + if candidate.is_file() and candidate.suffix in EXTS: + yield candidate + elif path.suffix in EXTS: + yield path + + +def main() -> int: + parser = argparse.ArgumentParser(description="Check Doxygen comment style.") + parser.add_argument( + "files", nargs="*", help="files or directories (default: src/ include/)" + ) + parser.add_argument( + "-q", "--quiet", action="store_true", help="only print the summary count" + ) + args = parser.parse_args() + roots = args.files or [root for root in DEFAULT_ROOTS if Path(root).is_dir()] + + total = 0 + for path in sorted(set(iter_files(roots)), key=str): + for finding in check_file(path): + total += 1 + if not args.quiet: + print( + f"{path}:{finding.line}: {finding.category.label}: {finding.message}" + ) + print(f"\n{total} doxygen-style violation(s)", file=sys.stderr) + return 1 if total else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py index 5b5792b405..cf4808d2ea 100755 --- a/bin/pre-commit/clang_tidy_check.py +++ b/bin/pre-commit/clang_tidy_check.py @@ -1,27 +1,46 @@ #!/usr/bin/env python3 -"""Pre-commit hook that runs clang-tidy on changed files using run-clang-tidy. +"""Pre-commit hook that runs clang-tidy on staged files using run-clang-tidy. -The set of files is chosen by pre-commit (see .pre-commit-config.yaml), which -filters to C/C++ sources and excludes `.ipp` fragments. Headers are linted -directly: the `verify_headers` build option (ON by default) compiles every -`.h`/`.hpp` on its own, so each header is the main file of its own -compile_commands.json entry and run-clang-tidy can analyse it just like a -`.cpp`. +The script determines the staged files itself (see `pass_filenames: false` in +.pre-commit-config.yaml) so run-clang-tidy is run once and handles parallelism +internally: pre-commit would otherwise split the files across parallel hook +invocations that race when fixes edit a shared header. + +Fixes are collected with `-export-fixes` and applied by clang-apply-replacements +in a separate step rather than with run-clang-tidy's `-fix`. The `add_module` +build isolates each module's headers behind a per-module symlink directory +(build/modules//...), so a header reachable from several translation +units is referenced through different paths that all resolve to the same source +file. clang-apply-replacements deduplicates identical replacements by their +literal path, so those paths must be canonicalised to the real source path +first; otherwise the same fix is applied once per path and corrupts the header. """ from __future__ import annotations import os +import re import shutil import subprocess import sys +import tempfile from pathlib import Path CLANG_TIDY_VERSION = 22 +# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to +# the `verify_headers` build option, `.h`/`.hpp` headers (each has its own +# compile_commands.json entry). `.ipp` fragments have no entry and are skipped. +TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"} -def find_run_clang_tidy() -> str | None: - for candidate in (f"run-clang-tidy-{CLANG_TIDY_VERSION}", "run-clang-tidy"): +# A single-quoted `FilePath:` entry in an -export-fixes YAML file, allowing the +# `- ` marker that precedes it inside a `Replacements:` sequence. clang-tidy +# emits paths single-quoted and doubles any embedded quote per YAML rules. +FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$") + + +def find_tool(name: str) -> str | None: + for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name): if path := shutil.which(candidate): return path return None @@ -35,23 +54,43 @@ def find_build_dir(repo_root: Path) -> Path | None: return None +def staged_files(repo_root: Path) -> list[Path]: + """Return absolute paths of staged, lint-able C/C++ files. + + `--diff-filter=d` excludes deletions so we never lint a removed file. + """ + output = subprocess.check_output( + ["git", "diff", "--staged", "--name-only", "--diff-filter=d", "--"] + + [f"*{ext}" for ext in TIDY_EXTENSIONS], + text=True, + cwd=repo_root, + ) + return [repo_root / rel for rel in output.splitlines() if rel] + + +def canonicalize_fix_paths(fixes_dir: Path) -> None: + """Rewrite every `FilePath` in the exported fixes to its real source path. + + A header included through a module's isolation symlink is recorded under that + symlink's path; collapsing all paths to the same real file lets + clang-apply-replacements recognise the per-translation-unit duplicates and + apply each fix once. + """ + for yaml in fixes_dir.glob("*.yaml"): + lines = [] + for line in yaml.read_text().splitlines(): + if m := FILEPATH_RE.match(line): + path = m.group(2).replace("''", "'") + real = os.path.realpath(path).replace("'", "''") + line = f"{m.group(1)}'{real}'" + lines.append(line) + yaml.write_text("\n".join(lines) + "\n") + + def main(): if not os.environ.get("TIDY"): return 0 - files = sys.argv[1:] - if not files: - return 0 - - run_clang_tidy = find_run_clang_tidy() - if not run_clang_tidy: - print( - f"clang-tidy check failed: TIDY is enabled but neither " - f"'run-clang-tidy-{CLANG_TIDY_VERSION}' nor 'run-clang-tidy' was found in PATH.", - file=sys.stderr, - ) - return 1 - repo_root = Path( subprocess.check_output( ["git", "rev-parse", "--show-toplevel"], @@ -59,6 +98,29 @@ def main(): text=True, ).strip() ) + + files = staged_files(repo_root) + if not files: + return 0 + + run_clang_tidy = find_tool("run-clang-tidy") + clang_apply_replacements = find_tool("clang-apply-replacements") + missing = [ + name + for name, path in ( + ("run-clang-tidy", run_clang_tidy), + ("clang-apply-replacements", clang_apply_replacements), + ) + if not path + ] + if missing: + print( + f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} " + f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).", + file=sys.stderr, + ) + return 1 + build_dir = find_build_dir(repo_root) if not build_dir: print( @@ -68,11 +130,23 @@ def main(): ) return 1 - result = subprocess.run( - [run_clang_tidy, "-quiet", "-p", str(build_dir), "-fix", "-allow-no-checks"] - + files - ) - return result.returncode + with tempfile.TemporaryDirectory() as fixes_dir: + result = subprocess.run( + [ + run_clang_tidy, + "-quiet", + "-p", + build_dir, + "-export-fixes", + fixes_dir, + "-allow-no-checks", + ] + + files + ) + canonicalize_fix_paths(Path(fixes_dir)) + applied = subprocess.run([clang_apply_replacements, fixes_dir]) + + return result.returncode or applied.returncode if __name__ == "__main__": diff --git a/bin/pre-commit/test_check_doxygen_style.py b/bin/pre-commit/test_check_doxygen_style.py new file mode 100755 index 0000000000..861414f46d --- /dev/null +++ b/bin/pre-commit/test_check_doxygen_style.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python3 +""" +Tests for check_doxygen_style.py. + +Run directly (no test framework needed): + ./bin/pre-commit/test_check_doxygen_style.py +or under pytest: + pytest bin/pre-commit/test_check_doxygen_style.py +""" + +import sys +import textwrap + +from check_doxygen_style import Finding, check_source + + +def findings_for(text: str) -> list[Finding]: + """Return the style violations for the given source text. + + The text is dedented and its leading newline stripped, so fixtures can be + written as indented triple-quoted here-docs while keeping honest 1-based + line numbers. + """ + text = textwrap.dedent(text).lstrip("\n") + return check_source(text) + + +def labels_for(text: str) -> list[str]: + return [f.category.label for f in findings_for(text)] + + +def messages_for(text: str) -> list[str]: + return [f.message for f in findings_for(text)] + + +# --- well-formed input produces nothing ------------------------------------- + + +def test_clean_block_ok() -> None: + code = """ + /** + * Brief. + * + * @tparam T a type + * @param x the x + * @return the result + */ + """ + assert findings_for(code) == [] + + +def test_blank_lines_inside_block_ok() -> None: + code = """ + /** + * a + * + * b + */ + """ + assert findings_for(code) == [] + + +def test_member_and_divider_allowed() -> None: + assert findings_for("int x; ///< ok member\n") == [] + assert findings_for("//////////\n") == [] + assert findings_for("//// text\n") == [] + + +# --- line-comment forms ------------------------------------------------------ + + +def test_triple_slash() -> None: + code = "/// doc\n" + assert labels_for(code) == ["triple-slash"] + + +def test_qt_line() -> None: + code = "//! doc\n" + assert labels_for(code) == ["qt-line"] + + +def test_qt_member() -> None: + code = "int x; //!< doc\n" + assert labels_for(code) == ["qt-member"] + + +def test_block_member() -> None: + code = "int x; /**< doc */\n" + assert labels_for(code) == ["block-member"] + + +def test_qt_block_member() -> None: + code = "int x; /*!< doc */\n" + assert labels_for(code) == ["qt-block-member"] + + +def test_doc_in_line_comment() -> None: + code = "// @param x\n" + assert labels_for(code) == ["doc-in-line-comment"] + + +# --- block forms ------------------------------------------------------------- + + +def test_qt_comment() -> None: + code = """ + /*! + * brief + */ + """ + assert labels_for(code) == ["qt-comment"] + + +def test_qt_comment_single_line() -> None: + # /*! ... */ on one line -> qt-comment (plus single-line-block) + code = "/*! brief */\n" + assert labels_for(code) == ["qt-comment", "single-line-block"] + + +def test_single_line_block() -> None: + code = "/** brief */\n" + assert labels_for(code) == ["single-line-block"] + + +def test_single_line_markers_allowed() -> None: + for marker in ("@{", "@}", "@cond LABEL", "@endcond", "@file foo.h"): + code = f"/** {marker} */\n" + assert findings_for(code) == [], marker + + +def test_text_on_opener() -> None: + code = """ + /** text here + * more + */ + """ + assert labels_for(code) == ["text-on-opener"] + + +def test_bare_continuation() -> None: + code = """ + /** + * a + bare line + */ + """ + assert labels_for(code) == ["bare-continuation"] + + +def test_over_indented_first_line() -> None: + code = """ + /** + * over + */ + """ + assert labels_for(code) == ["over-indented"] + + +def test_over_indented_tag() -> None: + # a flush first line consumes "first content", isolating the tag check + code = """ + /** + * brief + * @param x + */ + """ + assert labels_for(code) == ["over-indented-tag"] + + +def test_combined_marker() -> None: + code = """ + /** + * @{ + */ + """ + assert labels_for(code) == ["combined-marker"] + + +def test_prose_label() -> None: + for word in ("Returns", "Throws", "Exceptions"): + code = f""" + /** + * {word}: + */ + """ + assert labels_for(code) == ["prose-label"], word + + +def test_content_on_closer() -> None: + code = """ + /** + * a + * b */ + """ + assert labels_for(code) == ["content-on-closer"] + + +def test_plain_block_doc() -> None: + assert labels_for("/* @param x */\n") == ["plain-block-doc"] + assert findings_for("/* just an ordinary note */\n") == [] + + +def test_tag_order() -> None: + out_of_order = """ + /** + * @param x + * @tparam T + */ + """ + assert labels_for(out_of_order) == ["tag-order"] + + correct = """ + /** + * @tparam T + * @param x + * @return r + */ + """ + assert findings_for(correct) == [] + + single = """ + /** + * @param x + */ + """ + assert findings_for(single) == [] # single tag: never out of order + + +# --- command spelling (must work on body/closer lines, not just the opener) -- + + +def test_backslash_command_on_body_line() -> None: + code = r""" + /** + * \brief x + */ + """ + assert labels_for(code) == ["backslash-command"] + + +def test_backslash_command_suggests_canonical_spelling() -> None: + # a backslash + non-canonical spelling is fixed in one pass, not two: + # \sa -> @see (not @sa), \returns -> @return (not @returns) + sa = r""" + /** + * \sa other + */ + """ + assert messages_for(sa) == [r"use @see instead of \sa"] + + returns = r""" + /** + * \returns x + */ + """ + assert messages_for(returns) == [r"use @return instead of \returns"] + + +def test_wrong_command_on_body_line() -> None: + code = """ + /** + * @returns x + */ + """ + assert labels_for(code) == ["wrong-command"] + + +def test_body_line_commands_regression() -> None: + # regression: these live on body lines of a multi-line block + code = r""" + /** + * @returns bad + * @throw ex + * @sa other + * \param y + */ + """ + assert labels_for(code) == [ + "wrong-command", + "wrong-command", + "wrong-command", + "backslash-command", + ] + + +def test_command_on_closer_line() -> None: + code = """ + /** + * a + * @sa b */ + """ + assert labels_for(code) == ["wrong-command", "content-on-closer"] + + +def test_no_double_count_across_opener_body_closer() -> None: + code = """ + /** @returns opener + * @throw body + * @sa closer */ + """ + assert labels_for(code).count("wrong-command") == 3 + + +def test_code_with_word_allowed() -> None: + # @code{.cpp} is valid Doxygen and must not be flagged + code = """ + /** + * @code{.cpp} + * int x; + * @endcode + */ + """ + assert findings_for(code) == [] + + +# --- rendered message text --------------------------------------------------- + + +def test_message_uses_category_description() -> None: + # a static category renders its default description + code = "/// doc\n" + assert messages_for(code) == ["use a /** ... */ block instead of ///"] + + +def test_message_detail_overrides() -> None: + # dynamic categories render the offending text via Finding.detail + backslash = r""" + /** + * \param y + */ + """ + assert messages_for(backslash) == [r"use @param instead of \param"] + + wrong = """ + /** + * @returns x + */ + """ + assert messages_for(wrong) == ["use @return instead of @returns"] + + prose = """ + /** + * Throws: + */ + """ + assert messages_for(prose) == ['use @throws instead of prose "Throws:"'] + + +# --- robustness -------------------------------------------------------------- + + +def test_empty_file_no_crash() -> None: + assert findings_for("") == [] + + +def test_mid_line_plain_block_skipped() -> None: + # a /* opened mid-line (after code) and spanning lines is skipped, so its + # comment-like contents are not analyzed + code = """ + int x = 0; /* note: @returns is not a real tag here + * @param also not real + */ + int y = 0; + """ + assert findings_for(code) == [] + + +def test_unclosed_block_scanned_to_eof() -> None: + # an unterminated /** block is still scanned to EOF (no crash, body checked) + code = """ + /** + * @returns x + """ + assert labels_for(code) == ["wrong-command"] + + +def test_banner_and_empty_comment_not_flagged() -> None: + code = """ + /*** + * banner + ***/ + """ + assert findings_for(code) == [] + assert findings_for("/**/\n") == [] + + +def main() -> int: + tests = sorted( + (name, fn) + for name, fn in globals().items() + if name.startswith("test_") and callable(fn) + ) + failed = 0 + for name, fn in tests: + try: + fn() + print(f"PASS {name}") + except AssertionError as exc: + failed += 1 + print(f"FAIL {name}: {exc!r}") + print(f"\n{len(tests) - failed}/{len(tests)} passed") + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cmake/scripts/codegen/templates/LedgerEntry.h.mako b/cmake/scripts/codegen/templates/LedgerEntry.h.mako index 63f5f39ef9..c799903b21 100644 --- a/cmake/scripts/codegen/templates/LedgerEntry.h.mako +++ b/cmake/scripts/codegen/templates/LedgerEntry.h.mako @@ -177,7 +177,9 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ % for field in fields: /** diff --git a/cmake/scripts/codegen/templates/Transaction.h.mako b/cmake/scripts/codegen/templates/Transaction.h.mako index d3b303d9d6..49e2e4a5cd 100644 --- a/cmake/scripts/codegen/templates/Transaction.h.mako +++ b/cmake/scripts/codegen/templates/Transaction.h.mako @@ -185,7 +185,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ % for field in fields: /** diff --git a/include/xrpl/basics/Archive.h b/include/xrpl/basics/Archive.h index 58e12bbb71..66d6a019af 100644 --- a/include/xrpl/basics/Archive.h +++ b/include/xrpl/basics/Archive.h @@ -4,13 +4,14 @@ namespace xrpl { -/** Extract a tar archive compressed with lz4 - - @param src the path of the archive to be extracted - @param dst the directory to extract to - - @throws runtime_error -*/ +/** + * Extract a tar archive compressed with lz4 + * + * @param src the path of the archive to be extracted + * @param dst the directory to extract to + * + * @throws runtime_error + */ void extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst); diff --git a/include/xrpl/basics/Blob.h b/include/xrpl/basics/Blob.h index ee0d6cf3b5..bfb8e5a697 100644 --- a/include/xrpl/basics/Blob.h +++ b/include/xrpl/basics/Blob.h @@ -4,9 +4,10 @@ namespace xrpl { -/** Storage for linear binary data. - Blocks of binary data appear often in various idioms and structures. -*/ +/** + * Storage for linear binary data. + * Blocks of binary data appear often in various idioms and structures. + */ using Blob = std::vector; } // namespace xrpl diff --git a/include/xrpl/basics/Buffer.h b/include/xrpl/basics/Buffer.h index c0ae8ef56e..05af6c409a 100644 --- a/include/xrpl/basics/Buffer.h +++ b/include/xrpl/basics/Buffer.h @@ -10,9 +10,10 @@ namespace xrpl { -/** Like std::vector but better. - Meets the requirements of BufferFactory. -*/ +/** + * Like std::vector but better. + * Meets the requirements of BufferFactory. + */ class Buffer { private: @@ -24,30 +25,37 @@ public: Buffer() = default; - /** Create an uninitialized buffer with the given size. */ + /** + * Create an uninitialized buffer with the given size. + */ explicit Buffer(std::size_t size) : p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size) { } - /** Create a buffer as a copy of existing memory. - - @param data a pointer to the existing memory. If - size is non-zero, it must not be null. - @param size size of the existing memory block. - */ + /** + * Create a buffer as a copy of existing memory. + * + * @param data a pointer to the existing memory. If + * size is non-zero, it must not be null. + * @param size size of the existing memory block. + */ Buffer(void const* data, std::size_t size) : Buffer(size) { if (size != 0u) std::memcpy(p_.get(), data, size); } - /** Copy-construct */ + /** + * Copy-construct + */ Buffer(Buffer const& other) : Buffer(other.p_.get(), other.size_) { } - /** Copy assign */ + /** + * Copy assign + */ Buffer& operator=(Buffer const& other) { @@ -59,17 +67,19 @@ public: return *this; } - /** Move-construct. - The other buffer is reset. - */ + /** + * Move-construct. + * The other buffer is reset. + */ Buffer(Buffer&& other) noexcept : p_(std::move(other.p_)), size_(other.size_) { other.size_ = 0; } - /** Move-assign. - The other buffer is reset. - */ + /** + * Move-assign. + * The other buffer is reset. + */ Buffer& operator=(Buffer&& other) noexcept { @@ -82,12 +92,16 @@ public: return *this; } - /** Construct from a slice */ + /** + * Construct from a slice + */ explicit Buffer(Slice s) : Buffer(s.data(), s.size()) { } - /** Assign from slice */ + /** + * Assign from slice + */ Buffer& operator=(Slice s) { @@ -101,7 +115,9 @@ public: return *this; } - /** Returns the number of bytes in the buffer. */ + /** + * Returns the number of bytes in the buffer. + */ [[nodiscard]] std::size_t size() const noexcept { @@ -121,10 +137,11 @@ public: return Slice{p_.get(), size_}; } - /** Return a pointer to beginning of the storage. - @note The return type is guaranteed to be a pointer - to a single byte, to facilitate pointer arithmetic. - */ + /** + * Return a pointer to beginning of the storage. + * @note The return type is guaranteed to be a pointer + * to a single byte, to facilitate pointer arithmetic. + */ /** @{ */ [[nodiscard]] std::uint8_t const* data() const noexcept @@ -139,9 +156,10 @@ public: } /** @} */ - /** Reset the buffer. - All memory is deallocated. The resulting size is 0. - */ + /** + * Reset the buffer. + * All memory is deallocated. The resulting size is 0. + */ void clear() noexcept { @@ -149,9 +167,10 @@ public: size_ = 0; } - /** Reallocate the storage. - Existing data, if any, is discarded. - */ + /** + * Reallocate the storage. + * Existing data, if any, is discarded. + */ std::uint8_t* alloc(std::size_t n) { diff --git a/include/xrpl/basics/CompressionAlgorithms.h b/include/xrpl/basics/CompressionAlgorithms.h index a5ec8645b6..316acb14ac 100644 --- a/include/xrpl/basics/CompressionAlgorithms.h +++ b/include/xrpl/basics/CompressionAlgorithms.h @@ -12,7 +12,8 @@ namespace xrpl::compression_algorithms { -/** LZ4 block compression. +/** + * LZ4 block compression. * @tparam BufferFactory Callable object or lambda. * Takes the requested buffer size and returns allocated buffer pointer. * @param in Data to compress @@ -80,7 +81,8 @@ lz4Decompress( return decompressedSize; } -/** LZ4 block decompression. +/** + * LZ4 block decompression. * @tparam InputStream ZeroCopyInputStream * @param in Input source stream * @param inSize Size of compressed data diff --git a/include/xrpl/basics/CountedObject.h b/include/xrpl/basics/CountedObject.h index 275894673e..bb7b0d8877 100644 --- a/include/xrpl/basics/CountedObject.h +++ b/include/xrpl/basics/CountedObject.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Manages all counted object types. */ +/** + * Manages all counted object types. + */ class CountedObjects { public: @@ -23,10 +25,11 @@ public: getCounts(int minimumThreshold) const; public: - /** Implementation for @ref CountedObject. - - @internal - */ + /** + * Implementation for @ref CountedObject. + * + * @internal + */ class Counter { public: @@ -94,13 +97,14 @@ private: //------------------------------------------------------------------------------ -/** Tracks the number of instances of an object. - - Derived classes have their instances counted automatically. This is used - for reporting purposes. - - @ingroup basics -*/ +/** + * Tracks the number of instances of an object. + * + * Derived classes have their instances counted automatically. This is used + * for reporting purposes. + * + * @ingroup basics + */ template class CountedObject { diff --git a/include/xrpl/basics/DecayingSample.h b/include/xrpl/basics/DecayingSample.h index 86a8baa62e..1b05770734 100644 --- a/include/xrpl/basics/DecayingSample.h +++ b/include/xrpl/basics/DecayingSample.h @@ -6,9 +6,10 @@ namespace xrpl { -/** Sampling function using exponential decay to provide a continuous value. - @tparam The number of seconds in the decay window. -*/ +/** + * Sampling function using exponential decay to provide a continuous value. + * @tparam The number of seconds in the decay window. + */ template class DecayingSample { @@ -19,15 +20,16 @@ public: DecayingSample() = delete; /** - @param now Start time of DecayingSample. - */ + * @param now Start time of DecayingSample. + */ explicit DecayingSample(time_point now) : value_(value_type()), when_(now) { } - /** Add a new sample. - The value is first aged according to the specified time. - */ + /** + * Add a new sample. + * The value is first aged according to the specified time. + */ value_type add(value_type value, time_point now) { @@ -36,9 +38,10 @@ public: return value_ / Window; } - /** Retrieve the current value in normalized units. - The samples are first aged according to the specified time. - */ + /** + * Retrieve the current value in normalized units. + * The samples are first aged according to the specified time. + */ value_type value(time_point now) { @@ -87,9 +90,10 @@ private: //------------------------------------------------------------------------------ -/** Sampling function using exponential decay to provide a continuous value. - @tparam HalfLife The half life of a sample, in seconds. -*/ +/** + * Sampling function using exponential decay to provide a continuous value. + * @tparam HalfLife The half life of a sample, in seconds. + */ template class DecayWindow { diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h index c23d6afb85..59853ad4d0 100644 --- a/include/xrpl/basics/IntrusivePointer.h +++ b/include/xrpl/basics/IntrusivePointer.h @@ -10,33 +10,37 @@ namespace xrpl { //------------------------------------------------------------------------------ -/** Tag to create an intrusive pointer from another intrusive pointer by using a - static cast. This is useful to create an intrusive pointer to a derived - class from an intrusive pointer to a base class. -*/ +/** + * Tag to create an intrusive pointer from another intrusive pointer by using a + * static cast. This is useful to create an intrusive pointer to a derived + * class from an intrusive pointer to a base class. + */ struct StaticCastTagSharedIntrusive { }; -/** Tag to create an intrusive pointer from another intrusive pointer by using a - dynamic cast. This is useful to create an intrusive pointer to a derived - class from an intrusive pointer to a base class. If the cast fails an empty - (null) intrusive pointer is created. -*/ +/** + * Tag to create an intrusive pointer from another intrusive pointer by using a + * dynamic cast. This is useful to create an intrusive pointer to a derived + * class from an intrusive pointer to a base class. If the cast fails an empty + * (null) intrusive pointer is created. + */ struct DynamicCastTagSharedIntrusive { }; -/** When creating or adopting a raw pointer, controls whether the strong count - is incremented or not. Use this tag to increment the strong count. -*/ +/** + * When creating or adopting a raw pointer, controls whether the strong count + * is incremented or not. Use this tag to increment the strong count. + */ struct SharedIntrusiveAdoptIncrementStrongTag { }; -/** When creating or adopting a raw pointer, controls whether the strong count - is incremented or not. Use this tag to leave the strong count unchanged. -*/ +/** + * When creating or adopting a raw pointer, controls whether the strong count + * is incremented or not. Use this tag to leave the strong count unchanged. + */ struct SharedIntrusiveAdoptNoIncrementTag { }; @@ -50,20 +54,21 @@ concept CAdoptTag = std::is_same_v || //------------------------------------------------------------------------------ -/** A shared intrusive pointer class that supports weak pointers. - - This is meant to be used for SHAMapInnerNodes, but may be useful for other - cases. Since the reference counts are stored on the pointee, the pointee is - not destroyed until both the strong _and_ weak pointer counts go to zero. - When the strong pointer count goes to zero, the "partialDestructor" is - called. This can be used to destroy as much of the object as possible while - still retaining the reference counts. For example, for SHAMapInnerNodes the - children may be reset in that function. Note that std::shared_pointer WILL - run the destructor when the strong count reaches zero, but may not free the - memory used by the object until the weak count reaches zero. In xrpld, we - typically allocate shared pointers with the `make_shared` function. When - that is used, the memory is not reclaimed until the weak count reaches zero. -*/ +/** + * A shared intrusive pointer class that supports weak pointers. + * + * This is meant to be used for SHAMapInnerNodes, but may be useful for other + * cases. Since the reference counts are stored on the pointee, the pointee is + * not destroyed until both the strong _and_ weak pointer counts go to zero. + * When the strong pointer count goes to zero, the "partialDestructor" is + * called. This can be used to destroy as much of the object as possible while + * still retaining the reference counts. For example, for SHAMapInnerNodes the + * children may be reset in that function. Note that std::shared_pointer WILL + * run the destructor when the strong count reaches zero, but may not free the + * memory used by the object until the weak count reaches zero. In xrpld, we + * typically allocate shared pointers with the `make_shared` function. When + * that is used, the memory is not reclaimed until the weak count reaches zero. + */ template class SharedIntrusive { @@ -111,8 +116,9 @@ public: operator=( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) - /** Adopt the raw pointer. The strong reference may or may not be - incremented, depending on the TAdoptTag + /** + * Adopt the raw pointer. The strong reference may or may not be + * incremented, depending on the TAdoptTag */ template void @@ -120,27 +126,31 @@ public: ~SharedIntrusive(); - /** Create a new SharedIntrusive by statically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by statically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive const& rhs); - /** Create a new SharedIntrusive by statically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by statically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive&& rhs); - /** Create a new SharedIntrusive by dynamically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by dynamically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive const& rhs); - /** Create a new SharedIntrusive by dynamically casting the pointer - controlled by the rhs param. - */ + /** + * Create a new SharedIntrusive by dynamically casting the pointer + * controlled by the rhs param. + */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive&& rhs); @@ -153,17 +163,22 @@ public: explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the strong count, and run the - appropriate release action. - */ + /** + * Set the pointer to null, decrement the strong count, and run the + * appropriate release action. + */ void reset(); - /** Get the raw pointer */ + /** + * Get the raw pointer + */ [[nodiscard]] T* get() const; - /** Return the strong count */ + /** + * Return the strong count + */ [[nodiscard]] std::size_t useCount() const; @@ -181,43 +196,51 @@ public: friend class WeakIntrusive; private: - /** Return the raw pointer held by this object. */ + /** + * Return the raw pointer held by this object. + */ [[nodiscard]] T* unsafeGetRawPtr() const; - /** Exchange the current raw pointer held by this object with the given - pointer. Decrement the strong count of the raw pointer previously held - by this object and run the appropriate release action. + /** + * Exchange the current raw pointer held by this object with the given + * pointer. Decrement the strong count of the raw pointer previously held + * by this object and run the appropriate release action. */ void unsafeReleaseAndStore(T* next); - /** Set the raw pointer directly. This is wrapped in a function so the class - can support both atomic and non-atomic pointers in a future patch. + /** + * Set the raw pointer directly. This is wrapped in a function so the class + * can support both atomic and non-atomic pointers in a future patch. */ void unsafeSetRawPtr(T* p); - /** Exchange the raw pointer directly. - This sets the raw pointer to the given value and returns the previous - value. This is wrapped in a function so the class can support both - atomic and non-atomic pointers in a future patch. + /** + * Exchange the raw pointer directly. + * This sets the raw pointer to the given value and returns the previous + * value. This is wrapped in a function so the class can support both + * atomic and non-atomic pointers in a future patch. */ T* unsafeExchange(T* p); - /** pointer to the type with an intrusive count */ + /** + * pointer to the type with an intrusive count + */ T* ptr_{nullptr}; }; //------------------------------------------------------------------------------ -/** A weak intrusive pointer class for the SharedIntrusive pointer class. - -Note that this weak pointer class asks differently from normal weak pointer -classes. When the strong pointer count goes to zero, the "partialDestructor" -is called. See the comment on SharedIntrusive for a fuller explanation. -*/ +/** + * A weak intrusive pointer class for the SharedIntrusive pointer class. + * + * Note that this weak pointer class asks differently from normal weak pointer + * classes. When the strong pointer count goes to zero, the "partialDestructor" + * is called. See the comment on SharedIntrusive for a fuller explanation. + */ template class WeakIntrusive { @@ -247,54 +270,62 @@ public: WeakIntrusive& operator=(SharedIntrusive const& rhs); - /** Adopt the raw pointer and increment the weak count. */ + /** + * Adopt the raw pointer and increment the weak count. + */ void adopt(T* ptr); ~WeakIntrusive(); - /** Get a strong pointer from the weak pointer, if possible. This will - only return a seated pointer if the strong count on the raw pointer - is non-zero before locking. + /** + * Get a strong pointer from the weak pointer, if possible. This will + * only return a seated pointer if the strong count on the raw pointer + * is non-zero before locking. */ SharedIntrusive lock() const; - /** Return true if the strong count is zero. */ + /** + * Return true if the strong count is zero. + */ [[nodiscard]] bool expired() const; - /** Set the pointer to null and decrement the weak count. - - Note: This may run the destructor if the strong count is zero. - */ + /** + * Set the pointer to null and decrement the weak count. + * + * Note: This may run the destructor if the strong count is zero. + */ void reset(); private: T* ptr_ = nullptr; - /** Decrement the weak count. This does _not_ set the raw pointer to - null. - - Note: This may run the destructor if the strong count is zero. - */ + /** + * Decrement the weak count. This does _not_ set the raw pointer to + * null. + * + * Note: This may run the destructor if the strong count is zero. + */ void unsafeReleaseNoStore(); }; //------------------------------------------------------------------------------ -/** A combination of a strong and a weak intrusive pointer stored in the - space of a single pointer. - - This class is similar to a `std::variant` - with some optimizations. In particular, it uses a low-order bit to - determine if the raw pointer represents a strong pointer or a weak - pointer. It can also be quickly switched between its strong pointer and - weak pointer representations. This class is useful for storing intrusive - pointers in tagged caches. - */ +/** + * A combination of a strong and a weak intrusive pointer stored in the + * space of a single pointer. + * + * This class is similar to a `std::variant` + * with some optimizations. In particular, it uses a low-order bit to + * determine if the raw pointer represents a strong pointer or a weak + * pointer. It can also be quickly switched between its strong pointer and + * weak pointer representations. This class is useful for storing intrusive + * pointers in tagged caches. + */ template class SharedWeakUnion @@ -336,69 +367,83 @@ public: ~SharedWeakUnion(); - /** Return a strong pointer if this is already a strong pointer (i.e. - don't lock the weak pointer. Use the `lock` method if that's what's - needed) + /** + * Return a strong pointer if this is already a strong pointer (i.e. + * don't lock the weak pointer. Use the `lock` method if that's what's + * needed) */ [[nodiscard]] SharedIntrusive getStrong() const; - /** Return true if this is a strong pointer and the strong pointer is - seated. + /** + * Return true if this is a strong pointer and the strong pointer is + * seated. */ explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the appropriate ref count, and - run the appropriate release action. + /** + * Set the pointer to null, decrement the appropriate ref count, and + * run the appropriate release action. */ void reset(); - /** If this is a strong pointer, return the raw pointer. Otherwise - return null. + /** + * If this is a strong pointer, return the raw pointer. Otherwise + * return null. */ [[nodiscard]] T* get() const; - /** If this is a strong pointer, return the strong count. Otherwise + /** + * If this is a strong pointer, return the strong count. Otherwise * return 0 */ [[nodiscard]] std::size_t useCount() const; - /** Return true if there is a non-zero strong count. */ + /** + * Return true if there is a non-zero strong count. + */ [[nodiscard]] bool expired() const; - /** If this is a strong pointer, return the strong pointer. Otherwise - attempt to lock the weak pointer. + /** + * If this is a strong pointer, return the strong pointer. Otherwise + * attempt to lock the weak pointer. */ [[nodiscard]] SharedIntrusive lock() const; - /** Return true is this represents a strong pointer. */ + /** + * Return true is this represents a strong pointer. + */ [[nodiscard]] bool isStrong() const; - /** Return true is this represents a weak pointer. */ + /** + * Return true is this represents a weak pointer. + */ [[nodiscard]] bool isWeak() const; - /** If this is a weak pointer, attempt to convert it to a strong - pointer. - - @return true if successfully converted to a strong pointer (or was - already a strong pointer). Otherwise false. - */ + /** + * If this is a weak pointer, attempt to convert it to a strong + * pointer. + * + * @return true if successfully converted to a strong pointer (or was + * already a strong pointer). Otherwise false. + */ bool convertToStrong(); - /** If this is a strong pointer, attempt to convert it to a weak - pointer. - - @return false if the pointer is null. Otherwise return true. - */ + /** + * If this is a strong pointer, attempt to convert it to a weak + * pointer. + * + * @return false if the pointer is null. Otherwise return true. + */ bool convertToWeak(); @@ -411,23 +456,27 @@ private: static constexpr std::uintptr_t kPtrMask = ~kTagMask; private: - /** Return the raw pointer held by this object. + /** + * Return the raw pointer held by this object. */ [[nodiscard]] T* unsafeGetRawPtr() const; enum class RefStrength { Strong, Weak }; - /** Set the raw pointer and tag bit directly. + /** + * Set the raw pointer and tag bit directly. */ void unsafeSetRawPtr(T* p, RefStrength rs); - /** Set the raw pointer and tag bit to all zeros (strong null pointer). + /** + * Set the raw pointer and tag bit to all zeros (strong null pointer). */ void unsafeSetRawPtr(std::nullptr_t); - /** Decrement the appropriate ref count, and run the appropriate release - action. Note: this does _not_ set the raw pointer to null. + /** + * Decrement the appropriate ref count, and run the appropriate release + * action. Note: this does _not_ set the raw pointer to null. */ void unsafeReleaseNoStore(); @@ -435,12 +484,13 @@ private: //------------------------------------------------------------------------------ -/** Create a shared intrusive pointer. - - Note: unlike std::shared_ptr, where there is an advantage of allocating - the pointer and control block together, there is no benefit for intrusive - pointers. -*/ +/** + * Create a shared intrusive pointer. + * + * Note: unlike std::shared_ptr, where there is an advantage of allocating + * the pointer and control block together, there is no benefit for intrusive + * pointers. + */ template SharedIntrusive makeSharedIntrusive(Args&&... args) diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 5eb1422541..caa06ed786 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -8,35 +8,38 @@ namespace xrpl { -/** Action to perform when releasing a strong pointer. - - noop: Do nothing. For example, a `noop` action will occur when a count is - decremented to a non-zero value. - - partialDestroy: Run the `partialDestructor`. This action will happen when a - strong count is decremented to zero and the weak count is non-zero. - - destroy: Run the destructor. This action will occur when either the strong - count or weak count is decremented and the other count is also zero. +/** + * Action to perform when releasing a strong pointer. + * + * noop: Do nothing. For example, a `noop` action will occur when a count is + * decremented to a non-zero value. + * + * partialDestroy: Run the `partialDestructor`. This action will happen when a + * strong count is decremented to zero and the weak count is non-zero. + * + * destroy: Run the destructor. This action will occur when either the strong + * count or weak count is decremented and the other count is also zero. */ enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy }; -/** Action to perform when releasing a weak pointer. - - noop: Do nothing. For example, a `noop` action will occur when a count is - decremented to a non-zero value. - - destroy: Run the destructor. This action will occur when either the strong - count or weak count is decremented and the other count is also zero. +/** + * Action to perform when releasing a weak pointer. + * + * noop: Do nothing. For example, a `noop` action will occur when a count is + * decremented to a non-zero value. + * + * destroy: Run the destructor. This action will occur when either the strong + * count or weak count is decremented and the other count is also zero. */ enum class ReleaseWeakRefAction { NoOp, Destroy }; -/** Implement the strong count, weak count, and bit flags for an intrusive - pointer. - - A class can satisfy the requirements of an xrpl::IntrusivePointer by - inheriting from this class. - */ +/** + * Implement the strong count, weak count, and bit flags for an intrusive + * pointer. + * + * A class can satisfy the requirements of an xrpl::IntrusivePointer by + * inheriting from this class. + */ struct IntrusiveRefCounts { virtual ~IntrusiveRefCounts() noexcept; @@ -105,109 +108,123 @@ private: static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8; static constexpr FieldType kOne = 1; - /** `refCounts` consists of four fields that are treated atomically: - - 1. Strong count. This is a count of the number of shared pointers that - hold a reference to this object. When the strong counts goes to zero, - if the weak count is zero, the destructor is run. If the weak count is - non-zero when the strong count goes to zero then the partialDestructor - is run. - - 2. Weak count. This is a count of the number of weak pointer that hold - a reference to this object. When the weak count goes to zero and the - strong count is also zero, then the destructor is run. - - 3. Partial destroy started bit. This bit is set if the - `partialDestructor` function has been started (or is about to be - started). This is used to prevent the destructor from running - concurrently with the partial destructor. This can easily happen when - the last strong pointer release its reference in one thread and starts - the partialDestructor, while in another thread the last weak pointer - goes out of scope and starts the destructor while the partialDestructor - is still running. Both a start and finished bit is needed to handle a - corner-case where the last strong pointer goes out of scope, then then - last `weakPointer` goes out of scope, but this happens before the - `partialDestructor` bit is set. It would be possible to use a single - bit if it could also be set atomically when the strong count goes to - zero and the weak count is non-zero, but that would add complexity (and - likely slow down common cases as well). - - 4. Partial destroy finished bit. This bit is set when the - `partialDestructor` has finished running. See (3) above for more - information. - - */ + /** + * `refCounts` consists of four fields that are treated atomically: + * + * 1. Strong count. This is a count of the number of shared pointers that + * hold a reference to this object. When the strong counts goes to zero, + * if the weak count is zero, the destructor is run. If the weak count is + * non-zero when the strong count goes to zero then the partialDestructor + * is run. + * + * 2. Weak count. This is a count of the number of weak pointer that hold + * a reference to this object. When the weak count goes to zero and the + * strong count is also zero, then the destructor is run. + * + * 3. Partial destroy started bit. This bit is set if the + * `partialDestructor` function has been started (or is about to be + * started). This is used to prevent the destructor from running + * concurrently with the partial destructor. This can easily happen when + * the last strong pointer release its reference in one thread and starts + * the partialDestructor, while in another thread the last weak pointer + * goes out of scope and starts the destructor while the partialDestructor + * is still running. Both a start and finished bit is needed to handle a + * corner-case where the last strong pointer goes out of scope, then then + * last `weakPointer` goes out of scope, but this happens before the + * `partialDestructor` bit is set. It would be possible to use a single + * bit if it could also be set atomically when the strong count goes to + * zero and the weak count is non-zero, but that would add complexity (and + * likely slow down common cases as well). + * + * 4. Partial destroy finished bit. This bit is set when the + * `partialDestructor` has finished running. See (3) above for more + * information. + */ mutable std::atomic refCounts_{kStrongDelta}; - /** Amount to change the strong count when adding or releasing a reference - - Note: The strong count is stored in the low `StrongCountNumBits` bits - of refCounts - */ + /** + * Amount to change the strong count when adding or releasing a reference + * + * Note: The strong count is stored in the low `StrongCountNumBits` bits + * of refCounts + */ static constexpr FieldType kStrongDelta = 1; - /** Amount to change the weak count when adding or releasing a reference - - Note: The weak count is stored in the high `WeakCountNumBits` bits of - refCounts - */ + /** + * Amount to change the weak count when adding or releasing a reference + * + * Note: The weak count is stored in the high `WeakCountNumBits` bits of + * refCounts + */ static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits); - /** Flag that is set when the partialDestroy function has started running - (or is about to start running). - - See description of the `refCounts` field for a fuller description of - this field. - */ + /** + * Flag that is set when the partialDestroy function has started running + * (or is about to start running). + * + * See description of the `refCounts` field for a fuller description of + * this field. + */ static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1)); - /** Flag that is set when the partialDestroy function has finished running - - See description of the `refCounts` field for a fuller description of - this field. - */ + /** + * Flag that is set when the partialDestroy function has finished running + * + * See description of the `refCounts` field for a fuller description of + * this field. + */ static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2)); - /** Mask that will zero out all the `count` bits and leave the tag bits - unchanged. - */ + /** + * Mask that will zero out all the `count` bits and leave the tag bits + * unchanged. + */ static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask; - /** Mask that will zero out the `tag` bits and leave the count bits - unchanged. - */ + /** + * Mask that will zero out the `tag` bits and leave the count bits + * unchanged. + */ static constexpr FieldType kValueMask = ~kTagMask; - /** Mask that will zero out everything except the strong count. + /** + * Mask that will zero out everything except the strong count. */ static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask; - /** Mask that will zero out everything except the weak count. + /** + * Mask that will zero out everything except the weak count. */ static constexpr FieldType kWeakMask = (((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask; - /** Unpack the count and tag fields from the packed atomic integer form. */ + /** + * Unpack the count and tag fields from the packed atomic integer form. + */ struct RefCountPair { CountType strong; CountType weak; - /** The `partialDestroyStartedBit` is set to on when the partial - destroy function is started. It is not a boolean; it is a uint32 - with all bits zero with the possible exception of the - `partialDestroyStartedMask` bit. This is done so it can be directly - masked into the `combinedValue`. + /** + * The `partialDestroyStartedBit` is set to on when the partial + * destroy function is started. It is not a boolean; it is a uint32 + * with all bits zero with the possible exception of the + * `partialDestroyStartedMask` bit. This is done so it can be directly + * masked into the `combinedValue`. */ FieldType partialDestroyStartedBit{0}; - /** The `partialDestroyFinishedBit` is set to on when the partial - destroy function has finished. + /** + * The `partialDestroyFinishedBit` is set to on when the partial + * destroy function has finished. */ FieldType partialDestroyFinishedBit{0}; RefCountPair(FieldType v) noexcept; RefCountPair(CountType s, CountType w) noexcept; - /** Convert back to the packed integer form. */ + /** + * Convert back to the packed integer form. + */ [[nodiscard]] FieldType combinedValue() const noexcept; @@ -215,9 +232,10 @@ private: static_cast((kOne << kStrongCountNumBits) - 1); static constexpr CountType kMaxWeakValue = static_cast((kOne << kWeakCountNumBits) - 1); - /** Put an extra margin to detect when running up against limits. - This is only used in debug code, and is useful if we reduce the - number of bits in the strong and weak counts (to 16 and 14 bits). + /** + * Put an extra margin to detect when running up against limits. + * This is only used in debug code, and is useful if we reduce the + * number of bits in the strong and weak counts (to 16 and 14 bits). */ static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32; static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32; diff --git a/include/xrpl/basics/LocalValue.h b/include/xrpl/basics/LocalValue.h index 1c2a657a18..c5e544a343 100644 --- a/include/xrpl/basics/LocalValue.h +++ b/include/xrpl/basics/LocalValue.h @@ -70,11 +70,15 @@ public: { } - /** Stores instance of T specific to the calling coroutine or thread. */ + /** + * Stores instance of T specific to the calling coroutine or thread. + */ T& operator*(); - /** Stores instance of T specific to the calling coroutine or thread. */ + /** + * Stores instance of T specific to the calling coroutine or thread. + */ T* operator->() { diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 4e3437fe71..945dc1b4ec 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -16,7 +16,9 @@ namespace xrpl { -/** Manages partitions for logging. */ +/** + * Manages partitions for logging. + */ class Logs { private: @@ -40,69 +42,81 @@ private: writeAlways(beast::Severity level, std::string const& text) override; }; - /** Manages a system file containing logged output. - The system file remains open during program execution. Interfaces - are provided for interoperating with standard log management - tools like logrotate(8): - http://linuxcommand.org/man_pages/logrotate8.html - @note None of the listed interfaces are thread-safe. - */ + /** + * Manages a system file containing logged output. + * The system file remains open during program execution. Interfaces + * are provided for interoperating with standard log management + * tools like logrotate(8): + * http://linuxcommand.org/man_pages/logrotate8.html + * @note None of the listed interfaces are thread-safe. + */ class File { public: - /** Construct with no associated system file. - A system file may be associated later with @ref open. - @see open - */ + /** + * Construct with no associated system file. + * A system file may be associated later with @ref open. + * @see open + */ File(); - /** Destroy the object. - If a system file is associated, it will be flushed and closed. - */ + /** + * Destroy the object. + * If a system file is associated, it will be flushed and closed. + */ ~File() = default; - /** Determine if a system file is associated with the log. - @return `true` if a system file is associated and opened for - writing. - */ + /** + * Determine if a system file is associated with the log. + * @return `true` if a system file is associated and opened for + * writing. + */ [[nodiscard]] bool isOpen() const noexcept; - /** Associate a system file with the log. - If the file does not exist an attempt is made to create it - and open it for writing. If the file already exists an attempt is - made to open it for appending. - If a system file is already associated with the log, it is closed - first. - @return `true` if the file was opened. - */ + /** + * Associate a system file with the log. + * If the file does not exist an attempt is made to create it + * and open it for writing. If the file already exists an attempt is + * made to open it for appending. + * If a system file is already associated with the log, it is closed + * first. + * @return `true` if the file was opened. + */ bool open(boost::filesystem::path const& path); - /** Close and re-open the system file associated with the log - This assists in interoperating with external log management tools. - @return `true` if the file was opened. - */ + /** + * Close and re-open the system file associated with the log + * This assists in interoperating with external log management tools. + * @return `true` if the file was opened. + */ bool closeAndReopen(); - /** Close the system file if it is open. */ + /** + * Close the system file if it is open. + */ void close(); - /** write to the log file. - Does nothing if there is no associated system file. - */ + /** + * write to the log file. + * Does nothing if there is no associated system file. + */ void write(char const* text); - /** write to the log file and append an end of line marker. - Does nothing if there is no associated system file. - */ + /** + * write to the log file and append an end of line marker. + * Does nothing if there is no associated system file. + */ void writeln(char const* text); - /** Write to the log file using std::string. */ + /** + * Write to the log file using std::string. + */ /** @{ */ void write(std::string const& str) @@ -223,19 +237,21 @@ private: //------------------------------------------------------------------------------ // Debug logging: -/** Set the sink for the debug journal. - - @param sink unique_ptr to new debug Sink. - @return unique_ptr to the previous Sink. nullptr if there was no Sink. -*/ +/** + * Set the sink for the debug journal. + * + * @param sink unique_ptr to new debug Sink. + * @return unique_ptr to the previous Sink. nullptr if there was no Sink. + */ std::unique_ptr setDebugLogSink(std::unique_ptr sink); -/** Returns a debug journal. - The journal may drain to a null sink, so its output - may never be seen. Never use it for critical - information. -*/ +/** + * Returns a debug journal. + * The journal may drain to a null sink, so its output + * may never be seen. Never use it for critical + * information. + */ beast::Journal debugLog(); diff --git a/include/xrpl/basics/MathUtilities.h b/include/xrpl/basics/MathUtilities.h index 4552b335e1..78f5c76988 100644 --- a/include/xrpl/basics/MathUtilities.h +++ b/include/xrpl/basics/MathUtilities.h @@ -6,7 +6,8 @@ namespace xrpl { -/** Calculate one number divided by another number in percentage. +/** + * Calculate one number divided by another number in percentage. * The result is rounded up to the next integer, and capped in the range [0,100] * E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000 * calculatePercent(1, 99) = 2 because 1/99 = 0.010101 @@ -19,7 +20,7 @@ namespace xrpl { * @return the percentage, in [0, 100] * * @note total cannot be zero. - * */ + */ constexpr std::size_t calculatePercent(std::size_t count, std::size_t total) { diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 073da12f89..f90800c715 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -14,7 +14,6 @@ #include #include #include -#include #include namespace xrpl { @@ -48,46 +47,54 @@ isPowerOfTen(T value) namespace detail { -/** Builds a table of the powers of 10 +/** + * Builds a table of the powers of 10 * * This function is marked consteval, so it can only be run in * a constexpr context. This assures that it is and can only be run at * compile time. Doing it at runtime would be pretty wasteful and * inefficient. */ -constexpr std::size_t kInt64Digits = 20; -consteval std::array +constexpr std::size_t kUint64Digits = 20; +[[maybe_unused]] constexpr std::size_t kUint128Digits = 39; + +template +consteval std::array buildPowersOfTen() { - std::array result{}; + std::array result{}; - std::uint64_t power = 1; + T power = 1; std::size_t exponent = 0; // end the loop early so it doesn't overflow; for (; exponent < result.size() - 1; ++exponent, power *= 10) { result[exponent] = power; - if (power > std::numeric_limits::max() / 10) + if (power > std::numeric_limits::max() / 10) throw std::logic_error("Power of 10 table is too big"); } result[exponent] = power; - if (power < std::numeric_limits::max() / 10) - throw std::logic_error("Power of 10 table is not big enough for the uint64_t type"); + if (power < std::numeric_limits::max() / 10) + throw std::logic_error("Power of 10 table is not big enough for the given type"); return result; } } // namespace detail -constexpr std::array kPowerOfTen = detail::buildPowersOfTen(); +template +constexpr std::array kPowerOfTenImpl = detail::buildPowersOfTen(); + +constexpr auto kPowerOfTen = kPowerOfTenImpl; static_assert(kPowerOfTen[0] == 1); static_assert(kPowerOfTen[1] == 10); static_assert(kPowerOfTen[10] == 10'000'000'000); static_assert( - isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1); + isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1); -/** MantissaRange defines a range for the mantissa of a normalized Number. +/** + * MantissaRange defines a range for the mantissa of a normalized Number. * * The mantissa is in the range [min, max], where * * min is a power of 10, and @@ -124,17 +131,37 @@ struct MantissaRange final { using rep = std::uint64_t; + // NOLINTBEGIN(readability-enum-initial-value) + // The values don't matter, except for Large enum class MantissaScale { + // Small can be removed when either featureSingleAssetVault or featureLendingProtocol are + // retired Small, // LargeLegacy can be removed when fixCleanup3_2_0 is retired LargeLegacy, - Large, + // Large320 can be removed when fixCleanup3_3_0 is retired + Large320, + // If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large". + Large330, + // Large is a de-facto alias for "the latest", and is only here for backward compatibility + // in the extremely unlikely case that a downstream project made use of it. Note that + // because the behavior changed, this may still be a breaking change. + Large = Large330, }; + // NOLINTEND(readability-enum-initial-value) - // This entire enum can be removed when fixCleanup3_2_0 is retired - enum class CuspRoundingFix : bool { - Disabled = false, - Enabled = true, + // This entire enum can be removed when the last relevant amendment is retired + enum class CuspRoundingFix : std::uint8_t { + // Disabled can be removed when fixCleanup3_2_0 is retired + Disabled = 0, + // Enabled320 can be removed when fixCleanup3_3_0 is retired + Enabled320 = 1, + // If we ever get to the point that there's only one entry, remove the entire enum + Enabled330 = 2, + // Enabled is a de-facto alias for "the latest", and is only here for backward compatibility + // in the extremely unlikely case that a downstream project made use of it. Note that + // because the behavior changed, this may still be a breaking change. + Enabled = Enabled330, }; explicit constexpr MantissaRange(MantissaScale sc) : scale(sc) @@ -145,13 +172,27 @@ struct MantissaRange final int const log{getExponent(scale)}; rep const min{getMin(scale, log)}; rep const max{(min * 10) - 1}; - CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)}; - - static MantissaRange const& - getMantissaRange(MantissaScale scale); + CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)}; static std::set const& - getAllScales(); + getAllScales() + { + static std::set const kScales = { + MantissaRange::MantissaScale::Small, + MantissaRange::MantissaScale::LargeLegacy, + MantissaRange::MantissaScale::Large320, + MantissaRange::MantissaScale::Large330, + }; + return kScales; + } + + class Access + { + static constexpr MantissaRange const& + mantissaRange(MantissaScale scale); + + friend Number; + }; private: static constexpr int @@ -162,7 +203,8 @@ private: case MantissaScale::Small: return 15; case MantissaScale::LargeLegacy: - case MantissaScale::Large: + case MantissaScale::Large320: + case MantissaScale::Large330: return 18; // LCOV_EXCL_START default: @@ -191,24 +233,24 @@ private: case MantissaScale::Small: case MantissaScale::LargeLegacy: return CuspRoundingFix::Disabled; - case MantissaScale::Large: - return CuspRoundingFix::Enabled; + case MantissaScale::Large320: + return CuspRoundingFix::Enabled320; + case MantissaScale::Large330: + return CuspRoundingFix::Enabled330; default: // If called in a constexpr context, this throw assures that the build fails if an // invalid scale is used. throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE } } - - static std::unordered_map const& - getRanges(); }; // Like std::integral, but only 64-bit integral types. template concept Integral64 = std::is_same_v || std::is_same_v; -/** Number is a floating point type that can represent a wide range of values. +/** + * Number is a floating point type that can represent a wide range of values. * * It can represent all values that can be represented by an STAmount - * regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least @@ -304,7 +346,6 @@ concept Integral64 = std::is_same_v || std::is_same_v::max(); static_assert(kMaxRep == 9'223'372'036'854'775'807); static_assert(-kMaxRep == std::numeric_limits::min() + 1); + static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10; + static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL); // May need to make unchecked private struct Unchecked @@ -390,10 +433,11 @@ public: static Number lowest() noexcept; - /** Conversions to Number are implicit and conversions away from Number - * are explicit. This design encourages and facilitates the use of Number - * as the preferred type for floating point arithmetic as it makes - * "mixed mode" more convenient, e.g. MPTAmount + Number. + /** + * Conversions to Number are implicit and conversions away from Number + * are explicit. This design encourages and facilitates the use of Number + * as the preferred type for floating point arithmetic as it makes + * "mixed mode" more convenient, e.g. MPTAmount + Number. */ explicit operator rep() const; // round to nearest, even on tie @@ -448,7 +492,9 @@ public: return l.mantissa_ < r.mantissa_; } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept { @@ -502,14 +548,16 @@ public: static RoundingMode setround(RoundingMode inMode); - /** Returns which mantissa scale is currently in use for normalization. + /** + * Returns which mantissa scale is currently in use for normalization. * * If you think you need to call this outside of unit tests, no you don't. */ static MantissaRange::MantissaScale getMantissaScale(); - /** Changes which mantissa scale is used for normalization. + /** + * Changes which mantissa scale is used for normalization. * * If you think you need to call this outside of unit tests, no you don't. */ @@ -545,6 +593,13 @@ public: std::pair normalizeToRange() const; + // Safely convert rep (int64) mantissa to internalrep (uint64). If the rep + // is negative, returns the positive value. This takes a little extra work + // because converting std::numeric_limits::min() flirts with + // UB, and can vary across compilers. + static internalrep + externalToInternal(rep mantissa); + private: static thread_local RoundingMode mode; // The available ranges for mantissa @@ -554,10 +609,17 @@ private: // changing the values inside the range. static thread_local std::reference_wrapper kRange; + class Guard; + void normalize(MantissaRange const& range); - /** Normalize Number components to an arbitrary range. + // Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that + void + normalize(Guard const& guard); + + /** + * Normalize Number components to an arbitrary range. * * min/maxMantissa are parameters because this function is used by both * normalize(), which reads from kRange, and by normalizeToRange, @@ -571,7 +633,7 @@ private: int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled); + MantissaRange::CuspRoundingFix cuspRoundingFix); template friend void @@ -581,7 +643,7 @@ private: int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + MantissaRange::CuspRoundingFix cuspRoundingFix, bool dropped); [[nodiscard]] bool @@ -592,15 +654,6 @@ private: // exponent could go out of range, so it will be checked. [[nodiscard]] Number shiftExponent(int exponentDelta) const; - - // Safely convert rep (int64) mantissa to internalrep (uint64). If the rep - // is negative, returns the positive value. This takes a little extra work - // because converting std::numeric_limits::min() flirts with - // UB, and can vary across compilers. - static internalrep - externalToInternal(rep mantissa); - - class Guard; }; constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept @@ -635,7 +688,8 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0} { } -/** Returns the mantissa of the external view of the Number. +/** + * Returns the mantissa of the external view of the Number. * * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. @@ -656,7 +710,8 @@ Number::mantissa() const noexcept return sign * static_cast(m); } -/** Returns the exponent of the external view of the Number. +/** + * Returns the exponent of the external view of the Number. * * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. @@ -862,21 +917,11 @@ squelch(Number const& x, Number const& limit) noexcept return x; } -inline std::string -to_string(MantissaRange::MantissaScale const& scale) -{ - switch (scale) - { - case MantissaRange::MantissaScale::Small: - return "small"; - case MantissaRange::MantissaScale::LargeLegacy: - return "largeLegacy"; - case MantissaRange::MantissaScale::Large: - return "large"; - default: - throw std::runtime_error("Bad scale"); - } -} +std::string +to_string(MantissaRange::MantissaScale const& scale); + +std::string +to_string(Number::RoundingMode const& round); class SaveNumberRoundMode { @@ -915,10 +960,10 @@ public: operator=(NumberRoundModeGuard const&) = delete; }; -/** Sets the new scale and restores the old scale when it leaves scope. +/** + * Sets the new scale and restores the old scale when it leaves scope. * * If you think you need to use this class outside of unit tests, no you don't. - * */ class NumberMantissaScaleGuard { diff --git a/include/xrpl/basics/RangeSet.h b/include/xrpl/basics/RangeSet.h index 2ed543b376..3de882979e 100644 --- a/include/xrpl/basics/RangeSet.h +++ b/include/xrpl/basics/RangeSet.h @@ -13,23 +13,25 @@ namespace xrpl { -/** A closed interval over the domain T. - - For an instance ClosedInterval c, this represents the closed interval - (c.first(), c.last()). A single element interval has c.first() == c.last(). - - This is simply a type-alias for boost interval container library interval - set, so users should consult that documentation for available supporting - member and free functions. -*/ +/** + * A closed interval over the domain T. + * + * For an instance ClosedInterval c, this represents the closed interval + * (c.first(), c.last()). A single element interval has c.first() == c.last(). + * + * This is simply a type-alias for boost interval container library interval + * set, so users should consult that documentation for available supporting + * member and free functions. + */ template using ClosedInterval = boost::icl::closed_interval; -/** Create a closed range interval - - Helper function to create a closed range interval without having to qualify - the template argument. -*/ +/** + * Create a closed range interval + * + * Helper function to create a closed range interval without having to qualify + * the template argument. + */ template ClosedInterval range(T low, T high) @@ -37,28 +39,30 @@ range(T low, T high) return ClosedInterval(low, high); } -/** A set of closed intervals over the domain T. - - Represents a set of values of the domain T using the minimum number - of disjoint ClosedInterval. This is useful to represent ranges of - T where a few instances are missing, e.g. the set 1-5,8-9,11-14. - - This is simply a type-alias for boost interval container library interval - set, so users should consult that documentation for available supporting - member and free functions. -*/ +/** + * A set of closed intervals over the domain T. + * + * Represents a set of values of the domain T using the minimum number + * of disjoint ClosedInterval. This is useful to represent ranges of + * T where a few instances are missing, e.g. the set 1-5,8-9,11-14. + * + * This is simply a type-alias for boost interval container library interval + * set, so users should consult that documentation for available supporting + * member and free functions. + */ template using RangeSet = boost::icl::interval_set>; -/** Convert a ClosedInterval to a styled string - - The styled string is - "c.first()-c.last()" if c.first() != c.last() - "c.first()" if c.first() == c.last() - - @param ci The closed interval to convert - @return The style string -*/ +/** + * Convert a ClosedInterval to a styled string + * + * The styled string is + * "c.first()-c.last()" if c.first() != c.last() + * "c.first()" if c.first() == c.last() + * + * @param ci The closed interval to convert + * @return The style string + */ template std::string to_string(ClosedInterval const& ci) @@ -68,14 +72,15 @@ to_string(ClosedInterval const& ci) return std::to_string(ci.first()) + "-" + std::to_string(ci.last()); } -/** Convert the given RangeSet to a styled string. - - The styled string representation is the set of disjoint intervals joined - by commas. The string "empty" is returned if the set is empty. - - @param rs The rangeset to convert - @return The styled string -*/ +/** + * Convert the given RangeSet to a styled string. + * + * The styled string representation is the set of disjoint intervals joined + * by commas. The string "empty" is returned if the set is empty. + * + * @param rs The rangeset to convert + * @return The styled string + */ template std::string to_string(RangeSet const& rs) @@ -91,15 +96,16 @@ to_string(RangeSet const& rs) return s; } -/** Convert the given styled string to a RangeSet. - - The styled string representation is the set - of disjoint intervals joined by commas. - - @param rs The set to be populated - @param s The styled string to convert - @return True on successfully converting styled string -*/ +/** + * Convert the given styled string to a RangeSet. + * + * The styled string representation is the set + * of disjoint intervals joined by commas. + * + * @param rs The set to be populated + * @param s The styled string to convert + * @return True on successfully converting styled string + */ template [[nodiscard]] bool fromString(RangeSet& rs, std::string const& s) @@ -161,14 +167,15 @@ fromString(RangeSet& rs, std::string const& s) return result; } -/** Find the largest value not in the set that is less than a given value. - - @param rs The set of interest - @param t The value that must be larger than the result - @param minVal (Default is 0) The smallest allowed value - @return The largest v such that minV <= v < t and !contains(rs, v) or - std::nullopt if no such v exists. -*/ +/** + * Find the largest value not in the set that is less than a given value. + * + * @param rs The set of interest + * @param t The value that must be larger than the result + * @param minVal (Default is 0) The smallest allowed value + * @return The largest v such that minV <= v < t and !contains(rs, v) or + * std::nullopt if no such v exists. + */ template std::optional prevMissing(RangeSet const& rs, T t, T minVal = 0) diff --git a/include/xrpl/basics/Resolver.h b/include/xrpl/basics/Resolver.h index d48958b76d..239eb9630e 100644 --- a/include/xrpl/basics/Resolver.h +++ b/include/xrpl/basics/Resolver.h @@ -15,22 +15,29 @@ public: virtual ~Resolver() = 0; - /** Issue an asynchronous stop request. */ + /** + * Issue an asynchronous stop request. + */ virtual void stopAsync() = 0; - /** Issue a synchronous stop request. */ + /** + * Issue a synchronous stop request. + */ virtual void stop() = 0; - /** Issue a synchronous start request. */ + /** + * Issue a synchronous start request. + */ virtual void start() = 0; - /** resolve all hostnames on the list - @param names the names to be resolved - @param handler the handler to call - */ + /** + * resolve all hostnames on the list + * @param names the names to be resolved + * @param handler the handler to call + */ /** @{ */ template void diff --git a/include/xrpl/basics/SharedWeakCachePointer.h b/include/xrpl/basics/SharedWeakCachePointer.h index a143647a1e..1b78af2fae 100644 --- a/include/xrpl/basics/SharedWeakCachePointer.h +++ b/include/xrpl/basics/SharedWeakCachePointer.h @@ -7,13 +7,14 @@ namespace xrpl { -/** A combination of a std::shared_ptr and a std::weak_pointer. - - -This class is a wrapper to a `std::variant` -This class is useful for storing intrusive pointers in tagged caches using less -memory than storing both pointers directly. -*/ +/** + * A combination of a std::shared_ptr and a std::weak_pointer. + * + * + * This class is a wrapper to a `std::variant` + * This class is useful for storing intrusive pointers in tagged caches using less + * memory than storing both pointers directly. + */ template class SharedWeakCachePointer @@ -48,65 +49,79 @@ public: ~SharedWeakCachePointer(); - /** Return a strong pointer if this is already a strong pointer (i.e. don't - lock the weak pointer. Use the `lock` method if that's what's needed) + /** + * Return a strong pointer if this is already a strong pointer (i.e. don't + * lock the weak pointer. Use the `lock` method if that's what's needed) */ [[nodiscard]] std::shared_ptr const& getStrong() const; - /** Return true if this is a strong pointer and the strong pointer is - seated. + /** + * Return true if this is a strong pointer and the strong pointer is + * seated. */ explicit operator bool() const noexcept; - /** Set the pointer to null, decrement the appropriate ref count, and run - the appropriate release action. + /** + * Set the pointer to null, decrement the appropriate ref count, and run + * the appropriate release action. */ void reset(); - /** If this is a strong pointer, return the raw pointer. Otherwise return - null. + /** + * If this is a strong pointer, return the raw pointer. Otherwise return + * null. */ [[nodiscard]] T* get() const; - /** If this is a strong pointer, return the strong count. Otherwise return 0 + /** + * If this is a strong pointer, return the strong count. Otherwise return 0 */ [[nodiscard]] std::size_t useCount() const; - /** Return true if there is a non-zero strong count. */ + /** + * Return true if there is a non-zero strong count. + */ [[nodiscard]] bool expired() const; - /** If this is a strong pointer, return the strong pointer. Otherwise - attempt to lock the weak pointer. + /** + * If this is a strong pointer, return the strong pointer. Otherwise + * attempt to lock the weak pointer. */ [[nodiscard]] std::shared_ptr lock() const; - /** Return true is this represents a strong pointer. */ + /** + * Return true is this represents a strong pointer. + */ [[nodiscard]] bool isStrong() const; - /** Return true is this represents a weak pointer. */ + /** + * Return true is this represents a weak pointer. + */ [[nodiscard]] bool isWeak() const; - /** If this is a weak pointer, attempt to convert it to a strong pointer. - - @return true if successfully converted to a strong pointer (or was - already a strong pointer). Otherwise false. - */ + /** + * If this is a weak pointer, attempt to convert it to a strong pointer. + * + * @return true if successfully converted to a strong pointer (or was + * already a strong pointer). Otherwise false. + */ bool convertToStrong(); - /** If this is a strong pointer, attempt to convert it to a weak pointer. - - @return false if the pointer is null. Otherwise return true. - */ + /** + * If this is a strong pointer, attempt to convert it to a weak pointer. + * + * @return false if the pointer is null. Otherwise return true. + */ bool convertToWeak(); diff --git a/include/xrpl/basics/SlabAllocator.h b/include/xrpl/basics/SlabAllocator.h index 8e741991f6..7b6e88e8bc 100644 --- a/include/xrpl/basics/SlabAllocator.h +++ b/include/xrpl/basics/SlabAllocator.h @@ -33,7 +33,9 @@ class SlabAllocator static_assert(alignof(Type) == 8 || alignof(Type) == 4); - /** A block of memory that is owned by a slab allocator */ + /** + * A block of memory that is owned by a slab allocator + */ struct SlabBlock { // A mutex to protect the freelist for this block: @@ -80,7 +82,9 @@ class SlabAllocator SlabBlock& operator=(SlabBlock&& other) = delete; - /** Determines whether the given pointer belongs to this allocator */ + /** + * Determines whether the given pointer belongs to this allocator + */ bool own(std::uint8_t const* pIn) const noexcept { @@ -107,14 +111,15 @@ class SlabAllocator return ret; } - /** Return an item to this allocator's freelist. - - @param ptr The pointer to the chunk of memory being deallocated. - - @note This is a dangerous, private interface; the item being - returned should belong to this allocator. Debug builds - will check and assert if this is not the case. Release - builds will not. + /** + * Return an item to this allocator's freelist. + * + * @param ptr The pointer to the chunk of memory being deallocated. + * + * @note This is a dangerous, private interface; the item being + * returned should belong to this allocator. Debug builds + * will check and assert if this is not the case. Release + * builds will not. */ void deallocate(std::uint8_t* ptr) noexcept @@ -145,13 +150,14 @@ private: std::size_t const slabSize_; public: - /** Constructs a slab allocator able to allocate objects of a fixed size - - @param count the number of items the slab allocator can allocate; note - that a count of 0 is valid and means that the allocator - is, effectively, disabled. This can be very useful in some - contexts (e.g. when minimal memory usage is needed) and - allows for graceful failure. + /** + * Constructs a slab allocator able to allocate objects of a fixed size + * + * @param count the number of items the slab allocator can allocate; note + * that a count of 0 is valid and means that the allocator + * is, effectively, disabled. This can be very useful in some + * contexts (e.g. when minimal memory usage is needed) and + * allows for graceful failure. */ constexpr explicit SlabAllocator( std::size_t extra, @@ -179,17 +185,20 @@ public: // shutdown process up could make this possible. ~SlabAllocator() = default; - /** Returns the size of the memory block this allocator returns. */ + /** + * Returns the size of the memory block this allocator returns. + */ [[nodiscard]] constexpr std::size_t size() const noexcept { return itemSize_; } - /** Returns a suitably aligned pointer, if one is available. - - @return a pointer to a block of memory from the allocator, or - nullptr if the allocator can't satisfy this request. + /** + * Returns a suitably aligned pointer, if one is available. + * + * @return a pointer to a block of memory from the allocator, or + * nullptr if the allocator can't satisfy this request. */ std::uint8_t* allocate() noexcept @@ -250,12 +259,13 @@ public: return slab->allocate(); } - /** Returns the memory block to the allocator. - - @param ptr A pointer to a memory block. - @param size If non-zero, a hint as to the size of the block. - @return true if this memory block belonged to the allocator and has - been released; false otherwise. + /** + * Returns the memory block to the allocator. + * + * @param ptr A pointer to a memory block. + * @param size If non-zero, a hint as to the size of the block. + * @return true if this memory block belonged to the allocator and has + * been released; false otherwise. */ bool deallocate(std::uint8_t* ptr) noexcept @@ -278,7 +288,9 @@ public: } }; -/** A collection of slab allocators of various sizes for a given type. */ +/** + * A collection of slab allocators of various sizes for a given type. + */ template class SlabAllocatorSet { @@ -345,13 +357,14 @@ public: ~SlabAllocatorSet() = default; - /** Returns a suitably aligned pointer, if one is available. - - @param extra The number of extra bytes, above and beyond the size of - the object, that should be returned by the allocator. - - @return a pointer to a block of memory, or nullptr if the allocator - can't satisfy this request. + /** + * Returns a suitably aligned pointer, if one is available. + * + * @param extra The number of extra bytes, above and beyond the size of + * the object, that should be returned by the allocator. + * + * @return a pointer to a block of memory, or nullptr if the allocator + * can't satisfy this request. */ std::uint8_t* allocate(std::size_t extra) noexcept @@ -368,12 +381,13 @@ public: return nullptr; } - /** Returns the memory block to the allocator. - - @param ptr A pointer to a memory block. - - @return true if this memory block belonged to one of the allocators - in this set and has been released; false otherwise. + /** + * Returns the memory block to the allocator. + * + * @param ptr A pointer to a memory block. + * + * @return true if this memory block belonged to one of the allocators + * in this set and has been released; false otherwise. */ bool deallocate(std::uint8_t* ptr) noexcept diff --git a/include/xrpl/basics/Slice.h b/include/xrpl/basics/Slice.h index f87ca063b8..36e7615c3a 100644 --- a/include/xrpl/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -16,12 +16,13 @@ namespace xrpl { -/** An immutable linear range of bytes. - - A fully constructed Slice is guaranteed to be in a valid state. - A Slice is lightweight and copyable, it retains no ownership - of the underlying memory. -*/ +/** + * An immutable linear range of bytes. + * + * A fully constructed Slice is guaranteed to be in a valid state. + * A Slice is lightweight and copyable, it retains no ownership + * of the underlying memory. + */ class Slice { private: @@ -32,30 +33,37 @@ public: using value_type = std::uint8_t; using const_iterator = value_type const*; - /** Default constructed Slice has length 0. */ + /** + * Default constructed Slice has length 0. + */ Slice() noexcept = default; Slice(Slice const&) noexcept = default; Slice& operator=(Slice const&) noexcept = default; - /** Create a slice pointing to existing memory. */ + /** + * Create a slice pointing to existing memory. + */ Slice(void const* data, std::size_t size) noexcept : data_(reinterpret_cast(data)), size_(size) { } - /** Return `true` if the byte range is empty. */ + /** + * Return `true` if the byte range is empty. + */ [[nodiscard]] bool empty() const noexcept { return size_ == 0; } - /** Returns the number of bytes in the storage. - - This may be zero for an empty range. - */ + /** + * Returns the number of bytes in the storage. + * + * This may be zero for an empty range. + */ /** @{ */ [[nodiscard]] std::size_t size() const noexcept @@ -70,17 +78,20 @@ public: } /** @} */ - /** Return a pointer to beginning of the storage. - @note The return type is guaranteed to be a pointer - to a single byte, to facilitate pointer arithmetic. - */ + /** + * Return a pointer to beginning of the storage. + * @note The return type is guaranteed to be a pointer + * to a single byte, to facilitate pointer arithmetic. + */ [[nodiscard]] std::uint8_t const* data() const noexcept { return data_; } - /** Access raw bytes. */ + /** + * Access raw bytes. + */ std::uint8_t operator[](std::size_t i) const noexcept { @@ -88,7 +99,9 @@ public: return data_[i]; } - /** Advance the buffer. */ + /** + * Advance the buffer. + */ /** @{ */ Slice& operator+=(std::size_t n) @@ -108,7 +121,9 @@ public: } /** @} */ - /** Shrinks the slice by moving its start forward by n characters. */ + /** + * Shrinks the slice by moving its start forward by n characters. + */ void removePrefix(std::size_t n) { @@ -116,7 +131,9 @@ public: size_ -= n; } - /** Shrinks the slice by moving its end backward by n characters. */ + /** + * Shrinks the slice by moving its end backward by n characters. + */ void removeSuffix(std::size_t n) { @@ -147,16 +164,17 @@ public: return data_ + size_; } - /** Return a "sub slice" of given length starting at the given position - - Note that the subslice encompasses the range [pos, pos + rCount), - where rCount is the smaller of count and size() - pos. - - @param pos position of the first character - @count requested length - - @returns The requested subslice, if the request is valid. - @throws std::out_of_range if pos > size() + /** + * Return a "sub slice" of given length starting at the given position + * + * Note that the subslice encompasses the range [pos, pos + rCount), + * where rCount is the smaller of count and size() - pos. + * + * @param pos position of the first character + * @count requested length + * + * @return The requested subslice, if the request is valid. + * @throws std::out_of_range if pos > size() */ [[nodiscard]] Slice substr(std::size_t pos, std::size_t count = std::numeric_limits::max()) const diff --git a/include/xrpl/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h index 97df43d68f..2b360d2fda 100644 --- a/include/xrpl/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -17,15 +17,16 @@ namespace xrpl { -/** Format arbitrary binary data as an SQLite "blob literal". - - In SQLite, blob literals must be encoded when used in a query. Per - https://sqlite.org/lang_expr.html#literal_values_constants_ they are - encoded as string literals containing hexadecimal data and preceded - by a single 'X' character. - - @param blob An arbitrary blob of binary data - @return The input, encoded as a blob literal. +/** + * Format arbitrary binary data as an SQLite "blob literal". + * + * In SQLite, blob literals must be encoded when used in a query. Per + * https://sqlite.org/lang_expr.html#literal_values_constants_ they are + * encoded as string literals containing hexadecimal data and preceded + * by a single 'X' character. + * + * @param blob An arbitrary blob of binary data + * @return The input, encoded as a blob literal. */ std::string sqlBlobLiteral(Blob const& blob); @@ -130,11 +131,12 @@ trimWhitespace(std::string str); std::optional toUInt64(std::string const& s); -/** Determines if the given string looks like a TOML-file hosting domain. - - Do not use this function to determine if a particular string is a valid - domain, as this function may reject domains that are otherwise valid and - doesn't check whether the TLD is valid. +/** + * Determines if the given string looks like a TOML-file hosting domain. + * + * Do not use this function to determine if a particular string is a valid + * domain, as this function may reject domains that are otherwise valid and + * doesn't check whether the TLD is valid. */ bool isProperlyFormedTomlDomain(std::string_view domain); diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 16c87cf833..7bb2cb552b 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -41,18 +41,19 @@ struct ReplaceDynamically; } // namespace detail -/** Map/cache combination. - This class implements a cache and a map. The cache keeps objects alive - in the map. The map allows multiple code paths that reference objects - with the same tag to get the same actual object. - - So long as data is in the cache, it will stay in memory. - If it stays in memory even after it is ejected from the cache, - the map will track it. - - @note Callers must not modify data objects that are stored in the cache - unless they hold their own lock over all cache operations. -*/ +/** + * Map/cache combination. + * This class implements a cache and a map. The cache keeps objects alive + * in the map. The map allows multiple code paths that reference objects + * with the same tag to get the same actual object. + * + * So long as data is in the cache, it will stay in memory. + * If it stays in memory even after it is ejected from the cache, + * the map will track it. + * + * @note Callers must not modify data objects that are stored in the cache + * unless they hold their own lock over all cache operations. + */ template < class Key, class T, @@ -82,11 +83,15 @@ public: beast::insight::Collector::ptr const& collector = beast::insight::NullCollector::make()); public: - /** Return the clock associated with the cache. */ + /** + * Return the clock associated with the cache. + */ clock_type& clock(); - /** Returns the number of items in the container. */ + /** + * Returns the number of items in the container. + */ std::size_t size() const; @@ -105,9 +110,10 @@ public: void reset(); - /** Refresh the last access time on a key if present. - @return `true` If the key was found. - */ + /** + * Refresh the last access time on a key if present. + * @return `true` If the key was found. + */ template bool touchIfExists(KeyComparable const& key); @@ -130,14 +136,15 @@ private: SharedPointerType const&, SharedPointerType&>; - /** Shared implementation of the canonicalize family. - - `policy` selects how a collision is resolved when `key` already exists: - detail::ReplaceCached, detail::ReplaceClient or - detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is - invoked with the existing strong pointer and returns whether to replace - the cached value with `data`; for the tag policies it is unused. - */ + /** + * Shared implementation of the canonicalize family. + * + * `policy` selects how a collision is resolved when `key` already exists: + * detail::ReplaceCached, detail::ReplaceClient or + * detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is + * invoked with the existing strong pointer and returns whether to replace + * the cached value with `data`; for the tag policies it is unused. + */ template bool canonicalizeImpl( @@ -147,69 +154,73 @@ private: Callback&& replaceCallback = nullptr); public: - /** Replace aliased objects with originals. - - Due to concurrency it is possible for two separate objects with - the same content and referring to the same unique "thing" to exist. - This routine eliminates the duplicate and performs a replacement - on the callers shared pointer if needed. - - `replaceCallback` is a callable taking the existing strong pointer and - returning whether to replace the cached value with `data` (true) or to - keep the cached value and write it back into `data` (false). Because the - write-back case mutates `data`, `data` must be writable. - - @param key The key corresponding to the object - @param data A shared pointer to the data corresponding to the object. - @param replaceCallback A callable (existing strong pointer -> bool). - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Replace aliased objects with originals. + * + * Due to concurrency it is possible for two separate objects with + * the same content and referring to the same unique "thing" to exist. + * This routine eliminates the duplicate and performs a replacement + * on the callers shared pointer if needed. + * + * `replaceCallback` is a callable taking the existing strong pointer and + * returning whether to replace the cached value with `data` (true) or to + * keep the cached value and write it back into `data` (false). Because the + * write-back case mutates `data`, `data` must be writable. + * + * @param key The key corresponding to the object + * @param data A shared pointer to the data corresponding to the object. + * @param replaceCallback A callable (existing strong pointer -> bool). + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ template bool canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback); - /** Insert/update the canonical entry for `key`, always replacing the - cached value with `data`. - - If an entry already exists for `key`, the cached value is unconditionally - replaced with `data`; otherwise `data` is inserted. `data` is never - written back, so it may be const. - - @param key The key corresponding to the object. - @param data A shared pointer to the data corresponding to the object. - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Insert/update the canonical entry for `key`, always replacing the + * cached value with `data`. + * + * If an entry already exists for `key`, the cached value is unconditionally + * replaced with `data`; otherwise `data` is inserted. `data` is never + * written back, so it may be const. + * + * @param key The key corresponding to the object. + * @param data A shared pointer to the data corresponding to the object. + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ bool canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data); - /** Insert the canonical entry for `key`, keeping any existing cached value. - - If an entry already exists for `key`, the cached value is kept and - written back into `data` so the caller ends up with the canonical - object; otherwise `data` is inserted. Because `data` may be overwritten - it must be writable. - - @param key The key corresponding to the object. - @param data A shared pointer to the data corresponding to the object; - updated to the canonical value when one already exists. - - @return `true` if an existing live entry was found and used; `false` if a new entry was - inserted or an expired tracked entry was re-cached. - **/ + /** + * Insert the canonical entry for `key`, keeping any existing cached value. + * + * If an entry already exists for `key`, the cached value is kept and + * written back into `data` so the caller ends up with the canonical + * object; otherwise `data` is inserted. Because `data` may be overwritten + * it must be writable. + * + * @param key The key corresponding to the object. + * @param data A shared pointer to the data corresponding to the object; + * updated to the canonical value when one already exists. + * + * @return `true` if an existing live entry was found and used; `false` if a new entry was + * inserted or an expired tracked entry was re-cached. + */ bool canonicalizeReplaceClient(key_type const& key, SharedPointerType& data); SharedPointerType fetch(key_type const& key); - /** Insert the element into the container. - If the key already exists, nothing happens. - @return `true` If the element was inserted - */ + /** + * Insert the element into the container. + * If the key already exists, nothing happens. + * @return `true` If the element was inserted + */ template auto insert(key_type const& key, T const& value) -> ReturnType @@ -235,15 +246,18 @@ public: getKeys() const; // CachedSLEs functions. - /** Returns the fraction of cache hits. */ + /** + * Returns the fraction of cache hits. + */ double rate() const; - /** Fetch an item from the cache. - If the digest was not found, Handler - will be called with this signature: - SLE::const_pointer(void) - */ + /** + * Fetch an item from the cache. + * If the digest was not found, Handler + * will be called with this signature: + * SLE::const_pointer(void) + */ template SharedPointerType fetch(key_type const& digest, Handler const& h); diff --git a/include/xrpl/basics/ToString.h b/include/xrpl/basics/ToString.h index e9f8f43633..a54db8a8ce 100644 --- a/include/xrpl/basics/ToString.h +++ b/include/xrpl/basics/ToString.h @@ -5,10 +5,11 @@ namespace xrpl { -/** to_string() generalizes std::to_string to handle bools, chars, and strings. - - It's also possible to provide implementation of to_string for a class - which needs a string implementation. +/** + * to_string() generalizes std::to_string to handle bools, chars, and strings. + * + * It's also possible to provide implementation of to_string for a class + * which needs a string implementation. */ template diff --git a/include/xrpl/basics/UptimeClock.h b/include/xrpl/basics/UptimeClock.h index 502aae7c25..b375de4497 100644 --- a/include/xrpl/basics/UptimeClock.h +++ b/include/xrpl/basics/UptimeClock.h @@ -7,12 +7,13 @@ namespace xrpl { -/** Tracks program uptime to seconds precision. - - The timer caches the current time as a performance optimization. - This allows clients to query the current time thousands of times - per second. -*/ +/** + * Tracks program uptime to seconds precision. + * + * The timer caches the current time as a performance optimization. + * This allows clients to query the current time thousands of times + * per second. + */ class UptimeClock { diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 481a7dbd77..bee8b8b945 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -63,18 +63,19 @@ struct AlwaysFalseT : std::bool_constant } // namespace detail -/** Integers of any length that is a multiple of 32-bits - - @note This class stores its values internally in big-endian - form and that internal representation is part of the - binary protocol of the XRP Ledger and cannot be changed - arbitrarily without causing breakage. - - @tparam Bits The number of bits this integer should have; must - be at least 64 and a multiple of 32. - @tparam Tag An arbitrary type that functions as a tag and allows - the instantiation of "distinct" types that the same - number of bits. +/** + * Integers of any length that is a multiple of 32-bits + * + * @note This class stores its values internally in big-endian + * form and that internal representation is part of the + * binary protocol of the XRP Ledger and cannot be changed + * arbitrarily without causing breakage. + * + * @tparam Bits The number of bits this integer should have; must + * be at least 64 and a multiple of 32. + * @tparam Tag An arbitrary type that functions as a tag and allows + * the instantiation of "distinct" types that the same + * number of bits. */ template class BaseUInt @@ -154,21 +155,23 @@ public: return data() + kBytes; } - /** Value hashing function. - The seed prevents crafted inputs from causing degenerate parent - containers. - */ + /** + * Value hashing function. + * The seed prevents crafted inputs from causing degenerate parent + * containers. + */ using hasher = HardenedHash<>; //-------------------------------------------------------------------------- private: - /** Construct from a raw pointer. - The buffer pointed to by `data` must be at least Bits/8 bytes. - - @note the structure is used to disambiguate this from the std::uint64_t - constructor: something like base_uint(0) is ambiguous. - */ + /** + * Construct from a raw pointer. + * The buffer pointed to by `data` must be at least Bits/8 bytes. + * + * @note the structure is used to disambiguate this from the std::uint64_t + * constructor: something like base_uint(0) is ambiguous. + */ // NIKB TODO Remove the need for this constructor. struct VoidHelper { @@ -305,7 +308,9 @@ public: XRPL_ASSERT( c.size() * sizeof(typename Container::value_type) == size(), "xrpl::BaseUInt::fromRaw(Container auto) : input size match"); - std::memcpy(result.data_.data(), c.data(), size()); + std::size_t const canCopy = + std::min(size(), c.size() * sizeof(typename Container::value_type)); + std::memcpy(result.data_.data(), c.data(), canCopy); return result; } @@ -319,7 +324,11 @@ public: XRPL_ASSERT( c.size() * sizeof(typename Container::value_type) == size(), "xrpl::BaseUInt::operator=(Container auto) : input size match"); - std::memcpy(data_.data(), c.data(), size()); + std::size_t const canCopy = + std::min(size(), c.size() * sizeof(typename Container::value_type)); + if (canCopy < size()) + *this = beast::kZero; + std::memcpy(data_.data(), c.data(), canCopy); return *this; } @@ -503,13 +512,14 @@ public: h(a.data_.data(), sizeof(a.data_)); } - /** Parse a hex string into a base_uint - - The input must be precisely `2 * bytes` hexadecimal characters - long, with one exception: the value '0'. - - @param sv A null-terminated string of hexadecimal characters - @return true if the input was parsed properly; false otherwise. + /** + * Parse a hex string into a base_uint + * + * The input must be precisely `2 * bytes` hexadecimal characters + * long, with one exception: the value '0'. + * + * @param sv A null-terminated string of hexadecimal characters + * @return true if the input was parsed properly; false otherwise. */ [[nodiscard]] constexpr bool parseHex(std::string_view sv) @@ -595,7 +605,7 @@ template [[nodiscard]] constexpr bool operator==(BaseUInt const& lhs, BaseUInt const& rhs) { - return (lhs <=> rhs) == 0; + return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr) } //------------------------------------------------------------------------------ diff --git a/include/xrpl/basics/chrono.h b/include/xrpl/basics/chrono.h index 61246fc699..b855318524 100644 --- a/include/xrpl/basics/chrono.h +++ b/include/xrpl/basics/chrono.h @@ -21,15 +21,16 @@ using days = using weeks = std::chrono::duration>>; -/** Clock for measuring the network time. - - The epoch is January 1, 2000 - - epoch_offset - = date(2000-01-01) - date(1970-0-01) - = days(10957) - = seconds(946684800) -*/ +/** + * Clock for measuring the network time. + * + * The epoch is January 1, 2000 + * + * epoch_offset + * = date(2000-01-01) - date(1970-0-01) + * = days(10957) + * = seconds(946684800) + */ static constexpr std::chrono::seconds kEpochOffset = date::sys_days{date::year{2000} / 1 / 1} - date::sys_days{date::year{1970} / 1 / 1}; @@ -81,16 +82,21 @@ toStringIso(NetClock::time_point tp) return toStringIso(date::sys_time{tp.time_since_epoch() + kEpochOffset}); } -/** A clock for measuring elapsed time. - - The epoch is unspecified. -*/ +/** + * A clock for measuring elapsed time. + * + * The epoch is unspecified. + */ using Stopwatch = beast::AbstractClock; -/** A manual Stopwatch for unit tests. */ +/** + * A manual Stopwatch for unit tests. + */ using TestStopwatch = beast::ManualClock; -/** Returns an instance of a wall clock. */ +/** + * Returns an instance of a wall clock. + */ inline Stopwatch& stopwatch() { diff --git a/include/xrpl/basics/contract.h b/include/xrpl/basics/contract.h index 0e90687de3..6588cb5d1a 100644 --- a/include/xrpl/basics/contract.h +++ b/include/xrpl/basics/contract.h @@ -15,20 +15,23 @@ namespace xrpl { preconditions, postconditions, and invariants. */ -/** Generates and logs a call stack */ +/** + * Generates and logs a call stack + */ void logThrow(std::string const& title); -/** Rethrow the exception currently being handled. - - When called from within a catch block, it will pass - control to the next matching exception handler, if any. - Otherwise, std::terminate will be called. - - ASAN can't handle sudden jumps in control flow very well. This - function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from - triggering false positives, since it throws. -*/ +/** + * Rethrow the exception currently being handled. + * + * When called from within a catch block, it will pass + * control to the next matching exception handler, if any. + * Otherwise, std::terminate will be called. + * + * ASAN can't handle sudden jumps in control flow very well. This + * function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from + * triggering false positives, since it throws. + */ [[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void rethrow() { @@ -56,7 +59,9 @@ Throw(Args&&... args) throw std::move(e); } -/** Called when faulty logic causes a broken invariant. */ +/** + * Called when faulty logic causes a broken invariant. + */ [[noreturn]] void logicError(std::string const& how) noexcept; diff --git a/include/xrpl/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h index 5a855736b3..6b8277a560 100644 --- a/include/xrpl/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -39,33 +39,33 @@ makeSeedPair() noexcept /** * Seed functor once per construction - - A std compatible hash adapter that resists adversarial inputs. - For this to work, T must implement in its own namespace: - - @code - - template - void - hash_append (Hasher& h, T const& t) noexcept - { - // hash_append each base and member that should - // participate in forming the hash - using beast::hash_append; - hash_append (h, static_cast(t)); - hash_append (h, static_cast(t)); - // ... - hash_append (h, t.member1); - hash_append (h, t.member2); - // ... - } - - @endcode - - Do not use any version of Murmur or CityHash for the Hasher - template parameter (the hashing algorithm). For details - see https://131002.net/siphash/#at -*/ + * + * A std compatible hash adapter that resists adversarial inputs. + * For this to work, T must implement in its own namespace: + * + * @code + * + * template + * void + * hash_append (Hasher& h, T const& t) noexcept + * { + * // hash_append each base and member that should + * // participate in forming the hash + * using beast::hash_append; + * hash_append (h, static_cast(t)); + * hash_append (h, static_cast(t)); + * // ... + * hash_append (h, t.member1); + * hash_append (h, t.member2); + * // ... + * } + * + * @endcode + * + * Do not use any version of Murmur or CityHash for the Hasher + * template parameter (the hashing algorithm). For details + * see https://131002.net/siphash/#at + */ template class HardenedHash diff --git a/include/xrpl/basics/make_SSLContext.h b/include/xrpl/basics/make_SSLContext.h index 45ac637c36..c8ada176f9 100644 --- a/include/xrpl/basics/make_SSLContext.h +++ b/include/xrpl/basics/make_SSLContext.h @@ -7,11 +7,15 @@ namespace xrpl { -/** Create a self-signed SSL context that allows anonymous Diffie Hellman. */ +/** + * Create a self-signed SSL context that allows anonymous Diffie Hellman. + */ std::shared_ptr makeSslContext(std::string const& cipherList); -/** Create an authenticated SSL context using the specified files. */ +/** + * Create an authenticated SSL context using the specified files. + */ std::shared_ptr makeSslContextAuthed( std::string const& keyFile, diff --git a/include/xrpl/basics/mulDiv.h b/include/xrpl/basics/mulDiv.h index 9076da62f2..38fa57294b 100644 --- a/include/xrpl/basics/mulDiv.h +++ b/include/xrpl/basics/mulDiv.h @@ -7,16 +7,16 @@ namespace xrpl { constexpr auto kMuldivMax = std::numeric_limits::max(); -/** Return value*mul/div accurately. - Computes the result of the multiplication and division in - a single step, avoiding overflow and retaining precision. - Throws: - None - Returns: - `std::optional`: - `std::nullopt` if the calculation overflows. Otherwise, `value * mul - / div`. -*/ +/** + * Return value*mul/div accurately. + * + * Computes the result of the multiplication and division in + * a single step, avoiding overflow and retaining precision. + * + * @throws None + * @return `std::nullopt` if the calculation overflows. Otherwise, + * `value * mul / div`. + */ std::optional mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div); diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index c544e7d0c8..7aeb7d6145 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -33,16 +33,17 @@ template using is_engine = std::is_invocable_r; } // namespace detail -/** Return the default random engine. - - This engine is guaranteed to be deterministic, but by - default will be randomly seeded. It is NOT cryptographically - secure and MUST NOT be used to generate randomness that - will be used for keys, secure cookies, IVs, padding, etc. - - Each thread gets its own instance of the engine which - will be randomly seeded. -*/ +/** + * Return the default random engine. + * + * This engine is guaranteed to be deterministic, but by + * default will be randomly seeded. It is NOT cryptographically + * secure and MUST NOT be used to generate randomness that + * will be used for keys, secure cookies, IVs, padding, etc. + * + * Each thread gets its own instance of the engine which + * will be randomly seeded. + */ inline beast::xor_shift_engine& defaultPrng() { @@ -70,25 +71,26 @@ defaultPrng() return kEngine; } -/** Return a uniformly distributed random integer. - - @param min The smallest value to return. If not specified - the value defaults to 0. - @param max The largest value to return. If not specified - the value defaults to the largest value that - can be represented. - - The randomness is generated by the specified engine (or - the default engine if one is not specified). The result - is cryptographically secure only when the engine passed - into the function is cryptographically secure. - - @note The range is always a closed interval, so calling - rand_int(-5, 15) can return any integer in the - closed interval [-5, 15]; similarly, calling - rand_int(7) can return any integer in the closed - interval [0, 7]. -*/ +/** + * Return a uniformly distributed random integer. + * + * @param min The smallest value to return. If not specified + * the value defaults to 0. + * @param max The largest value to return. If not specified + * the value defaults to the largest value that + * can be represented. + * + * The randomness is generated by the specified engine (or + * the default engine if one is not specified). The result + * is cryptographically secure only when the engine passed + * into the function is cryptographically secure. + * + * @note The range is always a closed interval, so calling + * rand_int(-5, 15) can return any integer in the + * closed interval [-5, 15]; similarly, calling + * rand_int(7) can return any integer in the closed + * interval [0, 7]. + */ /** @{ */ template Integral @@ -144,7 +146,9 @@ randInt() } /** @} */ -/** Return a random byte */ +/** + * Return a random byte + */ /** @{ */ template Byte @@ -166,7 +170,9 @@ randByte() } /** @} */ -/** Return a random boolean value */ +/** + * Return a random boolean value + */ /** @{ */ template inline bool diff --git a/include/xrpl/basics/scope.h b/include/xrpl/basics/scope.h index e63bb69eb5..5821e1dacc 100644 --- a/include/xrpl/basics/scope.h +++ b/include/xrpl/basics/scope.h @@ -156,41 +156,41 @@ template ScopeSuccess(EF) -> ScopeSuccess; /** - Automatically unlocks and re-locks a unique_lock object. - - This is the reverse of a std::unique_lock object - instead of locking the - mutex for the lifetime of this object, it unlocks it. - - Make sure you don't try to unlock mutexes that aren't actually locked! - - This is essentially a less-versatile boost::reverse_lock. - - e.g. @code - - std::mutex mut; - - for (;;) - { - std::unique_lock myScopedLock{mut}; - // mut is now locked - - ... do some stuff with it locked .. - - while (xyz) - { - ... do some stuff with it locked .. - - scope_unlock unlocker{myScopedLock}; - - // mut is now unlocked for the remainder of this block, - // and re-locked at the end. - - ...do some stuff with it unlocked ... - } // mut gets locked here. - - } // mut gets unlocked here - @endcode -*/ + * Automatically unlocks and re-locks a unique_lock object. + * + * This is the reverse of a std::unique_lock object - instead of locking the + * mutex for the lifetime of this object, it unlocks it. + * + * Make sure you don't try to unlock mutexes that aren't actually locked! + * + * This is essentially a less-versatile boost::reverse_lock. + * + * e.g. @code + * + * std::mutex mut; + * + * for (;;) + * { + * std::unique_lock myScopedLock{mut}; + * // mut is now locked + * + * ... do some stuff with it locked .. + * + * while (xyz) + * { + * ... do some stuff with it locked .. + * + * scope_unlock unlocker{myScopedLock}; + * + * // mut is now unlocked for the remainder of this block, + * // and re-locked at the end. + * + * ...do some stuff with it unlocked ... + * } // mut gets locked here. + * + * } // mut gets unlocked here + * @endcode + */ template class ScopeUnlock diff --git a/include/xrpl/basics/spinlock.h b/include/xrpl/basics/spinlock.h index 2cc00efdef..87611f20ba 100644 --- a/include/xrpl/basics/spinlock.h +++ b/include/xrpl/basics/spinlock.h @@ -15,15 +15,16 @@ namespace xrpl { namespace detail { -/** Inform the processor that we are in a tight spin-wait loop. - - Spinlocks caught in tight loops can result in the processor's pipeline - filling up with comparison operations, resulting in a misprediction at - the time the lock is finally acquired, necessitating pipeline flushing - which is ridiculously expensive and results in very high latency. - - This function instructs the processor to "pause" for some architecture - specific amount of time, to prevent this. +/** + * Inform the processor that we are in a tight spin-wait loop. + * + * Spinlocks caught in tight loops can result in the processor's pipeline + * filling up with comparison operations, resulting in a misprediction at + * the time the lock is finally acquired, necessitating pipeline flushing + * which is ridiculously expensive and results in very high latency. + * + * This function instructs the processor to "pause" for some architecture + * specific amount of time, to prevent this. */ inline void spinPause() noexcept @@ -38,37 +39,39 @@ spinPause() noexcept } // namespace detail /** @{ */ -/** Classes to handle arrays of spinlocks packed into a single atomic integer: - - Packed spinlocks allow for tremendously space-efficient lock-sharding - but they come at a cost. - - First, the implementation is necessarily low-level and uses advanced - features like memory ordering and highly platform-specific tricks to - maximize performance. This imposes a significant and ongoing cost to - developers. - - Second, and perhaps most important, is that the packing of multiple - locks into a single integer which, albeit space-efficient, also has - performance implications stemming from data dependencies, increased - cache-coherency traffic between processors and heavier loads on the - processor's load/store units. - - To be sure, these locks can have advantages but they are definitely - not general purpose locks and should not be thought of or used that - way. The use cases for them are likely few and far between; without - a compelling reason to use them, backed by profiling data, it might - be best to use one of the standard locking primitives instead. Note - that in most common platforms, `std::mutex` is so heavily optimized - that it can, usually, outperform spinlocks. - - @tparam T An unsigned integral type (e.g. std::uint16_t) +/** + * Classes to handle arrays of spinlocks packed into a single atomic integer: + * + * Packed spinlocks allow for tremendously space-efficient lock-sharding + * but they come at a cost. + * + * First, the implementation is necessarily low-level and uses advanced + * features like memory ordering and highly platform-specific tricks to + * maximize performance. This imposes a significant and ongoing cost to + * developers. + * + * Second, and perhaps most important, is that the packing of multiple + * locks into a single integer which, albeit space-efficient, also has + * performance implications stemming from data dependencies, increased + * cache-coherency traffic between processors and heavier loads on the + * processor's load/store units. + * + * To be sure, these locks can have advantages but they are definitely + * not general purpose locks and should not be thought of or used that + * way. The use cases for them are likely few and far between; without + * a compelling reason to use them, backed by profiling data, it might + * be best to use one of the standard locking primitives instead. Note + * that in most common platforms, `std::mutex` is so heavily optimized + * that it can, usually, outperform spinlocks. + * + * @tparam T An unsigned integral type (e.g. std::uint16_t) */ -/** A class that grabs a single packed spinlock from an atomic integer. - - This class meets the requirements of Lockable: - https://en.cppreference.com/w/cpp/named_req/Lockable +/** + * A class that grabs a single packed spinlock from an atomic integer. + * + * This class meets the requirements of Lockable: + * https://en.cppreference.com/w/cpp/named_req/Lockable */ template class PackedSpinlock @@ -91,13 +94,14 @@ public: PackedSpinlock& operator=(PackedSpinlock const&) = delete; - /** A single spinlock packed inside the specified atomic - - @param lock The atomic integer inside which the spinlock is packed. - @param index The index of the spinlock this object acquires. - - @note For performance reasons, you should strive to have `lock` be - on a cacheline by itself. + /** + * A single spinlock packed inside the specified atomic + * + * @param lock The atomic integer inside which the spinlock is packed. + * @param index The index of the spinlock this object acquires. + * + * @note For performance reasons, you should strive to have `lock` be + * on a cacheline by itself. */ PackedSpinlock(std::atomic& lock, int index) : bits_(lock), mask_(static_cast(1) << index) { @@ -133,17 +137,18 @@ public: } }; -/** A spinlock implemented on top of an atomic integer. - - @note Using `packed_spinlock` and `spinlock` against the same underlying - atomic integer can result in `spinlock` not being able to actually - acquire the lock during periods of high contention, because of how - the two locks operate: `spinlock` will spin trying to grab all the - bits at once, whereas any given `packed_spinlock` will only try to - grab one bit at a time. Caveat emptor. - - This class meets the requirements of Lockable: - https://en.cppreference.com/w/cpp/named_req/Lockable +/** + * A spinlock implemented on top of an atomic integer. + * + * @note Using `packed_spinlock` and `spinlock` against the same underlying + * atomic integer can result in `spinlock` not being able to actually + * acquire the lock during periods of high contention, because of how + * the two locks operate: `spinlock` will spin trying to grab all the + * bits at once, whereas any given `packed_spinlock` will only try to + * grab one bit at a time. Caveat emptor. + * + * This class meets the requirements of Lockable: + * https://en.cppreference.com/w/cpp/named_req/Lockable */ template class Spinlock @@ -159,12 +164,13 @@ public: Spinlock& operator=(Spinlock const&) = delete; - /** Grabs the - - @param lock The atomic integer to spin against. - - @note For performance reasons, you should strive to have `lock` be - on a cacheline by itself. + /** + * Grabs the + * + * @param lock The atomic integer to spin against. + * + * @note For performance reasons, you should strive to have `lock` be + * on a cacheline by itself. */ Spinlock(std::atomic& lock) : lock_(lock) { diff --git a/include/xrpl/basics/tagged_integer.h b/include/xrpl/basics/tagged_integer.h index 5a088db863..2edb314a16 100644 --- a/include/xrpl/basics/tagged_integer.h +++ b/include/xrpl/basics/tagged_integer.h @@ -12,17 +12,18 @@ namespace xrpl { -/** A type-safe wrap around standard integral types - - The tag is used to implement type safety, catching mismatched types at - compile time. Multiple instantiations wrapping the same underlying integral - type are distinct types (distinguished by tag) and will not interoperate. A - tagged_integer supports all the usual assignment, arithmetic, comparison and - shifting operations defined for the underlying type - - The tag is not meant as a unit, which would require restricting the set of - allowed arithmetic operations. -*/ +/** + * A type-safe wrap around standard integral types + * + * The tag is used to implement type safety, catching mismatched types at + * compile time. Multiple instantiations wrapping the same underlying integral + * type are distinct types (distinguished by tag) and will not interoperate. A + * tagged_integer supports all the usual assignment, arithmetic, comparison and + * shifting operations defined for the underlying type + * + * The tag is not meant as a unit, which would require restricting the set of + * allowed arithmetic operations. + */ template class TaggedInteger : boost::totally_ordered< TaggedInteger, diff --git a/include/xrpl/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h index f67ff4a692..d87bdafe45 100644 --- a/include/xrpl/beast/asio/io_latency_probe.h +++ b/include/xrpl/beast/asio/io_latency_probe.h @@ -14,7 +14,9 @@ namespace beast { -/** Measures handler latency on an io_context queue. */ +/** + * Measures handler latency on an io_context queue. + */ template class IOLatencyProbe { @@ -42,7 +44,9 @@ public: cancel(lock, true); } - /** Return the io_context associated with the latency probe. */ + /** + * Return the io_context associated with the latency probe. + */ /** @{ */ boost::asio::io_context& getIoContext() @@ -57,9 +61,10 @@ public: } /** @} */ - /** Cancel all pending i/o. - Any handlers which have already been queued will still be called. - */ + /** + * Cancel all pending i/o. + * Any handlers which have already been queued will still be called. + */ /** @{ */ void cancel() @@ -76,10 +81,11 @@ public: } /** @} */ - /** Measure one sample of i/o latency. - Handler will be called with this signature: - void Handler (Duration d); - */ + /** + * Measure one sample of i/o latency. + * Handler will be called with this signature: + * void Handler (Duration d); + */ template void sampleOne(Handler&& handler) @@ -91,10 +97,11 @@ public: ios_, SampleOp(std::forward(handler), Clock::now(), false, this)); } - /** Initiate continuous i/o latency sampling. - Handler will be called with this signature: - void Handler (std::chrono::milliseconds); - */ + /** + * Initiate continuous i/o latency sampling. + * Handler will be called with this signature: + * void Handler (std::chrono::milliseconds); + */ template void sample(Handler&& handler) diff --git a/include/xrpl/beast/clock/abstract_clock.h b/include/xrpl/beast/clock/abstract_clock.h index 15d785d138..6e23700730 100644 --- a/include/xrpl/beast/clock/abstract_clock.h +++ b/include/xrpl/beast/clock/abstract_clock.h @@ -2,34 +2,35 @@ namespace beast { -/** Abstract interface to a clock. - - This makes now() a member function instead of a static member, so - an instance of the class can be dependency injected, facilitating - unit tests where time may be controlled. - - An abstract_clock inherits all the nested types of the Clock - template parameter. - - Example: - - @code - - struct Implementation - { - using clock_type = abstract_clock ; - clock_type& clock_; - explicit Implementation (clock_type& clock) - : clock_(clock) - { - } - }; - - @endcode - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * Abstract interface to a clock. + * + * This makes now() a member function instead of a static member, so + * an instance of the class can be dependency injected, facilitating + * unit tests where time may be controlled. + * + * An abstract_clock inherits all the nested types of the Clock + * template parameter. + * + * Example: + * + * @code + * + * struct Implementation + * { + * using clock_type = abstract_clock ; + * clock_type& clock_; + * explicit Implementation (clock_type& clock) + * : clock_(clock) + * { + * } + * }; + * + * @endcode + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ template class AbstractClock { @@ -46,7 +47,9 @@ public: AbstractClock() = default; AbstractClock(AbstractClock const&) = default; - /** Returns the current time. */ + /** + * Returns the current time. + */ [[nodiscard]] virtual time_point now() const = 0; }; @@ -74,11 +77,12 @@ struct AbstractClockWrapper : public AbstractClock //------------------------------------------------------------------------------ -/** Returns a global instance of an abstract clock. - @tparam Facade A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock - @tparam Clock The actual concrete clock to use. -*/ +/** + * Returns a global instance of an abstract clock. + * @tparam Facade A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + * @tparam Clock The actual concrete clock to use. + */ template AbstractClock& getAbstractClock() diff --git a/include/xrpl/beast/clock/basic_seconds_clock.h b/include/xrpl/beast/clock/basic_seconds_clock.h index 5a267e9458..dce521d0b8 100644 --- a/include/xrpl/beast/clock/basic_seconds_clock.h +++ b/include/xrpl/beast/clock/basic_seconds_clock.h @@ -4,15 +4,16 @@ namespace beast { -/** A clock whose minimum resolution is one second. - - The purpose of this class is to optimize the performance of the now() - member function call. It uses a dedicated thread that wakes up at least - once per second to sample the requested trivial clock. - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * A clock whose minimum resolution is one second. + * + * The purpose of this class is to optimize the performance of the now() + * member function call. It uses a dedicated thread that wakes up at least + * once per second to sample the requested trivial clock. + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ class BasicSecondsClock { public: diff --git a/include/xrpl/beast/clock/manual_clock.h b/include/xrpl/beast/clock/manual_clock.h index 8b3e4e63c6..4dc9553644 100644 --- a/include/xrpl/beast/clock/manual_clock.h +++ b/include/xrpl/beast/clock/manual_clock.h @@ -7,15 +7,16 @@ namespace beast { -/** Manual clock implementation. - - This concrete class implements the @ref abstract_clock interface and - allows the time to be advanced manually, mainly for the purpose of - providing a clock in unit tests. - - @tparam Clock A type meeting these requirements: - http://en.cppreference.com/w/cpp/concept/Clock -*/ +/** + * Manual clock implementation. + * + * This concrete class implements the @ref abstract_clock interface and + * allows the time to be advanced manually, mainly for the purpose of + * providing a clock in unit tests. + * + * @tparam Clock A type meeting these requirements: + * http://en.cppreference.com/w/cpp/concept/Clock + */ template class ManualClock : public AbstractClock { @@ -38,7 +39,9 @@ public: return now_; } - /** Set the current time of the manual clock. */ + /** + * Set the current time of the manual clock. + */ void set(time_point const& when) { @@ -48,7 +51,9 @@ public: now_ = when; } - /** Convenience for setting the time in seconds from epoch. */ + /** + * Convenience for setting the time in seconds from epoch. + */ template void set(Integer secondsFromEpoch) @@ -56,7 +61,9 @@ public: set(time_point(duration(std::chrono::seconds(secondsFromEpoch)))); } - /** Advance the clock by a duration. */ + /** + * Advance the clock by a duration. + */ template void advance(std::chrono::duration const& elapsed) @@ -67,7 +74,9 @@ public: now_ += elapsed; } - /** Convenience for advancing the clock by one second. */ + /** + * Convenience for advancing the clock by one second. + */ ManualClock& operator++() { diff --git a/include/xrpl/beast/container/aged_container_utility.h b/include/xrpl/beast/container/aged_container_utility.h index f43e59b0f7..da3e4e0500 100644 --- a/include/xrpl/beast/container/aged_container_utility.h +++ b/include/xrpl/beast/container/aged_container_utility.h @@ -7,7 +7,9 @@ namespace beast { -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template std::size_t expire(AgedContainer& c, std::chrono::duration const& age) diff --git a/include/xrpl/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h index c98f16022f..5b60ef7e6d 100644 --- a/include/xrpl/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -39,22 +39,23 @@ struct IsBoostReverseIterator> : std::tru explicit IsBoostReverseIterator() = default; }; -/** Associative container where each element is also indexed by time. - - This container mirrors the interface of the standard library ordered - associative containers, with the addition that each element is associated - with a `when` `time_point` which is obtained from the value of the clock's - `now`. The function `touch` updates the time for an element to the current - time as reported by the clock. - - An extra set of iterator types and member functions are provided in the - `chronological` memberspace that allow traversal in temporal or reverse - temporal order. This container is useful as a building block for caches - whose items expire after a certain amount of time. The chronological - iterators allow for fully customizable expiration strategies. - - @see aged_set, aged_multiset, aged_map, aged_multimap -*/ +/** + * Associative container where each element is also indexed by time. + * + * This container mirrors the interface of the standard library ordered + * associative containers, with the addition that each element is associated + * with a `when` `time_point` which is obtained from the value of the clock's + * `now`. The function `touch` updates the time for an element to the current + * time as reported by the clock. + * + * An extra set of iterator types and member functions are provided in the + * `chronological` memberspace that allow traversal in temporal or reverse + * temporal order. This container is useful as a building block for caches + * whose items expire after a certain amount of time. The chronological + * iterators allow for fully customizable expiration strategies. + * + * @see aged_set, aged_multiset, aged_map, aged_multimap + */ template < bool IsMulti, bool IsMap, @@ -1795,7 +1796,9 @@ swap( lhs.swap(rhs); } -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template < bool IsMulti, bool IsMap, diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index 9be9e96ba2..db10e8cc23 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -43,23 +43,24 @@ TODO namespace beast { namespace detail { -/** Associative container where each element is also indexed by time. - - This container mirrors the interface of the standard library unordered - associative containers, with the addition that each element is associated - with a `when` `time_point` which is obtained from the value of the clock's - `now`. The function `touch` updates the time for an element to the current - time as reported by the clock. - - An extra set of iterator types and member functions are provided in the - `chronological` memberspace that allow traversal in temporal or reverse - temporal order. This container is useful as a building block for caches - whose items expire after a certain amount of time. The chronological - iterators allow for fully customizable expiration strategies. - - @see aged_unordered_set, aged_unordered_multiset - @see aged_unordered_map, aged_unordered_multimap -*/ +/** + * Associative container where each element is also indexed by time. + * + * This container mirrors the interface of the standard library unordered + * associative containers, with the addition that each element is associated + * with a `when` `time_point` which is obtained from the value of the clock's + * `now`. The function `touch` updates the time for an element to the current + * time as reported by the clock. + * + * An extra set of iterator types and member functions are provided in the + * `chronological` memberspace that allow traversal in temporal or reverse + * temporal order. This container is useful as a building block for caches + * whose items expire after a certain amount of time. The chronological + * iterators allow for fully customizable expiration strategies. + * + * @see aged_unordered_set, aged_unordered_multiset + * @see aged_unordered_map, aged_unordered_multimap + */ template < bool IsMulti, bool IsMap, @@ -2709,7 +2710,9 @@ swap( lhs.swap(rhs); } -/** Expire aged container items past the specified age. */ +/** + * Expire aged container items past the specified age. + */ template < bool IsMulti, bool IsMap, diff --git a/include/xrpl/beast/core/CurrentThreadName.h b/include/xrpl/beast/core/CurrentThreadName.h index 3cdfe4c678..d1f14a6f80 100644 --- a/include/xrpl/beast/core/CurrentThreadName.h +++ b/include/xrpl/beast/core/CurrentThreadName.h @@ -12,9 +12,10 @@ namespace beast { -/** Changes the name of the caller thread. - Different OSes may place different length or content limits on this name. -*/ +/** + * 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); @@ -24,13 +25,14 @@ setCurrentThreadName(std::string_view newThreadName); // Maximum number of characters is therefore 15. constexpr std::size_t kMaxThreadNameLength = 15; -/** Sets the name of the caller thread with compile-time size checking. - @tparam N The size of the string literal including null terminator - @param newThreadName A string literal to set as the thread name - - This template overload enforces that thread names are at most 16 characters - (including null terminator) at compile time, matching Linux's limit. -*/ +/** + * Sets the name of the caller thread with compile-time size checking. + * @tparam N The size of the string literal including null terminator + * @param newThreadName A string literal to set as the thread name + * + * This template overload enforces that thread names are at most 16 characters + * (including null terminator) at compile time, matching Linux's limit. + */ template void setCurrentThreadName(char const (&newThreadName)[N]) @@ -41,14 +43,15 @@ setCurrentThreadName(char const (&newThreadName)[N]) } #endif -/** Returns the name of the caller 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. -*/ +/** + * Returns the name of the caller 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 getCurrentThreadName(); diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index 1162d83078..7cf21892bd 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -163,17 +163,19 @@ struct LexicalCast //------------------------------------------------------------------------------ -/** Thrown when a conversion is not possible with LexicalCast. - Only used in the throw variants of lexicalCast. -*/ +/** + * Thrown when a conversion is not possible with LexicalCast. + * Only used in the throw variants of lexicalCast. + */ struct BadLexicalCast : public std::bad_cast { explicit BadLexicalCast() = default; }; -/** Intelligently convert from one type to another. - @return `false` if there was a parsing or range error -*/ +/** + * Intelligently convert from one type to another. + * @return `false` if there was a parsing or range error + */ template bool lexicalCastChecked(Out& out, In in) @@ -181,12 +183,13 @@ lexicalCastChecked(Out& out, In in) return detail::LexicalCast()(out, in); } -/** Convert from one type to another, throw on error - - An exception of type BadLexicalCast is thrown if the conversion fails. - - @return The new type. -*/ +/** + * Convert from one type to another, throw on error + * + * An exception of type BadLexicalCast is thrown if the conversion fails. + * + * @return The new type. + */ template Out lexicalCastThrow(In in) @@ -197,11 +200,12 @@ lexicalCastThrow(In in) throw BadLexicalCast(); } -/** Convert from one type to another. - - @param defaultValue The value returned if parsing fails - @return The new type. -*/ +/** + * Convert from one type to another. + * + * @param defaultValue The value returned if parsing fails + * @return The new type. + */ template Out lexicalCast(In in, Out defaultValue = Out()) diff --git a/include/xrpl/beast/core/List.h b/include/xrpl/beast/core/List.h index 1eeeaa87d1..b9b6829d31 100644 --- a/include/xrpl/beast/core/List.h +++ b/include/xrpl/beast/core/List.h @@ -11,7 +11,9 @@ class List; namespace detail { -/** Copy `const` attribute from T to U if present. */ +/** + * Copy `const` attribute from T to U if present. + */ /** @{ */ template struct CopyConst @@ -153,110 +155,111 @@ private: } // namespace detail -/** Intrusive doubly linked list. - - This intrusive List is a container similar in operation to std::list in the - Standard Template Library (STL). Like all @ref intrusive containers, List - requires you to first derive your class from List<>::Node: - - @code - - struct Object : List ::Node - { - explicit Object (int value) : value_ (value) - { - } - - int value_; - }; - - @endcode - - Now we define the list, and add a couple of items. - - @code - - List list; - - list.push_back (* (new Object (1))); - list.push_back (* (new Object (2))); - - @endcode - - For compatibility with the standard containers, push_back() expects a - reference to the object. Unlike the standard container, however, push_back() - places the actual object in the list and not a copy-constructed duplicate. - - Iterating over the list follows the same idiom as the STL: - - @code - - for (List ::iterator iter = list.begin(); iter != list.end; ++iter) - std::cout << iter->value_; - - @endcode - - You can even use BOOST_FOREACH, or range based for loops: - - @code - - BOOST_FOREACH (Object& object, list) // boost only - std::cout << object.value_; - - for (Object& object : list) // C++11 only - std::cout << object.value_; - - @endcode - - Because List is mostly STL compliant, it can be passed into STL algorithms: - e.g. `std::for_each()` or `std::find_first_of()`. - - In general, objects placed into a List should be dynamically allocated - although this cannot be enforced at compile time. Since the caller provides - the storage for the object, the caller is also responsible for deleting the - object. An object still exists after being removed from a List, until the - caller deletes it. This means an element can be moved from one List to - another with practically no overhead. - - Unlike the standard containers, an object may only exist in one list at a - time, unless special preparations are made. The Tag template parameter is - used to distinguish between different list types for the same object, - allowing the object to exist in more than one list simultaneously. - - For example, consider an actor system where a global list of actors is - maintained, so that they can each be periodically receive processing - time. We wish to also maintain a list of the subset of actors that require - a domain-dependent update. To achieve this, we declare two tags, the - associated list types, and the list element thusly: - - @code - - struct Actor; // Forward declaration required - - struct ProcessTag { }; - struct UpdateTag { }; - - using ProcessList = List ; - using UpdateList = List ; - - // Derive from both node types so we can be in each list at once. - // - struct Actor : ProcessList::Node, UpdateList::Node - { - bool process (); // returns true if we need an update - void update (); - }; - - @endcode - - @tparam T The base type of element which the list will store - pointers to. - - @tparam Tag An optional unique type name used to distinguish lists and - nodes, when the object can exist in multiple lists simultaneously. - - @ingroup beast_core intrusive -*/ +/** + * Intrusive doubly linked list. + * + * This intrusive List is a container similar in operation to std::list in the + * Standard Template Library (STL). Like all @ref intrusive containers, List + * requires you to first derive your class from List<>::Node: + * + * @code + * + * struct Object : List ::Node + * { + * explicit Object (int value) : value_ (value) + * { + * } + * + * int value_; + * }; + * + * @endcode + * + * Now we define the list, and add a couple of items. + * + * @code + * + * List list; + * + * list.push_back (* (new Object (1))); + * list.push_back (* (new Object (2))); + * + * @endcode + * + * For compatibility with the standard containers, push_back() expects a + * reference to the object. Unlike the standard container, however, push_back() + * places the actual object in the list and not a copy-constructed duplicate. + * + * Iterating over the list follows the same idiom as the STL: + * + * @code + * + * for (List ::iterator iter = list.begin(); iter != list.end; ++iter) + * std::cout << iter->value_; + * + * @endcode + * + * You can even use BOOST_FOREACH, or range based for loops: + * + * @code + * + * BOOST_FOREACH (Object& object, list) // boost only + * std::cout << object.value_; + * + * for (Object& object : list) // C++11 only + * std::cout << object.value_; + * + * @endcode + * + * Because List is mostly STL compliant, it can be passed into STL algorithms: + * e.g. `std::for_each()` or `std::find_first_of()`. + * + * In general, objects placed into a List should be dynamically allocated + * although this cannot be enforced at compile time. Since the caller provides + * the storage for the object, the caller is also responsible for deleting the + * object. An object still exists after being removed from a List, until the + * caller deletes it. This means an element can be moved from one List to + * another with practically no overhead. + * + * Unlike the standard containers, an object may only exist in one list at a + * time, unless special preparations are made. The Tag template parameter is + * used to distinguish between different list types for the same object, + * allowing the object to exist in more than one list simultaneously. + * + * For example, consider an actor system where a global list of actors is + * maintained, so that they can each be periodically receive processing + * time. We wish to also maintain a list of the subset of actors that require + * a domain-dependent update. To achieve this, we declare two tags, the + * associated list types, and the list element thusly: + * + * @code + * + * struct Actor; // Forward declaration required + * + * struct ProcessTag { }; + * struct UpdateTag { }; + * + * using ProcessList = List ; + * using UpdateList = List ; + * + * // Derive from both node types so we can be in each list at once. + * // + * struct Actor : ProcessList::Node, UpdateList::Node + * { + * bool process (); // returns true if we need an update + * void update (); + * }; + * + * @endcode + * + * @tparam T The base type of element which the list will store + * pointers to. + * + * @tparam Tag An optional unique type name used to distinguish lists and + * nodes, when the object can exist in multiple lists simultaneously. + * + * @ingroup beast_core intrusive + */ template class List { @@ -274,7 +277,9 @@ public: using iterator = detail::ListIterator; using const_iterator = detail::ListIterator; - /** Create an empty list. */ + /** + * Create an empty list. + */ List() { head_.prev_ = nullptr; // identifies the head @@ -286,119 +291,133 @@ public: List& operator=(List const&) = delete; - /** Determine if the list is empty. - @return `true` if the list is empty. - */ + /** + * Determine if the list is empty. + * @return `true` if the list is empty. + */ [[nodiscard]] bool empty() const noexcept { return size() == 0; } - /** Returns the number of elements in the list. */ + /** + * Returns the number of elements in the list. + */ [[nodiscard]] size_type size() const noexcept { return size_; } - /** Obtain a reference to the first element. - @invariant The list may not be empty. - @return A reference to the first element. - */ + /** + * Obtain a reference to the first element. + * @invariant The list may not be empty. + * @return A reference to the first element. + */ reference front() noexcept { return element_from(head_.next_); } - /** Obtain a const reference to the first element. - @invariant The list may not be empty. - @return A const reference to the first element. - */ + /** + * Obtain a const reference to the first element. + * @invariant The list may not be empty. + * @return A const reference to the first element. + */ [[nodiscard]] const_reference front() const noexcept { return element_from(head_.next_); } - /** Obtain a reference to the last element. - @invariant The list may not be empty. - @return A reference to the last element. - */ + /** + * Obtain a reference to the last element. + * @invariant The list may not be empty. + * @return A reference to the last element. + */ reference back() noexcept { return element_from(tail_.prev_); } - /** Obtain a const reference to the last element. - @invariant The list may not be empty. - @return A const reference to the last element. - */ + /** + * Obtain a const reference to the last element. + * @invariant The list may not be empty. + * @return A const reference to the last element. + */ [[nodiscard]] const_reference back() const noexcept { return element_from(tail_.prev_); } - /** Obtain an iterator to the beginning of the list. - @return An iterator pointing to the beginning of the list. - */ + /** + * Obtain an iterator to the beginning of the list. + * @return An iterator pointing to the beginning of the list. + */ iterator begin() noexcept { return iterator(head_.next_); } - /** Obtain a const iterator to the beginning of the list. - @return A const iterator pointing to the beginning of the list. - */ + /** + * Obtain a const iterator to the beginning of the list. + * @return A const iterator pointing to the beginning of the list. + */ [[nodiscard]] const_iterator begin() const noexcept { return const_iterator(head_.next_); } - /** Obtain a const iterator to the beginning of the list. - @return A const iterator pointing to the beginning of the list. - */ + /** + * Obtain a const iterator to the beginning of the list. + * @return A const iterator pointing to the beginning of the list. + */ [[nodiscard]] const_iterator cbegin() const noexcept { return const_iterator(head_.next_); } - /** Obtain a iterator to the end of the list. - @return An iterator pointing to the end of the list. - */ + /** + * Obtain a iterator to the end of the list. + * @return An iterator pointing to the end of the list. + */ iterator end() noexcept { return iterator(&tail_); } - /** Obtain a const iterator to the end of the list. - @return A constiterator pointing to the end of the list. - */ + /** + * Obtain a const iterator to the end of the list. + * @return A constiterator pointing to the end of the list. + */ [[nodiscard]] const_iterator end() const noexcept { return const_iterator(&tail_); } - /** Obtain a const iterator to the end of the list - @return A constiterator pointing to the end of the list. - */ + /** + * Obtain a const iterator to the end of the list + * @return A constiterator pointing to the end of the list. + */ [[nodiscard]] const_iterator cend() const noexcept { return const_iterator(&tail_); } - /** Clear the list. - @note This does not free the elements. - */ + /** + * Clear the list. + * @note This does not free the elements. + */ void clear() noexcept { @@ -407,12 +426,13 @@ public: size_ = 0; } - /** Insert an element. - @invariant The element must not already be in the list. - @param pos The location to insert after. - @param element The element to insert. - @return An iterator pointing to the newly inserted element. - */ + /** + * Insert an element. + * @invariant The element must not already be in the list. + * @param pos The location to insert after. + * @param element The element to insert. + * @return An iterator pointing to the newly inserted element. + */ iterator insert(iterator pos, T& element) noexcept { @@ -425,11 +445,12 @@ public: return iterator(node); } - /** Insert another list into this one. - The other list is cleared. - @param pos The location to insert after. - @param other The list to insert. - */ + /** + * Insert another list into this one. + * The other list is cleared. + * @param pos The location to insert after. + * @param other The list to insert. + */ void insert(iterator pos, List& other) noexcept { @@ -445,11 +466,12 @@ public: } } - /** Remove an element. - @invariant The element must exist in the list. - @param pos An iterator pointing to the element to remove. - @return An iterator pointing to the next element after the one removed. - */ + /** + * Remove an element. + * @invariant The element must exist in the list. + * @param pos An iterator pointing to the element to remove. + * @return An iterator pointing to the next element after the one removed. + */ iterator erase(iterator pos) noexcept { @@ -461,20 +483,22 @@ public: return pos; } - /** Insert an element at the beginning of the list. - @invariant The element must not exist in the list. - @param element The element to insert. - */ + /** + * Insert an element at the beginning of the list. + * @invariant The element must not exist in the list. + * @param element The element to insert. + */ iterator pushFront(T& element) noexcept { return insert(begin(), element); } - /** Remove the element at the beginning of the list. - @invariant The list must not be empty. - @return A reference to the popped element. - */ + /** + * Remove the element at the beginning of the list. + * @invariant The list must not be empty. + * @return A reference to the popped element. + */ T& popFront() noexcept { @@ -483,20 +507,22 @@ public: return element; } - /** Append an element at the end of the list. - @invariant The element must not exist in the list. - @param element The element to append. - */ + /** + * Append an element at the end of the list. + * @invariant The element must not exist in the list. + * @param element The element to append. + */ iterator pushBack(T& element) noexcept { return insert(end(), element); } - /** Remove the element at the end of the list. - @invariant The list must not be empty. - @return A reference to the popped element. - */ + /** + * Remove the element at the end of the list. + * @invariant The list must not be empty. + * @return A reference to the popped element. + */ T& popBack() noexcept { @@ -505,7 +531,9 @@ public: return element; } - /** Swap contents with another list. */ + /** + * Swap contents with another list. + */ void swap(List& other) noexcept { @@ -515,42 +543,46 @@ public: append(temp); } - /** Insert another list at the beginning of this list. - The other list is cleared. - @param list The other list to insert. - */ + /** + * Insert another list at the beginning of this list. + * The other list is cleared. + * @param list The other list to insert. + */ iterator prepend(List& list) noexcept { return insert(begin(), list); } - /** Append another list at the end of this list. - The other list is cleared. - @param list the other list to append. - */ + /** + * Append another list at the end of this list. + * The other list is cleared. + * @param list the other list to append. + */ iterator append(List& list) noexcept { return insert(end(), list); } - /** Obtain an iterator from an element. - @invariant The element must exist in the list. - @param element The element to obtain an iterator for. - @return An iterator to the element. - */ + /** + * Obtain an iterator from an element. + * @invariant The element must exist in the list. + * @param element The element to obtain an iterator for. + * @return An iterator to the element. + */ iterator iteratorTo(T& element) const noexcept { return iterator(static_cast(&element)); } - /** Obtain a const iterator from an element. - @invariant The element must exist in the list. - @param element The element to obtain an iterator for. - @return A const iterator to the element. - */ + /** + * Obtain a const iterator from an element. + * @invariant The element must exist in the list. + * @param element The element to obtain an iterator for. + * @return A const iterator to the element. + */ [[nodiscard]] const_iterator constIteratorTo(T const& element) const noexcept { diff --git a/include/xrpl/beast/core/LockFreeStack.h b/include/xrpl/beast/core/LockFreeStack.h index dd135d2d98..849edc8fce 100644 --- a/include/xrpl/beast/core/LockFreeStack.h +++ b/include/xrpl/beast/core/LockFreeStack.h @@ -103,18 +103,19 @@ operator!=( //------------------------------------------------------------------------------ -/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack. - - This stack is implemented using the same intrusive interface as List. - All mutations are lock-free. - - The caller is responsible for preventing the "ABA" problem: - http://en.wikipedia.org/wiki/ABA_problem - - @param Tag A type name used to distinguish lists and nodes, for - putting objects in multiple lists. If this parameter is - omitted, the default tag is used. -*/ +/** + * Multiple Producer, Multiple Consumer (MPMC) intrusive stack. + * + * This stack is implemented using the same intrusive interface as List. + * All mutations are lock-free. + * + * The caller is responsible for preventing the "ABA" problem: + * http://en.wikipedia.org/wiki/ABA_problem + * + * @param Tag A type name used to distinguish lists and nodes, for + * putting objects in multiple lists. If this parameter is + * omitted, the default tag is used. + */ template class LockFreeStack { @@ -162,24 +163,27 @@ public: LockFreeStack& operator=(LockFreeStack const&) = delete; - /** Returns true if the stack is empty. */ + /** + * Returns true if the stack is empty. + */ [[nodiscard]] bool empty() const { return head_.load() == &end_; } - /** Push a node onto the stack. - The caller is responsible for preventing the ABA problem. - This operation is lock-free. - Thread safety: - Safe to call from any thread. - - @param node The node to push. - - @return `true` if the stack was previously empty. If multiple threads - are attempting to push, only one will receive `true`. - */ + /** + * Push a node onto the stack. + * The caller is responsible for preventing the ABA problem. + * This operation is lock-free. + * Thread safety: + * Safe to call from any thread. + * + * @param node The node to push. + * + * @return `true` if the stack was previously empty. If multiple threads + * are attempting to push, only one will receive `true`. + */ // VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list? bool pushFront(Node* node) @@ -195,15 +199,16 @@ public: return first; } - /** Pop an element off the stack. - The caller is responsible for preventing the ABA problem. - This operation is lock-free. - Thread safety: - Safe to call from any thread. - - @return The element that was popped, or `nullptr` if the stack - was empty. - */ + /** + * Pop an element off the stack. + * The caller is responsible for preventing the ABA problem. + * This operation is lock-free. + * Thread safety: + * Safe to call from any thread. + * + * @return The element that was popped, or `nullptr` if the stack + * was empty. + */ Element* popFront() { @@ -219,12 +224,13 @@ public: return static_cast(node); } - /** Return a forward iterator to the beginning or end of the stack. - Undefined behavior results if push_front or pop_front is called - while an iteration is in progress. - Thread safety: - Caller is responsible for synchronization. - */ + /** + * Return a forward iterator to the beginning or end of the stack. + * Undefined behavior results if push_front or pop_front is called + * while an iteration is in progress. + * Thread safety: + * Caller is responsible for synchronization. + */ /** @{ */ iterator begin() diff --git a/include/xrpl/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h index 826a43d3f8..338942c252 100644 --- a/include/xrpl/beast/core/SemanticVersion.h +++ b/include/xrpl/beast/core/SemanticVersion.h @@ -6,13 +6,14 @@ namespace beast { -/** A Semantic Version number. - - Identifies the build of a particular version of software using - the Semantic Versioning Specification described here: - - http://semver.org/ -*/ +/** + * A Semantic Version number. + * + * Identifies the build of a particular version of software using + * the Semantic Versioning Specification described here: + * + * http://semver.org/ + */ class SemanticVersion { public: @@ -29,14 +30,17 @@ public: SemanticVersion(std::string_view version); - /** Parse a semantic version string. - The parsing is as strict as possible. - @return `true` if the string was parsed. - */ + /** + * Parse a semantic version string. + * The parsing is as strict as possible. + * @return `true` if the string was parsed. + */ bool parse(std::string_view input); - /** Produce a string from semantic version components. */ + /** + * Produce a string from semantic version components. + */ [[nodiscard]] std::string print() const; @@ -52,9 +56,10 @@ public: } }; -/** Compare two SemanticVersions against each other. - The comparison follows the rules as per the specification. -*/ +/** + * Compare two SemanticVersions against each other. + * The comparison follows the rules as per the specification. + */ int compare(SemanticVersion const& lhs, SemanticVersion const& rhs); diff --git a/include/xrpl/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h index 3592ffbfe8..c5374f95e5 100644 --- a/include/xrpl/beast/hash/hash_append.h +++ b/include/xrpl/beast/hash/hash_append.h @@ -135,19 +135,20 @@ struct IsUniquelyRepresented> explicit IsUniquelyRepresented() = default; }; -/** Metafunction returning `true` if the type can be hashed in one call. - - For `IsContiguouslyHashable::value` to be true, then for every - combination of possible values of `T` held in `x` and `y`, - if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))` - return 0; i.e. that `x` and `y` are represented by the same bit pattern. - - For example: A two's complement `int` should be contiguously hashable. - Every bit pattern produces a unique value that does not compare equal to - any other bit pattern's value. A IEEE floating point should not be - contiguously hashable because -0. and 0. have different bit patterns, - though they compare equal. -*/ +/** + * Metafunction returning `true` if the type can be hashed in one call. + * + * For `IsContiguouslyHashable::value` to be true, then for every + * combination of possible values of `T` held in `x` and `y`, + * if `x == y`, then it must be true that `memcmp(&x, &y, sizeof(T))` + * return 0; i.e. that `x` and `y` are represented by the same bit pattern. + * + * For example: A two's complement `int` should be contiguously hashable. + * Every bit pattern produces a unique value that does not compare equal to + * any other bit pattern's value. A IEEE floating point should not be + * contiguously hashable because -0. and 0. have different bit patterns, + * though they compare equal. + */ /** @{ */ template struct IsContiguouslyHashable @@ -172,29 +173,30 @@ struct IsContiguouslyHashable //------------------------------------------------------------------------------ -/** Logically concatenate input data to a `Hasher`. - - Hasher requirements: - - `X` is the type `Hasher` - `h` is a value of type `x` - `p` is a value convertible to `void const*` - `n` is a value of type `std::size_t`, greater than zero - - Expression: - `h.append (p, n);` - Throws: - Never - Effect: - Adds the input data to the hasher state. - - Expression: - `static_cast(j)` - Throws: - Never - Effect: - Returns the resulting hash of all the input data. -*/ +/** + * Logically concatenate input data to a `Hasher`. + * + * Hasher requirements: + * + * `X` is the type `Hasher` + * `h` is a value of type `x` + * `p` is a value convertible to `void const*` + * `n` is a value of type `std::size_t`, greater than zero + * + * Expression: + * `h.append (p, n);` + * Throws: + * Never + * Effect: + * Adds the input data to the hasher state. + * + * Expression: + * `static_cast(j)` + * Throws: + * Never + * Effect: + * Returns the resulting hash of all the input data. + */ /** @{ */ // scalars diff --git a/include/xrpl/beast/insight/Collector.h b/include/xrpl/beast/insight/Collector.h index 3f83e329d4..9da2a8bb74 100644 --- a/include/xrpl/beast/insight/Collector.h +++ b/include/xrpl/beast/insight/Collector.h @@ -12,16 +12,17 @@ namespace beast::insight { -/** Interface for a manager that allows collection of metrics. - - To export metrics from a class, pass and save a shared_ptr to this - interface in the class constructor. Create the metric objects - as desired (counters, events, gauges, meters, and an optional hook) - using the interface. - - @see Counter, Event, Gauge, Hook, Meter - @see NullCollector, StatsDCollector -*/ +/** + * Interface for a manager that allows collection of metrics. + * + * To export metrics from a class, pass and save a shared_ptr to this + * interface in the class constructor. Create the metric objects + * as desired (counters, events, gauges, meters, and an optional hook) + * using the interface. + * + * @see Counter, Event, Gauge, Hook, Meter + * @see NullCollector, StatsDCollector + */ class Collector { public: @@ -29,18 +30,19 @@ public: virtual ~Collector() = 0; - /** Create a hook. - - A hook is called at each collection interval, on an implementation - defined thread. This is a convenience facility for gathering metrics - in the polling style. The typical usage is to update all the metrics - of interest in the handler. - - Handler will be called with this signature: - void handler (void) - - @see Hook - */ + /** + * Create a hook. + * + * A hook is called at each collection interval, on an implementation + * defined thread. This is a convenience facility for gathering metrics + * in the polling style. The typical usage is to update all the metrics + * of interest in the handler. + * + * Handler will be called with this signature: + * void handler (void) + * + * @see Hook + */ /** @{ */ template Hook @@ -53,9 +55,10 @@ public: makeHook(HookImpl::HandlerType const& handler) = 0; /** @} */ - /** Create a counter with the specified name. - @see Counter - */ + /** + * Create a counter with the specified name. + * @see Counter + */ /** @{ */ virtual Counter makeCounter(std::string const& name) = 0; @@ -69,9 +72,10 @@ public: } /** @} */ - /** Create an event with the specified name. - @see Event - */ + /** + * Create an event with the specified name. + * @see Event + */ /** @{ */ virtual Event makeEvent(std::string const& name) = 0; @@ -85,9 +89,10 @@ public: } /** @} */ - /** Create a gauge with the specified name. - @see Gauge - */ + /** + * Create a gauge with the specified name. + * @see Gauge + */ /** @{ */ virtual Gauge makeGauge(std::string const& name) = 0; @@ -101,9 +106,10 @@ public: } /** @} */ - /** Create a meter with the specified name. - @see Meter - */ + /** + * Create a meter with the specified name. + * @see Meter + */ /** @{ */ virtual Meter makeMeter(std::string const& name) = 0; diff --git a/include/xrpl/beast/insight/Counter.h b/include/xrpl/beast/insight/Counter.h index 482808b2c7..875fadf33a 100644 --- a/include/xrpl/beast/insight/Counter.h +++ b/include/xrpl/beast/insight/Counter.h @@ -7,34 +7,39 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A counter is a gauge calculated at the server. The owner of the counter - may increment and decrement the value by an amount. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A counter is a gauge calculated at the server. The owner of the counter + * may increment and decrement the value by an amount. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Counter final { public: using value_type = CounterImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Counter() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Counter(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Increment the counter. */ + /** + * Increment the counter. + */ /** @{ */ void increment(value_type amount) const diff --git a/include/xrpl/beast/insight/Event.h b/include/xrpl/beast/insight/Event.h index afccf9baba..c3ff1a8877 100644 --- a/include/xrpl/beast/insight/Event.h +++ b/include/xrpl/beast/insight/Event.h @@ -8,35 +8,40 @@ namespace beast::insight { -/** A metric for reporting event timing. - - An event is an operation that has an associated millisecond time, or - other integral value. Because events happen at a specific moment, the - metric only supports a push-style interface. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for reporting event timing. + * + * An event is an operation that has an associated millisecond time, or + * other integral value. Because events happen at a specific moment, the + * metric only supports a push-style interface. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Event final { public: using value_type = EventImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Event() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Event(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Push an event notification. */ + /** + * Push an event notification. + */ template void notify(std::chrono::duration const& value) const diff --git a/include/xrpl/beast/insight/Gauge.h b/include/xrpl/beast/insight/Gauge.h index 9a23ea6665..ef62e252b3 100644 --- a/include/xrpl/beast/insight/Gauge.h +++ b/include/xrpl/beast/insight/Gauge.h @@ -7,40 +7,44 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A gauge is an instantaneous measurement of a value, like the gas gauge - in a car. The caller directly sets the value, or adjusts it by a - specified amount. The value is kept in the client rather than the collector. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A gauge is an instantaneous measurement of a value, like the gas gauge + * in a car. The caller directly sets the value, or adjusts it by a + * specified amount. The value is kept in the client rather than the collector. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Gauge final { public: using value_type = GaugeImpl::value_type; using difference_type = GaugeImpl::difference_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Gauge() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Gauge(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Set the value on the gauge. - A Collector implementation should combine multiple calls to value - changes into a single change if the calls occur within a single - collection interval. - */ + /** + * Set the value on the gauge. + * A Collector implementation should combine multiple calls to value + * changes into a single change if the calls occur within a single + * collection interval. + */ /** @{ */ void set(value_type value) const @@ -62,7 +66,9 @@ public: } /** @} */ - /** Adjust the value of the gauge. */ + /** + * Adjust the value of the gauge. + */ /** @{ */ void increment(difference_type amount) const diff --git a/include/xrpl/beast/insight/Group.h b/include/xrpl/beast/insight/Group.h index 3e0eb93452..ecf7709546 100644 --- a/include/xrpl/beast/insight/Group.h +++ b/include/xrpl/beast/insight/Group.h @@ -7,13 +7,17 @@ namespace beast::insight { -/** A collector front-end that manages a group of metrics. */ +/** + * A collector front-end that manages a group of metrics. + */ class Group : public Collector { public: using ptr = std::shared_ptr; - /** Returns the name of this group, for diagnostics. */ + /** + * Returns the name of this group, for diagnostics. + */ [[nodiscard]] virtual std::string const& name() const = 0; }; diff --git a/include/xrpl/beast/insight/Groups.h b/include/xrpl/beast/insight/Groups.h index cfe4d99bdc..77fc2d3336 100644 --- a/include/xrpl/beast/insight/Groups.h +++ b/include/xrpl/beast/insight/Groups.h @@ -8,13 +8,17 @@ namespace beast::insight { -/** A container for managing a set of metric groups. */ +/** + * A container for managing a set of metric groups. + */ class Groups { public: virtual ~Groups() = 0; - /** Find or create a new collector with a given name. */ + /** + * Find or create a new collector with a given name. + */ /** @{ */ virtual Group::ptr const& get(std::string const& name) = 0; @@ -27,7 +31,9 @@ public: /** @} */ }; -/** Create a group container that uses the specified collector. */ +/** + * Create a group container that uses the specified collector. + */ std::unique_ptr makeGroups(Collector::ptr const& collector); diff --git a/include/xrpl/beast/insight/Hook.h b/include/xrpl/beast/insight/Hook.h index 8dbe5a4be0..572a9ffcb4 100644 --- a/include/xrpl/beast/insight/Hook.h +++ b/include/xrpl/beast/insight/Hook.h @@ -7,20 +7,24 @@ namespace beast::insight { -/** A reference to a handler for performing polled collection. */ +/** + * A reference to a handler for performing polled collection. + */ class Hook final { public: - /** Create a null hook. - A null hook has no associated handler. - */ + /** + * Create a null hook. + * A null hook has no associated handler. + */ Hook() = default; - /** Create a hook referencing the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create a hook referencing the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Hook(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Meter.h b/include/xrpl/beast/insight/Meter.h index 25ffabd928..ac2f3a352c 100644 --- a/include/xrpl/beast/insight/Meter.h +++ b/include/xrpl/beast/insight/Meter.h @@ -7,33 +7,38 @@ namespace beast::insight { -/** A metric for measuring an integral value. - - A meter may be thought of as an increment-only counter. - - This is a lightweight reference wrapper which is cheap to copy and assign. - When the last reference goes away, the metric is no longer collected. -*/ +/** + * A metric for measuring an integral value. + * + * A meter may be thought of as an increment-only counter. + * + * This is a lightweight reference wrapper which is cheap to copy and assign. + * When the last reference goes away, the metric is no longer collected. + */ class Meter final { public: using value_type = MeterImpl::value_type; - /** Create a null metric. - A null metric reports no information. - */ + /** + * Create a null metric. + * A null metric reports no information. + */ Meter() = default; - /** Create the metric reference the specified implementation. - Normally this won't be called directly. Instead, call the appropriate - factory function in the Collector interface. - @see Collector. - */ + /** + * Create the metric reference the specified implementation. + * Normally this won't be called directly. Instead, call the appropriate + * factory function in the Collector interface. + * @see Collector. + */ explicit Meter(std::shared_ptr impl) : impl_(std::move(impl)) { } - /** Increment the meter. */ + /** + * Increment the meter. + */ /** @{ */ void increment(value_type amount) const diff --git a/include/xrpl/beast/insight/NullCollector.h b/include/xrpl/beast/insight/NullCollector.h index 67903420fa..ffafe6d6d5 100644 --- a/include/xrpl/beast/insight/NullCollector.h +++ b/include/xrpl/beast/insight/NullCollector.h @@ -6,7 +6,9 @@ namespace beast::insight { -/** A Collector which does not collect metrics. */ +/** + * A Collector which does not collect metrics. + */ class NullCollector : public Collector { public: diff --git a/include/xrpl/beast/insight/StatsDCollector.h b/include/xrpl/beast/insight/StatsDCollector.h index 9a438c48f1..e14d3a27ff 100644 --- a/include/xrpl/beast/insight/StatsDCollector.h +++ b/include/xrpl/beast/insight/StatsDCollector.h @@ -9,20 +9,22 @@ namespace beast::insight { -/** A Collector that reports metrics to a StatsD server. - Reference: - https://github.com/b/statsd_spec -*/ +/** + * A Collector that reports metrics to a StatsD server. + * Reference: + * https://github.com/b/statsd_spec + */ class StatsDCollector : public Collector { public: explicit StatsDCollector() = default; - /** Create a StatsD collector. - @param address The IP address and port of the StatsD server. - @param prefix A string pre-pended before each metric name. - @param journal Destination for logging output. - */ + /** + * Create a StatsD collector. + * @param address The IP address and port of the StatsD server. + * @param prefix A string pre-pended before each metric name. + * @param journal Destination for logging output. + */ static std::shared_ptr make(IP::Endpoint const& address, std::string const& prefix, Journal journal); }; diff --git a/include/xrpl/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h index f4327b7b8a..4f4fb189a6 100644 --- a/include/xrpl/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -19,42 +19,54 @@ namespace IP { using Address = boost::asio::ip::address; -/** Returns the address represented as a string. */ +/** + * Returns the address represented as a string. + */ inline std::string to_string(Address const& addr) { return addr.to_string(); } -/** Returns `true` if this is a loopback address. */ +/** + * Returns `true` if this is a loopback address. + */ inline bool isLoopback(Address const& addr) { return addr.is_loopback(); } -/** Returns `true` if the address is unspecified. */ +/** + * Returns `true` if the address is unspecified. + */ inline bool isUnspecified(Address const& addr) { return addr.is_unspecified(); } -/** Returns `true` if the address is a multicast address. */ +/** + * Returns `true` if the address is a multicast address. + */ inline bool isMulticast(Address const& addr) { return addr.is_multicast(); } -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ inline bool isPrivate(Address const& addr) { return (addr.is_v4()) ? isPrivate(addr.to_v4()) : isPrivate(addr.to_v6()); } -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ inline bool isPublic(Address const& addr) { diff --git a/include/xrpl/beast/net/IPAddressConversion.h b/include/xrpl/beast/net/IPAddressConversion.h index b5fb697233..73777cf841 100644 --- a/include/xrpl/beast/net/IPAddressConversion.h +++ b/include/xrpl/beast/net/IPAddressConversion.h @@ -6,23 +6,29 @@ namespace beast::IP { -/** Convert to Endpoint. - The port is set to zero. -*/ +/** + * Convert to Endpoint. + * The port is set to zero. + */ Endpoint fromAsio(boost::asio::ip::address const& address); -/** Convert to Endpoint. */ +/** + * Convert to Endpoint. + */ Endpoint fromAsio(boost::asio::ip::tcp::endpoint const& endpoint); -/** Convert to asio::ip::address. - The port is ignored. -*/ +/** + * Convert to asio::ip::address. + * The port is ignored. + */ boost::asio::ip::address toAsioAddress(Endpoint const& endpoint); -/** Convert to asio::ip::tcp::endpoint. */ +/** + * Convert to asio::ip::tcp::endpoint. + */ boost::asio::ip::tcp::endpoint toAsioEndpoint(Endpoint const& endpoint); diff --git a/include/xrpl/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h index 9367fbe1eb..94943af3ea 100644 --- a/include/xrpl/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -6,17 +6,22 @@ namespace beast::IP { using AddressV4 = boost::asio::ip::address_v4; -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ bool isPrivate(AddressV4 const& addr); -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ bool isPublic(AddressV4 const& addr); -/** Returns the address class for the given address. - @note Class 'D' represents multicast addresses (224.*.*.*). -*/ +/** + * Returns the address class for the given address. + * @note Class 'D' represents multicast addresses (224.*.*.*). + */ char getClass(AddressV4 const& address); diff --git a/include/xrpl/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h index 1bfa079990..b51cb62532 100644 --- a/include/xrpl/beast/net/IPAddressV6.h +++ b/include/xrpl/beast/net/IPAddressV6.h @@ -6,11 +6,15 @@ namespace beast::IP { using AddressV6 = boost::asio::ip::address_v6; -/** Returns `true` if the address is a private unroutable address. */ +/** + * Returns `true` if the address is a private unroutable address. + */ bool isPrivate(AddressV6 const& addr); -/** Returns `true` if the address is a public routable address. */ +/** + * Returns `true` if the address is a public routable address. + */ bool isPublic(AddressV6 const& addr); diff --git a/include/xrpl/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h index 0b661108f2..c4b269e9c3 100644 --- a/include/xrpl/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -17,51 +17,68 @@ namespace beast::IP { using Port = std::uint16_t; -/** A version-independent IP address and port combination. */ +/** + * A version-independent IP address and port combination. + */ class Endpoint { public: - /** Create an unspecified endpoint. */ + /** + * Create an unspecified endpoint. + */ Endpoint(); - /** Create an endpoint from the address and optional port. */ + /** + * Create an endpoint from the address and optional port. + */ explicit Endpoint(Address addr, Port port = 0); - /** Create an Endpoint from a string. - If the port is omitted, the endpoint will have a zero port. - @return An optional endpoint; will be `std::nullopt` on failure - */ + /** + * Create an Endpoint from a string. + * If the port is omitted, the endpoint will have a zero port. + * @return An optional endpoint; will be `std::nullopt` on failure + */ static std::optional fromStringChecked(std::string const& s); static Endpoint fromString(std::string const& s); - /** Returns a string representing the endpoint. */ + /** + * Returns a string representing the endpoint. + */ [[nodiscard]] std::string toString() const; - /** Returns the port number on the endpoint. */ + /** + * Returns the port number on the endpoint. + */ [[nodiscard]] Port port() const { return port_; } - /** Returns a new Endpoint with a different port. */ + /** + * Returns a new Endpoint with a different port. + */ [[nodiscard]] Endpoint atPort(Port port) const { return Endpoint(addr_, port); } - /** Returns the address portion of this endpoint. */ + /** + * Returns the address portion of this endpoint. + */ [[nodiscard]] Address const& address() const { return addr_; } - /** Convenience accessors for the address part. */ + /** + * Convenience accessors for the address part. + */ /** @{ */ [[nodiscard]] bool isV4() const @@ -85,7 +102,9 @@ public: } /** @} */ - /** Arithmetic comparison. */ + /** + * Arithmetic comparison. + */ /** @{ */ friend bool operator==(Endpoint const& lhs, Endpoint const& rhs); @@ -131,35 +150,45 @@ private: // Properties -/** Returns `true` if the endpoint is a loopback address. */ +/** + * Returns `true` if the endpoint is a loopback address. + */ inline bool isLoopback(Endpoint const& endpoint) { return isLoopback(endpoint.address()); } -/** Returns `true` if the endpoint is unspecified. */ +/** + * Returns `true` if the endpoint is unspecified. + */ inline bool isUnspecified(Endpoint const& endpoint) { return isUnspecified(endpoint.address()); } -/** Returns `true` if the endpoint is a multicast address. */ +/** + * Returns `true` if the endpoint is a multicast address. + */ inline bool isMulticast(Endpoint const& endpoint) { return isMulticast(endpoint.address()); } -/** Returns `true` if the endpoint is a private unroutable address. */ +/** + * Returns `true` if the endpoint is a private unroutable address. + */ inline bool isPrivate(Endpoint const& endpoint) { return isPrivate(endpoint.address()); } -/** Returns `true` if the endpoint is a public routable address. */ +/** + * Returns `true` if the endpoint is a public routable address. + */ inline bool isPublic(Endpoint const& endpoint) { @@ -168,14 +197,18 @@ isPublic(Endpoint const& endpoint) //------------------------------------------------------------------------------ -/** Returns the endpoint represented as a string. */ +/** + * Returns the endpoint represented as a string. + */ inline std::string to_string(Endpoint const& endpoint) { return endpoint.toString(); } -/** Output stream conversion. */ +/** + * Output stream conversion. + */ template OutputStream& operator<<(OutputStream& os, Endpoint const& endpoint) @@ -184,7 +217,9 @@ operator<<(OutputStream& os, Endpoint const& endpoint) return os; } -/** Input stream conversion. */ +/** + * Input stream conversion. + */ std::istream& operator>>(std::istream& is, Endpoint& endpoint); @@ -193,7 +228,9 @@ operator>>(std::istream& is, Endpoint& endpoint); //------------------------------------------------------------------------------ namespace std { -/** std::hash support. */ +/** + * std::hash support. + */ template <> struct hash<::beast::IP::Endpoint> { @@ -208,7 +245,9 @@ struct hash<::beast::IP::Endpoint> } // namespace std namespace boost { -/** boost::hash support. */ +/** + * boost::hash support. + */ template <> struct hash<::beast::IP::Endpoint> { diff --git a/include/xrpl/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h index 7c681ab140..1986568553 100644 --- a/include/xrpl/beast/rfc2616.h +++ b/include/xrpl/beast/rfc2616.h @@ -30,17 +30,20 @@ struct CiEqualPred } }; -/** Returns `true` if `c` is linear white space. - - This excludes the CRLF sequence allowed for line continuations. -*/ +/** + * Returns `true` if `c` is linear white space. + * + * This excludes the CRLF sequence allowed for line continuations. + */ inline bool isLws(char c) { return c == ' ' || c == '\t'; } -/** Returns `true` if `c` is any whitespace character. */ +/** + * Returns `true` if `c` is any whitespace character. + */ inline bool isWhite(char c) { @@ -87,14 +90,15 @@ trimRight(String const& s) } // namespace detail -/** Parse a character sequence of values separated by commas. - Double quotes and escape sequences will be converted. Excess white - space, commas, double quotes, and empty elements are not copied. - Format: - #(token|quoted-string) - Reference: - http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 -*/ +/** + * Parse a character sequence of values separated by commas. + * Double quotes and escape sequences will be converted. Excess white + * space, commas, double quotes, and empty elements are not copied. + * Format: + * #(token|quoted-string) + * Reference: + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 + */ template < class FwdIt, class Result = std::vector::value_type>>, @@ -189,14 +193,15 @@ splitCommas(boost::beast::string_view const& s) //------------------------------------------------------------------------------ -/** Iterates through a comma separated list. - - Meets the requirements of ForwardIterator. - - List defined in rfc2616 2.1. - - @note Values returned may contain backslash escapes. -*/ +/** + * Iterates through a comma separated list. + * + * Meets the requirements of ForwardIterator. + * + * List defined in rfc2616 2.1. + * + * @note Values returned may contain backslash escapes. + */ class ListIterator { using iter_type = boost::string_ref::const_iterator; @@ -323,17 +328,20 @@ ListIterator::increment() } } } -/** Returns true if two strings are equal. - - A case-insensitive comparison is used. -*/ +/** + * Returns true if two strings are equal. + * + * A case-insensitive comparison is used. + */ inline bool ciEqual(boost::string_ref s1, boost::string_ref s2) { return boost::range::equal(s1, s2, detail::CiEqualPred{}); } -/** Returns a range representing the list. */ +/** + * Returns a range representing the list. + */ inline boost::iterator_range makeList(boost::string_ref const& field) { @@ -341,20 +349,22 @@ makeList(boost::string_ref const& field) ListIterator{field.begin(), field.end()}, ListIterator{field.end(), field.end()}}; } -/** Returns true if the specified token exists in the list. - - A case-insensitive comparison is used. -*/ +/** + * Returns true if the specified token exists in the list. + * + * A case-insensitive comparison is used. + */ template bool tokenInList(boost::string_ref const& value, boost::string_ref const& token) { - for (auto const& item : makeList(value)) - { - if (ciEqual(item, token)) - return true; - } - return false; + auto const list = makeList(value); + // ListIterator is not default-constructible, so it does not model a std::ranges + // sentinel/range; the classic std::any_of (which only needs an input iterator) + // is used instead. + // NOLINTNEXTLINE(modernize-use-ranges) + return std::any_of( + list.begin(), list.end(), [&token](auto const& item) { return ciEqual(item, token); }); } template diff --git a/include/xrpl/beast/test/yield_to.h b/include/xrpl/beast/test/yield_to.h index 1a34ec436e..b3aa482dd5 100644 --- a/include/xrpl/beast/test/yield_to.h +++ b/include/xrpl/beast/test/yield_to.h @@ -19,12 +19,13 @@ namespace beast::test { -/** Mix-in to support tests using asio coroutines. - - Derive from this class and use yield_to to launch test - functions inside coroutines. This is handy for testing - asynchronous asio code. -*/ +/** + * Mix-in to support tests using asio coroutines. + * + * Derive from this class and use yield_to to launch test + * functions inside coroutines. This is handy for testing + * asynchronous asio code. + */ class EnableYieldTo { protected: @@ -38,7 +39,9 @@ private: std::size_t running_ = 0; public: - /// The type of yield context passed to functions. + /** + * The type of yield context passed to functions. + */ using yield_context = boost::asio::yield_context; explicit EnableYieldTo(std::size_t concurrency = 1) : work_(boost::asio::make_work_guard(ios_)) @@ -57,24 +60,27 @@ public: t.join(); } - /// Return the `io_context` associated with the object + /** + * Return the `io_context` associated with the object + */ boost::asio::io_context& getIoContext() { return ios_; } - /** Run one or more functions, each in a coroutine. - - This call will block until all coroutines terminate. - - Each functions should have this signature: - @code - void f(yield_context); - @endcode - - @param fn... One or more functions to invoke. - */ + /** + * Run one or more functions, each in a coroutine. + * + * This call will block until all coroutines terminate. + * + * Each functions should have this signature: + * @code + * void f(yield_context); + * @endcode + * + * @param fn... One or more functions to invoke. + */ #if BEAST_DOXYGEN template void diff --git a/include/xrpl/beast/unit_test/amount.h b/include/xrpl/beast/unit_test/amount.h index 3a392f393f..c1e4357753 100644 --- a/include/xrpl/beast/unit_test/amount.h +++ b/include/xrpl/beast/unit_test/amount.h @@ -10,7 +10,9 @@ namespace beast::unit_test { -/** Utility for producing nicely composed output of amounts with units. */ +/** + * Utility for producing nicely composed output of amounts with units. + */ class Amount { private: diff --git a/include/xrpl/beast/unit_test/detail/const_container.h b/include/xrpl/beast/unit_test/detail/const_container.h index 6826bf4258..9f4646cbdb 100644 --- a/include/xrpl/beast/unit_test/detail/const_container.h +++ b/include/xrpl/beast/unit_test/detail/const_container.h @@ -6,10 +6,11 @@ namespace beast::unit_test::detail { -/** Adapter to constrain a container interface. - The interface allows for limited read only operations. Derived classes - provide additional behavior. -*/ +/** + * Adapter to constrain a container interface. + * The interface allows for limited read only operations. Derived classes + * provide additional behavior. + */ template class ConstContainer { @@ -38,21 +39,27 @@ public: using iterator = cont_type::const_iterator; using const_iterator = cont_type::const_iterator; - /** Returns `true` if the container is empty. */ + /** + * Returns `true` if the container is empty. + */ [[nodiscard]] bool empty() const { return cont_.empty(); } - /** Returns the number of items in the container. */ + /** + * Returns the number of items in the container. + */ [[nodiscard]] size_type size() const { return cont_.size(); } - /** Returns forward iterators for traversal. */ + /** + * Returns forward iterators for traversal. + */ /** @{ */ [[nodiscard]] const_iterator begin() const diff --git a/include/xrpl/beast/unit_test/global_suites.h b/include/xrpl/beast/unit_test/global_suites.h index 72ed738bdb..18e5bc3a6b 100644 --- a/include/xrpl/beast/unit_test/global_suites.h +++ b/include/xrpl/beast/unit_test/global_suites.h @@ -10,7 +10,9 @@ namespace beast::unit_test { namespace detail { -/// Holds test suites registered during static initialization. +/** + * Holds test suites registered during static initialization. + */ inline SuiteList& globalSuites() { @@ -34,7 +36,9 @@ struct InsertSuite } // namespace detail -/// Holds test suites registered during static initialization. +/** + * Holds test suites registered during static initialization. + */ inline SuiteList const& globalSuites() { diff --git a/include/xrpl/beast/unit_test/match.h b/include/xrpl/beast/unit_test/match.h index 5faeaa1100..966574b833 100644 --- a/include/xrpl/beast/unit_test/match.h +++ b/include/xrpl/beast/unit_test/match.h @@ -121,42 +121,48 @@ Selector::operator()(SuiteInfo const& s) // Utility functions for producing predicates to select suites. -/** Returns a predicate that implements a smart matching rule. - The predicate checks the suite, module, and library fields of the - SuiteInfo in that order. When it finds a match, it changes modes - depending on what was found: - - If a suite is matched first, then only the suite is selected. The - suite may be marked manual. - - If a module is matched first, then only suites from that module - and library not marked manual are selected from then on. - - If a library is matched first, then only suites from that library - not marked manual are selected from then on. - -*/ +/** + * Returns a predicate that implements a smart matching rule. + * The predicate checks the suite, module, and library fields of the + * SuiteInfo in that order. When it finds a match, it changes modes + * depending on what was found: + * + * If a suite is matched first, then only the suite is selected. The + * suite may be marked manual. + * + * If a module is matched first, then only suites from that module + * and library not marked manual are selected from then on. + * + * If a library is matched first, then only suites from that library + * not marked manual are selected from then on. + */ inline Selector matchAuto(std::string const& name) { return Selector(Selector::ModeT::Automatch, name); } -/** Return a predicate that matches all suites not marked manual. */ +/** + * Return a predicate that matches all suites not marked manual. + */ inline Selector matchAll() { return Selector(Selector::ModeT::All); } -/** Returns a predicate that matches a specific suite. */ +/** + * Returns a predicate that matches a specific suite. + */ inline Selector matchSuite(std::string const& name) { return Selector(Selector::ModeT::Suite, name); } -/** Returns a predicate that matches all suites in a library. */ +/** + * Returns a predicate that matches all suites in a library. + */ inline Selector matchLibrary(std::string const& name) { diff --git a/include/xrpl/beast/unit_test/recorder.h b/include/xrpl/beast/unit_test/recorder.h index 1b7347dc2e..fcadb63fc9 100644 --- a/include/xrpl/beast/unit_test/recorder.h +++ b/include/xrpl/beast/unit_test/recorder.h @@ -13,7 +13,9 @@ namespace beast::unit_test { -/** A test runner that stores the results. */ +/** + * A test runner that stores the results. + */ class Recorder : public Runner { private: @@ -24,7 +26,9 @@ private: public: Recorder() = default; - /** Returns a report with the results of all completed suites. */ + /** + * Returns a report with the results of all completed suites. + */ [[nodiscard]] Results const& report() const { diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index a903d9f8c2..0fe77a7862 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -25,9 +25,10 @@ namespace beast::unit_test { namespace detail { -/** A simple test runner that writes everything to a stream in real time. - The totals are output when the object is destroyed. -*/ +/** + * A simple test runner that writes everything to a stream in real time. + * The totals are output when the object is destroyed. + */ template class Reporter : public Runner { diff --git a/include/xrpl/beast/unit_test/results.h b/include/xrpl/beast/unit_test/results.h index 718d764c9f..273ad5b129 100644 --- a/include/xrpl/beast/unit_test/results.h +++ b/include/xrpl/beast/unit_test/results.h @@ -13,11 +13,15 @@ namespace beast::unit_test { -/** Holds a set of test condition outcomes in a testcase. */ +/** + * Holds a set of test condition outcomes in a testcase. + */ class CaseResults { public: - /** Holds the result of evaluating one test condition. */ + /** + * Holds the result of evaluating one test condition. + */ struct Test { explicit Test(bool pass) : pass(pass) @@ -41,28 +45,36 @@ private: public: TestsT() = default; - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return cont().size(); } - /** Returns the number of failed test conditions. */ + /** + * Returns the number of failed test conditions. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Register a successful test condition. */ + /** + * Register a successful test condition. + */ void pass() { cont().emplace_back(true); } - /** Register a failed test condition. */ + /** + * Register a failed test condition. + */ void fail(std::string const& reason = "") { @@ -74,7 +86,9 @@ private: class LogT : public detail::ConstContainer> { public: - /** Insert a string into the log. */ + /** + * Insert a string into the log. + */ void insert(std::string const& s) { @@ -89,23 +103,31 @@ public: { } - /** Returns the name of this testcase. */ + /** + * Returns the name of this testcase. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Memberspace for a container of test condition outcomes. */ + /** + * Memberspace for a container of test condition outcomes. + */ TestsT tests; - /** Memberspace for a container of testcase log messages. */ + /** + * Memberspace for a container of testcase log messages. + */ LogT log; }; //-------------------------------------------------------------------------- -/** Holds the set of testcase results in a suite. */ +/** + * Holds the set of testcase results in a suite. + */ class SuiteResults : public detail::ConstContainer> { private: @@ -118,28 +140,36 @@ public: { } - /** Returns the name of this suite. */ + /** + * Returns the name of this suite. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return total_; } - /** Returns the number of failures. */ + /** + * Returns the number of failures. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Insert a set of testcase results. */ + /** + * Insert a set of testcase results. + */ /** @{ */ void insert(CaseResults&& r) @@ -162,7 +192,9 @@ public: //------------------------------------------------------------------------------ // VFALCO TODO Make this a template class using scoped allocators -/** Holds the results of running a set of testsuites. */ +/** + * Holds the results of running a set of testsuites. + */ class Results : public detail::ConstContainer> { private: @@ -173,28 +205,36 @@ private: public: Results() = default; - /** Returns the total number of test cases. */ + /** + * Returns the total number of test cases. + */ [[nodiscard]] std::size_t cases() const { return cases_; } - /** Returns the total number of test conditions. */ + /** + * Returns the total number of test conditions. + */ [[nodiscard]] std::size_t total() const { return total_; } - /** Returns the number of failures. */ + /** + * Returns the number of failures. + */ [[nodiscard]] std::size_t failed() const { return failed_; } - /** Insert a set of suite results. */ + /** + * Insert a set of suite results. + */ /** @{ */ void insert(SuiteResults&& r) diff --git a/include/xrpl/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h index b88bfc5fe1..f8f9deca48 100644 --- a/include/xrpl/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -13,11 +13,12 @@ namespace beast::unit_test { -/** Unit test runner interface. - - Derived classes can customize the reporting behavior. This interface is - injected into the unit_test class to receive the results of the tests. -*/ +/** + * Unit test runner interface. + * + * Derived classes can customize the reporting behavior. This interface is + * injected into the unit_test class to receive the results of the tests. + */ class Runner { std::string arg_; @@ -33,110 +34,132 @@ public: Runner& operator=(Runner const&) = delete; - /** Set the argument string. - - The argument string is available to suites and - allows for customization of the test. Each suite - defines its own syntax for the argument string. - The same argument is passed to all suites. - */ + /** + * Set the argument string. + * + * The argument string is available to suites and + * allows for customization of the test. Each suite + * defines its own syntax for the argument string. + * The same argument is passed to all suites. + */ void arg(std::string const& s) { arg_ = s; } - /** Returns the argument string. */ + /** + * Returns the argument string. + */ [[nodiscard]] std::string const& arg() const { return arg_; } - /** Run the specified suite. - @return `true` if any conditions failed. - */ + /** + * Run the specified suite. + * @return `true` if any conditions failed. + */ template bool run(SuiteInfo const& s); - /** Run a sequence of suites. - The expression - `FwdIter::value_type` - must be convertible to `SuiteInfo`. - @return `true` if any conditions failed. - */ + /** + * Run a sequence of suites. + * The expression + * `FwdIter::value_type` + * must be convertible to `SuiteInfo`. + * @return `true` if any conditions failed. + */ template bool run(FwdIter first, FwdIter last); - /** Conditionally run a sequence of suites. - pred will be called as: - @code - bool pred(SuiteInfo const&); - @endcode - @return `true` if any conditions failed. - */ + /** + * Conditionally run a sequence of suites. + * pred will be called as: + * @code + * bool pred(SuiteInfo const&); + * @endcode + * @return `true` if any conditions failed. + */ template bool runIf(FwdIter first, FwdIter last, Pred pred = Pred{}); - /** Run all suites in a container. - @return `true` if any conditions failed. - */ + /** + * Run all suites in a container. + * @return `true` if any conditions failed. + */ template bool runEach(SequenceContainer const& c); - /** Conditionally run suites in a container. - pred will be called as: - @code - bool pred(SuiteInfo const&); - @endcode - @return `true` if any conditions failed. - */ + /** + * Conditionally run suites in a container. + * pred will be called as: + * @code + * bool pred(SuiteInfo const&); + * @endcode + * @return `true` if any conditions failed. + */ template bool runEachIf(SequenceContainer const& c, Pred pred = Pred{}); protected: - /// Called when a new suite starts. + /** + * Called when a new suite starts. + */ virtual void onSuiteBegin(SuiteInfo const&) { } - /// Called when a suite ends. + /** + * Called when a suite ends. + */ virtual void onSuiteEnd() { } - /// Called when a new case starts. + /** + * Called when a new case starts. + */ virtual void onCaseBegin(std::string const&) { } - /// Called when a new case ends. + /** + * Called when a new case ends. + */ virtual void onCaseEnd() { } - /// Called for each passing condition. + /** + * Called for each passing condition. + */ virtual void onPass() { } - /// Called for each failing condition. + /** + * Called for each failing condition. + */ virtual void onFail(std::string const&) { } - /// Called when a test logs output. + /** + * Called when a test logs output. + */ virtual void onLog(std::string const&) { diff --git a/include/xrpl/beast/unit_test/suite.h b/include/xrpl/beast/unit_test/suite.h index 487663fcc5..c20fe2522c 100644 --- a/include/xrpl/beast/unit_test/suite.h +++ b/include/xrpl/beast/unit_test/suite.h @@ -41,13 +41,14 @@ class Thread; enum class AbortT { NoAbortOnFail, AbortOnFail }; -/** A testsuite class. - - Derived classes execute a series of testcases, where each testcase is - a series of pass/fail tests. To provide a unit test using this class, - derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a - translation unit. -*/ +/** + * A testsuite class. + * + * Derived classes execute a series of testcases, where each testcase is + * a series of pass/fail tests. To provide a unit test using this class, + * derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a + * translation unit. + */ class Suite { private: @@ -118,16 +119,17 @@ private: { } - /** Open a new testcase. - - A testcase is a series of evaluated test conditions. A test - suite may have multiple test cases. A test is associated with - the last opened testcase. When the test first runs, a default - unnamed case is opened. Tests with only one case may omit the - call to testcase. - - @param abort Determines if suite continues running after a failure. - */ + /** + * Open a new testcase. + * + * A testcase is a series of evaluated test conditions. A test + * suite may have multiple test cases. A test is associated with + * the last opened testcase. When the test first runs, a default + * unnamed case is opened. Tests with only one case may omit the + * call to testcase. + * + * @param abort Determines if suite continues running after a failure. + */ void operator()(std::string const& name, AbortT abort = AbortT::NoAbortOnFail); @@ -140,19 +142,23 @@ private: }; public: - /** Logging output stream. - - Text sent to the log output stream will be forwarded to - the output stream associated with the runner. - */ + /** + * Logging output stream. + * + * Text sent to the log output stream will be forwarded to + * the output stream associated with the runner. + */ LogOs log; - /** Memberspace for declaring test cases. */ + /** + * Memberspace for declaring test cases. + */ TestcaseT testcase; - /** Returns the "current" running suite. - If no suite is running, nullptr is returned. - */ + /** + * Returns the "current" running suite. + * If no suite is running, nullptr is returned. + */ static Suite* thisSuite() { @@ -168,30 +174,34 @@ public: Suite& operator=(Suite const&) = delete; - /** Invokes the test using the specified runner. - - Data members are set up here instead of the constructor as a - convenience to writing the derived class to avoid repetition of - forwarded constructor arguments to the base. - Normally this is called by the framework for you. - */ + /** + * Invokes the test using the specified runner. + * + * Data members are set up here instead of the constructor as a + * convenience to writing the derived class to avoid repetition of + * forwarded constructor arguments to the base. + * Normally this is called by the framework for you. + */ template void operator()(Runner& r); - /** Record a successful test condition. */ + /** + * Record a successful test condition. + */ template void pass(); - /** Record a failure. - - @param reason Optional text added to the output on a failure. - - @param file The source code file where the test failed. - - @param line The source code line number where the test failed. - */ + /** + * Record a failure. + * + * @param reason Optional text added to the output on a failure. + * + * @param file The source code file where the test failed. + * + * @param line The source code line number where the test failed. + */ /** @{ */ template void @@ -202,23 +212,24 @@ public: fail(std::string const& reason = ""); /** @} */ - /** Evaluate a test condition. - - This function provides improved logging by incorporating the - file name and line number into the reported output on failure, - as well as additional text specified by the caller. - - @param shouldBeTrue The condition to test. The condition - is evaluated in a boolean context. - - @param reason Optional added text to output on a failure. - - @param file The source code file where the test failed. - - @param line The source code line number where the test failed. - - @return `true` if the test condition indicates success. - */ + /** + * Evaluate a test condition. + * + * This function provides improved logging by incorporating the + * file name and line number into the reported output on failure, + * as well as additional text specified by the caller. + * + * @param shouldBeTrue The condition to test. The condition + * is evaluated in a boolean context. + * + * @param reason Optional added text to output on a failure. + * + * @param file The source code file where the test failed. + * + * @param line The source code line number where the test failed. + * + * @return `true` if the test condition indicates success. + */ /** @{ */ template bool @@ -275,15 +286,19 @@ public: return unexcept(f, ""); } - /** Return the argument associated with the runner. */ + /** + * Return the argument associated with the runner. + */ std::string const& arg() const { return runner_->arg(); } - // DEPRECATED - // @return `true` if the test condition indicates success(a false value) + /** + * DEPRECATED + * @return `true` if the test condition indicates success(a false value) + */ template bool unexpected(Condition shouldBeFalse, String const& reason); @@ -305,7 +320,9 @@ private: return &kPTs; } - /** Runs the suite. */ + /** + * Runs the suite. + */ virtual void run() = 0; @@ -558,18 +575,20 @@ Suite::run(Runner& r) } #ifndef BEAST_EXPECT -/** Check a precondition. - - If the condition is false, the file and line number are reported. -*/ +/** + * Check a precondition. + * + * If the condition is false, the file and line number are reported. + */ #define BEAST_EXPECT(cond) expect(cond, __FILE__, __LINE__) #endif #ifndef BEAST_EXPECTS -/** Check a precondition. - - If the condition is false, the file and line number are reported. -*/ +/** + * Check a precondition. + * + * If the condition is false, the file and line number are reported. + */ #define BEAST_EXPECTS(cond, reason) \ ((cond) ? (pass(), true) : (fail((reason), __FILE__, __LINE__), false)) #endif @@ -593,41 +612,43 @@ Suite::run(Runner& r) // #ifndef BEAST_DEFINE_TESTSUITE -/** Enables insertion of test suites into the global container. - The default is to insert all test suite definitions into the global - container. If BEAST_DEFINE_TESTSUITE is user defined, this macro - has no effect. -*/ +/** + * Enables insertion of test suites into the global container. + * The default is to insert all test suite definitions into the global + * container. If BEAST_DEFINE_TESTSUITE is user defined, this macro + * has no effect. + */ #ifndef BEAST_NO_UNIT_TEST_INLINE #define BEAST_NO_UNIT_TEST_INLINE 0 #endif -/** Define a unit test suite. - - Class The type representing the class being tested. - Module Identifies the module. - Library Identifies the library. - - The declaration for the class implementing the test should be the same - as Class ## _test. For example, if Class is aged_ordered_container, the - test class must be declared as: - - @code - - struct aged_ordered_container_test : beast::unit_test::suite - { - //... - }; - - @endcode - - The macro invocation must appear in the same namespace as the test class. - - Unit test priorities were introduced so parallel unit_test::suites would - execute faster. Suites with longer running times have higher priorities - than unit tests with shorter running times. Suites with no priorities - are assumed to run most quickly, so they run last. -*/ +/** + * Define a unit test suite. + * + * Class The type representing the class being tested. + * Module Identifies the module. + * Library Identifies the library. + * + * The declaration for the class implementing the test should be the same + * as Class ## _test. For example, if Class is aged_ordered_container, the + * test class must be declared as: + * + * @code + * + * struct aged_ordered_container_test : beast::unit_test::suite + * { + * //... + * }; + * + * @endcode + * + * The macro invocation must appear in the same namespace as the test class. + * + * Unit test priorities were introduced so parallel unit_test::suites would + * execute faster. Suites with longer running times have higher priorities + * than unit tests with shorter running times. Suites with no priorities + * are assumed to run most quickly, so they run last. + */ #if BEAST_NO_UNIT_TEST_INLINE #define BEAST_DEFINE_TESTSUITE(Class, Module, Library) diff --git a/include/xrpl/beast/unit_test/suite_info.h b/include/xrpl/beast/unit_test/suite_info.h index bda10ae7e3..c4e3496f13 100644 --- a/include/xrpl/beast/unit_test/suite_info.h +++ b/include/xrpl/beast/unit_test/suite_info.h @@ -13,7 +13,9 @@ namespace beast::unit_test { class Runner; -/** Associates a unit test type with metadata. */ +/** + * Associates a unit test type with metadata. + */ class SuiteInfo { using run_type = std::function; @@ -60,21 +62,27 @@ public: return library_; } - /// Returns `true` if this suite only runs manually. + /** + * Returns `true` if this suite only runs manually. + */ [[nodiscard]] bool manual() const { return manual_; } - /// Return the canonical suite name as a string. + /** + * Return the canonical suite name as a string. + */ [[nodiscard]] std::string fullName() const { return library_ + "." + module_ + "." + name_; } - /// Run a new instance of the associated test suite. + /** + * Run a new instance of the associated test suite. + */ void run(Runner& r) const { @@ -93,7 +101,9 @@ public: //------------------------------------------------------------------------------ -/// Convenience for producing SuiteInfo for a given test type. +/** + * Convenience for producing SuiteInfo for a given test type. + */ template SuiteInfo makeSuiteInfo(std::string name, std::string module, std::string library, bool manual, int priority) diff --git a/include/xrpl/beast/unit_test/suite_list.h b/include/xrpl/beast/unit_test/suite_list.h index 7dd0dd80f0..057a362859 100644 --- a/include/xrpl/beast/unit_test/suite_list.h +++ b/include/xrpl/beast/unit_test/suite_list.h @@ -16,7 +16,9 @@ namespace beast::unit_test { -/// A container of test suites. +/** + * A container of test suites. + */ class SuiteList : public detail::ConstContainer> { private: @@ -26,10 +28,11 @@ private: #endif public: - /** Insert a suite into the set. - - The suite must not already exist. - */ + /** + * Insert a suite into the set. + * + * The suite must not already exist. + */ template void insert(char const* name, char const* module, char const* library, bool manual, int priority); diff --git a/include/xrpl/beast/unit_test/thread.h b/include/xrpl/beast/unit_test/thread.h index 91d8cf3cab..5a5a99d149 100644 --- a/include/xrpl/beast/unit_test/thread.h +++ b/include/xrpl/beast/unit_test/thread.h @@ -14,7 +14,9 @@ namespace beast::unit_test { -/** Replacement for std::thread that handles exceptions in unit tests. */ +/** + * Replacement for std::thread that handles exceptions in unit tests. + */ class Thread { private: diff --git a/include/xrpl/beast/utility/Journal.h b/include/xrpl/beast/utility/Journal.h index 3de3cfb0e0..9f0a1ead66 100644 --- a/include/xrpl/beast/utility/Journal.h +++ b/include/xrpl/beast/utility/Journal.h @@ -10,7 +10,9 @@ namespace beast { -/** Severity level / threshold of a Journal message. */ +/** + * Severity level / threshold of a Journal message. + */ enum class Severity : std::uint8_t { All = 0, @@ -25,18 +27,19 @@ enum class Severity : std::uint8_t { None = Disabled }; -/** A generic endpoint for log messages. - - The Journal has a few simple goals: - - * To be light-weight and copied by value. - * To allow logging statements to be left in source code. - * The logging is controlled at run-time based on a logging threshold. - - It is advisable to check Journal::active(level) prior to formatting log - text. Doing so sidesteps expensive text formatting when the results - will not be sent to the log. -*/ +/** + * A generic endpoint for log messages. + * + * The Journal has a few simple goals: + * + * * To be light-weight and copied by value. + * * To allow logging statements to be left in source code. + * * The logging is controlled at run-time based on a logging threshold. + * + * It is advisable to check Journal::active(level) prior to formatting log + * text. Doing so sidesteps expensive text formatting when the results + * will not be sent to the log. + */ class Journal { public: @@ -49,7 +52,9 @@ private: public: //-------------------------------------------------------------------------- - /** Abstraction for the underlying message destination. */ + /** + * Abstraction for the underlying message destination. + */ class Sink { protected: @@ -63,36 +68,47 @@ public: Sink& operator=(Sink const& lhs) = delete; - /** Returns `true` if text at the passed severity produces output. */ + /** + * Returns `true` if text at the passed severity produces output. + */ [[nodiscard]] virtual bool active(Severity level) const; - /** Returns `true` if a message is also written to the Output Window - * (MSVC). */ + /** + * Returns `true` if a message is also written to the Output Window + * (MSVC). + */ [[nodiscard]] virtual bool console() const; - /** Set whether messages are also written to the Output Window (MSVC). + /** + * Set whether messages are also written to the Output Window (MSVC). */ virtual void console(bool output); - /** Returns the minimum severity level this sink will report. */ + /** + * Returns the minimum severity level this sink will report. + */ [[nodiscard]] virtual Severity threshold() const; - /** Set the minimum severity this sink will report. */ + /** + * Set the minimum severity this sink will report. + */ virtual void threshold(Severity thresh); - /** Write text to the sink at the specified severity. - A conforming implementation will not write the text if the passed - level is below the current threshold(). - */ + /** + * Write text to the sink at the specified severity. + * A conforming implementation will not write the text if the passed + * level is below the current threshold(). + */ virtual void write(Severity level, std::string const& text) = 0; - /** Bypass filter and write text to the sink at the specified severity. + /** + * Bypass filter and write text to the sink at the specified severity. * Always write the message, but maintain the same formatting as if * it passed through a level filter. * @@ -116,7 +132,9 @@ public: static_assert(std::is_nothrow_destructible_v); #endif - /** Returns a Sink which does nothing. */ + /** + * Returns a Sink which does nothing. + */ static Sink& getNullSink(); @@ -174,26 +192,33 @@ public: //-------------------------------------------------------------------------- public: - /** Provide a light-weight way to check active() before string formatting */ + /** + * Provide a light-weight way to check active() before string formatting + */ class Stream { public: - /** Create a stream which produces no output. */ + /** + * Create a stream which produces no output. + */ explicit Stream() : sink_(getNullSink()), level_(Severity::Disabled) { } - /** Create a stream that writes at the given level. - - Constructor is inlined so checking active() very inexpensive. - */ + /** + * Create a stream that writes at the given level. + * + * Constructor is inlined so checking active() very inexpensive. + */ Stream(Sink& sink, Severity level) : sink_(sink), level_(level) { XRPL_ASSERT( level_ < Severity::Disabled, "beast::Journal::Stream::Stream : maximum level"); } - /** Construct or copy another Stream. */ + /** + * Construct or copy another Stream. + */ Stream(Stream const& other) : Stream(other.sink_, other.level_) { } @@ -201,21 +226,27 @@ public: Stream& operator=(Stream const& other) = delete; - /** Returns the Sink that this Stream writes to. */ + /** + * Returns the Sink that this Stream writes to. + */ [[nodiscard]] Sink& sink() const { return sink_; } - /** Returns the Severity level of messages this Stream reports. */ + /** + * Returns the Severity level of messages this Stream reports. + */ [[nodiscard]] Severity level() const { return level_; } - /** Returns `true` if sink logs anything at this stream's level. */ + /** + * Returns `true` if sink logs anything at this stream's level. + */ /** @{ */ [[nodiscard]] bool active() const @@ -230,7 +261,9 @@ public: } /** @} */ - /** Output stream support. */ + /** + * Output stream support. + */ /** @{ */ ScopedStream operator<<(std::ostream& manip(std::ostream&)) const; @@ -256,39 +289,50 @@ public: //-------------------------------------------------------------------------- - /** Journal has no default constructor. */ + /** + * Journal has no default constructor. + */ Journal() = delete; - /** Create a journal that writes to the specified sink. */ + /** + * Create a journal that writes to the specified sink. + */ explicit Journal(Sink& sink) : sink_(&sink) { } - /** Returns the Sink associated with this Journal. */ + /** + * Returns the Sink associated with this Journal. + */ [[nodiscard]] Sink& sink() const { return *sink_; } - /** Returns a stream for this sink, with the specified severity level. */ + /** + * Returns a stream for this sink, with the specified severity level. + */ [[nodiscard]] Stream stream(Severity level) const { return Stream(*sink_, level); } - /** Returns `true` if any message would be logged at this severity level. - For a message to be logged, the severity must be at or above the - sink's severity threshold. - */ + /** + * Returns `true` if any message would be logged at this severity level. + * For a message to be logged, the severity must be at or above the + * sink's severity threshold. + */ [[nodiscard]] bool active(Severity level) const { return sink_->active(level); } - /** Severity stream access functions. */ + /** + * Severity stream access functions. + */ /** @{ */ [[nodiscard]] Stream trace() const diff --git a/include/xrpl/beast/utility/PropertyStream.h b/include/xrpl/beast/utility/PropertyStream.h index 3fb6df53d9..f32f5b7fef 100644 --- a/include/xrpl/beast/utility/PropertyStream.h +++ b/include/xrpl/beast/utility/PropertyStream.h @@ -12,7 +12,9 @@ namespace beast { //------------------------------------------------------------------------------ -/** Abstract stream with RAII containers that produce a property tree. */ +/** + * Abstract stream with RAII containers that produce a property tree. + */ class PropertyStream { public: @@ -306,7 +308,9 @@ public: // //------------------------------------------------------------------------------ -/** Subclasses can be called to write to a stream and have children. */ +/** + * Subclasses can be called to write to a stream and have children. + */ class PropertyStream::Source { private: @@ -324,17 +328,22 @@ public: Source& operator=(Source const&) = delete; - /** Returns the name of this source. */ + /** + * Returns the name of this source. + */ [[nodiscard]] std::string const& name() const; - /** Add a child source. */ + /** + * Add a child source. + */ void add(Source& source); - /** Add a child source by pointer. - The source pointer is returned so it can be used in ctor-initializers. - */ + /** + * Add a child source by pointer. + * The source pointer is returned so it can be used in ctor-initializers. + */ template Derived* add(Derived* child) @@ -343,45 +352,55 @@ public: return child; } - /** Remove a child source from this Source. */ + /** + * Remove a child source from this Source. + */ void remove(Source& child); - /** Remove all child sources from this Source. */ + /** + * Remove all child sources from this Source. + */ void removeAll(); - /** Write only this Source to the stream. */ + /** + * Write only this Source to the stream. + */ void writeOne(PropertyStream& stream); - /** write this source and all its children recursively to the stream. */ + /** + * write this source and all its children recursively to the stream. + */ void write(PropertyStream& stream); - /** Parse the path and write the corresponding Source and optional children. - If the source is found, it is written. If the wildcard character '*' - exists as the last character in the path, then all the children are - written recursively. - */ + /** + * Parse the path and write the corresponding Source and optional children. + * If the source is found, it is written. If the wildcard character '*' + * exists as the last character in the path, then all the children are + * written recursively. + */ void write(PropertyStream& stream, std::string const& path); - /** Parse the dot-delimited Source path and return the result. - The first value will be a pointer to the Source object corresponding - to the given path. If no Source object exists, then the first value - will be nullptr and the second value will be undefined. - The second value is a boolean indicating whether or not the path string - specifies the wildcard character '*' as the last character. - - print statement examples - "parent.child" prints child and all of its children - "parent.child." start at the parent and print down to child - "parent.grandchild" prints nothing- grandchild not direct descendent - "parent.grandchild." starts at the parent and prints down to grandchild - "parent.grandchild.*" starts at parent, print through grandchild - children - */ + /** + * Parse the dot-delimited Source path and return the result. + * The first value will be a pointer to the Source object corresponding + * to the given path. If no Source object exists, then the first value + * will be nullptr and the second value will be undefined. + * The second value is a boolean indicating whether or not the path string + * specifies the wildcard character '*' as the last character. + * + * print statement examples + * "parent.child" prints child and all of its children + * "parent.child." start at the parent and print down to child + * "parent.grandchild" prints nothing- grandchild not direct descendent + * "parent.grandchild." starts at the parent and prints down to grandchild + * "parent.grandchild.*" starts at parent, print through grandchild + * children + */ std::pair find(std::string path); @@ -401,9 +420,10 @@ public: //-------------------------------------------------------------------------- - /** Subclass override. - The default version does nothing. - */ + /** + * Subclass override. + * The default version does nothing. + */ virtual void onWrite(Map&); }; diff --git a/include/xrpl/beast/utility/WrappedSink.h b/include/xrpl/beast/utility/WrappedSink.h index a24ad595db..3ab48e1939 100644 --- a/include/xrpl/beast/utility/WrappedSink.h +++ b/include/xrpl/beast/utility/WrappedSink.h @@ -7,7 +7,9 @@ namespace beast { -/** Wraps a Journal::Sink to prefix its output with a string. */ +/** + * Wraps a Journal::Sink to prefix its output with a string. + */ // A WrappedSink both is a Sink and has a Sink: // o It inherits from Sink so it has the correct interface. diff --git a/include/xrpl/beast/utility/Zero.h b/include/xrpl/beast/utility/Zero.h index e28589760b..406921c500 100644 --- a/include/xrpl/beast/utility/Zero.h +++ b/include/xrpl/beast/utility/Zero.h @@ -4,22 +4,23 @@ namespace beast { -/** Zero allows classes to offer efficient comparisons to zero. - - Zero is a struct to allow classes to efficiently compare with zero without - requiring an rvalue construction. - - It's often the case that we have classes which combine a number and a unit. - In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons - like t > 1 or t != 1 do not. - - The class Zero allows such comparisons to be easily made. - - The comparing class T either needs to have a method called signum() which - returns a positive number, 0, or a negative; or there needs to be a signum - function which resolves in the namespace which takes an instance of T and - returns a positive, zero or negative number. -*/ +/** + * Zero allows classes to offer efficient comparisons to zero. + * + * Zero is a struct to allow classes to efficiently compare with zero without + * requiring an rvalue construction. + * + * It's often the case that we have classes which combine a number and a unit. + * In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons + * like t > 1 or t != 1 do not. + * + * The class Zero allows such comparisons to be easily made. + * + * The comparing class T either needs to have a method called signum() which + * returns a positive number, 0, or a negative; or there needs to be a signum + * function which resolves in the namespace which takes an instance of T and + * returns a positive, zero or negative number. + */ struct Zero { @@ -28,7 +29,9 @@ struct Zero inline constexpr Zero kZero{}; -/** Default implementation of signum calls the method on the class. */ +/** + * Default implementation of signum calls the method on the class. + */ template auto signum(T const& t) diff --git a/include/xrpl/beast/utility/maybe_const.h b/include/xrpl/beast/utility/maybe_const.h index 10b2eaf7f6..848ea86cb2 100644 --- a/include/xrpl/beast/utility/maybe_const.h +++ b/include/xrpl/beast/utility/maybe_const.h @@ -4,7 +4,9 @@ namespace beast { -/** Makes T const or non const depending on a bool. */ +/** + * Makes T const or non const depending on a bool. + */ template struct MaybeConst { @@ -13,7 +15,9 @@ struct MaybeConst conditional_t::type const, std::remove_const_t>; }; -/** Alias for omitting `typename`. */ +/** + * Alias for omitting `typename`. + */ template using maybe_const_t = MaybeConst::type; diff --git a/include/xrpl/beast/utility/temp_dir.h b/include/xrpl/beast/utility/temp_dir.h index ec661b51c4..a0ff1e6940 100644 --- a/include/xrpl/beast/utility/temp_dir.h +++ b/include/xrpl/beast/utility/temp_dir.h @@ -6,11 +6,12 @@ namespace beast { -/** RAII temporary directory. - - The directory and all its contents are deleted when - the instance of `temp_dir` is destroyed. -*/ +/** + * RAII temporary directory. + * + * The directory and all its contents are deleted when + * the instance of `temp_dir` is destroyed. + */ class TempDir { boost::filesystem::path path_; @@ -22,7 +23,9 @@ public: operator=(TempDir const&) = delete; #endif - /// Construct a temporary directory. + /** + * Construct a temporary directory. + */ TempDir() { auto const dir = boost::filesystem::temp_directory_path(); @@ -33,7 +36,9 @@ public: boost::filesystem::create_directory(path_); } - /// Destroy a temporary directory. + /** + * Destroy a temporary directory. + */ ~TempDir() { // use non-throwing calls in the destructor @@ -42,17 +47,20 @@ public: // TODO: warn/notify if ec set ? } - /// Get the native path for the temporary directory + /** + * Get the native path for the temporary directory + */ [[nodiscard]] std::string path() const { return path_.string(); } - /** Get the native path for the a file. - - The file does not need to exist. - */ + /** + * Get the native path for the a file. + * + * The file does not need to exist. + */ [[nodiscard]] std::string file(std::string const& name) const { diff --git a/include/xrpl/beast/xor_shift_engine.h b/include/xrpl/beast/xor_shift_engine.h index 45baecf101..6a7272c195 100644 --- a/include/xrpl/beast/xor_shift_engine.h +++ b/include/xrpl/beast/xor_shift_engine.h @@ -85,14 +85,15 @@ XorShiftEngine::murmurhash3(result_type x) -> result_type } // namespace detail -/** XOR-shift Generator. - - Meets the requirements of UniformRandomNumberGenerator. - - Simple and fast RNG based on: - http://xorshift.di.unimi.it/xorshift128plus.c - does not accept seed==0 -*/ +/** + * XOR-shift Generator. + * + * Meets the requirements of UniformRandomNumberGenerator. + * + * Simple and fast RNG based on: + * http://xorshift.di.unimi.it/xorshift128plus.c + * does not accept seed==0 + */ using xor_shift_engine = detail::XorShiftEngine<>; } // namespace beast diff --git a/include/xrpl/conditions/Condition.h b/include/xrpl/conditions/Condition.h index 3c798663d7..365a41a087 100644 --- a/include/xrpl/conditions/Condition.h +++ b/include/xrpl/conditions/Condition.h @@ -24,42 +24,49 @@ enum class Type : std::uint8_t { class Condition { public: - /** The largest binary condition we support. - - @note This value will be increased in the future, but it - must never decrease, as that could cause conditions - that were previously considered valid to no longer - be allowed. - */ + /** + * The largest binary condition we support. + * + * @note This value will be increased in the future, but it + * must never decrease, as that could cause conditions + * that were previously considered valid to no longer + * be allowed. + */ static constexpr std::size_t kMaxSerializedCondition = 128; - /** Load a condition from its binary form - - @param s The buffer containing the fulfillment to load. - @param ec Set to the error, if any occurred. - - The binary format for a condition is specified in the - cryptoconditions RFC. See: - - https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.2 - */ + /** + * Load a condition from its binary form + * + * @param s The buffer containing the fulfillment to load. + * @param ec Set to the error, if any occurred. + * + * The binary format for a condition is specified in the + * cryptoconditions RFC. See: + * + * https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.2 + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec); public: Type type; - /** An identifier for this condition. - - This fingerprint is meant to be unique only with - respect to other conditions of the same type. - */ + /** + * An identifier for this condition. + * + * This fingerprint is meant to be unique only with + * respect to other conditions of the same type. + */ Buffer fingerprint; - /** The cost associated with this condition. */ + /** + * The cost associated with this condition. + */ std::uint32_t cost; - /** For compound conditions, set of conditions includes */ + /** + * For compound conditions, set of conditions includes + */ std::set subtypes; Condition(Type t, std::uint32_t c, Slice fp) : type(t), fingerprint(fp), cost(c) diff --git a/include/xrpl/conditions/Fulfillment.h b/include/xrpl/conditions/Fulfillment.h index a3001b2620..11f3165a58 100644 --- a/include/xrpl/conditions/Fulfillment.h +++ b/include/xrpl/conditions/Fulfillment.h @@ -14,64 +14,73 @@ namespace xrpl::cryptoconditions { struct Fulfillment { public: - /** The largest binary fulfillment we support. - - @note This value will be increased in the future, but it - must never decrease, as that could cause fulfillments - that were previously considered valid to no longer - be allowed. - */ + /** + * The largest binary fulfillment we support. + * + * @note This value will be increased in the future, but it + * must never decrease, as that could cause fulfillments + * that were previously considered valid to no longer + * be allowed. + */ static constexpr std::size_t kMaxSerializedFulfillment = 256; - /** Load a fulfillment from its binary form - - @param s The buffer containing the fulfillment to load. - @param ec Set to the error, if any occurred. - - The binary format for a fulfillment is specified in the - cryptoconditions RFC. See: - - https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.3 - */ + /** + * Load a fulfillment from its binary form + * + * @param s The buffer containing the fulfillment to load. + * @param ec Set to the error, if any occurred. + * + * The binary format for a fulfillment is specified in the + * cryptoconditions RFC. See: + * + * https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-7.3 + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec); public: virtual ~Fulfillment() = default; - /** Returns the fulfillment's fingerprint: - - The fingerprint is an octet string uniquely - representing this fulfillment's condition - with respect to other conditions of the - same type. - */ + /** + * Returns the fulfillment's fingerprint: + * + * The fingerprint is an octet string uniquely + * representing this fulfillment's condition + * with respect to other conditions of the + * same type. + */ [[nodiscard]] virtual Buffer fingerprint() const = 0; - /** Returns the type of this condition. */ + /** + * Returns the type of this condition. + */ [[nodiscard]] virtual Type type() const = 0; - /** Validates a fulfillment. */ + /** + * Validates a fulfillment. + */ [[nodiscard]] virtual bool validate(Slice data) const = 0; - /** Calculates the cost associated with this fulfillment. * - - The cost function is deterministic and depends on the - type and properties of the condition and the fulfillment - that the condition is generated from. - */ + /** + * Calculates the cost associated with this fulfillment. * + * + * The cost function is deterministic and depends on the + * type and properties of the condition and the fulfillment + * that the condition is generated from. + */ [[nodiscard]] virtual std::uint32_t cost() const = 0; - /** Returns the condition associated with the given fulfillment. - - This process is completely deterministic. All implementations - will, if compliant, produce the identical condition for the - same fulfillment. - */ + /** + * Returns the condition associated with the given fulfillment. + * + * This process is completely deterministic. All implementations + * will, if compliant, produce the identical condition for the + * same fulfillment. + */ [[nodiscard]] virtual Condition condition() const = 0; }; @@ -90,36 +99,40 @@ operator!=(Fulfillment const& lhs, Fulfillment const& rhs) return !(lhs == rhs); } -/** Determine whether the given fulfillment and condition match */ +/** + * Determine whether the given fulfillment and condition match + */ bool match(Fulfillment const& f, Condition const& c); -/** Verify if the given message satisfies the fulfillment. - - @param f The fulfillment - @param c The condition - @param m The message - - @note the message is not relevant for some conditions - and a fulfillment will successfully satisfy its - condition for any given message. -*/ +/** + * Verify if the given message satisfies the fulfillment. + * + * @param f The fulfillment + * @param c The condition + * @param m The message + * + * @note the message is not relevant for some conditions + * and a fulfillment will successfully satisfy its + * condition for any given message. + */ bool validate(Fulfillment const& f, Condition const& c, Slice m); -/** Verify a cryptoconditional trigger. - - A cryptoconditional trigger is a cryptocondition with - an empty message. - - When using such triggers, it is recommended that the - trigger be of type preimage, prefix or threshold. If - a signature type is used (i.e. Ed25519 or RSA-SHA256) - then the Ed25519 or RSA keys should be single-use keys. - - @param f The fulfillment - @param c The condition -*/ +/** + * Verify a cryptoconditional trigger. + * + * A cryptoconditional trigger is a cryptocondition with + * an empty message. + * + * When using such triggers, it is recommended that the + * trigger be of type preimage, prefix or threshold. If + * a signature type is used (i.e. Ed25519 or RSA-SHA256) + * then the Ed25519 or RSA keys should be single-use keys. + * + * @param f The fulfillment + * @param c The condition + */ bool validate(Fulfillment const& f, Condition const& c); diff --git a/include/xrpl/conditions/detail/PreimageSha256.h b/include/xrpl/conditions/detail/PreimageSha256.h index 0973a52e4a..007588a0b5 100644 --- a/include/xrpl/conditions/detail/PreimageSha256.h +++ b/include/xrpl/conditions/detail/PreimageSha256.h @@ -18,23 +18,25 @@ namespace xrpl::cryptoconditions { class PreimageSha256 final : public Fulfillment { public: - /** The maximum allowed length of a preimage. - - The specification does not specify a minimum supported - length, nor does it require all conditions to support - the same minimum length. - - While future versions of this code will never lower - this limit, they may opt to raise it. - */ + /** + * The maximum allowed length of a preimage. + * + * The specification does not specify a minimum supported + * length, nor does it require all conditions to support + * the same minimum length. + * + * While future versions of this code will never lower + * this limit, they may opt to raise it. + */ static constexpr std::size_t kMaxPreimageLength = 128; - /** Parse the payload for a PreimageSha256 condition - - @param s A slice containing the DER encoded payload - @param ec indicates success or failure of the operation - @return the preimage, if successful; empty pointer otherwise. - */ + /** + * Parse the payload for a PreimageSha256 condition + * + * @param s A slice containing the DER encoded payload + * @param ec indicates success or failure of the operation + * @return the preimage, if successful; empty pointer otherwise. + */ static std::unique_ptr deserialize(Slice s, std::error_code& ec) { diff --git a/include/xrpl/config/BasicConfig.h b/include/xrpl/config/BasicConfig.h index 5680b51fe7..607a0c3e5f 100644 --- a/include/xrpl/config/BasicConfig.h +++ b/include/xrpl/config/BasicConfig.h @@ -21,9 +21,10 @@ using IniFileSections = std::unordered_map //------------------------------------------------------------------------------ -/** Holds a collection of configuration values. - A configuration file contains zero or more sections. -*/ +/** + * Holds a collection of configuration values. + * A configuration file contains zero or more sections. + */ class Section { private: @@ -36,28 +37,34 @@ private: using const_iterator = decltype(lookup_)::const_iterator; public: - /** Create an empty section. */ + /** + * Create an empty section. + */ explicit Section(std::string name = ""); - /** Returns the name of this section. */ + /** + * Returns the name of this section. + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Returns all the lines in the section. - This includes everything. - */ + /** + * Returns all the lines in the section. + * This includes everything. + */ [[nodiscard]] std::vector const& lines() const { return lines_; } - /** Returns all the values in the section. - Values are non-empty lines which are not key/value pairs. - */ + /** + * Returns all the values in the section. + * Values are non-empty lines which are not key/value pairs. + */ [[nodiscard]] std::vector const& values() const { @@ -84,7 +91,7 @@ public: * Get the legacy value for this section. * * @return The retrieved value. A section with an empty legacy value returns - an empty string. + * an empty string. */ [[nodiscard]] std::string legacy() const @@ -99,28 +106,34 @@ public: return lines_[0]; } - /** Set a key/value pair. - The previous value is discarded. - */ + /** + * Set a key/value pair. + * The previous value is discarded. + */ void set(std::string const& key, std::string const& value); - /** Append a set of lines to this section. - Lines containing key/value pairs are added to the map, - else they are added to the values list. Everything is - added to the lines list. - */ + /** + * Append a set of lines to this section. + * Lines containing key/value pairs are added to the map, + * else they are added to the values list. Everything is + * added to the lines list. + */ void append(std::vector const& lines); - /** Append a line to this section. */ + /** + * Append a line to this section. + */ void append(std::string const& line) { append(std::vector{line}); } - /** Returns `true` if a key with the given name exists. */ + /** + * Returns `true` if a key with the given name exists. + */ [[nodiscard]] bool exists(std::string const& name) const; @@ -134,7 +147,9 @@ public: return boost::lexical_cast(iter->second); } - /// Returns a value if present, else another value. + /** + * Returns a value if present, else another value. + */ template [[nodiscard]] T valueOr(std::string const& name, T const& other) const @@ -199,23 +214,27 @@ public: //------------------------------------------------------------------------------ -/** Holds unparsed configuration information. - The raw data sections are processed with intermediate parsers specific - to each module instead of being all parsed in a central location. -*/ +/** + * Holds unparsed configuration information. + * The raw data sections are processed with intermediate parsers specific + * to each module instead of being all parsed in a central location. + */ class BasicConfig { private: std::unordered_map map_; public: - /** Returns `true` if a section with the given name exists. */ + /** + * Returns `true` if a section with the given name exists. + */ [[nodiscard]] bool exists(std::string const& name) const; - /** Returns the section with the given name. - If the section does not exist, an empty section is returned. - */ + /** + * Returns the section with the given name. + * If the section does not exist, an empty section is returned. + */ /** @{ */ Section& section(std::string const& name); @@ -236,37 +255,39 @@ public: } /** @} */ - /** Overwrite a key/value pair with a command line argument - If the section does not exist it is created. - The previous value, if any, is overwritten. - */ + /** + * Overwrite a key/value pair with a command line argument + * If the section does not exist it is created. + * The previous value, if any, is overwritten. + */ void overwrite(std::string const& section, std::string const& key, std::string const& value); - /** Remove all the key/value pairs from the section. + /** + * Remove all the key/value pairs from the section. */ void deprecatedClearSection(std::string const& section); /** - * Set a value that is not a key/value pair. + * Set a value that is not a key/value pair. * - * The value is stored as the section's first value and may be retrieved - * through section::legacy. + * The value is stored as the section's first value and may be retrieved + * through section::legacy. * - * @param section Name of the section to modify. - * @param value Contents of the legacy value. + * @param section Name of the section to modify. + * @param value Contents of the legacy value. */ void legacy(std::string const& section, std::string value); /** - * Get the legacy value of a section. A section with a - * single-line value may be retrieved as a legacy value. + * Get the legacy value of a section. A section with a + * single-line value may be retrieved as a legacy value. * - * @param sectionName Retrieve the contents of this section's - * legacy value. - * @return Contents of the legacy value. + * @param sectionName Retrieve the contents of this section's + * legacy value. + * @return Contents of the legacy value. */ [[nodiscard]] std::string legacy(std::string const& sectionName) const; @@ -289,11 +310,12 @@ protected: //------------------------------------------------------------------------------ -/** Set a value from a configuration Section - If the named value is not found or doesn't parse as a T, - the variable is unchanged. - @return `true` if value was set. -*/ +/** + * Set a value from a configuration Section + * If the named value is not found or doesn't parse as a T, + * the variable is unchanged. + * @return `true` if value was set. + */ template bool set(T& target, std::string const& name, Section const& section) @@ -312,11 +334,12 @@ set(T& target, std::string const& name, Section const& section) return foundAndValid; } -/** Set a value from a configuration Section - If the named value is not found or doesn't cast to T, - the variable is assigned the default. - @return `true` if the named value was found and is valid. -*/ +/** + * Set a value from a configuration Section + * If the named value is not found or doesn't cast to T, + * the variable is assigned the default. + * @return `true` if the named value was found and is valid. + */ template bool set(T& target, T const& defaultValue, std::string const& name, Section const& section) @@ -327,10 +350,11 @@ set(T& target, T const& defaultValue, std::string const& name, Section const& se return foundAndValid; } -/** Retrieve a key/value pair from a section. - @return The value string converted to T if it exists - and can be parsed, or else defaultValue. -*/ +/** + * Retrieve a key/value pair from a section. + * @return The value string converted to T if it exists + * and can be parsed, or else defaultValue. + */ // NOTE This routine might be more clumsy than the previous two template T diff --git a/include/xrpl/core/ClosureCounter.h b/include/xrpl/core/ClosureCounter.h index ed15db032e..33899d671b 100644 --- a/include/xrpl/core/ClosureCounter.h +++ b/include/xrpl/core/ClosureCounter.h @@ -30,8 +30,8 @@ namespace xrpl { * the caller that they should drop the closure and cancel their operation. * `join` blocks until all existing closure substitutes are destroyed. * - * \tparam Ret The return type of the closure. - * \tparam Args The argument types of the closure. + * @tparam Ret The return type of the closure. + * @tparam Args The argument types of the closure. */ template class ClosureCounter @@ -131,18 +131,21 @@ public: ClosureCounter& operator=(ClosureCounter const&) = delete; - /** Destructor verifies all in-flight closures are complete. */ + /** + * Destructor verifies all in-flight closures are complete. + */ ~ClosureCounter() { using namespace std::chrono_literals; join("ClosureCounter", 1s, debugLog()); } - /** Returns once all counted in-flight closures are destroyed. - - @param name Name reported if join time exceeds wait. - @param wait If join() exceeds this duration report to Journal. - @param j Journal written to if wait is exceeded. + /** + * Returns once all counted in-flight closures are destroyed. + * + * @param name Name reported if join time exceeds wait. + * @param wait If join() exceeds this duration report to Journal. + * @param j Journal written to if wait is exceeded. */ void join(char const* name, std::chrono::milliseconds wait, beast::Journal j) @@ -160,13 +163,14 @@ public: } } - /** Wrap the passed closure with a reference counter. - - @param closure Closure that accepts Args parameters and returns Ret. - @return If join() has been called returns std::nullopt. Otherwise - returns a std::optional that wraps closure with a - reference counter. - */ + /** + * Wrap the passed closure with a reference counter. + * + * @param closure Closure that accepts Args parameters and returns Ret. + * @return If join() has been called returns std::nullopt. Otherwise + * returns a std::optional that wraps closure with a + * reference counter. + */ template std::optional> wrap(Closure&& closure) @@ -180,19 +184,22 @@ public: return ret; } - /** Current number of Closures outstanding. Only useful for testing. */ + /** + * Current number of Closures outstanding. Only useful for testing. + */ int count() const { return closureCount_; } - /** Returns true if this has been joined. - - Even if true is returned, counted closures may still be in flight. - However if (joined() && (count() == 0)) there should be no more - counted closures in flight. - */ + /** + * Returns true if this has been joined. + * + * Even if true is returned, counted closures may still be in flight. + * However if (joined() && (count() == 0)) there should be no more + * counted closures in flight. + */ bool joined() const { diff --git a/include/xrpl/core/Coro.ipp b/include/xrpl/core/Coro.ipp index 133caf37a9..9a45dac504 100644 --- a/include/xrpl/core/Coro.ipp +++ b/include/xrpl/core/Coro.ipp @@ -4,8 +4,10 @@ namespace xrpl { -/// Coroutine stack size (1.5 MB). Increased from 1 MB because -/// ASAN-instrumented deep call stacks exceeded the original limit. +/** + * Coroutine stack size (1.5 MB). Increased from 1 MB because + * ASAN-instrumented deep call stacks exceeded the original limit. + */ constexpr std::size_t kCoroStackSize = 1536 * 1024; template diff --git a/include/xrpl/core/HashRouter.h b/include/xrpl/core/HashRouter.h index dad1afb405..20aafecc5f 100644 --- a/include/xrpl/core/HashRouter.h +++ b/include/xrpl/core/HashRouter.h @@ -75,19 +75,21 @@ any(HashRouterFlags flags) class Config; -/** Routing table for objects identified by hash. - - This table keeps track of which hashes have been received by which peers. - It is used to manage the routing and broadcasting of messages in the peer - to peer overlay. -*/ +/** + * Routing table for objects identified by hash. + * + * This table keeps track of which hashes have been received by which peers. + * It is used to manage the routing and broadcasting of messages in the peer + * to peer overlay. + */ class HashRouter { public: // The type here *MUST* match the type of Peer::id_t using PeerShortID = std::uint32_t; - /** Structure used to customize @ref HashRouter behavior. + /** + * Structure used to customize @ref HashRouter behavior. * * Even though these items are configurable, they are undocumented. Don't * change them unless there is a good reason, and network-wide coordination @@ -97,22 +99,27 @@ public: */ struct Setup { - /// Default constructor + /** + * Default constructor + */ explicit Setup() = default; using seconds = std::chrono::seconds; - /** Expiration time for a hash entry + /** + * Expiration time for a hash entry */ seconds holdTime{300}; - /** Amount of time required before a relayed item will be relayed again. + /** + * Amount of time required before a relayed item will be relayed again. */ seconds relayTime{30}; }; private: - /** An entry in the routing table. + /** + * An entry in the routing table. */ class Entry : public CountedObject { @@ -138,26 +145,31 @@ private: flags_ |= flagsToSet; } - /** Return set of peers we've relayed to and reset tracking */ + /** + * Return set of peers we've relayed to and reset tracking + */ std::set releasePeerSet() { return std::move(peers_); } - /** Return seated relay time point if the message has been relayed */ + /** + * Return seated relay time point if the message has been relayed + */ [[nodiscard]] std::optional relayed() const { return relayed_; } - /** Determines if this item should be relayed. - - Checks whether the item has been recently relayed. - If it has, return false. If it has not, update the - last relay timestamp and return true. - */ + /** + * Determines if this item should be relayed. + * + * Checks whether the item has been recently relayed. + * If it has, return false. If it has not, update the + * last relay timestamp and return true. + */ bool shouldRelay(Stopwatch::time_point const& now, std::chrono::seconds relayTime) { @@ -203,11 +215,13 @@ public: bool addSuppressionPeer(uint256 const& key, PeerShortID peer); - /** Add a suppression peer and get message's relay status. + /** + * Add a suppression peer and get message's relay status. * Return pair: * element 1: true if the peer is added. * element 2: optional is seated to the relay time point or - * is unseated if has not relayed yet. */ + * is unseated if has not relayed yet. + */ std::pair> addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer); @@ -222,28 +236,30 @@ public: HashRouterFlags& flags, std::chrono::seconds txInterval); - /** Set the flags on a hash. - - @return `true` if the flags were changed. `false` if unchanged. - */ + /** + * Set the flags on a hash. + * + * @return `true` if the flags were changed. `false` if unchanged. + */ bool setFlags(uint256 const& key, HashRouterFlags flags); HashRouterFlags getFlags(uint256 const& key); - /** Determines whether the hashed item should be relayed. - - Effects: - - If the item should be relayed, this function will not - return a seated optional again until the relay time has expired. - The internal set of peers will also be reset. - - @return A `std::optional` set of peers which do not need to be - relayed to. If the result is unseated, the item should - _not_ be relayed. - */ + /** + * Determines whether the hashed item should be relayed. + * + * Effects: + * + * If the item should be relayed, this function will not + * return a seated optional again until the relay time has expired. + * The internal set of peers will also be reset. + * + * @return A `std::optional` set of peers which do not need to be + * relayed to. If the result is unseated, the item should + * _not_ be relayed. + */ std::optional> shouldRelay(uint256 const& key); diff --git a/include/xrpl/core/Job.h b/include/xrpl/core/Job.h index e16d7412bf..93b39701be 100644 --- a/include/xrpl/core/Job.h +++ b/include/xrpl/core/Job.h @@ -83,12 +83,13 @@ class Job : public CountedObject public: using clock_type = std::chrono::steady_clock; - /** Default constructor. - - Allows Job to be used as a container type. - - This is used to allow things like jobMap [key] = value. - */ + /** + * Default constructor. + * + * Allows Job to be used as a container type. + * + * This is used to allow things like jobMap [key] = value. + */ // VFALCO NOTE I'd prefer not to have a default constructed object. // What is the semantic meaning of a Job with no associated // function? Having the invariant "all Job objects refer to @@ -108,7 +109,9 @@ public: [[nodiscard]] JobType getType() const; - /** Returns the time when the job was queued. */ + /** + * Returns the time when the job was queued. + */ [[nodiscard]] clock_type::time_point const& queueTime() const; diff --git a/include/xrpl/core/JobQueue.h b/include/xrpl/core/JobQueue.h index e4b64546f3..0c9fc76357 100644 --- a/include/xrpl/core/JobQueue.h +++ b/include/xrpl/core/JobQueue.h @@ -45,20 +45,23 @@ struct CoroCreateT explicit CoroCreateT() = default; }; -/** A pool of threads to perform work. - - A job posted will always run to completion. - - Coroutines that are suspended must be resumed, - and run to completion. - - When the JobQueue stops, it waits for all jobs - and coroutines to finish. -*/ +/** + * A pool of threads to perform work. + * + * A job posted will always run to completion. + * + * Coroutines that are suspended must be resumed, + * and run to completion. + * + * When the JobQueue stops, it waits for all jobs + * and coroutines to finish. + */ class JobQueue : private Workers::Callback { public: - /** Coroutines must run to completion. */ + /** + * Coroutines must run to completion. + */ class Coro : public std::enable_shared_from_this { private: @@ -87,55 +90,64 @@ public: ~Coro(); - /** Suspend coroutine execution. - Effects: - The coroutine's stack is saved. - The associated Job thread is released. - Note: - The associated Job function returns. - Undefined behavior if called consecutively without a corresponding - post. - */ + /** + * Suspend coroutine execution. + * Effects: + * The coroutine's stack is saved. + * The associated Job thread is released. + * Note: + * The associated Job function returns. + * Undefined behavior if called consecutively without a corresponding + * post. + */ void yield() const; - /** Schedule coroutine execution. - Effects: - Returns immediately. - A new job is scheduled to resume the execution of the coroutine. - When the job runs, the coroutine's stack is restored and execution - continues at the beginning of coroutine function or the - statement after the previous call to yield. Undefined behavior if - called after the coroutine has completed with a return (as opposed to - a yield()). Undefined behavior if post() or resume() called - consecutively without a corresponding yield. - - @return true if the Coro's job is added to the JobQueue. - */ + /** + * Schedule coroutine execution. + * Effects: + * Returns immediately. + * A new job is scheduled to resume the execution of the coroutine. + * When the job runs, the coroutine's stack is restored and execution + * continues at the beginning of coroutine function or the + * statement after the previous call to yield. Undefined behavior if + * called after the coroutine has completed with a return (as opposed to + * a yield()). Undefined behavior if post() or resume() called + * consecutively without a corresponding yield. + * + * @return true if the Coro's job is added to the JobQueue. + */ bool post(); - /** Resume coroutine execution. - Effects: - The coroutine continues execution from where it last left off - using this same thread. - If the coroutine has already completed, returns immediately - (handles the documented post-before-yield race condition). - Undefined behavior if resume() or post() called consecutively - without a corresponding yield. - */ + /** + * Resume coroutine execution. + * Effects: + * The coroutine continues execution from where it last left off + * using this same thread. + * If the coroutine has already completed, returns immediately + * (handles the documented post-before-yield race condition). + * Undefined behavior if resume() or post() called consecutively + * without a corresponding yield. + */ void resume(); - /** Returns true if the Coro is still runnable (has not returned). */ + /** + * Returns true if the Coro is still runnable (has not returned). + */ [[nodiscard]] bool runnable() const; - /** Once called, the Coro allows early exit without an assert. */ + /** + * Once called, the Coro allows early exit without an assert. + */ void expectEarlyExit(); - /** Waits until coroutine returns from the user function. */ + /** + * Waits until coroutine returns from the user function. + */ void join(); }; @@ -150,14 +162,15 @@ public: perf::PerfLog& perfLog); ~JobQueue() override; - /** Adds a job to the JobQueue. - - @param type The type of job. - @param name Name of the job. - @param jobHandler Callable with signature void(). Called when the job is executed. - - @return true if jobHandler added to queue. - */ + /** + * Adds a job to the JobQueue. + * + * @param type The type of job. + * @param name Name of the job. + * @param jobHandler Callable with signature void(). Called when the job is executed. + * + * @return true if jobHandler added to queue. + */ template bool addJob(JobType type, std::string const& name, JobHandler&& jobHandler) @@ -170,40 +183,46 @@ public: return false; } - /** Creates a coroutine and adds a job to the queue which will run it. - - @param t The type of job. - @param name Name of the job. - @param f Has a signature of void(std::shared_ptr). Called when the - job executes. - - @return shared_ptr to posted Coro. nullptr if post was not successful. - */ + /** + * Creates a coroutine and adds a job to the queue which will run it. + * + * @param t The type of job. + * @param name Name of the job. + * @param f Has a signature of void(std::shared_ptr). Called when the + * job executes. + * + * @return shared_ptr to posted Coro. nullptr if post was not successful. + */ template std::shared_ptr postCoro(JobType t, std::string const& name, F&& f); - /** Jobs waiting at this priority. + /** + * Jobs waiting at this priority. */ int getJobCount(JobType t) const; - /** Jobs waiting plus running at this priority. + /** + * Jobs waiting plus running at this priority. */ int getJobCountTotal(JobType t) const; - /** All waiting jobs at or greater than this priority. + /** + * All waiting jobs at or greater than this priority. */ int getJobCountGE(JobType t) const; - /** Return a scoped LoadEvent. + /** + * Return a scoped LoadEvent. */ std::unique_ptr makeLoadEvent(JobType t, std::string const& name); - /** Add multiple load events. + /** + * Add multiple load events. */ void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed); @@ -216,7 +235,9 @@ public: json::Value getJson(int c = 0); - /** Block until no jobs running. */ + /** + * Block until no jobs running. + */ void rendezvous(); diff --git a/include/xrpl/core/JobTypeInfo.h b/include/xrpl/core/JobTypeInfo.h index b5db0dbaab..302a462ac6 100644 --- a/include/xrpl/core/JobTypeInfo.h +++ b/include/xrpl/core/JobTypeInfo.h @@ -8,21 +8,26 @@ namespace xrpl { -/** Holds all the 'static' information about a job, which does not change */ +/** + * Holds all the 'static' information about a job, which does not change + */ class JobTypeInfo { private: JobType const type_; std::string const name_; - /** The limit on the number of running jobs for this job type. - - A limit of 0 marks this as a "special job" which is not - dispatched via the job queue. + /** + * The limit on the number of running jobs for this job type. + * + * A limit of 0 marks this as a "special job" which is not + * dispatched via the job queue. */ int const limit_; - /** Average and peak latencies for this job type. 0 is none specified */ + /** + * Average and peak latencies for this job type. 0 is none specified + */ std::chrono::milliseconds const avgLatency_; std::chrono::milliseconds const peakLatency_; diff --git a/include/xrpl/core/NetworkIDService.h b/include/xrpl/core/NetworkIDService.h index 009f9ba6f8..8e2b3fcfe2 100644 --- a/include/xrpl/core/NetworkIDService.h +++ b/include/xrpl/core/NetworkIDService.h @@ -4,25 +4,27 @@ namespace xrpl { -/** Service that provides access to the network ID. - - This service provides read-only access to the network ID configured - for this server. The network ID identifies which network (mainnet, - testnet, devnet, or custom network) this server is configured to - connect to. - - Well-known network IDs: - - 0: Mainnet - - 1: Testnet - - 2: Devnet - - 1025+: Custom networks (require NetworkID field in transactions) -*/ +/** + * Service that provides access to the network ID. + * + * This service provides read-only access to the network ID configured + * for this server. The network ID identifies which network (mainnet, + * testnet, devnet, or custom network) this server is configured to + * connect to. + * + * Well-known network IDs: + * - 0: Mainnet + * - 1: Testnet + * - 2: Devnet + * - 1025+: Custom networks (require NetworkID field in transactions) + */ class NetworkIDService { public: virtual ~NetworkIDService() = default; - /** Get the configured network ID + /** + * Get the configured network ID * * @return The network ID this server is configured for */ diff --git a/include/xrpl/core/PeerReservationTable.h b/include/xrpl/core/PeerReservationTable.h index e6f6dd622e..c95c88b967 100644 --- a/include/xrpl/core/PeerReservationTable.h +++ b/include/xrpl/core/PeerReservationTable.h @@ -81,7 +81,7 @@ public: /** * @return the replaced reservation if it existed - * @throw soci::soci_error + * @throws soci::soci_error */ std::optional insertOrAssign(PeerReservation const& reservation); diff --git a/include/xrpl/core/ServiceRegistry.h b/include/xrpl/core/ServiceRegistry.h index 50bf2d7c10..592964134b 100644 --- a/include/xrpl/core/ServiceRegistry.h +++ b/include/xrpl/core/ServiceRegistry.h @@ -83,17 +83,17 @@ using RCLValidations = Validations; using NodeCache = TaggedCache; -/** Service registry for dependency injection. - - This abstract interface provides access to various services and components - used throughout the application. It separates the service locator pattern - from the Application lifecycle management. - - Components that need access to services can hold a reference to - ServiceRegistry rather than Application when they only need service - access and not lifecycle management. - -*/ +/** + * Service registry for dependency injection. + * + * This abstract interface provides access to various services and components + * used throughout the application. It separates the service locator pattern + * from the Application lifecycle management. + * + * Components that need access to services can hold a reference to + * ServiceRegistry rather than Application when they only need service + * access and not lifecycle management. + */ class ServiceRegistry { public: @@ -240,7 +240,9 @@ public: [[nodiscard]] virtual std::optional const& getTrapTxID() const = 0; - /** Retrieve the "wallet database" */ + /** + * Retrieve the "wallet database" + */ virtual DatabaseCon& getWalletDB() = 0; diff --git a/include/xrpl/core/detail/Workers.h b/include/xrpl/core/detail/Workers.h index d20ebf7a64..6829d5a14b 100644 --- a/include/xrpl/core/detail/Workers.h +++ b/include/xrpl/core/detail/Workers.h @@ -60,7 +60,9 @@ class PerfLog; class Workers { public: - /** Called to perform tasks as needed. */ + /** + * Called to perform tasks as needed. + */ struct Callback { virtual ~Callback() = default; @@ -69,27 +71,29 @@ public: Callback& operator=(Callback const&) = delete; - /** Perform a task. - - The call is made on a thread owned by Workers. It is important - that you only process one task from inside your callback. Each - call to addTask will result in exactly one call to processTask. - - @param instance The worker thread instance. - - @see Workers::addTask - */ + /** + * Perform a task. + * + * The call is made on a thread owned by Workers. It is important + * that you only process one task from inside your callback. Each + * call to addTask will result in exactly one call to processTask. + * + * @param instance The worker thread instance. + * + * @see Workers::addTask + */ virtual void processTask(int instance) = 0; }; - /** Create the object. - - A number of initial threads may be optionally specified. The - default is to create one thread per CPU. - - @param threadNames The name given to each created worker thread. - */ + /** + * Create the object. + * + * A number of initial threads may be optionally specified. The + * default is to create one thread per CPU. + * + * @param threadNames The name given to each created worker thread. + */ explicit Workers( Callback& callback, perf::PerfLog* perfLog, @@ -98,49 +102,54 @@ public: ~Workers(); - /** Retrieve the desired number of threads. - - This just returns the number of active threads that were requested. If - there was a recent call to setNumberOfThreads, the actual number of - active threads may be temporarily different from what was last requested. - - @note This function is not thread-safe. - */ + /** + * Retrieve the desired number of threads. + * + * This just returns the number of active threads that were requested. If + * there was a recent call to setNumberOfThreads, the actual number of + * active threads may be temporarily different from what was last requested. + * + * @note This function is not thread-safe. + */ [[nodiscard]] int getNumberOfThreads() const noexcept; - /** Set the desired number of threads. - @note This function is not thread-safe. - */ + /** + * Set the desired number of threads. + * @note This function is not thread-safe. + */ void setNumberOfThreads(int numberOfThreads); - /** Pause all threads and wait until they are paused. - - If a thread is processing a task it will pause as soon as the task - completes. There may still be tasks signaled even after all threads - have paused. - - @note This function is not thread-safe. - */ + /** + * Pause all threads and wait until they are paused. + * + * If a thread is processing a task it will pause as soon as the task + * completes. There may still be tasks signaled even after all threads + * have paused. + * + * @note This function is not thread-safe. + */ void stop(); - /** Add a task to be performed. - - Every call to addTask will eventually result in a call to - Callback::processTask unless the Workers object is destroyed or - the number of threads is never set above zero. - - @note This function is thread-safe. - */ + /** + * Add a task to be performed. + * + * Every call to addTask will eventually result in a call to + * Callback::processTask unless the Workers object is destroyed or + * the number of threads is never set above zero. + * + * @note This function is thread-safe. + */ void addTask(); - /** Get the number of currently executing calls of Callback::processTask. - While this function is thread-safe, the value may not stay - accurate for very long. It's mainly for diagnostic purposes. - */ + /** + * Get the number of currently executing calls of Callback::processTask. + * While this function is thread-safe, the value may not stay + * accurate for very long. It's mainly for diagnostic purposes. + */ [[nodiscard]] int numberOfCurrentlyRunningTasks() const noexcept; diff --git a/include/xrpl/core/detail/semaphore.h b/include/xrpl/core/detail/semaphore.h index e40463e322..abf6705097 100644 --- a/include/xrpl/core/detail/semaphore.h +++ b/include/xrpl/core/detail/semaphore.h @@ -45,14 +45,17 @@ private: public: using size_type = std::size_t; - /** Create the semaphore, with an optional initial count. - If unspecified, the initial count is zero. - */ + /** + * Create the semaphore, with an optional initial count. + * If unspecified, the initial count is zero. + */ explicit BasicSemaphore(size_type count = 0) : count_(count) { } - /** Increment the count and unblock one waiting thread. */ + /** + * Increment the count and unblock one waiting thread. + */ void notify() { @@ -61,7 +64,9 @@ public: cond_.notify_one(); } - /** Block until notify is called. */ + /** + * Block until notify is called. + */ void wait() { @@ -71,9 +76,10 @@ public: --count_; } - /** Perform a non-blocking wait. - @return `true` If the wait would be satisfied. - */ + /** + * Perform a non-blocking wait. + * @return `true` If the wait would be satisfied. + */ bool tryWait() { diff --git a/include/xrpl/crypto/RFC1751.h b/include/xrpl/crypto/RFC1751.h index 278f3c207b..3de65c3028 100644 --- a/include/xrpl/crypto/RFC1751.h +++ b/include/xrpl/crypto/RFC1751.h @@ -16,13 +16,14 @@ public: static void getEnglishFromKey(std::string& strHuman, std::string const& strKey); - /** Chooses a single dictionary word from the data. - - This is not particularly secure but it can be useful to provide - a unique name for something given a GUID or fixed data. We use - it to turn the pubkey_node into an easily remembered and identified - 4 character string. - */ + /** + * Chooses a single dictionary word from the data. + * + * This is not particularly secure but it can be useful to provide + * a unique name for something given a GUID or fixed data. We use + * it to turn the pubkey_node into an easily remembered and identified + * 4 character string. + */ static std::string getWordFromBlob(void const* blob, size_t bytes); diff --git a/include/xrpl/crypto/csprng.h b/include/xrpl/crypto/csprng.h index cdc6a723c8..e19d33a464 100644 --- a/include/xrpl/crypto/csprng.h +++ b/include/xrpl/crypto/csprng.h @@ -7,14 +7,15 @@ namespace xrpl { -/** A cryptographically secure random number engine - - The engine is thread-safe (it uses a lock to serialize - access) and will, automatically, mix in some randomness - from std::random_device. - - Meets the requirements of UniformRandomNumberEngine -*/ +/** + * A cryptographically secure random number engine + * + * The engine is thread-safe (it uses a lock to serialize + * access) and will, automatically, mix in some randomness + * from std::random_device. + * + * Meets the requirements of UniformRandomNumberEngine + */ class CsprngEngine { private: @@ -34,15 +35,21 @@ public: CsprngEngine(); ~CsprngEngine(); - /** Mix entropy into the pool */ + /** + * Mix entropy into the pool + */ void mixEntropy(void* buffer = nullptr, std::size_t count = 0); - /** Generate a random integer */ + /** + * Generate a random integer + */ result_type operator()(); - /** Fill a buffer with the requested amount of random data */ + /** + * Fill a buffer with the requested amount of random data + */ void operator()(void* ptr, std::size_t count); @@ -61,14 +68,15 @@ public: } }; -/** The default cryptographically secure PRNG - - Use this when you need to generate random numbers or - data that will be used for encryption or passed into - cryptographic routines. - - This meets the requirements of UniformRandomNumberEngine -*/ +/** + * The default cryptographically secure PRNG + * + * Use this when you need to generate random numbers or + * data that will be used for encryption or passed into + * cryptographic routines. + * + * This meets the requirements of UniformRandomNumberEngine + */ CsprngEngine& cryptoPrng(); diff --git a/include/xrpl/crypto/secure_erase.h b/include/xrpl/crypto/secure_erase.h index 74284b03f7..38531afc1d 100644 --- a/include/xrpl/crypto/secure_erase.h +++ b/include/xrpl/crypto/secure_erase.h @@ -4,20 +4,21 @@ namespace xrpl { -/** Attempts to clear the given blob of memory. - - The underlying implementation of this function takes pains to - attempt to outsmart the compiler from optimizing the clearing - away. Please note that, despite that, remnants of content may - remain floating around in memory as well as registers, caches - and more. - - For a more in-depth discussion of the subject please see the - below posts by Colin Percival: - - http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html - http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html -*/ +/** + * Attempts to clear the given blob of memory. + * + * The underlying implementation of this function takes pains to + * attempt to outsmart the compiler from optimizing the clearing + * away. Please note that, despite that, remnants of content may + * remain floating around in memory as well as registers, caches + * and more. + * + * For a more in-depth discussion of the subject please see the + * below posts by Colin Percival: + * + * http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html + * http://www.daemonology.net/blog/2014-09-06-zeroing-buffers-is-insufficient.html + */ void secureErase(void* dest, std::size_t bytes); diff --git a/include/xrpl/json/JsonPropertyStream.h b/include/xrpl/json/JsonPropertyStream.h index 405a61cd34..498283c16b 100644 --- a/include/xrpl/json/JsonPropertyStream.h +++ b/include/xrpl/json/JsonPropertyStream.h @@ -8,7 +8,9 @@ namespace xrpl { -/** A PropertyStream::Sink which produces a json::Value of type ValueType::Object. */ +/** + * A PropertyStream::Sink which produces a json::Value of type ValueType::Object. + */ class JsonPropertyStream : public beast::PropertyStream { public: diff --git a/include/xrpl/json/Output.h b/include/xrpl/json/Output.h index c01253f713..53d453c277 100644 --- a/include/xrpl/json/Output.h +++ b/include/xrpl/json/Output.h @@ -17,18 +17,20 @@ stringOutput(std::string& s) return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); }; } -/** Writes a minimal representation of a Json value to an Output in O(n) time. - - Data is streamed right to the output, so only a marginal amount of memory is - used. This can be very important for a very large json::Value. +/** + * Writes a minimal representation of a Json value to an Output in O(n) time. + * + * Data is streamed right to the output, so only a marginal amount of memory is + * used. This can be very important for a very large json::Value. */ void outputJson(json::Value const&, Output const&); -/** Return the minimal string representation of a json::Value in O(n) time. - - This requires a memory allocation for the full size of the output. - If possible, use outputJson(). +/** + * Return the minimal string representation of a json::Value in O(n) time. + * + * This requires a memory allocation for the full size of the output. + * If possible, use outputJson(). */ std::string jsonAsString(json::Value const&); diff --git a/include/xrpl/json/Writer.h b/include/xrpl/json/Writer.h index 024876a43c..ec7fd6a0d2 100644 --- a/include/xrpl/json/Writer.h +++ b/include/xrpl/json/Writer.h @@ -12,98 +12,98 @@ namespace json { /** - * Writer implements an O(1)-space, O(1)-granular output JSON writer. + * Writer implements an O(1)-space, O(1)-granular output JSON writer. * - * O(1)-space means that it uses a fixed amount of memory, and that there are - * no heap allocations at each step of the way. + * O(1)-space means that it uses a fixed amount of memory, and that there are + * no heap allocations at each step of the way. * - * O(1)-granular output means the writer only outputs in small segments of a - * bounded size, using a bounded number of CPU cycles in doing so. This is - * very helpful in scheduling long jobs. + * O(1)-granular output means the writer only outputs in small segments of a + * bounded size, using a bounded number of CPU cycles in doing so. This is + * very helpful in scheduling long jobs. * - * The tradeoff is that you have to fill items in the JSON tree as you go, - * and you can never go backward. + * The tradeoff is that you have to fill items in the JSON tree as you go, + * and you can never go backward. * - * Writer can write single JSON tokens, but the typical use is to write out an - * entire JSON object. For example: + * Writer can write single JSON tokens, but the typical use is to write out an + * entire JSON object. For example: * - * { - * Writer w (out); + * { + * Writer w (out); * - * w.startObject (); // Start the root object. - * w.set ("hello", "world"); - * w.set ("goodbye", 23); - * w.finishObject (); // Finish the root object. - * } + * w.startObject (); // Start the root object. + * w.set ("hello", "world"); + * w.set ("goodbye", 23); + * w.finishObject (); // Finish the root object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":"world","goodbye":23} + * {"hello":"world","goodbye":23} * - * There can be an object inside an object: + * There can be an object inside an object: * - * { - * Writer w (out); + * { + * Writer w (out); * - * w.startObject (); // Start the root object. - * w.set ("hello", "world"); + * w.startObject (); // Start the root object. + * w.set ("hello", "world"); * - * w.startObjectSet ("subobject"); // Start a sub-object. - * w.set ("goodbye", 23); // Add a key, value assignment. - * w.finishObject (); // Finish the sub-object. + * w.startObjectSet ("subobject"); // Start a sub-object. + * w.set ("goodbye", 23); // Add a key, value assignment. + * w.finishObject (); // Finish the sub-object. * - * w.finishObject (); // Finish the root-object. - * } + * w.finishObject (); // Finish the root-object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":"world","subobject":{"goodbye":23}}. + * {"hello":"world","subobject":{"goodbye":23}}. * - * Arrays work similarly + * Arrays work similarly * - * { - * Writer w (out); - * w.startObject (); // Start the root object. + * { + * Writer w (out); + * w.startObject (); // Start the root object. * - * w.startArraySet ("hello"); // Start an array. - * w.append (23) // Append some items. - * w.append ("skidoo") - * w.finishArray (); // Finish the array. + * w.startArraySet ("hello"); // Start an array. + * w.append (23) // Append some items. + * w.append ("skidoo") + * w.finishArray (); // Finish the array. * - * w.finishObject (); // Finish the root object. - * } + * w.finishObject (); // Finish the root object. + * } * - * which outputs the string + * which outputs the string * - * {"hello":[23,"skidoo"]}. + * {"hello":[23,"skidoo"]}. * * - * If you've reached the end of a long object, you can just use finishAll() - * which finishes all arrays and objects that you have started. + * If you've reached the end of a long object, you can just use finishAll() + * which finishes all arrays and objects that you have started. * - * { - * Writer w (out); - * w.startObject (); // Start the root object. + * { + * Writer w (out); + * w.startObject (); // Start the root object. * - * w.startArraySet ("hello"); // Start an array. - * w.append (23) // Append an item. + * w.startArraySet ("hello"); // Start an array. + * w.append (23) // Append an item. * - * w.startArrayAppend () // Start a sub-array. - * w.append ("one"); - * w.append ("two"); + * w.startArrayAppend () // Start a sub-array. + * w.append ("one"); + * w.append ("two"); * - * w.startObjectAppend (); // Append a sub-object. - * w.finishAll (); // Finish everything. - * } + * w.startObjectAppend (); // Append a sub-object. + * w.finishAll (); // Finish everything. + * } * - * which outputs the string + * which outputs the string * - * {"hello":[23,["one","two",{}]]}. + * {"hello":[23,["one","two",{}]]}. * - * For convenience, the destructor of Writer calls w.finishAll() which makes - * sure that all arrays and objects are closed. This means that you can throw - * an exception, or have a coroutine simply clean up the stack, and be sure - * that you do in fact generate a complete JSON object. + * For convenience, the destructor of Writer calls w.finishAll() which makes + * sure that all arrays and objects are closed. This means that you can throw + * an exception, or have a coroutine simply clean up the stack, and be sure + * that you do in fact generate a complete JSON object. */ class Writer @@ -118,26 +118,37 @@ public: ~Writer(); - /** Start a new collection at the root level. */ + /** + * Start a new collection at the root level. + */ void startRoot(CollectionType); - /** Start a new collection inside an array. */ + /** + * Start a new collection inside an array. + */ void startAppend(CollectionType); - /** Start a new collection inside an object. */ + /** + * Start a new collection inside an object. + */ void startSet(CollectionType, std::string const& key); - /** Finish the collection most recently started. */ + /** + * Finish the collection most recently started. + */ void finish(); - /** Finish all objects and arrays. After finishArray() has been called, no - * more operations can be performed. */ + /** + * Finish all objects and arrays. After finishArray() has been called, no + * more operations can be performed. + */ void finishAll(); - /** Append a value to an array. + /** + * Append a value to an array. * * Scalar must be a scalar - that is, a number, boolean, string, string * literal, nullptr or json::Value @@ -150,12 +161,15 @@ public: output(t); } - /** Add a comma before this next item if not the first item in an array. - Useful if you are writing the actual array yourself. */ + /** + * Add a comma before this next item if not the first item in an array. + * Useful if you are writing the actual array yourself. + */ void rawAppend(); - /** Add a key, value assignment to an object. + /** + * Add a key, value assignment to an object. * * Scalar must be a scalar - that is, a number, boolean, string, string * literal, or nullptr. @@ -174,8 +188,10 @@ public: output(t); } - /** Emit just "tag": as part of an object. Useful if you are writing the - actual value data yourself. */ + /** + * Emit just "tag": as part of an object. Useful if you are writing the + * actual value data yourself. + */ void rawSet(std::string const& key); @@ -194,22 +210,32 @@ public: void output(json::Value const&); - /** Output a null. */ + /** + * Output a null. + */ void output(std::nullptr_t); - /** Output a float. */ + /** + * Output a float. + */ void output(float); - /** Output a double. */ + /** + * Output a double. + */ void output(double); - /** Output a bool. */ + /** + * Output a bool. + */ void output(bool); - /** Output numbers or booleans. */ + /** + * Output numbers or booleans. + */ template void output(Type t) diff --git a/include/xrpl/json/json_reader.h b/include/xrpl/json/json_reader.h index d1e4ada579..ed60f49ce4 100644 --- a/include/xrpl/json/json_reader.h +++ b/include/xrpl/json/json_reader.h @@ -12,9 +12,9 @@ namespace json { -/** \brief Unserialize a JSON document into a +/** + * @brief Unserialize a JSON document into a * Value. - * */ class Reader { @@ -22,48 +22,55 @@ public: using Char = char; using Location = Char const*; - /** \brief Constructs a Reader allowing all features + /** + * @brief Constructs a Reader allowing all features * for parsing. */ Reader() = default; - /** \brief Read a Value from a JSON - * document. \param document UTF-8 encoded string containing the document to - * read. \param root [out] Contains the root value of the document if it was + /** + * @brief Read a Value from a JSON + * document. @param document UTF-8 encoded string containing the document to + * read. @param root [out] Contains the root value of the document if it was * successfully parsed. - * \return \c true if the document was successfully parsed, \c false if an + * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(std::string const& document, Value& root); - /** \brief Read a Value from a JSON - * document. \param document UTF-8 encoded string containing the document to - * read. \param root [out] Contains the root value of the document if it was + /** + * @brief Read a Value from a JSON + * document. @param document UTF-8 encoded string containing the document to + * read. @param root [out] Contains the root value of the document if it was * successfully parsed. - * \return \c true if the document was successfully parsed, \c false if an + * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(char const* beginDoc, char const* endDoc, Value& root); - /// \brief Parse from input stream. - /// \see json::operator>>(std::istream&, json::Value&). + /** + * @brief Parse from input stream. + * @see json::operator>>(std::istream&, json::Value&). + */ bool parse(std::istream& is, Value& root); - /** \brief Read a Value from a JSON buffer - * sequence. \param root [out] Contains the root value of the document if it - * was successfully parsed. \param UTF-8 encoded buffer sequence. \return \c - * true if the buffer was successfully parsed, \c false if an error + /** + * @brief Read a Value from a JSON buffer + * sequence. @param root [out] Contains the root value of the document if it + * was successfully parsed. @param UTF-8 encoded buffer sequence. @return @c + * true if the buffer was successfully parsed, @c false if an error * occurred. */ template bool parse(Value& root, BufferSequence const& bs); - /** \brief Returns a user friendly string that list errors in the parsed - * document. \return Formatted error message with the list of errors with + /** + * @brief Returns a user friendly string that list errors in the parsed + * document. @return Formatted error message with the list of errors with * their location in the parsed document. An empty string is returned if no * error occurred during parsing. */ @@ -195,30 +202,31 @@ Reader::parse(Value& root, BufferSequence const& bs) return parse(s, root); } -/** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { -"dir": { - "file": { - // The input stream JSON would be nested here. - } -} - } - \endverbatim - \throw std::exception on parse error. - \see json::operator<<() -*/ +/** + * @brief Read from 'sin' into 'root'. + * + * Always keep comments from the input JSON. + * + * This can be used to read a file into a particular sub-object. + * For example: + * @code + * json::Value root; + * cin >> root["dir"]["file"]; + * cout << root; + * @endcode + * Result: + * @verbatim + * { + * "dir": { + * "file": { + * // The input stream JSON would be nested here. + * } + * } + * } + * @endverbatim + * @throws std::exception on parse error. + * @see json::operator<<() + */ std::istream& operator>>(std::istream&, Value&); diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index f786c6a9dc..47ad3ac1e0 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -9,11 +9,13 @@ #include #include -/** \brief JSON (JavaScript Object Notation). +/** + * @brief JSON (JavaScript Object Notation). */ namespace json { -/** \brief Type of the value held by a Value object. +/** + * @brief Type of the value held by a Value object. */ enum class ValueType { Null = 0, ///< 'null' value @@ -26,19 +28,20 @@ enum class ValueType { Object ///< object value (collection of name/value pairs). }; -/** \brief Lightweight wrapper to tag static string. +/** + * @brief Lightweight wrapper to tag static string. * * Value constructor and ValueType::Object member assignment takes advantage of the * StaticString and avoid the cost of string duplication when storing the * string or the member name. * * Example of usage: - * \code + * @code * json::Value aValue( StaticString("some text") ); * json::Value object; * static const StaticString code("code"); * object[code] = 1234; - * \endcode + * @endcode */ class StaticString { @@ -99,7 +102,8 @@ operator!=(StaticString x, std::string const& y) return !(y == x); } -/** \brief Represents a JSON value. +/** + * @brief Represents a JSON value. * * This class is a discriminated union wrapper that can represent a: * - signed integer [range: Value::kMinInt - Value::kMaxInt] @@ -175,37 +179,39 @@ public: using ObjectValues = std::map; public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass ValueType::Array. - To create an empty object, pass ValueType::Object. - Another Value can then be set to this one by assignment. - This is useful since clear() and resize() will not alter types. - - Examples: - \code - json::Value null_value; // null - json::Value arr_value(json::ValueType::Array); // [] - json::Value obj_value(json::ValueType::Object); // {} - \endcode - */ + /** + * @brief Create a default Value of the given type. + * + * This is a very useful constructor. + * To create an empty array, pass ValueType::Array. + * To create an empty object, pass ValueType::Object. + * Another Value can then be set to this one by assignment. + * This is useful since clear() and resize() will not alter types. + * + * Examples: + * @code + * json::Value null_value; // null + * json::Value arr_value(json::ValueType::Array); // [] + * json::Value obj_value(json::ValueType::Object); // {} + * @endcode + */ Value(ValueType type = ValueType::Null); Value(Int value); Value(UInt value); Value(double value); Value(char const* value); Value(xrpl::Number const& value); - /** \brief Constructs a value from a static string. - + /** + * @brief Constructs a value from a static string. + * * Like other value string constructor but do not duplicate the string for * internal storage. The given string must remain alive after the call to - this + * this * constructor. * Example of usage: - * \code + * @code * json::Value aValue( StaticString("some text") ); - * \endcode + * @endcode */ Value(StaticString const& value); Value(std::string const& value); @@ -220,7 +226,9 @@ public: Value(Value&& other) noexcept; - /// Swap values. + /** + * Swap values. + */ void swap(Value& other) noexcept; @@ -229,7 +237,9 @@ public: [[nodiscard]] char const* asCString() const; - /** Returns the unquoted string value. */ + /** + * Returns the unquoted string value. + */ [[nodiscard]] std::string asString() const; [[nodiscard]] Int @@ -241,13 +251,17 @@ public: [[nodiscard]] bool asBool() const; - /** Correct absolute value from int or unsigned int */ + /** + * Correct absolute value from int or unsigned int + */ [[nodiscard]] UInt asAbsUInt() const; // TODO: What is the "empty()" method this docstring mentions? - /** isNull() tests to see if this field is null. Don't use this method to - test for emptiness: use empty(). */ + /** + * isNull() tests to see if this field is null. Don't use this method to + * test for emptiness: use empty(). + */ [[nodiscard]] bool isNull() const; [[nodiscard]] bool @@ -276,116 +290,157 @@ public: [[nodiscard]] bool isConvertibleTo(ValueType other) const; - /// Number of values in array or object + /** + * Number of values in array or object + */ [[nodiscard]] UInt size() const; - /** Returns false if this is an empty array, empty object, empty string, - or null. */ + /** + * Returns false if this is an empty array, empty object, empty string, + * or null. + */ explicit operator bool() const; - /// Remove all object members and array elements. - /// \pre type() is ValueType::Array, ValueType::Object, or ValueType::Null - /// \post type() is unchanged + /** + * Remove all object members and array elements. + * @pre type() is ValueType::Array, ValueType::Object, or ValueType::Null + * @post type() is unchanged + */ void clear(); - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted in the array so that its size is index+1. (You may need to say - /// 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /** + * Access an array element (zero based index ). + * If the array contains less than index element, then null value are + * inserted in the array so that its size is index+1. (You may need to say + * 'value[0u]' to get your compiler to distinguish + * this from the operator[] which takes a string.) + */ Value& operator[](UInt index); - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) + /** + * Access an array element (zero based index ) + * (You may need to say 'value[0u]' to get your compiler to distinguish + * this from the operator[] which takes a string.) + */ Value const& operator[](UInt index) const; - /// If the array contains at least index+1 elements, returns the element - /// value, otherwise returns defaultValue. + /** + * If the array contains at least index+1 elements, returns the element + * value, otherwise returns defaultValue. + */ [[nodiscard]] Value get(UInt index, Value const& defaultValue) const; - /// Return true if index < size(). + /** + * Return true if index < size(). + */ [[nodiscard]] bool isValidIndex(UInt index) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; + /** + * @brief Append value to array at the end. + * + * Equivalent to jsonvalue[jsonvalue.size()] = value; + */ Value& append(Value const& value); Value& append(Value&& value); - /// Access an object value by name, create a null member if it does not - /// exist. + /** + * Access an object value by name, create a null member if it does not + * exist. + */ Value& operator[](char const* key); - /// Access an object value by name, returns null if there is no member with - /// that name. + /** + * Access an object value by name, returns null if there is no member with + * that name. + */ Value const& operator[](char const* key) const; - /// Access an object value by name, create a null member if it does not - /// exist. + /** + * Access an object value by name, create a null member if it does not + * exist. + */ Value& operator[](std::string const& key); - /// Access an object value by name, returns null if there is no member with - /// that name. + /** + * Access an object value by name, returns null if there is no member with + * that name. + */ Value const& operator[](std::string const& key) const; - /** \brief Access an object value by name, create a null member if it does - not exist. - + /** + * @brief Access an object value by name, create a null member if it does + * not exist. + * * If the object as no entry for that name, then the member name used to - store + * store * the new entry is not duplicated. * Example of use: - * \code + * @code * json::Value object; * static const StaticString code("code"); * object[code] = 1234; - * \endcode + * @endcode */ Value& operator[](StaticString const& key); Value const& operator[](StaticString const& key) const; - /// Return the member named key if it exist, defaultValue otherwise. + /** + * Return the member named key if it exist, defaultValue otherwise. + */ Value get(char const* key, Value const& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. + /** + * Return the member named key if it exist, defaultValue otherwise. + */ [[nodiscard]] Value get(std::string const& key, Value const& defaultValue) const; - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is ValueType::Object or ValueType::Null - /// \post type() is unchanged + /** + * @brief Remove and return the named member. + * + * Do nothing if it did not exist. + * @return the removed Value, or null. + * @pre type() is ValueType::Object or ValueType::Null + * @post type() is unchanged + */ Value removeMember(char const* key); - /// Same as removeMember(const char*) + /** + * Same as removeMember(const char*) + */ Value removeMember(std::string const& key); - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ bool isMember(char const* key) const; - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ [[nodiscard]] bool isMember(std::string const& key) const; - /// Return true if the object has a member named key. + /** + * Return true if the object has a member named key. + */ [[nodiscard]] bool isMember(StaticString const& key) const; - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is ValueType::Object or ValueType::Null - /// \post if type() was ValueType::Null, it remains ValueType::Null + /** + * @brief Return a list of the member names. + * + * If null, return an empty list. + * @pre type() is ValueType::Object or ValueType::Null + * @post if type() was ValueType::Null, it remains ValueType::Null + */ [[nodiscard]] Members getMemberNames() const; @@ -461,7 +516,8 @@ operator>=(Value const& x, Value const& y) return !(x < y); } -/** \brief Experimental do not use: Allocator to customize member name and +/** + * @brief Experimental do not use: Allocator to customize member name and * string value memory management done by Value. * * - makeMemberName() and releaseMemberName() are called to respectively @@ -486,8 +542,8 @@ public: releaseStringValue(char* value) = 0; }; -/** \brief base class for Value iterators. - * +/** + * @brief base class for Value iterators. */ class ValueIteratorBase { @@ -512,17 +568,23 @@ public: return !isEqual(other); } - /// Return either the index or the member name of the referenced value as a - /// Value. + /** + * Return either the index or the member name of the referenced value as a + * Value. + */ [[nodiscard]] Value key() const; - /// Return the index of the referenced Value. -1 if it is not an ValueType::Array. + /** + * Return the index of the referenced Value. -1 if it is not an ValueType::Array. + */ [[nodiscard]] UInt index() const; - /// Return the member name of the referenced Value. "" if it is not an - /// ValueType::Object. + /** + * Return the member name of the referenced Value. "" if it is not an + * ValueType::Object. + */ [[nodiscard]] char const* memberName() const; @@ -551,8 +613,8 @@ private: bool isNull_; }; -/** \brief const iterator for object and array value. - * +/** + * @brief const iterator for object and array value. */ class ValueConstIterator : public ValueIteratorBase { @@ -569,7 +631,8 @@ public: ValueConstIterator(ValueConstIterator const& other) = default; private: - /*! \internal Use by Value to create an iterator. + /** + * @internal Use by Value to create an iterator. */ explicit ValueConstIterator(Value::ObjectValues::iterator const& current); @@ -614,7 +677,8 @@ public: } }; -/** \brief Iterator for object and array value. +/** + * @brief Iterator for object and array value. */ class ValueIterator : public ValueIteratorBase { @@ -632,7 +696,8 @@ public: ValueIterator(ValueIterator const& other); private: - /*! \internal Use by Value to create an iterator. + /** + * @internal Use by Value to create an iterator. */ explicit ValueIterator(Value::ObjectValues::iterator const& current); diff --git a/include/xrpl/json/json_writer.h b/include/xrpl/json/json_writer.h index 4bc15b71da..65c8b20931 100644 --- a/include/xrpl/json/json_writer.h +++ b/include/xrpl/json/json_writer.h @@ -13,7 +13,8 @@ namespace json { class Value; -/** \brief Abstract class for writers. +/** + * @brief Abstract class for writers. */ class WriterBase { @@ -23,12 +24,13 @@ public: write(Value const& root) = 0; }; -/** \brief Outputs a Value in JSON format +/** + * @brief Outputs a Value in JSON format * without formatting (not human friendly). * * The JSON document is written in a single line. It is not intended for 'human' * consumption, but may be useful to support feature such as RPC where bandwidth - * is limited. \sa Reader, Value + * is limited. @see Reader, Value */ class FastWriter : public WriterBase @@ -48,7 +50,8 @@ private: std::string document_; }; -/** \brief Writes a Value in JSON format in a +/** + * @brief Writes a Value in JSON format in a * human friendly way. * * The rules for line break and indent are as follow: @@ -64,7 +67,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * \sa Reader, Value + * @see Reader, Value */ class StyledWriter : public WriterBase { @@ -73,8 +76,9 @@ public: ~StyledWriter() override = default; public: // overridden from Writer - /** \brief Serialize a Value in JSON - * format. \param root Value to serialize. \return String containing the + /** + * @brief Serialize a Value in JSON + * format. @param root Value to serialize. @return String containing the * JSON document that represents the root value. */ std::string @@ -108,26 +112,27 @@ private: bool addChildValues_{}; }; -/** \brief Writes a Value in JSON format in a - human friendly way, to a stream rather than to a string. +/** + * @brief Writes a Value in JSON format in a + * human friendly way, to a stream rather than to a string. * * The rules for line break and indent are as follow: * - Object value: * - if empty then print {} without indent and line break * - if not empty the print '{', line break & indent, print one value per - line + * line * and then unindent and line break and print '}'. * - Array value: * - if empty then print [] without indent and line break * - if the array contains no object value, empty array or some other value - types, + * types, * and all the values fit on one lines, then print the array on a single - line. + * line. * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value + * @param indentation Each level will be indented by this amount extra. + * @see Reader, Value */ class StyledStreamWriter { @@ -136,10 +141,11 @@ public: ~StyledStreamWriter() = default; public: - /** \brief Serialize a Value in JSON - * format. \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not + /** + * @brief Serialize a Value in JSON + * format. @param out Stream to write to. (Can be ostringstream, e.g.) + * @param root Value to serialize. + * @note There is no point in deriving from Writer, since write() should not * return a value. */ void @@ -184,8 +190,10 @@ valueToString(bool value); std::string valueToQuotedString(char const* value); -/// \brief Output using the StyledStreamWriter. -/// \see json::operator>>() +/** + * @brief Output using the StyledStreamWriter. + * @see json::operator>>() + */ std::ostream& operator<<(std::ostream&, Value const& root); @@ -265,12 +273,13 @@ writeValue(Write const& write, Value const& value) } // namespace detail -/** Stream compact JSON to the specified function. - - @param jv The json::Value to write - @param write Invocable with signature void(void const*, std::size_t) that - is called when output should be written to the stream. -*/ +/** + * Stream compact JSON to the specified function. + * + * @param jv The json::Value to write + * @param write Invocable with signature void(void const*, std::size_t) that + * is called when output should be written to the stream. + */ template void stream(json::Value const& jv, Write const& write) @@ -279,29 +288,31 @@ stream(json::Value const& jv, Write const& write) write("\n", 1); } -/** Decorator for streaming out compact json - - Use - - json::Value jv; - out << json::Compact{jv} - - to write a single-line, compact version of `jv` to the stream, rather - than the styled format that comes from undecorated streaming. -*/ +/** + * Decorator for streaming out compact json + * + * Use + * + * json::Value jv; + * out << json::Compact{jv} + * + * to write a single-line, compact version of `jv` to the stream, rather + * than the styled format that comes from undecorated streaming. + */ class Compact { json::Value jv_; public: - /** Wrap a json::Value for compact streaming - - @param jv The json::Value to stream - - @note For now, we do not support wrapping lvalues to avoid - potentially costly copies. If we find a need, we can consider - adding support for compact lvalue streaming in the future. - */ + /** + * Wrap a json::Value for compact streaming + * + * @param jv The json::Value to stream + * + * @note For now, we do not support wrapping lvalues to avoid + * potentially costly copies. If we find a need, we can consider + * adding support for compact lvalue streaming in the future. + */ Compact(json::Value&& jv) : jv_{std::move(jv)} { } diff --git a/include/xrpl/json/to_string.h b/include/xrpl/json/to_string.h index 1d7b4c785a..bdd7a51e6a 100644 --- a/include/xrpl/json/to_string.h +++ b/include/xrpl/json/to_string.h @@ -6,11 +6,15 @@ namespace json { -/** Writes a json::Value to an std::string. */ +/** + * Writes a json::Value to an std::string. + */ std::string to_string(Value const&); -/** Writes a json::Value to an std::string. */ +/** + * Writes a json::Value to an std::string. + */ std::string pretty(Value const&); diff --git a/include/xrpl/ledger/AcceptedLedgerTx.h b/include/xrpl/ledger/AcceptedLedgerTx.h index f59b8a074d..283dcf6e24 100644 --- a/include/xrpl/ledger/AcceptedLedgerTx.h +++ b/include/xrpl/ledger/AcceptedLedgerTx.h @@ -21,17 +21,17 @@ namespace xrpl { /** - A transaction that is in a closed ledger. - - Description - - An accepted ledger transaction contains additional information that the - server needs to tell clients about the transaction. For example, - - The transaction in JSON form - - Which accounts are affected - * This is used by InfoSub to report to clients - - Cached stuff -*/ + * A transaction that is in a closed ledger. + * + * Description + * + * An accepted ledger transaction contains additional information that the + * server needs to tell clients about the transaction. For example, + * - The transaction in JSON form + * - Which accounts are affected + * * This is used by InfoSub to report to clients + * - Cached stuff + */ class AcceptedLedgerTx : public CountedObject { public: diff --git a/include/xrpl/ledger/AmendmentTable.h b/include/xrpl/ledger/AmendmentTable.h index 6598be5a5c..c3ef779eb1 100644 --- a/include/xrpl/ledger/AmendmentTable.h +++ b/include/xrpl/ledger/AmendmentTable.h @@ -37,10 +37,11 @@ namespace xrpl { class ServiceRegistry; -/** The amendment table stores the list of enabled and potential amendments. - Individuals amendments are voted on by validators during the consensus - process. -*/ +/** + * The amendment table stores the list of enabled and potential amendments. + * Individuals amendments are voted on by validators during the consensus + * process. + */ class AmendmentTable { public: @@ -90,11 +91,15 @@ public: [[nodiscard]] virtual json::Value getJson(bool isAdmin) const = 0; - /** Returns a json::ValueType::Object. */ + /** + * Returns a json::ValueType::Object. + */ [[nodiscard]] virtual json::Value getJson(uint256 const& amendment, bool isAdmin) const = 0; - /** Called when a new fully-validated ledger is accepted. */ + /** + * Called when a new fully-validated ledger is accepted. + */ void doValidatedLedger(std::shared_ptr const& lastValidatedLedger) { @@ -107,9 +112,10 @@ public: } } - /** Called to determine whether the amendment logic needs to process - a new validated ledger. (If it could have changed things.) - */ + /** + * Called to determine whether the amendment logic needs to process + * a new validated ledger. (If it could have changed things.) + */ [[nodiscard]] virtual bool needValidatedLedger(LedgerIndex seq) const = 0; diff --git a/include/xrpl/ledger/ApplyView.h b/include/xrpl/ledger/ApplyView.h index e519013d9a..724d89b7c6 100644 --- a/include/xrpl/ledger/ApplyView.h +++ b/include/xrpl/ledger/ApplyView.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include // IWYU pragma: keep @@ -11,6 +12,7 @@ #include #include #include +#include #include #include @@ -90,47 +92,50 @@ operator&=(ApplyFlags& lhs, ApplyFlags const& rhs) //------------------------------------------------------------------------------ -/** Writeable view to a ledger, for applying a transaction. - - This refinement of ReadView provides an interface where - the SLE can be "checked out" for modifications and put - back in an updated or removed state. Also added is an - interface to provide contextual information necessary - to calculate the results of transaction processing, - including the metadata if the view is later applied to - the parent (using an interface in the derived class). - The context info also includes values from the base - ledger such as sequence number and the network time. - - This allows implementations to journal changes made to - the state items in a ledger, with the option to apply - those changes to the base or discard the changes without - affecting the base. - - Typical usage is to call read() for non-mutating - operations. - - For mutating operations the sequence is as follows: - - // Add a new value - v.insert(sle); - - // Check out a value for modification - sle = v.peek(k); - - // Indicate that changes were made - v.update(sle) - - // Or, erase the value - v.erase(sle) - - The invariant is that insert, update, and erase may not - be called with any SLE which belongs to different view. -*/ +/** + * Writeable view to a ledger, for applying a transaction. + * + * This refinement of ReadView provides an interface where + * the SLE can be "checked out" for modifications and put + * back in an updated or removed state. Also added is an + * interface to provide contextual information necessary + * to calculate the results of transaction processing, + * including the metadata if the view is later applied to + * the parent (using an interface in the derived class). + * The context info also includes values from the base + * ledger such as sequence number and the network time. + * + * This allows implementations to journal changes made to + * the state items in a ledger, with the option to apply + * those changes to the base or discard the changes without + * affecting the base. + * + * Typical usage is to call read() for non-mutating + * operations. + * + * For mutating operations the sequence is as follows: + * + * // Add a new value + * v.insert(sle); + * + * // Check out a value for modification + * sle = v.peek(k); + * + * // Indicate that changes were made + * v.update(sle) + * + * // Or, erase the value + * v.erase(sle) + * + * The invariant is that insert, update, and erase may not + * be called with any SLE which belongs to different view. + */ class ApplyView : public ReadView { private: - /** Add an entry to a directory using the specified insert strategy */ + /** + * Add an entry to a directory using the specified insert strategy + */ std::optional dirAdd( bool preserveOrder, @@ -141,84 +146,89 @@ private: public: ApplyView() = default; - /** Returns the tx apply flags. - - Flags can affect the outcome of transaction - processing. For example, transactions applied - to an open ledger generate "local" failures, - while transactions applied to the consensus - ledger produce hard failures (and claim a fee). - */ + /** + * Returns the tx apply flags. + * + * Flags can affect the outcome of transaction + * processing. For example, transactions applied + * to an open ledger generate "local" failures, + * while transactions applied to the consensus + * ledger produce hard failures (and claim a fee). + */ [[nodiscard]] virtual ApplyFlags flags() const = 0; - /** Prepare to modify the SLE associated with key. - - Effects: - - Gives the caller ownership of a modifiable - SLE associated with the specified key. - - The returned SLE may be used in a subsequent - call to erase or update. - - The SLE must not be passed to any other ApplyView. - - @return `nullptr` if the key is not present - */ + /** + * Prepare to modify the SLE associated with key. + * + * Effects: + * + * Gives the caller ownership of a modifiable + * SLE associated with the specified key. + * + * The returned SLE may be used in a subsequent + * call to erase or update. + * + * The SLE must not be passed to any other ApplyView. + * + * @return `nullptr` if the key is not present + */ virtual SLE::pointer peek(Keylet const& k) = 0; - /** Remove a peeked SLE. - - Requirements: - - `sle` was obtained from prior call to peek() - on this instance of the RawView. - - Effects: - - The key is no longer associated with the SLE. - */ + /** + * Remove a peeked SLE. + * + * Requirements: + * + * `sle` was obtained from prior call to peek() + * on this instance of the RawView. + * + * Effects: + * + * The key is no longer associated with the SLE. + */ virtual void erase(SLE::ref sle) = 0; - /** Insert a new state SLE - - Requirements: - - `sle` was not obtained from any calls to - peek() on any instances of RawView. - - The SLE's key must not already exist. - - Effects: - - The key in the state map is associated - with the SLE. - - The RawView acquires ownership of the shared_ptr. - - @note The key is taken from the SLE - */ + /** + * Insert a new state SLE + * + * Requirements: + * + * `sle` was not obtained from any calls to + * peek() on any instances of RawView. + * + * The SLE's key must not already exist. + * + * Effects: + * + * The key in the state map is associated + * with the SLE. + * + * The RawView acquires ownership of the shared_ptr. + * + * @note The key is taken from the SLE + */ virtual void insert(SLE::ref sle) = 0; - /** Indicate changes to a peeked SLE - - Requirements: - - The SLE's key must exist. - - `sle` was obtained from prior call to peek() - on this instance of the RawView. - - Effects: - - The SLE is updated - - @note The key is taken from the SLE - */ + /** + * Indicate changes to a peeked SLE + * + * Requirements: + * + * The SLE's key must exist. + * + * `sle` was obtained from prior call to peek() + * on this instance of the RawView. + * + * Effects: + * + * The SLE is updated + * + * @note The key is taken from the SLE + */ /** @{ */ virtual void update(SLE::ref sle) = 0; @@ -248,7 +258,8 @@ public: XRPL_ASSERT(amount.holds(), "creditHookMPT: amount is for MPTIssue"); } - /** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. + /** + * Facilitate tracking of MPT sold by an issuer owning MPT sell offer. * Unlike IOU, MPT doesn't have bi-directional relationship with an issuer, * where a trustline limits an amount that can be issued to a holder. * Consequently, the credit step (last MPTEndpointStep or @@ -288,27 +299,28 @@ public: // Called when the owner count changes // This is required to support PaymentSandbox virtual void - adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) + adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next) { } - /** Append an entry to a directory - - Entries in the directory will be stored in order of insertion, i.e. new - entries will always be added at the tail end of the last page. - - @param directory the base of the directory - @param key the entry to insert - @param describe callback to add required entries to a new page - - @return a \c std::optional which, if insertion was successful, - will contain the page number in which the item was stored. - - @note this function may create a page (including a root page), if no - page with space is available. This function will only fail if the - page counter exceeds the protocol-defined maximum number of - allowable pages. - */ + /** + * Append an entry to a directory + * + * Entries in the directory will be stored in order of insertion, i.e. new + * entries will always be added at the tail end of the last page. + * + * @param directory the base of the directory + * @param key the entry to insert + * @param describe callback to add required entries to a new page + * + * @return a @c std::optional which, if insertion was successful, + * will contain the page number in which the item was stored. + * + * @note this function may create a page (including a root page), if no + * page with space is available. This function will only fail if the + * page counter exceeds the protocol-defined maximum number of + * allowable pages. + */ /** @{ */ std::optional dirAppend( @@ -331,23 +343,24 @@ public: } /** @} */ - /** Insert an entry to a directory - - Entries in the directory will be stored in a semi-random order, but - each page will be maintained in sorted order. - - @param directory the base of the directory - @param key the entry to insert - @param describe callback to add required entries to a new page - - @return a \c std::optional which, if insertion was successful, - will contain the page number in which the item was stored. - - @note this function may create a page (including a root page), if no - page with space is available.this function will only fail if the - page counter exceeds the protocol-defined maximum number of - allowable pages. - */ + /** + * Insert an entry to a directory + * + * Entries in the directory will be stored in a semi-random order, but + * each page will be maintained in sorted order. + * + * @param directory the base of the directory + * @param key the entry to insert + * @param describe callback to add required entries to a new page + * + * @return a @c std::optional which, if insertion was successful, + * will contain the page number in which the item was stored. + * + * @note this function may create a page (including a root page), if no + * page with space is available.this function will only fail if the + * page counter exceeds the protocol-defined maximum number of + * allowable pages. + */ /** @{ */ std::optional dirInsert( @@ -368,21 +381,22 @@ public: } /** @} */ - /** Remove an entry from a directory - - @param directory the base of the directory - @param page the page number for this page - @param key the entry to remove - @param keepRoot if deleting the last entry, don't - delete the root page (i.e. the directory itself). - - @return \c true if the entry was found and deleted and - \c false otherwise. - - @note This function will remove zero or more pages from the directory; - the root page will not be deleted even if it is empty, unless - \p keepRoot is not set and the directory is empty. - */ + /** + * Remove an entry from a directory + * + * @param directory the base of the directory + * @param page the page number for this page + * @param key the entry to remove + * @param keepRoot if deleting the last entry, don't + * delete the root page (i.e. the directory itself). + * + * @return @c true if the entry was found and deleted and + * @c false otherwise. + * + * @note This function will remove zero or more pages from the directory; + * the root page will not be deleted even if it is empty, unless + * \p keepRoot is not set and the directory is empty. + */ /** @{ */ bool dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot); @@ -394,29 +408,51 @@ public: } /** @} */ - /** Remove the specified directory, invoking the callback for every node. */ + /** + * Remove the specified directory, invoking the callback for every node. + */ bool dirDelete(Keylet const& directory, std::function const&); - /** Remove the specified directory, if it is empty. - - @param directory the identifier of the directory node to be deleted - @return \c true if the directory was found and was successfully deleted - \c false otherwise. - - @note The function should only be called with the root entry (i.e. with - the first page) of a directory. - */ + /** + * Remove the specified directory, if it is empty. + * + * @param directory the identifier of the directory node to be deleted + * @return @c true if the directory was found and was successfully deleted + * @c false otherwise. + * + * @note The function should only be called with the root entry (i.e. with + * the first page) of a directory. + */ bool emptyDirDelete(Keylet const& directory); }; -namespace directory { -/** Helper functions for managing low-level directory operations. - These are not part of the ApplyView interface. +/** + * Bundles the mutable ledger view and the transaction being applied. + * + * Passed together to avoid threading two separate parameters through every + * helper that needs both the view (for state reads/writes) and the + * transaction (for field inspection and metadata). + * + * Both members are non-owning references; the caller is responsible for + * ensuring that the referenced objects outlive the ApplyViewContext. + * + * TODO: replace with ApplyContext after it's untangled with xrpl/tx + */ +struct ApplyViewContext +{ + ApplyView& view; + STTx const& tx; +}; - Don't use them unless you really, really know what you're doing. - Instead use dirAdd, dirInsert, etc. +namespace directory { +/** + * Helper functions for managing low-level directory operations. + * These are not part of the ApplyView interface. + * + * Don't use them unless you really, really know what you're doing. + * Instead use dirAdd, dirInsert, etc. */ std::uint64_t diff --git a/include/xrpl/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h index 9a3734a8ca..630153f90a 100644 --- a/include/xrpl/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -18,12 +18,13 @@ namespace xrpl { -/** Editable, discardable view that can build metadata for one tx. - - Iteration of the tx map is delegated to the base. - - @note Presented as ApplyView to clients. -*/ +/** + * Editable, discardable view that can build metadata for one tx. + * + * Iteration of the tx map is delegated to the base. + * + * @note Presented as ApplyView to clients. + */ class ApplyViewImpl final : public detail::ApplyViewBase { public: @@ -37,12 +38,13 @@ public: ApplyViewImpl(ApplyViewImpl&&) = default; ApplyViewImpl(ReadView const* base, ApplyFlags flags); - /** Apply the transaction. - - After a call to `apply`, the only valid - operation on this object is to call the - destructor. - */ + /** + * Apply the transaction. + * + * After a call to `apply`, the only valid + * operation on this object is to call the + * destructor. + */ std::optional apply( OpenView& to, @@ -52,25 +54,28 @@ public: bool isDryRun, beast::Journal j); - /** Set the amount of currency delivered. - - This value is used when generating metadata - for payments, to set the DeliveredAmount field. - If the amount is not specified, the field is - excluded from the resulting metadata. - */ + /** + * Set the amount of currency delivered. + * + * This value is used when generating metadata + * for payments, to set the DeliveredAmount field. + * If the amount is not specified, the field is + * excluded from the resulting metadata. + */ void deliver(STAmount const& amount) { deliver_ = amount; } - /** Get the number of modified entries + /** + * Get the number of modified entries */ std::size_t size(); - /** Visit modified entries + /** + * Visit modified entries */ void visit( diff --git a/include/xrpl/ledger/CachedView.h b/include/xrpl/ledger/CachedView.h index 1da3a67563..b9e2cf8d66 100644 --- a/include/xrpl/ledger/CachedView.h +++ b/include/xrpl/ledger/CachedView.h @@ -133,10 +133,11 @@ public: } // namespace detail -/** Wraps a DigestAwareReadView to provide caching. - - @tparam Base A subclass of DigestAwareReadView -*/ +/** + * Wraps a DigestAwareReadView to provide caching. + * + * @tparam Base A subclass of DigestAwareReadView + */ template class CachedView : public detail::CachedViewImpl { @@ -158,10 +159,11 @@ public: { } - /** Returns the base type. - - @note This breaks encapsulation and bypasses the cache. - */ + /** + * Returns the base type. + * + * @note This breaks encapsulation and bypasses the cache. + */ std::shared_ptr const& base() const { diff --git a/include/xrpl/ledger/CanonicalTXSet.h b/include/xrpl/ledger/CanonicalTXSet.h index f8349dfab6..11aadf4e92 100644 --- a/include/xrpl/ledger/CanonicalTXSet.h +++ b/include/xrpl/ledger/CanonicalTXSet.h @@ -13,13 +13,13 @@ namespace xrpl { -/** Holds transactions which were deferred to the next pass of consensus. - - "Canonical" refers to the order in which transactions are applied. - - - Puts transactions from the same account in SeqProxy order - -*/ +/** + * Holds transactions which were deferred to the next pass of consensus. + * + * "Canonical" refers to the order in which transactions are applied. + * + * - Puts transactions from the same account in SeqProxy order + */ // VFALCO TODO rename to SortedTxSet class CanonicalTXSet : public CountedObject { diff --git a/include/xrpl/ledger/Dir.h b/include/xrpl/ledger/Dir.h index 05df887d8b..233719cdeb 100644 --- a/include/xrpl/ledger/Dir.h +++ b/include/xrpl/ledger/Dir.h @@ -13,18 +13,19 @@ namespace xrpl { -/** A class that simplifies iterating ledger directory pages - - The Dir class provides a forward iterator for walking through - the uint256 values contained in ledger directories. - - The Dir class also allows accelerated directory walking by - stepping directly from one page to the next using the next_page() - member function. - - As of July 2024, the Dir class is only being used with NFTokenOffer - directories and for unit tests. -*/ +/** + * A class that simplifies iterating ledger directory pages + * + * The Dir class provides a forward iterator for walking through + * the uint256 values contained in ledger directories. + * + * The Dir class also allows accelerated directory walking by + * stepping directly from one page to the next using the next_page() + * member function. + * + * As of July 2024, the Dir class is only being used with NFTokenOffer + * directories and for unit tests. + */ class Dir { private: diff --git a/include/xrpl/ledger/Ledger.h b/include/xrpl/ledger/Ledger.h index 3453389a5e..e1dd2c422e 100644 --- a/include/xrpl/ledger/Ledger.h +++ b/include/xrpl/ledger/Ledger.h @@ -42,32 +42,33 @@ struct CreateGenesisT }; extern CreateGenesisT const kCreateGenesis; -/** Holds a ledger. - - The ledger is composed of two SHAMaps. The state map holds all of the - ledger entries such as account roots and order books. The tx map holds - all of the transactions and associated metadata that made it into that - particular ledger. Most of the operations on a ledger are concerned - with the state map. - - This can hold just the header, a partial set of data, or the entire set - of data. It all depends on what is in the corresponding SHAMap entry. - Various functions are provided to populate or depopulate the caches that - the object holds references to. - - Ledgers are constructed as either mutable or immutable. - - 1) If you are the sole owner of a mutable ledger, you can do whatever you - want with no need for locks. - - 2) If you have an immutable ledger, you cannot ever change it, so no need - for locks. - - 3) Mutable ledgers cannot be shared. - - @note Presented to clients as ReadView - @note Calls virtuals in the constructor, so marked as final -*/ +/** + * Holds a ledger. + * + * The ledger is composed of two SHAMaps. The state map holds all of the + * ledger entries such as account roots and order books. The tx map holds + * all of the transactions and associated metadata that made it into that + * particular ledger. Most of the operations on a ledger are concerned + * with the state map. + * + * This can hold just the header, a partial set of data, or the entire set + * of data. It all depends on what is in the corresponding SHAMap entry. + * Various functions are provided to populate or depopulate the caches that + * the object holds references to. + * + * Ledgers are constructed as either mutable or immutable. + * + * 1) If you are the sole owner of a mutable ledger, you can do whatever you + * want with no need for locks. + * + * 2) If you have an immutable ledger, you cannot ever change it, so no need + * for locks. + * + * 3) Mutable ledgers cannot be shared. + * + * @note Presented to clients as ReadView + * @note Calls virtuals in the constructor, so marked as final + */ class Ledger final : public std::enable_shared_from_this, public DigestAwareReadView, public TxsRawView, @@ -82,20 +83,21 @@ public: Ledger& operator=(Ledger&&) = delete; - /** Create the Genesis ledger. - - The Genesis ledger contains a single account whose - AccountID is generated with a Generator using the seed - computed from the string "masterpassphrase" and ordinal - zero. - - The account has an XRP balance equal to the total amount - of XRP in the system. No more XRP than the amount which - starts in this account can ever exist, with amounts - used to pay fees being destroyed. - - Amendments specified are enabled in the genesis ledger - */ + /** + * Create the Genesis ledger. + * + * The Genesis ledger contains a single account whose + * AccountID is generated with a Generator using the seed + * computed from the string "masterpassphrase" and ordinal + * zero. + * + * The account has an XRP balance equal to the total amount + * of XRP in the system. No more XRP than the amount which + * starts in this account can ever exist, with amounts + * used to pay fees being destroyed. + * + * Amendments specified are enabled in the genesis ledger + */ Ledger( CreateGenesisT, Rules rules, @@ -105,13 +107,14 @@ public: Ledger(LedgerHeader const& info, Rules rules, Family& family); - /** Used for ledgers loaded from JSON files - - @param acquire If true, acquires the ledger if not found locally - - @note The fees parameter provides default values, but setup() may - override them from the ledger state if fee-related SLEs exist. - */ + /** + * Used for ledgers loaded from JSON files + * + * @param acquire If true, acquires the ledger if not found locally + * + * @note The fees parameter provides default values, but setup() may + * override them from the ledger state if fee-related SLEs exist. + */ Ledger( LedgerHeader const& info, bool& loaded, @@ -121,12 +124,13 @@ public: Family& family, beast::Journal j); - /** Create a new ledger following a previous ledger - - The ledger will have the sequence number that - follows previous, and have - parentCloseTime == previous.closeTime. - */ + /** + * Create a new ledger following a previous ledger + * + * The ledger will have the sequence number that + * follows previous, and have + * parentCloseTime == previous.closeTime. + */ Ledger(Ledger const& previous, NetClock::time_point closeTime); // used for database ledgers @@ -369,11 +373,15 @@ public: void updateNegativeUNL(); - /** Returns true if the ledger is a flag ledger */ + /** + * Returns true if the ledger is a flag ledger + */ bool isFlagLedger() const; - /** Returns true if the ledger directly precedes a flag ledger */ + /** + * Returns true if the ledger directly precedes a flag ledger + */ bool isVotingLedger() const; @@ -387,23 +395,25 @@ private: bool setup(); - /** @brief Deserialize a SHAMapItem containing a single STTx. + /** + * @brief Deserialize a SHAMapItem containing a single STTx. * * @param item The SHAMapItem to deserialize. * @return A shared pointer to the deserialized transaction. - * @throw May throw on deserialization error. + * @throws May throw on deserialization error. */ static std::shared_ptr deserializeTx(SHAMapItem const& item); - /** @brief Deserialize a SHAMapItem containing STTx + STObject metadata. + /** + * @brief Deserialize a SHAMapItem containing STTx + STObject metadata. * * The SHAMapItem must contain two variable length serialization objects. * * @param item The SHAMapItem to deserialize. * @return A pair containing shared pointers to the deserialized transaction * and metadata. - * @throw May throw on deserialization error. + * @throws May throw on deserialization error. */ static std::pair, std::shared_ptr> deserializeTxPlusMeta(SHAMapItem const& item); @@ -425,7 +435,9 @@ private: beast::Journal j_; }; -/** A ledger wrapped in a CachedView. */ +/** + * A ledger wrapped in a CachedView. + */ using CachedLedger = CachedView; } // namespace xrpl diff --git a/include/xrpl/ledger/LedgerTiming.h b/include/xrpl/ledger/LedgerTiming.h index a97e229046..77254a434b 100644 --- a/include/xrpl/ledger/LedgerTiming.h +++ b/include/xrpl/ledger/LedgerTiming.h @@ -8,11 +8,12 @@ namespace xrpl { -/** Possible ledger close time resolutions. - - Values should not be duplicated. - @see getNextLedgerTimeResolution -*/ +/** + * Possible ledger close time resolutions. + * + * Values should not be duplicated. + * @see getNextLedgerTimeResolution + */ constexpr std::chrono::seconds kLedgerPossibleTimeResolutions[] = { std::chrono::seconds{10}, std::chrono::seconds{20}, @@ -21,41 +22,50 @@ constexpr std::chrono::seconds kLedgerPossibleTimeResolutions[] = { std::chrono::seconds{90}, std::chrono::seconds{120}}; -//! Initial resolution of ledger close time. +/** + * Initial resolution of ledger close time. + */ constexpr auto kLedgerDefaultTimeResolution = kLedgerPossibleTimeResolutions[2]; -//! Close time resolution in genesis ledger +/** + * Close time resolution in genesis ledger + */ constexpr auto kLedgerGenesisTimeResolution = kLedgerPossibleTimeResolutions[0]; -//! How often we increase the close time resolution (in numbers of ledgers) +/** + * How often we increase the close time resolution (in numbers of ledgers) + */ constexpr auto kIncreaseLedgerTimeResolutionEvery = 8; -//! How often we decrease the close time resolution (in numbers of ledgers) +/** + * How often we decrease the close time resolution (in numbers of ledgers) + */ constexpr auto kDecreaseLedgerTimeResolutionEvery = 1; -/** Calculates the close time resolution for the specified ledger. - - The XRPL protocol uses binning to represent time intervals using only one - timestamp. This allows servers to derive a common time for the next ledger, - without the need for perfectly synchronized clocks. - The time resolution (i.e. the size of the intervals) is adjusted dynamically - based on what happened in the last ledger, to try to avoid disagreements. - - @param previousResolution the resolution used for the prior ledger - @param previousAgree whether consensus agreed on the close time of the prior - ledger - @param ledgerSeq the sequence number of the new ledger - - @pre previousResolution must be a valid bin - from @ref kLedgerPossibleTimeResolutions - - @tparam Rep Type representing number of ticks in std::chrono::duration - @tparam Period An std::ratio representing tick period in - std::chrono::duration - @tparam Seq Unsigned integer-like type corresponding to the ledger sequence - number. It should be comparable to 0 and support modular - division. Built-in and tagged_integers are supported. -*/ +/** + * Calculates the close time resolution for the specified ledger. + * + * The XRPL protocol uses binning to represent time intervals using only one + * timestamp. This allows servers to derive a common time for the next ledger, + * without the need for perfectly synchronized clocks. + * The time resolution (i.e. the size of the intervals) is adjusted dynamically + * based on what happened in the last ledger, to try to avoid disagreements. + * + * @tparam Rep Type representing number of ticks in std::chrono::duration + * @tparam Period An std::ratio representing tick period in + * std::chrono::duration + * @tparam Seq Unsigned integer-like type corresponding to the ledger sequence + * number. It should be comparable to 0 and support modular + * division. Built-in and tagged_integers are supported. + * + * @param previousResolution the resolution used for the prior ledger + * @param previousAgree whether consensus agreed on the close time of the prior + * ledger + * @param ledgerSeq the sequence number of the new ledger + * + * @pre previousResolution must be a valid bin + * from @ref kLedgerPossibleTimeResolutions + */ template std::chrono::duration getNextLedgerTimeResolution( @@ -98,13 +108,14 @@ getNextLedgerTimeResolution( return previousResolution; } -/** Calculates the close time for a ledger, given a close time resolution. - - @param closeTime The time to be rounded - @param closeResolution The resolution - @return @b closeTime rounded to the nearest multiple of @b closeResolution. - Rounds up if @b closeTime is midway between multiples of @b closeResolution. -*/ +/** + * Calculates the close time for a ledger, given a close time resolution. + * + * @param closeTime The time to be rounded + * @param closeResolution The resolution + * @return @b closeTime rounded to the nearest multiple of @b closeResolution. + * Rounds up if @b closeTime is midway between multiples of @b closeResolution. + */ template std::chrono::time_point roundCloseTime( @@ -119,15 +130,16 @@ roundCloseTime( return closeTime - (closeTime.time_since_epoch() % closeResolution); } -/** Calculate the effective ledger close time - - After adjusting the ledger close time based on the current resolution, also - ensure it is sufficiently separated from the prior close time. - - @param closeTime The raw ledger close time - @param resolution The current close time resolution - @param priorCloseTime The close time of the prior ledger -*/ +/** + * Calculate the effective ledger close time + * + * After adjusting the ledger close time based on the current resolution, also + * ensure it is sufficiently separated from the prior close time. + * + * @param closeTime The raw ledger close time + * @param resolution The current close time resolution + * @param priorCloseTime The close time of the prior ledger + */ template std::chrono::time_point effCloseTime( diff --git a/include/xrpl/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h index 875909715c..3f8e950b02 100644 --- a/include/xrpl/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -25,21 +25,23 @@ namespace xrpl { -/** Open ledger construction tag. - - Views constructed with this tag will have the - rules of open ledgers applied during transaction - processing. +/** + * Open ledger construction tag. + * + * Views constructed with this tag will have the + * rules of open ledgers applied during transaction + * processing. */ inline constexpr struct OpenLedgerT { explicit constexpr OpenLedgerT() = default; } kOpenLedger{}; -/** Batch view construction tag. - - Views constructed with this tag are part of a stack of views - used during batch transaction application. +/** + * Batch view construction tag. + * + * Views constructed with this tag are part of a stack of views + * used during batch transaction application. */ inline constexpr struct BatchViewT { @@ -48,10 +50,11 @@ inline constexpr struct BatchViewT //------------------------------------------------------------------------------ -/** Writable ledger view that accumulates state and tx changes. - - @note Presented as ReadView to clients. -*/ +/** + * Writable ledger view that accumulates state and tx changes. + * + * @note Presented as ReadView to clients. + */ class OpenView final : public ReadView, public TxsRawView { private: @@ -95,7 +98,9 @@ private: detail::RawStateTable items_; std::shared_ptr hold_; - /// In batch mode, the number of transactions already executed. + /** + * In batch mode, the number of transactions already executed. + */ std::size_t baseTxCount_ = 0; bool open_ = true; @@ -109,40 +114,42 @@ public: OpenView(OpenView&&) = default; - /** Construct a shallow copy. - - Effects: - - Creates a new object with a copy of - the modification state table. - - The objects managed by shared pointers are - not duplicated but shared between instances. - Since the SLEs are immutable, calls on the - RawView interface cannot break invariants. - */ + /** + * Construct a shallow copy. + * + * Effects: + * + * Creates a new object with a copy of + * the modification state table. + * + * The objects managed by shared pointers are + * not duplicated but shared between instances. + * Since the SLEs are immutable, calls on the + * RawView interface cannot break invariants. + */ OpenView(OpenView const&); - /** Construct an open ledger view. - - Effects: - - The sequence number is set to the - sequence number of parent plus one. - - The parentCloseTime is set to the - closeTime of parent. - - If `hold` is not nullptr, retains - ownership of a copy of `hold` until - the MetaView is destroyed. - - Calls to rules() will return the - rules provided on construction. - - The tx list starts empty and will contain - all newly inserted tx. - */ + /** + * Construct an open ledger view. + * + * Effects: + * + * The sequence number is set to the + * sequence number of parent plus one. + * + * The parentCloseTime is set to the + * closeTime of parent. + * + * If `hold` is not nullptr, retains + * ownership of a copy of `hold` until + * the MetaView is destroyed. + * + * Calls to rules() will return the + * rules provided on construction. + * + * The tx list starts empty and will contain + * all newly inserted tx. + */ OpenView( OpenLedgerT, ReadView const* base, @@ -159,35 +166,41 @@ public: baseTxCount_ = base.txCount(); } - /** Construct a new last closed ledger. - - Effects: - - The LedgerHeader is copied from the base. - - The rules are inherited from the base. - - The tx list starts empty and will contain - all newly inserted tx. - */ + /** + * Construct a new last closed ledger. + * + * Effects: + * + * The LedgerHeader is copied from the base. + * + * The rules are inherited from the base. + * + * The tx list starts empty and will contain + * all newly inserted tx. + */ OpenView(ReadView const* base, std::shared_ptr hold = nullptr); - /** Returns true if this reflects an open ledger. */ + /** + * Returns true if this reflects an open ledger. + */ bool open() const override { return open_; } - /** Return the number of tx inserted since creation. - - This is used to set the "apply ordinal" - when calculating transaction metadata. - */ + /** + * Return the number of tx inserted since creation. + * + * This is used to set the "apply ordinal" + * when calculating transaction metadata. + */ std::size_t txCount() const; - /** Apply changes. */ + /** + * Apply changes. + */ void apply(TxsRawView& to) const; diff --git a/include/xrpl/ledger/OrderBookDB.h b/include/xrpl/ledger/OrderBookDB.h index a44183900c..96dc94b1f4 100644 --- a/include/xrpl/ledger/OrderBookDB.h +++ b/include/xrpl/ledger/OrderBookDB.h @@ -14,85 +14,92 @@ namespace xrpl { -/** Tracks order books in the ledger. - - This interface provides access to order book information, including: - - Which order books exist in the ledger - - Querying order books by issue - - Managing order book subscriptions - - The order book database is updated as ledgers are accepted and provides - efficient lookup of order book information for pathfinding and client - subscriptions. -*/ +/** + * Tracks order books in the ledger. + * + * This interface provides access to order book information, including: + * - Which order books exist in the ledger + * - Querying order books by issue + * - Managing order book subscriptions + * + * The order book database is updated as ledgers are accepted and provides + * efficient lookup of order book information for pathfinding and client + * subscriptions. + */ class OrderBookDB { public: virtual ~OrderBookDB() = default; - /** Initialize or update the order book database with a new ledger. - - This method should be called when a new ledger is accepted to update - the order book database with the current state of all order books. - - @param ledger The ledger to scan for order books - */ + /** + * Initialize or update the order book database with a new ledger. + * + * This method should be called when a new ledger is accepted to update + * the order book database with the current state of all order books. + * + * @param ledger The ledger to scan for order books + */ virtual void setup(std::shared_ptr const& ledger) = 0; - /** Add an order book to track. - - @param book The order book to add - */ + /** + * Add an order book to track. + * + * @param book The order book to add + */ virtual void addOrderBook(Book const& book) = 0; - /** Get all order books that want a specific issue. - - Returns a list of all order books where the taker pays the specified - issue. This is useful for pathfinding to find all possible next hops - from a given currency. - - @param asset The asset to search for - @param domain Optional domain restriction for the order book - @return Vector of books that want this issue - */ + /** + * Get all order books that want a specific issue. + * + * Returns a list of all order books where the taker pays the specified + * issue. This is useful for pathfinding to find all possible next hops + * from a given currency. + * + * @param asset The asset to search for + * @param domain Optional domain restriction for the order book + * @return Vector of books that want this issue + */ virtual std::vector getBooksByTakerPays(Asset const& asset, std::optional const& domain = std::nullopt) = 0; - /** Get the count of order books that want a specific issue. - - @param asset The asset to search for - @param domain Optional domain restriction for the order book - @return Number of books that want this issue - */ + /** + * Get the count of order books that want a specific issue. + * + * @param asset The asset to search for + * @param domain Optional domain restriction for the order book + * @return Number of books that want this issue + */ virtual int getBookSize(Asset const& asset, std::optional const& domain = std::nullopt) = 0; - /** Check if an order book to XRP exists for the given issue. - - @param asset The asset to check - @param domain Optional domain restriction for the order book - @return true if a book from this issue to XRP exists - */ + /** + * Check if an order book to XRP exists for the given issue. + * + * @param asset The asset to check + * @param domain Optional domain restriction for the order book + * @return true if a book from this issue to XRP exists + */ virtual bool isBookToXRP(Asset const& asset, std::optional const& domain = std::nullopt) = 0; }; -/** Extract the set of books affected by a transaction. +/** + * Extract the set of books affected by a transaction. * - * Walks the transaction's metadata nodes and collects every order book - * whose offers were created, modified, or deleted. Used by NetworkOPs to - * fan transaction notifications out to book subscribers. + * Walks the transaction's metadata nodes and collects every order book + * whose offers were created, modified, or deleted. Used by NetworkOPs to + * fan transaction notifications out to book subscribers. * - * @param alTx The accepted ledger transaction to inspect. - * @param j Journal used to log per-node parsing failures. Inspecting an - * offer node can throw if a required field is missing; in that - * case the bad node is skipped and a warn-level message is - * emitted via @p j. Other affected books in the same transaction - * are still returned. - * @return The set of books whose offers were created, modified, or - * deleted. May be empty for non-offer transactions. + * @param alTx The accepted ledger transaction to inspect. + * @param j Journal used to log per-node parsing failures. Inspecting an + * offer node can throw if a required field is missing; in that + * case the bad node is skipped and a warn-level message is + * emitted via @p j. Other affected books in the same transaction + * are still returned. + * @return The set of books whose offers were created, modified, or + * deleted. May be empty for non-offer transactions. */ hash_set affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j); diff --git a/include/xrpl/ledger/OwnerCounts.h b/include/xrpl/ledger/OwnerCounts.h new file mode 100644 index 0000000000..74d60014b5 --- /dev/null +++ b/include/xrpl/ledger/OwnerCounts.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include // IWYU pragma: keep +#include + +#include +#include + +namespace xrpl { + +struct OwnerCounts +{ + std::uint32_t owner = 0; + std::uint32_t sponsored = 0; + std::uint32_t sponsoring = 0; + + OwnerCounts() = default; + OwnerCounts(SLE::const_ref sle) + : owner(sle->at(sfOwnerCount)) + , sponsored(sle->at(sfSponsoredOwnerCount)) + , sponsoring(sle->at(sfSponsoringOwnerCount)) + { + XRPL_ASSERT( + owner >= sponsored, + "xrpl::OwnerCounts : OwnerCount must be greater than or equal to " + "SponsoredOwnerCount"); + XRPL_ASSERT(sle->getType() == ltACCOUNT_ROOT, "xrpl::OwnerCounts : sle is AccountRoot"); + } + + [[nodiscard]] std::uint32_t + count() const + { + std::int64_t const x = static_cast(owner) - sponsored + sponsoring; + if (x < 0) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::OwnerCounts::count : count less than zero"); + return 0; + // LCOV_EXCL_STOP + } + + if (x > std::numeric_limits::max()) + return std::numeric_limits::max(); // LCOV_EXCL_LINE + return static_cast(x); + } + + auto + operator<=>(OwnerCounts const& o) const + { + if (auto cmp = count() <=> o.count(); cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + if (auto cmp = owner <=> o.owner; cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + if (auto cmp = sponsored <=> o.sponsored; cmp != 0) // NOLINT(modernize-use-nullptr) + return cmp; + return sponsoring <=> o.sponsoring; + } + + bool + operator==(OwnerCounts const& o) const + { + return this == &o || + (owner == o.owner && sponsored == o.sponsored && sponsoring == o.sponsoring); + } +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/PaymentSandbox.h b/include/xrpl/ledger/PaymentSandbox.h index 0117a962ff..e725bdd556 100644 --- a/include/xrpl/ledger/PaymentSandbox.h +++ b/include/xrpl/ledger/PaymentSandbox.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -107,12 +108,12 @@ public: issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance); void - ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next); + ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next); // Get the adjusted owner count. Since DeferredCredits is meant to be used // in payments, and payments only decrease owner counts, return the max // remembered owner count. - [[nodiscard]] std::optional + [[nodiscard]] std::optional ownerCount(AccountID const& id) const; void @@ -124,25 +125,26 @@ private: std::map creditsIOU_; std::map creditsMPT_; - std::map ownerCounts_; + std::map ownerCounts_; }; } // namespace detail //------------------------------------------------------------------------------ -/** A wrapper which makes credits unavailable to balances. - - This is used for payments and pathfinding, so that consuming - liquidity from a path never causes portions of that path or - other paths to gain liquidity. - - The behavior of certain free functions in the ApplyView API - will change via the balanceHook and creditHook overrides - of PaymentSandbox. - - @note Presented as ApplyView to clients -*/ +/** + * A wrapper which makes credits unavailable to balances. + * + * This is used for payments and pathfinding, so that consuming + * liquidity from a path never causes portions of that path or + * other paths to gain liquidity. + * + * The behavior of certain free functions in the ApplyView API + * will change via the balanceHook and creditHook overrides + * of PaymentSandbox. + * + * @note Presented as ApplyView to clients + */ class PaymentSandbox final : public detail::ApplyViewBase { public: @@ -163,16 +165,17 @@ public: { } - /** Construct on top of existing PaymentSandbox. - - The changes are pushed to the parent when - apply() is called. - - @param parent A non-null pointer to the parent. - - @note A pointer is used to prevent confusion - with copy construction. - */ + /** + * Construct on top of existing PaymentSandbox. + * + * The changes are pushed to the parent when + * apply() is called. + * + * @param parent A non-null pointer to the parent. + * + * @note A pointer is used to prevent confusion + * with copy construction. + */ // VFALCO If we are constructing on top of a PaymentSandbox, // or a PaymentSandbox-derived class, we MUST go through // one of these constructors or invariants will be broken. @@ -218,17 +221,19 @@ public: override; void - adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override; + adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next) + override; - [[nodiscard]] std::uint32_t - ownerCountHook(AccountID const& account, std::uint32_t count) const override; + [[nodiscard]] OwnerCounts + ownerCountHook(AccountID const& account, OwnerCounts const& count) const override; - /** Apply changes to base view. - - `to` must contain contents identical to the parent - view passed upon construction, else undefined - behavior will result. - */ + /** + * Apply changes to base view. + * + * `to` must contain contents identical to the parent + * view passed upon construction, else undefined + * behavior will result. + */ /** @{ */ void apply(RawView& to); diff --git a/include/xrpl/ledger/PendingSaves.h b/include/xrpl/ledger/PendingSaves.h index a18292df68..723ae1aef1 100644 --- a/include/xrpl/ledger/PendingSaves.h +++ b/include/xrpl/ledger/PendingSaves.h @@ -8,12 +8,13 @@ namespace xrpl { -/** Keeps track of which ledgers haven't been fully saved. - - During the ledger building process this collection will keep - track of those ledgers that are being built but have not yet - been completely written. -*/ +/** + * Keeps track of which ledgers haven't been fully saved. + * + * During the ledger building process this collection will keep + * track of those ledgers that are being built but have not yet + * been completely written. + */ class PendingSaves { private: @@ -22,12 +23,13 @@ private: std::condition_variable await_; public: - /** Start working on a ledger - - This is called prior to updating the SQLite indexes. - - @return 'true' if work should be done - */ + /** + * Start working on a ledger + * + * This is called prior to updating the SQLite indexes. + * + * @return 'true' if work should be done + */ bool startWork(LedgerIndex seq) { @@ -45,12 +47,13 @@ public: return true; } - /** Finish working on a ledger - - This is called after updating the SQLite indexes. - The tracking of the work in progress is removed and - threads awaiting completion are notified. - */ + /** + * Finish working on a ledger + * + * This is called after updating the SQLite indexes. + * The tracking of the work in progress is removed and + * threads awaiting completion are notified. + */ void finishWork(LedgerIndex seq) { @@ -60,7 +63,9 @@ public: await_.notify_all(); } - /** Return `true` if a ledger is in the progress of being saved. */ + /** + * Return `true` if a ledger is in the progress of being saved. + */ bool pending(LedgerIndex seq) { @@ -68,14 +73,15 @@ public: return map_.contains(seq); } - /** Check if a ledger should be dispatched - - Called to determine whether work should be done or - dispatched. If work is already in progress and the - call is synchronous, wait for work to be completed. - - @return 'true' if work should be done or dispatched - */ + /** + * Check if a ledger should be dispatched + * + * Called to determine whether work should be done or + * dispatched. If work is already in progress and the + * call is synchronous, wait for work to be completed. + * + * @return 'true' if work should be done or dispatched + */ bool shouldWork(LedgerIndex seq, bool isSynchronous) { @@ -108,12 +114,13 @@ public: } while (true); } - /** Get a snapshot of the pending saves - - Each entry in the returned map corresponds to a ledger - that is in progress or dispatched. The boolean indicates - whether work is currently in progress. - */ + /** + * Get a snapshot of the pending saves + * + * Each entry in the returned map corresponds to a ledger + * that is in progress or dispatched. The boolean indicates + * whether work is currently in progress. + */ std::map getSnapshot() const { diff --git a/include/xrpl/ledger/RawView.h b/include/xrpl/ledger/RawView.h index ac2674226f..b94a7aab27 100644 --- a/include/xrpl/ledger/RawView.h +++ b/include/xrpl/ledger/RawView.h @@ -9,10 +9,11 @@ namespace xrpl { -/** Interface for ledger entry changes. - - Subclasses allow raw modification of ledger entries. -*/ +/** + * Interface for ledger entry changes. + * + * Subclasses allow raw modification of ledger entries. + */ class RawView { public: @@ -22,66 +23,72 @@ public: RawView& operator=(RawView const&) = delete; - /** Delete an existing state item. - - The SLE is provided so the implementation - can calculate metadata. - */ + /** + * Delete an existing state item. + * + * The SLE is provided so the implementation + * can calculate metadata. + */ virtual void rawErase(SLE::ref sle) = 0; - /** Unconditionally insert a state item. - - Requirements: - The key must not already exist. - - Effects: - - The key is associated with the SLE. - - @note The key is taken from the SLE - */ + /** + * Unconditionally insert a state item. + * + * Requirements: + * The key must not already exist. + * + * Effects: + * + * The key is associated with the SLE. + * + * @note The key is taken from the SLE + */ virtual void rawInsert(SLE::ref sle) = 0; - /** Unconditionally replace a state item. - - Requirements: - - The key must exist. - - Effects: - - The key is associated with the SLE. - - @note The key is taken from the SLE - */ + /** + * Unconditionally replace a state item. + * + * Requirements: + * + * The key must exist. + * + * Effects: + * + * The key is associated with the SLE. + * + * @note The key is taken from the SLE + */ virtual void rawReplace(SLE::ref sle) = 0; - /** Destroy XRP. - - This is used to pay for transaction fees. - */ + /** + * Destroy XRP. + * + * This is used to pay for transaction fees. + */ virtual void rawDestroyXRP(XRPAmount const& fee) = 0; }; //------------------------------------------------------------------------------ -/** Interface for changing ledger entries with transactions. - - Allows raw modification of ledger entries and insertion - of transactions into the transaction map. -*/ +/** + * Interface for changing ledger entries with transactions. + * + * Allows raw modification of ledger entries and insertion + * of transactions into the transaction map. + */ class TxsRawView : public RawView { public: - /** Add a transaction to the tx map. - - Closed ledgers must have metadata, - while open ledgers omit metadata. - */ + /** + * Add a transaction to the tx map. + * + * Closed ledgers must have metadata, + * while open ledgers omit metadata. + */ virtual void rawTxInsert( ReadView::key_type const& key, diff --git a/include/xrpl/ledger/ReadView.h b/include/xrpl/ledger/ReadView.h index 724533039b..d0010b6030 100644 --- a/include/xrpl/ledger/ReadView.h +++ b/include/xrpl/ledger/ReadView.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,12 +30,13 @@ namespace xrpl { //------------------------------------------------------------------------------ -/** A view into a ledger. - - This interface provides read access to state - and transaction items. There is no checkpointing - or calculation of metadata. -*/ +/** + * A view into a ledger. + * + * This interface provides read access to state + * and transaction items. There is no checkpointing + * or calculation of metadata. + */ class ReadView { public: @@ -84,72 +87,87 @@ public: { } - /** Returns information about the ledger. */ + /** + * Returns information about the ledger. + */ [[nodiscard]] virtual LedgerHeader const& header() const = 0; - /** Returns true if this reflects an open ledger. */ + /** + * Returns true if this reflects an open ledger. + */ [[nodiscard]] virtual bool open() const = 0; - /** Returns the close time of the previous ledger. */ + /** + * Returns the close time of the previous ledger. + */ [[nodiscard]] NetClock::time_point parentCloseTime() const { return header().parentCloseTime; } - /** Returns the sequence number of the base ledger. */ + /** + * Returns the sequence number of the base ledger. + */ [[nodiscard]] LedgerIndex seq() const { return header().seq; } - /** Returns the fees for the base ledger. */ + /** + * Returns the fees for the base ledger. + */ [[nodiscard]] virtual Fees const& fees() const = 0; - /** Returns the tx processing rules. */ + /** + * Returns the tx processing rules. + */ [[nodiscard]] virtual Rules const& rules() const = 0; - /** Determine if a state item exists. - - @note This can be more efficient than calling read. - - @return `true` if a SLE is associated with the - specified key. - */ + /** + * Determine if a state item exists. + * + * @note This can be more efficient than calling read. + * + * @return `true` if a SLE is associated with the + * specified key. + */ [[nodiscard]] virtual bool exists(Keylet const& k) const = 0; - /** Return the key of the next state item. - - This returns the key of the first state item - whose key is greater than the specified key. If - no such key is present, std::nullopt is returned. - - If `last` is engaged, returns std::nullopt when - the key returned would be outside the open - interval (key, last). - */ + /** + * Return the key of the next state item. + * + * This returns the key of the first state item + * whose key is greater than the specified key. If + * no such key is present, std::nullopt is returned. + * + * If `last` is engaged, returns std::nullopt when + * the key returned would be outside the open + * interval (key, last). + */ [[nodiscard]] virtual std::optional succ(key_type const& key, std::optional const& last = std::nullopt) const = 0; - /** Return the state item associated with a key. - - Effects: - If the key exists, gives the caller ownership - of the non-modifiable corresponding SLE. - - @note While the returned SLE is `const` from the - perspective of the caller, it can be changed - by other callers through raw operations. - - @return `nullptr` if the key is not present or - if the type does not match. - */ + /** + * Return the state item associated with a key. + * + * Effects: + * If the key exists, gives the caller ownership + * of the non-modifiable corresponding SLE. + * + * @note While the returned SLE is `const` from the + * perspective of the caller, it can be changed + * by other callers through raw operations. + * + * @return `nullptr` if the key is not present or + * if the type does not match. + */ [[nodiscard]] virtual SLE::const_pointer read(Keylet const& k) const = 0; @@ -189,8 +207,8 @@ public: // changes that accounts make during a payment. `ownerCountHook` adjusts the // ownerCount so it returns the max value of the ownerCount so far. // This is required to support PaymentSandbox. - [[nodiscard]] virtual std::uint32_t - ownerCountHook(AccountID const& account, std::uint32_t count) const + [[nodiscard]] virtual OwnerCounts + ownerCountHook(AccountID const& account, OwnerCounts const& count) const { return count; } @@ -215,22 +233,24 @@ public: [[nodiscard]] virtual std::unique_ptr txsEnd() const = 0; - /** Returns `true` if a tx exists in the tx map. - - A tx exists in the map if it is part of the - base ledger, or if it is a newly inserted tx. - */ + /** + * Returns `true` if a tx exists in the tx map. + * + * A tx exists in the map if it is part of the + * base ledger, or if it is a newly inserted tx. + */ [[nodiscard]] virtual bool txExists(key_type const& key) const = 0; - /** Read a transaction from the tx map. - - If the view represents an open ledger, - the metadata object will be empty. - - @return A pair of nullptr if the - key is not found in the tx map. - */ + /** + * Read a transaction from the tx map. + * + * If the view represents an open ledger, + * the metadata object will be empty. + * + * @return A pair of nullptr if the + * key is not found in the tx map. + */ [[nodiscard]] virtual tx_type txRead(key_type const& key) const = 0; @@ -238,11 +258,12 @@ public: // Memberspaces // - /** Iterable range of ledger state items. - - @note Visiting each state entry in the ledger can - become quite expensive as the ledger grows. - */ + /** + * Iterable range of ledger state items. + * + * @note Visiting each state entry in the ledger can + * become quite expensive as the ledger grows. + */ SlesType sles; // The range of transactions @@ -251,7 +272,9 @@ public: //------------------------------------------------------------------------------ -/** ReadView that associates keys with digests. */ +/** + * ReadView that associates keys with digests. + */ class DigestAwareReadView : public ReadView { public: @@ -260,10 +283,11 @@ public: DigestAwareReadView() = default; DigestAwareReadView(DigestAwareReadView const&) = default; - /** Return the digest associated with the key. - - @return std::nullopt if the item does not exist. - */ + /** + * Return the digest associated with the key. + * + * @return std::nullopt if the item does not exist. + */ [[nodiscard]] virtual std::optional digest(key_type const& key) const = 0; }; diff --git a/include/xrpl/ledger/Sandbox.h b/include/xrpl/ledger/Sandbox.h index ca8838631f..fd48e339eb 100644 --- a/include/xrpl/ledger/Sandbox.h +++ b/include/xrpl/ledger/Sandbox.h @@ -7,12 +7,13 @@ namespace xrpl { -/** Discardable, editable view to a ledger. - - The sandbox inherits the flags of the base. - - @note Presented as ApplyView to clients. -*/ +/** + * Discardable, editable view to a ledger. + * + * The sandbox inherits the flags of the base. + * + * @note Presented as ApplyView to clients. + */ class Sandbox : public detail::ApplyViewBase { public: diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index c89764df1d..768e518008 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -35,26 +35,27 @@ enum class SkipEntry : bool { No = false, Yes }; // //------------------------------------------------------------------------------ -/** Determines whether the given expiration time has passed. - - In the XRP Ledger, expiration times are defined as the number of whole - seconds after the "XRPL epoch" which, for historical reasons, is set - to January 1, 2000 (00:00 UTC). - - This is like the way the Unix epoch works, except the XRPL epoch is - precisely 946,684,800 seconds after the Unix Epoch. - - See https://xrpl.org/basic-data-types.html#specifying-time - - Expiration is defined in terms of the close time of the parent ledger, - because we definitively know the time that it closed (since consensus - agrees on time) but we do not know the closing time of the ledger that - is under construction. - - @param view The ledger whose parent time is used as the clock. - @param exp The optional expiration time we want to check. - - @returns `true` if `exp` is in the past; `false` otherwise. +/** + * Determines whether the given expiration time has passed. + * + * In the XRP Ledger, expiration times are defined as the number of whole + * seconds after the "XRPL epoch" which, for historical reasons, is set + * to January 1, 2000 (00:00 UTC). + * + * This is like the way the Unix epoch works, except the XRPL epoch is + * precisely 946,684,800 seconds after the Unix Epoch. + * + * See https://xrpl.org/basic-data-types.html#specifying-time + * + * Expiration is defined in terms of the close time of the parent ledger, + * because we definitively know the time that it closed (since consensus + * agrees on time) but we do not know the closing time of the ledger that + * is under construction. + * + * @param view The ledger whose parent time is used as the clock. + * @param exp The optional expiration time we want to check. + * + * @return `true` if `exp` is in the past; `false` otherwise. */ [[nodiscard]] bool hasExpired(ReadView const& view, std::optional const& exp); @@ -83,41 +84,44 @@ using majorityAmendments_t = std::map; [[nodiscard]] majorityAmendments_t getMajorityAmendments(ReadView const& view); -/** Return the hash of a ledger by sequence. - The hash is retrieved by looking up the "skip list" - in the passed ledger. As the skip list is limited - in size, if the requested ledger sequence number is - out of the range of ledgers represented in the skip - list, then std::nullopt is returned. - @return The hash of the ledger with the - given sequence number or std::nullopt. -*/ +/** + * Return the hash of a ledger by sequence. + * The hash is retrieved by looking up the "skip list" + * in the passed ledger. As the skip list is limited + * in size, if the requested ledger sequence number is + * out of the range of ledgers represented in the skip + * list, then std::nullopt is returned. + * @return The hash of the ledger with the + * given sequence number or std::nullopt. + */ [[nodiscard]] std::optional hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal); -/** Find a ledger index from which we could easily get the requested ledger - - The index that we return should meet two requirements: - 1) It must be the index of a ledger that has the hash of the ledger - we are looking for. This means that its sequence must be equal to - greater than the sequence that we want but not more than 256 greater - since each ledger contains the hashes of the 256 previous ledgers. - - 2) Its hash must be easy for us to find. This means it must be 0 mod 256 - because every such ledger is permanently enshrined in a LedgerHashes - page which we can easily retrieve via the skip list. -*/ +/** + * Find a ledger index from which we could easily get the requested ledger + * + * The index that we return should meet two requirements: + * 1) It must be the index of a ledger that has the hash of the ledger + * we are looking for. This means that its sequence must be equal to + * greater than the sequence that we want but not more than 256 greater + * since each ledger contains the hashes of the 256 previous ledgers. + * + * 2) Its hash must be easy for us to find. This means it must be 0 mod 256 + * because every such ledger is permanently enshrined in a LedgerHashes + * page which we can easily retrieve via the skip list. + */ inline LedgerIndex getCandidateLedger(LedgerIndex requested) { return (requested + 255) & (~255); } -/** Return false if the test ledger is provably incompatible - with the valid ledger, that is, they could not possibly - both be valid. Use the first form if you have both ledgers, - use the second form if you have not acquired the valid ledger yet -*/ +/** + * Return false if the test ledger is provably incompatible + * with the valid ledger, that is, they could not possibly + * both be valid. Use the first form if you have both ledgers, + * use the second form if you have not acquired the valid ledger yet + */ [[nodiscard]] bool areCompatible( ReadView const& validLedger, @@ -146,7 +150,8 @@ dirLink( SLE::pointer& object, SF_UINT64 const& node = sfOwnerNode); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -169,7 +174,8 @@ canWithdraw( STAmount const& amount, bool hasDestinationTag); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -191,7 +197,8 @@ canWithdraw( STAmount const& amount, bool hasDestinationTag); -/** Checks that can withdraw funds from an object to itself or a destination. +/** + * Checks that can withdraw funds from an object to itself or a destination. * * The receiver may be either the submitting account (sfAccount) or a different * destination account (sfDestination). @@ -210,8 +217,7 @@ canWithdraw(ReadView const& view, STTx const& tx); [[nodiscard]] TER doWithdraw( - ApplyView& view, - STTx const& tx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, @@ -219,13 +225,15 @@ doWithdraw( STAmount const& amount, beast::Journal j); -/** Deleter function prototype. Returns the status of the entry deletion +/** + * Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status * is always tesSUCCESS if the entry should be skipped. */ using EntryDeleter = std::function(LedgerEntryType, uint256 const&, SLE::pointer&)>; -/** Cleanup owner directory entries on account delete. +/** + * Cleanup owner directory entries on account delete. * Used for a regular and AMM accounts deletion. The caller * has to provide the deleter function, which handles details of * specific account-owned object deletion. @@ -240,12 +248,13 @@ cleanupOnAccountDelete( beast::Journal j, std::optional maxNodesToDelete = std::nullopt); -/** Has the specified time passed? - - @param now the current time - @param mark the cutoff point - @return true if \a now refers to a time strictly after \a mark, else false. -*/ +/** + * Has the specified time passed? + * + * @param now the current time + * @param mark the cutoff point + * @return true if \a now refers to a time strictly after \a mark, else false. + */ bool after(NetClock::time_point now, std::uint32_t mark); diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index c6a2053010..7d41bfce81 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -53,7 +53,8 @@ enum class IsDeposit : bool { No = false, Yes = true }; inline Number const kAMMInvariantRelativeTolerance{1, -11}; -/** Calculate LP Tokens given AMM pool reserves. +/** + * Calculate LP Tokens given AMM pool reserves. * @param asset1 AMM one side of the pool reserve * @param asset2 AMM another side of the pool reserve * @return LP Tokens as IOU @@ -61,7 +62,8 @@ inline Number const kAMMInvariantRelativeTolerance{1, -11}; STAmount ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue); -/** Calculate LP Tokens given asset's deposit amount. +/** + * Calculate LP Tokens given asset's deposit amount. * @param asset1Balance current AMM asset1 balance * @param asset1Deposit requested asset1 deposit amount * @param lptAMMBalance AMM LPT balance @@ -75,10 +77,11 @@ lpTokensOut( STAmount const& lptAMMBalance, std::uint16_t tfee); -/** Calculate asset deposit given LP Tokens. +/** + * Calculate asset deposit given LP Tokens. * @param asset1Balance current AMM asset1 balance - * @param lpTokens LP Tokens * @param lptAMMBalance AMM LPT balance + * @param lpTokens LP Tokens * @param tfee trading fee in basis points * @return */ @@ -89,7 +92,8 @@ ammAssetIn( STAmount const& lpTokens, std::uint16_t tfee); -/** Calculate LP Tokens given asset's withdraw amount. Return 0 +/** + * Calculate LP Tokens given asset's withdraw amount. Return 0 * if can't calculate. * @param asset1Balance current AMM asset1 balance * @param asset1Withdraw requested asset1 withdraw amount @@ -104,7 +108,8 @@ lpTokensIn( STAmount const& lptAMMBalance, std::uint16_t tfee); -/** Calculate asset withdrawal by tokens +/** + * Calculate asset withdrawal by tokens * @param assetBalance balance of the asset being withdrawn * @param lptAMMBalance total AMM Tokens balance * @param lpTokens LP Tokens balance @@ -118,7 +123,8 @@ ammAssetOut( STAmount const& lpTokens, std::uint16_t tfee); -/** Check if the relative distance between the qualities +/** + * Check if the relative distance between the qualities * is within the requested distance. * @param calcQuality calculated quality * @param reqQuality requested quality @@ -137,7 +143,8 @@ withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Nu return ((min.rate() - max.rate()) / min.rate()) < dist; } -/** Check if the relative distance between the amounts +/** + * Check if the relative distance between the amounts * is within the requested distance. * @param calc calculated amount * @param req requested amount @@ -158,13 +165,15 @@ withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist) return ((max - min) / max) < dist; } -/** Solve quadratic equation to find takerGets or takerPays. Round +/** + * Solve quadratic equation to find takerGets or takerPays. Round * to minimize the amount in order to maximize the quality. */ std::optional solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c); -/** Generate AMM offer starting with takerGets when AMM pool +/** + * Generate AMM offer starting with takerGets when AMM pool * from the payment perspective is IOU(in)/XRP(out) * Equations: * Spot Price Quality after the offer is consumed: @@ -231,7 +240,8 @@ getAMMOfferStartWithTakerGets( return amounts; } -/** Generate AMM offer starting with takerPays when AMM pool +/** + * Generate AMM offer starting with takerPays when AMM pool * from the payment perspective is XRP(in)/IOU(out) or IOU(in)/IOU(out). * Equations: * Spot Price Quality after the offer is consumed: @@ -298,7 +308,8 @@ getAMMOfferStartWithTakerPays( return amounts; } -/** Generate AMM offer so that either updated Spot Price Quality (SPQ) +/** + * Generate AMM offer so that either updated Spot Price Quality (SPQ) * is equal to LOB quality (in this case AMM offer quality is * better than LOB quality) or AMM offer is equal to LOB quality * (in this case SPQ is better than LOB quality). @@ -415,7 +426,8 @@ changeSpotPriceQuality( return amounts; } -/** AMM pool invariant - the product (A * B) after swap in/out has to remain +/** + * AMM pool invariant - the product (A * B) after swap in/out has to remain * at least the same: (A + in) * (B - out) >= A * B * XRP round-off may result in a smaller product after swap in/out. * To address this: @@ -427,7 +439,8 @@ changeSpotPriceQuality( * value is increased. */ -/** Swap assetIn into the pool and swap out a proportional amount +/** + * Swap assetIn into the pool and swap out a proportional amount * of the other asset. Implements AMM Swap in. * @see [XLS30d:AMM * Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78) @@ -493,7 +506,8 @@ swapAssetIn(TAmounts const& pool, TIn const& assetIn, std::uint16_t t Number::RoundingMode::Downward); } -/** Swap assetOut out of the pool and swap in a proportional amount +/** + * Swap assetOut out of the pool and swap in a proportional amount * of the other asset. Implements AMM Swap out. * @see [XLS30d:AMM * Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78) @@ -559,12 +573,14 @@ swapAssetOut(TAmounts const& pool, TOut const& assetOut, std::uint16_ Number::RoundingMode::Upward); } -/** Return square of n. +/** + * Return square of n. */ Number square(Number const& n); -/** Adjust LP tokens to deposit/withdraw. +/** + * Adjust LP tokens to deposit/withdraw. * Amount type keeps 16 digits. Maintaining the LP balance by adding * deposited tokens or subtracting withdrawn LP tokens from LP balance * results in losing precision in LP balance. I.e. the resulting LP balance @@ -578,7 +594,8 @@ square(Number const& n); STAmount adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit); -/** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if +/** + * Calls adjustLPTokens() and adjusts deposit or withdraw amounts if * the adjusted LP tokens are less than the provided LP tokens. * @param amountBalance asset1 pool balance * @param amount asset1 to deposit or withdraw @@ -599,7 +616,8 @@ adjustAmountsByLPTokens( std::uint16_t tfee, IsDeposit isDeposit); -/** Positive solution for quadratic equation: +/** + * Positive solution for quadratic equation: * x = (-b + sqrt(b**2 + 4*a*c))/(2*a) */ Number @@ -630,7 +648,8 @@ getAssetRounding(IsDeposit isDeposit) } // namespace detail -/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas +/** + * Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas * calculate the amount as a fractional value of the pool balance. The rounding * takes place on the last step of multiplying the balance by the fraction if * AMMv1_3 is enabled. @@ -654,7 +673,8 @@ getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDe return multiply(balance, frac, rm); } -/** Round AMM single deposit/withdrawal amount. +/** + * Round AMM single deposit/withdrawal amount. * The lambda's are used to delay evaluation until the function * is executed so that the calculation is not done twice. noRoundCb() is * called if AMMv1_3 is disabled. Otherwise, the rounding is set and @@ -671,7 +691,8 @@ getRoundedAsset( std::function const& productCb, IsDeposit isDeposit); -/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas +/** + * Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas * calculate the lptokens as a fractional value of the AMM total lptokens. * The rounding takes place on the last step of multiplying the balance by * the fraction if AMMv1_3 is enabled. The tokens are then @@ -685,7 +706,8 @@ getRoundedLPTokens( Number const& frac, IsDeposit isDeposit); -/** Round AMM single deposit/withdrawal LPToken amount. +/** + * Round AMM single deposit/withdrawal LPToken amount. * The lambda's are used to delay evaluation until the function is executed * so that the calculations are not done twice. * noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set @@ -732,7 +754,8 @@ adjustAssetOutByTokens( STAmount const& tokens, std::uint16_t tfee); -/** Find a fraction of tokens after the tokens are adjusted. The fraction +/** + * Find a fraction of tokens after the tokens are adjusted. The fraction * is used to adjust equal deposit/withdraw amount. */ Number @@ -742,7 +765,8 @@ adjustFracByTokens( STAmount const& tokens, Number const& frac); -/** Get AMM pool balances. +/** + * Get AMM pool balances. */ std::pair ammPoolHolds( @@ -754,7 +778,8 @@ ammPoolHolds( AuthHandling authHandling, beast::Journal const j); -/** Check AMM pool product invariant after an AMM operation that changes LP tokens +/** + * Check AMM pool product invariant after an AMM operation that changes LP tokens * (deposit/withdraw/clawback) from an already calculated pool product mean. * Returns tecPRECISION_LOSS if poolProductMean < newLPTokenBalance beyond the * invariant tolerance, @@ -763,7 +788,8 @@ ammPoolHolds( TER checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance); -/** Check AMM pool product invariant after an AMM operation that changes LP tokens +/** + * Check AMM pool product invariant after an AMM operation that changes LP tokens * (deposit/withdraw/clawback). * Returns tecPRECISION_LOSS if sqrt(asset1 * asset2) < newLPTokenBalance beyond * the invariant tolerance, @@ -778,7 +804,8 @@ checkAMMPrecisionLoss( STAmount const& newLPTokenBalance, beast::Journal const j); -/** Get AMM pool and LP token balances. If both optIssue are +/** + * Get AMM pool and LP token balances. If both optIssue are * provided then they are used as the AMM token pair issues. * Otherwise the missing issues are fetched from ammSle. */ @@ -792,7 +819,8 @@ ammHolds( AuthHandling authHandling, beast::Journal const j); -/** Get the balance of LP tokens. +/** + * Get the balance of LP tokens. */ STAmount ammLPHolds( @@ -810,25 +838,29 @@ ammLPHolds( AccountID const& lpAccount, beast::Journal const j); -/** Get AMM trading fee for the given account. The fee is discounted +/** + * Get AMM trading fee for the given account. The fee is discounted * if the account is the auction slot owner or one of the slot's authorized * accounts. */ std::uint16_t getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account); -/** Returns total amount held by AMM for the given token. +/** + * Returns total amount held by AMM for the given token. */ STAmount ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset); -/** Delete trustlines to AMM. If all trustlines are deleted then +/** + * Delete trustlines to AMM. If all trustlines are deleted then * AMM object and account are deleted. Otherwise tecINCOMPLETE is returned. */ TER deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j); -/** Initialize Auction and Voting slots and set the trading/discounted fee. +/** + * Initialize Auction and Voting slots and set the trading/discounted fee. */ void initializeFeeAuctionVote( @@ -838,14 +870,16 @@ initializeFeeAuctionVote( Asset const& lptAsset, std::uint16_t tfee); -/** Return true if the Liquidity Provider is the only AMM provider, false +/** + * Return true if the Liquidity Provider is the only AMM provider, false * otherwise. Return tecINTERNAL if encountered an unexpected condition, * for instance Liquidity Provider has more than one LPToken trustline. */ std::expected isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount); -/** Due to rounding, the LPTokenBalance of the last LP might +/** + * Due to rounding, the LPTokenBalance of the last LP might * not match the LP's trustline balance. If it's within the tolerance, * update LPTokenBalance to match the LP's trustline balance. */ diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index d0cfc175a3..350fc6ca85 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -14,35 +14,324 @@ #include #include +#include #include #include namespace xrpl { -/** Check if the issuer has the global freeze flag set. - @param issuer The account to check - @return true if the account has global freeze set -*/ +/** + * Check if the issuer has the global freeze flag set. + * @param issuer The account to check + * @return true if the account has global freeze set + */ [[nodiscard]] bool isGlobalFrozen(ReadView const& view, AccountID const& issuer); -// Calculate liquid XRP balance for an account. -// This function may be used to calculate the amount of XRP that -// the holder is able to freely spend. It subtracts reserve requirements. -// -// ownerCountAdj adjusts the owner count in case the caller calculates -// before ledger entries are added or removed. Positive to add, negative -// to subtract. -// -// @param ownerCountAdj positive to add to count, negative to reduce count. +/** + * Calculate liquid XRP balance for an account. + * + * This function may be used to calculate the amount of XRP that + * the holder is able to freely spend. It subtracts reserve requirements. + * + * ownerCountAdj adjusts the owner count in case the caller calculates + * before ledger entries are added or removed. Positive to add, negative + * to subtract. + * + * @param view The ledger view to read from + * @param id The account ID to check + * @param ownerCountAdj Positive to add to count, negative to reduce count + * @param j Journal for logging + * @return The liquid XRP amount available to the account + */ [[nodiscard]] XRPAmount xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j); -/** Adjust the owner count up or down. */ -void -adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j); +struct Adjustment +{ + std::int32_t ownerCountDelta = 0; + std::int32_t accountCountDelta = 0; +}; -/** Returns IOU issuer transfer fee as Rate. Rate specifies +/** + * Returns the account reserve, in drops. + * + * Actual owner count can be adjusted by delta in ownerCountAdj + * Actual reserve count can be adjusted by delta in accountCountAdj + * The reserve is calculated as: + * (ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) * + * increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base" + * + * @param view The ledger view to read from + * @param sle The ledger entry for the account + * @param j Journal for logging + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @return The account reserve amount in drops + */ +[[nodiscard]] XRPAmount +accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj = {}); + +/** + * Convenience overload that accepts AccountID instead of SLE. + * + * @param view The ledger view to read from + * @param id The account ID + * @param j Journal for logging + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @return The account reserve amount in drops + */ +[[nodiscard]] inline XRPAmount +accountReserve(ReadView const& view, AccountID const& id, beast::Journal j, Adjustment adj = {}) +{ + return accountReserve(view, view.read(keylet::account(id)), j, adj); +} + +/** + * Check if an account has sufficient reserve. + * + * @param view The ledger view to read from + * @param tx The transaction being processed + * @param accSle The account's ledger entry + * @param accBalance The account's balance + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to + * subtract. + * @param j Journal for logging (default: null sink) + * @param insufReserveCode The transaction result code to return if the reserve is insufficient + * (default: tecINSUFFICIENT_RESERVE). + * @return Transaction result code + */ +[[nodiscard]] TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + SLE::const_ref sponsorSle, + Adjustment adj, + beast::Journal j, + TER insufReserveCode = tecINSUFFICIENT_RESERVE); + +/** + * Check if an account has sufficient reserve, deriving the sponsor internally. + * + * Equivalent to the overload above, but resolves the sponsor via + * getEffectiveTxReserveSponsor(ctx, accSle) instead of taking it explicitly. Use this + * in the common case where the sponsor is simply the transaction's reserve + * sponsor for accSle. Callers that must force the account's-own-reserve branch + * (passing a null sponsor) or supply a different sponsor should use the + * explicit overload above. + * + * @param ctx The apply-view context (view + tx) + * @param accSle The account's ledger entry + * @param accBalance The account's balance + * @param adj Reserve adjustments (owner/account count deltas) + * @param j Journal for logging (default: null sink) + * @return Transaction result code + */ +[[nodiscard]] TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + Adjustment adj, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + +/** + * Return number of the objects which reserve is covered by the account(sle) (so called "owner + * count"). Actual owner count can be adjusted by delta in ownerCountAdj. + * + * @param sle The account's ledger entry + * @param j Journal for logging + * @param ownerCountAdj Adjustment to the owner count (default: 0) + * @return The adjusted owner count + */ +std::uint32_t +ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj = 0); + +/** + * Increase owner-count fields when the caller supplies the sponsor. + * + * This helper does not create a ledger object. It updates reserve accounting + * after the caller has created/updated an object. + * If sponsorSle is provided, this also adjusts the account's sponsored count + * and the sponsor's sponsoring count. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +void +increaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j); + +/** + * Increase owner-count fields, deriving the tx reserve sponsor internally. + * + * Equivalent to the overload above, but resolves the sponsor via + * getEffectiveTxReserveSponsor(ctx, accountSle) instead of taking it explicitly. Use + * this when the sponsor is the transaction's reserve sponsor for accountSle + * (the common create path). Deletion paths, which derive the sponsor from an + * object's sfSponsor field, should keep using the explicit overload. + * + * @param ctx The apply-view context (view + tx) + * @param accountSle The account's ledger entry + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +void +increaseOwnerCount( + ApplyViewContext ctx, + SLE::ref accountSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of SLE references. + * + * @param view The apply view for making changes + * @param account The account ID + * @param sponsor The optional sponsor account ID + * @param count Amount to add to the owner count + * @param j Journal for logging + */ +inline void +increaseOwnerCount( + ApplyView& view, + AccountID const& account, + std::optional const& sponsor, + std::uint32_t count, + beast::Journal j) +{ + increaseOwnerCount( + view, + view.peek(keylet::account(account)), + sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(), + count, + j); +} + +/** + * Decrease owner-count fields when the caller supplies the sponsor. + * + * This helper does not delete a ledger object. It updates reserve accounting + * after the caller has removed an owner-counted reserve, or for special + * owner-count changes whose sponsor cannot be derived from an object's + * sfSponsor field. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param sponsorSle The sponsor's ledger entry (if applicable) + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +void +decreaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of SLE references. + * + * @param view The apply view for making changes + * @param account The account ID + * @param sponsor The optional sponsor account ID + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +inline void +decreaseOwnerCount( + ApplyView& view, + AccountID const& account, + std::optional const& sponsor, + std::uint32_t count, + beast::Journal j) +{ + decreaseOwnerCount( + view, + view.peek(keylet::account(account)), + sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(), + count, + j); +} + +/** + * Decrease owner-count fields for an existing ledger object. + * + * This helper derives the reserve sponsor from objectSle's sfSponsor field, + * then updates the same owner-count fields as decreaseOwnerCount. Use this + * when removing an existing object whose reserve sponsor is stored on that + * object. + * + * @param view The apply view for making changes + * @param accountSle The account's ledger entry + * @param objectSle The object's ledger entry + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +void +decreaseOwnerCountForObject( + ApplyView& view, + SLE::ref accountSle, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j); + +/** + * Convenience overload that accepts AccountID instead of account SLE reference. + * + * @param view The apply view for making changes + * @param account The account ID + * @param objectSle The object's ledger entry + * @param count Amount to remove from the owner count + * @param j Journal for logging + */ +inline void +decreaseOwnerCountForObject( + ApplyView& view, + AccountID const& account, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j) +{ + SLE::ref accountSle = view.peek(keylet::account(account)); + decreaseOwnerCountForObject(view, accountSle, objectSle, count, j); +} + +/** + * Adjust a LoanBroker's owner count. + * + * A LoanBroker's sfOwnerCount tracks the number of outstanding loans on + * that broker; it is not a reserve-backed owner count and is distinct + * from the broker's pseudo-account's owner count. Loans can never carry a + * reserve sponsor (LoanSet rejects reserve sponsorship at preflight), so + * this never involves sponsor accounting and never invokes the + * ownerCountHook used for ACCOUNT_ROOT reserve tracking. + * + * @param view The apply view for making changes + * @param brokerSle The LoanBroker's ledger entry + * @param delta Amount to add (positive) or remove (negative) from the count + * @param j Journal for logging + */ +void +adjustLoanBrokerOwnerCount( + ApplyView& view, + SLE::ref brokerSle, + std::int32_t delta, + beast::Journal j); + +/** + * Returns IOU issuer transfer fee as Rate. Rate specifies * the fee as fractions of 1 billion. For example, 1% transfer rate * is represented as 1,010,000,000. * @param issuer The IOU issuer @@ -50,35 +339,40 @@ adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Jour [[nodiscard]] Rate transferRate(ReadView const& view, AccountID const& issuer); -/** Generate a pseudo-account address from a pseudo owner key. - @param pseudoOwnerKey The key to generate the address from - @return The generated account ID -*/ +/** + * Generate a pseudo-account address from a pseudo owner key. + * @param pseudoOwnerKey The key to generate the address from + * @return The generated account ID + */ AccountID pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey); -/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account - if set. - - The list is constructed during initialization and is const after that. - Pseudo-account designator fields MUST be maintained by including the - SField::sMD_PseudoAccount flag in the SField definition. -*/ +/** + * Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account + * if set. + * + * The list is constructed during initialization and is const after that. + * Pseudo-account designator fields MUST be maintained by including the + * SField::sMD_PseudoAccount flag in the SField definition. + */ [[nodiscard]] std::vector const& getPseudoAccountFields(); -/** Returns true if and only if sleAcct is a pseudo-account or specific - pseudo-accounts in pseudoFieldFilter. - - Returns false if sleAcct is: - - NOT a pseudo-account OR - - NOT a ltACCOUNT_ROOT OR - - null pointer -*/ +/** + * Returns true if and only if sleAcct is a pseudo-account or specific + * pseudo-accounts in pseudoFieldFilter. + * + * Returns false if sleAcct is: + * - NOT a pseudo-account OR + * - NOT a ltACCOUNT_ROOT OR + * - null pointer + */ [[nodiscard]] bool isPseudoAccount(SLE::const_pointer sleAcct, std::set const& pseudoFieldFilter = {}); -/** Convenience overload that reads the account from the view. */ +/** + * Convenience overload that reads the account from the view. + */ [[nodiscard]] inline bool isPseudoAccount( ReadView const& view, @@ -99,11 +393,12 @@ isPseudoAccount( [[nodiscard]] std::expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField); -/** Checks the destination and tag. - - - Checks that the SLE is not null. - - If the SLE requires a destination tag, checks that there is a tag. -*/ +/** + * Checks the destination and tag. + * + * - Checks that the SLE is not null. + * - If the SLE requires a destination tag, checks that there is a tag. + */ [[nodiscard]] TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag); diff --git a/include/xrpl/ledger/helpers/DirectoryHelpers.h b/include/xrpl/ledger/helpers/DirectoryHelpers.h index a95b9bc95a..25085c4252 100644 --- a/include/xrpl/ledger/helpers/DirectoryHelpers.h +++ b/include/xrpl/ledger/helpers/DirectoryHelpers.h @@ -95,19 +95,20 @@ internalDirFirst( } // namespace detail /** @{ */ -/** Returns the first entry in the directory, advancing the index - - @deprecated These are legacy function that are considered deprecated - and will soon be replaced with an iterator-based model - that is easier to use. You should not use them in new code. - - @param view The view against which to operate - @param root The root (i.e. first page) of the directory to iterate - @param page The current page - @param index The index inside the current page - @param entry The entry at the current index - - @return true if the directory isn't empty; false otherwise +/** + * Returns the first entry in the directory, advancing the index + * + * @deprecated These are legacy function that are considered deprecated + * and will soon be replaced with an iterator-based model + * that is easier to use. You should not use them in new code. + * + * @param view The view against which to operate + * @param root The root (i.e. first page) of the directory to iterate + * @param page The current page + * @param index The index inside the current page + * @param entry The entry at the current index + * + * @return true if the directory isn't empty; false otherwise */ bool cdirFirst( @@ -127,19 +128,20 @@ dirFirst( /** @} */ /** @{ */ -/** Returns the next entry in the directory, advancing the index - - @deprecated These are legacy function that are considered deprecated - and will soon be replaced with an iterator-based model - that is easier to use. You should not use them in new code. - - @param view The view against which to operate - @param root The root (i.e. first page) of the directory to iterate - @param page The current page - @param index The index inside the current page - @param entry The entry at the current index - - @return true if the directory isn't empty; false otherwise +/** + * Returns the next entry in the directory, advancing the index + * + * @deprecated These are legacy function that are considered deprecated + * and will soon be replaced with an iterator-based model + * that is easier to use. You should not use them in new code. + * + * @param view The view against which to operate + * @param root The root (i.e. first page) of the directory to iterate + * @param page The current page + * @param index The index inside the current page + * @param entry The entry at the current index + * + * @return true if the directory isn't empty; false otherwise */ bool cdirNext( @@ -158,16 +160,19 @@ dirNext( uint256& entry); /** @} */ -/** Iterate all items in the given directory. */ +/** + * Iterate all items in the given directory. + */ void forEachItem(ReadView const& view, Keylet const& root, std::function const& f); -/** Iterate all items after an item in the given directory. - @param after The key of the item to start after - @param hint The directory page containing `after` - @param limit The maximum number of items to return - @return `false` if the iteration failed -*/ +/** + * Iterate all items after an item in the given directory. + * @param after The key of the item to start after + * @param hint The directory page containing `after` + * @param limit The maximum number of items to return + * @return `false` if the iteration failed + */ bool forEachItemAfter( ReadView const& view, @@ -177,19 +182,22 @@ forEachItemAfter( unsigned int limit, std::function const& f); -/** Iterate all items in an account's owner directory. */ +/** + * Iterate all items in an account's owner directory. + */ inline void forEachItem(ReadView const& view, AccountID const& id, std::function const& f) { forEachItem(view, keylet::ownerDir(id), f); } -/** Iterate all items after an item in an owner directory. - @param after The key of the item to start after - @param hint The directory page containing `after` - @param limit The maximum number of items to return - @return `false` if the iteration failed -*/ +/** + * Iterate all items after an item in an owner directory. + * @param after The key of the item to start after + * @param hint The directory page containing `after` + * @param limit The maximum number of items to return + * @return `false` if the iteration failed + */ inline bool forEachItemAfter( ReadView const& view, @@ -202,13 +210,16 @@ forEachItemAfter( return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f); } -/** Returns `true` if the directory is empty - @param key The key of the directory -*/ +/** + * Returns `true` if the directory is empty + * @param key The key of the directory + */ [[nodiscard]] bool dirIsEmpty(ReadView const& view, Keylet const& k); -/** Returns a function that sets the owner on a directory SLE */ +/** + * Returns a function that sets the owner on a directory SLE + */ [[nodiscard]] std::function describeOwnerDir(AccountID const& account); diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index d173c33c56..9f54e53769 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -22,17 +23,15 @@ #include #include -#include - namespace xrpl { template TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -43,10 +42,10 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -66,16 +65,26 @@ escrowUnlockApplyHelper( if (receiverIssuer) return tesSUCCESS; - if (!view.exists(trustLineKey) && createAsset) + if (!ctx.view.exists(trustLineKey) && createAsset) { // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + + if (auto const ret = checkReserve( + ctx, + sleDest, + xrpBalance, + *sponsorSle, + {.ownerCountDelta = 1}, + journal, + tecNO_LINE_INSUF_RESERVE); + !isTesSuccess(ret)) { JLOG(journal.trace()) << "Trust line does not exist. " "Insufficient reserve to create line."; - - return tecNO_LINE_INSUF_RESERVE; + return ret; } Currency const currency = issue.currency; @@ -83,7 +92,7 @@ escrowUnlockApplyHelper( initialBalance.get().account = noAccount(); if (TER const ter = trustCreate( - view, // payment sandbox + ctx.view, // payment sandbox recvLow, // is dest low? issuer, // source receiver, // destination @@ -97,19 +106,20 @@ escrowUnlockApplyHelper( Issue(currency, receiver), // limit of zero 0, // quality in 0, // quality out + *sponsorSle, // sponsor journal); // journal !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE } - view.update(sleDest); + ctx.view.update(sleDest); } - if (!view.exists(trustLineKey) && !receiverIssuer) + if (!ctx.view.exists(trustLineKey) && !receiverIssuer) return tecNO_LINE; - auto const xferRate = transferRate(view, amount); + auto const xferRate = transferRate(ctx.view, amount); // update if issuer rate is less than locked rate if (xferRate < lockedRate) lockedRate = xferRate; @@ -137,7 +147,7 @@ escrowUnlockApplyHelper( // of the funds if (!createAsset) { - auto const sleRippleState = view.peek(trustLineKey); + auto const sleRippleState = ctx.view.peek(trustLineKey); if (!sleRippleState) return tecINTERNAL; // LCOV_EXCL_LINE @@ -163,7 +173,7 @@ escrowUnlockApplyHelper( // if destination is not the issuer then transfer funds if (!receiverIssuer) { - auto const ter = directSendNoFee(view, issuer, receiver, finalAmt, true, journal); + auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE } @@ -173,10 +183,10 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyView& view, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, - STAmount const& xrpBalance, + XRPAmount xrpBalance, STAmount const& amount, AccountID const& issuer, AccountID const& sender, @@ -189,27 +199,32 @@ escrowUnlockApplyHelper( auto const mptID = amount.get().getMptID(); auto const issuanceKey = keylet::mptokenIssuance(mptID); - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer) + auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver); + if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer) { - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - return tecINSUFFICIENT_RESERVE; - } + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE - if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter)) + if (auto const ret = checkReserve( + ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return ret; + + if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0); + !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE } // update owner count. - adjustOwnerCount(view, sleDest, 1, journal); + increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal); } - if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer) + if (!ctx.view.exists(mptKeylet) && !receiverIssuer) return tecNO_PERMISSION; - auto const xferRate = transferRate(view, amount); + auto const xferRate = transferRate(ctx.view, amount); // update if issuer rate is less than locked rate if (xferRate < lockedRate) lockedRate = xferRate; @@ -232,11 +247,11 @@ escrowUnlockApplyHelper( finalAmt = amount.value() - xferFee; } return unlockEscrowMPT( - view, + ctx.view, sender, receiver, finalAmt, - view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt, + ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt, journal); } diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 873abae272..e2605e9ab7 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -63,7 +63,9 @@ static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60; Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval); -/// Ensure the periodic payment is always rounded consistently +/** + * Ensure the periodic payment is always rounded consistently + */ inline Number roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale) { @@ -127,7 +129,8 @@ struct LoanPaymentParts operator==(LoanPaymentParts const& other) const; }; -/** This structure captures the parts of a loan state. +/** + * This structure captures the parts of a loan state. * * Whether the values are theoretical (unrounded) or rounded will depend on how * it was computed. @@ -324,12 +327,14 @@ struct PaymentComponents // - extra: An additional payment beyond the regular schedule (overpayment) PaymentSpecialCase specialCase = PaymentSpecialCase::None; - // Calculates the tracked interest portion of this payment. - // This is derived from the other components as: - // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta - // - // @return The amount of tracked interest included in this payment that - // will be paid to the vault. + /** + * Calculates the tracked interest portion of this payment. + * This is derived from the other components as: + * trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta + * + * @return The amount of tracked interest included in this payment that + * will be paid to the vault. + */ [[nodiscard]] Number trackedInterestPart() const; }; @@ -401,7 +406,8 @@ struct LoanStateDeltas // The difference in management fee outstanding between two loan states. Number managementFee; - /* Calculates the total change across all components. + /** + * Calculates the total change across all components. * @return The sum of principal, interest, and management fee deltas. */ [[nodiscard]] Number diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index a725871231..5418e5b26a 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -29,15 +29,17 @@ namespace xrpl { [[nodiscard]] bool isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue); -/** Returns true if @p account's MPToken for @p mptIssue carries the - * individual-lock flag (lsfMPTLocked). +/** + * Returns true if @p account's MPToken for @p mptIssue carries the + * individual-lock flag (lsfMPTLocked). * - * @warning This checks only the raw per-holder lock bit. It does **not** - * perform the transitive vault pseudo-account check: if @p mptIssue is a - * vault share whose underlying asset is frozen, this function returns false. - * Call @ref isFrozen instead when determining whether an account may send or - * receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and - * isVaultPseudoAccountFrozen into a single complete check. */ + * @warning This checks only the raw per-holder lock bit. It does **not** + * perform the transitive vault pseudo-account check: if @p mptIssue is a + * vault share whose underlying asset is frozen, this function returns false. + * Call @ref isFrozen instead when determining whether an account may send or + * receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and + * isVaultPseudoAccountFrozen into a single complete check. + */ [[nodiscard]] bool isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); @@ -61,7 +63,8 @@ isAnyFrozen( // //------------------------------------------------------------------------------ -/** Returns MPT transfer fee as Rate. Rate specifies +/** + * Returns MPT transfer fee as Rate. Rate specifies * the fee as fractions of 1 billion. For example, 1% transfer rate * is represented as 1,010,000,000. * @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object @@ -86,7 +89,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue); [[nodiscard]] TER authorizeMPToken( - ApplyView& view, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -94,7 +97,8 @@ authorizeMPToken( std::uint32_t flags = 0, std::optional holderID = std::nullopt); -/** Check if the account lacks required authorization for MPT. +/** + * Check if the account lacks required authorization for MPT. * * requireAuth check is recursive for MPT shares in a vault, descending to * assets in the vault, up to maxAssetCheckDepth recursion depth. This is @@ -109,7 +113,8 @@ requireAuth( AuthType authType = AuthType::Legacy, std::uint8_t depth = 0); -/** Enforce account has MPToken to match its authorization. +/** + * Enforce account has MPToken to match its authorization. * * Called from doApply - it will check for expired (and delete if found any) * credentials matching DomainID set in MPTokenIssuance. Must be called if @@ -117,50 +122,52 @@ requireAuth( */ [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyView& view, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, beast::Journal j); -/** Resolve the underlying asset of a vault share. +/** + * Resolve the underlying asset of a vault share. * - * Reads sfReferenceHolding from @p sleShareIssuance to determine which - * asset the vault wraps. @p sleHolding must be the SLE that - * sfReferenceHolding points to — either an ltMPTOKEN (returns its - * MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue). + * Reads sfReferenceHolding from @p sleShareIssuance to determine which + * asset the vault wraps. @p sleHolding must be the SLE that + * sfReferenceHolding points to — either an ltMPTOKEN (returns its + * MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue). * - * @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN - * or ltRIPPLE_STATE. Passing any other type is undefined behaviour. - * @param sleShareIssuance MPTokenIssuance SLE for the vault share token. - * @param sleHolding SLE referenced by sfReferenceHolding. - * @return The underlying Asset (MPTIssue or Issue). + * @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN + * or ltRIPPLE_STATE. Passing any other type is undefined behaviour. + * @param sleShareIssuance MPTokenIssuance SLE for the vault share token. + * @param sleHolding SLE referenced by sfReferenceHolding. + * @return The underlying Asset (MPTIssue or Issue). */ [[nodiscard]] Asset assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding); -/** Check whether @p to may receive the given MPT from @p from. +/** + * Check whether @p to may receive the given MPT from @p from. * - * The check passes when any of the following is true: - * - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or - * - @p from or @p to is the issuer, or - * - lsfMPTCanTransfer is set on the MPTokenIssuance. + * The check passes when any of the following is true: + * - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or + * - @p from or @p to is the issuer, or + * - lsfMPTCanTransfer is set on the MPTokenIssuance. * - * For vault shares (MPTokenIssuances that carry sfReferenceHolding) the - * check recurses into the underlying asset's transferability. This - * recursion is defensive; vault-of-vault-shares is rejected at vault - * creation, so in practice depth never exceeds 1. + * For vault shares (MPTokenIssuances that carry sfReferenceHolding) the + * check recurses into the underlying asset's transferability. This + * recursion is defensive; vault-of-vault-shares is rejected at vault + * creation, so in practice depth never exceeds 1. * - * @param view Ledger state to read from. - * @param mptIssue The MPT issuance being transferred. - * @param from Sending account. - * @param to Receiving account. - * @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer - * check. Use for recovery paths (e.g. unwinding SAV or - * Lending Protocol positions after an issuer revokes - * transferability). - * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. - * @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise. + * @param view Ledger state to read from. + * @param mptIssue The MPT issuance being transferred. + * @param from Sending account. + * @param to Receiving account. + * @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer + * check. Use for recovery paths (e.g. unwinding SAV or + * Lending Protocol positions after an issuer revokes + * transferability). + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise. */ [[nodiscard]] TER canTransfer( @@ -171,22 +178,24 @@ canTransfer( WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No, std::uint8_t depth = 0); -/** Check whether @p asset may be traded on the DEX. +/** + * Check whether @p asset may be traded on the DEX. * - * For IOU assets the check delegates to the existing offer/AMM freeze - * logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance. - * Vault shares recurse into the underlying asset's tradability via - * sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth. + * For IOU assets the check delegates to the existing offer/AMM freeze + * logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance. + * Vault shares recurse into the underlying asset's tradability via + * sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth. * - * @param view Ledger state to read from. - * @param asset The asset to check. - * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. - * @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise. + * @param view Ledger state to read from. + * @param asset The asset to check. + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise. */ [[nodiscard]] TER canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0); -/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue. +/** + * Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue. */ [[nodiscard]] TER canMPTTradeAndTransfer( @@ -203,7 +212,7 @@ canMPTTradeAndTransfer( [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -211,7 +220,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); @@ -243,6 +252,7 @@ createMPToken( ApplyView& view, MPTID const& mptIssuanceID, AccountID const& account, + SLE::ref sponsorSle, std::uint32_t const flags); TER @@ -250,6 +260,7 @@ checkCreateMPT( xrpl::ApplyView& view, xrpl::MPTIssue const& mptIssue, xrpl::AccountID const& holder, + SLE::ref sponsorSle, beast::Journal j); //------------------------------------------------------------------------------ @@ -270,7 +281,8 @@ availableMPTAmount(SLE const& sleIssuance); std::int64_t availableMPTAmount(ReadView const& view, MPTID const& mptID); -/** Checks for two types of OutstandingAmount overflow during a send operation. +/** + * Checks for two types of OutstandingAmount overflow during a send operation. * 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when * `OutstandingAmount > MaximumAmount`. This threshold is used for direct * directSendNoFee transactions that bypass the payment engine. @@ -295,7 +307,8 @@ isMPTOverflow( [[nodiscard]] STAmount issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue); -/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. +/** + * Facilitate tracking of MPT sold by an issuer owning MPT sell offer. * See ApplyView::issuerSelfDebitHookMPT(). */ void diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 1c4d395fbe..d9d195c559 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -24,19 +24,25 @@ namespace xrpl::nft { -/** Delete up to a specified number of offers from the specified token offer - * directory. */ +/** + * Delete up to a specified number of offers from the specified token offer + * directory. + */ std::size_t removeTokenOffersWithLimit( ApplyView& view, Keylet const& directory, std::size_t maxDeletableOffers); -/** Finds the specified token in the owner's token directory. */ +/** + * Finds the specified token in the owner's token directory. + */ std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID); -/** Finds the token in the owner's token directory. Returns token and page. */ +/** + * Finds the token in the owner's token directory. Returns token and page. + */ struct TokenAndPage { STObject token; @@ -49,33 +55,39 @@ struct TokenAndPage std::optional findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftokenID); -/** Insert the token in the owner's token directory. */ +/** + * Insert the token in the owner's token directory. + */ TER insertToken(ApplyView& view, AccountID owner, STObject&& nft); -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID); TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref page); -/** Deletes the given token offer. - - An offer is tracked in two separate places: - - The token's 'buy' directory, if it's a buy offer; or - - The token's 'sell' directory, if it's a sell offer; and - - The owner directory of the account that placed the offer. - - The offer also consumes one incremental reserve. +/** + * Deletes the given token offer. + * + * An offer is tracked in two separate places: + * - The token's 'buy' directory, if it's a buy offer; or + * - The token's 'sell' directory, if it's a sell offer; and + * - The owner directory of the account that placed the offer. + * + * The offer also consumes one incremental reserve. */ bool deleteTokenOffer(ApplyView& view, SLE::ref offer); -/** Repairs the links in an NFTokenPage directory. - - Returns true if a repair took place, otherwise false. -*/ +/** + * Repairs the links in an NFTokenPage directory. + * + * Returns true if a repair took place, otherwise false. + */ bool repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner); @@ -89,7 +101,9 @@ changeTokenURI( uint256 const& nftokenID, std::optional const& uri); -/** Preflight checks shared by NFTokenCreateOffer and NFTokenMint */ +/** + * Preflight checks shared by NFTokenCreateOffer and NFTokenMint + */ NotTEC tokenOfferCreatePreflight( AccountID const& acctID, @@ -101,7 +115,9 @@ tokenOfferCreatePreflight( std::optional const& owner = std::nullopt, std::uint32_t txFlags = tfSellNFToken); -/** Preclaim checks shared by NFTokenCreateOffer and NFTokenMint */ +/** + * Preclaim checks shared by NFTokenCreateOffer and NFTokenMint + */ TER tokenOfferCreatePreclaim( ReadView const& view, @@ -115,7 +131,9 @@ tokenOfferCreatePreclaim( std::optional const& owner = std::nullopt, std::uint32_t txFlags = tfSellNFToken); -/** doApply implementation shared by NFTokenCreateOffer and NFTokenMint */ +/** + * doApply implementation shared by NFTokenCreateOffer and NFTokenMint + */ TER tokenOfferCreateApply( ApplyView& view, diff --git a/include/xrpl/ledger/helpers/OfferHelpers.h b/include/xrpl/ledger/helpers/OfferHelpers.h index fc863dff0a..524288ea33 100644 --- a/include/xrpl/ledger/helpers/OfferHelpers.h +++ b/include/xrpl/ledger/helpers/OfferHelpers.h @@ -7,18 +7,19 @@ namespace xrpl { -/** Delete an offer. - - Requirements: - The offer must exist. - The caller must have already checked permissions. - - @param view The ApplyView to modify. - @param sle The offer to delete. - @param j Journal for logging. - - @return tesSUCCESS on success, otherwise an error code. -*/ +/** + * Delete an offer. + * + * Requirements: + * The offer must exist. + * The caller must have already checked permissions. + * + * @param view The ApplyView to modify. + * @param sle The offer to delete. + * @param j Journal for logging. + * + * @return tesSUCCESS on success, otherwise an error code. + */ // [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile. TER offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j); diff --git a/include/xrpl/ledger/helpers/OracleHelpers.h b/include/xrpl/ledger/helpers/OracleHelpers.h new file mode 100644 index 0000000000..da04618e46 --- /dev/null +++ b/include/xrpl/ledger/helpers/OracleHelpers.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include // IWYU pragma: keep +#include + +#include +#include + +namespace xrpl { + +constexpr std::uint32_t kMinOracleReserveCount = 1; +constexpr std::uint32_t kMaxOracleReserveCount = 2; +constexpr std::size_t kOracleReserveCountThreshold = 5; + +template + requires requires(T const& t) { t.size(); } +inline std::uint32_t +calculateOracleReserve(T const& priceDataSeries) +{ + return priceDataSeries.size() > kOracleReserveCountThreshold ? kMaxOracleReserveCount + : kMinOracleReserveCount; +} + +inline std::uint32_t +calculateOracleReserve(SLE::const_ref oracleSle) +{ + return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries)); +} + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h index 6e8cd17f7f..5e1f590c58 100644 --- a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h +++ b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h @@ -12,37 +12,40 @@ namespace xrpl { -/** Close a payment channel and return its remaining funds to the channel owner. +/** + * Close a payment channel and return its remaining funds to the channel owner. * - * @param slep The SLE for the PayChannel object to close. - * @param view The apply view in which ledger state modifications are made. - * @param key The ledger key identifying the PayChannel entry. - * @param j Journal used for fatal-level diagnostic messages. - * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal - * fails; tefINTERNAL if the source account SLE cannot be found. + * @param slep The SLE for the PayChannel object to close. + * @param view The apply view in which ledger state modifications are made. + * @param key The ledger key identifying the PayChannel entry. + * @param j Journal used for fatal-level diagnostic messages. + * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal + * fails; tefINTERNAL if the source account SLE cannot be found. */ TER closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j); -/** Add two uint32_t values with saturation at UINT32_MAX. +/** + * Add two uint32_t values with saturation at UINT32_MAX. * - * @param rules The current ledger rules used to check amendment status. - * @param lhs Left-hand operand. - * @param rhs Right-hand operand. - * @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment - * is active. + * @param rules The current ledger rules used to check amendment status. + * @param lhs Left-hand operand. + * @param rhs Right-hand operand. + * @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment + * is active. */ uint32_t saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs); -/** Determine whether a payment channel time field represents an expired time. +/** + * Determine whether a payment channel time field represents an expired time. * - * @param view The apply view providing the parent close time and rules. - * @param timeField The optional expiry timestamp (seconds since the XRP - * Ledger epoch). If empty, the function returns false. - * @return @c true if @p timeField is set and the indicated time is - * in the past relative to the view's parent close time; - * @c false otherwise. + * @param view The apply view providing the parent close time and rules. + * @param timeField The optional expiry timestamp (seconds since the XRP + * Ledger epoch). If empty, the function returns false. + * @return @c true if @p timeField is set and the indicated time is + * in the past relative to the view's parent close time; + * @c false otherwise. */ bool isChannelExpired(ApplyView const& view, std::optional timeField); diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index ab09b931cc..a0508d074f 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -31,13 +31,14 @@ namespace xrpl { // //------------------------------------------------------------------------------ -/** Calculate the maximum amount of IOUs that an account can hold - @param view the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. - @return The maximum amount that can be held. -*/ +/** + * Calculate the maximum amount of IOUs that an account can hold + * @param view the ledger to check against. + * @param account the account of interest. + * @param issuer the issuer of the IOU. + * @param currency the IOU to check. + * @return The maximum amount that can be held. + */ /** @{ */ STAmount creditLimit( @@ -50,12 +51,13 @@ IOUAmount creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur); /** @} */ -/** Returns the amount of IOUs issued by issuer that are held by an account - @param view the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. -*/ +/** + * Returns the amount of IOUs issued by issuer that are held by an account + * @param view the ledger to check against. + * @param account the account of interest. + * @param issuer the issuer of the IOU. + * @param currency the IOU to check. + */ /** @{ */ STAmount creditBalance( @@ -134,10 +136,11 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& iss // //------------------------------------------------------------------------------ -/** Create a trust line - - This can set an initial balance. -*/ +/** + * Create a trust line + * + * This can set an initial balance. + */ [[nodiscard]] TER trustCreate( ApplyView& view, @@ -156,6 +159,7 @@ trustCreate( // Issuer should be the account being set. std::uint32_t uQualityIn, std::uint32_t uQualityOut, + SLE::ref sponsorSle, beast::Journal j); [[nodiscard]] TER @@ -178,6 +182,7 @@ issueIOU( AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j); [[nodiscard]] TER @@ -194,7 +199,8 @@ redeemIOU( // //------------------------------------------------------------------------------ -/** Check if the account lacks required authorization. +/** + * Check if the account lacks required authorization. * * Return tecNO_AUTH or tecNO_LINE if it does * and tesSUCCESS otherwise. @@ -218,7 +224,8 @@ requireAuth( AccountID const& account, AuthType authType = AuthType::Legacy); -/** Check if the destination account is allowed +/** + * Check if the destination account is allowed * to receive IOU. Return terNO_RIPPLE if rippling is * disabled on both sides and tesSUCCESS otherwise. */ @@ -231,11 +238,13 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc // //------------------------------------------------------------------------------ -/// Any transactors that call addEmptyHolding() in doApply must call -/// canAddHolding() in preflight with the same View and Asset +/** + * Any transactors that call addEmptyHolding() in doApply must call + * canAddHolding() in preflight with the same View and Asset + */ [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -243,12 +252,13 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal); -/** Delete trustline to AMM. The passed `sle` must be obtained from a prior +/** + * Delete trustline to AMM. The passed `sle` must be obtained from a prior * call to view.peek(). Fail if neither side of the trustline is AMM or * if ammAccountID is seated and is not one of the trustline's side. */ @@ -259,7 +269,8 @@ deleteAMMTrustLine( std::optional const& ammAccountID, beast::Journal j); -/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior +/** + * Delete AMMs MPToken. The passed `sle` must be obtained from a prior * call to view.peek(). */ [[nodiscard]] TER diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h new file mode 100644 index 0000000000..98bf419140 --- /dev/null +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +/** + * Whether the given transaction type may use reserve sponsorship (v1). + * + * Reserve sponsorship is restricted to an explicit allow-list of transaction + * types; all others reject spfSponsorReserve at preflight. + */ +bool +isReserveSponsorAllowed(TxType txType); + +/** + * Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set). + */ +inline bool +isFeeSponsored(STTx const& tx) +{ + return tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u); +} + +/** + * Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set). + */ +inline bool +isReserveSponsored(STTx const& tx) +{ + return tx.isFieldPresent(sfSponsor) && + ((tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u); +} + +/** + * Return the AccountID of the transaction's reserve sponsor, or nullopt if unsponsored. + */ +std::optional +getTxReserveSponsorID(STTx const& tx); + +/** + * Return a mutable SLE for the transaction's reserve sponsor account. + * + * @param ctx The apply-view context (view + tx) + * @return The sponsor account SLE, a null pointer if the tx is not + * reserve-sponsored, or tecINTERNAL if the sponsor account cannot + * be loaded (an already-checked invariant). + */ +std::expected +getTxReserveSponsor(ApplyViewContext ctx); + +/** + * Return a read-only SLE for the transaction's reserve sponsor account. + * + * @param view The ledger read view + * @param tx The transaction to inspect + * @return The sponsor account SLE, a null pointer if the tx is not + * reserve-sponsored, or tecINTERNAL if the sponsor account cannot + * be loaded (an already-checked invariant). + */ +std::expected +getTxReserveSponsor(ReadView const& view, STTx const& tx); + +/** + * The transaction's reserve sponsor for the given account, if applicable. + * + * A reserve sponsor only covers the transaction submitter's own objects, so + * this returns the tx reserve sponsor SLE only when accountSle is the tx's own + * (non-pseudo) account; otherwise it returns a null sponsor pointer. This is + * the single source of truth for the "sponsor applies to tx.Account only" rule + * that the sponsor-deriving helper overloads in AccountRootHelpers rely on. + * + * @param ctx The apply-view context (view + tx) + * @param accountSle The account whose sponsor is being resolved + * @return The sponsor SLE (nullptr if unsponsored), or tecINTERNAL if the + * sponsor account cannot be loaded (an already-checked invariant) + */ +[[nodiscard]] std::expected +getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle); + +/** + * Return the AccountID stored in the given sponsor field of a ledger entry, or nullopt if absent. + */ +std::optional +getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Return a mutable SLE for the reserve sponsor recorded on a ledger entry. + * + * Reads the sponsor AccountID from @p field on @p sle and peeks the + * corresponding account root in @p view. + * + * @param view The mutable apply view + * @param sle The ledger entry whose sponsor field is inspected + * @param field The field that holds the sponsor AccountID (defaults to sfSponsor) + * @return The sponsor account SLE, or a null pointer if the entry is unsponsored. + */ +SLE::pointer +getLedgerEntryReserveSponsor( + ApplyView& view, + SLE::const_ref sle, + SF_ACCOUNT const& field = sfSponsor); + +/** + * Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE. + * + * Sets @p field on @p sle to the AccountID from @p sponsorSle. A no-op when + * @p sponsorSle is null (unsponsored). For RippleState entries the field must + * be sfHighSponsor or sfLowSponsor; for all other entry types it must be + * sfSponsor. + * + * @param sle The ledger entry to stamp + * @param sponsorSle The sponsor's account root SLE (null → no-op) + * @param field The sponsor field to set (defaults to sfSponsor) + */ +void +addSponsorToLedgerEntry( + SLE::ref sle, + SLE::const_ref sponsorSle, + SF_ACCOUNT const& field = sfSponsor); + +/** + * Stamp the transaction's reserve sponsor onto a newly-created ledger entry. + * + * Equivalent to the overload above, but resolves the sponsor via + * getTxReserveSponsor(ctx) instead of taking it explicitly. A no-op when the + * transaction is not reserve-sponsored. The entry is assumed to be owned by + * the transaction submitter, which is the only account a tx reserve sponsor + * can cover. + */ +void +addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Remove the reserve sponsor field from a ledger entry. + * + * A no-op when @p field is not present on @p sle. For RippleState entries + * the field must be sfHighSponsor or sfLowSponsor; for all other entry types + * it must be sfSponsor. + * + * @param sle The ledger entry to modify + * @param field The sponsor field to clear (defaults to sfSponsor) + */ +void +removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor); + +/** + * Whether @p account is the owner of a ledger entry for sponsorship purposes. + * + * Ownership rules vary by entry type. For RippleState entries the owner is + * whichever side of the trust line holds the reserve. For credentials, the + * owner is the subject once accepted and the issuer before acceptance. + * + * @param view The ledger read view (used for SignerList lookup) + * @param sle The ledger entry whose owner is checked + * @param account The candidate account to match against + * @return true if @p account owns @p sle, false otherwise. + */ +bool +isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account); + +/** + * Whether this ledger entry type can have a reserve sponsor attached to it. + */ +bool +isLedgerEntrySupportedBySponsorship(SLE const& sle); + +/** + * Return the number of owner-count units the ledger entry consumes. + * + * Most entries cost 1. Exceptions: Oracles scale with their price-data series + * size, Vaults cost 2 (vault + pseudo-account), and legacy SignerList entries + * (pre-MultiSignReserve) cost 2 + signer count. + */ +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle); + +/** + * Return the SField used to store the reserve sponsor for @p owner on @p sle. + * + * For most entry types this is sfSponsor. RippleState entries use + * sfHighSponsor or sfLowSponsor depending on which side of the trust line + * @p owner holds. + * + * @param sle The ledger entry + * @param owner The account whose sponsor field is needed + * @return sfHighSponsor, sfLowSponsor, or sfSponsor as appropriate. + */ +SF_ACCOUNT const& +getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner); + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 32f785a0d6..501101136a 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,21 +28,30 @@ namespace xrpl { // //------------------------------------------------------------------------------ -/** Controls the treatment of frozen account balances */ +/** + * Controls the treatment of frozen account balances + */ enum class FreezeHandling { IgnoreFreeze, ZeroIfFrozen }; -/** Controls the treatment of unauthorized MPT balances */ +/** + * Controls the treatment of unauthorized MPT balances + */ enum class AuthHandling { IgnoreAuth, ZeroIfUnauthorized }; -/** Controls whether to include the account's full spendable balance */ +/** + * Controls whether to include the account's full spendable balance + */ enum class SpendableHandling { SimpleBalance, FullBalance }; enum class WaiveTransferFee : bool { No = false, Yes }; -/** Controls whether accountSend is allowed to overflow OutstandingAmount **/ +/** + * Controls whether accountSend is allowed to overflow OutstandingAmount * + */ enum class AllowMPTOverflow : bool { No = false, Yes }; -/** Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs. +/** + * Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs. * * Default is No (enforce). Use Yes at call sites that must remain available * even when an MPT issuer has cleared lsfMPTCanTransfer - for example, @@ -80,9 +90,9 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset); /** - * isFrozen check is recursive for MPT shares in a vault, descending to - * assets in the vault, up to maxAssetCheckDepth recursion depth. This is - * purely defensive, as we currently do not allow such vaults to be created. + * isFrozen check is recursive for MPT shares in a vault, descending to + * assets in the vault, up to maxAssetCheckDepth recursion depth. This is + * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool isFrozen( @@ -121,9 +131,9 @@ isDeepFrozen( std::uint8_t depth = 0); /** - * isFrozen check is recursive for MPT shares in a vault, descending to - * assets in the vault, up to maxAssetCheckDepth recursion depth. This is - * purely defensive, as we currently do not allow such vaults to be created. + * isFrozen check is recursive for MPT shares in a vault, descending to + * assets in the vault, up to maxAssetCheckDepth recursion depth. This is + * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool isDeepFrozen( @@ -284,7 +294,8 @@ accountFunds( AuthHandling authHandling, beast::Journal j); -/** Returns the transfer fee as Rate based on the type of token +/** + * Returns the transfer fee as Rate based on the type of token * @param view The ledger view * @param amount The amount to transfer */ @@ -302,7 +313,7 @@ canAddHolding(ReadView const& view, Asset const& asset); [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -310,7 +321,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal); @@ -349,7 +360,8 @@ canTransfer( // --> bCheckIssuer : normally require issuer to be involved. // [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles. -/** Calls static directSendNoFeeIOU if saAmount represents Issue. +/** + * Calls static directSendNoFeeIOU if saAmount represents Issue. * Calls static directSendNoFeeMPT if saAmount represents MPTIssue. */ TER @@ -361,7 +373,8 @@ directSendNoFee( bool bCheckIssuer, beast::Journal j); -/** Calls static accountSendIOU if saAmount represents Issue. +/** + * Calls static accountSendIOU if saAmount represents Issue. * Calls static accountSendMPT if saAmount represents MPTIssue. */ [[nodiscard]] TER @@ -371,11 +384,13 @@ accountSend( AccountID const& to, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle = {}, WaiveTransferFee waiveFee = WaiveTransferFee::No, AllowMPTOverflow allowOverflow = AllowMPTOverflow::No); using MultiplePaymentDestinations = std::vector>; -/** Like accountSend, except one account is sending multiple payments (with the +/** + * Like accountSend, except one account is sending multiple payments (with the * same asset!) simultaneously * * Calls static accountSendMultiIOU if saAmount represents Issue. diff --git a/include/xrpl/ledger/helpers/VaultHelpers.h b/include/xrpl/ledger/helpers/VaultHelpers.h index 2344b4de77..1bd1663314 100644 --- a/include/xrpl/ledger/helpers/VaultHelpers.h +++ b/include/xrpl/ledger/helpers/VaultHelpers.h @@ -9,57 +9,63 @@ namespace xrpl { -/** From the perspective of a vault, return the number of shares to give - depositor when they offer a fixed amount of assets. Note, since shares are - MPT, this number is integral and always truncated in this calculation. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param assets The amount of assets to convert. - - @return The number of shares, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of shares to give + * depositor when they offer a fixed amount of assets. Note, since shares are + * MPT, this number is integral and always truncated in this calculation. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param assets The amount of assets to convert. + * + * @return The number of shares, or nullopt on error. + */ [[nodiscard]] std::optional assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets); -/** From the perspective of a vault, return the number of assets to take from - depositor when they receive a fixed amount of shares. Note, since shares are - MPT, they are always an integral number. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param shares The amount of shares to convert. - - @return The number of assets, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of assets to take from + * depositor when they receive a fixed amount of shares. Note, since shares are + * MPT, they are always an integral number. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param shares The amount of shares to convert. + * + * @return The number of assets, or nullopt on error. + */ [[nodiscard]] std::optional sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares); -/** Controls whether to truncate shares instead of rounding. */ +/** + * Controls whether to truncate shares instead of rounding. + */ enum class TruncateShares : bool { No = false, Yes = true }; -/** Controls whether the withdraw conversion helpers - (assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract - sfLossUnrealized from sfAssetsTotal before computing the exchange rate. - The default (No) applies the standard discounted rate; Yes is used when - the redeemer is the sole remaining shareholder. -*/ +/** + * Controls whether the withdraw conversion helpers + * (assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract + * sfLossUnrealized from sfAssetsTotal before computing the exchange rate. + * The default (No) applies the standard discounted rate; Yes is used when + * the redeemer is the sole remaining shareholder. + */ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true }; -/** From the perspective of a vault, return the number of shares to demand from - the depositor when they ask to withdraw a fixed amount of assets. Since - shares are MPT this number is integral, and it will be rounded to nearest - unless explicitly requested to be truncated instead. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param assets The amount of assets to convert. - @param truncate Whether to truncate instead of rounding. - @param waive Whether to waive the unrealized-loss discount when computing - the exchange rate. - - @return The number of shares, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of shares to demand from + * the depositor when they ask to withdraw a fixed amount of assets. Since + * shares are MPT this number is integral, and it will be rounded to nearest + * unless explicitly requested to be truncated instead. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param assets The amount of assets to convert. + * @param truncate Whether to truncate instead of rounding. + * @param waive Whether to waive the unrealized-loss discount when computing + * the exchange rate. + * + * @return The number of shares, or nullopt on error. + */ [[nodiscard]] std::optional assetsToSharesWithdraw( SLE::const_ref vault, @@ -68,18 +74,19 @@ assetsToSharesWithdraw( TruncateShares truncate = TruncateShares::No, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); -/** From the perspective of a vault, return the number of assets to give the - depositor when they redeem a fixed amount of shares. Note, since shares are - MPT, they are always an integral number. - - @param vault The vault SLE. - @param issuance The MPTokenIssuance SLE for the vault's shares. - @param shares The amount of shares to convert. - @param waive Whether to waive (i.e. not subtract) the vault's unrealized - loss when computing the exchange rate. - - @return The number of assets, or nullopt on error. -*/ +/** + * From the perspective of a vault, return the number of assets to give the + * depositor when they redeem a fixed amount of shares. Note, since shares are + * MPT, they are always an integral number. + * + * @param vault The vault SLE. + * @param issuance The MPTokenIssuance SLE for the vault's shares. + * @param shares The amount of shares to convert. + * @param waive Whether to waive (i.e. not subtract) the vault's unrealized + * loss when computing the exchange rate. + * + * @return The number of assets, or nullopt on error. + */ [[nodiscard]] std::optional sharesToAssetsWithdraw( SLE::const_ref vault, @@ -87,15 +94,16 @@ sharesToAssetsWithdraw( STAmount const& shares, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); -/** Returns true iff `account` holds all of the vault's outstanding shares — - i.e. is the sole remaining shareholder. Returns false if the account - holds no shares or fewer than the total outstanding. - - @param view The ledger view. - @param account The candidate sole shareholder. - @param issuance The MPTokenIssuance SLE for the vault's shares; provides - both the share MPTID and the outstanding-amount total. -*/ +/** + * Returns true iff `account` holds all of the vault's outstanding shares — + * i.e. is the sole remaining shareholder. Returns false if the account + * holds no shares or fewer than the total outstanding. + * + * @param view The ledger view. + * @param account The candidate sole shareholder. + * @param issuance The MPTokenIssuance SLE for the vault's shares; provides + * both the share MPTID and the outstanding-amount total. + */ [[nodiscard]] bool isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance); diff --git a/include/xrpl/net/HTTPClient.h b/include/xrpl/net/HTTPClient.h index 7ed9b35b9b..752afac9c4 100644 --- a/include/xrpl/net/HTTPClient.h +++ b/include/xrpl/net/HTTPClient.h @@ -14,7 +14,8 @@ namespace xrpl { -/** Provides an asynchronous HTTP client implementation with optional SSL. +/** + * Provides an asynchronous HTTP client implementation with optional SSL. */ class HTTPClient { @@ -30,14 +31,15 @@ public: bool sslVerify, beast::Journal j); - /** Destroys the global SSL context created by initializeSSLContext(). + /** + * Destroys the global SSL context created by initializeSSLContext(). * - * This releases the underlying boost::asio::ssl::context and any - * associated OpenSSL resources. Must not be called while any - * HTTPClient requests are in flight. + * This releases the underlying boost::asio::ssl::context and any + * associated OpenSSL resources. Must not be called while any + * HTTPClient requests are in flight. * - * @note Currently only called from tests during teardown. In production, - * the SSL context lives for the lifetime of the process. + * @note Currently only called from tests during teardown. In production, + * the SSL context lives for the lifetime of the process. */ static void cleanupSSLContext(); diff --git a/include/xrpl/net/RegisterSSLCerts.h b/include/xrpl/net/RegisterSSLCerts.h index 5cc9934638..004f893515 100644 --- a/include/xrpl/net/RegisterSSLCerts.h +++ b/include/xrpl/net/RegisterSSLCerts.h @@ -5,13 +5,14 @@ #include namespace xrpl { -/** Register default SSL certificates. - - Register the system default SSL root certificates. On linux/mac, - this just calls asio's `set_default_verify_paths` to look in standard - operating system locations. On windows, it uses the OS certificate - store accessible via CryptoAPI. -*/ +/** + * Register default SSL certificates. + * + * Register the system default SSL root certificates. On linux/mac, + * this just calls asio's `set_default_verify_paths` to look in standard + * operating system locations. On windows, it uses the OS certificate + * store accessible via CryptoAPI. + */ void registerSSLCerts(boost::asio::ssl::context&, boost::system::error_code&, beast::Journal j); diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 29c4a8b526..564a874c5e 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -15,33 +15,37 @@ namespace xrpl::NodeStore { -/** A backend used for the NodeStore. - - The NodeStore uses a swappable backend so that other database systems - can be tried. Different databases may offer various features such - as improved performance, fault tolerant or distributed storage, or - all in-memory operation. - - A given instance of a backend is fixed to a particular key size. -*/ +/** + * A backend used for the NodeStore. + * + * The NodeStore uses a swappable backend so that other database systems + * can be tried. Different databases may offer various features such + * as improved performance, fault tolerant or distributed storage, or + * all in-memory operation. + * + * A given instance of a backend is fixed to a particular key size. + */ class Backend { public: - /** Destroy the backend. - - All open files are closed and flushed. If there are batched writes - or other tasks scheduled, they will be completed before this call - returns. - */ + /** + * Destroy the backend. + * + * All open files are closed and flushed. If there are batched writes + * or other tasks scheduled, they will be completed before this call + * returns. + */ virtual ~Backend() = default; - /** Get the human-readable name of this backend. - This is used for diagnostic output. - */ + /** + * Get the human-readable name of this backend. + * This is used for diagnostic output. + */ virtual std::string getName() = 0; - /** Get the block size for backends that support it + /** + * Get the block size for backends that support it */ [[nodiscard]] virtual std::optional getBlockSize() const @@ -49,25 +53,28 @@ public: return std::nullopt; } - /** Open the backend. - @param createIfMissing Create the database files if necessary. - This allows the caller to catch exceptions. - */ + /** + * Open the backend. + * @param createIfMissing Create the database files if necessary. + * This allows the caller to catch exceptions. + */ virtual void open(bool createIfMissing = true) = 0; - /** Returns true is the database is open. + /** + * Returns true is the database is open. */ virtual bool isOpen() = 0; - /** Open the backend. - @param createIfMissing Create the database files if necessary. - @param appType Deterministic appType used to create a backend. - @param uid Deterministic uid used to create a backend. - @param salt Deterministic salt used to create a backend. - @throws std::runtime_error is function is called not for NuDB backend. - */ + /** + * Open the backend. + * @param createIfMissing Create the database files if necessary. + * @param appType Deterministic appType used to create a backend. + * @param uid Deterministic uid used to create a backend. + * @param salt Deterministic salt used to create a backend. + * @throws std::runtime_error is function is called not for NuDB backend. + */ virtual void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) { @@ -75,60 +82,70 @@ public: "Deterministic appType/uid/salt not supported by backend " + getName()); } - /** Close the backend. - This allows the caller to catch exceptions. - */ + /** + * Close the backend. + * This allows the caller to catch exceptions. + */ virtual void close() = 0; - /** Fetch a single object. - If the object is not found or an error is encountered, the - result will indicate the condition. - @note This will be called concurrently. - @param hash The hash of the object. - @param pObject [out] The created object if successful. - @return The result of the operation. - */ + /** + * Fetch a single object. + * If the object is not found or an error is encountered, the + * result will indicate the condition. + * @note This will be called concurrently. + * @param hash The hash of the object. + * @param pObject [out] The created object if successful. + * @return The result of the operation. + */ virtual Status fetch(uint256 const& hash, std::shared_ptr* pObject) = 0; - /** Store a single object. - Depending on the implementation this may happen immediately - or deferred using a scheduled task. - @note This will be called concurrently. - @param object The object to store. - */ + /** + * Store a single object. + * Depending on the implementation this may happen immediately + * or deferred using a scheduled task. + * @note This will be called concurrently. + * @param object The object to store. + */ virtual void store(std::shared_ptr const& object) = 0; - /** Store a group of objects. - @note This function will not be called concurrently with - itself or @ref store. - */ + /** + * Store a group of objects. + * @note This function will not be called concurrently with + * itself or @ref store. + */ virtual void storeBatch(Batch const& batch) = 0; virtual void sync() = 0; - /** Visit every object in the database - This is usually called during import. - @note This routine will not be called concurrently with itself - or other methods. - @see import - */ + /** + * Visit every object in the database + * This is usually called during import. + * @note This routine will not be called concurrently with itself + * or other methods. + * @see import + */ virtual void forEach(std::function)> f) = 0; - /** Estimate the number of write operations pending. */ + /** + * Estimate the number of write operations pending. + */ virtual int getWriteLoad() = 0; - /** Remove contents on disk upon destruction. */ + /** + * Remove contents on disk upon destruction. + */ virtual void setDeletePath() = 0; - /** Perform consistency checks on database. + /** + * Perform consistency checks on database. * * This method is implemented only by NuDBBackend. It is not yet called * anywhere, but it might be a good idea to one day call it at startup to @@ -139,7 +156,9 @@ public: { } - /** Returns the number of file descriptors the backend expects to need. */ + /** + * Returns the number of file descriptors the backend expects to need. + */ [[nodiscard]] virtual int fdRequired() const = 0; }; diff --git a/include/xrpl/nodestore/Database.h b/include/xrpl/nodestore/Database.h index 49002ee301..96ba91bd76 100644 --- a/include/xrpl/nodestore/Database.h +++ b/include/xrpl/nodestore/Database.h @@ -27,100 +27,109 @@ class Section; namespace xrpl::NodeStore { -/** Persistency layer for NodeObject - - A Node is a ledger object which is uniquely identified by a key, which is - the 256-bit hash of the body of the node. The payload is a variable length - block of serialized data. - - All ledger data is stored as node objects and as such, needs to be persisted - between launches. Furthermore, since the set of node objects will in - general be larger than the amount of available memory, purged node objects - which are later accessed must be retrieved from the node store. - - @see NodeObject -*/ +/** + * Persistency layer for NodeObject + * + * A Node is a ledger object which is uniquely identified by a key, which is + * the 256-bit hash of the body of the node. The payload is a variable length + * block of serialized data. + * + * All ledger data is stored as node objects and as such, needs to be persisted + * between launches. Furthermore, since the set of node objects will in + * general be larger than the amount of available memory, purged node objects + * which are later accessed must be retrieved from the node store. + * + * @see NodeObject + */ class Database { public: Database() = delete; - /** Construct the node store. - - @param scheduler The scheduler to use for performing asynchronous tasks. - @param readThreads The number of asynchronous read threads to create. - @param config The configuration settings - @param journal Destination for logging output. - */ + /** + * Construct the node store. + * + * @param scheduler The scheduler to use for performing asynchronous tasks. + * @param readThreads The number of asynchronous read threads to create. + * @param config The configuration settings + * @param journal Destination for logging output. + */ Database(Scheduler& scheduler, int readThreads, Section const& config, beast::Journal j); - /** Destroy the node store. - All pending operations are completed, pending writes flushed, - and files closed before this returns. - */ + /** + * Destroy the node store. + * All pending operations are completed, pending writes flushed, + * and files closed before this returns. + */ virtual ~Database(); - /** Retrieve the name associated with this backend. - This is used for diagnostics and may not reflect the actual path - or paths used by the underlying backend. - */ + /** + * Retrieve the name associated with this backend. + * This is used for diagnostics and may not reflect the actual path + * or paths used by the underlying backend. + */ virtual std::string getName() const = 0; - /** Import objects from another database. */ + /** + * Import objects from another database. + */ virtual void importDatabase(Database& source) = 0; - /** Retrieve the estimated number of pending write operations. - This is used for diagnostics. - */ + /** + * Retrieve the estimated number of pending write operations. + * This is used for diagnostics. + */ virtual std::int32_t getWriteLoad() const = 0; - /** Store the object. - - The caller's Blob parameter is overwritten. - - @param type The type of object. - @param data The payload of the object. The caller's - variable is overwritten. - @param hash The 256-bit hash of the payload data. - @param ledgerSeq The sequence of the ledger the object belongs to. - - @return `true` if the object was stored? - */ + /** + * Store the object. + * + * The caller's Blob parameter is overwritten. + * + * @param type The type of object. + * @param data The payload of the object. The caller's + * variable is overwritten. + * @param hash The 256-bit hash of the payload data. + * @param ledgerSeq The sequence of the ledger the object belongs to. + * + * @return `true` if the object was stored? + */ virtual void store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t ledgerSeq) = 0; - /* Check if two ledgers are in the same database - - If these two sequence numbers map to the same database, - the result of a fetch with either sequence number would - be identical. - - @param s1 The first sequence number - @param s2 The second sequence number - - @return 'true' if both ledgers would be in the same DB - - */ + /** + * Check if two ledgers are in the same database + * + * If these two sequence numbers map to the same database, + * the result of a fetch with either sequence number would + * be identical. + * + * @param s1 The first sequence number + * @param s2 The second sequence number + * + * @return 'true' if both ledgers would be in the same DB + */ virtual bool isSameDB(std::uint32_t s1, std::uint32_t s2) = 0; virtual void sync() = 0; - /** Fetch a node object. - If the object is known to be not in the database, isn't found in the - database during the fetch, or failed to load correctly during the fetch, - `nullptr` is returned. - - @note This can be called concurrently. - @param hash The key of the object to retrieve. - @param ledgerSeq The sequence of the ledger where the object is stored. - @param fetchType the type of fetch, synchronous or asynchronous. - @return The object, or nullptr if it couldn't be retrieved. - */ + /** + * Fetch a node object. + * If the object is known to be not in the database, isn't found in the + * database during the fetch, or failed to load correctly during the fetch, + * `nullptr` is returned. + * + * @note This can be called concurrently. + * @param hash The key of the object to retrieve. + * @param ledgerSeq The sequence of the ledger where the object is stored. + * @param fetchType the type of fetch, synchronous or asynchronous. + * @return The object, or nullptr if it couldn't be retrieved. + */ std::shared_ptr fetchNodeObject( uint256 const& hash, @@ -128,29 +137,33 @@ public: FetchType fetchType = FetchType::Synchronous, bool duplicate = false); - /** Fetch an object without waiting. - If I/O is required to determine whether or not the object is present, - `false` is returned. Otherwise, `true` is returned and `object` is set - to refer to the object, or `nullptr` if the object is not present. - If I/O is required, the I/O is scheduled and `true` is returned - - @note This can be called concurrently. - @param hash The key of the object to retrieve - @param ledgerSeq The sequence of the ledger where the - object is stored. - @param callback Callback function when read completes - */ + /** + * Fetch an object without waiting. + * If I/O is required to determine whether or not the object is present, + * `false` is returned. Otherwise, `true` is returned and `object` is set + * to refer to the object, or `nullptr` if the object is not present. + * If I/O is required, the I/O is scheduled and `true` is returned + * + * @note This can be called concurrently. + * @param hash The key of the object to retrieve + * @param ledgerSeq The sequence of the ledger where the + * object is stored. + * @param callback Callback function when read completes + */ virtual void asyncFetch( uint256 const& hash, std::uint32_t ledgerSeq, std::function const&)>&& callback); - /** Remove expired entries from the positive and negative caches. */ + /** + * Remove expired entries from the positive and negative caches. + */ virtual void sweep() = 0; - /** Gather statistics pertaining to read and write activities. + /** + * Gather statistics pertaining to read and write activities. * * @param obj Json object reference into which to place counters. */ @@ -187,7 +200,9 @@ public: void getCountsJson(json::Value& obj); - /** Returns the number of file descriptors the database expects to need */ + /** + * Returns the number of file descriptors the database expects to need + */ int fdRequired() const { @@ -200,7 +215,8 @@ public: bool isStopping() const; - /** @return The earliest ledger sequence allowed + /** + * @return The earliest ledger sequence allowed */ [[nodiscard]] std::uint32_t earliestLedgerSeq() const noexcept @@ -277,13 +293,14 @@ private: FetchReport& fetchReport, bool duplicate) = 0; - /** Visit every object in the database - This is usually called during import. - - @note This routine will not be called concurrently with itself - or other methods. - @see import - */ + /** + * Visit every object in the database + * This is usually called during import. + * + * @note This routine will not be called concurrently with itself + * or other methods. + * @see import + */ virtual void forEach(std::function)> f) = 0; diff --git a/include/xrpl/nodestore/DatabaseRotating.h b/include/xrpl/nodestore/DatabaseRotating.h index 69eb31261d..5381b5c435 100644 --- a/include/xrpl/nodestore/DatabaseRotating.h +++ b/include/xrpl/nodestore/DatabaseRotating.h @@ -28,18 +28,32 @@ public: { } - /** Rotates the backends. - - @param newBackend New writable backend - @param f A function executed after the rotation outside of lock. The - values passed to f will be the new backend database names _after_ - rotation. - */ + /** + * Rotates the backends. + * + * @param newBackend New writable backend + * @param f A function executed after the rotation outside of lock. The + * values passed to f will be the new backend database names _after_ + * rotation. + */ virtual void rotate( std::unique_ptr&& newBackend, std::function const& f) = 0; + + /** + * Marks an online-delete rotation as in progress (or completed). + * + * While in flight, a read served by the archive backend is copied + * forward into the writable backend even for ordinary + * (duplicate == false) fetches: the archive is about to be deleted, + * and a node body canonicalized into caches during the rotation + * window would otherwise survive only in RAM once the archive is + * dropped. + */ + virtual void + setRotationInFlight(bool inFlight) = 0; }; } // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/DummyScheduler.h b/include/xrpl/nodestore/DummyScheduler.h index f626115786..49b0d37462 100644 --- a/include/xrpl/nodestore/DummyScheduler.h +++ b/include/xrpl/nodestore/DummyScheduler.h @@ -5,7 +5,9 @@ namespace xrpl::NodeStore { -/** Simple NodeStore Scheduler that just performs the tasks synchronously. */ +/** + * Simple NodeStore Scheduler that just performs the tasks synchronously. + */ class DummyScheduler : public Scheduler { public: diff --git a/include/xrpl/nodestore/Factory.h b/include/xrpl/nodestore/Factory.h index e79ae3e05d..a18023a8a8 100644 --- a/include/xrpl/nodestore/Factory.h +++ b/include/xrpl/nodestore/Factory.h @@ -16,24 +16,29 @@ class Section; namespace xrpl::NodeStore { -/** Base class for backend factories. */ +/** + * Base class for backend factories. + */ class Factory { public: virtual ~Factory() = default; - /** Retrieve the name of this factory. */ + /** + * Retrieve the name of this factory. + */ [[nodiscard]] virtual std::string getName() const = 0; - /** Create an instance of this factory's backend. - - @param keyBytes The fixed number of bytes per key. - @param parameters A set of key/value configuration pairs. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for running tasks. - @return A pointer to the Backend object. - */ + /** + * Create an instance of this factory's backend. + * + * @param keyBytes The fixed number of bytes per key. + * @param parameters A set of key/value configuration pairs. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for running tasks. + * @return A pointer to the Backend object. + */ virtual std::unique_ptr createInstance( size_t keyBytes, @@ -42,15 +47,16 @@ public: Scheduler& scheduler, beast::Journal journal) = 0; - /** Create an instance of this factory's backend. - - @param keyBytes The fixed number of bytes per key. - @param parameters A set of key/value configuration pairs. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for running tasks. - @param context The context used by database. - @return A pointer to the Backend object. - */ + /** + * Create an instance of this factory's backend. + * + * @param keyBytes The fixed number of bytes per key. + * @param parameters A set of key/value configuration pairs. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for running tasks. + * @param context The context used by database. + * @return A pointer to the Backend object. + */ virtual std::unique_ptr createInstance( size_t keyBytes, diff --git a/include/xrpl/nodestore/Manager.h b/include/xrpl/nodestore/Manager.h index f813412846..54d99fe94b 100644 --- a/include/xrpl/nodestore/Manager.h +++ b/include/xrpl/nodestore/Manager.h @@ -12,7 +12,9 @@ namespace xrpl::NodeStore { -/** Singleton for managing NodeStore factories and back ends. */ +/** + * Singleton for managing NodeStore factories and back ends. + */ class Manager { public: @@ -22,26 +24,35 @@ public: Manager& operator=(Manager const&) = delete; - /** Returns the instance of the manager singleton. */ + /** + * Returns the instance of the manager singleton. + */ static Manager& instance(); - /** Add a factory. */ + /** + * Add a factory. + */ virtual void insert(Factory& factory) = 0; - /** Remove a factory. */ + /** + * Remove a factory. + */ virtual void erase(Factory& factory) = 0; - /** Return a pointer to the matching factory if it exists. - @param name The name to match, performed case-insensitive. - @return `nullptr` if a match was not found. - */ + /** + * Return a pointer to the matching factory if it exists. + * @param name The name to match, performed case-insensitive. + * @return `nullptr` if a match was not found. + */ virtual Factory* find(std::string const& name) = 0; - /** Create a backend. */ + /** + * Create a backend. + */ virtual std::unique_ptr makeBackend( Section const& parameters, @@ -49,34 +60,35 @@ public: Scheduler& scheduler, beast::Journal journal) = 0; - /** Construct a NodeStore database. - - The parameters are key value pairs passed to the backend. The - 'type' key must exist, it defines the choice of backend. Most - backends also require a 'path' field. - - Some choices for 'type' are: - HyperLevelDB, LevelDBFactory, SQLite, MDB - - If the fastBackendParameter is omitted or empty, no ephemeral database - is used. If the scheduler parameter is omitted or unspecified, a - synchronous scheduler is used which performs all tasks immediately on - the caller's thread. - - @note If the database cannot be opened or created, an exception is - thrown. - - @param name A diagnostic label for the database. - @param burstSize Backend burst size in bytes. - @param scheduler The scheduler to use for performing asynchronous tasks. - @param readThreads The number of async read threads to create - @param backendParameters The parameter string for the persistent - backend. - @param fastBackendParameters [optional] The parameter string for the - ephemeral backend. - - @return The opened database. - */ + /** + * Construct a NodeStore database. + * + * The parameters are key value pairs passed to the backend. The + * 'type' key must exist, it defines the choice of backend. Most + * backends also require a 'path' field. + * + * Some choices for 'type' are: + * HyperLevelDB, LevelDBFactory, SQLite, MDB + * + * If the fastBackendParameter is omitted or empty, no ephemeral database + * is used. If the scheduler parameter is omitted or unspecified, a + * synchronous scheduler is used which performs all tasks immediately on + * the caller's thread. + * + * @note If the database cannot be opened or created, an exception is + * thrown. + * + * @param name A diagnostic label for the database. + * @param burstSize Backend burst size in bytes. + * @param scheduler The scheduler to use for performing asynchronous tasks. + * @param readThreads The number of async read threads to create + * @param backendParameters The parameter string for the persistent + * backend. + * @param fastBackendParameters [optional] The parameter string for the + * ephemeral backend. + * + * @return The opened database. + */ virtual std::unique_ptr makeDatabase( std::size_t burstSize, diff --git a/include/xrpl/nodestore/NodeObject.h b/include/xrpl/nodestore/NodeObject.h index 3f3b75d5f8..b96d65fa12 100644 --- a/include/xrpl/nodestore/NodeObject.h +++ b/include/xrpl/nodestore/NodeObject.h @@ -12,7 +12,9 @@ namespace xrpl { -/** The types of node objects. */ +/** + * The types of node objects. + */ enum class NodeObjectType : std::uint32_t { Unknown = 0, Ledger = 1, @@ -21,15 +23,16 @@ enum class NodeObjectType : std::uint32_t { Dummy = 512 // an invalid or missing object }; -/** A simple object that the Ledger uses to store entries. - NodeObjects are comprised of a type, a hash, and a blob. - They can be uniquely identified by the hash, which is a half-SHA512 of - the blob. The blob is a variable length block of serialized data. The - type identifies what the blob contains. - - @note No checking is performed to make sure the hash matches the data. - @see SHAMap -*/ +/** + * A simple object that the Ledger uses to store entries. + * NodeObjects are comprised of a type, a hash, and a blob. + * They can be uniquely identified by the hash, which is a half-SHA512 of + * the blob. The blob is a variable length block of serialized data. The + * type identifies what the blob contains. + * + * @note No checking is performed to make sure the hash matches the data. + * @see SHAMap + */ class NodeObject : public CountedObject { public: @@ -48,29 +51,36 @@ public: // This constructor is private, use createObject instead. NodeObject(NodeObjectType type, Blob&& data, uint256 const& hash, PrivateAccess); - /** Create an object from fields. - - The caller's variable is modified during this call. The - underlying storage for the Blob is taken over by the NodeObject. - - @param type The type of object. - @param ledgerIndex The ledger in which this object appears. - @param data A buffer containing the payload. The caller's variable - is overwritten. - @param hash The 256-bit hash of the payload data. - */ + /** + * Create an object from fields. + * + * The caller's variable is modified during this call. The + * underlying storage for the Blob is taken over by the NodeObject. + * + * @param type The type of object. + * @param ledgerIndex The ledger in which this object appears. + * @param data A buffer containing the payload. The caller's variable + * is overwritten. + * @param hash The 256-bit hash of the payload data. + */ static std::shared_ptr createObject(NodeObjectType type, Blob&& data, uint256 const& hash); - /** Returns the type of this object. */ + /** + * Returns the type of this object. + */ [[nodiscard]] NodeObjectType getType() const; - /** Returns the hash of the data. */ + /** + * Returns the hash of the data. + */ [[nodiscard]] uint256 const& getHash() const; - /** Returns the underlying data. */ + /** + * Returns the underlying data. + */ [[nodiscard]] Blob const& getData() const; diff --git a/include/xrpl/nodestore/Scheduler.h b/include/xrpl/nodestore/Scheduler.h index 588ff19bdc..5d93a80eaa 100644 --- a/include/xrpl/nodestore/Scheduler.h +++ b/include/xrpl/nodestore/Scheduler.h @@ -8,7 +8,9 @@ namespace xrpl::NodeStore { enum class FetchType { Synchronous, Async }; -/** Contains information about a fetch operation. */ +/** + * Contains information about a fetch operation. + */ struct FetchReport { explicit FetchReport(FetchType fetchType) : fetchType(fetchType) @@ -20,7 +22,9 @@ struct FetchReport bool wasFound = false; }; -/** Contains information about a batch write operation. */ +/** + * Contains information about a batch write operation. + */ struct BatchWriteReport { explicit BatchWriteReport() = default; @@ -29,36 +33,40 @@ struct BatchWriteReport int writeCount; }; -/** Scheduling for asynchronous backend activity - - For improved performance, a backend has the option of performing writes - in batches. These writes can be scheduled using the provided scheduler - object. - - @see BatchWriter -*/ +/** + * Scheduling for asynchronous backend activity + * + * For improved performance, a backend has the option of performing writes + * in batches. These writes can be scheduled using the provided scheduler + * object. + * + * @see BatchWriter + */ class Scheduler { public: virtual ~Scheduler() = default; - /** Schedules a task. - Depending on the implementation, the task may be invoked either on - the current thread of execution, or an unspecified - implementation-defined foreign thread. - */ + /** + * Schedules a task. + * Depending on the implementation, the task may be invoked either on + * the current thread of execution, or an unspecified + * implementation-defined foreign thread. + */ virtual void scheduleTask(Task& task) = 0; - /** Reports completion of a fetch - Allows the scheduler to monitor the node store's performance - */ + /** + * Reports completion of a fetch + * Allows the scheduler to monitor the node store's performance + */ virtual void onFetch(FetchReport const& report) = 0; - /** Reports the completion of a batch write - Allows the scheduler to monitor the node store's performance - */ + /** + * Reports the completion of a batch write + * Allows the scheduler to monitor the node store's performance + */ virtual void onBatchWrite(BatchWriteReport const& report) = 0; }; diff --git a/include/xrpl/nodestore/Task.h b/include/xrpl/nodestore/Task.h index 0695970a68..59fe648476 100644 --- a/include/xrpl/nodestore/Task.h +++ b/include/xrpl/nodestore/Task.h @@ -2,14 +2,17 @@ namespace xrpl::NodeStore { -/** Derived classes perform scheduled tasks. */ +/** + * Derived classes perform scheduled tasks. + */ struct Task { virtual ~Task() = default; - /** Performs the task. - The call may take place on a foreign thread. - */ + /** + * Performs the task. + * The call may take place on a foreign thread. + */ virtual void performScheduledTask() = 0; }; diff --git a/include/xrpl/nodestore/Types.h b/include/xrpl/nodestore/Types.h index eaee82c99e..872d948a36 100644 --- a/include/xrpl/nodestore/Types.h +++ b/include/xrpl/nodestore/Types.h @@ -18,7 +18,9 @@ static constexpr auto kBatchWritePreallocationSize = 256; // static constexpr auto kBatchWriteLimitSize = 65536; -/** Return codes from Backend operations. */ +/** + * Return codes from Backend operations. + */ enum class Status { Ok = 0, NotFound = 1, @@ -29,7 +31,9 @@ enum class Status { CustomCode = 100 }; -/** A batch of NodeObjects to write at once. */ +/** + * A batch of NodeObjects to write at once. + */ using Batch = std::vector>; } // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/BatchWriter.h b/include/xrpl/nodestore/detail/BatchWriter.h index 7fa23bcb3e..b89df0da14 100644 --- a/include/xrpl/nodestore/detail/BatchWriter.h +++ b/include/xrpl/nodestore/detail/BatchWriter.h @@ -11,18 +11,21 @@ namespace xrpl::NodeStore { -/** Batch-writing assist logic. - - The batch writes are performed with a scheduled task. Use of the - class it not required. A backend can implement its own write batching, - or skip write batching if doing so yields a performance benefit. - - @see Scheduler -*/ +/** + * Batch-writing assist logic. + * + * The batch writes are performed with a scheduled task. Use of the + * class it not required. A backend can implement its own write batching, + * or skip write batching if doing so yields a performance benefit. + * + * @see Scheduler + */ class BatchWriter : private Task { public: - /** This callback does the actual writing. */ + /** + * This callback does the actual writing. + */ struct Callback { virtual ~Callback() = default; @@ -35,24 +38,30 @@ public: writeBatch(Batch const& batch) = 0; }; - /** Create a batch writer. */ + /** + * Create a batch writer. + */ BatchWriter(Callback& callback, Scheduler& scheduler); - /** Destroy a batch writer. - - Anything pending in the batch is written out before this returns. - */ + /** + * Destroy a batch writer. + * + * Anything pending in the batch is written out before this returns. + */ ~BatchWriter() override; - /** Store the object. - - This will add to the batch and initiate a scheduled task to - write the batch out. - */ + /** + * Store the object. + * + * This will add to the batch and initiate a scheduled task to + * write the batch out. + */ void store(std::shared_ptr const& object); - /** Get an estimate of the amount of writing I/O pending. */ + /** + * Get an estimate of the amount of writing I/O pending. + */ int getWriteLoad(); diff --git a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h index 6343275c76..ecbe9a513d 100644 --- a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h +++ b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -69,11 +70,22 @@ public: void sweep() override; + void + setRotationInFlight(bool inFlight) override; + private: std::shared_ptr writableBackend_; std::shared_ptr archiveBackend_; mutable std::mutex mutex_; + // True between SHAMapStore starting the cache-freshen phase and the + // completion of rotate(). While true, archive hits on ordinary + // (duplicate == false) fetches are copied forward into the writable + // backend; copyForwardCount_ tallies them per rotation for the + // summary line logged at swap. + std::atomic rotationInFlight_{false}; + std::atomic copyForwardCount_{0}; + std::shared_ptr fetchNodeObject(uint256 const& hash, std::uint32_t, FetchReport& fetchReport, bool duplicate) override; diff --git a/include/xrpl/nodestore/detail/DecodedBlob.h b/include/xrpl/nodestore/detail/DecodedBlob.h index 02ccd5787a..d0cc5e3404 100644 --- a/include/xrpl/nodestore/detail/DecodedBlob.h +++ b/include/xrpl/nodestore/detail/DecodedBlob.h @@ -6,30 +6,37 @@ namespace xrpl::NodeStore { -/** Parsed key/value blob into NodeObject components. - - This will extract the information required to construct a NodeObject. It - also does consistency checking and returns the result, so it is possible - to determine if the data is corrupted without throwing an exception. Not - all forms of corruption are detected so further analysis will be needed - to eliminate false negatives. - - @note This defines the database format of a NodeObject! -*/ +/** + * Parsed key/value blob into NodeObject components. + * + * This will extract the information required to construct a NodeObject. It + * also does consistency checking and returns the result, so it is possible + * to determine if the data is corrupted without throwing an exception. Not + * all forms of corruption are detected so further analysis will be needed + * to eliminate false negatives. + * + * @note This defines the database format of a NodeObject! + */ class DecodedBlob { public: - /** Construct the decoded blob from raw data. */ + /** + * Construct the decoded blob from raw data. + */ DecodedBlob(void const* key, void const* value, int valueBytes); - /** Determine if the decoding was successful. */ + /** + * Determine if the decoding was successful. + */ [[nodiscard]] bool wasOk() const noexcept { return success_; } - /** Create a NodeObject from this data. */ + /** + * Create a NodeObject from this data. + */ std::shared_ptr createObject(); diff --git a/include/xrpl/nodestore/detail/EncodedBlob.h b/include/xrpl/nodestore/detail/EncodedBlob.h index 3982ab1b95..d668cdccd8 100644 --- a/include/xrpl/nodestore/detail/EncodedBlob.h +++ b/include/xrpl/nodestore/detail/EncodedBlob.h @@ -14,47 +14,54 @@ namespace xrpl::NodeStore { -/** Convert a NodeObject from in-memory to database format. - - The (suboptimal) database format consists of: - - - 8 prefix bytes which will typically be 0, but don't assume that's the - case; earlier versions of the code would use these bytes to store the - ledger index either once or twice. - - A single byte denoting the type of the object. - - The payload. - - @note This class is typically instantiated on the stack, so the size of - the object does not matter as much as it normally would since the - allocation is, effectively, free. - - We leverage that fact to preallocate enough memory to handle most - payloads as part of this object, eliminating the need for dynamic - allocation. As of this writing ~94% of objects require fewer than - 1024 payload bytes. +/** + * Convert a NodeObject from in-memory to database format. + * + * The (suboptimal) database format consists of: + * + * - 8 prefix bytes which will typically be 0, but don't assume that's the + * case; earlier versions of the code would use these bytes to store the + * ledger index either once or twice. + * - A single byte denoting the type of the object. + * - The payload. + * + * @note This class is typically instantiated on the stack, so the size of + * the object does not matter as much as it normally would since the + * allocation is, effectively, free. + * + * We leverage that fact to preallocate enough memory to handle most + * payloads as part of this object, eliminating the need for dynamic + * allocation. As of this writing ~94% of objects require fewer than + * 1024 payload bytes. */ class EncodedBlob { - /** The 32-byte key of the serialized object. */ + /** + * The 32-byte key of the serialized object. + */ std::array key_{}; - /** A pre-allocated buffer for the serialized object. - - The buffer is large enough for the 9 byte prefix and at least - 1024 more bytes. The precise size is calculated automatically - at compile time so as to avoid wasting space on padding bytes. + /** + * A pre-allocated buffer for the serialized object. + * + * The buffer is large enough for the 9 byte prefix and at least + * 1024 more bytes. The precise size is calculated automatically + * at compile time so as to avoid wasting space on padding bytes. */ std::array payload_{}; - /** The size of the serialized data. */ + /** + * The size of the serialized data. + */ std::uint32_t size_; - /** A pointer to the serialized data. - - This may point to the pre-allocated buffer (if it is sufficiently - large) or to a dynamically allocated buffer. + /** + * A pointer to the serialized data. + * + * This may point to the pre-allocated buffer (if it is sufficiently + * large) or to a dynamically allocated buffer. */ std::uint8_t* const ptr_; diff --git a/include/xrpl/protocol/AMMCore.h b/include/xrpl/protocol/AMMCore.h index c4fccd029a..a3666c7960 100644 --- a/include/xrpl/protocol/AMMCore.h +++ b/include/xrpl/protocol/AMMCore.h @@ -33,17 +33,20 @@ class STObject; class STAmount; class Rules; -/** Calculate Liquidity Provider Token (LPT) Currency. +/** + * Calculate Liquidity Provider Token (LPT) Currency. */ Currency ammLPTCurrency(Asset const& asset1, Asset const& asset2); -/** Calculate LPT Issue from AMM asset pair. +/** + * Calculate LPT Issue from AMM asset pair. */ Issue ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID); -/** Validate the amount. +/** + * Validate the amount. * If validZero is false and amount is beast::zero then invalid amount. * Return error code if invalid amount. * If pair then validate amount's issue matches one of the pair's issue. @@ -65,17 +68,20 @@ invalidAMMAssetPair( Asset const& asset2, std::optional> const& pair = std::nullopt); -/** Get time slot of the auction slot. +/** + * Get time slot of the auction slot. */ std::optional ammAuctionTimeSlot(std::uint64_t current, STObject const& auctionSlot); -/** Return true if required AMM amendment is enabled +/** + * Return true if required AMM amendment is enabled */ bool ammEnabled(Rules const&); -/** Convert to the fee from the basis points +/** + * Convert to the fee from the basis points * @param tfee trading fee in {0, 1000} * 1 = 1/10bps or 0.001%, 1000 = 1% */ @@ -85,7 +91,8 @@ getFee(std::uint16_t tfee) return Number{tfee} / kAuctionSlotFeeScaleFactor; } -/** Get fee multiplier (1 - tfee) +/** + * Get fee multiplier (1 - tfee) * @tfee trading fee in basis points */ inline Number @@ -94,7 +101,8 @@ feeMult(std::uint16_t tfee) return 1 - getFee(tfee); } -/** Get fee multiplier (1 - tfee / 2) +/** + * Get fee multiplier (1 - tfee / 2) * @tfee trading fee in basis points */ inline Number diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index a7d49246ca..ab3c5a996b 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -28,43 +28,53 @@ public: } // namespace detail -/** A 160-bit unsigned that uniquely identifies an account. */ +/** + * A 160-bit unsigned that uniquely identifies an account. + */ using AccountID = BaseUInt<160, detail::AccountIDTag>; -/** Convert AccountID to base58 checked string */ +/** + * Convert AccountID to base58 checked string + */ std::string toBase58(AccountID const& v); -/** Parse AccountID from checked, base58 string. - @return std::nullopt if a parse error occurs -*/ +/** + * Parse AccountID from checked, base58 string. + * @return std::nullopt if a parse error occurs + */ template <> std::optional parseBase58(std::string const& s); -/** Compute AccountID from public key. - - The account ID is computed as the 160-bit hash of the - public key data. This excludes the version byte and - guard bytes included in the base58 representation. - -*/ +/** + * Compute AccountID from public key. + * + * The account ID is computed as the 160-bit hash of the + * public key data. This excludes the version byte and + * guard bytes included in the base58 representation. + */ // VFALCO In PublicKey.h for now // AccountID // calcAccountID (PublicKey const& pk); -/** A special account that's used as the "issuer" for XRP. */ +/** + * A special account that's used as the "issuer" for XRP. + */ AccountID const& xrpAccount(); -/** A placeholder for empty accounts. */ +/** + * A placeholder for empty accounts. + */ AccountID const& noAccount(); -/** Convert hex or base58 string to AccountID. - - @return `true` if the parsing was successful. -*/ +/** + * Convert hex or base58 string to AccountID. + * + * @return `true` if the parsing was successful. + */ // DEPRECATED bool toIssuer(AccountID&, std::string const&); @@ -91,17 +101,18 @@ operator<<(std::ostream& os, AccountID const& x) return os; } -/** Initialize the global cache used to map AccountID to base58 conversions. - - The cache is optional and need not be initialized. But because conversion - is expensive (it requires a SHA-256 operation) in most cases the overhead - of the cache is worth the benefit. - - @param count The number of entries the cache should accommodate. Zero will - disable the cache, releasing any memory associated with it. - - @note The function will only initialize the cache the first time it is - invoked. Subsequent invocations do nothing. +/** + * Initialize the global cache used to map AccountID to base58 conversions. + * + * The cache is optional and need not be initialized. But because conversion + * is expensive (it requires a SHA-256 operation) in most cases the overhead + * of the cache is worth the benefit. + * + * @param count The number of entries the cache should accommodate. Zero will + * disable the cache, releasing any memory associated with it. + * + * @note The function will only initialize the cache the first time it is + * invoked. Subsequent invocations do nothing. */ void initAccountIdCache(std::size_t count); diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h index 2bf24b19fe..8e9c09eb89 100644 --- a/include/xrpl/protocol/Asset.h +++ b/include/xrpl/protocol/Asset.h @@ -62,7 +62,8 @@ private: public: Asset() = default; - /** Conversions to Asset are implicit and conversions to specific issue + /** + * Conversions to Asset are implicit and conversions to specific issue * type are explicit. This design facilitates the use of Asset. */ Asset(Issue const& issue) : issue_(issue) @@ -149,7 +150,8 @@ public: friend constexpr bool operator==(BadAsset const& lhs, Asset const& rhs); - /** Return true if both assets refer to the same currency (regardless of + /** + * Return true if both assets refer to the same currency (regardless of * issuer) or MPT issuance. Otherwise return false. */ friend constexpr bool diff --git a/include/xrpl/protocol/Book.h b/include/xrpl/protocol/Book.h index 476bdba35a..a83eb41b24 100644 --- a/include/xrpl/protocol/Book.h +++ b/include/xrpl/protocol/Book.h @@ -19,10 +19,11 @@ namespace xrpl { -/** Specifies an order book. - The order book is a pair of Issues called in and out. - @see Issue. -*/ +/** + * Specifies an order book. + * The order book is a pair of Issues called in and out. + * @see Issue. + */ class Book final : public CountedObject { public: @@ -60,7 +61,9 @@ hash_append(Hasher& h, Book const& b) Book reversed(Book const& book); -/** Equality comparison. */ +/** + * Equality comparison. + */ /** @{ */ [[nodiscard]] constexpr bool operator==(Book const& lhs, Book const& rhs) @@ -69,14 +72,16 @@ operator==(Book const& lhs, Book const& rhs) } /** @} */ -/** Strict weak ordering. */ +/** + * Strict weak ordering. + */ /** @{ */ [[nodiscard]] constexpr std::weak_ordering operator<=>(Book const& lhs, Book const& rhs) { - if (auto const c{lhs.in <=> rhs.in}; c != 0) + if (auto const c{lhs.in <=> rhs.in}; c != 0) // NOLINT(modernize-use-nullptr) return c; - if (auto const c{lhs.out <=> rhs.out}; c != 0) + if (auto const c{lhs.out <=> rhs.out}; c != 0) // NOLINT(modernize-use-nullptr) return c; // Manually compare optionals diff --git a/include/xrpl/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h index a60c37e714..18ba20f23c 100644 --- a/include/xrpl/protocol/BuildInfo.h +++ b/include/xrpl/protocol/BuildInfo.h @@ -4,74 +4,83 @@ #include #include -/** Versioning information for this build. */ +/** + * Versioning information for this build. + */ // VFALCO The namespace is deprecated namespace xrpl::BuildInfo { -/** Server version. - Follows the Semantic Versioning Specification: - http://semver.org/ -*/ +/** + * Server version. + * Follows the Semantic Versioning Specification: + * http://semver.org/ + */ std::string const& getVersionString(); -/** Full server version string. - This includes the name of the server. It is used in the peer - protocol hello message and also the headers of some HTTP replies. -*/ +/** + * Full server version string. + * This includes the name of the server. It is used in the peer + * protocol hello message and also the headers of some HTTP replies. + */ std::string const& getFullVersionString(); -/** Encode an arbitrary server software version in a 64-bit integer. - - The general format is: - - ........-........-........-........-........-........-........-........ - XXXXXXXX-XXXXXXXX-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY - - X: 16 bits identifying the particular implementation - Y: 48 bits of data specific to the implementation - - The xrpld-specific format (implementation ID is: 0x18 0x3B) is: - - 00011000-00111011-MMMMMMMM-mmmmmmmm-pppppppp-TTNNNNNN-00000000-00000000 - - M: 8-bit major version (0-255) - m: 8-bit minor version (0-255) - p: 8-bit patch version (0-255) - T: 11 if neither an RC nor a beta - 10 if an RC - 01 if a beta - N: 6-bit rc/beta number (1-63) - - @param the version string - @return the encoded version in a 64-bit integer -*/ +/** + * Encode an arbitrary server software version in a 64-bit integer. + * + * The general format is: + * + * ........-........-........-........-........-........-........-........ + * XXXXXXXX-XXXXXXXX-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY-YYYYYYYY + * + * X: 16 bits identifying the particular implementation + * Y: 48 bits of data specific to the implementation + * + * The xrpld-specific format (implementation ID is: 0x18 0x3B) is: + * + * 00011000-00111011-MMMMMMMM-mmmmmmmm-pppppppp-TTNNNNNN-00000000-00000000 + * + * M: 8-bit major version (0-255) + * m: 8-bit minor version (0-255) + * p: 8-bit patch version (0-255) + * T: 11 if neither an RC nor a beta + * 10 if an RC + * 01 if a beta + * N: 6-bit rc/beta number (1-63) + * + * @param the version string + * @return the encoded version in a 64-bit integer + */ std::uint64_t encodeSoftwareVersion(std::string_view versionStr); -/** Returns this server's version packed in a 64-bit integer. */ +/** + * Returns this server's version packed in a 64-bit integer. + */ std::uint64_t getEncodedVersion(); -/** Check if the encoded software version is an xrpld software version. - - @param version another node's encoded software version - @return true if the version is an xrpld software version, false otherwise -*/ +/** + * Check if the encoded software version is an xrpld software version. + * + * @param version another node's encoded software version + * @return true if the version is an xrpld software version, false otherwise + */ bool isXrpldVersion(std::uint64_t version); -/** Check if the version is newer than the local node's xrpld software - version. - - @param version another node's encoded software version - @return true if the version is newer than the local node's xrpld software - version, false otherwise. - - @note This function only understands version numbers that are generated by - xrpld. Please see the encodeSoftwareVersion() function for detail. -*/ +/** + * Check if the version is newer than the local node's xrpld software + * version. + * + * @param version another node's encoded software version + * @return true if the version is newer than the local node's xrpld software + * version, false otherwise. + * + * @note This function only understands version numbers that are generated by + * xrpld. Please see the encodeSoftwareVersion() function for detail. + */ bool isNewerVersion(std::uint64_t version); diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 325117eed4..ecf7970aba 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -28,7 +28,9 @@ namespace xrpl { */ struct ConfidentialRecipient { - /** @brief The recipient's ElGamal public key (size=xrpl::kEcPubKeyLength). */ + /** + * @brief The recipient's ElGamal public key (size=xrpl::kEcPubKeyLength). + */ Slice publicKey; /** @@ -44,10 +46,14 @@ struct ConfidentialRecipient */ struct EcPair { - /** @brief First ElGamal ciphertext component. */ + /** + * @brief First ElGamal ciphertext component. + */ secp256k1_pubkey c1; - /** @brief Second ElGamal ciphertext component. */ + /** + * @brief Second ElGamal ciphertext component. + */ secp256k1_pubkey c2; }; diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index 38b8bc6d76..8ac7c8c58f 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -147,10 +147,11 @@ enum ErrorCodeI { RpcLast = RpcUnexpectedLedgerType // rpcLAST should always equal the last code. }; -/** Codes returned in the `warnings` array of certain RPC commands. - - These values need to remain stable. -*/ +/** + * Codes returned in the `warnings` array of certain RPC commands. + * + * These values need to remain stable. + */ // Protocol-wide, 50+ files // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum WarningCodeI { @@ -168,7 +169,9 @@ enum WarningCodeI { namespace RPC { -/** Maps an rpc error code to its token, default message, and HTTP status. */ +/** + * Maps an rpc error code to its token, default message, and HTTP status. + */ struct ErrorInfo { // Default ctor needed to produce an empty std::array during constexpr eval. @@ -193,11 +196,15 @@ struct ErrorInfo int httpStatus; }; -/** Returns an ErrorInfo that reflects the error code. */ +/** + * Returns an ErrorInfo that reflects the error code. + */ ErrorInfo const& getErrorInfo(ErrorCodeI code); -/** Add or update the json update to reflect the error code. */ +/** + * Add or update the json update to reflect the error code. + */ /** @{ */ void injectError(ErrorCodeI code, json::Value& json); @@ -206,7 +213,9 @@ void injectError(ErrorCodeI code, std::string const& message, json::Value& json); /** @} */ -/** Returns a new json object that reflects the error code. */ +/** + * Returns a new json object that reflects the error code. + */ /** @{ */ json::Value makeError(ErrorCodeI code); @@ -214,7 +223,9 @@ json::Value makeError(ErrorCodeI code, std::string const& message); /** @} */ -/** Returns a new json object that indicates invalid parameters. */ +/** + * Returns a new json object that indicates invalid parameters. + */ /** @{ */ inline json::Value makeParamError(std::string const& message) @@ -314,17 +325,23 @@ notValidatorError() /** @} */ -/** Returns `true` if the json contains an rpc error specification. */ +/** + * Returns `true` if the json contains an rpc error specification. + */ bool containsError(json::Value const& json); -/** Returns http status that corresponds to the error code. */ +/** + * Returns http status that corresponds to the error code. + */ int errorCodeHttpStatus(ErrorCodeI code); } // namespace RPC -/** Returns a single string with the contents of an RPC error. */ +/** + * Returns a single string with the contents of an RPC error. + */ std::string rpcErrorString(json::Value const& jv); diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 927fde542a..f15b7e2d3f 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -112,7 +112,9 @@ validFeatureName(auto fn) -> bool enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes = 1 }; enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported = 1 }; -/** All amendments libxrpl knows about. */ +/** + * All amendments libxrpl knows about. + */ std::map const& allAmendments(); @@ -152,23 +154,27 @@ static constexpr std::size_t kNumFeatures = #undef XRPL_FEATURE #pragma pop_macro("XRPL_FEATURE") -/** Amendments that this server supports and the default voting behavior. - Whether they are enabled depends on the Rules defined in the validated - ledger */ +/** + * Amendments that this server supports and the default voting behavior. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& supportedAmendments(); -/** Amendments that this server won't vote for by default. - - This function is only used in unit tests. -*/ +/** + * Amendments that this server won't vote for by default. + * + * This function is only used in unit tests. + */ std::size_t numDownVotedAmendments(); -/** Amendments that this server will vote for by default. - - This function is only used in unit tests. -*/ +/** + * Amendments that this server will vote for by default. + * + * This function is only used in unit tests. + */ std::size_t numUpVotedAmendments(); diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 4e79204f4c..fdfadcd8fd 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -2,7 +2,6 @@ #include -#include #include namespace xrpl { @@ -11,20 +10,27 @@ namespace xrpl { // This was the reference fee units used in the old fee calculation. inline constexpr std::uint32_t kFeeUnitsDeprecated = 10; -/** Reflects the fee settings for a particular ledger. - - The fees are always the same for any transactions applied - to a ledger. Changes to fees occur in between ledgers. -*/ +/** + * Reflects the fee settings for a particular ledger. + * + * The fees are always the same for any transactions applied + * to a ledger. Changes to fees occur in between ledgers. + */ struct Fees { - /** @brief Cost of a reference transaction in drops. */ + /** + * @brief Cost of a reference transaction in drops. + */ XRPAmount base{0}; - /** @brief Minimum XRP an account must hold to exist on the ledger. */ + /** + * @brief Minimum XRP an account must hold to exist on the ledger. + */ XRPAmount reserve{0}; - /** @brief Additional XRP reserve required per owned ledger object. */ + /** + * @brief Additional XRP reserve required per owned ledger object. + */ XRPAmount increment{0}; explicit Fees() = default; @@ -37,15 +43,16 @@ struct Fees { } - /** Returns the account reserve given the owner count, in drops. - - The reserve is calculated as the reserve base plus - the reserve increment times the number of increments. - */ + /** + * Returns the account reserve given the owner count, in drops. + * + * The reserve is calculated as the reserve base times the number of accounts plus the reserve + * increment times the number of increments. + */ [[nodiscard]] XRPAmount - accountReserve(std::size_t ownerCount) const + accountReserve(std::uint32_t ownerCount, std::uint32_t accountCount) const { - return reserve + ownerCount * increment; + return (reserve * accountCount) + (increment * ownerCount); } }; diff --git a/include/xrpl/protocol/HashPrefix.h b/include/xrpl/protocol/HashPrefix.h index 1b05d450a1..9d4471d05c 100644 --- a/include/xrpl/protocol/HashPrefix.h +++ b/include/xrpl/protocol/HashPrefix.h @@ -17,55 +17,80 @@ makeHashPrefix(char a, char b, char c) } // namespace detail -/** Prefix for hashing functions. - - These prefixes are inserted before the source material used to generate - various hashes. This is done to put each hash in its own "space." This way, - two different types of objects with the same binary data will produce - different hashes. - - Each prefix is a 4-byte value with the last byte set to zero and the first - three bytes formed from the ASCII equivalent of some arbitrary string. For - example "TXN". - - @note Hash prefixes are part of the protocol; you cannot, arbitrarily, - change the type or the value of any of these without causing breakage. -*/ +/** + * Prefix for hashing functions. + * + * These prefixes are inserted before the source material used to generate + * various hashes. This is done to put each hash in its own "space." This way, + * two different types of objects with the same binary data will produce + * different hashes. + * + * Each prefix is a 4-byte value with the last byte set to zero and the first + * three bytes formed from the ASCII equivalent of some arbitrary string. For + * example "TXN". + * + * @note Hash prefixes are part of the protocol; you cannot, arbitrarily, + * change the type or the value of any of these without causing breakage. + */ enum class HashPrefix : std::uint32_t { - /** transaction plus signature to give transaction ID */ + /** + * transaction plus signature to give transaction ID + */ TransactionId = detail::makeHashPrefix('T', 'X', 'N'), - /** transaction plus metadata */ + /** + * transaction plus metadata + */ TxNode = detail::makeHashPrefix('S', 'N', 'D'), - /** account state */ + /** + * account state + */ LeafNode = detail::makeHashPrefix('M', 'L', 'N'), - /** inner node in V1 tree */ + /** + * inner node in V1 tree + */ InnerNode = detail::makeHashPrefix('M', 'I', 'N'), - /** ledger master data for signing */ + /** + * ledger master data for signing + */ LedgerMaster = detail::makeHashPrefix('L', 'W', 'R'), - /** inner transaction to sign */ + /** + * inner transaction to sign + */ TxSign = detail::makeHashPrefix('S', 'T', 'X'), - /** inner transaction to multi-sign */ + /** + * inner transaction to multi-sign + */ TxMultiSign = detail::makeHashPrefix('S', 'M', 'T'), - /** validation for signing */ + /** + * validation for signing + */ Validation = detail::makeHashPrefix('V', 'A', 'L'), - /** proposal for signing */ + /** + * proposal for signing + */ Proposal = detail::makeHashPrefix('P', 'R', 'P'), - /** Manifest */ + /** + * Manifest + */ Manifest = detail::makeHashPrefix('M', 'A', 'N'), - /** Payment Channel Claim */ + /** + * Payment Channel Claim + */ PaymentChannelClaim = detail::makeHashPrefix('C', 'L', 'M'), - /** Batch */ + /** + * Batch + */ Batch = detail::makeHashPrefix('B', 'C', 'H'), }; diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 186ce054f1..060ad3d828 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -11,16 +11,17 @@ namespace xrpl { -/** Floating point representation of amounts with high dynamic range - - Amounts are stored as a normalized signed mantissa and an exponent. The - range of the normalized exponent is [-96,80] and the range of the absolute - value of the normalized mantissa is [1000000000000000, 9999999999999999]. - - Arithmetic operations can throw std::overflow_error during normalization - if the amount exceeds the largest representable amount, but underflows - will silently truncate to zero. -*/ +/** + * Floating point representation of amounts with high dynamic range + * + * Amounts are stored as a normalized signed mantissa and an exponent. The + * range of the normalized exponent is [-96,80] and the range of the absolute + * value of the normalized mantissa is [1000000000000000, 9999999999999999]. + * + * Arithmetic operations can throw std::overflow_error during normalization + * if the amount exceeds the largest representable amount, but underflows + * will silently truncate to zero. + */ class IOUAmount : private boost::totally_ordered, private boost::additive { private: @@ -29,12 +30,13 @@ private: mantissa_type mantissa_{}; exponent_type exponent_{}; - /** Adjusts the mantissa and exponent to the proper range. - - This can throw if the amount cannot be normalized, or is larger than - the largest value that can be represented as an IOU amount. Amounts - that are too small to be represented normalize to 0. - */ + /** + * Adjusts the mantissa and exponent to the proper range. + * + * This can throw if the amount cannot be normalized, or is larger than + * the largest value that can be represented as an IOU amount. Amounts + * that are too small to be represented normalize to 0. + */ void normalize(); @@ -66,11 +68,15 @@ public: bool operator<(IOUAmount const& other) const; - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit operator bool() const noexcept; - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] int signum() const noexcept; diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 053a66787f..07493da0bd 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -24,74 +23,90 @@ namespace xrpl { class SeqProxy; -/** Keylet computation functions. - - Entries in the ledger are located using 256-bit locators. The locators are - calculated using a wide range of parameters specific to the entry whose - locator we are calculating (e.g. an account's locator is derived from the - account's address, whereas the locator for an offer is derived from the - account and the offer sequence.) - - To enhance type safety during lookup and make the code more robust, we use - keylets, which contain not only the locator of the object but also the type - of the object being referenced. - - These functions each return a type-specific keylet. -*/ +/** + * Keylet computation functions. + * + * Entries in the ledger are located using 256-bit locators. The locators are + * calculated using a wide range of parameters specific to the entry whose + * locator we are calculating (e.g. an account's locator is derived from the + * account's address, whereas the locator for an offer is derived from the + * account and the offer sequence.) + * + * To enhance type safety during lookup and make the code more robust, we use + * keylets, which contain not only the locator of the object but also the type + * of the object being referenced. + * + * These functions each return a type-specific keylet. + */ namespace keylet { -/** AccountID root */ +/** + * AccountID root + */ Keylet account(AccountID const& id) noexcept; -/** The index of the amendment table */ +/** + * The index of the amendment table + */ Keylet const& amendments() noexcept; -/** Any item that can be in an owner dir. */ +/** + * Any item that can be in an owner dir. + */ Keylet child(uint256 const& key) noexcept; -/** The index of the "short" skip list - - The "short" skip list is a node (at a fixed index) that holds the hashes - of ledgers since the last flag ledger. It will contain, at most, 256 hashes. -*/ +/** + * The index of the "short" skip list + * + * The "short" skip list is a node (at a fixed index) that holds the hashes + * of ledgers since the last flag ledger. It will contain, at most, 256 hashes. + */ Keylet const& skip() noexcept; -/** The index of the long skip for a particular ledger range. - - The "long" skip list is a node that holds the hashes of (up to) 256 flag - ledgers. - - It can be used to efficiently skip back to any ledger using only two hops: - the first hop gets the "long" skip list for the ledger it wants to retrieve - and uses it to get the hash of the flag ledger whose short skip list will - contain the hash of the requested ledger. -*/ +/** + * The index of the long skip for a particular ledger range. + * + * The "long" skip list is a node that holds the hashes of (up to) 256 flag + * ledgers. + * + * It can be used to efficiently skip back to any ledger using only two hops: + * the first hop gets the "long" skip list for the ledger it wants to retrieve + * and uses it to get the hash of the flag ledger whose short skip list will + * contain the hash of the requested ledger. + */ Keylet skip(LedgerIndex ledger) noexcept; -/** The (fixed) index of the object containing the ledger fees. */ +/** + * The (fixed) index of the object containing the ledger fees. + */ Keylet const& feeSettings() noexcept; -/** The (fixed) index of the object containing the ledger negativeUNL. */ +/** + * The (fixed) index of the object containing the ledger negativeUNL. + */ Keylet const& negativeUNL() noexcept; -/** The beginning of an order book */ +/** + * The beginning of an order book + */ Keylet book(Book const& b); -/** The index of a trust line for a given currency - - Note that a trustline is *shared* between two accounts (commonly referred - to as the issuer and the holder); if Alice sets up a trust line to Bob for - BTC, and Bob trusts Alice for BTC, here is only a single BTC trust line - between them. -*/ +/** + * The index of a trust line for a given currency + * + * Note that a trustline is *shared* between two accounts (commonly referred + * to as the issuer and the holder); if Alice sets up a trust line to Bob for + * BTC, and Bob trusts Alice for BTC, here is only a single BTC trust line + * between them. + */ /** @{ */ Keylet trustLine(AccountID const& id0, AccountID const& id1, Currency const& currency) noexcept; @@ -103,7 +118,9 @@ trustLine(AccountID const& id, Issue const& issue) noexcept } /** @} */ -/** An offer from an account */ +/** + * An offer from an account + */ /** @{ */ Keylet offer(AccountID const& id, std::uint32_t seq) noexcept; @@ -115,15 +132,21 @@ offer(uint256 const& key) noexcept } /** @} */ -/** The initial directory page for a specific quality */ +/** + * The initial directory page for a specific quality + */ Keylet quality(Keylet const& k, std::uint64_t q) noexcept; -/** The directory for the next lower quality */ +/** + * The directory for the next lower quality + */ Keylet next(Keylet const& k); -/** A ticket belonging to an account */ +/** + * A ticket belonging to an account + */ /** @{ */ Keylet ticket(AccountID const& id, std::uint32_t ticketSeq); @@ -138,11 +161,21 @@ ticket(uint256 const& key) } /** @} */ -/** A SignerList */ +/** + * A SignerList + */ Keylet signerList(AccountID const& account) noexcept; -/** A Check */ +/** + * A Sponsorship + */ +Keylet +sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept; + +/** + * A Check + */ /** @{ */ Keylet check(AccountID const& id, std::uint32_t seq) noexcept; @@ -154,7 +187,9 @@ check(uint256 const& key) noexcept } /** @} */ -/** A DepositPreauth */ +/** + * A DepositPreauth + */ /** @{ */ Keylet depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept; @@ -173,15 +208,21 @@ depositPreauth(uint256 const& key) noexcept //------------------------------------------------------------------------------ -/** Any ledger entry */ +/** + * Any ledger entry + */ Keylet unchecked(uint256 const& key) noexcept; -/** The root page of an account's directory */ +/** + * The root page of an account's directory + */ Keylet ownerDir(AccountID const& id) noexcept; -/** A page in a directory */ +/** + * A page in a directory + */ /** @{ */ Keylet page(uint256 const& root, std::uint64_t index = 0) noexcept; @@ -194,27 +235,36 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept } /** @} */ -/** An escrow entry */ +/** + * An escrow entry + */ Keylet escrow(AccountID const& src, std::uint32_t seq) noexcept; -/** A PaymentChannel */ +/** + * A PaymentChannel + */ Keylet payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept; -/** NFT page keylets - - Unlike objects whose ledger identifiers are produced by hashing data, - NFT page identifiers are composite identifiers, consisting of the owner's - 160-bit AccountID, followed by a 96-bit value that determines which NFT - tokens are candidates for that page. +/** + * NFT page keylets + * + * Unlike objects whose ledger identifiers are produced by hashing data, + * NFT page identifiers are composite identifiers, consisting of the owner's + * 160-bit AccountID, followed by a 96-bit value that determines which NFT + * tokens are candidates for that page. */ /** @{ */ -/** A keylet for the owner's first possible NFT page. */ +/** + * A keylet for the owner's first possible NFT page. + */ Keylet nftokenPageMin(AccountID const& owner); -/** A keylet for the owner's last possible NFT page. */ +/** + * A keylet for the owner's last possible NFT page. + */ Keylet nftokenPageMax(AccountID const& owner); @@ -222,7 +272,9 @@ Keylet nftokenPage(Keylet const& k, uint256 const& token); /** @} */ -/** An offer from an account to buy or sell an NFT */ +/** + * An offer from an account to buy or sell an NFT + */ Keylet nftokenOffer(AccountID const& owner, std::uint32_t seq); @@ -232,22 +284,30 @@ nftokenOffer(uint256 const& offer) return {ltNFTOKEN_OFFER, offer}; } -/** The directory of buy offers for the specified NFT */ +/** + * The directory of buy offers for the specified NFT + */ Keylet nftBuys(uint256 const& id) noexcept; -/** The directory of sell offers for the specified NFT */ +/** + * The directory of sell offers for the specified NFT + */ Keylet nftSells(uint256 const& id) noexcept; -/** AMM entry */ +/** + * AMM entry + */ Keylet amm(Asset const& issue1, Asset const& issue2) noexcept; Keylet amm(uint256 const& amm) noexcept; -/** A keylet for Delegate object */ +/** + * A keylet for Delegate object + */ Keylet delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept; @@ -362,21 +422,8 @@ struct KeyletDesc bool includeInTests{}; }; -// This list should include all of the keylet functions that take a single -// AccountID parameter. -std::array, 6> const kDirectAccountKeylets{ - {{.function = &keylet::account, .expectedLEName = jss::AccountRoot, .includeInTests = false}, - {.function = &keylet::ownerDir, .expectedLEName = jss::DirectoryNode, .includeInTests = true}, - {.function = &keylet::signerList, .expectedLEName = jss::SignerList, .includeInTests = true}, - // It's normally impossible to create an item at nftpage_min, but - // test it anyway, since the invariant checks for it. - {.function = &keylet::nftokenPageMin, - .expectedLEName = jss::NFTokenPage, - .includeInTests = true}, - {.function = &keylet::nftokenPageMax, - .expectedLEName = jss::NFTokenPage, - .includeInTests = true}, - {.function = &keylet::did, .expectedLEName = jss::DID, .includeInTests = true}}}; +// This list should include all of the keylet functions that take a single AccountID parameter. +extern std::array, 6> const kDirectAccountKeylets; MPTID makeMptID(std::uint32_t sequence, AccountID const& account); diff --git a/include/xrpl/protocol/InnerObjectFormats.h b/include/xrpl/protocol/InnerObjectFormats.h index c8312c3701..7364e83cfd 100644 --- a/include/xrpl/protocol/InnerObjectFormats.h +++ b/include/xrpl/protocol/InnerObjectFormats.h @@ -6,14 +6,16 @@ namespace xrpl { -/** Manages the list of known inner object formats. +/** + * Manages the list of known inner object formats. */ class InnerObjectFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known inner object formats. - */ + /** + * Create the object. + * This will load the object with all the known inner object formats. + */ InnerObjectFormats(); public: diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 3d556e83eb..5cd8731609 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -10,9 +10,10 @@ namespace xrpl { -/** A currency issued by an account. - @see Currency, AccountID, Issue, Book -*/ +/** + * A currency issued by an account. + * @see Currency, AccountID, Issue, Book + */ class Issue { public: @@ -70,7 +71,9 @@ hash_append(Hasher& h, Issue const& r) hash_append(h, r.currency, r.account); } -/** Equality comparison. */ +/** + * Equality comparison. + */ /** @{ */ [[nodiscard]] constexpr bool operator==(Issue const& lhs, Issue const& rhs) @@ -79,12 +82,14 @@ operator==(Issue const& lhs, Issue const& rhs) } /** @} */ -/** Strict weak ordering. */ +/** + * Strict weak ordering. + */ /** @{ */ [[nodiscard]] constexpr std::weak_ordering operator<=>(Issue const& lhs, Issue const& rhs) { - if (auto const c{lhs.currency <=> rhs.currency}; c != 0) + if (auto const c{lhs.currency <=> rhs.currency}; c != 0) // NOLINT(modernize-use-nullptr) return c; if (isXRP(lhs.currency)) @@ -96,7 +101,9 @@ operator<=>(Issue const& lhs, Issue const& rhs) //------------------------------------------------------------------------------ -/** Returns an asset specifier that represents XRP. */ +/** + * Returns an asset specifier that represents XRP. + */ inline Issue const& xrpIssue() { @@ -104,7 +111,9 @@ xrpIssue() return kIssue; } -/** Returns an asset specifier that represents no account and currency. */ +/** + * Returns an asset specifier that represents no account and currency. + */ inline Issue const& noIssue() { diff --git a/include/xrpl/protocol/Keylet.h b/include/xrpl/protocol/Keylet.h index 19704e2a11..48d494f564 100644 --- a/include/xrpl/protocol/Keylet.h +++ b/include/xrpl/protocol/Keylet.h @@ -7,14 +7,15 @@ namespace xrpl { class STLedgerEntry; -/** A pair of SHAMap key and LedgerEntryType. - - A Keylet identifies both a key in the state map - and its ledger entry type. - - @note Keylet is a portmanteau of the words key - and LET, an acronym for LedgerEntryType. -*/ +/** + * A pair of SHAMap key and LedgerEntryType. + * + * A Keylet identifies both a key in the state map + * and its ledger entry type. + * + * @note Keylet is a portmanteau of the words key + * and LET, an acronym for LedgerEntryType. + */ struct Keylet { uint256 key; @@ -24,7 +25,9 @@ struct Keylet { } - /** Returns true if the SLE matches the type */ + /** + * Returns true if the SLE matches the type + */ [[nodiscard]] bool check(STLedgerEntry const&) const; }; diff --git a/include/xrpl/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h index c31e28c37d..385feb2c27 100644 --- a/include/xrpl/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -15,18 +15,20 @@ namespace xrpl { -/** Manages a list of known formats. - - Each format has a name, an associated KeyType (typically an enumeration), - and a predefined @ref SOElement. - - @tparam KeyType The type of key identifying the format. -*/ +/** + * Manages a list of known formats. + * + * Each format has a name, an associated KeyType (typically an enumeration), + * and a predefined @ref SOElement. + * + * @tparam KeyType The type of key identifying the format. + */ template class KnownFormats { public: - /** A known format. + /** + * A known format. */ class Item { @@ -46,7 +48,8 @@ public: "KnownFormats KeyType must be integral or enum."); } - /** Retrieve the name of the format. + /** + * Retrieve the name of the format. */ [[nodiscard]] std::string const& getName() const @@ -54,7 +57,8 @@ public: return name_; } - /** Retrieve the transaction type this format represents. + /** + * Retrieve the transaction type this format represents. */ [[nodiscard]] KeyType getType() const @@ -74,32 +78,35 @@ public: KeyType const type_; }; - /** Create the known formats object. - - Derived classes will load the object with all the known formats. - */ + /** + * Create the known formats object. + * + * Derived classes will load the object with all the known formats. + */ private: KnownFormats() : name_(beast::typeName()) { } public: - /** Destroy the known formats object. - - The defined formats are deleted. - */ + /** + * Destroy the known formats object. + * + * The defined formats are deleted. + */ virtual ~KnownFormats() = default; KnownFormats(KnownFormats const&) = delete; KnownFormats& operator=(KnownFormats const&) = delete; - /** Retrieve the type for a format specified by name. - - If the format name is unknown, an exception is thrown. - - @param name The name of the type. - @return The type. - */ + /** + * Retrieve the type for a format specified by name. + * + * If the format name is unknown, an exception is thrown. + * + * @param name The name of the type. + * @return The type. + */ [[nodiscard]] KeyType findTypeByName(std::string const& name) const { @@ -110,7 +117,8 @@ public: name.substr(0, std::min(name.size(), std::size_t(32))) + "'"); } - /** Retrieve a format based on its type. + /** + * Retrieve a format based on its type. */ [[nodiscard]] Item const* findByType(KeyType type) const @@ -135,7 +143,8 @@ public: } protected: - /** Retrieve a format based on its name. + /** + * Retrieve a format based on its name. */ [[nodiscard]] Item const* findByName(std::string const& name) const @@ -146,15 +155,16 @@ protected: return itr->second; } - /** Add a new format. - - @param name The name of this format. - @param type The type of this format. - @param uniqueFields A std::vector of unique fields - @param commonFields A std::vector of common fields - - @return The created format. - */ + /** + * Add a new format. + * + * @param name The name of this format. + * @param type The type of this format. + * @param uniqueFields A std::vector of unique fields + * @param commonFields A std::vector of common fields + * + * @return The created format. + */ Item const& add(char const* name, KeyType type, diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 5b8a8cc2c5..7c504f6bdd 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -12,28 +12,29 @@ #include namespace xrpl { -/** Identifiers for on-ledger objects. - - Each ledger object requires a unique type identifier, which is stored within the object itself; - this makes it possible to iterate the entire ledger and determine each object's type and verify - that the object you retrieved from a given hash matches the expected type. - - @warning Since these values are stored inside objects stored on the ledger they are part of the - protocol. - **Changing them should be avoided because without special handling, this will result in a hard - fork.** - - @note Values outside this range may be used internally by the code for various purposes, but - attempting to use such values to identify on-ledger objects will result in an invariant failure. - - @note When retiring types, the specific values should not be removed but should be marked as - [[deprecated]]. This is to avoid accidental reuse of identifiers. - - @todo The C++ language does not enable checking for duplicate values here. - If it becomes possible then we should do this. - - @ingroup protocol -*/ +/** + * Identifiers for on-ledger objects. + * + * Each ledger object requires a unique type identifier, which is stored within the object itself; + * this makes it possible to iterate the entire ledger and determine each object's type and verify + * that the object you retrieved from a given hash matches the expected type. + * + * @warning Since these values are stored inside objects stored on the ledger they are part of the + * protocol. + * **Changing them should be avoided because without special handling, this will result in a hard + * fork.** + * + * @note Values outside this range may be used internally by the code for various purposes, but + * attempting to use such values to identify on-ledger objects will result in an invariant failure. + * + * @note When retiring types, the specific values should not be removed but should be marked as + * [[deprecated]]. This is to avoid accidental reuse of identifiers. + * + * @todo The C++ language does not enable checking for duplicate values here. + * If it becomes possible then we should do this. + * + * @ingroup protocol + */ // Protocol-critical, hundreds of usages // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum LedgerEntryType : std::uint16_t { @@ -49,66 +50,72 @@ enum LedgerEntryType : std::uint16_t { #pragma pop_macro("LEDGER_ENTRY") //--------------------------------------------------------------------------- - /** A special type, matching any ledger entry type. - - The value does not represent a concrete type, but rather is used in contexts where the - specific type of a ledger object is unimportant, unknown or unavailable. - - Objects with this special type cannot be created or stored on the ledger. - - \sa keylet::unchecked - */ + /** + * A special type, matching any ledger entry type. + * + * The value does not represent a concrete type, but rather is used in contexts where the + * specific type of a ledger object is unimportant, unknown or unavailable. + * + * Objects with this special type cannot be created or stored on the ledger. + * + * @see keylet::unchecked + */ ltANY = 0, - /** A special type, matching any ledger type except directory nodes. - - The value does not represent a concrete type, but rather is used in contexts where the - ledger object must not be a directory node but its specific type is otherwise unimportant, - unknown or unavailable. - - Objects with this special type cannot be created or stored on the ledger. - - \sa keylet::child + /** + * A special type, matching any ledger type except directory nodes. + * + * The value does not represent a concrete type, but rather is used in contexts where the + * ledger object must not be a directory node but its specific type is otherwise unimportant, + * unknown or unavailable. + * + * Objects with this special type cannot be created or stored on the ledger. + * + * @see keylet::child */ ltCHILD = 0x1CD2, //--------------------------------------------------------------------------- - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltNICKNAME [[deprecated("This object type is not supported and should not be used.")]] = 0x006e, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. + /** + * A legacy, deprecated type. + * + * @deprecated **This object type is not supported and should not be used.** + * Support for this type of object was never implemented. + * No objects of this type were ever created. */ ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] = 0x0067, }; -/** Ledger object flags. - - These flags are specified in ledger objects and modify their behavior. - - @warning Ledger object flags form part of the protocol. - **Changing them should be avoided because without special handling, this will result in a hard - fork.** - - @ingroup protocol -*/ +/** + * Ledger object flags. + * + * These flags are specified in ledger objects and modify their behavior. + * + * @warning Ledger object flags form part of the protocol. + * **Changing them should be avoided because without special handling, this will result in a hard + * fork.** + * + * @ingroup protocol + */ #pragma push_macro("XMACRO") #pragma push_macro("TO_VALUE") #pragma push_macro("VALUE_TO_MAP") @@ -208,7 +215,11 @@ enum LedgerEntryType : std::uint16_t { LEDGER_OBJECT(Loan, \ LSF_FLAG(lsfLoanDefault, 0x00010000) \ LSF_FLAG(lsfLoanImpaired, 0x00020000) \ - LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ + LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \ + \ + LEDGER_OBJECT(Sponsorship, \ + LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \ + LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000)) // clang-format on @@ -285,14 +296,16 @@ getAllLedgerFlags() //------------------------------------------------------------------------------ -/** Holds the list of known ledger entry formats. +/** + * Holds the list of known ledger entry formats. */ class LedgerFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known ledger formats. - */ + /** + * Create the object. + * This will load the object with all the known ledger formats. + */ LedgerFormats(); public: diff --git a/include/xrpl/protocol/LedgerHeader.h b/include/xrpl/protocol/LedgerHeader.h index df8f314c5f..d169e53e2c 100644 --- a/include/xrpl/protocol/LedgerHeader.h +++ b/include/xrpl/protocol/LedgerHeader.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Information about the notional ledger backing the view. */ +/** + * Information about the notional ledger backing the view. + */ struct LedgerHeader { explicit LedgerHeader() = default; @@ -67,15 +69,21 @@ getCloseAgree(LedgerHeader const& info) void addRaw(LedgerHeader const&, Serializer&, bool includeHash = false); -/** Deserialize a ledger header from a byte array. */ +/** + * Deserialize a ledger header from a byte array. + */ LedgerHeader deserializeHeader(Slice data, bool hasHash = false); -/** Deserialize a ledger header (prefixed with 4 bytes) from a byte array. */ +/** + * Deserialize a ledger header (prefixed with 4 bytes) from a byte array. + */ LedgerHeader deserializePrefixedHeader(Slice data, bool hasHash = false); -/** Calculate the hash of a ledger header. */ +/** + * Calculate the hash of a ledger header. + */ uint256 calculateLedgerHash(LedgerHeader const& info); diff --git a/include/xrpl/protocol/LedgerShortcut.h b/include/xrpl/protocol/LedgerShortcut.h index 68c31c4c3c..037621121d 100644 --- a/include/xrpl/protocol/LedgerShortcut.h +++ b/include/xrpl/protocol/LedgerShortcut.h @@ -9,13 +9,19 @@ namespace xrpl { * without needing to specify their exact hash or sequence number. */ enum class LedgerShortcut { - /** The current working ledger (open, not yet closed) */ + /** + * The current working ledger (open, not yet closed) + */ Current, - /** The most recently closed ledger (may not be validated) */ + /** + * The most recently closed ledger (may not be validated) + */ Closed, - /** The most recently validated ledger */ + /** + * The most recently validated ledger + */ Validated }; diff --git a/include/xrpl/protocol/MPTAmount.h b/include/xrpl/protocol/MPTAmount.h index 329d83610e..462092f7dd 100644 --- a/include/xrpl/protocol/MPTAmount.h +++ b/include/xrpl/protocol/MPTAmount.h @@ -60,7 +60,9 @@ public: bool operator<(MPTAmount const& other) const; - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept; @@ -69,14 +71,17 @@ public: return value(); } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept; - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const; @@ -100,14 +105,18 @@ MPTAmount::operator=(beast::Zero) return *this; } -/** Returns true if the amount is not zero */ +/** + * Returns true if the amount is not zero + */ constexpr MPTAmount:: operator bool() const noexcept { return value_ != 0; } -/** Return the sign of the amount */ +/** + * Return the sign of the amount + */ constexpr int MPTAmount::signum() const noexcept { @@ -116,10 +125,11 @@ MPTAmount::signum() const noexcept return (value_ != 0) ? 1 : 0; } -/** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. -*/ +/** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ constexpr MPTAmount::value_type MPTAmount::value() const { diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index 0c495aa57f..7f473da6a2 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -82,7 +82,8 @@ operator<=>(MPTIssue const& lhs, MPTIssue const& rhs) return lhs.mptID_ <=> rhs.mptID_; } -/** MPT is a non-native token. +/** + * MPT is a non-native token. */ inline bool isXRP(MPTID const&) diff --git a/include/xrpl/protocol/NFTSyntheticSerializer.h b/include/xrpl/protocol/NFTSyntheticSerializer.h index a1d8bce985..bef05b9a8f 100644 --- a/include/xrpl/protocol/NFTSyntheticSerializer.h +++ b/include/xrpl/protocol/NFTSyntheticSerializer.h @@ -9,10 +9,9 @@ namespace xrpl::RPC { /** - Adds common synthetic fields to transaction-related JSON responses - - @{ + * Adds common synthetic fields to transaction-related JSON responses */ +/** @{ */ void insertNFTSyntheticInJson(json::Value&, std::shared_ptr const&, TxMeta const&); /** @} */ diff --git a/include/xrpl/protocol/NFTokenID.h b/include/xrpl/protocol/NFTokenID.h index f61c6bd5cb..b1b994eabd 100644 --- a/include/xrpl/protocol/NFTokenID.h +++ b/include/xrpl/protocol/NFTokenID.h @@ -12,13 +12,13 @@ namespace xrpl { /** - Add a `nftoken_ids` field to the `meta` output parameter. - The field is only added to successful NFTokenMint, NFTokenAcceptOffer, - and NFTokenCancelOffer transactions. - - Helper functions are not static because they can be used by Clio. - @{ + * Add a `nftoken_ids` field to the `meta` output parameter. + * The field is only added to successful NFTokenMint, NFTokenAcceptOffer, + * and NFTokenCancelOffer transactions. + * + * Helper functions are not static because they can be used by Clio. */ +/** @{ */ bool canHaveNFTokenID(std::shared_ptr const& serializedTx, TxMeta const& transactionMeta); diff --git a/include/xrpl/protocol/NFTokenOfferID.h b/include/xrpl/protocol/NFTokenOfferID.h index c4a80356bf..4810f7932a 100644 --- a/include/xrpl/protocol/NFTokenOfferID.h +++ b/include/xrpl/protocol/NFTokenOfferID.h @@ -11,12 +11,12 @@ namespace xrpl { /** - Add an `offer_id` field to the `meta` output parameter. - The field is only added to successful NFTokenCreateOffer transactions. - - Helper functions are not static because they can be used by Clio. - @{ + * Add an `offer_id` field to the `meta` output parameter. + * The field is only added to successful NFTokenCreateOffer transactions. + * + * Helper functions are not static because they can be used by Clio. */ +/** @{ */ bool canHaveNFTokenOfferID( std::shared_ptr const& serializedTx, diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index f802cfe058..e83e1c97b6 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -14,64 +14,88 @@ namespace xrpl { -/** Protocol specific constants. - - This information is, implicitly, part of the protocol. - - @note Changing these values without adding code to the - server to detect "pre-change" and "post-change" - will result in a hard fork. - - @ingroup protocol -*/ -/** Smallest legal byte size of a transaction. */ +/** + * Protocol specific constants. + * + * This information is, implicitly, part of the protocol. + * + * @note Changing these values without adding code to the + * server to detect "pre-change" and "post-change" + * will result in a hard fork. + * + * @ingroup protocol + */ +/** + * Smallest legal byte size of a transaction. + */ constexpr std::size_t kTxMinSizeBytes = 32; -/** Largest legal byte size of a transaction. */ +/** + * Largest legal byte size of a transaction. + */ constexpr std::size_t kTxMaxSizeBytes = megabytes(1); -/** The maximum number of unfunded offers to delete at once */ +/** + * The maximum number of unfunded offers to delete at once + */ constexpr std::size_t kUnfundedOfferRemoveLimit = 1000; -/** The maximum number of expired offers to delete at once */ +/** + * The maximum number of expired offers to delete at once + */ constexpr std::size_t kExpiredOfferRemoveLimit = 256; -/** The maximum number of metadata entries allowed in one transaction */ +/** + * The maximum number of metadata entries allowed in one transaction + */ constexpr std::size_t kOversizeMetaDataCap = 5200; -/** The maximum number of entries per directory page */ +/** + * The maximum number of entries per directory page + */ constexpr std::size_t kDirNodeMaxEntries = 32; -/** The maximum number of pages allowed in a directory - - Made obsolete by fixDirectoryLimit amendment. -*/ +/** + * The maximum number of pages allowed in a directory + * + * Made obsolete by fixDirectoryLimit amendment. + */ constexpr std::uint64_t kDirNodeMaxPages = 262144; -/** The maximum number of items in an NFT page */ +/** + * The maximum number of items in an NFT page + */ constexpr std::size_t kDirMaxTokensPerPage = 32; -/** The maximum number of owner directory entries for account to be deletable */ +/** + * The maximum number of owner directory entries for account to be deletable + */ constexpr std::size_t kMaxDeletableDirEntries = 1000; -/** The maximum number of token offers that can be canceled at once */ +/** + * The maximum number of token offers that can be canceled at once + */ constexpr std::size_t kMaxTokenOfferCancelCount = 500; -/** The maximum number of offers in an offer directory for NFT to be burnable */ +/** + * The maximum number of offers in an offer directory for NFT to be burnable + */ constexpr std::size_t kMaxDeletableTokenOfferEntries = 500; -/** The maximum token transfer fee allowed. - - Token transfer fees can range from 0% to 50% and are specified in tenths of - a basis point; that is a value of 1000 represents a transfer fee of 1% and - a value of 10000 represents a transfer fee of 10%. - - Note that for extremely low transfer fees values, it is possible that the - calculated fee will be 0. +/** + * The maximum token transfer fee allowed. + * + * Token transfer fees can range from 0% to 50% and are specified in tenths of + * a basis point; that is a value of 1000 represents a transfer fee of 1% and + * a value of 10000 represents a transfer fee of 10%. + * + * Note that for extremely low transfer fees values, it is possible that the + * calculated fee will be 0. */ constexpr std::uint16_t kMaxTransferFee = 50000; -/** There are 10,000 basis points (bips) in 100%. +/** + * There are 10,000 basis points (bips) in 100%. * * Basis points represent 0.01%. * @@ -116,36 +140,41 @@ tenthBipsOfValue(T value, TenthBips bips) } namespace Lending { -/** The maximum management fee rate allowed by a loan broker in 1/10 bips. - - Valid values are between 0 and 10% inclusive. -*/ +/** + * The maximum management fee rate allowed by a loan broker in 1/10 bips. + * + * Valid values are between 0 and 10% inclusive. + */ constexpr TenthBips16 kMaxManagementFeeRate( unsafeCast(percentageToTenthBips(10).value())); static_assert(kMaxManagementFeeRate == TenthBips16(std::uint16_t(10'000u))); -/** The maximum coverage rate required of a loan broker in 1/10 bips. - - Valid values are between 0 and 100% inclusive. -*/ +/** + * The maximum coverage rate required of a loan broker in 1/10 bips. + * + * Valid values are between 0 and 100% inclusive. + */ constexpr TenthBips32 kMaxCoverRate = percentageToTenthBips(100); static_assert(kMaxCoverRate == TenthBips32(100'000u)); -/** The maximum overpayment fee on a loan in 1/10 bips. -* - Valid values are between 0 and 100% inclusive. -*/ +/** + * The maximum overpayment fee on a loan in 1/10 bips. + * + * Valid values are between 0 and 100% inclusive. + */ constexpr TenthBips32 kMaxOverpaymentFee = percentageToTenthBips(100); static_assert(kMaxOverpaymentFee == TenthBips32(100'000u)); -/** Annualized interest rate of the Loan in 1/10 bips. +/** + * Annualized interest rate of the Loan in 1/10 bips. * * Valid values are between 0 and 100% inclusive. */ constexpr TenthBips32 kMaxInterestRate = percentageToTenthBips(100); static_assert(kMaxInterestRate == TenthBips32(100'000u)); -/** The maximum premium added to the interest rate for late payments on a loan +/** + * The maximum premium added to the interest rate for late payments on a loan * in 1/10 bips. * * Valid values are between 0 and 100% inclusive. @@ -153,7 +182,8 @@ static_assert(kMaxInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxLateInterestRate = percentageToTenthBips(100); static_assert(kMaxLateInterestRate == TenthBips32(100'000u)); -/** The maximum close interest rate charged for repaying a loan early in 1/10 +/** + * The maximum close interest rate charged for repaying a loan early in 1/10 * bips. * * Valid values are between 0 and 100% inclusive. @@ -161,7 +191,8 @@ static_assert(kMaxLateInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxCloseInterestRate = percentageToTenthBips(100); static_assert(kMaxCloseInterestRate == TenthBips32(100'000u)); -/** The maximum overpayment interest rate charged on loan overpayments in 1/10 +/** + * The maximum overpayment interest rate charged on loan overpayments in 1/10 * bips. * * Valid values are between 0 and 100% inclusive. @@ -169,7 +200,8 @@ static_assert(kMaxCloseInterestRate == TenthBips32(100'000u)); constexpr TenthBips32 kMaxOverpaymentInterestRate = percentageToTenthBips(100); static_assert(kMaxOverpaymentInterestRate == TenthBips32(100'000u)); -/** LoanPay transaction cost will be one base fee per X combined payments +/** + * LoanPay transaction cost will be one base fee per X combined payments * * The number of payments is estimated based on the Amount paid and the Loan's * Fixed Payment size. Overpayments (indicated with the tfLoanOverpayment flag) @@ -180,7 +212,8 @@ static_assert(kMaxOverpaymentInterestRate == TenthBips32(100'000u)); */ static constexpr int kLoanPaymentsPerFeeIncrement = 5; -/** Maximum number of combined payments that a LoanPay transaction will process +/** + * Maximum number of combined payments that a LoanPay transaction will process * * This limit is enforced during the loan payment process, and thus is not * estimated. If the limit is hit, no further payments or overpayments will be @@ -205,173 +238,267 @@ static constexpr int kLoanPaymentsPerFeeIncrement = 5; static constexpr int kLoanMaximumPaymentsPerTransaction = 100; } // namespace Lending -/** The maximum length of a URI inside an NFT */ +/** + * The maximum length of a URI inside an NFT + */ constexpr std::size_t kMaxTokenUriLength = 256; -/** The maximum length of a Data element inside a DID */ +/** + * The maximum length of a Data element inside a DID + */ constexpr std::size_t kMaxDidDocumentLength = 256; -/** The maximum length of a URI inside a DID */ +/** + * The maximum length of a URI inside a DID + */ constexpr std::size_t kMaxDidUriLength = 256; -/** The maximum length of an Attestation inside a DID */ +/** + * The maximum length of an Attestation inside a DID + */ constexpr std::size_t kMaxDidDataLength = 256; -/** The maximum length of a domain */ +/** + * The maximum length of a domain + */ constexpr std::size_t kMaxDomainLength = 256; -/** The maximum length of a URI inside a Credential */ +/** + * The maximum length of a URI inside a Credential + */ constexpr std::size_t kMaxCredentialUriLength = 256; -/** The maximum length of a CredentialType inside a Credential */ +/** + * The maximum length of a CredentialType inside a Credential + */ constexpr std::size_t kMaxCredentialTypeLength = 64; -/** The maximum number of credentials can be passed in array */ +/** + * The maximum number of credentials can be passed in array + */ constexpr std::size_t kMaxCredentialsArraySize = 8; -/** The maximum number of credentials can be passed in array for permissioned - * domain */ +/** + * The maximum number of credentials can be passed in array for permissioned + * domain + */ constexpr std::size_t kMaxPermissionedDomainCredentialsArraySize = 10; -/** The maximum length of MPTokenMetadata */ +/** + * The maximum length of MPTokenMetadata + */ constexpr std::size_t kMaxMpTokenMetadataLength = 1024; -/** The maximum amount of MPTokenIssuance */ +/** + * The maximum amount of MPTokenIssuance + */ constexpr std::uint64_t kMaxMpTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; static_assert(Number::kMaxRep >= kMaxMpTokenAmount); -/** The maximum length of Data payload */ +/** + * The maximum length of Data payload + */ constexpr std::size_t kMaxDataPayloadLength = 256; -/** Vault withdrawal policies */ +/** + * Vault withdrawal policies + */ constexpr std::uint8_t kVaultStrategyFirstComeFirstServe = 1; -/** Default IOU scale factor for a Vault */ +/** + * Default IOU scale factor for a Vault + */ constexpr std::uint8_t kVaultDefaultIouScale = 6; -/** Maximum scale factor for a Vault. The number is chosen to ensure that -1 IOU can be always converted to shares. -10^19 > maxMPTokenAmount (2^64-1) > 10^18 */ +/** + * Maximum scale factor for a Vault. The number is chosen to ensure that + * 1 IOU can be always converted to shares. + * 10^19 > maxMPTokenAmount (2^64-1) > 10^18 + */ constexpr std::uint8_t kVaultMaximumIouScale = 18; -/** Maximum recursion depth for vault shares being put as an asset inside - * another vault; counted from 0 */ +/** + * Maximum recursion depth for vault shares being put as an asset inside + * another vault; counted from 0 + */ constexpr std::uint8_t kMaxAssetCheckDepth = 5; -/** A ledger index. */ +/** + * A ledger index. + */ using LedgerIndex = std::uint32_t; constexpr std::uint32_t kFlagLedgerInterval = 256; -/** Returns true if the given ledgerIndex is a voting ledgerIndex */ +/** + * Returns true if the given ledgerIndex is a voting ledgerIndex + */ bool isVotingLedger(LedgerIndex seq); -/** Returns true if the given ledgerIndex is a flag ledgerIndex */ +/** + * Returns true if the given ledgerIndex is a flag ledgerIndex + */ bool isFlagLedger(LedgerIndex seq); -/** A transaction identifier. - The value is computed as the hash of the - canonicalized, serialized transaction object. -*/ +/** + * A transaction identifier. + * The value is computed as the hash of the + * canonicalized, serialized transaction object. + */ using TxID = uint256; -/** The maximum number of trustlines to delete as part of AMM account +/** + * The maximum number of trustlines to delete as part of AMM account * deletion cleanup. */ constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512; -/** The maximum length of a URI inside an Oracle */ +/** + * The maximum length of a URI inside an Oracle + */ constexpr std::size_t kMaxOracleUri = 256; -/** The maximum length of a Provider inside an Oracle */ +/** + * The maximum length of a Provider inside an Oracle + */ constexpr std::size_t kMaxOracleProvider = 256; -/** The maximum size of a data series array inside an Oracle */ +/** + * The maximum size of a data series array inside an Oracle + */ constexpr std::size_t kMaxOracleDataSeries = 10; -/** The maximum length of a SymbolClass inside an Oracle */ +/** + * The maximum length of a SymbolClass inside an Oracle + */ constexpr std::size_t kMaxOracleSymbolClass = 16; -/** The maximum allowed time difference between lastUpdateTime and the time - of the last closed ledger -*/ +/** + * The maximum allowed time difference between lastUpdateTime and the time + * of the last closed ledger + */ constexpr std::size_t kMaxLastUpdateTimeDelta = 300; -/** The maximum price scaling factor +/** + * The maximum price scaling factor */ constexpr std::size_t kMaxPriceScale = 20; -/** The maximum percentage of outliers to trim +/** + * The maximum percentage of outliers to trim */ constexpr std::size_t kMaxTrim = 25; -/** The maximum number of delegate permissions an account can grant +/** + * The maximum number of delegate permissions an account can grant */ constexpr std::size_t kPermissionMaxSize = 10; -/** The maximum number of transactions that can be in a batch. */ +/** + * The maximum number of transactions that can be in a batch. + */ constexpr std::size_t kMaxBatchTxCount = 8; -/** The maximum number of batch signers. */ +/** + * The maximum number of batch signers. + */ constexpr std::size_t kMaxBatchSigners = kMaxBatchTxCount * 3; -/** Length of a secp256k1 scalar in bytes. */ +/** + * Length of a secp256k1 scalar in bytes. + */ constexpr std::size_t kEcScalarLength = kMPT_SCALAR_SIZE; -/** Length of EC point (compressed) */ +/** + * Length of EC point (compressed) + */ constexpr std::size_t kCompressedEcPointLength = 33; -/** Length of one compressed EC point component in an EC ElGamal ciphertext. */ +/** + * Length of one compressed EC point component in an EC ElGamal ciphertext. + */ constexpr std::size_t kEcCiphertextComponentLength = kMPT_ELGAMAL_CIPHER_SIZE; -/** EC ElGamal ciphertext length: two compressed EC points concatenated. */ +/** + * EC ElGamal ciphertext length: two compressed EC points concatenated. + */ constexpr std::size_t kEcGamalEncryptedTotalLength = kMPT_ELGAMAL_TOTAL_SIZE; -/** Length of EC public key (compressed) */ +/** + * Length of EC public key (compressed) + */ constexpr std::size_t kEcPubKeyLength = kMPT_PUBKEY_SIZE; -/** Length of EC private key in bytes */ +/** + * Length of EC private key in bytes + */ constexpr std::size_t kEcPrivKeyLength = kMPT_PRIVKEY_SIZE; -/** Length of the EC blinding factor in bytes */ +/** + * Length of the EC blinding factor in bytes + */ constexpr std::size_t kEcBlindingFactorLength = kMPT_BLINDING_FACTOR_SIZE; -/** Length of Schnorr ZKProof for public key registration (compact form) in bytes */ +/** + * Length of Schnorr ZKProof for public key registration (compact form) in bytes + */ constexpr std::size_t kEcSchnorrProofLength = kMPT_SCHNORR_PROOF_SIZE; -/** Length of Pedersen Commitment (compressed) */ +/** + * Length of Pedersen Commitment (compressed) + */ constexpr std::size_t kEcPedersenCommitmentLength = kMPT_PEDERSEN_COMMIT_SIZE; -/** Length of single bulletproof (range proof for 1 commitment) in bytes */ +/** + * Length of single bulletproof (range proof for 1 commitment) in bytes + */ constexpr std::size_t kEcSingleBulletproofLength = kMPT_SINGLE_BULLETPROOF_SIZE; -/** Length of double bulletproof (range proof for 2 commitments) in bytes */ +/** + * Length of double bulletproof (range proof for 2 commitments) in bytes + */ constexpr std::size_t kEcDoubleBulletproofLength = kMPT_DOUBLE_BULLETPROOF_SIZE; -/** Length of the compact sigma proof component for ConfidentialMPTSend. */ +/** + * Length of the compact sigma proof component for ConfidentialMPTSend. + */ constexpr std::size_t kEcSendSigmaProofLength = SECP256K1_COMPACT_STANDARD_PROOF_SIZE; -/** 192 bytes compact sigma proof + 754 bytes double bulletproof. */ +/** + * 192 bytes compact sigma proof + 754 bytes double bulletproof. + */ constexpr std::size_t kEcSendProofLength = kEcSendSigmaProofLength + kEcDoubleBulletproofLength; -/** Length of the compact sigma proof component for ConfidentialMPTConvertBack. */ +/** + * Length of the compact sigma proof component for ConfidentialMPTConvertBack. + */ constexpr std::size_t kEcConvertBackSigmaProofLength = SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE; -/** 128 bytes compact sigma proof + 688 bytes single bulletproof. */ +/** + * 128 bytes compact sigma proof + 688 bytes single bulletproof. + */ constexpr std::size_t kEcConvertBackProofLength = kEcConvertBackSigmaProofLength + kEcSingleBulletproofLength; -/** Length of the ZKProof for ConfidentialMPTClawback. */ +/** + * Length of the ZKProof for ConfidentialMPTClawback. + */ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE; -/** Extra base fee multiplier charged to confidential MPT transactions. */ +/** + * Extra base fee multiplier charged to confidential MPT transactions. + */ constexpr std::uint32_t kConfidentialFeeMultiplier = 9; -/** Compressed EC point prefix for even y-coordinate */ +/** + * Compressed EC point prefix for even y-coordinate + */ constexpr std::uint8_t kEcCompressedPrefixEvenY = 0x02; -/** Compressed EC point prefix for odd y-coordinate */ +/** + * Compressed EC point prefix for odd y-coordinate + */ constexpr std::uint8_t kEcCompressedPrefixOddY = 0x03; } // namespace xrpl diff --git a/include/xrpl/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h index 13db17fc6e..98301af487 100644 --- a/include/xrpl/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -26,28 +26,29 @@ namespace xrpl { -/** A public key. - - Public keys are used in the public-key cryptography - system used to verify signatures attached to messages. - - The format of the public key is XRPL specific, - information needed to determine the cryptosystem - parameters used is stored inside the key. - - As of this writing two systems are supported: - - secp256k1 - ed25519 - - secp256k1 public keys consist of a 33 byte - compressed public key, with the lead byte equal - to 0x02 or 0x03. - - The ed25519 public keys consist of a 1 byte - prefix constant 0xED, followed by 32 bytes of - public key data. -*/ +/** + * A public key. + * + * Public keys are used in the public-key cryptography + * system used to verify signatures attached to messages. + * + * The format of the public key is XRPL specific, + * information needed to determine the cryptosystem + * parameters used is stored inside the key. + * + * As of this writing two systems are supported: + * + * secp256k1 + * ed25519 + * + * secp256k1 public keys consist of a 33 byte + * compressed public key, with the lead byte equal + * to 0x02 or 0x03. + * + * The ed25519 public keys consist of a 1 byte + * prefix constant 0xED, followed by 32 bytes of + * public key data. + */ class PublicKey { protected: @@ -66,11 +67,12 @@ public: PublicKey& operator=(PublicKey const& other); - /** Create a public key. - - Preconditions: - publicKeyType(slice) != std::nullopt - */ + /** + * Create a public key. + * + * Preconditions: + * publicKeyType(slice) != std::nullopt + */ explicit PublicKey(Slice const& slice); [[nodiscard]] std::uint8_t const* @@ -121,7 +123,8 @@ public: } }; -/** Print the public key to a stream. +/** + * Print the public key to a stream. */ std::ostream& operator<<(std::ostream& os, PublicKey const& pk); @@ -180,39 +183,41 @@ parseBase58(TokenType type, std::string const& s); enum class ECDSACanonicality { Canonical, FullyCanonical }; -/** Determines the canonicality of a signature. - - A canonical signature is in its most reduced form. - For example the R and S components do not contain - additional leading zeroes. However, even in - canonical form, (R,S) and (R,G-S) are both - valid signatures for message M. - - Therefore, to prevent malleability attacks we - define a fully canonical signature as one where: - - R < G - S - - where G is the curve order. - - This routine returns std::nullopt if the format - of the signature is invalid (for example, the - points are encoded incorrectly). - - @return std::nullopt if the signature fails - validity checks. - - @note Only the format of the signature is checked, - no verification cryptography is performed. -*/ +/** + * Determines the canonicality of a signature. + * + * A canonical signature is in its most reduced form. + * For example the R and S components do not contain + * additional leading zeroes. However, even in + * canonical form, (R,S) and (R,G-S) are both + * valid signatures for message M. + * + * Therefore, to prevent malleability attacks we + * define a fully canonical signature as one where: + * + * R < G - S + * + * where G is the curve order. + * + * This routine returns std::nullopt if the format + * of the signature is invalid (for example, the + * points are encoded incorrectly). + * + * @return std::nullopt if the signature fails + * validity checks. + * + * @note Only the format of the signature is checked, + * no verification cryptography is performed. + */ std::optional ecdsaCanonicality(Slice const& sig); -/** Returns the type of public key. - - @return std::nullopt If the public key does not - represent a known type. -*/ +/** + * Returns the type of public key. + * + * @return std::nullopt If the public key does not + * represent a known type. + */ /** @{ */ [[nodiscard]] std::optional publicKeyType(Slice const& slice); @@ -224,7 +229,9 @@ publicKeyType(PublicKey const& publicKey) } /** @} */ -/** Verify a secp256k1 signature on the digest of a message. */ +/** + * Verify a secp256k1 signature on the digest of a message. + */ [[nodiscard]] bool verifyDigest( PublicKey const& publicKey, @@ -232,14 +239,17 @@ verifyDigest( Slice const& sig, bool mustBeFullyCanonical = true) noexcept; -/** Verify a signature on a message. - With secp256k1 signatures, the data is first hashed with - SHA512-Half, and the resulting digest is signed. -*/ +/** + * Verify a signature on a message. + * With secp256k1 signatures, the data is first hashed with + * SHA512-Half, and the resulting digest is signed. + */ [[nodiscard]] bool verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept; -/** Calculate the 160-bit node ID from a node public key. */ +/** + * Calculate the 160-bit node ID from a node public key. + */ NodeID calcNodeID(PublicKey const&); diff --git a/include/xrpl/protocol/Quality.h b/include/xrpl/protocol/Quality.h index de61d79ca5..3475efa977 100644 --- a/include/xrpl/protocol/Quality.h +++ b/include/xrpl/protocol/Quality.h @@ -14,15 +14,16 @@ namespace xrpl { -/** Represents a pair of input and output currencies. - - The input currency can be converted to the output - currency by multiplying by the rate, represented by - Quality. - - For offers, "in" is always TakerPays and "out" is - always TakerGets. -*/ +/** + * Represents a pair of input and output currencies. + * + * The input currency can be converted to the output + * currency by multiplying by the rate, represented by + * Quality. + * + * For offers, "in" is always TakerPays and "out" is + * always TakerGets. + */ template struct TAmounts { @@ -36,7 +37,9 @@ struct TAmounts { } - /** Returns `true` if either quantity is not positive. */ + /** + * Returns `true` if either quantity is not positive. + */ [[nodiscard]] bool empty() const noexcept { @@ -84,11 +87,12 @@ operator!=(TAmounts const& lhs, TAmounts const& rhs) noexcept // XRPL specific constant used for parsing qualities and other things #define QUALITY_ONE 1'000'000'000 -/** Represents the logical ratio of output currency to input currency. - Internally this is stored using a custom floating point representation, - as the inverse of the ratio, so that quality will be descending in - a sequence of actual values that represent qualities. -*/ +/** + * Represents the logical ratio of output currency to input currency. + * Internally this is stored using a custom floating point representation, + * as the inverse of the ratio, so that quality will be descending in + * a sequence of actual values that represent qualities. + */ class Quality { public: @@ -109,26 +113,36 @@ private: public: Quality() = default; - /** Create a quality from the integer encoding of an STAmount */ + /** + * Create a quality from the integer encoding of an STAmount + */ explicit Quality(std::uint64_t value); - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ explicit Quality(Amounts const& amount); - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ template explicit Quality(TAmounts const& amount) : Quality(Amounts(toSTAmount(amount.in), toSTAmount(amount.out))) { } - /** Create a quality from the ratio of two amounts. */ + /** + * Create a quality from the ratio of two amounts. + */ template Quality(Out const& out, In const& in) : Quality(Amounts(toSTAmount(in), toSTAmount(out))) { } - /** Advances to the next higher quality level. */ + /** + * Advances to the next higher quality level. + */ /** @{ */ Quality& operator++(); @@ -137,7 +151,9 @@ public: operator++(int); /** @} */ - /** Advances to the next lower quality level. */ + /** + * Advances to the next lower quality level. + */ /** @{ */ Quality& operator--(); @@ -146,23 +162,27 @@ public: operator--(int); /** @} */ - /** Returns the quality as STAmount. */ + /** + * Returns the quality as STAmount. + */ [[nodiscard]] STAmount rate() const { return amountFromQuality(value_); } - /** Returns the quality rounded up to the specified number - of decimal digits. - */ + /** + * Returns the quality rounded up to the specified number + * of decimal digits. + */ [[nodiscard]] Quality round(int tickSize) const; - /** Returns the scaled amount with in capped. - Math is avoided if the result is exact. The output is clamped - to prevent money creation. - */ + /** + * Returns the scaled amount with in capped. + * Math is avoided if the result is exact. The output is clamped + * to prevent money creation. + */ [[nodiscard]] Amounts ceilIn(Amounts const& amount, STAmount const& limit) const; @@ -180,10 +200,11 @@ public: [[nodiscard]] TAmounts ceilInStrict(TAmounts const& amount, In const& limit, bool roundUp) const; - /** Returns the scaled amount with out capped. - Math is avoided if the result is exact. The input is clamped - to prevent money creation. - */ + /** + * Returns the scaled amount with out capped. + * Math is avoided if the result is exact. The input is clamped + * to prevent money creation. + */ [[nodiscard]] Amounts ceilOut(Amounts const& amount, STAmount const& limit) const; @@ -215,10 +236,11 @@ private: Round... round) const; public: - /** Returns `true` if lhs is lower quality than `rhs`. - Lower quality means the taker receives a worse deal. - Higher quality is better for the taker. - */ + /** + * Returns `true` if lhs is lower quality than `rhs`. + * Lower quality means the taker receives a worse deal. + * Higher quality is better for the taker. + */ friend bool operator<(Quality const& lhs, Quality const& rhs) noexcept { @@ -357,10 +379,11 @@ Quality::ceilOutStrict(TAmounts const& amount, Out const& limit, bool r return ceilTAmountsHelper(amount, limit, amount.out, kCeilOutFnPtr, roundUp); } -/** Calculate the quality of a two-hop path given the two hops. - @param lhs The first leg of the path: input to intermediate. - @param rhs The second leg of the path: intermediate to output. -*/ +/** + * Calculate the quality of a two-hop path given the two hops. + * @param lhs The first leg of the path: input to intermediate. + * @param rhs The second leg of the path: intermediate to output. + */ Quality composedQuality(Quality const& lhs, Quality const& rhs); diff --git a/include/xrpl/protocol/QualityFunction.h b/include/xrpl/protocol/QualityFunction.h index 7830519deb..128b37ce12 100644 --- a/include/xrpl/protocol/QualityFunction.h +++ b/include/xrpl/protocol/QualityFunction.h @@ -12,7 +12,8 @@ namespace xrpl { -/** Average quality of a path as a function of `out`: q(out) = m * out + b, +/** + * Average quality of a path as a function of `out`: q(out) = m * out + b, * where m = -1 / poolGets, b = poolPays / poolGets. If CLOB offer then * `m` is equal to 0 `b` is equal to the offer's quality. The function * is derived by substituting `in` in q = out / in with the swap out formula @@ -45,19 +46,22 @@ public: template QualityFunction(TAmounts const& amounts, std::uint32_t tfee, AMMTag); - /** Combines QF with the next step QF + /** + * Combines QF with the next step QF */ void combine(QualityFunction const& qf); - /** Find output to produce the requested + /** + * Find output to produce the requested * average quality. * @param quality requested average quality (quality limit) */ std::optional outFromAvgQ(Quality const& quality); - /** Return true if the quality function is constant + /** + * Return true if the quality function is constant */ [[nodiscard]] bool isConst() const diff --git a/include/xrpl/protocol/Rate.h b/include/xrpl/protocol/Rate.h index b8b04c8fb9..048787cab5 100644 --- a/include/xrpl/protocol/Rate.h +++ b/include/xrpl/protocol/Rate.h @@ -10,12 +10,13 @@ namespace xrpl { -/** Represents a transfer rate - - Transfer rates are specified as fractions of 1 billion. - For example, a transfer rate of 1% is represented as - 1,010,000,000. -*/ +/** + * Represents a transfer rate + * + * Transfer rates are specified as fractions of 1 billion. + * For example, a transfer rate of 1% is represented as + * 1,010,000,000. + */ struct Rate : private boost::totally_ordered { std::uint32_t value; @@ -65,13 +66,17 @@ STAmount divideRound(STAmount const& amount, Rate const& rate, Asset const& asset, bool roundUp); namespace nft { -/** Given a transfer fee (in basis points) convert it to a transfer rate. */ +/** + * Given a transfer fee (in basis points) convert it to a transfer rate. + */ Rate transferFeeAsRate(std::uint16_t fee); } // namespace nft -/** A transfer rate signifying a 1:1 exchange */ +/** + * A transfer rate signifying a 1:1 exchange + */ extern Rate const kParityRate; } // namespace xrpl diff --git a/include/xrpl/protocol/Rules.h b/include/xrpl/protocol/Rules.h index da2031650f..2c2136b6e8 100644 --- a/include/xrpl/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -11,7 +11,8 @@ namespace xrpl { -/** Check whether a feature is enabled in the current ledger rules +/** + * Check whether a feature is enabled in the current ledger rules * * @param feature The feature to be tested. * @param resultIfNoRules What to return if called from outside a Transactor context. @@ -19,7 +20,8 @@ namespace xrpl { bool isFeatureEnabled(uint256 const& feature, bool resultIfNoRules); -/** Check whether a feature is enabled in the current ledger rules +/** + * Check whether a feature is enabled in the current ledger rules * * @param feature The feature to be tested. * @@ -31,7 +33,9 @@ isFeatureEnabled(uint256 const& feature); class DigestAwareReadView; -/** Rules controlling protocol behavior. */ +/** + * Rules controlling protocol behavior. + */ class Rules { private: @@ -54,11 +58,12 @@ public: Rules() = delete; - /** Construct an empty rule set. - - These are the rules reflected by - the genesis ledger. - */ + /** + * Construct an empty rule set. + * + * These are the rules reflected by + * the genesis ledger. + */ explicit Rules(std::unordered_set> const& presets); private: @@ -80,14 +85,17 @@ private: presets() const; public: - /** Returns `true` if a feature is enabled. */ + /** + * Returns `true` if a feature is enabled. + */ [[nodiscard]] bool enabled(uint256 const& feature) const; - /** Returns `true` if two rule sets are identical. - - @note This is for diagnostics. - */ + /** + * Returns `true` if two rule sets are identical. + * + * @note This is for diagnostics. + */ bool operator==(Rules const&) const; @@ -101,7 +109,8 @@ getCurrentTransactionRules(); void setCurrentTransactionRules(std::optional r); -/** RAII class to set and restore the current transaction rules +/** + * RAII class to set and restore the current transaction rules */ class CurrentTransactionRulesGuard { diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index d97bcb0a1d..21ab7813f9 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -117,16 +117,17 @@ fieldCode(int id, int index) return (id << 16) | index; } -/** Identifies fields. - - Fields are necessary to tag data in signed transactions so that - the binary format of the transaction can be canonicalized. All - SFields are created at compile time. - - Each SField, once constructed, lives until program termination, and there - is only one instance per fieldType/fieldValue pair which serves the - entire application. -*/ +/** + * Identifies fields. + * + * Fields are necessary to tag data in signed transactions so that + * the binary format of the transaction can be canonicalized. All + * SFields are created at compile time. + * + * Each SField, once constructed, lives until program termination, and there + * is only one instance per fieldType/fieldValue pair which serves the + * entire application. + */ class SField { public: @@ -299,7 +300,9 @@ private: static std::unordered_map knownNameToField; }; -/** A field with a type known at compile time. */ +/** + * A field with a type known at compile time. + */ template struct TypedField : SField { @@ -309,7 +312,9 @@ struct TypedField : SField explicit TypedField(PrivateAccessTagT pat, Args&&... args); }; -/** Indicate std::optional field semantics. */ +/** + * Indicate std::optional field semantics. + */ template struct OptionaledField { diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h index 682a7c655e..cb24ee315a 100644 --- a/include/xrpl/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Kind of element in each entry of an SOTemplate. */ +/** + * Kind of element in each entry of an SOTemplate. + */ // 2026 usages, 129 files // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum SOEStyle { @@ -25,13 +27,17 @@ enum SOEStyle { }; // Part of a Python-parsed DSL (transactions.macro); bare enumerator names required by the parser -/** Amount fields that can support MPT */ +/** + * Amount fields that can support MPT + */ // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum SOETxMPTIssue { SoeMptNone, SoeMptSupported, SoeMptNotSupported }; //------------------------------------------------------------------------------ -/** An element in a SOTemplate. */ +/** + * An element in a SOTemplate. + */ class SOElement { // Use std::reference_wrapper so SOElement can be stored in a std::vector. @@ -90,10 +96,11 @@ public: //------------------------------------------------------------------------------ -/** Defines the fields and their attributes within a STObject. - Each subclass of SerializedObject will provide its own template - describing the available fields and their metadata attributes. -*/ +/** + * Defines the fields and their attributes within a STObject. + * Each subclass of SerializedObject will provide its own template + * describing the available fields and their metadata attributes. + */ class SOTemplate { public: @@ -103,14 +110,16 @@ public: SOTemplate& operator=(SOTemplate&& other) = default; - /** Create a template populated with all fields. - After creating the template fields cannot be added, modified, or removed. - */ + /** + * Create a template populated with all fields. + * After creating the template fields cannot be added, modified, or removed. + */ SOTemplate(std::vector uniqueFields, std::vector commonFields = {}); - /** Create a template populated with all fields. - Note: Defers to the vector constructor above. - */ + /** + * Create a template populated with all fields. + * Note: Defers to the vector constructor above. + */ SOTemplate( std::initializer_list uniqueFields, std::initializer_list commonFields = {}); @@ -140,14 +149,18 @@ public: return end(); } - /** The number of entries in this template */ + /** + * The number of entries in this template + */ [[nodiscard]] std::size_t size() const { return elements_.size(); } - /** Retrieve the position of a named field. */ + /** + * Retrieve the position of a named field. + */ [[nodiscard]] int getIndex(SField const&) const; diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 5e53a85129..cc80481582 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -190,7 +190,9 @@ public: [[nodiscard]] int signum() const noexcept; - /** Returns a zero value with the same issuer and currency. */ + /** + * Returns a zero value with the same issuer and currency. + */ [[nodiscard]] STAmount zeroed() const; @@ -255,7 +257,9 @@ public: void clear(Asset const& asset); - /** Set the Issue for this amount. */ + /** + * Set the Issue for this amount. + */ void setIssue(Asset const& asset); @@ -704,7 +708,8 @@ divRoundStrict(STAmount const& v1, STAmount const& v2, Asset const& asset, bool std::uint64_t getRate(STAmount const& offerOut, STAmount const& offerIn); -/** Round an arbitrary precision Amount to the precision of an STAmount that has +/** + * Round an arbitrary precision Amount to the precision of an STAmount that has * a given exponent. * * This is used to ensure that calculations involving IOU amounts do not collect @@ -714,7 +719,6 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); * @param scale An exponent value to establish the precision limit of * `value`. Should be larger than `value.exponent()`. * @param rounding Optional Number rounding mode - * */ [[nodiscard]] STAmount roundToScale( @@ -722,7 +726,8 @@ roundToScale( std::int32_t scale, Number::RoundingMode rounding = Number::getround()); -/** Round an arbitrary precision Number IN PLACE to the precision of a given +/** + * Round an arbitrary precision Number IN PLACE to the precision of a given * Asset. * * This is used to ensure that calculations do not collect dust for IOUs, or @@ -738,7 +743,8 @@ roundToAsset(A const& asset, Number& value) value = STAmount{asset, value}; } -/** Round an arbitrary precision Number to the precision of a given Asset. +/** + * Round an arbitrary precision Number to the precision of a given Asset. * * This is used to ensure that calculations do not collect dust beyond specified * scale for IOUs, or fractional amounts for the integral types XRP and MPT. @@ -780,7 +786,8 @@ canAdd(STAmount const& amt1, STAmount const& amt2); bool canSubtract(STAmount const& amt1, STAmount const& amt2); -/** Get the scale of a Number for a given asset. +/** + * Get the scale of a Number for a given asset. * * "scale" is similar to "exponent", but from the perspective of STAmount, which has different rules * and mantissa ranges for determining the exponent than Number. diff --git a/include/xrpl/protocol/STBase.h b/include/xrpl/protocol/STBase.h index 341c80edd7..acc5500a57 100644 --- a/include/xrpl/protocol/STBase.h +++ b/include/xrpl/protocol/STBase.h @@ -15,7 +15,9 @@ namespace xrpl { -/// Note, should be treated as flags that can be | and & +/** + * Note, should be treated as flags that can be | and & + */ struct JsonOptions { using underlying_t = unsigned int; @@ -53,22 +55,28 @@ struct JsonOptions [[nodiscard]] constexpr auto friend operator!=(JsonOptions lh, JsonOptions rh) noexcept -> bool = default; - /// Returns JsonOptions union of lh and rh + /** + * Returns JsonOptions union of lh and rh + */ [[nodiscard]] constexpr JsonOptions friend operator|(JsonOptions lh, JsonOptions rh) noexcept { return {lh.value | rh.value}; } - /// Returns JsonOptions intersection of lh and rh + /** + * Returns JsonOptions intersection of lh and rh + */ [[nodiscard]] constexpr JsonOptions friend operator&(JsonOptions lh, JsonOptions rh) noexcept { return {lh.value & rh.value}; } - /// Returns JsonOptions binary negation, can be used with & (above) for set - /// difference e.g. `(options & ~JsonOptions::kIncludeDate)` + /** + * Returns JsonOptions binary negation, can be used with & (above) for set + * difference e.g. `(options & ~JsonOptions::kIncludeDate)` + */ [[nodiscard]] constexpr JsonOptions friend operator~(JsonOptions v) noexcept { @@ -103,19 +111,20 @@ class STVar; //------------------------------------------------------------------------------ -/** A type which can be exported to a well known binary format. - - A STBase: - - Always a field - - Can always go inside an eligible enclosing STBase - (such as STArray) - - Has a field name - - Like JSON, a SerializedObject is a basket which has rules - on what it can hold. - - @note "ST" stands for "Serialized Type." -*/ +/** + * A type which can be exported to a well known binary format. + * + * A STBase: + * - Always a field + * - Can always go inside an eligible enclosing STBase + * (such as STArray) + * - Has a field name + * + * Like JSON, a SerializedObject is a basket which has rules + * on what it can hold. + * + * @note "ST" stands for "Serialized Type." + */ class STBase { SField const* fName_; @@ -162,9 +171,10 @@ public: [[nodiscard]] virtual bool isDefault() const; - /** A STBase is a field. - This sets the name. - */ + /** + * A STBase is a field. + * This sets the name. + */ void setFName(SField const& n); diff --git a/include/xrpl/protocol/STExchange.h b/include/xrpl/protocol/STExchange.h index a9c1f57bd8..ad5bd4c012 100644 --- a/include/xrpl/protocol/STExchange.h +++ b/include/xrpl/protocol/STExchange.h @@ -18,7 +18,9 @@ namespace xrpl { -/** Convert between serialized type U and C++ type T. */ +/** + * Convert between serialized type U and C++ type T. + */ template struct STExchange; @@ -90,7 +92,9 @@ struct STExchange //------------------------------------------------------------------------------ -/** Return the value of a field in an STObject as a given type. */ +/** + * Return the value of a field in an STObject as a given type. + */ /** @{ */ template std::optional @@ -119,7 +123,9 @@ get(STObject const& st, TypedField const& f) } /** @} */ -/** Set a field value in an STObject. */ +/** + * Set a field value in an STObject. + */ template void set(STObject& st, TypedField const& f, T&& t) @@ -127,7 +133,9 @@ set(STObject& st, TypedField const& f, T&& t) st.set(STExchange>::set(f, std::forward(t))); } -/** Set a blob field using an init function. */ +/** + * Set a blob field using an init function. + */ template void set(STObject& st, TypedField const& f, std::size_t size, Init&& init) @@ -135,7 +143,9 @@ set(STObject& st, TypedField const& f, std::size_t size, Init&& init) st.set(std::make_unique(f, size, init)); } -/** Set a blob field from data. */ +/** + * Set a blob field from data. + */ template void set(STObject& st, TypedField const& f, void const* data, std::size_t size) @@ -143,7 +153,9 @@ set(STObject& st, TypedField const& f, void const* data, std::size_t siz st.set(std::make_unique(f, data, size)); } -/** Remove a field in an STObject. */ +/** + * Remove a field in an STObject. + */ template void erase(STObject& st, TypedField const& f) diff --git a/include/xrpl/protocol/STLedgerEntry.h b/include/xrpl/protocol/STLedgerEntry.h index a5f449f99c..8731488adb 100644 --- a/include/xrpl/protocol/STLedgerEntry.h +++ b/include/xrpl/protocol/STLedgerEntry.h @@ -33,7 +33,9 @@ public: using const_pointer = std::shared_ptr; using const_ref = std::shared_ptr const&; - /** Create an empty object with the given key and type. */ + /** + * Create an empty object with the given key and type. + */ explicit STLedgerEntry(Keylet const& k); STLedgerEntry(LedgerEntryType type, uint256 const& key); STLedgerEntry(SerialIter& sit, uint256 const& index); @@ -52,10 +54,11 @@ public: [[nodiscard]] json::Value getJson(JsonOptions options = JsonOptions::Values::None) const override; - /** Returns the 'key' (or 'index') of this item. - The key identifies this entry's position in - the SHAMap associative container. - */ + /** + * Returns the 'key' (or 'index') of this item. + * The key identifies this entry's position in + * the SHAMap associative container. + */ [[nodiscard]] uint256 const& key() const; @@ -105,10 +108,11 @@ inline STLedgerEntry::STLedgerEntry( { } -/** Returns the 'key' (or 'index') of this item. - The key identifies this entry's position in - the SHAMap associative container. -*/ +/** + * Returns the 'key' (or 'index') of this item. + * The key identifies this entry's position in + * the SHAMap associative container. + */ inline uint256 const& STLedgerEntry::key() const { diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index fe5c611648..a1cdff22e0 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -229,10 +229,12 @@ public: [[nodiscard]] AccountID getAccountID(SField const& field) const; - /** The account responsible for the fee and authorization: the delegate when - sfDelegate is present, otherwise the account. */ + /** + * The account responsible for the authorization: the delegate when + * sfDelegate is present, otherwise the account. + */ [[nodiscard]] AccountID - getFeePayer() const; + getInitiator() const; [[nodiscard]] Blob getFieldVL(SField const& field) const; @@ -252,103 +254,112 @@ public: [[nodiscard]] STNumber const& getFieldNumber(SField const& field) const; - /** Get the value of a field. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return The value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get the value of a field. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return The value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template T::value_type operator[](TypedField const& f) const; - /** Get the value of a field as a std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return std::nullopt if the field is not present, else the value of - the specified field. - */ + /** + * Get the value of a field as a std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return std::nullopt if the field is not present, else the value of + * the specified field. + */ template std::optional> operator[](OptionaledField const& of) const; - /** Get a modifiable field value. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return A modifiable reference to the value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get a modifiable field value. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return A modifiable reference to the value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template ValueProxy operator[](TypedField const& f); - /** Return a modifiable field value as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return Transparent proxy object to an `optional` holding a modifiable - reference to the value of the specified field. Returns - std::nullopt if the field is not present. - */ + /** + * Return a modifiable field value as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return Transparent proxy object to an `optional` holding a modifiable + * reference to the value of the specified field. Returns + * std::nullopt if the field is not present. + */ template OptionalProxy operator[](OptionaledField const& of); - /** Get the value of a field. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return The value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get the value of a field. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return The value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template [[nodiscard]] T::value_type at(TypedField const& f) const; - /** Get the value of a field as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return std::nullopt if the field is not present, else the value of - the specified field. - */ + /** + * Get the value of a field as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return std::nullopt if the field is not present, else the value of + * the specified field. + */ template [[nodiscard]] std::optional> at(OptionaledField const& of) const; - /** Get a modifiable field value. - @param A TypedField built from an SField value representing the desired - object field. In typical use, the TypedField will be implicitly - constructed. - @return A modifiable reference to the value of the specified field. - @throws STObject::FieldErr if the field is not present. - */ + /** + * Get a modifiable field value. + * @param A TypedField built from an SField value representing the desired + * object field. In typical use, the TypedField will be implicitly + * constructed. + * @return A modifiable reference to the value of the specified field. + * @throws STObject::FieldErr if the field is not present. + */ template ValueProxy at(TypedField const& f); - /** Return a modifiable field value as std::optional - - @param An OptionaledField built from an SField value representing the - desired object field. In typical use, the OptionaledField will be - constructed by using the ~ operator on an SField. - @return Transparent proxy object to an `optional` holding a modifiable - reference to the value of the specified field. Returns - std::nullopt if the field is not present. - */ + /** + * Return a modifiable field value as std::optional + * + * @param An OptionaledField built from an SField value representing the + * desired object field. In typical use, the OptionaledField will be + * constructed by using the ~ operator on an SField. + * @return Transparent proxy object to an `optional` holding a modifiable + * reference to the value of the specified field. Returns + * std::nullopt if the field is not present. + */ template OptionalProxy at(OptionaledField const& of); - /** Set a field. - if the field already exists, it is replaced. - */ + /** + * Set a field. + * if the field already exists, it is replaced. + */ void set(std::unique_ptr v); @@ -503,8 +514,10 @@ public: value_type operator*() const; - /// Do not use operator->() unless the field is required, or you've checked - /// that it's set. + /** + * Do not use operator->() unless the field is required, or you've checked + * that it's set. + */ T const* operator->() const; @@ -604,17 +617,20 @@ public: OptionalProxy& operator=(OptionalProxy const&) = delete; - /** Returns `true` if the field is set. - - Fields with soeDEFAULT and set to the - default value will return `true` - */ + /** + * Returns `true` if the field is set. + * + * Fields with soeDEFAULT and set to the + * default value will return `true` + */ explicit operator bool() const noexcept; operator optional_type() const; - /** Explicit conversion to std::optional */ + /** + * Explicit conversion to std::optional + */ optional_type operator~() const; @@ -761,8 +777,10 @@ STObject::Proxy::operator*() const -> value_type return this->value(); } -/// Do not use operator->() unless the field is required, or you've checked that -/// it's set. +/** + * Do not use operator->() unless the field is required, or you've checked that + * it's set. + */ template T const* STObject::Proxy::operator->() const diff --git a/include/xrpl/protocol/STParsedJSON.h b/include/xrpl/protocol/STParsedJSON.h index 1eeecc8b9e..7189e0ec89 100644 --- a/include/xrpl/protocol/STParsedJSON.h +++ b/include/xrpl/protocol/STParsedJSON.h @@ -9,26 +9,33 @@ namespace xrpl { -/** Maximum JSON object nesting depth permitted during parsing. */ +/** + * Maximum JSON object nesting depth permitted during parsing. + */ inline constexpr std::size_t kMaxParsedJsonDepth = 64; -/** Maximum number of elements permitted in any JSON array field during parsing. - Requests exceeding this limit are rejected with an invalidParams error. */ +/** + * Maximum number of elements permitted in any JSON array field during parsing. + * Requests exceeding this limit are rejected with an invalidParams error. + */ inline constexpr std::size_t kMaxParsedJsonArraySize = 512; -/** Holds the serialized result of parsing an input JSON object. - This does validation and checking on the provided JSON. -*/ +/** + * Holds the serialized result of parsing an input JSON object. + * This does validation and checking on the provided JSON. + */ class STParsedJSONObject { public: - /** Parses and creates an STParsedJSON object. - The result of the parsing is stored in object and error. - Exceptions: - Does not throw. - @param name The name of the JSON field, used in diagnostics. - @param json The JSON-RPC to parse. - */ + /** + * Parses and creates an STParsedJSON object. + * The result of the parsing is stored in object and error. + * + * @note Does not throw. + * + * @param name The name of the JSON field, used in diagnostics. + * @param json The JSON-RPC to parse. + */ STParsedJSONObject(std::string const& name, json::Value const& json); STParsedJSONObject() = delete; @@ -37,10 +44,14 @@ public: operator=(STParsedJSONObject const&) = delete; ~STParsedJSONObject() = default; - /** The STObject if the parse was successful. */ + /** + * The STObject if the parse was successful. + */ std::optional object; - /** On failure, an appropriate set of error values. */ + /** + * On failure, an appropriate set of error values. + */ json::Value error; }; diff --git a/include/xrpl/protocol/STTakesAsset.h b/include/xrpl/protocol/STTakesAsset.h index 70bafd0e91..95667e4868 100644 --- a/include/xrpl/protocol/STTakesAsset.h +++ b/include/xrpl/protocol/STTakesAsset.h @@ -7,7 +7,8 @@ namespace xrpl { -/** Intermediate class for any STBase-derived class to store an Asset. +/** + * Intermediate class for any STBase-derived class to store an Asset. * * In the class definition, this class should be specified as a base class * _instead_ of STBase. @@ -41,7 +42,8 @@ STTakesAsset::associateAsset(Asset const& a) class STLedgerEntry; -/** Associate an Asset with all sMD_NeedsAsset fields in a ledger entry. +/** + * Associate an Asset with all sMD_NeedsAsset fields in a ledger entry. * * This function iterates over all fields in the given ledger entry. For each * field that is set and has the SField::sMD_NeedsAsset metadata flag, it calls @@ -54,7 +56,6 @@ class STLedgerEntry; * * @param sle The ledger entry whose fields will be updated. * @param asset The Asset to associate with the relevant fields. - * */ void associateAsset(STLedgerEntry& sle, Asset const& asset); diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index b36207bf61..989bd11c10 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -55,12 +55,13 @@ public: explicit STTx(SerialIter&& sit); explicit STTx(STObject&& object); - /** Constructs a transaction. - - The returned transaction will have the specified type and - any fields that the callback function adds to the object - that's passed in. - */ + /** + * Constructs a transaction. + * + * The returned transaction will have the specified type and + * any fields that the callback function adds to the object + * that's passed in. + */ STTx(TxType type, std::function assembler); // STObject functions. @@ -92,7 +93,9 @@ public: [[nodiscard]] SeqProxy getSeqProxy() const; - /** Returns the first non-zero value of (Sequence, TicketSequence). */ + /** + * Returns the first non-zero value of (Sequence, TicketSequence). + */ [[nodiscard]] std::uint32_t getSeqValue() const; @@ -114,10 +117,11 @@ public: SecretKey const& secretKey, std::optional> signatureTarget = {}); - /** Check the signature. - @param rules The current ledger rules. - @return `true` if valid signature. If invalid, the error message string. - */ + /** + * Check the signature. + * @param rules The current ledger rules. + * @return `true` if valid signature. If invalid, the error message string. + */ [[nodiscard]] std::expected checkSign(Rules const& rules) const; @@ -141,13 +145,17 @@ public: [[nodiscard]] std::vector const& getBatchTransactionIDs() const; + [[nodiscard]] AccountID + getFeePayerID() const; + private: - /** Check the signature. - @param rules The current ledger rules. - @param sigObject Reference to object that contains the signature fields. - Will be *this more often than not. - @return `true` if valid signature. If invalid, the error message string. - */ + /** + * Check the signature. + * @param rules The current ledger rules. + * @param sigObject Reference to object that contains the signature fields. + * Will be *this more often than not. + * @return `true` if valid signature. If invalid, the error message string. + */ [[nodiscard]] std::expected checkSign(Rules const& rules, STObject const& sigObject) const; @@ -178,17 +186,20 @@ private: bool passesLocalChecks(STObject const& st, std::string&); -/** Sterilize a transaction. - - The transaction is serialized and then deserialized, - ensuring that all equivalent transactions are in canonical - form. This also ensures that program metadata such as - the transaction's digest, are all computed. -*/ +/** + * Sterilize a transaction. + * + * The transaction is serialized and then deserialized, + * ensuring that all equivalent transactions are in canonical + * form. This also ensures that program metadata such as + * the transaction's digest, are all computed. + */ std::shared_ptr sterilize(STTx const& stx); -/** Check whether a transaction is a pseudo-transaction */ +/** + * Check whether a transaction is a pseudo-transaction + */ bool isPseudoTx(STObject const& tx); diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 67a6594419..444fdfa600 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -54,30 +54,32 @@ class STValidation final : public STObject, public CountedObject NetClock::time_point seenTime_; public: - /** Construct a STValidation from a peer from serialized data. - - @param sit Iterator over serialized data - @param lookupNodeID Invocable with signature - NodeID(PublicKey const&) - used to find the Node ID based on the public key - that signed the validation. For manifest based - validators, this should be the NodeID of the master - public key. - @param checkSignature Whether to verify the data was signed properly - - @note Throws if the object is not valid - */ + /** + * Construct a STValidation from a peer from serialized data. + * + * @param sit Iterator over serialized data + * @param lookupNodeID Invocable with signature + * NodeID(PublicKey const&) + * used to find the Node ID based on the public key + * that signed the validation. For manifest based + * validators, this should be the NodeID of the master + * public key. + * @param checkSignature Whether to verify the data was signed properly + * + * @note Throws if the object is not valid + */ template STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature); - /** Construct, sign and trust a new STValidation issued by this node. - - @param signTime When the validation is signed - @param publicKey The current signing public key - @param secretKey The current signing secret key - @param nodeID ID corresponding to node's public master key - @param f callback function to "fill" the validation with necessary data - */ + /** + * Construct, sign and trust a new STValidation issued by this node. + * + * @param signTime When the validation is signed + * @param publicKey The current signing public key + * @param secretKey The current signing secret key + * @param nodeID ID corresponding to node's public master key + * @param f callback function to "fill" the validation with necessary data + */ template STValidation( NetClock::time_point signTime, @@ -183,14 +185,15 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch XRPL_ASSERT(nodeID_.isNonZero(), "xrpl::STValidation::STValidation(SerialIter) : nonzero node"); } -/** Construct, sign and trust a new STValidation issued by this node. - - @param signTime When the validation is signed - @param publicKey The current signing public key - @param secretKey The current signing secret key - @param nodeID ID corresponding to node's public master key - @param f callback function to "fill" the validation with necessary data -*/ +/** + * Construct, sign and trust a new STValidation issued by this node. + * + * @param signTime When the validation is signed + * @param publicKey The current signing public key + * @param secretKey The current signing secret key + * @param nodeID ID corresponding to node's public master key + * @param f callback function to "fill" the validation with necessary data + */ template STValidation::STValidation( NetClock::time_point signTime, diff --git a/include/xrpl/protocol/STVector256.h b/include/xrpl/protocol/STVector256.h index 46a1abc713..5a8418fef5 100644 --- a/include/xrpl/protocol/STVector256.h +++ b/include/xrpl/protocol/STVector256.h @@ -50,7 +50,9 @@ public: void setValue(STVector256 const& v); - /** Retrieve a copy of the vector we contain */ + /** + * Retrieve a copy of the vector we contain + */ explicit operator std::vector() const; @@ -138,7 +140,9 @@ STVector256::setValue(STVector256 const& v) value_ = v.value_; } -/** Retrieve a copy of the vector we contain */ +/** + * Retrieve a copy of the vector we contain + */ inline STVector256:: operator std::vector() const { diff --git a/include/xrpl/protocol/SecretKey.h b/include/xrpl/protocol/SecretKey.h index 8a0d917ab4..6d353acac0 100644 --- a/include/xrpl/protocol/SecretKey.h +++ b/include/xrpl/protocol/SecretKey.h @@ -17,7 +17,9 @@ namespace xrpl { -/** A secret key. */ +/** + * A secret key. + */ class SecretKey { public: @@ -56,11 +58,12 @@ public: return sizeof(buf_); } - /** Convert the secret key to a hexadecimal string. - - @note The operator<< function is deliberately omitted - to avoid accidental exposure of secret key material. - */ + /** + * Convert the secret key to a hexadecimal string. + * + * @note The operator<< function is deliberately omitted + * to avoid accidental exposure of secret key material. + */ [[nodiscard]] std::string toString() const; @@ -97,7 +100,9 @@ operator!=(SecretKey const& lhs, SecretKey const& rhs) = delete; //------------------------------------------------------------------------------ -/** Parse a secret key */ +/** + * Parse a secret key + */ template <> std::optional parseBase58(TokenType type, std::string const& s); @@ -108,38 +113,48 @@ toBase58(TokenType type, SecretKey const& sk) return encodeBase58Token(type, sk.data(), sk.size()); } -/** Create a secret key using secure random numbers. */ +/** + * Create a secret key using secure random numbers. + */ SecretKey randomSecretKey(); -/** Generate a new secret key deterministically. */ +/** + * Generate a new secret key deterministically. + */ SecretKey generateSecretKey(KeyType type, Seed const& seed); -/** Derive the public key from a secret key. */ +/** + * Derive the public key from a secret key. + */ PublicKey derivePublicKey(KeyType type, SecretKey const& sk); -/** Generate a key pair deterministically. - - This algorithm is specific to the XRPL: - - For secp256k1 key pairs, the seed is converted - to a Generator and used to compute the key pair - corresponding to ordinal 0 for the generator. -*/ +/** + * Generate a key pair deterministically. + * + * This algorithm is specific to the XRPL: + * + * For secp256k1 key pairs, the seed is converted + * to a Generator and used to compute the key pair + * corresponding to ordinal 0 for the generator. + */ std::pair generateKeyPair(KeyType type, Seed const& seed); -/** Create a key pair using secure random numbers. */ +/** + * Create a key pair using secure random numbers. + */ std::pair randomKeyPair(KeyType type); -/** Generate a signature for a message digest. - This can only be used with secp256k1 since Ed25519's - security properties come, in part, from how the message - is hashed. -*/ +/** + * Generate a signature for a message digest. + * This can only be used with secp256k1 since Ed25519's + * security properties come, in part, from how the message + * is hashed. + */ /** @{ */ Buffer signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest); @@ -151,10 +166,11 @@ signDigest(KeyType type, SecretKey const& sk, uint256 const& digest) } /** @} */ -/** Generate a signature for a message. - With secp256k1 signatures, the data is first hashed with - SHA512-Half, and the resulting digest is signed. -*/ +/** + * Generate a signature for a message. + * With secp256k1 signatures, the data is first hashed with + * SHA512-Half, and the resulting digest is signed. + */ /** @{ */ Buffer sign(PublicKey const& pk, SecretKey const& sk, Slice const& message); diff --git a/include/xrpl/protocol/Seed.h b/include/xrpl/protocol/Seed.h index a669f52079..4ccdd6707f 100644 --- a/include/xrpl/protocol/Seed.h +++ b/include/xrpl/protocol/Seed.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Seeds are used to generate deterministic secret keys. */ +/** + * Seeds are used to generate deterministic secret keys. + */ class Seed { private: @@ -27,12 +29,15 @@ public: Seed& operator=(Seed const&) = default; - /** Destroy the seed. - The buffer will first be securely erased. - */ + /** + * Destroy the seed. + * The buffer will first be securely erased. + */ ~Seed(); - /** Construct a seed */ + /** + * Construct a seed + */ /** @{ */ explicit Seed(Slice const& slice); explicit Seed(uint128 const& seed); @@ -77,42 +82,52 @@ public: //------------------------------------------------------------------------------ -/** Create a seed using secure random numbers. */ +/** + * Create a seed using secure random numbers. + */ Seed randomSeed(); -/** Generate a seed deterministically. - - The algorithm is specific to the XRPL: - - The seed is calculated as the first 128 bits - of the SHA512-Half of the string text excluding - any terminating null. - - @note This will not attempt to determine the format of - the string (e.g. hex or base58). -*/ +/** + * Generate a seed deterministically. + * + * The algorithm is specific to the XRPL: + * + * The seed is calculated as the first 128 bits + * of the SHA512-Half of the string text excluding + * any terminating null. + * + * @note This will not attempt to determine the format of + * the string (e.g. hex or base58). + */ Seed generateSeed(std::string const& passPhrase); -/** Parse a Base58 encoded string into a seed */ +/** + * Parse a Base58 encoded string into a seed + */ template <> std::optional parseBase58(std::string const& s); -/** Attempt to parse a string as a seed. - - @param str the string to parse - @param rfc1751 true if we should attempt RFC1751 style parsing (deprecated) - * */ +/** + * Attempt to parse a string as a seed. + * + * @param str the string to parse + * @param rfc1751 true if we should attempt RFC1751 style parsing (deprecated) + */ std::optional parseGenericSeed(std::string const& str, bool rfc1751 = true); -/** Encode a Seed in RFC1751 format */ +/** + * Encode a Seed in RFC1751 format + */ std::string seedAs1751(Seed const& seed); -/** Format a seed as a Base58 string */ +/** + * Format a seed as a Base58 string + */ inline std::string toBase58(Seed const& seed) { diff --git a/include/xrpl/protocol/SeqProxy.h b/include/xrpl/protocol/SeqProxy.h index be040cceec..e6a97be0e7 100644 --- a/include/xrpl/protocol/SeqProxy.h +++ b/include/xrpl/protocol/SeqProxy.h @@ -5,33 +5,34 @@ namespace xrpl { -/** A type that represents either a sequence value or a ticket value. - - We use the value() of a SeqProxy in places where a sequence was used - before. An example of this is the sequence of an Offer stored in the - ledger. We do the same thing with the in-ledger identifier of a - Check, Payment Channel, and Escrow. - - Why is this safe? If we use the SeqProxy::value(), how do we know that - each ledger entry will be unique? - - There are two components that make this safe: - - 1. A "TicketCreate" transaction carefully avoids creating a ticket - that corresponds with an already used Sequence or Ticket value. - The transactor does this by referring to the account root's - sequence number. Creating the ticket advances the account root's - sequence number so the same ticket (or sequence) value cannot be - used again. - - 2. When a "TicketCreate" transaction creates a batch of tickets it advances - the account root sequence to one past the largest created ticket. - - Therefore all tickets in a batch other than the first may never have - the same value as a sequence on that same account. And since a ticket - may only be used once there will never be any duplicates within this - account. -*/ +/** + * A type that represents either a sequence value or a ticket value. + * + * We use the value() of a SeqProxy in places where a sequence was used + * before. An example of this is the sequence of an Offer stored in the + * ledger. We do the same thing with the in-ledger identifier of a + * Check, Payment Channel, and Escrow. + * + * Why is this safe? If we use the SeqProxy::value(), how do we know that + * each ledger entry will be unique? + * + * There are two components that make this safe: + * + * 1. A "TicketCreate" transaction carefully avoids creating a ticket + * that corresponds with an already used Sequence or Ticket value. + * The transactor does this by referring to the account root's + * sequence number. Creating the ticket advances the account root's + * sequence number so the same ticket (or sequence) value cannot be + * used again. + * + * 2. When a "TicketCreate" transaction creates a batch of tickets it advances + * the account root sequence to one past the largest created ticket. + * + * Therefore all tickets in a batch other than the first may never have + * the same value as a sequence on that same account. And since a ticket + * may only be used once there will never be any duplicates within this + * account. + */ class SeqProxy { public: @@ -51,7 +52,9 @@ public: SeqProxy& operator=(SeqProxy const& other) = default; - /** Factory function to return a sequence-based SeqProxy */ + /** + * Factory function to return a sequence-based SeqProxy + */ static constexpr SeqProxy sequence(std::uint32_t v) { diff --git a/include/xrpl/protocol/Sign.h b/include/xrpl/protocol/Sign.h index 18f085352d..fad2c35c9e 100644 --- a/include/xrpl/protocol/Sign.h +++ b/include/xrpl/protocol/Sign.h @@ -11,17 +11,18 @@ namespace xrpl { -/** Sign an STObject - - @param st Object to sign - @param prefix Prefix to insert before serialized object when hashing - @param type Signing key type used to derive public key - @param sk Signing secret key - @param sigField Field in which to store the signature on the object. - If not specified the value defaults to `sfSignature`. - - @note If a signature already exists, it is overwritten. -*/ +/** + * Sign an STObject + * + * @param st Object to sign + * @param prefix Prefix to insert before serialized object when hashing + * @param type Signing key type used to derive public key + * @param sk Signing secret key + * @param sigField Field in which to store the signature on the object. + * If not specified the value defaults to `sfSignature`. + * + * @note If a signature already exists, it is overwritten. + */ void sign( STObject& st, @@ -30,14 +31,15 @@ sign( SecretKey const& sk, SF_VL const& sigField = sfSignature); -/** Returns `true` if STObject contains valid signature - - @param st Signed object - @param prefix Prefix inserted before serialized object when hashing - @param pk Public key for verifying signature - @param sigField Object's field containing the signature. - If not specified the value defaults to `sfSignature`. -*/ +/** + * Returns `true` if STObject contains valid signature + * + * @param st Signed object + * @param prefix Prefix inserted before serialized object when hashing + * @param pk Public key for verifying signature + * @param sigField Object's field containing the signature. + * If not specified the value defaults to `sfSignature`. + */ bool verify( STObject const& st, @@ -45,22 +47,25 @@ verify( PublicKey const& pk, SF_VL const& sigField = sfSignature); -/** Return a Serializer suitable for computing a multisigning TxnSignature. */ +/** + * Return a Serializer suitable for computing a multisigning TxnSignature. + */ Serializer buildMultiSigningData(STObject const& obj, AccountID const& signingID); -/** Break the multi-signing hash computation into 2 parts for optimization. - - We can optimize verifying multiple multisignatures by splitting the - data building into two parts; - o A large part that is shared by all of the computations. - o A small part that is unique to each signer in the multisignature. - - The following methods support that optimization: - 1. startMultiSigningData provides the large part which can be shared. - 2. finishMultiSigningData caps the passed in serializer with each - signer's unique data. -*/ +/** + * Break the multi-signing hash computation into 2 parts for optimization. + * + * We can optimize verifying multiple multisignatures by splitting the + * data building into two parts; + * o A large part that is shared by all of the computations. + * o A small part that is unique to each signer in the multisignature. + * + * The following methods support that optimization: + * 1. startMultiSigningData provides the large part which can be shared. + * 2. finishMultiSigningData caps the passed in serializer with each + * signer's unique data. + */ Serializer startMultiSigningData(STObject const& obj); diff --git a/include/xrpl/protocol/SystemParameters.h b/include/xrpl/protocol/SystemParameters.h index b31dd0cd42..6ca36c8d9a 100644 --- a/include/xrpl/protocol/SystemParameters.h +++ b/include/xrpl/protocol/SystemParameters.h @@ -21,22 +21,30 @@ systemName() return kName; } -/** Configure the native currency. */ +/** + * Configure the native currency. + */ -/** Number of drops in the genesis account. */ +/** + * Number of drops in the genesis account. + */ constexpr XRPAmount kInitialXrp{100'000'000'000 * kDropsPerXrp}; static_assert(kInitialXrp.drops() == 100'000'000'000'000'000); static_assert(Number::kMaxRep >= kInitialXrp.drops()); -/** Returns true if the amount does not exceed the initial XRP in existence. */ +/** + * Returns true if the amount does not exceed the initial XRP in existence. + */ inline bool isLegalAmount(XRPAmount const& amount) { return amount <= kInitialXrp; } -/** Returns true if the absolute value of the amount does not exceed the initial - * XRP in existence. */ +/** + * Returns true if the absolute value of the amount does not exceed the initial + * XRP in existence. + */ inline bool isLegalAmountSigned(XRPAmount const& amount) { @@ -51,20 +59,30 @@ systemCurrencyCode() return kCode; } -/** The XRP ledger network's earliest allowed sequence */ +/** + * The XRP ledger network's earliest allowed sequence + */ static constexpr std::uint32_t kXrpLedgerEarliestSeq{32570u}; -/** The XRP Ledger mainnet's earliest ledger with a FeeSettings object. Only - * used in asserts and tests. */ +/** + * The XRP Ledger mainnet's earliest ledger with a FeeSettings object. Only + * used in asserts and tests. + */ static constexpr std::uint32_t kXrpLedgerEarliestFees{562177u}; -/** The minimum amount of support an amendment should have. */ +/** + * The minimum amount of support an amendment should have. + */ constexpr std::ratio<80, 100> kAmendmentMajorityCalcThreshold; -/** The minimum amount of time an amendment must hold a majority */ +/** + * The minimum amount of time an amendment must hold a majority + */ constexpr std::chrono::seconds const kDefaultAmendmentMajorityTime = weeks{2}; } // namespace xrpl -/** Default peer port (IANA registered) */ +/** + * Default peer port (IANA registered) + */ inline constexpr std::uint16_t kDefaultPeerPort{2459}; diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 54b081f358..730d021254 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -225,6 +225,7 @@ enum TERcodes : TERUnderlyingType { // create a pseudo-account terNO_DELEGATE_PERMISSION, // Delegate does not have permission terLOCKED, // MPT is locked + terNO_PERMISSION, // No permission but retry }; //------------------------------------------------------------------------------ @@ -368,6 +369,7 @@ enum TECcodes : TERUnderlyingType { // reclaimed after those networks reset. tecNO_DELEGATE_PERMISSION = 198, tecBAD_PROOF = 199, + tecNO_SPONSOR_PERMISSION = 200, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 461afd24e7..0afdebb898 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -12,29 +12,30 @@ namespace xrpl { -/** Transaction flags. - - These flags are specified in a transaction's 'Flags' field and modify - the behavior of that transaction. - - There are two types of flags: - - (1) Universal flags: these are flags which apply to, and are interpreted the same way by, - all transactions, except, perhaps, to special pseudo-transactions. - - (2) Tx-Specific flags: these are flags which are interpreted according to the type of the - transaction being executed. That is, the same numerical flag value may have different - effects, depending on the transaction being executed. - - @note The universal transaction flags occupy the high-order 8 bits. - The tx-specific flags occupy the remaining 24 bits. - - @warning Transaction flags form part of the protocol. - **Changing them should be avoided because without special handling, this will result in - a hard fork.** - - @ingroup protocol -*/ +/** + * Transaction flags. + * + * These flags are specified in a transaction's 'Flags' field and modify + * the behavior of that transaction. + * + * There are two types of flags: + * + * (1) Universal flags: these are flags which apply to, and are interpreted the same way by, + * all transactions, except, perhaps, to special pseudo-transactions. + * + * (2) Tx-Specific flags: these are flags which are interpreted according to the type of the + * transaction being executed. That is, the same numerical flag value may have different + * effects, depending on the transaction being executed. + * + * @note The universal transaction flags occupy the high-order 8 bits. + * The tx-specific flags occupy the remaining 24 bits. + * + * @warning Transaction flags form part of the protocol. + * **Changing them should be avoided because without special handling, this will result in + * a hard fork.** + * + * @ingroup protocol + */ using FlagValue = std::uint32_t; @@ -102,7 +103,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TRANSACTION(Payment, \ TF_FLAG(tfNoRippleDirect, 0x00010000) \ TF_FLAG(tfPartialPayment, 0x00020000) \ - TF_FLAG(tfLimitQuality, 0x00040000), \ + TF_FLAG(tfLimitQuality, 0x00040000) \ + TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \ MASK_ADJ(0)) \ \ TRANSACTION(TrustSet, \ @@ -141,7 +143,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \ TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \ TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \ - TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \ + TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \ MASK_ADJ(0)) \ \ TRANSACTION(MPTokenAuthorize, \ @@ -215,6 +217,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TF_FLAG(tfLoanDefault, 0x00010000) \ TF_FLAG(tfLoanImpair, 0x00020000) \ TF_FLAG(tfLoanUnimpair, 0x00040000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(SponsorshipSet, \ + TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \ + TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \ + TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \ + TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \ + TF_FLAG(tfDeleteObject, 0x00100000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(SponsorshipTransfer, \ + TF_FLAG(tfSponsorshipEnd, 0x00010000) \ + TF_FLAG(tfSponsorshipCreate, 0x00020000) \ + TF_FLAG(tfSponsorshipReassign, 0x00040000), \ MASK_ADJ(0)) // clang-format on @@ -444,6 +460,12 @@ getAsfFlagMap() #pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP") #pragma pop_macro("ACCOUNTSET_FLAGS") +// Sponsor flags (spf) + +inline constexpr FlagValue spfSponsorFee = 1; +inline constexpr FlagValue spfSponsorReserve = 2; +inline constexpr FlagValue spfSponsorFlagMask = ~(spfSponsorFee | spfSponsorReserve); + } // namespace xrpl // NOLINTEND(readability-identifier-naming) diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index 36eb6d0889..8fb32c93cb 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -8,34 +8,36 @@ namespace xrpl { -/** Transaction type identifiers. - - These are part of the binary message format. - - @ingroup protocol -*/ -/** Transaction type identifiers - - Each ledger object requires a unique type identifier, which is stored - within the object itself; this makes it possible to iterate the entire - ledger and determine each object's type and verify that the object you - retrieved from a given hash matches the expected type. - - @warning Since these values are included in transactions, which are signed - objects, and used by the code to determine the type of transaction - being invoked, they are part of the protocol. **Changing them - should be avoided because without special handling, this will - result in a hard fork.** - - @note When retiring types, the specific values should not be removed but - should be marked as [[deprecated]]. This is to avoid accidental - reuse of identifiers. - - @todo The C++ language does not enable checking for duplicate values - here. If it becomes possible then we should do this. - - @ingroup protocol -*/ +/** + * Transaction type identifiers. + * + * These are part of the binary message format. + * + * @ingroup protocol + */ +/** + * Transaction type identifiers + * + * Each ledger object requires a unique type identifier, which is stored + * within the object itself; this makes it possible to iterate the entire + * ledger and determine each object's type and verify that the object you + * retrieved from a given hash matches the expected type. + * + * @warning Since these values are included in transactions, which are signed + * objects, and used by the code to determine the type of transaction + * being invoked, they are part of the protocol. **Changing them + * should be avoided because without special handling, this will + * result in a hard fork.** + * + * @note When retiring types, the specific values should not be removed but + * should be marked as [[deprecated]]. This is to avoid accidental + * reuse of identifiers. + * + * @todo The C++ language does not enable checking for duplicate values + * here. If it becomes possible then we should do this. + * + * @ingroup protocol + */ // clang-format off // Protocol-critical, hundreds of usages // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) @@ -52,28 +54,38 @@ enum TxType : std::uint16_t #undef TRANSACTION #pragma pop_macro("TRANSACTION") - /** This transaction type is deprecated; it is retained for historical purposes. */ + /** + * This transaction type is deprecated; it is retained for historical purposes. + */ TtNicknameSet [[deprecated("This transaction type is not supported and should not be used.")]] = 6, - /** This transaction type is deprecated; it is retained for historical purposes. */ + /** + * This transaction type is deprecated; it is retained for historical purposes. + */ TtContract [[deprecated("This transaction type is not supported and should not be used.")]] = 9, - /** This identifier was never used, but the slot is reserved for historical purposes. */ + /** + * This identifier was never used, but the slot is reserved for historical purposes. + */ TtSpinalTap [[deprecated("This transaction type is not supported and should not be used.")]] = 11, - /** This transaction type installs a hook. */ + /** + * This transaction type installs a hook. + */ TtHookSet [[maybe_unused]] = 22, }; // clang-format on -/** Manages the list of known transaction formats. +/** + * Manages the list of known transaction formats. */ class TxFormats : public KnownFormats { private: - /** Create the object. - This will load the object with all the known transaction formats. - */ + /** + * Create the object. + * This will load the object with all the known transaction formats. + */ TxFormats(); public: diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 813f1b1615..88652afba9 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -60,7 +60,9 @@ public: STObject& getAffectedNode(uint256 const&); - /** Return a list of accounts affected by this transaction */ + /** + * Return a list of accounts affected by this transaction + */ [[nodiscard]] boost::container::flat_set getAffectedAccounts() const; diff --git a/include/xrpl/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h index 1a3cb96691..25b0d1ffc9 100644 --- a/include/xrpl/protocol/UintTypes.h +++ b/include/xrpl/protocol/UintTypes.h @@ -30,34 +30,50 @@ public: } // namespace detail -/** Directory is an index into the directory of offer books. - The last 64 bits of this are the quality. */ +/** + * Directory is an index into the directory of offer books. + * The last 64 bits of this are the quality. + */ using Directory = BaseUInt<256, detail::DirectoryTag>; -/** Currency is a hash representing a specific currency. */ +/** + * Currency is a hash representing a specific currency. + */ using Currency = BaseUInt<160, detail::CurrencyTag>; -/** NodeID is a 160-bit hash representing one node. */ +/** + * NodeID is a 160-bit hash representing one node. + */ using NodeID = BaseUInt<160, detail::NodeIDTag>; -/** MPTID is a 192-bit value representing MPT Issuance ID, +/** + * MPTID is a 192-bit value representing MPT Issuance ID, * which is a concatenation of a 32-bit sequence (big endian) - * and a 160-bit account */ + * and a 160-bit account + */ using MPTID = BaseUInt<192>; -/** Domain is a 256-bit hash representing a specific domain. */ +/** + * Domain is a 256-bit hash representing a specific domain. + */ using Domain = BaseUInt<256>; -/** XRP currency. */ +/** + * XRP currency. + */ Currency const& xrpCurrency(); -/** A placeholder for empty currencies. */ +/** + * A placeholder for empty currencies. + */ Currency const& noCurrency(); -/** We deliberately disallow the currency that looks like "XRP" because too - many people were using it instead of the correct XRP currency. */ +/** + * We deliberately disallow the currency that looks like "XRP" because too + * many people were using it instead of the correct XRP currency. + */ Currency const& badCurrency(); @@ -67,26 +83,30 @@ isXRP(Currency const& c) return c == beast::kZero; } -/** Returns "", "XRP", or three letter ISO code. */ +/** + * Returns "", "XRP", or three letter ISO code. + */ std::string to_string(Currency const& c); -/** Tries to convert a string to a Currency, returns true on success. - - @note This function will return success if the resulting currency is - badCurrency(). This legacy behavior is unfortunate; changing this - will require very careful checking everywhere and may mean having - to rewrite some unit test code. -*/ +/** + * Tries to convert a string to a Currency, returns true on success. + * + * @note This function will return success if the resulting currency is + * badCurrency(). This legacy behavior is unfortunate; changing this + * will require very careful checking everywhere and may mean having + * to rewrite some unit test code. + */ bool toCurrency(Currency&, std::string const&); -/** Tries to convert a string to a Currency, returns noCurrency() on failure. - - @note This function can return badCurrency(). This legacy behavior is - unfortunate; changing this will require very careful checking - everywhere and may mean having to rewrite some unit test code. -*/ +/** + * Tries to convert a string to a Currency, returns noCurrency() on failure. + * + * @note This function can return badCurrency(). This legacy behavior is + * unfortunate; changing this will require very careful checking + * everywhere and may mean having to rewrite some unit test code. + */ Currency toCurrency(std::string const&); diff --git a/include/xrpl/protocol/Units.h b/include/xrpl/protocol/Units.h index 8b418704d6..169ee2c543 100644 --- a/include/xrpl/protocol/Units.h +++ b/include/xrpl/protocol/Units.h @@ -20,18 +20,26 @@ namespace xrpl { namespace unit { -/** "drops" are the smallest divisible amount of XRP. This is what most - of the code uses. */ +/** + * "drops" are the smallest divisible amount of XRP. This is what most + * of the code uses. + */ struct dropTag; -/** "fee levels" are used by the transaction queue to compare the relative - cost of transactions that require different levels of effort to process. - See also: src/xrpld/app/misc/FeeEscalation.md#fee-level */ +/** + * "fee levels" are used by the transaction queue to compare the relative + * cost of transactions that require different levels of effort to process. + * See also: src/xrpld/app/misc/FeeEscalation.md#fee-level + */ struct feelevelTag; -/** unitless values are plain scalars wrapped in a ValueUnit. They are - used for calculations in this header. */ +/** + * unitless values are plain scalars wrapped in a ValueUnit. They are + * used for calculations in this header. + */ struct unitlessTag; -/** Units to represent basis points (bips) and 1/10 basis points */ +/** + * Units to represent basis points (bips) and 1/10 basis points + */ class BipsTag; class TenthBipsTag; @@ -42,13 +50,14 @@ template concept Valid = std::is_class_v && std::is_object_v && std::is_object_v; -/** `Usable` is checked to ensure that only values with - known valid type tags can be used (sometimes transparently) in - non-unit contexts. At the time of implementation, this includes - all known tags, but more may be added in the future, and they - should not be added automatically unless determined to be - appropriate. -*/ +/** + * `Usable` is checked to ensure that only values with + * known valid type tags can be used (sometimes transparently) in + * non-unit contexts. At the time of implementation, this includes + * all known tags, but more may be added in the future, and they + * should not be added automatically unless determined to be + * appropriate. + */ template concept Usable = Valid && (std::is_same_v || @@ -115,9 +124,11 @@ public: return *this; } - /** Instances with the same unit, and a type that is - "safe" to convert to this one can be converted - implicitly */ + /** + * Instances with the same unit, and a type that is + * "safe" to convert to this one can be converted + * implicitly + */ template Other> constexpr ValueUnit(ValueUnit const& value) requires SafeToCast @@ -260,14 +271,18 @@ public: return value_ < other.value_; } - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept { return value_ != 0; } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept { @@ -276,7 +291,9 @@ public: return value_ ? 1 : 0; } - /** Returns the number of drops */ + /** + * Returns the number of drops + */ // TODO: Move this to a new class, maybe with the old "TaggedFee" name [[nodiscard]] constexpr value_type fee() const @@ -319,10 +336,11 @@ public: } } - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const { diff --git a/include/xrpl/protocol/XRPAmount.h b/include/xrpl/protocol/XRPAmount.h index 3190980ecb..26e10929df 100644 --- a/include/xrpl/protocol/XRPAmount.h +++ b/include/xrpl/protocol/XRPAmount.h @@ -138,7 +138,9 @@ public: return drops_ < other.drops_; } - /** Returns true if the amount is not zero */ + /** + * Returns true if the amount is not zero + */ explicit constexpr operator bool() const noexcept { @@ -150,7 +152,9 @@ public: return drops(); } - /** Return the sign of the amount */ + /** + * Return the sign of the amount + */ [[nodiscard]] constexpr int signum() const noexcept { @@ -159,7 +163,9 @@ public: return (drops_ != 0) ? 1 : 0; } - /** Returns the number of drops */ + /** + * Returns the number of drops + */ [[nodiscard]] constexpr value_type drops() const { @@ -217,10 +223,11 @@ public: return static_cast(drops_); } - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ + /** + * Returns the underlying value. Code SHOULD NOT call this + * function unless the type has been abstracted away, + * e.g. in a templated function. + */ [[nodiscard]] constexpr value_type value() const { @@ -241,7 +248,9 @@ public: } }; -/** Number of drops per 1 XRP */ +/** + * Number of drops per 1 XRP + */ constexpr XRPAmount kDropsPerXrp{1'000'000}; constexpr double diff --git a/include/xrpl/protocol/detail/STVar.h b/include/xrpl/protocol/detail/STVar.h index e95461c253..12026f3d09 100644 --- a/include/xrpl/protocol/detail/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -120,7 +120,8 @@ private: } } - /** Construct requested Serializable Type according to id. + /** + * Construct requested Serializable Type according to id. * The variadic args are: (SField), or (SerialIter, SField). * depth is ignored in former case. */ diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 2980e66070..6ae8ef56fd 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -16,6 +16,7 @@ // Keep it sorted in reverse chronological order. XRPL_FEATURE(DefragDirectories, Supported::No, VoteBehavior::DefaultNo) +XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(ConfidentialTransfer, Supported::Yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 2f403708c3..90810e06d2 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -127,29 +127,32 @@ LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({ \sa keylet::account */ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({ - {sfAccount, SoeRequired}, - {sfSequence, SoeRequired}, - {sfBalance, SoeRequired}, - {sfOwnerCount, SoeRequired}, - {sfPreviousTxnID, SoeRequired}, - {sfPreviousTxnLgrSeq, SoeRequired}, - {sfAccountTxnID, SoeOptional}, - {sfRegularKey, SoeOptional}, - {sfEmailHash, SoeOptional}, - {sfWalletLocator, SoeOptional}, - {sfWalletSize, SoeOptional}, - {sfMessageKey, SoeOptional}, - {sfTransferRate, SoeOptional}, - {sfDomain, SoeOptional}, - {sfTickSize, SoeOptional}, - {sfTicketCount, SoeOptional}, - {sfNFTokenMinter, SoeOptional}, - {sfMintedNFTokens, SoeDefault}, - {sfBurnedNFTokens, SoeDefault}, - {sfFirstNFTokenSequence, SoeOptional}, - {sfAMMID, SoeOptional}, // pseudo-account designator - {sfVaultID, SoeOptional}, // pseudo-account designator - {sfLoanBrokerID, SoeOptional}, // pseudo-account designator + {sfAccount, SoeRequired}, + {sfSequence, SoeRequired}, + {sfBalance, SoeRequired}, + {sfOwnerCount, SoeRequired}, + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfAccountTxnID, SoeOptional}, + {sfRegularKey, SoeOptional}, + {sfEmailHash, SoeOptional}, + {sfWalletLocator, SoeOptional}, + {sfWalletSize, SoeOptional}, + {sfMessageKey, SoeOptional}, + {sfTransferRate, SoeOptional}, + {sfDomain, SoeOptional}, + {sfTickSize, SoeOptional}, + {sfTicketCount, SoeOptional}, + {sfNFTokenMinter, SoeOptional}, + {sfMintedNFTokens, SoeDefault}, + {sfBurnedNFTokens, SoeDefault}, + {sfFirstNFTokenSequence, SoeOptional}, + {sfSponsoredOwnerCount, SoeDefault}, + {sfSponsoringOwnerCount, SoeDefault}, + {sfSponsoringAccountCount, SoeDefault}, + {sfAMMID, SoeOptional}, // pseudo-account designator + {sfVaultID, SoeOptional}, // pseudo-account designator + {sfLoanBrokerID, SoeOptional}, // pseudo-account designator })) /** A ledger object which contains a list of object identifiers. @@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({ {sfHighNode, SoeOptional}, {sfHighQualityIn, SoeOptional}, {sfHighQualityOut, SoeOptional}, + {sfHighSponsor, SoeOptional}, + {sfLowSponsor, SoeOptional}, })) /** The ledger object which lists the network's fee settings. @@ -616,5 +621,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ {sfLoanScale, SoeDefault}, })) +/** A ledger object representing a sponsorship. + \sa keylet::sponsorship + */ +LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({ + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfOwner, SoeRequired}, + {sfSponsee, SoeRequired}, + {sfFeeAmount, SoeOptional}, + {sfMaxFee, SoeOptional}, + {sfRemainingOwnerCount, SoeDefault}, + {sfOwnerNode, SoeRequired}, + {sfSponseeNode, SoeRequired}, +})) + #undef EXPAND #undef LEDGER_ENTRY_DUPLICATE diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 0d453eea11..4ef76c8b75 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -114,6 +114,11 @@ TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bi TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips) TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips) TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69) +TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70) +TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71) +TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72) +TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73) +TYPED_SFIELD(sfSponsorFlags, UINT32, 74) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) @@ -148,6 +153,7 @@ TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SFie TYPED_SFIELD(sfVaultNode, UINT64, 30) TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31) TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::kSmdBaseTen|SField::kSmdDefault) +TYPED_SFIELD(sfSponseeNode, UINT64, 33) // 128-bit TYPED_SFIELD(sfEmailHash, UINT128, 1) @@ -209,6 +215,7 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, TYPED_SFIELD(sfLoanID, UINT256, 38) TYPED_SFIELD(sfReferenceHolding, UINT256, 39) TYPED_SFIELD(sfBlindingFactor, UINT256, 40) +TYPED_SFIELD(sfObjectID, UINT256, 41) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) @@ -268,6 +275,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28) TYPED_SFIELD(sfSignatureReward, AMOUNT, 29) TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30) TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31) +TYPED_SFIELD(sfFeeAmount, AMOUNT, 32) +TYPED_SFIELD(sfMaxFee, AMOUNT, 33) // variable length (common) TYPED_SFIELD(sfPublicKey, VL, 1) @@ -343,6 +352,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) TYPED_SFIELD(sfSubject, ACCOUNT, 24) TYPED_SFIELD(sfBorrower, ACCOUNT, 25) TYPED_SFIELD(sfCounterparty, ACCOUNT, 26) +TYPED_SFIELD(sfSponsor, ACCOUNT, 27) +TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28) +TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29) +TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30) +TYPED_SFIELD(sfSponsee, ACCOUNT, 31) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever) @@ -407,6 +421,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34) UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) UNTYPED_SFIELD(sfBook, OBJECT, 36) UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning) +UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning) // array of objects (common) // ARRAY/1 is reserved for end of array diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index b0c4f66bae..e805596c00 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -1165,6 +1165,35 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback, {sfZKProof, SoeRequired}, })) +/** This transaction transfers sponsorship on an object/account. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer, + Delegation::NotDelegable, + featureSponsor, + NoPriv, + ({ + {sfObjectID, SoeOptional}, + {sfSponsee, SoeOptional}, +})) + +/** This transaction creates a Sponsorship object. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet, + Delegation::Delegable, + featureSponsor, + NoPriv, + ({ + {sfCounterpartySponsor, SoeOptional}, + {sfSponsee, SoeOptional}, + {sfFeeAmount, SoeOptional}, + {sfMaxFee, SoeOptional}, + {sfRemainingOwnerCount, SoeOptional}, +})) + /** 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/include/xrpl/protocol/digest.h b/include/xrpl/protocol/digest.h index c1e70cada2..44fca3d1ea 100644 --- a/include/xrpl/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -12,21 +12,23 @@ namespace xrpl { -/** Message digest functions used in the codebase - - @note These are modeled to meet the requirements of `Hasher` in the - `hash_append` interface, discussed in proposal: - - N3980 "Types Don't Know #" - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html -*/ +/** + * Message digest functions used in the codebase + * + * @note These are modeled to meet the requirements of `Hasher` in the + * `hash_append` interface, discussed in proposal: + * + * N3980 "Types Don't Know #" + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html + */ //------------------------------------------------------------------------------ -/** RIPEMD-160 digest - - @note This uses the OpenSSL implementation -*/ +/** + * RIPEMD-160 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslRipemd160Hasher { public: @@ -46,10 +48,11 @@ private: char ctx_[96]{}; }; -/** SHA-512 digest - - @note This uses the OpenSSL implementation -*/ +/** + * SHA-512 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslSha512Hasher { public: @@ -69,10 +72,11 @@ private: char ctx_[216]{}; }; -/** SHA-256 digest - - @note This uses the OpenSSL implementation -*/ +/** + * SHA-256 digest + * + * @note This uses the OpenSSL implementation + */ struct OpensslSha256Hasher { public: @@ -100,21 +104,22 @@ using sha512_hasher = OpensslSha512Hasher; //------------------------------------------------------------------------------ -/** Returns the RIPEMD-160 digest of the SHA256 hash of the message. - - This operation is used to compute the 160-bit identifier - representing an XRPL account, from a message. Typically the - message is the public key of the account - which is not - stored in the account root. - - The same computation is used regardless of the cryptographic - scheme implied by the public key. For example, the public key - may be an ed25519 public key or a secp256k1 public key. Support - for new cryptographic systems may be added, using the same - formula for calculating the account identifier. - - Meets the requirements of Hasher (in hash_append) -*/ +/** + * Returns the RIPEMD-160 digest of the SHA256 hash of the message. + * + * This operation is used to compute the 160-bit identifier + * representing an XRPL account, from a message. Typically the + * message is the public key of the account - which is not + * stored in the account root. + * + * The same computation is used regardless of the cryptographic + * scheme implied by the public key. For example, the public key + * may be an ed25519 public key or a secp256k1 public key. Support + * for new cryptographic systems may be added, using the same + * formula for calculating the account identifier. + * + * Meets the requirements of Hasher (in hash_append) + */ struct RipeshaHasher { private: @@ -145,11 +150,12 @@ public: namespace detail { -/** Returns the SHA512-Half digest of a message. - - The SHA512-Half is the first 256 bits of the - SHA-512 digest of the message. -*/ +/** + * Returns the SHA512-Half digest of a message. + * + * The SHA512-Half is the first 256 bits of the + * SHA-512 digest of the message. + */ template struct BasicSha512HalfHasher { @@ -201,7 +207,9 @@ using sha512_half_hasher_s = detail::BasicSha512HalfHasher; //------------------------------------------------------------------------------ -/** Returns the SHA512-Half of a series of objects. */ +/** + * Returns the SHA512-Half of a series of objects. + */ template sha512_half_hasher::result_type sha512Half(Args const&... args) @@ -212,12 +220,13 @@ sha512Half(Args const&... args) return static_cast(h); } -/** Returns the SHA512-Half of a series of objects. - - Postconditions: - Temporary memory storing copies of - input messages will be cleared. -*/ +/** + * Returns the SHA512-Half of a series of objects. + * + * Postconditions: + * Temporary memory storing copies of + * input messages will be cleared. + */ template sha512_half_hasher_s::result_type sha512HalfS(Args const&... args) diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 191ed385f3..63e877ca31 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -110,6 +110,7 @@ JSS(accounts); // in: LedgerEntry, Subscribe, handlers/Ledger JSS(accounts_proposed); // in: Subscribe, Unsubscribe JSS(action); // JSS(active); // out: OverlayImpl +JSS(actor); // in/out: AccountTx JSS(acquiring); // out: LedgerRequest JSS(address); // out: PeerImp JSS(affected); // out: AcceptedLedgerTx @@ -133,6 +134,7 @@ JSS(attestation_reward_account); // JSS(auction_slot); // out: amm_info JSS(authorized); // out: AccountLines JSS(authorize); // out: delegate +JSS(authorizer); // in/out: AccountTx JSS(authorized_credentials); // in: ledger_entry DepositPreauth JSS(auth_accounts); // out: amm_info JSS(auth_change); // out: AccountInfo @@ -191,6 +193,7 @@ JSS(converge_time); // out: NetworkOPs JSS(converge_time_s); // out: NetworkOPs JSS(cookie); // out: NetworkOPs JSS(count); // in: AccountTx*, ValidatorList +JSS(counter_party); // in/out: AccountTx JSS(counters); // in/out: retrieve counters JSS(credentials); // in: deposit_authorized JSS(credential_type); // in: LedgerEntry DepositPreauth @@ -270,6 +273,7 @@ JSS(freeze); // out: AccountLines JSS(freeze_peer); // out: AccountLines JSS(deep_freeze); // out: AccountLines JSS(deep_freeze_peer); // out: AccountLines +JSS(delegate_filter); // in/out: AccountTx JSS(frozen_balances); // out: GatewayBalances JSS(full); // in: LedgerClearer, handlers/Ledger JSS(full_reply); // out: PathFind @@ -558,6 +562,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind JSS(source_amount); // in: PathRequest, RipplePathFind JSS(source_currencies); // in: PathRequest, RipplePathFind JSS(source_tag); // out: AccountChannels +JSS(sponsee); // in: LedgerEntry +JSS(sponsor); // in: LedgerEntry +JSS(sponsored); // in: AccountObjects JSS(stand_alone); // out: NetworkOPs JSS(standard_deviation); // out: get_aggregate_price JSS(start); // in: TxHistory diff --git a/include/xrpl/protocol/serialize.h b/include/xrpl/protocol/serialize.h index e758b57d49..130a2366cb 100644 --- a/include/xrpl/protocol/serialize.h +++ b/include/xrpl/protocol/serialize.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Serialize an object to a blob. */ +/** + * Serialize an object to a blob. + */ template Blob serializeBlob(Object const& o) @@ -19,7 +21,9 @@ serializeBlob(Object const& o) return s.peekData(); } -/** Serialize an object to a hex string. */ +/** + * Serialize an object to a hex string. + */ inline std::string serializeHex(STObject const& o) { diff --git a/include/xrpl/protocol/tokens.h b/include/xrpl/protocol/tokens.h index bce7e7b5d6..0ce80030e6 100644 --- a/include/xrpl/protocol/tokens.h +++ b/include/xrpl/protocol/tokens.h @@ -35,17 +35,18 @@ template [[nodiscard]] std::optional parseBase58(TokenType type, std::string const& s); -/** Encode data in Base58Check format using XRPL alphabet - - For details on the format see - https://xrpl.org/base58-encodings.html#base58-encodings - - @param type The type of token to encode. - @param token Pointer to the data to encode. - @param size The size of the data to encode. - - @return the encoded token. -*/ +/** + * Encode data in Base58Check format using XRPL alphabet + * + * For details on the format see + * https://xrpl.org/base58-encodings.html#base58-encodings + * + * @param type The type of token to encode. + * @param token Pointer to the data to encode. + * @param size The size of the data to encode. + * + * @return the encoded token. + */ [[nodiscard]] std::string encodeBase58Token(TokenType type, void const* token, std::size_t size); diff --git a/include/xrpl/protocol_autogen/LedgerEntryBase.h b/include/xrpl/protocol_autogen/LedgerEntryBase.h index 5758adbb24..7902055b85 100644 --- a/include/xrpl/protocol_autogen/LedgerEntryBase.h +++ b/include/xrpl/protocol_autogen/LedgerEntryBase.h @@ -158,7 +158,9 @@ public: } protected: - /** @brief The underlying serialized ledger entry being wrapped. */ + /** + * @brief The underlying serialized ledger entry being wrapped. + */ SLE::const_pointer sle_; }; diff --git a/include/xrpl/protocol_autogen/TransactionBase.h b/include/xrpl/protocol_autogen/TransactionBase.h index 161d718c66..a135709576 100644 --- a/include/xrpl/protocol_autogen/TransactionBase.h +++ b/include/xrpl/protocol_autogen/TransactionBase.h @@ -450,7 +450,9 @@ public: } protected: - /** @brief The underlying transaction object being wrapped. */ + /** + * @brief The underlying transaction object being wrapped. + */ std::shared_ptr tx_; }; diff --git a/include/xrpl/protocol_autogen/ledger_entries/AMM.h b/include/xrpl/protocol_autogen/ledger_entries/AMM.h index 11fd3738c9..e6daaf0043 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AMM.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AMM.h @@ -265,7 +265,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h index f9a12a027f..04249b4e04 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h @@ -447,6 +447,78 @@ public: return this->sle_->isFieldPresent(sfFirstNFTokenSequence); } + /** + * @brief Get sfSponsoredOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoredOwnerCount() const + { + if (hasSponsoredOwnerCount()) + return this->sle_->at(sfSponsoredOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoredOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoredOwnerCount() const + { + return this->sle_->isFieldPresent(sfSponsoredOwnerCount); + } + + /** + * @brief Get sfSponsoringOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoringOwnerCount() const + { + if (hasSponsoringOwnerCount()) + return this->sle_->at(sfSponsoringOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoringOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoringOwnerCount() const + { + return this->sle_->isFieldPresent(sfSponsoringOwnerCount); + } + + /** + * @brief Get sfSponsoringAccountCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsoringAccountCount() const + { + if (hasSponsoringAccountCount()) + return this->sle_->at(sfSponsoringAccountCount); + return std::nullopt; + } + + /** + * @brief Check if sfSponsoringAccountCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsoringAccountCount() const + { + return this->sle_->isFieldPresent(sfSponsoringAccountCount); + } + /** * @brief Get sfAMMID (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -564,7 +636,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) @@ -786,6 +860,39 @@ public: return *this; } + /** + * @brief Set sfSponsoredOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoredOwnerCount(std::decay_t const& value) + { + object_[sfSponsoredOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfSponsoringOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoringOwnerCount(std::decay_t const& value) + { + object_[sfSponsoringOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfSponsoringAccountCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setSponsoringAccountCount(std::decay_t const& value) + { + object_[sfSponsoringAccountCount] = value; + return *this; + } + /** * @brief Set sfAMMID (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h index 6a801308ca..c8b6c2d524 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h @@ -175,7 +175,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAmendments (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h index 2c7479b243..31d0e50c02 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h @@ -219,7 +219,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Check.h b/include/xrpl/protocol_autogen/ledger_entries/Check.h index 5b3fd10b92..d354425c39 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Check.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Check.h @@ -278,7 +278,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Credential.h b/include/xrpl/protocol_autogen/ledger_entries/Credential.h index dfce76e45c..f4d243aea8 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Credential.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Credential.h @@ -228,7 +228,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfSubject (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DID.h b/include/xrpl/protocol_autogen/ledger_entries/DID.h index ad423377e7..71113862c5 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DID.h @@ -202,7 +202,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h index bfe5f5587a..84a33ea2be 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h @@ -181,7 +181,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h index 069bed6b77..c4c4434251 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h @@ -179,7 +179,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h index 50659c33f6..7879f104af 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h @@ -440,7 +440,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h index f3c033d26d..106d69722f 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h @@ -372,7 +372,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h index 8f43d3b782..21478f749d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h +++ b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h @@ -294,7 +294,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfBaseFee (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h index f1d3684b55..2c6747b07b 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h @@ -139,7 +139,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfFirstLedgerSequence (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Loan.h b/include/xrpl/protocol_autogen/ledger_entries/Loan.h index 5d837736ec..a0abf9bd97 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Loan.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Loan.h @@ -616,7 +616,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h index 88f05e3433..281af2cfeb 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h @@ -387,7 +387,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h index 379cfe53f5..874d779d09 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h @@ -335,7 +335,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h index b6c77093ac..8518a0fe14 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h @@ -420,7 +420,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfIssuer (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h index 072d3721f9..61aaccacdc 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h @@ -217,7 +217,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h index 5e00cb1120..1aea6d6b01 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h @@ -166,7 +166,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousPageMin (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h index 7ca9729082..a35865202d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h @@ -199,7 +199,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfDisabledValidators (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Offer.h b/include/xrpl/protocol_autogen/ledger_entries/Offer.h index f51b54cfd2..e3539fc1fc 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Offer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Offer.h @@ -268,7 +268,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h index 902032f94f..727e63ea84 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h @@ -231,7 +231,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h index 61a4e2d044..4d00fbb7e5 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h @@ -339,7 +339,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h index 638dda2420..e793e2d658 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h @@ -157,7 +157,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h index e8debfe792..dda1b78e66 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h +++ b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h @@ -243,6 +243,54 @@ public: { return this->sle_->isFieldPresent(sfHighQualityOut); } + + /** + * @brief Get sfHighSponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getHighSponsor() const + { + if (hasHighSponsor()) + return this->sle_->at(sfHighSponsor); + return std::nullopt; + } + + /** + * @brief Check if sfHighSponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasHighSponsor() const + { + return this->sle_->isFieldPresent(sfHighSponsor); + } + + /** + * @brief Get sfLowSponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getLowSponsor() const + { + if (hasLowSponsor()) + return this->sle_->at(sfLowSponsor); + return std::nullopt; + } + + /** + * @brief Check if sfLowSponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasLowSponsor() const + { + return this->sle_->isFieldPresent(sfLowSponsor); + } }; /** @@ -287,7 +335,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfBalance (SoeRequired) @@ -410,6 +460,28 @@ public: return *this; } + /** + * @brief Set sfHighSponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + RippleStateBuilder& + setHighSponsor(std::decay_t const& value) + { + object_[sfHighSponsor] = value; + return *this; + } + + /** + * @brief Set sfLowSponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + RippleStateBuilder& + setLowSponsor(std::decay_t const& value) + { + object_[sfLowSponsor] = value; + return *this; + } + /** * @brief Build and return the completed RippleState wrapper. * @param index The ledger entry index. diff --git a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h index 443e5588f9..3aff8fa51b 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h +++ b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h @@ -181,7 +181,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfOwner (SoeOptional) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h b/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h new file mode 100644 index 0000000000..065e655682 --- /dev/null +++ b/include/xrpl/protocol_autogen/ledger_entries/Sponsorship.h @@ -0,0 +1,346 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::ledger_entries { + +class SponsorshipBuilder; + +/** + * @brief Ledger Entry: Sponsorship + * + * Type: ltSPONSORSHIP (0x0090) + * RPC Name: sponsorship + * + * Immutable wrapper around SLE providing type-safe field access. + * Use SponsorshipBuilder to construct new ledger entries. + */ +class Sponsorship : public LedgerEntryBase +{ +public: + static constexpr LedgerEntryType entryType = ltSPONSORSHIP; + + /** + * @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + explicit Sponsorship(SLE::const_pointer sle) + : LedgerEntryBase(std::move(sle)) + { + // Verify ledger entry type + if (sle_->getType() != entryType) + { + throw std::runtime_error("Invalid ledger entry type for Sponsorship"); + } + } + + // Ledger entry-specific field getters + + /** + * @brief Get sfPreviousTxnID (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getPreviousTxnID() const + { + return this->sle_->at(sfPreviousTxnID); + } + + /** + * @brief Get sfPreviousTxnLgrSeq (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getPreviousTxnLgrSeq() const + { + return this->sle_->at(sfPreviousTxnLgrSeq); + } + + /** + * @brief Get sfOwner (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getOwner() const + { + return this->sle_->at(sfOwner); + } + + /** + * @brief Get sfSponsee (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getSponsee() const + { + return this->sle_->at(sfSponsee); + } + + /** + * @brief Get sfFeeAmount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getFeeAmount() const + { + if (hasFeeAmount()) + return this->sle_->at(sfFeeAmount); + return std::nullopt; + } + + /** + * @brief Check if sfFeeAmount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFeeAmount() const + { + return this->sle_->isFieldPresent(sfFeeAmount); + } + + /** + * @brief Get sfMaxFee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getMaxFee() const + { + if (hasMaxFee()) + return this->sle_->at(sfMaxFee); + return std::nullopt; + } + + /** + * @brief Check if sfMaxFee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasMaxFee() const + { + return this->sle_->isFieldPresent(sfMaxFee); + } + + /** + * @brief Get sfRemainingOwnerCount (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getRemainingOwnerCount() const + { + if (hasRemainingOwnerCount()) + return this->sle_->at(sfRemainingOwnerCount); + return std::nullopt; + } + + /** + * @brief Check if sfRemainingOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasRemainingOwnerCount() const + { + return this->sle_->isFieldPresent(sfRemainingOwnerCount); + } + + /** + * @brief Get sfOwnerNode (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getOwnerNode() const + { + return this->sle_->at(sfOwnerNode); + } + + /** + * @brief Get sfSponseeNode (SoeRequired) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getSponseeNode() const + { + return this->sle_->at(sfSponseeNode); + } +}; + +/** + * @brief Builder for Sponsorship ledger entries. + * + * Provides a fluent interface for constructing ledger entries with method chaining. + * Uses STObject internally for flexible ledger entry construction. + * Inherits common field setters from LedgerEntryBuilderBase. + */ +class SponsorshipBuilder : public LedgerEntryBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipBuilder with required fields. + * @param previousTxnID The sfPreviousTxnID field value. + * @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value. + * @param owner The sfOwner field value. + * @param sponsee The sfSponsee field value. + * @param ownerNode The sfOwnerNode field value. + * @param sponseeNode The sfSponseeNode field value. + */ + SponsorshipBuilder(std::decay_t const& previousTxnID,std::decay_t const& previousTxnLgrSeq,std::decay_t const& owner,std::decay_t const& sponsee,std::decay_t const& ownerNode,std::decay_t const& sponseeNode) + : LedgerEntryBuilderBase(ltSPONSORSHIP) + { + setPreviousTxnID(previousTxnID); + setPreviousTxnLgrSeq(previousTxnLgrSeq); + setOwner(owner); + setSponsee(sponsee); + setOwnerNode(ownerNode); + setSponseeNode(sponseeNode); + } + + /** + * @brief Construct a SponsorshipBuilder from an existing SLE object. + * @param sle The existing ledger entry to copy from. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + SponsorshipBuilder(SLE::const_pointer sle) + { + if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP) + { + throw std::runtime_error("Invalid ledger entry type for Sponsorship"); + } + object_ = *sle; + } + + /** + * @brief Ledger entry-specific field setters + */ + + /** + * @brief Set sfPreviousTxnID (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setPreviousTxnID(std::decay_t const& value) + { + object_[sfPreviousTxnID] = value; + return *this; + } + + /** + * @brief Set sfPreviousTxnLgrSeq (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setPreviousTxnLgrSeq(std::decay_t const& value) + { + object_[sfPreviousTxnLgrSeq] = value; + return *this; + } + + /** + * @brief Set sfOwner (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setOwner(std::decay_t const& value) + { + object_[sfOwner] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Set sfFeeAmount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setFeeAmount(std::decay_t const& value) + { + object_[sfFeeAmount] = value; + return *this; + } + + /** + * @brief Set sfMaxFee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setMaxFee(std::decay_t const& value) + { + object_[sfMaxFee] = value; + return *this; + } + + /** + * @brief Set sfRemainingOwnerCount (SoeDefault) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setRemainingOwnerCount(std::decay_t const& value) + { + object_[sfRemainingOwnerCount] = value; + return *this; + } + + /** + * @brief Set sfOwnerNode (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setOwnerNode(std::decay_t const& value) + { + object_[sfOwnerNode] = value; + return *this; + } + + /** + * @brief Set sfSponseeNode (SoeRequired) + * @return Reference to this builder for method chaining. + */ + SponsorshipBuilder& + setSponseeNode(std::decay_t const& value) + { + object_[sfSponseeNode] = value; + return *this; + } + + /** + * @brief Build and return the completed Sponsorship wrapper. + * @param index The ledger entry index. + * @return The constructed ledger entry wrapper. + */ + Sponsorship + build(uint256 const& index) + { + return Sponsorship{std::make_shared(std::move(object_), index)}; + } +}; + +} // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h index 6fa5b57f6c..e1205bdb67 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h @@ -143,7 +143,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Vault.h b/include/xrpl/protocol_autogen/ledger_entries/Vault.h index d1aaeb4ed8..2bf92b4f5d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Vault.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Vault.h @@ -339,7 +339,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfPreviousTxnID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h index 3f8058a4a1..bfd7a0a8b8 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h @@ -196,7 +196,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h index e24009a4b7..4872d4063e 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h @@ -170,7 +170,9 @@ public: object_ = *sle; } - /** @brief Ledger entry-specific field setters */ + /** + * @brief Ledger entry-specific field setters + */ /** * @brief Set sfAccount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMBid.h b/include/xrpl/protocol_autogen/transactions/AMMBid.h index cd2792e810..30a2b6f2ab 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMBid.h +++ b/include/xrpl/protocol_autogen/transactions/AMMBid.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMClawback.h b/include/xrpl/protocol_autogen/transactions/AMMClawback.h index ccbd7d99e6..38aba892c4 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMClawback.h +++ b/include/xrpl/protocol_autogen/transactions/AMMClawback.h @@ -154,7 +154,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfHolder (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMCreate.h b/include/xrpl/protocol_autogen/transactions/AMMCreate.h index cc88428e7a..c6ccd4e860 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMCreate.h +++ b/include/xrpl/protocol_autogen/transactions/AMMCreate.h @@ -127,7 +127,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAmount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMDelete.h b/include/xrpl/protocol_autogen/transactions/AMMDelete.h index 4cc0497c32..05899a46c8 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDelete.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDelete.h @@ -114,7 +114,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h index e01332c3e2..5416547dab 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h @@ -246,7 +246,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMVote.h b/include/xrpl/protocol_autogen/transactions/AMMVote.h index b19d440c84..7dce3c252f 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMVote.h +++ b/include/xrpl/protocol_autogen/transactions/AMMVote.h @@ -127,7 +127,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h index 196f0faba2..81258f22d6 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h @@ -220,7 +220,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AccountDelete.h b/include/xrpl/protocol_autogen/transactions/AccountDelete.h index c0346d9499..cf6e97bb63 100644 --- a/include/xrpl/protocol_autogen/transactions/AccountDelete.h +++ b/include/xrpl/protocol_autogen/transactions/AccountDelete.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/AccountSet.h b/include/xrpl/protocol_autogen/transactions/AccountSet.h index 7ddda45752..55c449e78e 100644 --- a/include/xrpl/protocol_autogen/transactions/AccountSet.h +++ b/include/xrpl/protocol_autogen/transactions/AccountSet.h @@ -346,7 +346,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfEmailHash (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/Batch.h b/include/xrpl/protocol_autogen/transactions/Batch.h index 00f553ada7..1a59d2b4c0 100644 --- a/include/xrpl/protocol_autogen/transactions/Batch.h +++ b/include/xrpl/protocol_autogen/transactions/Batch.h @@ -123,7 +123,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfRawTransactions (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCancel.h b/include/xrpl/protocol_autogen/transactions/CheckCancel.h index 4f7534278b..b75b717e3f 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCancel.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCancel.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfCheckID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCash.h b/include/xrpl/protocol_autogen/transactions/CheckCash.h index a58a20c57e..c742a15154 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCash.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCash.h @@ -153,7 +153,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfCheckID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CheckCreate.h b/include/xrpl/protocol_autogen/transactions/CheckCreate.h index 17f985ac63..63e55f8604 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCreate.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCreate.h @@ -191,7 +191,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/Clawback.h b/include/xrpl/protocol_autogen/transactions/Clawback.h index ecd7ebe7a2..9a3a7f9feb 100644 --- a/include/xrpl/protocol_autogen/transactions/Clawback.h +++ b/include/xrpl/protocol_autogen/transactions/Clawback.h @@ -126,7 +126,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAmount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h index 2b16590649..c80fc81dc5 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTClawback.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h index f7a4cb601a..dec7f733c9 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvert.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h index 68bf326645..53a8e64125 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTConvertBack.h @@ -203,7 +203,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h index bb932080d8..848da42a41 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTMergeInbox.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h index 2d8a77d56f..806a2586e9 100644 --- a/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h +++ b/include/xrpl/protocol_autogen/transactions/ConfidentialMPTSend.h @@ -268,7 +268,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialAccept.h b/include/xrpl/protocol_autogen/transactions/CredentialAccept.h index 152c18ea09..f2ab546320 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialAccept.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialAccept.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfIssuer (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialCreate.h b/include/xrpl/protocol_autogen/transactions/CredentialCreate.h index d7e056f590..6cf09c852b 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialCreate.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialCreate.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSubject (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/CredentialDelete.h b/include/xrpl/protocol_autogen/transactions/CredentialDelete.h index 512f230a26..24a2bfa62a 100644 --- a/include/xrpl/protocol_autogen/transactions/CredentialDelete.h +++ b/include/xrpl/protocol_autogen/transactions/CredentialDelete.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSubject (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/DIDDelete.h b/include/xrpl/protocol_autogen/transactions/DIDDelete.h index 5f90821bfd..304287883d 100644 --- a/include/xrpl/protocol_autogen/transactions/DIDDelete.h +++ b/include/xrpl/protocol_autogen/transactions/DIDDelete.h @@ -86,7 +86,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Build and return the DIDDelete wrapper. diff --git a/include/xrpl/protocol_autogen/transactions/DIDSet.h b/include/xrpl/protocol_autogen/transactions/DIDSet.h index 27242cda71..67e5ba23c5 100644 --- a/include/xrpl/protocol_autogen/transactions/DIDSet.h +++ b/include/xrpl/protocol_autogen/transactions/DIDSet.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDIDDocument (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/DelegateSet.h b/include/xrpl/protocol_autogen/transactions/DelegateSet.h index 1f454ff4a1..592a778952 100644 --- a/include/xrpl/protocol_autogen/transactions/DelegateSet.h +++ b/include/xrpl/protocol_autogen/transactions/DelegateSet.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAuthorize (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/DepositPreauth.h b/include/xrpl/protocol_autogen/transactions/DepositPreauth.h index d303ffbeff..b5d575aac5 100644 --- a/include/xrpl/protocol_autogen/transactions/DepositPreauth.h +++ b/include/xrpl/protocol_autogen/transactions/DepositPreauth.h @@ -186,7 +186,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAuthorize (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/EnableAmendment.h b/include/xrpl/protocol_autogen/transactions/EnableAmendment.h index b397c5df87..e811ca16df 100644 --- a/include/xrpl/protocol_autogen/transactions/EnableAmendment.h +++ b/include/xrpl/protocol_autogen/transactions/EnableAmendment.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerSequence (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCancel.h b/include/xrpl/protocol_autogen/transactions/EscrowCancel.h index 4da943c351..e7e49eca0d 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCancel.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCancel.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h index 35775c31ae..b994e4ec07 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h @@ -217,7 +217,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h index f6ca73d209..2476def5c2 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOwner (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h index 52723ad5eb..af86dea0b0 100644 --- a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h +++ b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerFixType (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h index 4842381362..875e0a4c5e 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverClawback.h @@ -139,7 +139,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h index 98cebdccb2..38cc113844 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverDeposit.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h index e734c6802a..56a93acbb4 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerCoverWithdraw.h @@ -165,7 +165,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h index 55c0a12381..29b3a787fd 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h b/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h index 854022242d..41c87c281d 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h +++ b/include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h @@ -255,7 +255,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanDelete.h b/include/xrpl/protocol_autogen/transactions/LoanDelete.h index 70c9e50097..8ed537b37a 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanDelete.h +++ b/include/xrpl/protocol_autogen/transactions/LoanDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanManage.h b/include/xrpl/protocol_autogen/transactions/LoanManage.h index f11782c4e1..5eb95d21b1 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanManage.h +++ b/include/xrpl/protocol_autogen/transactions/LoanManage.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanPay.h b/include/xrpl/protocol_autogen/transactions/LoanPay.h index 4012225e17..8e1faeb981 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanPay.h +++ b/include/xrpl/protocol_autogen/transactions/LoanPay.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/LoanSet.h b/include/xrpl/protocol_autogen/transactions/LoanSet.h index 3fa3c905c2..2cadebd02e 100644 --- a/include/xrpl/protocol_autogen/transactions/LoanSet.h +++ b/include/xrpl/protocol_autogen/transactions/LoanSet.h @@ -500,7 +500,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLoanBrokerID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h b/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h index 90440c41f9..2fb93eaf35 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenAuthorize.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h index d723a3041c..e6fece8354 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceCreate.h @@ -242,7 +242,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAssetScale (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h index 19c4530792..cbcd206097 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceDestroy.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h index 8099af1148..803868c640 100644 --- a/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h +++ b/include/xrpl/protocol_autogen/transactions/MPTokenIssuanceSet.h @@ -281,7 +281,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfMPTokenIssuanceID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h index 7627437a52..325d2d7fbd 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenAcceptOffer.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenBuyOffer (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h b/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h index 9f981b1ec4..ec423ea468 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenBurn.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h index 2eca27fc8d..4c4fb1dc65 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenCancelOffer.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenOffers (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h b/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h index a5c6e226d9..a535a578e0 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenCreateOffer.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenMint.h b/include/xrpl/protocol_autogen/transactions/NFTokenMint.h index ad68f1a18b..5af41eb3dd 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenMint.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenMint.h @@ -255,7 +255,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenTaxon (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/NFTokenModify.h b/include/xrpl/protocol_autogen/transactions/NFTokenModify.h index 277146ce78..9b9701fed6 100644 --- a/include/xrpl/protocol_autogen/transactions/NFTokenModify.h +++ b/include/xrpl/protocol_autogen/transactions/NFTokenModify.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfNFTokenID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OfferCancel.h b/include/xrpl/protocol_autogen/transactions/OfferCancel.h index 833924f683..5e6010e0dd 100644 --- a/include/xrpl/protocol_autogen/transactions/OfferCancel.h +++ b/include/xrpl/protocol_autogen/transactions/OfferCancel.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOfferSequence (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OfferCreate.h b/include/xrpl/protocol_autogen/transactions/OfferCreate.h index 13e868643f..ffc1216297 100644 --- a/include/xrpl/protocol_autogen/transactions/OfferCreate.h +++ b/include/xrpl/protocol_autogen/transactions/OfferCreate.h @@ -192,7 +192,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfTakerPays (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OracleDelete.h b/include/xrpl/protocol_autogen/transactions/OracleDelete.h index d6d46d1d7a..ebdc8fb7e9 100644 --- a/include/xrpl/protocol_autogen/transactions/OracleDelete.h +++ b/include/xrpl/protocol_autogen/transactions/OracleDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOracleDocumentID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/OracleSet.h b/include/xrpl/protocol_autogen/transactions/OracleSet.h index 1d295b4cc2..0ec6d5cad0 100644 --- a/include/xrpl/protocol_autogen/transactions/OracleSet.h +++ b/include/xrpl/protocol_autogen/transactions/OracleSet.h @@ -203,7 +203,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfOracleDocumentID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/Payment.h b/include/xrpl/protocol_autogen/transactions/Payment.h index b2e82cd6af..389900bf12 100644 --- a/include/xrpl/protocol_autogen/transactions/Payment.h +++ b/include/xrpl/protocol_autogen/transactions/Payment.h @@ -295,7 +295,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h index 1db3057366..4c567b13f4 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelClaim.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfChannel (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h index cf0cbae2aa..0a513d575a 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelCreate.h @@ -190,7 +190,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDestination (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h b/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h index 3612fcbf48..51210dd796 100644 --- a/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h +++ b/include/xrpl/protocol_autogen/transactions/PaymentChannelFund.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfChannel (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h b/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h index e08ff1ed5a..3db921776c 100644 --- a/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h +++ b/include/xrpl/protocol_autogen/transactions/PermissionedDomainDelete.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDomainID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h b/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h index 68f1cfebb2..3e352cad76 100644 --- a/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h +++ b/include/xrpl/protocol_autogen/transactions/PermissionedDomainSet.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfDomainID (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SetFee.h b/include/xrpl/protocol_autogen/transactions/SetFee.h index bc5fc0e603..177f39199b 100644 --- a/include/xrpl/protocol_autogen/transactions/SetFee.h +++ b/include/xrpl/protocol_autogen/transactions/SetFee.h @@ -294,7 +294,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLedgerSequence (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SetRegularKey.h b/include/xrpl/protocol_autogen/transactions/SetRegularKey.h index 1eca1bef25..a943bb0279 100644 --- a/include/xrpl/protocol_autogen/transactions/SetRegularKey.h +++ b/include/xrpl/protocol_autogen/transactions/SetRegularKey.h @@ -112,7 +112,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfRegularKey (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/SignerListSet.h b/include/xrpl/protocol_autogen/transactions/SignerListSet.h index c711864e95..6e9d0e41ba 100644 --- a/include/xrpl/protocol_autogen/transactions/SignerListSet.h +++ b/include/xrpl/protocol_autogen/transactions/SignerListSet.h @@ -123,7 +123,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfSignerQuorum (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h b/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h new file mode 100644 index 0000000000..0124da5e58 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/SponsorshipSet.h @@ -0,0 +1,292 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class SponsorshipSetBuilder; + +/** + * @brief Transaction: SponsorshipSet + * + * Type: ttSPONSORSHIP_SET (91) + * Delegable: Delegation::Delegable + * Amendment: featureSponsor + * Privileges: NoPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use SponsorshipSetBuilder to construct new transactions. + */ +class SponsorshipSet : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET; + + /** + * @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit SponsorshipSet(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for SponsorshipSet"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfCounterpartySponsor (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getCounterpartySponsor() const + { + if (hasCounterpartySponsor()) + { + return this->tx_->at(sfCounterpartySponsor); + } + return std::nullopt; + } + + /** + * @brief Check if sfCounterpartySponsor is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasCounterpartySponsor() const + { + return this->tx_->isFieldPresent(sfCounterpartySponsor); + } + + /** + * @brief Get sfSponsee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsee() const + { + if (hasSponsee()) + { + return this->tx_->at(sfSponsee); + } + return std::nullopt; + } + + /** + * @brief Check if sfSponsee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsee() const + { + return this->tx_->isFieldPresent(sfSponsee); + } + + /** + * @brief Get sfFeeAmount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getFeeAmount() const + { + if (hasFeeAmount()) + { + return this->tx_->at(sfFeeAmount); + } + return std::nullopt; + } + + /** + * @brief Check if sfFeeAmount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFeeAmount() const + { + return this->tx_->isFieldPresent(sfFeeAmount); + } + + /** + * @brief Get sfMaxFee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getMaxFee() const + { + if (hasMaxFee()) + { + return this->tx_->at(sfMaxFee); + } + return std::nullopt; + } + + /** + * @brief Check if sfMaxFee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasMaxFee() const + { + return this->tx_->isFieldPresent(sfMaxFee); + } + + /** + * @brief Get sfRemainingOwnerCount (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getRemainingOwnerCount() const + { + if (hasRemainingOwnerCount()) + { + return this->tx_->at(sfRemainingOwnerCount); + } + return std::nullopt; + } + + /** + * @brief Check if sfRemainingOwnerCount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasRemainingOwnerCount() const + { + return this->tx_->isFieldPresent(sfRemainingOwnerCount); + } +}; + +/** + * @brief Builder for SponsorshipSet transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses STObject internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class SponsorshipSetBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipSetBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttSPONSORSHIP_SET, account, sequence, fee) + { + } + + /** + * @brief Construct a SponsorshipSetBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + SponsorshipSetBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttSPONSORSHIP_SET) + { + throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder"); + } + object_ = *tx; + } + + /** + * @brief Transaction-specific field setters + */ + + /** + * @brief Set sfCounterpartySponsor (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setCounterpartySponsor(std::decay_t const& value) + { + object_[sfCounterpartySponsor] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Set sfFeeAmount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setFeeAmount(std::decay_t const& value) + { + object_[sfFeeAmount] = value; + return *this; + } + + /** + * @brief Set sfMaxFee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setMaxFee(std::decay_t const& value) + { + object_[sfMaxFee] = value; + return *this; + } + + /** + * @brief Set sfRemainingOwnerCount (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipSetBuilder& + setRemainingOwnerCount(std::decay_t const& value) + { + object_[sfRemainingOwnerCount] = value; + return *this; + } + + /** + * @brief Build and return the SponsorshipSet wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + SponsorshipSet + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return SponsorshipSet{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h b/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h new file mode 100644 index 0000000000..ab26e887e3 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/SponsorshipTransfer.h @@ -0,0 +1,181 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class SponsorshipTransferBuilder; + +/** + * @brief Transaction: SponsorshipTransfer + * + * Type: ttSPONSORSHIP_TRANSFER (90) + * Delegable: Delegation::NotDelegable + * Amendment: featureSponsor + * Privileges: NoPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use SponsorshipTransferBuilder to construct new transactions. + */ +class SponsorshipTransfer : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER; + + /** + * @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit SponsorshipTransfer(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for SponsorshipTransfer"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfObjectID (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getObjectID() const + { + if (hasObjectID()) + { + return this->tx_->at(sfObjectID); + } + return std::nullopt; + } + + /** + * @brief Check if sfObjectID is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasObjectID() const + { + return this->tx_->isFieldPresent(sfObjectID); + } + + /** + * @brief Get sfSponsee (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getSponsee() const + { + if (hasSponsee()) + { + return this->tx_->at(sfSponsee); + } + return std::nullopt; + } + + /** + * @brief Check if sfSponsee is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasSponsee() const + { + return this->tx_->isFieldPresent(sfSponsee); + } +}; + +/** + * @brief Builder for SponsorshipTransfer transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses STObject internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class SponsorshipTransferBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new SponsorshipTransferBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttSPONSORSHIP_TRANSFER, account, sequence, fee) + { + } + + /** + * @brief Construct a SponsorshipTransferBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + SponsorshipTransferBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER) + { + throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder"); + } + object_ = *tx; + } + + /** + * @brief Transaction-specific field setters + */ + + /** + * @brief Set sfObjectID (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipTransferBuilder& + setObjectID(std::decay_t const& value) + { + object_[sfObjectID] = value; + return *this; + } + + /** + * @brief Set sfSponsee (SoeOptional) + * @return Reference to this builder for method chaining. + */ + SponsorshipTransferBuilder& + setSponsee(std::decay_t const& value) + { + object_[sfSponsee] = value; + return *this; + } + + /** + * @brief Build and return the SponsorshipTransfer wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + SponsorshipTransfer + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return SponsorshipTransfer{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/TicketCreate.h b/include/xrpl/protocol_autogen/transactions/TicketCreate.h index 0b4206152e..0d8670a76a 100644 --- a/include/xrpl/protocol_autogen/transactions/TicketCreate.h +++ b/include/xrpl/protocol_autogen/transactions/TicketCreate.h @@ -99,7 +99,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfTicketCount (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/TrustSet.h b/include/xrpl/protocol_autogen/transactions/TrustSet.h index 30d537492d..22891b94ec 100644 --- a/include/xrpl/protocol_autogen/transactions/TrustSet.h +++ b/include/xrpl/protocol_autogen/transactions/TrustSet.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfLimitAmount (SoeOptional) diff --git a/include/xrpl/protocol_autogen/transactions/UNLModify.h b/include/xrpl/protocol_autogen/transactions/UNLModify.h index a8556ca699..6569e4bf7d 100644 --- a/include/xrpl/protocol_autogen/transactions/UNLModify.h +++ b/include/xrpl/protocol_autogen/transactions/UNLModify.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfUNLModifyDisabling (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultClawback.h b/include/xrpl/protocol_autogen/transactions/VaultClawback.h index 8ec1d359dc..270ccc94bb 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultClawback.h +++ b/include/xrpl/protocol_autogen/transactions/VaultClawback.h @@ -139,7 +139,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultCreate.h b/include/xrpl/protocol_autogen/transactions/VaultCreate.h index c8efa83cbf..b7e1527754 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultCreate.h +++ b/include/xrpl/protocol_autogen/transactions/VaultCreate.h @@ -256,7 +256,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfAsset (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultDelete.h b/include/xrpl/protocol_autogen/transactions/VaultDelete.h index b4c08ae229..67cc32f543 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultDelete.h +++ b/include/xrpl/protocol_autogen/transactions/VaultDelete.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultDeposit.h b/include/xrpl/protocol_autogen/transactions/VaultDeposit.h index 01f707e07f..5bb5362114 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/VaultDeposit.h @@ -113,7 +113,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultSet.h b/include/xrpl/protocol_autogen/transactions/VaultSet.h index 5bdc6a7a95..14df70f13b 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultSet.h +++ b/include/xrpl/protocol_autogen/transactions/VaultSet.h @@ -177,7 +177,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h b/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h index 75b5b90035..3211524e1f 100644 --- a/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/VaultWithdraw.h @@ -165,7 +165,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfVaultID (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h b/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h index 36d8170af1..b8d551c5e1 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAccountCreateCommit.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h b/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h index a04eec1505..22b57803dc 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAddAccountCreateAttestation.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h b/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h index 1896f11681..5e80c05aae 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h +++ b/include/xrpl/protocol_autogen/transactions/XChainAddClaimAttestation.h @@ -229,7 +229,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainClaim.h b/include/xrpl/protocol_autogen/transactions/XChainClaim.h index f5b680049e..ec403b5eb8 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainClaim.h +++ b/include/xrpl/protocol_autogen/transactions/XChainClaim.h @@ -164,7 +164,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCommit.h b/include/xrpl/protocol_autogen/transactions/XChainCommit.h index a9ce7d1d08..48b2263645 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCommit.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCommit.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h b/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h index ae5be4108f..9614b0bd88 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCreateBridge.h @@ -138,7 +138,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h b/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h index 7c1beb20c7..d17759619f 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h +++ b/include/xrpl/protocol_autogen/transactions/XChainCreateClaimID.h @@ -125,7 +125,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h b/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h index 30558ab88c..e79c9139ce 100644 --- a/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h +++ b/include/xrpl/protocol_autogen/transactions/XChainModifyBridge.h @@ -151,7 +151,9 @@ public: object_ = *tx; } - /** @brief Transaction-specific field setters */ + /** + * @brief Transaction-specific field setters + */ /** * @brief Set sfXChainBridge (SoeRequired) diff --git a/include/xrpl/rdb/RelationalDatabase.h b/include/xrpl/rdb/RelationalDatabase.h index 91c282ff16..e5784c7418 100644 --- a/include/xrpl/rdb/RelationalDatabase.h +++ b/include/xrpl/rdb/RelationalDatabase.h @@ -46,6 +46,22 @@ struct LedgerRange uint32_t max; }; +/** + * @brief Enumeration of possible delegate types that can occur during filtering in account_tx + */ +enum class DelegateType { + Actor, ///< Another account signed and submitted transactions on behalf of this account (this + ///< account is the owner/delegator). + Authorizer ///< This account signed and submitted transactions on behalf of another account + ///< (this account is the signer/delegatee). +}; + +struct DelegateFilter +{ + DelegateType type = DelegateType::Actor; + std::optional counterparty; +}; + class RelationalDatabase { public: @@ -65,8 +81,10 @@ public: struct AccountTxOptions { AccountID const& account; - /// Ledger sequence range to search. A value of 0 for min or max - /// means unbounded in that direction (no constraint applied). + /** + * Ledger sequence range to search. A value of 0 for min or max + * means unbounded in that direction (no constraint applied). + */ LedgerRange ledgerRange{}; std::uint32_t offset = 0; std::uint32_t limit = 0; @@ -80,6 +98,7 @@ public: std::optional marker; std::uint32_t limit = 0; bool bAdmin = false; + std::optional delegate; }; using AccountTx = std::pair, std::shared_ptr>; @@ -99,6 +118,7 @@ public: bool forward = false; uint32_t limit = 0; std::optional marker; + std::optional delegate; }; struct AccountTxResult @@ -107,6 +127,7 @@ public: LedgerRange ledgerRange{}; uint32_t limit = 0; std::optional marker; + std::optional delegate; }; virtual ~RelationalDatabase() = default; diff --git a/include/xrpl/rdb/SociDB.h b/include/xrpl/rdb/SociDB.h index 0f427bd18b..80d83f0f75 100644 --- a/include/xrpl/rdb/SociDB.h +++ b/include/xrpl/rdb/SociDB.h @@ -1,18 +1,19 @@ #pragma once -/** An embedded database wrapper with an intuitive, type-safe interface. - - This collection of classes let's you access embedded SQLite databases - using C++ syntax that is very similar to regular SQL. - - This module requires the @ref beast_sqlite external module. -*/ +/** + * An embedded database wrapper with an intuitive, type-safe interface. + * + * This collection of classes let's you access embedded SQLite databases + * using C++ syntax that is very similar to regular SQL. + * + * This module requires the @ref beast_sqlite external module. + */ #include #include #include -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" #endif @@ -35,9 +36,9 @@ namespace xrpl { class BasicConfig; /** - DBConfig is used when a client wants to delay opening a soci::session after - parsing the config parameters. If a client want to open a session - immediately, use the free function "open" below. + * DBConfig is used when a client wants to delay opening a soci::session after + * parsing the config parameters. If a client want to open a session + * immediately, use the free function "open" below. */ class DBConfig { @@ -53,27 +54,27 @@ public: }; /** - Open a soci session. - - @param s Session to open. - - @param config Parameters to pick the soci backend and how to connect to that - backend. - - @param dbName Name of the database. This has different meaning for different - backends. Sometimes it is part of a filename (sqlite3), - other times it is a database name (postgresql). -*/ + * Open a soci session. + * + * @param s Session to open. + * + * @param config Parameters to pick the soci backend and how to connect to that + * backend. + * + * @param dbName Name of the database. This has different meaning for different + * backends. Sometimes it is part of a filename (sqlite3), + * other times it is a database name (postgresql). + */ void open(soci::session& s, BasicConfig const& config, std::string const& dbName); /** - * Open a soci session. + * Open a soci session. * - * @param s Session to open. - * @param beName Backend name. - * @param connectionString Connection string to forward to soci::open. - * see the soci::open documentation for how to use this. + * @param s Session to open. + * @param beName Backend name. + * @param connectionString Connection string to forward to soci::open. + * see the soci::open documentation for how to use this. * */ void @@ -107,17 +108,18 @@ public: checkpoint() = 0; }; -/** Returns a new checkpointer which makes checkpoints of a - soci database every checkpointPageCount pages, using a job on the job queue. - - The checkpointer contains references to the session and job queue - and so must outlive them both. +/** + * Returns a new checkpointer which makes checkpoints of a + * soci database every checkpointPageCount pages, using a job on the job queue. + * + * The checkpointer contains references to the session and job queue + * and so must outlive them both. */ std::shared_ptr makeCheckpointer(std::uintptr_t id, std::weak_ptr, JobQueue&, ServiceRegistry&); } // namespace xrpl -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic pop #endif diff --git a/include/xrpl/resource/Charge.h b/include/xrpl/resource/Charge.h index 394d641e6c..12ea548fd2 100644 --- a/include/xrpl/resource/Charge.h +++ b/include/xrpl/resource/Charge.h @@ -6,28 +6,40 @@ namespace xrpl::Resource { -/** A consumption charge. */ +/** + * A consumption charge. + */ class Charge { public: - /** The type used to hold a consumption charge. */ + /** + * The type used to hold a consumption charge. + */ using value_type = int; // A default constructed Charge has no way to get a label. Delete Charge() = delete; - /** Create a charge with the specified cost and name. */ + /** + * Create a charge with the specified cost and name. + */ Charge(value_type cost, std::string label = std::string()); - /** Return the human readable label associated with the charge. */ + /** + * Return the human readable label associated with the charge. + */ [[nodiscard]] std::string const& label() const; - /** Return the cost of the charge in Resource::Manager units. */ + /** + * Return the cost of the charge in Resource::Manager units. + */ [[nodiscard]] value_type cost() const; - /** Converts this charge into a human readable string. */ + /** + * Converts this charge into a human readable string. + */ [[nodiscard]] std::string toString() const; diff --git a/include/xrpl/resource/Consumer.h b/include/xrpl/resource/Consumer.h index 18207832ac..9abcbffc82 100644 --- a/include/xrpl/resource/Consumer.h +++ b/include/xrpl/resource/Consumer.h @@ -13,7 +13,9 @@ namespace xrpl::Resource { struct Entry; class Logic; -/** An endpoint that consumes resources. */ +/** + * An endpoint that consumes resources. + */ class Consumer { private: @@ -27,42 +29,55 @@ public: Consumer& operator=(Consumer const& other); - /** Return a human readable string uniquely identifying this consumer. */ + /** + * Return a human readable string uniquely identifying this consumer. + */ [[nodiscard]] std::string toString() const; - /** Returns `true` if this is a privileged endpoint. */ + /** + * Returns `true` if this is a privileged endpoint. + */ [[nodiscard]] bool isUnlimited() const; - /** Raise the Consumer's privilege level to a Named endpoint. - The reference to the original endpoint descriptor is released. - */ + /** + * Raise the Consumer's privilege level to a Named endpoint. + * The reference to the original endpoint descriptor is released. + */ void elevate(std::string const& name); - /** Returns the current disposition of this consumer. - This should be checked upon creation to determine if the consumer - should be disconnected immediately. - */ + /** + * Returns the current disposition of this consumer. + * This should be checked upon creation to determine if the consumer + * should be disconnected immediately. + */ [[nodiscard]] Disposition disposition() const; - /** Apply a load charge to the consumer. */ + /** + * Apply a load charge to the consumer. + */ Disposition charge(Charge const& fee, std::string const& context = {}); - /** Returns `true` if the consumer should be warned. - This consumes the warning. - */ + /** + * Returns `true` if the consumer should be warned. + * This consumes the warning. + */ bool warn(); - /** Returns `true` if the consumer should be disconnected. */ + /** + * Returns `true` if the consumer should be disconnected. + */ bool disconnect(beast::Journal const& j); - /** Returns the credit balance representing consumption. */ + /** + * Returns the credit balance representing consumption. + */ int balance(); diff --git a/include/xrpl/resource/Disposition.h b/include/xrpl/resource/Disposition.h index 6dad8db19a..cd5bceafa5 100644 --- a/include/xrpl/resource/Disposition.h +++ b/include/xrpl/resource/Disposition.h @@ -2,16 +2,24 @@ namespace xrpl::Resource { -/** The disposition of a consumer after applying a load charge. */ +/** + * The disposition of a consumer after applying a load charge. + */ enum class Disposition { - /** No action required. */ + /** + * No action required. + */ Ok - /** Consumer should be warned that consumption is high. */ + /** + * Consumer should be warned that consumption is high. + */ , Warn - /** Consumer should be disconnected for excess consumption. */ + /** + * Consumer should be disconnected for excess consumption. + */ , Drop }; diff --git a/include/xrpl/resource/Fees.h b/include/xrpl/resource/Fees.h index 55d539ac6a..5001b504d6 100644 --- a/include/xrpl/resource/Fees.h +++ b/include/xrpl/resource/Fees.h @@ -4,7 +4,9 @@ namespace xrpl::Resource { -/** Schedule of fees charged for imposing load on the server. */ +/** + * Schedule of fees charged for imposing load on the server. + */ /** @{ */ extern Charge const kFeeMalformedRequest; // A request that we can immediately tell is invalid. extern Charge const kFeeRequestNoReply; // A request that we cannot satisfy. diff --git a/include/xrpl/resource/Gossip.h b/include/xrpl/resource/Gossip.h index e626af37c3..4ad5852de0 100644 --- a/include/xrpl/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -6,12 +6,16 @@ namespace xrpl::Resource { -/** Data format for exchanging consumption information across peers. */ +/** + * Data format for exchanging consumption information across peers. + */ struct Gossip { explicit Gossip() = default; - /** Describes a single consumer. */ + /** + * Describes a single consumer. + */ struct Item { explicit Item() = default; diff --git a/include/xrpl/resource/ResourceManager.h b/include/xrpl/resource/ResourceManager.h index 13e0d09343..03aab60c75 100644 --- a/include/xrpl/resource/ResourceManager.h +++ b/include/xrpl/resource/ResourceManager.h @@ -16,7 +16,9 @@ namespace xrpl::Resource { -/** Tracks load and resource consumption. */ +/** + * Tracks load and resource consumption. + */ class Manager : public beast::PropertyStream::Source { protected: @@ -25,8 +27,10 @@ protected: public: ~Manager() override = 0; - /** Create a new endpoint keyed by inbound IP address or the forwarded - * IP if proxied. */ + /** + * Create a new endpoint keyed by inbound IP address or the forwarded + * IP if proxied. + */ virtual Consumer newInboundEndpoint(beast::IP::Endpoint const& address) = 0; virtual Consumer @@ -35,27 +39,36 @@ public: bool const proxy, std::string_view forwardedFor) = 0; - /** Create a new endpoint keyed by outbound IP address and port. */ + /** + * Create a new endpoint keyed by outbound IP address and port. + */ virtual Consumer newOutboundEndpoint(beast::IP::Endpoint const& address) = 0; - /** Create a new unlimited endpoint keyed by forwarded IP. */ + /** + * Create a new unlimited endpoint keyed by forwarded IP. + */ virtual Consumer newUnlimitedEndpoint(beast::IP::Endpoint const& address) = 0; - /** Extract packaged consumer information for export. */ + /** + * Extract packaged consumer information for export. + */ virtual Gossip exportConsumers() = 0; - /** Extract consumer information for reporting. */ + /** + * Extract consumer information for reporting. + */ virtual json::Value getJson() = 0; virtual json::Value getJson(int threshold) = 0; - /** Import packaged consumer information. - @param origin An identifier that unique labels the origin. - */ + /** + * Import packaged consumer information. + * @param origin An identifier that unique labels the origin. + */ virtual void importConsumers(std::string const& origin, Gossip const& gossip) = 0; }; diff --git a/include/xrpl/resource/detail/Entry.h b/include/xrpl/resource/detail/Entry.h index 6f44ac2c29..1336bda6ab 100644 --- a/include/xrpl/resource/detail/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -23,8 +23,8 @@ struct Entry : public beast::List::Node Entry() = delete; /** - @param now Construction time of Entry. - */ + * @param now Construction time of Entry. + */ explicit Entry(clock_type::time_point const now) : refcount(0), localBalance(now), remoteBalance(0) { diff --git a/include/xrpl/resource/detail/Import.h b/include/xrpl/resource/detail/Import.h index a3df6fd73b..b19dbc4d1a 100644 --- a/include/xrpl/resource/detail/Import.h +++ b/include/xrpl/resource/detail/Import.h @@ -7,7 +7,9 @@ namespace xrpl::Resource { -/** A set of imported consumer data from a gossip origin. */ +/** + * A set of imported consumer data from a gossip origin. + */ struct Import { struct Item diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index 78cd0ac36c..3f36ad84a3 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -192,7 +192,9 @@ public: return getJson(kWarningThreshold); } - /** Returns a json::ValueType::Object. */ + /** + * Returns a json::ValueType::Object. + */ json::Value getJson(int threshold) { diff --git a/include/xrpl/resource/detail/Tuning.h b/include/xrpl/resource/detail/Tuning.h index 7b2046f45c..62f7fa3f9d 100644 --- a/include/xrpl/resource/detail/Tuning.h +++ b/include/xrpl/resource/detail/Tuning.h @@ -4,7 +4,9 @@ namespace xrpl::Resource { -/** Tunable constants. */ +/** + * Tunable constants. + */ // balance at which a warning is issued static constexpr auto kWarningThreshold = 5000; diff --git a/include/xrpl/server/Handoff.h b/include/xrpl/server/Handoff.h index b80a9c5745..6dc547467b 100644 --- a/include/xrpl/server/Handoff.h +++ b/include/xrpl/server/Handoff.h @@ -13,7 +13,9 @@ using http_request_type = boost::beast::http::request; -/** Used to indicate the result of a server connection handoff. */ +/** + * Used to indicate the result of a server connection handoff. + */ struct Handoff { // When `true`, the Session will close the socket. The diff --git a/include/xrpl/server/InfoSub.h b/include/xrpl/server/InfoSub.h index d708a456c4..2e9bd857c7 100644 --- a/include/xrpl/server/InfoSub.h +++ b/include/xrpl/server/InfoSub.h @@ -35,20 +35,21 @@ public: doStatus(json::Value const&) = 0; }; -/** Manages a client's subscription to data feeds. +/** + * Manages a client's subscription to data feeds. * - * An InfoSub holds a non-owning reference to its `Source` (typically the - * process-wide `NetworkOPsImp`). The destructor reaches back into the - * `Source` to remove this subscriber from every server-side subscription - * map. + * An InfoSub holds a non-owning reference to its `Source` (typically the + * process-wide `NetworkOPsImp`). The destructor reaches back into the + * `Source` to remove this subscriber from every server-side subscription + * map. * - * @note Lifetime contract: every `InfoSub` instance MUST be destroyed - * before the backing `Source`. NetworkOPsImp shutdown drops all - * subscriber strong refs before its own teardown to satisfy this. - * @note Thread-safety: per-instance state is guarded by `lock_`. The - * destructor reads tracking sets without taking `lock_` because - * the strong-pointer ref-count is zero at destruction time, so - * no other thread can be calling the public mutators. + * @note Lifetime contract: every `InfoSub` instance MUST be destroyed + * before the backing `Source`. NetworkOPsImp shutdown drops all + * subscriber strong refs before its own teardown to satisfy this. + * @note Thread-safety: per-instance state is guarded by `lock_`. The + * destructor reads tracking sets without taking `lock_` because + * the strong-pointer ref-count is zero at destruction time, so + * no other thread can be calling the public mutators. */ class InfoSub : public CountedObject { @@ -64,7 +65,8 @@ public: using Consumer = Resource::Consumer; public: - /** Abstracts the source of subscription data. + /** + * Abstracts the source of subscription data. */ class Source { @@ -217,7 +219,8 @@ public: virtual bool tryRemoveRpcSub(std::string const& strUrl) = 0; - /** Journal used by InfoSub for diagnostics that occur after the + /** + * Journal used by InfoSub for diagnostics that occur after the * owning subsystem (e.g. application-level Logs) is the only * surviving sink — primarily destructor-time cleanup failures. */ @@ -249,27 +252,29 @@ public: void deleteSubAccountInfo(AccountID const& account, bool rt); - /** Record that this subscriber is following @p book. + /** + * Record that this subscriber is following @p book. * - * Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a - * matching unsubBook for every book this subscriber is tracking, - * keeping per-subscriber state symmetric with the server-side map. + * Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a + * matching unsubBook for every book this subscriber is tracking, + * keeping per-subscriber state symmetric with the server-side map. * - * @param book The order book this subscriber has just subscribed to. - * @note Idempotent: re-inserting an already-tracked book is a no-op. - * @note Thread-safe: takes InfoSub::lock_. + * @param book The order book this subscriber has just subscribed to. + * @note Idempotent: re-inserting an already-tracked book is a no-op. + * @note Thread-safe: takes InfoSub::lock_. */ void insertBookSubscription(Book const& book); - /** Stop tracking @p book for this subscriber. + /** + * Stop tracking @p book for this subscriber. * - * Called by the unsubscribe RPC handler so that the book is not - * re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription. + * Called by the unsubscribe RPC handler so that the book is not + * re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription. * - * @param book The order book to forget. - * @note No-op if @p book was not previously inserted. - * @note Thread-safe: takes InfoSub::lock_. + * @param book The order book to forget. + * @note No-op if @p book was not previously inserted. + * @note Thread-safe: takes InfoSub::lock_. */ void deleteBookSubscription(Book const& book); diff --git a/include/xrpl/server/LoadFeeTrack.h b/include/xrpl/server/LoadFeeTrack.h index a19ca063d5..3afd602241 100644 --- a/include/xrpl/server/LoadFeeTrack.h +++ b/include/xrpl/server/LoadFeeTrack.h @@ -13,15 +13,16 @@ namespace xrpl { struct Fees; -/** Manages the current fee schedule. - - The "base" fee is the cost to send a reference transaction under no load, - expressed in millionths of one XRP. - - The "load" fee is how much the local server currently charges to send a - reference transaction. This fee fluctuates based on the load of the - server. -*/ +/** + * Manages the current fee schedule. + * + * The "base" fee is the cost to send a reference transaction under no load, + * expressed in millionths of one XRP. + * + * The "load" fee is how much the local server currently charges to send a + * reference transaction. This fee fluctuates based on the load of the + * server. + */ class LoadFeeTrack final { public: diff --git a/include/xrpl/server/Manifest.h b/include/xrpl/server/Manifest.h index c58c784de3..710545271a 100644 --- a/include/xrpl/server/Manifest.h +++ b/include/xrpl/server/Manifest.h @@ -68,22 +68,32 @@ namespace xrpl { struct Manifest { - /// The manifest in serialized form. + /** + * The manifest in serialized form. + */ std::string serialized; - /// The master key associated with this manifest. + /** + * The master key associated with this manifest. + */ PublicKey masterKey; - /// The ephemeral key associated with this manifest. + /** + * The ephemeral key associated with this manifest. + */ // A revoked manifest does not have a signingKey // This field is specified as "optional" in manifestFormat's // SOTemplate std::optional signingKey; - /// The sequence number of this manifest. + /** + * The sequence number of this manifest. + */ std::uint32_t sequence = 0; - /// The domain, if one was specified in the manifest; empty otherwise. + /** + * The domain, if one was specified in the manifest; empty otherwise. + */ std::string domain; Manifest() = delete; @@ -109,46 +119,61 @@ struct Manifest Manifest& operator=(Manifest&& other) = default; - /// Returns `true` if manifest signature is valid + /** + * Returns `true` if manifest signature is valid + */ [[nodiscard]] bool verify() const; - /// Returns hash of serialized manifest data + /** + * Returns hash of serialized manifest data + */ [[nodiscard]] uint256 hash() const; - /// Returns `true` if manifest revokes master key + /** + * Returns `true` if manifest revokes master key + */ // The maximum possible sequence number means that the master key has // been revoked static bool revoked(std::uint32_t sequence); - /// Returns `true` if manifest revokes master key + /** + * Returns `true` if manifest revokes master key + */ [[nodiscard]] bool revoked() const; - /// Returns manifest signature + /** + * Returns manifest signature + */ [[nodiscard]] std::optional getSignature() const; - /// Returns manifest master key signature + /** + * Returns manifest master key signature + */ [[nodiscard]] Blob getMasterSignature() const; }; -/** Format the specified manifest to a string for debugging purposes. */ +/** + * Format the specified manifest to a string for debugging purposes. + */ std::string to_string(Manifest const& m); -/** Constructs Manifest from serialized string - - @param s Serialized manifest string - - @return `std::nullopt` if string is invalid - - @note This does not verify manifest signatures. - `Manifest::verify` should be called after constructing manifest. -*/ +/** + * Constructs Manifest from serialized string + * + * @param s Serialized manifest string + * + * @return `std::nullopt` if string is invalid + * + * @note This does not verify manifest signatures. + * `Manifest::verify` should be called after constructing manifest. + */ /** @{ */ std::optional deserializeManifest(Slice s, beast::Journal journal); @@ -200,19 +225,29 @@ loadValidatorToken( beast::Journal journal = beast::Journal(beast::Journal::getNullSink())); enum class ManifestDisposition { - /// Manifest is valid + /** + * Manifest is valid + */ Accepted = 0, - /// Sequence is too old + /** + * Sequence is too old + */ Stale, - /// The master key is not acceptable to us + /** + * The master key is not acceptable to us + */ BadMasterKey, - /// The ephemeral key is not acceptable to us + /** + * The ephemeral key is not acceptable to us + */ BadEphemeralKey, - /// Timely, but invalid signature + /** + * Timely, but invalid signature + */ Invalid }; @@ -238,17 +273,23 @@ to_string(ManifestDisposition m) class DatabaseCon; -/** Remembers manifests with the highest sequence number. */ +/** + * Remembers manifests with the highest sequence number. + */ class ManifestCache { private: beast::Journal j_; std::shared_mutex mutable mutex_; - /** Active manifests stored by master public key. */ + /** + * Active manifests stored by master public key. + */ hash_map map_; - /** Master public keys stored by current ephemeral public key. */ + /** + * Master public keys stored by current ephemeral public key. + */ hash_map signingToMasterKeys_; std::atomic seq_{0}; @@ -258,104 +299,114 @@ public: { } - /** A monotonically increasing number used to detect new manifests. */ + /** + * A monotonically increasing number used to detect new manifests. + */ std::uint32_t sequence() const { return seq_.load(); } - /** Returns master key's current signing key. - - @param pk Master public key - - @return pk if no known signing key from a manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns master key's current signing key. + * + * @param pk Master public key + * + * @return pk if no known signing key from a manifest + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional getSigningKey(PublicKey const& pk) const; - /** Returns ephemeral signing key's master public key. - - @param pk Ephemeral signing public key - - @return pk if signing key is not in a valid manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns ephemeral signing key's master public key. + * + * @param pk Ephemeral signing public key + * + * @return pk if signing key is not in a valid manifest + * + * @par Thread Safety + * + * May be called concurrently + */ PublicKey getMasterKey(PublicKey const& pk) const; - /** Returns master key's current manifest sequence. - - @return sequence corresponding to Master public key - if configured or std::nullopt otherwise - */ + /** + * Returns master key's current manifest sequence. + * + * @return sequence corresponding to Master public key + * if configured or std::nullopt otherwise + */ std::optional getSequence(PublicKey const& pk) const; - /** Returns domain claimed by a given public key - - @return domain corresponding to Master public key - if present, otherwise std::nullopt - */ + /** + * Returns domain claimed by a given public key + * + * @return domain corresponding to Master public key + * if present, otherwise std::nullopt + */ std::optional getDomain(PublicKey const& pk) const; - /** Returns manifest corresponding to a given public key - - @return manifest corresponding to Master public key - if present, otherwise std::nullopt - */ + /** + * Returns manifest corresponding to a given public key + * + * @return manifest corresponding to Master public key + * if present, otherwise std::nullopt + */ std::optional getManifest(PublicKey const& pk) const; - /** Returns `true` if master key has been revoked in a manifest. - - @param pk Master public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if master key has been revoked in a manifest. + * + * @param pk Master public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool revoked(PublicKey const& pk) const; - /** Add manifest to cache. - - @param m Manifest to add - - @return `ManifestDisposition::accepted` if successful, or - `stale` or `invalid` otherwise - - @par Thread Safety - - May be called concurrently - */ + /** + * Add manifest to cache. + * + * @param m Manifest to add + * + * @return `ManifestDisposition::accepted` if successful, or + * `stale` or `invalid` otherwise + * + * @par Thread Safety + * + * May be called concurrently + */ ManifestDisposition applyManifest(Manifest m); - /** Populate manifest cache with manifests in database and config. - - @param dbCon Database connection with dbTable - - @param dbTable Database table - - @param configManifest Base64 encoded manifest for local node's - validator keys - - @param configRevocation Base64 encoded validator key revocation - from the config - - @par Thread Safety - - May be called concurrently - */ + /** + * Populate manifest cache with manifests in database and config. + * + * @param dbCon Database connection with dbTable + * + * @param dbTable Database table + * + * @param configManifest Base64 encoded manifest for local node's + * validator keys + * + * @param configRevocation Base64 encoded validator key revocation + * from the config + * + * @par Thread Safety + * + * May be called concurrently + */ bool load( DatabaseCon& dbCon, @@ -363,48 +414,51 @@ public: std::string const& configManifest, std::vector const& configRevocation); - /** Populate manifest cache with manifests in database. - - @param dbCon Database connection with dbTable - - @param dbTable Database table - - @par Thread Safety - - May be called concurrently - */ + /** + * Populate manifest cache with manifests in database. + * + * @param dbCon Database connection with dbTable + * + * @param dbTable Database table + * + * @par Thread Safety + * + * May be called concurrently + */ void load(DatabaseCon& dbCon, std::string const& dbTable); - /** Save cached manifests to database. - - @param dbCon Database connection with `ValidatorManifests` table - - @param isTrusted Function that returns true if manifest is trusted - - @par Thread Safety - - May be called concurrently - */ + /** + * Save cached manifests to database. + * + * @param dbCon Database connection with `ValidatorManifests` table + * + * @param isTrusted Function that returns true if manifest is trusted + * + * @par Thread Safety + * + * May be called concurrently + */ void save( DatabaseCon& dbCon, std::string const& dbTable, std::function const& isTrusted); - /** Invokes the callback once for every populated manifest. - - @note Do not call ManifestCache member functions from within the - callback. This can re-lock the mutex from the same thread, which is UB. - @note Do not write ManifestCache member variables from within the - callback. This can lead to data races. - - @param f Function called for each manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every populated manifest. + * + * @note Do not call ManifestCache member functions from within the + * callback. This can re-lock the mutex from the same thread, which is UB. + * @note Do not write ManifestCache member variables from within the + * callback. This can lead to data races. + * + * @param f Function called for each manifest + * + * @par Thread Safety + * + * May be called concurrently + */ template void forEachManifest(Function&& f) const @@ -417,22 +471,23 @@ public: } } - /** Invokes the callback once for every populated manifest. - - @note Do not call ManifestCache member functions from within the - callback. This can re-lock the mutex from the same thread, which is UB. - @note Do not write ManifestCache member variables from - within the callback. This can lead to data races. - - @param pf Pre-function called with the maximum number of times f will be - called (useful for memory allocations) - - @param f Function called for each manifest - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every populated manifest. + * + * @note Do not call ManifestCache member functions from within the + * callback. This can re-lock the mutex from the same thread, which is UB. + * @note Do not write ManifestCache member variables from + * within the callback. This can lead to data races. + * + * @param pf Pre-function called with the maximum number of times f will be + * called (useful for memory allocations) + * + * @param f Function called for each manifest + * + * @par Thread Safety + * + * May be called concurrently + */ template void forEachManifest(PreFun&& pf, EachFun&& f) const diff --git a/include/xrpl/server/NetworkOPs.h b/include/xrpl/server/NetworkOPs.h index ed2dbbb220..d3d3bb9f70 100644 --- a/include/xrpl/server/NetworkOPs.h +++ b/include/xrpl/server/NetworkOPs.h @@ -47,35 +47,37 @@ class SHAMap; // there's a functional network. // -/** Specifies the mode under which the server believes it's operating. - - This has implications about how the server processes transactions and - how it responds to requests (e.g. account balance request). - - @note Other code relies on the numerical values of these constants; do - not change them without verifying each use and ensuring that it is - not a breaking change. -*/ +/** + * Specifies the mode under which the server believes it's operating. + * + * This has implications about how the server processes transactions and + * how it responds to requests (e.g. account balance request). + * + * @note Other code relies on the numerical values of these constants; do + * not change them without verifying each use and ensuring that it is + * not a breaking change. + */ enum class OperatingMode { - DISCONNECTED = 0, //!< not ready to process requests - CONNECTED = 1, //!< convinced we are talking to the network - SYNCING = 2, //!< fallen slightly behind - TRACKING = 3, //!< convinced we agree with the network - FULL = 4 //!< we have the ledger and can even validate + DISCONNECTED = 0, ///< not ready to process requests + CONNECTED = 1, ///< convinced we are talking to the network + SYNCING = 2, ///< fallen slightly behind + TRACKING = 3, ///< convinced we agree with the network + FULL = 4 ///< we have the ledger and can even validate }; -/** Provides server functionality for clients. - - Clients include backend applications, local commands, and connected - clients. This class acts as a proxy, fulfilling the command with local - data if possible, or asking the network and returning the results if - needed. - - A backend application or local client can trust a local instance of - xrpld / NetworkOPs. However, client software connecting to non-local - instances of xrpld will need to be hardened to protect against hostile - or unreliable servers. -*/ +/** + * Provides server functionality for clients. + * + * Clients include backend applications, local commands, and connected + * clients. This class acts as a proxy, fulfilling the command with local + * data if possible, or asking the network and returning the results if + * needed. + * + * A backend application or local client can trust a local instance of + * xrpld / NetworkOPs. However, client software connecting to non-local + * instances of xrpld will need to be hardened to protect against hostile + * or unreliable servers. + */ class NetworkOPs : public InfoSub::Source { public: @@ -225,12 +227,13 @@ public: virtual json::Value getLedgerFetchInfo() = 0; - /** Accepts the current transaction tree, return the new ledger's sequence - - This API is only used via RPC with the server in STANDALONE mode and - performs a virtual consensus round, with all the transactions we are - proposing being accepted. - */ + /** + * Accepts the current transaction tree, return the new ledger's sequence + * + * This API is only used via RPC with the server in STANDALONE mode and + * performs a virtual consensus round, with all the transactions we are + * proposing being accepted. + */ virtual std::uint32_t acceptLedger(std::optional consensusDelay = std::nullopt) = 0; @@ -259,15 +262,16 @@ public: virtual void stateAccounting(json::Value& obj) = 0; - /** Total number of (book, subscriber) entries currently tracked. + /** + * Total number of (book, subscriber) entries currently tracked. * - * Counts every weak_ptr stored across every book in subBook_, NOT the - * number of distinct subscribers and NOT the number of distinct - * books: a single subscriber following N books contributes N entries. + * Counts every weak_ptr stored across every book in subBook_, NOT the + * number of distinct subscribers and NOT the number of distinct + * books: a single subscriber following N books contributes N entries. * - * @note Diagnostic accessor; intended for tests and operator visibility - * into per-book subscription state. The returned value is a - * snapshot under the subscription lock. + * @note Diagnostic accessor; intended for tests and operator visibility + * into per-book subscription state. The returned value is a + * snapshot under the subscription lock. */ virtual std::size_t getBookSubscribersCount() = 0; diff --git a/include/xrpl/server/Port.h b/include/xrpl/server/Port.h index c48a2546c1..b8bca6f95f 100644 --- a/include/xrpl/server/Port.h +++ b/include/xrpl/server/Port.h @@ -22,7 +22,9 @@ namespace xrpl { class Section; -/** Configuration information for a Server listening port. */ +/** + * Configuration information for a Server listening port. + */ struct Port { explicit Port() = default; diff --git a/include/xrpl/server/Server.h b/include/xrpl/server/Server.h index 956a414be8..f8d6005d0c 100644 --- a/include/xrpl/server/Server.h +++ b/include/xrpl/server/Server.h @@ -9,7 +9,9 @@ namespace xrpl { -/** Create the HTTP server using the specified handler. */ +/** + * Create the HTTP server using the specified handler. + */ template std::unique_ptr makeServer(Handler& handler, boost::asio::io_context& ioContext, beast::Journal journal) diff --git a/include/xrpl/server/Session.h b/include/xrpl/server/Session.h index 266570862a..be8d9a497c 100644 --- a/include/xrpl/server/Session.h +++ b/include/xrpl/server/Session.h @@ -15,11 +15,12 @@ namespace xrpl { -/** Persistent state information for a connection session. - These values are preserved between calls for efficiency. - Some fields are input parameters, some are output parameters, - and all only become defined during specific callbacks. -*/ +/** + * Persistent state information for a connection session. + * These values are preserved between calls for efficiency. + * Some fields are input parameters, some are output parameters, + * and all only become defined during specific callbacks. + */ class Session { public: @@ -29,29 +30,40 @@ public: operator=(Session const&) = delete; virtual ~Session() = default; - /** A user-definable pointer. - The initial value is always zero. - Changes to the value are persisted between calls. - */ + /** + * A user-definable pointer. + * The initial value is always zero. + * Changes to the value are persisted between calls. + */ void* tag = nullptr; - /** Returns the Journal to use for logging. */ + /** + * Returns the Journal to use for logging. + */ virtual beast::Journal journal() = 0; - /** Returns the Port settings for this connection. */ + /** + * Returns the Port settings for this connection. + */ virtual Port const& port() = 0; - /** Returns the remote address of the connection. */ + /** + * Returns the remote address of the connection. + */ virtual beast::IP::Endpoint remoteAddress() = 0; - /** Returns the current HTTP request. */ + /** + * Returns the current HTTP request. + */ virtual http_request_type& request() = 0; - /** Send a copy of data asynchronously. */ + /** + * Send a copy of data asynchronously. + */ /** @{ */ void write(std::string_view s) @@ -80,32 +92,37 @@ public: /** @} */ - /** Detach the session. - This holds the session open so that the response can be sent - asynchronously. Calls to io_context::run made by the server - will not return until all detached sessions are closed. - */ + /** + * Detach the session. + * This holds the session open so that the response can be sent + * asynchronously. Calls to io_context::run made by the server + * will not return until all detached sessions are closed. + */ virtual std::shared_ptr detach() = 0; - /** Indicate that the response is complete. - The handler should call this when it has completed writing - the response. If Keep-Alive is indicated on the connection, - this will trigger a read for the next request; else, the - connection will be closed when all remaining data has been sent. - */ + /** + * Indicate that the response is complete. + * The handler should call this when it has completed writing + * the response. If Keep-Alive is indicated on the connection, + * this will trigger a read for the next request; else, the + * connection will be closed when all remaining data has been sent. + */ virtual void complete() = 0; - /** Close the session. - This will be performed asynchronously. The session will be - closed gracefully after all pending writes have completed. - @param graceful `true` to wait until all data has finished sending. - */ + /** + * Close the session. + * This will be performed asynchronously. The session will be + * closed gracefully after all pending writes have completed. + * @param graceful `true` to wait until all data has finished sending. + */ virtual void close(bool graceful) = 0; - /** Convert the connection to WebSocket. */ + /** + * Convert the connection to WebSocket. + */ virtual std::shared_ptr websocketUpgrade() = 0; }; diff --git a/include/xrpl/server/SimpleWriter.h b/include/xrpl/server/SimpleWriter.h index 996403eafb..597e6e6d8f 100644 --- a/include/xrpl/server/SimpleWriter.h +++ b/include/xrpl/server/SimpleWriter.h @@ -14,7 +14,9 @@ namespace xrpl { -/// Deprecated: Writer that serializes a HTTP/1 message +/** + * Deprecated: Writer that serializes a HTTP/1 message + */ class SimpleWriter : public Writer { boost::beast::multi_buffer sb_; diff --git a/include/xrpl/server/WSSession.h b/include/xrpl/server/WSSession.h index 0087f9f50f..69f7decdb3 100644 --- a/include/xrpl/server/WSSession.h +++ b/include/xrpl/server/WSSession.h @@ -28,23 +28,24 @@ public: operator=(WSMsg const&) = delete; virtual ~WSMsg() = default; - /** Retrieve message data. - - Returns a tribool indicating whether or not - data is available, and a ConstBufferSequence - representing the data. - - tribool values: - maybe: Data is not ready yet - false: Data is available - true: Data is available, and - it is the last chunk of bytes. - - Derived classes that do not know when the data - ends (for example, when returning the output of a - paged database query) may return `true` and an - empty vector. - */ + /** + * Retrieve message data. + * + * Returns a tribool indicating whether or not + * data is available, and a ConstBufferSequence + * representing the data. + * + * tribool values: + * maybe: Data is not ready yet + * false: Data is available + * true: Data is available, and + * it is the last chunk of bytes. + * + * Derived classes that do not know when the data + * ends (for example, when returning the output of a + * paged database query) may return `true` and an + * empty vector. + */ virtual std::pair> prepare(std::size_t bytes, std::function resume) = 0; }; @@ -106,7 +107,9 @@ struct WSSession [[nodiscard]] virtual boost::asio::ip::tcp::endpoint const& remoteEndpoint() const = 0; - /** Send a WebSockets message. */ + /** + * Send a WebSockets message. + */ virtual void send(std::shared_ptr w) = 0; @@ -116,10 +119,11 @@ struct WSSession virtual void close(boost::beast::websocket::close_reason const& reason) = 0; - /** Indicate that the response is complete. - The handler should call this when it has completed writing - the response. - */ + /** + * Indicate that the response is complete. + * The handler should call this when it has completed writing + * the response. + */ virtual void complete() = 0; }; diff --git a/include/xrpl/server/Wallet.h b/include/xrpl/server/Wallet.h index eea44db200..ed8378989f 100644 --- a/include/xrpl/server/Wallet.h +++ b/include/xrpl/server/Wallet.h @@ -78,19 +78,22 @@ saveManifests( void addValidatorManifest(soci::session& session, std::string const& serialized); -/** Delete any saved public/private key associated with this node. */ +/** + * Delete any saved public/private key associated with this node. + */ void clearNodeIdentity(soci::session& session); -/** Returns a stable public and private key for this node. - - The node's public identity is defined by a secp256k1 keypair - that is (normally) randomly generated. This function will - return such a keypair, securely generating one if needed. - - @param session Session with the database. - - @return Pair of public and private secp256k1 keys. +/** + * Returns a stable public and private key for this node. + * + * The node's public identity is defined by a secp256k1 keypair + * that is (normally) randomly generated. This function will + * return such a keypair, securely generating one if needed. + * + * @param session Session with the database. + * + * @return Pair of public and private secp256k1 keys. */ std::pair getNodeIdentity(soci::session& session); diff --git a/include/xrpl/server/Writer.h b/include/xrpl/server/Writer.h index fe9d9519e1..a80563af43 100644 --- a/include/xrpl/server/Writer.h +++ b/include/xrpl/server/Writer.h @@ -13,26 +13,32 @@ class Writer public: virtual ~Writer() = default; - /** Returns `true` if there is no more data to pull. */ + /** + * Returns `true` if there is no more data to pull. + */ virtual bool complete() = 0; - /** Removes bytes from the input sequence. - - Can be called with 0. - */ + /** + * Removes bytes from the input sequence. + * + * Can be called with 0. + */ virtual void consume(std::size_t bytes) = 0; - /** Add data to the input sequence. - @param bytes A hint to the number of bytes desired. - @param resume A functor to later resume execution. - @return `true` if the writer is ready to provide more data. - */ + /** + * Add data to the input sequence. + * @param bytes A hint to the number of bytes desired. + * @param resume A functor to later resume execution. + * @return `true` if the writer is ready to provide more data. + */ virtual bool prepare(std::size_t bytes, std::function resume) = 0; - /** Returns a ConstBufferSequence representing the input sequence. */ + /** + * Returns a ConstBufferSequence representing the input sequence. + */ virtual std::vector data() = 0; }; diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 7b35dbd4be..c7553c1da3 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -35,7 +35,9 @@ namespace xrpl { -/** Represents an active connection. */ +/** + * Represents an active connection. + */ template class BaseHTTPPeer : public IOList::Work, public Session { diff --git a/include/xrpl/server/detail/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h index d557140bd4..59a866ab8c 100644 --- a/include/xrpl/server/detail/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -30,7 +30,9 @@ namespace xrpl { -/** Represents an active WebSocket connection. */ +/** + * Represents an active WebSocket connection. + */ template class BaseWSPeer : public BasePeer, public WSSession { @@ -48,9 +50,11 @@ private: boost::beast::multi_buffer rb_; boost::beast::multi_buffer wb_; std::list> wq_; - /// The socket has been closed, or will close after the next write - /// finishes. Do not do any more writes, and don't try to close - /// again. + /** + * The socket has been closed, or will close after the next write + * finishes. Do not do any more writes, and don't try to close + * again. + */ bool doClose_ = false; boost::beast::websocket::close_reason cr_; waitable_timer timer_; diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index d2d7a7baf4..285bde14ac 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -40,7 +40,9 @@ namespace xrpl { -/** A listening socket. */ +/** + * A listening socket. + */ template class Door : public IOList::Work, public std::enable_shared_from_this> { @@ -129,12 +131,13 @@ public: void run(); - /** Close the Door listening socket and connections. - The listening socket is closed, and all open connections - belonging to the Door are closed. - Thread Safety: - May be called concurrently - */ + /** + * Close the Door listening socket and connections. + * The listening socket is closed, and all open connections + * belonging to the Door are closed. + * Thread Safety: + * May be called concurrently + */ void close() override; diff --git a/include/xrpl/server/detail/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h index df2bba0284..c2e411cf9b 100644 --- a/include/xrpl/server/detail/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -26,39 +26,45 @@ namespace xrpl { using Endpoints = std::unordered_map; -/** A multi-protocol server. - - This server maintains multiple configured listening ports, - with each listening port allows for multiple protocols including - HTTP, HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. -*/ +/** + * A multi-protocol server. + * + * This server maintains multiple configured listening ports, + * with each listening port allows for multiple protocols including + * HTTP, HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. + */ class Server { public: - /** Destroy the server. - The server is closed if it is not already closed. This call - blocks until the server has stopped. - */ + /** + * Destroy the server. + * The server is closed if it is not already closed. This call + * blocks until the server has stopped. + */ virtual ~Server() = default; - /** Returns the Journal associated with the server. */ + /** + * Returns the Journal associated with the server. + */ virtual beast::Journal journal() = 0; - /** Set the listening port settings. - This may only be called once. - */ + /** + * Set the listening port settings. + * This may only be called once. + */ virtual Endpoints ports(std::vector const& v) = 0; - /** Close the server. - The close is performed asynchronously. The handler will be notified - when the server has stopped. The server is considered stopped when - there are no pending I/O completion handlers and all connections - have closed. - Thread safety: - Safe to call concurrently from any thread. - */ + /** + * Close the server. + * The close is performed asynchronously. The handler will be notified + * when the server has stopped. The server is considered stopped when + * there are no pending I/O completion handlers and all connections + * have closed. + * Thread safety: + * Safe to call concurrently from any thread. + */ virtual void close() = 0; }; diff --git a/include/xrpl/server/detail/io_list.h b/include/xrpl/server/detail/io_list.h index 0153bd3457..7ffa85b898 100644 --- a/include/xrpl/server/detail/io_list.h +++ b/include/xrpl/server/detail/io_list.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Manages a set of objects performing asynchronous I/O. */ +/** + * Manages a set of objects performing asynchronous I/O. + */ class IOList final { public: @@ -31,12 +33,13 @@ public: destroy(); } - /** Return the IOList associated with the work. - - Requirements: - The call to IOList::emplace to - create the work has already returned. - */ + /** + * Return the IOList associated with the work. + * + * Requirements: + * The call to IOList::emplace to + * create the work has already returned. + */ IOList& ios() { @@ -62,71 +65,74 @@ private: public: IOList() = default; - /** Destroy the list. - - Effects: - Closes the IOList if it was not previously - closed. No finisher is invoked in this case. - - Blocks until all work is destroyed. - */ + /** + * Destroy the list. + * + * Effects: + * Closes the IOList if it was not previously + * closed. No finisher is invoked in this case. + * + * Blocks until all work is destroyed. + */ ~IOList() { destroy(); } - /** Return `true` if the list is closed. - - Thread Safety: - Undefined result if called concurrently - with close(). - */ + /** + * Return `true` if the list is closed. + * + * Thread Safety: + * Undefined result if called concurrently + * with close(). + */ [[nodiscard]] bool closed() const { return closed_; } - /** Create associated work if not closed. - - Requirements: - `std::is_base_of_v == true` - - Thread Safety: - May be called concurrently. - - Effects: - Atomically creates, inserts, and returns new - work T, or returns nullptr if the io_list is - closed, - - If the call succeeds and returns a new object, - it is guaranteed that a subsequent call to close - will invoke Work::close on the object. - - */ + /** + * Create associated work if not closed. + * + * Requirements: + * `std::is_base_of_v == true` + * + * Thread Safety: + * May be called concurrently. + * + * Effects: + * Atomically creates, inserts, and returns new + * work T, or returns nullptr if the io_list is + * closed, + * + * If the call succeeds and returns a new object, + * it is guaranteed that a subsequent call to close + * will invoke Work::close on the object. + */ template std::shared_ptr emplace(Args&&... args); - /** Cancel active I/O. - - Thread Safety: - May not be called concurrently. - - Effects: - Associated work is closed. - - Finisher if provided, will be called when - all associated work is destroyed. The finisher - may be called from a foreign thread, or within - the call to this function. - - Only the first call to close will set the - finisher. - - No effect after the first call. - */ + /** + * Cancel active I/O. + * + * Thread Safety: + * May not be called concurrently. + * + * Effects: + * Associated work is closed. + * + * Finisher if provided, will be called when + * all associated work is destroyed. The finisher + * may be called from a foreign thread, or within + * the call to this function. + * + * Only the first call to close will set the + * finisher. + * + * No effect after the first call. + */ template void close(Finisher&& f); @@ -137,20 +143,21 @@ public: close([] {}); } - /** Block until the io_list stops. - - Effects: - The caller is blocked until the io_list is - closed and all associated work is destroyed. - - Thread safety: - May be called concurrently. - - Preconditions: - No call to io_context::run on any io_context - used by work objects associated with this io_list - exists in the caller's call stack. - */ + /** + * Block until the io_list stops. + * + * Effects: + * The caller is blocked until the io_list is + * closed and all associated work is destroyed. + * + * Thread safety: + * May be called concurrently. + * + * Preconditions: + * No call to io_context::run on any io_context + * used by work objects associated with this io_list + * exists in the caller's call stack. + */ template void join(); diff --git a/include/xrpl/shamap/Family.h b/include/xrpl/shamap/Family.h index c5bf953bfd..7624b3e600 100644 --- a/include/xrpl/shamap/Family.h +++ b/include/xrpl/shamap/Family.h @@ -35,18 +35,23 @@ public: virtual beast::Journal const& journal() = 0; - /** Return a pointer to the Family Full Below Cache */ + /** + * Return a pointer to the Family Full Below Cache + */ virtual std::shared_ptr getFullBelowCache() = 0; - /** Return a pointer to the Family Tree Node Cache */ + /** + * Return a pointer to the Family Tree Node Cache + */ virtual std::shared_ptr getTreeNodeCache() = 0; virtual void sweep() = 0; - /** Acquire ledger that has a missing node by ledger sequence + /** + * Acquire ledger that has a missing node by ledger sequence * * @param refNum Sequence of ledger to acquire. * @param nodeHash Hash of missing node to report in throw. @@ -54,7 +59,8 @@ public: virtual void missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) = 0; - /** Acquire ledger that has a missing node by ledger hash + /** + * Acquire ledger that has a missing node by ledger hash * * @param refHash Hash of ledger to acquire. * @param refNum Ledger sequence with missing node. diff --git a/include/xrpl/shamap/FullBelowCache.h b/include/xrpl/shamap/FullBelowCache.h index b6d1142eb4..1bb67c7453 100644 --- a/include/xrpl/shamap/FullBelowCache.h +++ b/include/xrpl/shamap/FullBelowCache.h @@ -17,9 +17,10 @@ namespace xrpl { namespace detail { -/** Remembers which tree keys have all descendants resident. - This optimizes the process of acquiring a complete tree. -*/ +/** + * Remembers which tree keys have all descendants resident. + * This optimizes the process of acquiring a complete tree. + */ class BasicFullBelowCache { private: @@ -31,13 +32,14 @@ public: using key_type = uint256; using clock_type = CacheType::clock_type; - /** Construct the cache. - - @param name A label for diagnostics and stats reporting. - @param collector The collector to use for reporting stats. - @param targetSize The cache target size. - @param targetExpirationSeconds The expiration time for items. - */ + /** + * Construct the cache. + * + * @param name A label for diagnostics and stats reporting. + * @param collector The collector to use for reporting stats. + * @param targetSize The cache target size. + * @param targetExpirationSeconds The expiration time for items. + */ BasicFullBelowCache( std::string const& name, clock_type& clock, @@ -49,59 +51,67 @@ public: { } - /** Return the clock associated with the cache. */ + /** + * Return the clock associated with the cache. + */ clock_type& clock() { return cache_.clock(); } - /** Return the number of elements in the cache. - Thread safety: - Safe to call from any thread. - */ + /** + * Return the number of elements in the cache. + * Thread safety: + * Safe to call from any thread. + */ std::size_t size() const { return cache_.size(); } - /** Remove expired cache items. - Thread safety: - Safe to call from any thread. - */ + /** + * Remove expired cache items. + * Thread safety: + * Safe to call from any thread. + */ void sweep() { cache_.sweep(); } - /** Refresh the last access time of an item, if it exists. - Thread safety: - Safe to call from any thread. - @param key The key to refresh. - @return `true` If the key exists. - */ + /** + * Refresh the last access time of an item, if it exists. + * Thread safety: + * Safe to call from any thread. + * @param key The key to refresh. + * @return `true` If the key exists. + */ bool touchIfExists(key_type const& key) { return cache_.touchIfExists(key); } - /** Insert a key into the cache. - If the key already exists, the last access time will still - be refreshed. - Thread safety: - Safe to call from any thread. - @param key The key to insert. - */ + /** + * Insert a key into the cache. + * If the key already exists, the last access time will still + * be refreshed. + * Thread safety: + * Safe to call from any thread. + * @param key The key to insert. + */ void insert(key_type const& key) { cache_.insert(key); } - /** generation determines whether cached entry is valid */ + /** + * generation determines whether cached entry is valid + */ std::uint32_t getGeneration() const { diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index d49e323b3f..a1194ccfd3 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -38,55 +38,62 @@ namespace xrpl { class SHAMapNodeID; class SHAMapSyncFilter; -/** Describes the current state of a given SHAMap */ +/** + * Describes the current state of a given SHAMap + */ enum class SHAMapState { - /** The map is in flux and objects can be added and removed. - - Example: map underlying the open ledger. + /** + * The map is in flux and objects can be added and removed. + * + * Example: map underlying the open ledger. */ Modifying = 0, - /** The map is set in stone and cannot be changed. - - Example: a map underlying a given closed ledger. + /** + * The map is set in stone and cannot be changed. + * + * Example: a map underlying a given closed ledger. */ Immutable = 1, - /** The map's hash is fixed but valid nodes may be missing and can be added. - - Example: a map that's syncing a given peer's closing ledger. + /** + * The map's hash is fixed but valid nodes may be missing and can be added. + * + * Example: a map that's syncing a given peer's closing ledger. */ Synching = 2, - /** The map is known to not be valid. - - Example: usually synching a corrupt ledger. + /** + * The map is known to not be valid. + * + * Example: usually synching a corrupt ledger. */ Invalid = 3, }; -/** A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree. - - A radix tree is a tree with two properties: - - 1. The key for a node is represented by the node's position in the tree - (the "prefix property"). - 2. A node with only one child is merged with that child - (the "merge property") - - These properties result in a significantly smaller memory footprint for - a radix tree. - - A fan-out of 16 means that each node in the tree has at most 16 - children. See https://en.wikipedia.org/wiki/Radix_tree - - A Merkle tree is a tree where each non-leaf node is labelled with the hash - of the combined labels of its children nodes. - - A key property of a Merkle tree is that testing for node inclusion is - O(log(N)) where N is the number of nodes in the tree. - - See https://en.wikipedia.org/wiki/Merkle_tree +/** + * A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree. + * + * A radix tree is a tree with two properties: + * + * 1. The key for a node is represented by the node's position in the tree + * (the "prefix property"). + * 2. A node with only one child is merged with that child + * (the "merge property") + * + * These properties result in a significantly smaller memory footprint for + * a radix tree. + * + * A fan-out of 16 means that each node in the tree has at most 16 + * children. See https://en.wikipedia.org/wiki/Radix_tree + * + * A Merkle tree is a tree where each non-leaf node is labelled with the hash + * of the combined labels of its children nodes. + * + * A key property of a Merkle tree is that testing for node inclusion is + * O(log(N)) where N is the number of nodes in the tree. + * + * See https://en.wikipedia.org/wiki/Merkle_tree */ class SHAMap { @@ -94,10 +101,14 @@ private: Family& f_; beast::Journal journal_; - /** ID to distinguish this map for all others we're sharing nodes with. */ + /** + * ID to distinguish this map for all others we're sharing nodes with. + */ std::uint32_t cowid_ = 1; - /** The sequence of the ledger that this map references, if any. */ + /** + * The sequence of the ledger that this map references, if any. + */ std::uint32_t ledgerSeq_ = 0; SHAMapTreeNodePtr root_; @@ -107,11 +118,15 @@ private: mutable bool full_ = false; // Map is believed complete in database public: - /** Number of children each non-leaf node has (the 'radix tree' part of the - * map) */ + /** + * Number of children each non-leaf node has (the 'radix tree' part of the + * map) + */ static constexpr unsigned int kBranchFactor = SHAMapInnerNode::kBranchFactor; - /** The depth of the hash map: data is only present in the leaves */ + /** + * The depth of the hash map: data is only present in the leaves + */ static constexpr unsigned int kLeafDepth = 64; using DeltaItem = @@ -147,10 +162,11 @@ public: //-------------------------------------------------------------------------- - /** Iterator to a SHAMap's leaves - This is always a const iterator. - Meets the requirements of ForwardRange. - */ + /** + * Iterator to a SHAMap's leaves + * This is always a const iterator. + * Meets the requirements of ForwardRange. + */ class ConstIterator; ConstIterator @@ -180,7 +196,9 @@ public: // normal hash access functions - /** Does the tree have an item with the given ID? */ + /** + * Does the tree have an item with the given ID? + */ bool hasItem(uint256 const& id) const; @@ -208,60 +226,66 @@ public: peekItem(uint256 const& id, SHAMapHash& hash) const; // traverse functions - /** Find the first item after the given item. - - @param id the identifier of the item. - - @note The item does not need to exist. + /** + * Find the first item after the given item. + * + * @param id the identifier of the item. + * + * @note The item does not need to exist. */ ConstIterator upperBound(uint256 const& id) const; - /** Find the object with the greatest object id smaller than the input id. - - @param id the identifier of the item. - - @note The item does not need to exist. + /** + * Find the object with the greatest object id smaller than the input id. + * + * @param id the identifier of the item. + * + * @note The item does not need to exist. */ ConstIterator lowerBound(uint256 const& id) const; - /** Visit every node in this SHAMap - - @param function called with every node visited. - If function returns false, visitNodes exits. - */ + /** + * Visit every node in this SHAMap + * + * @param function called with every node visited. + * If function returns false, visitNodes exits. + */ void visitNodes(std::function const& function) const; - /** Visit every node in this SHAMap that - is not present in the specified SHAMap - - @param function called with every node visited. - If function returns false, visitDifferences exits. - */ + /** + * Visit every node in this SHAMap that + * is not present in the specified SHAMap + * + * @param function called with every node visited. + * If function returns false, visitDifferences exits. + */ void visitDifferences(SHAMap const* have, std::function const&) const; - /** Visit every leaf node in this SHAMap - - @param function called with every non inner node visited. - */ + /** + * Visit every leaf node in this SHAMap + * + * @param function called with every non inner node visited. + */ void visitLeaves(std::function const&)> const&) const; // comparison/sync functions - /** Check for nodes in the SHAMap not available - - Traverse the SHAMap efficiently, maximizing I/O - concurrency, to discover nodes referenced in the - SHAMap but not available locally. - - @param maxNodes The maximum number of found nodes to return - @param filter The filter to use when retrieving nodes - @param return The nodes known to be missing - */ + /** + * Check for nodes in the SHAMap not available + * + * Traverse the SHAMap efficiently, maximizing I/O + * concurrency, to discover nodes referenced in the + * SHAMap but not available locally. + * + * @param maxNodes The maximum number of found nodes to return + * @param filter The filter to use when retrieving nodes + * @param return The nodes known to be missing + */ std::vector> getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter); @@ -291,7 +315,9 @@ public: static bool verifyProofPath(uint256 const& rootHash, uint256 const& key, std::vector const& path); - /** Serializes the root in a format appropriate for sending over the wire */ + /** + * Serializes the root in a format appropriate for sending over the wire + */ void serializeRoot(Serializer& s) const; @@ -317,11 +343,15 @@ public: bool compare(SHAMap const& otherMap, Delta& differences, int maxCount) const; - /** Convert any modified nodes to shared. */ + /** + * Convert any modified nodes to shared. + */ int unshare(); - /** Flush modified nodes to the nodestore and convert them to shared. */ + /** + * Flush modified nodes to the nodestore and convert them to shared. + */ int flushDirty(NodeObjectType t); @@ -364,30 +394,42 @@ private: SHAMapTreeNodePtr checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const; - /** Update hashes up to the root */ + /** + * Update hashes up to the root + */ void dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal); - /** Walk towards the specified id, returning the node. Caller must check - if the return is nullptr, and if not, if the node->peekItem()->key() == - id */ + /** + * Walk towards the specified id, returning the node. Caller must check + * if the return is nullptr, and if not, if the node->peekItem()->key() == + * id + */ SHAMapLeafNode* walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const; - /** Return nullptr if key not found */ + /** + * Return nullptr if key not found + */ SHAMapLeafNode* findKey(uint256 const& id) const; - /** Unshare the node, allowing it to be modified */ + /** + * Unshare the node, allowing it to be modified + */ template intr_ptr::SharedPtr unshareNode(intr_ptr::SharedPtr, SHAMapNodeID const& nodeID); - /** prepare a node to be modified before flushing */ + /** + * prepare a node to be modified before flushing + */ template intr_ptr::SharedPtr preFlushNode(intr_ptr::SharedPtr node) const; - /** write and canonicalize modified node */ + /** + * write and canonicalize modified node + */ SHAMapTreeNodePtr writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const; @@ -442,7 +484,9 @@ private: SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode&, int branch) const; - /** If there is only one leaf below this node, get its contents */ + /** + * If there is only one leaf below this node, get its contents + */ boost::intrusive_ptr const& onlyBelow(SHAMapTreeNode*) const; diff --git a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h index ee81107f61..96c853eb28 100644 --- a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h +++ b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a state object. */ +/** + * A leaf node for a state object. + */ class SHAMapAccountStateLeafNode final : public SHAMapLeafNode, public CountedObject { diff --git a/include/xrpl/shamap/SHAMapInnerNode.h b/include/xrpl/shamap/SHAMapInnerNode.h index 0fb4e24077..44d3bd6279 100644 --- a/include/xrpl/shamap/SHAMapInnerNode.h +++ b/include/xrpl/shamap/SHAMapInnerNode.h @@ -18,63 +18,72 @@ namespace xrpl { class SHAMapInnerNode final : public SHAMapTreeNode, public CountedObject { public: - /** Each inner node has 16 children (the 'radix tree' part of the map) */ + /** + * Each inner node has 16 children (the 'radix tree' part of the map) + */ static constexpr unsigned int kBranchFactor = 16; private: - /** Opaque type that contains the `hashes` array (array of type - `SHAMapHash`) and the `children` array (array of type - `intr_ptr::SharedPtr`). + /** + * Opaque type that contains the `hashes` array (array of type + * `SHAMapHash`) and the `children` array (array of type + * `intr_ptr::SharedPtr`). */ TaggedPointer hashesAndChildren_; std::uint32_t fullBelowGen_ = 0; std::uint16_t isBranch_ = 0; - /** A bitlock for the children of this node, with one bit per child */ + /** + * A bitlock for the children of this node, with one bit per child + */ mutable std::atomic lock_ = 0; - /** Convert arrays stored in `hashesAndChildren_` so they can store the - requested number of children. - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - - @note the arrays may allocate more than the requested value in - `toAllocate`. This is due to the implementation of TagPointer, which - only supports allocating arrays of 4 different sizes. + /** + * Convert arrays stored in `hashesAndChildren_` so they can store the + * requested number of children. + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note the arrays may allocate more than the requested value in + * `toAllocate`. This is due to the implementation of TagPointer, which + * only supports allocating arrays of 4 different sizes. */ void resizeChildArrays(std::uint8_t toAllocate); - /** Get the child's index inside the `hashes` or `children` array (stored in - `hashesAndChildren_`. - - These arrays may or may not be sparse). The optional will be empty is an - empty branch is requested and the arrays are sparse. - - @param i index of the requested child + /** + * Get the child's index inside the `hashes` or `children` array (stored in + * `hashesAndChildren_`. + * + * These arrays may or may not be sparse). The optional will be empty is an + * empty branch is requested and the arrays are sparse. + * + * @param i index of the requested child */ std::optional getChildIndex(int i) const; - /** Call the `f` callback for all 16 (branchFactor) branches - even if - the branch is empty. - - @param f a one parameter callback function. The parameter is the - child's hash. - */ + /** + * Call the `f` callback for all 16 (branchFactor) branches - even if + * the branch is empty. + * + * @param f a one parameter callback function. The parameter is the + * child's hash. + */ template void iterChildren(F&& f) const; - /** Call the `f` callback for all non-empty branches. - - @param f a two parameter callback function. The first parameter is - the branch number, the second parameter is the index into the array. - For dense formats these are the same, for sparse they may be - different. - */ + /** + * Call the `f` callback for all non-empty branches. + * + * @param f a two parameter callback function. The first parameter is + * the branch number, the second parameter is the index into the array. + * For dense formats these are the same, for sparse they may be + * different. + */ template void iterNonEmptyChildIndexes(F&& f) const; @@ -149,7 +158,9 @@ public: void updateHash() override; - /** Recalculate the hash of all children and this node. */ + /** + * Recalculate the hash of all children and this node. + */ void updateHashDeep(); diff --git a/include/xrpl/shamap/SHAMapLeafNode.h b/include/xrpl/shamap/SHAMapLeafNode.h index 112c243131..26cfde9fe8 100644 --- a/include/xrpl/shamap/SHAMapLeafNode.h +++ b/include/xrpl/shamap/SHAMapLeafNode.h @@ -46,11 +46,12 @@ public: boost::intrusive_ptr const& peekItem() const; - /** Set the item that this node points to and update the node's hash. - - @param i the new item - @return false if the change was, effectively, a noop (that is, if the - hash was unchanged); true otherwise. + /** + * Set the item that this node points to and update the node's hash. + * + * @param i the new item + * @return false if the change was, effectively, a noop (that is, if the + * hash was unchanged); true otherwise. */ bool setItem(boost::intrusive_ptr i); diff --git a/include/xrpl/shamap/SHAMapNodeID.h b/include/xrpl/shamap/SHAMapNodeID.h index b812c10ca9..6094892091 100644 --- a/include/xrpl/shamap/SHAMapNodeID.h +++ b/include/xrpl/shamap/SHAMapNodeID.h @@ -12,7 +12,9 @@ namespace xrpl { -/** Identifies a node inside a SHAMap */ +/** + * Identifies a node inside a SHAMap + */ class SHAMapNodeID : public CountedObject { private: @@ -64,7 +66,9 @@ public: createID(int depth, uint256 const& key); // FIXME-C++20: use spaceship and operator synthesis - /** Comparison operators */ + /** + * Comparison operators + */ bool operator<(SHAMapNodeID const& n) const { @@ -117,7 +121,8 @@ operator<<(std::ostream& out, SHAMapNodeID const& node) return out << to_string(node); } -/** Return an object representing a serialized SHAMap Node ID +/** + * Return an object representing a serialized SHAMap Node ID * * @param s A string of bytes * @param data a non-null pointer to a buffer of @param size bytes. @@ -136,7 +141,9 @@ deserializeSHAMapNodeID(std::string_view s) } /** @} */ -/** Returns the branch that would contain the given hash */ +/** + * Returns the branch that would contain the given hash + */ [[nodiscard]] unsigned int selectBranch(SHAMapNodeID const& id, uint256 const& hash); diff --git a/include/xrpl/shamap/SHAMapSyncFilter.h b/include/xrpl/shamap/SHAMapSyncFilter.h index b6ce175915..60f340db3c 100644 --- a/include/xrpl/shamap/SHAMapSyncFilter.h +++ b/include/xrpl/shamap/SHAMapSyncFilter.h @@ -7,7 +7,9 @@ #include #include -/** Callback for filtering SHAMap during sync. */ +/** + * Callback for filtering SHAMap during sync. + */ namespace xrpl { class SHAMapSyncFilter diff --git a/include/xrpl/shamap/SHAMapTreeNode.h b/include/xrpl/shamap/SHAMapTreeNode.h index 1eebbaa17f..c8b242238a 100644 --- a/include/xrpl/shamap/SHAMapTreeNode.h +++ b/include/xrpl/shamap/SHAMapTreeNode.h @@ -39,18 +39,20 @@ class SHAMapTreeNode : public IntrusiveRefCounts protected: SHAMapHash hash_; - /** Determines the owning SHAMap, if any. Used for copy-on-write semantics. - - If this value is 0, the node is not dirty and does not need to be - flushed. It is eligible for sharing and may be included multiple - SHAMap instances. + /** + * Determines the owning SHAMap, if any. Used for copy-on-write semantics. + * + * If this value is 0, the node is not dirty and does not need to be + * flushed. It is eligible for sharing and may be included multiple + * SHAMap instances. */ std::uint32_t cowid_; - /** Construct a node - - @param cowid The identifier of a SHAMap. For more, see #cowid_ - @param hash The hash associated with this node, if any. + /** + * Construct a node + * + * @param cowid The identifier of a SHAMap. For more, see #cowid_ + * @param hash The hash associated with this node, if any. */ /** @{ */ explicit SHAMapTreeNode(std::uint32_t cowid) noexcept : cowid_(cowid) @@ -74,28 +76,30 @@ public: virtual void partialDestructor() {}; - /** \defgroup SHAMap Copy-on-Write Support - - By nature, a node may appear in multiple SHAMap instances. Rather - than actually duplicating these nodes, SHAMap opts to be memory - efficient and uses copy-on-write semantics for nodes. - - Only nodes that are not modified and don't need to be flushed back - can be shared. Once a node needs to be changed, it must first be - copied and the copy must marked as not shareable. - - Note that just because a node may not be *owned* by a given SHAMap - instance does not mean that the node is NOT a part of any SHAMap. It - only means that the node is not owned exclusively by any one SHAMap. - - For more on copy-on-write, check out: - https://en.wikipedia.org/wiki/Copy-on-write + /** + * @defgroup SHAMap Copy-on-Write Support + * + * By nature, a node may appear in multiple SHAMap instances. Rather + * than actually duplicating these nodes, SHAMap opts to be memory + * efficient and uses copy-on-write semantics for nodes. + * + * Only nodes that are not modified and don't need to be flushed back + * can be shared. Once a node needs to be changed, it must first be + * copied and the copy must marked as not shareable. + * + * Note that just because a node may not be *owned* by a given SHAMap + * instance does not mean that the node is NOT a part of any SHAMap. It + * only means that the node is not owned exclusively by any one SHAMap. + * + * For more on copy-on-write, check out: + * https://en.wikipedia.org/wiki/Copy-on-write */ /** @{ */ - /** Returns the SHAMap that owns this node. - - @return the ID of the SHAMap that owns this node, or 0 if the - node is not owned by any SHAMap and is a candidate for sharing. + /** + * Returns the SHAMap that owns this node. + * + * @return the ID of the SHAMap that owns this node, or 0 if the + * node is not owned by any SHAMap and is a candidate for sharing. */ std::uint32_t cowid() const @@ -103,10 +107,11 @@ public: return cowid_; } - /** If this node is shared with another map, mark it as no longer shared. - - Only nodes that are not modified and do not need to be flushed back - should be marked as unshared. + /** + * If this node is shared with another map, mark it as no longer shared. + * + * Only nodes that are not modified and do not need to be flushed back + * should be marked as unshared. */ void unshare() @@ -114,39 +119,55 @@ public: cowid_ = 0; } - /** Make a copy of this node, setting the owner. */ + /** + * Make a copy of this node, setting the owner. + */ virtual SHAMapTreeNodePtr clone(std::uint32_t cowid) const = 0; /** @} */ - /** Recalculate the hash of this node. */ + /** + * Recalculate the hash of this node. + */ virtual void updateHash() = 0; - /** Return the hash of this node. */ + /** + * Return the hash of this node. + */ SHAMapHash const& getHash() const { return hash_; } - /** Determines the type of node. */ + /** + * Determines the type of node. + */ virtual SHAMapNodeType getType() const = 0; - /** Determines if this is a leaf node. */ + /** + * Determines if this is a leaf node. + */ virtual bool isLeaf() const = 0; - /** Determines if this is an inner node. */ + /** + * Determines if this is an inner node. + */ virtual bool isInner() const = 0; - /** Serialize the node in a format appropriate for sending over the wire */ + /** + * Serialize the node in a format appropriate for sending over the wire + */ virtual void serializeForWire(Serializer&) const = 0; - /** Serialize the node in a format appropriate for hashing */ + /** + * Serialize the node in a format appropriate for hashing + */ virtual void serializeWithPrefix(Serializer&) const = 0; diff --git a/include/xrpl/shamap/SHAMapTxLeafNode.h b/include/xrpl/shamap/SHAMapTxLeafNode.h index 86186434f8..9b9ac2f996 100644 --- a/include/xrpl/shamap/SHAMapTxLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a transaction. No metadata is included. */ +/** + * A leaf node for a transaction. No metadata is included. + */ class SHAMapTxLeafNode final : public SHAMapLeafNode, public CountedObject { public: diff --git a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h index 9e4573d45b..6f8a7ebfda 100644 --- a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h @@ -15,7 +15,9 @@ namespace xrpl { -/** A leaf node for a transaction and its associated metadata. */ +/** + * A leaf node for a transaction and its associated metadata. + */ class SHAMapTxPlusMetaLeafNode final : public SHAMapLeafNode, public CountedObject { diff --git a/include/xrpl/shamap/detail/TaggedPointer.h b/include/xrpl/shamap/detail/TaggedPointer.h index 79f3464d5b..509e6cc58d 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.h +++ b/include/xrpl/shamap/detail/TaggedPointer.h @@ -12,111 +12,122 @@ namespace xrpl { -/** TaggedPointer is a combination of a pointer and a mask stored in the - lowest two bits. - - Since pointers do not have arbitrary alignment, the lowest bits in the - pointer are guaranteed to be zero. TaggedPointer stores information in these - low bits. When dereferencing the pointer, these low "tag" bits are set to - zero. When accessing the tag bits, the high "pointer" bits are set to zero. - - The "pointer" part points to the equivalent to an array of - `SHAMapHash` followed immediately by an array of - `shared_ptr`. The sizes of these arrays are - determined by the tag. The tag is an index into an array (`boundaries`, - defined in the cpp file) that specifies the size. Both arrays are the - same size. Note that the sizes may be smaller than the full 16 elements - needed to explicitly store all the children. In this case, the arrays - only store the non-empty children. The non-empty children are stored in - index order. For example, if only children `2` and `14` are non-empty, a - two-element array would store child `2` in array index 0 and child `14` - in array index 1. There are functions to convert between a child's tree - index and the child's index in a sparse array. - - The motivation for this class is saving RAM. A large percentage of inner - nodes only store a small number of children. Memory can be saved by - storing the inner node's children in sparse arrays. Measurements show - that on average a typical SHAMap's inner nodes can be stored using only - 25% of the original space. -*/ +/** + * TaggedPointer is a combination of a pointer and a mask stored in the + * lowest two bits. + * + * Since pointers do not have arbitrary alignment, the lowest bits in the + * pointer are guaranteed to be zero. TaggedPointer stores information in these + * low bits. When dereferencing the pointer, these low "tag" bits are set to + * zero. When accessing the tag bits, the high "pointer" bits are set to zero. + * + * The "pointer" part points to the equivalent to an array of + * `SHAMapHash` followed immediately by an array of + * `shared_ptr`. The sizes of these arrays are + * determined by the tag. The tag is an index into an array (`boundaries`, + * defined in the cpp file) that specifies the size. Both arrays are the + * same size. Note that the sizes may be smaller than the full 16 elements + * needed to explicitly store all the children. In this case, the arrays + * only store the non-empty children. The non-empty children are stored in + * index order. For example, if only children `2` and `14` are non-empty, a + * two-element array would store child `2` in array index 0 and child `14` + * in array index 1. There are functions to convert between a child's tree + * index and the child's index in a sparse array. + * + * The motivation for this class is saving RAM. A large percentage of inner + * nodes only store a small number of children. Memory can be saved by + * storing the inner node's children in sparse arrays. Measurements show + * that on average a typical SHAMap's inner nodes can be stored using only + * 25% of the original space. + */ class TaggedPointer { private: static_assert( alignof(SHAMapHash) >= 4, "Bad alignment: Tag pointer requires low two bits to be zero."); - /** Upper bits are the pointer, lowest two bits are the tag - A moved-from object will have a tp_ of zero. - */ + /** + * Upper bits are the pointer, lowest two bits are the tag + * A moved-from object will have a tp_ of zero. + */ std::uintptr_t tp_ = 0; - /** bit-and with this mask to get the tag bits (lowest two bits) */ + /** + * bit-and with this mask to get the tag bits (lowest two bits) + */ static constexpr std::uintptr_t kTagMask = 3; - /** bit-and with this mask to get the pointer bits (mask out the tag) */ + /** + * bit-and with this mask to get the pointer bits (mask out the tag) + */ static constexpr std::uintptr_t kPtrMask = ~kTagMask; - /** Deallocate memory and run destructors */ + /** + * Deallocate memory and run destructors + */ void destroyHashesAndChildren(); struct RawAllocateTag { }; - /** This constructor allocates space for the hashes and children, but - does not run constructors. - - @param RawAllocateTag used to select overload only - - @param numChildren allocate space for at least this number of children - (must be <= branchFactor) - - @note Since the hashes/children destructors are always run in the - TaggedPointer destructor, this means those constructors _must_ be run - after this constructor is run. This constructor is private and only used - in places where the hashes/children constructor are subsequently run. - */ + /** + * This constructor allocates space for the hashes and children, but + * does not run constructors. + * + * @param RawAllocateTag used to select overload only + * + * @param numChildren allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note Since the hashes/children destructors are always run in the + * TaggedPointer destructor, this means those constructors _must_ be run + * after this constructor is run. This constructor is private and only used + * in places where the hashes/children constructor are subsequently run. + */ explicit TaggedPointer(RawAllocateTag, std::uint8_t numChildren); public: TaggedPointer() = delete; explicit TaggedPointer(std::uint8_t numChildren); - /** Constructor is used change the number of allocated children. - - Existing children from `other` are copied (toAllocate must be >= the - number of children). The motivation for making this a constructor is it - saves unneeded copying and zeroing out of hashes if this were - implemented directly in the SHAMapInnerNode class. - - @param other children and hashes are moved from this param - - @param isBranch bitset of non-empty children in `other` - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - */ + /** + * Constructor is used change the number of allocated children. + * + * Existing children from `other` are copied (toAllocate must be >= the + * number of children). The motivation for making this a constructor is it + * saves unneeded copying and zeroing out of hashes if this were + * implemented directly in the SHAMapInnerNode class. + * + * @param other children and hashes are moved from this param + * + * @param isBranch bitset of non-empty children in `other` + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + */ explicit TaggedPointer(TaggedPointer&& other, std::uint16_t isBranch, std::uint8_t toAllocate); - /** Given `other` with the specified children in `srcBranches`, create a - new TaggedPointer with the allocated number of children and the - children specified in `dstBranches`. - - @param other children and hashes are moved from this param - - @param srcBranches bitset of non-empty children in `other` - - @param dstBranches bitset of children to copy from `other` (or space to - leave in a sparse array - see note below) - - @param toAllocate allocate space for at least this number of children - (must be <= branchFactor) - - @note a child may be absent in srcBranches but present in dstBranches - (if dst has a sparse representation, space for the new child will be - left in the sparse array). Typically, srcBranches and dstBranches will - differ by at most one bit. The function works correctly if they differ - by more, but there are likely more efficient algorithms to consider if - this becomes a common use-case. - */ + /** + * Given `other` with the specified children in `srcBranches`, create a + * new TaggedPointer with the allocated number of children and the + * children specified in `dstBranches`. + * + * @param other children and hashes are moved from this param + * + * @param srcBranches bitset of non-empty children in `other` + * + * @param dstBranches bitset of children to copy from `other` (or space to + * leave in a sparse array - see note below) + * + * @param toAllocate allocate space for at least this number of children + * (must be <= branchFactor) + * + * @note a child may be absent in srcBranches but present in dstBranches + * (if dst has a sparse representation, space for the new child will be + * left in the sparse array). Typically, srcBranches and dstBranches will + * differ by at most one bit. The function works correctly if they differ + * by more, but there are likely more efficient algorithms to consider if + * this becomes a common use-case. + */ explicit TaggedPointer( TaggedPointer&& other, std::uint16_t srcBranches, @@ -132,68 +143,81 @@ public: ~TaggedPointer(); - /** Decode the tagged pointer into its tag and pointer */ + /** + * Decode the tagged pointer into its tag and pointer + */ [[nodiscard]] std::pair decode() const; - /** Get the number of elements allocated for each array */ + /** + * Get the number of elements allocated for each array + */ [[nodiscard]] std::uint8_t capacity() const; - /** Check if the arrays have a dense format. - - @note The dense format is when there is an array element for all 16 - (branchFactor) possible children. - */ + /** + * Check if the arrays have a dense format. + * + * @note The dense format is when there is an array element for all 16 + * (branchFactor) possible children. + */ [[nodiscard]] bool isDense() const; - /** Get the number of elements in each array and a pointer to the start - of each array. - */ + /** + * Get the number of elements in each array and a pointer to the start + * of each array. + */ [[nodiscard]] std::tuple getHashesAndChildren() const; - /** Get the `hashes` array */ + /** + * Get the `hashes` array + */ [[nodiscard]] SHAMapHash* getHashes() const; - /** Get the `children` array */ + /** + * Get the `children` array + */ [[nodiscard]] SHAMapTreeNodePtr* getChildren() const; - /** Call the `f` callback for all 16 (branchFactor) branches - even if - the branch is empty. - - @param isBranch bitset of non-empty children - - @param f a one parameter callback function. The parameter is the - child's hash. + /** + * Call the `f` callback for all 16 (branchFactor) branches - even if + * the branch is empty. + * + * @param isBranch bitset of non-empty children + * + * @param f a one parameter callback function. The parameter is the + * child's hash. */ template void iterChildren(std::uint16_t isBranch, F&& f) const; - /** Call the `f` callback for all non-empty branches. - - @param isBranch bitset of non-empty children - - @param f a two parameter callback function. The first parameter is - the branch number, the second parameter is the index into the array. - For dense formats these are the same, for sparse they may be - different. + /** + * Call the `f` callback for all non-empty branches. + * + * @param isBranch bitset of non-empty children + * + * @param f a two parameter callback function. The first parameter is + * the branch number, the second parameter is the index into the array. + * For dense formats these are the same, for sparse they may be + * different. */ template void iterNonEmptyChildIndexes(std::uint16_t isBranch, F&& f) const; - /** Get the child's index inside the `hashes` or `children` array (which - may or may not be sparse). The optional will be empty if an empty - branch is requested and the children are sparse. - - @param isBranch bitset of non-empty children - - @param i index of the requested child + /** + * Get the child's index inside the `hashes` or `children` array (which + * may or may not be sparse). The optional will be empty if an empty + * branch is requested and the children are sparse. + * + * @param isBranch bitset of non-empty children + * + * @param i index of the requested child */ [[nodiscard]] std::optional getChildIndex(std::uint16_t isBranch, int i) const; diff --git a/include/xrpl/tx/ApplyContext.h b/include/xrpl/tx/ApplyContext.h index ca778387cd..472afdf624 100644 --- a/include/xrpl/tx/ApplyContext.h +++ b/include/xrpl/tx/ApplyContext.h @@ -21,7 +21,9 @@ namespace xrpl { -/** State information when applying a tx. */ +/** + * State information when applying a tx. + */ class ApplyContext { public: @@ -83,7 +85,9 @@ public: return flags_; } - /** Sets the DeliveredAmount field in the metadata */ + /** + * Sets the DeliveredAmount field in the metadata + */ void deliver(STAmount const& amount) { @@ -91,18 +95,26 @@ public: view_->deliver(amount); } - /** Discard changes and start fresh. */ + /** + * Discard changes and start fresh. + */ void discard(); - /** Apply the transaction result to the base. */ + /** + * Apply the transaction result to the base. + */ std::optional apply(TER); - /** Get the number of unapplied changes. */ + /** + * Get the number of unapplied changes. + */ std::size_t size(); - /** Visit unapplied changes. */ + /** + * Visit unapplied changes. + */ void visit( std::functionrawDestroyXRP(fee); } - /** Applies all invariant checkers one by one. - - @param result the result generated by processing this transaction. - @param fee the fee charged for this transaction - @return the result code that should be returned for this transaction. + /** + * Applies all invariant checkers one by one. + * + * @param result the result generated by processing this transaction. + * @param fee the fee charged for this transaction + * @return the result code that should be returned for this transaction. */ TER checkInvariants(TER const result, XRPAmount const fee); + ApplyViewContext + getApplyViewContext() + { + XRPL_ASSERT( + view_.has_value(), + "xrpl::ApplyContext::getApplyViewContext : view_ emplaced in constructor"); + return {.view = *view_, .tx = tx}; + } + private: static TER failInvariantCheck(TER const result); diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index bc5e8c80e7..a71285f70e 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -29,7 +31,9 @@ namespace xrpl { -/** State information when preflighting a tx. */ +/** + * State information when preflighting a tx. + */ struct PreflightContext { public: @@ -72,7 +76,9 @@ public: operator=(PreflightContext const&) = delete; }; -/** State information when determining if a tx is likely to claim a fee. */ +/** + * State information when determining if a tx is likely to claim a fee. + */ struct PreclaimContext { public: @@ -126,6 +132,21 @@ struct PreflightResult; // Needed for preflight specialization class Change; +enum class FeePayerType { + Account, + Delegate, + SponsorCoSigned, + SponsorPreFunded, +}; + +struct FeePayer +{ + AccountID id; + Keylet keylet; + SF_AMOUNT const& balanceField; + FeePayerType type{FeePayerType::Account}; +}; + class Transactor { protected: @@ -144,7 +165,9 @@ public: enum class ConsequencesFactoryType { Normal, Blocker, Custom }; - /** Process the transaction. */ + /** + * Process the transaction. + */ ApplyResult operator()(); @@ -160,16 +183,17 @@ public: return ctx_.view(); } - /** Check all invariants for the current transaction. + /** + * Check all invariants for the current transaction. * - * Runs transaction-specific invariants first (visitInvariantEntry + - * finalizeInvariants), then protocol-level invariants. Both layers - * always run; the worst failure code is returned. + * Runs transaction-specific invariants first (visitInvariantEntry + + * finalizeInvariants), then protocol-level invariants. Both layers + * always run; the worst failure code is returned. * - * @param result the tentative TER from transaction processing. - * @param fee the fee consumed by the transaction. + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. * - * @return the final TER after all invariant checks. + * @return the final TER after all invariant checks. */ [[nodiscard]] TER checkInvariants(TER result, XRPAmount fee); @@ -298,6 +322,10 @@ public: return T::checkGranularSemantics(view, tx, heldGranularPermissions); } + + static NotTEC + checkSponsor(ReadView const& view, STTx const& tx); + ///////////////////////////////////////////////////// // Interface used by AccountDelete @@ -320,40 +348,42 @@ protected: virtual TER doApply() = 0; - /** Inspect a single ledger entry modified by this transaction. + /** + * Inspect a single ledger entry modified by this transaction. * - * Called once for every SLE created, modified, or deleted by the - * transaction, before finalizeInvariants. Implementations should - * accumulate whatever state they need to verify transaction-specific - * post-conditions. + * Called once for every SLE created, modified, or deleted by the + * transaction, before finalizeInvariants. Implementations should + * accumulate whatever state they need to verify transaction-specific + * post-conditions. * - * @param isDelete true if the entry was erased from the ledger. - * @param before the entry's state before the transaction (nullptr - * for newly created entries). - * @param after the entry's state as supplied by the apply logic - * for this transaction. For deletions, this is the - * SLE being erased and is not guaranteed to be null; - * callers must use isDelete rather than after == nullptr - * to detect deletions. + * @param isDelete true if the entry was erased from the ledger. + * @param before the entry's state before the transaction (nullptr + * for newly created entries). + * @param after the entry's state as supplied by the apply logic + * for this transaction. For deletions, this is the + * SLE being erased and is not guaranteed to be null; + * callers must use isDelete rather than after == nullptr + * to detect deletions. */ virtual void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0; - /** Check transaction-specific post-conditions after all entries have - * been visited. + /** + * Check transaction-specific post-conditions after all entries have + * been visited. * - * Called once after every modified ledger entry has been passed to - * visitInvariantEntry. Returns true if all transaction-specific - * invariants hold, or false to fail the transaction with - * tecINVARIANT_FAILED. + * Called once after every modified ledger entry has been passed to + * visitInvariantEntry. Returns true if all transaction-specific + * invariants hold, or false to fail the transaction with + * tecINVARIANT_FAILED. * - * @param tx the transaction being applied. - * @param result the tentative TER result so far. - * @param fee the fee consumed by the transaction. - * @param view read-only view of the ledger after the transaction. - * @param j journal for logging invariant failures. + * @param tx the transaction being applied. + * @param result the tentative TER result so far. + * @param fee the fee consumed by the transaction. + * @param view read-only view of the ledger after the transaction. + * @param j journal for logging invariant failures. * - * @return true if all invariants pass; false otherwise. + * @return true if all invariants pass; false otherwise. */ [[nodiscard]] virtual bool finalizeInvariants( @@ -363,14 +393,15 @@ protected: ReadView const& view, beast::Journal const& j) = 0; - /** Compute the minimum fee required to process a transaction - with a given baseFee based on the current server load. - - @param registry The service registry. - @param baseFee The base fee of a candidate transaction - @see xrpl::calculateBaseFee - @param fees Fee settings from the current ledger - @param flags Transaction processing fees + /** + * Compute the minimum fee required to process a transaction + * with a given baseFee based on the current server load. + * + * @param registry The service registry. + * @param baseFee The base fee of a candidate transaction + * @see xrpl::calculateBaseFee + * @param fees Fee settings from the current ledger + * @param flags Transaction processing fees */ static XRPAmount minimumFee(ServiceRegistry& registry, XRPAmount baseFee, Fees const& fees, ApplyFlags flags); @@ -419,12 +450,16 @@ protected: unit::ValueUnit max, unit::ValueUnit min = unit::ValueUnit{}); - /// Minimum will usually be zero. + /** + * Minimum will usually be zero. + */ template static bool validNumericMinimum(std::optional value, T min = T{}); - /// Minimum will usually be zero. + /** + * Minimum will usually be zero. + */ template static bool validNumericMinimum( @@ -459,6 +494,9 @@ private: std::pair reset(XRPAmount fee); + static FeePayer + getFeePayer(ReadView const& view, STTx const& tx); + TER consumeSeqProxy(SLE::pointer const& sleAccount); TER @@ -469,44 +507,48 @@ private: void trapTransaction(uint256) const; - /** Performs early sanity checks on the account and fee fields. - - (And passes flagMask to preflight0) - - Do not try to call preflight1 from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Performs early sanity checks on the account and fee fields. + * + * (And passes flagMask to preflight0) + * + * Do not try to call preflight1 from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflight1(PreflightContext const& ctx, std::uint32_t flagMask); - /** Checks whether the signature appears valid - - Do not try to call preflight2 from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Checks whether the signature appears valid + * + * Do not try to call preflight2 from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflight2(PreflightContext const& ctx); - /** Universal validations - - Valid MPTAmount and XRPAmount - - Do not try to call preflightUniversal from preflight() in derived classes. See - the description of invokePreflight for details. - */ + /** + * Universal validations + * - Valid MPTAmount and XRPAmount + * + * Do not try to call preflightUniversal from preflight() in derived classes. See + * the description of invokePreflight for details. + */ static NotTEC preflightUniversal(PreflightContext const& ctx); - /** Check transaction-specific invariants only. + /** + * Check transaction-specific invariants only. * - * Walks every modified ledger entry via visitInvariantEntry, then - * calls finalizeInvariants on the derived transactor. Returns - * tecINVARIANT_FAILED if any transaction invariant is violated. + * Walks every modified ledger entry via visitInvariantEntry, then + * calls finalizeInvariants on the derived transactor. Returns + * tecINVARIANT_FAILED if any transaction invariant is violated. * - * @param result the tentative TER from transaction processing. - * @param fee the fee consumed by the transaction. + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. * - * @return the original result if all invariants pass, or - * tecINVARIANT_FAILED otherwise. + * @return the original result if all invariants pass, or + * tecINVARIANT_FAILED otherwise. */ [[nodiscard]] TER checkTransactionInvariants(TER result, XRPAmount fee); @@ -518,20 +560,24 @@ Transactor::checkExtraFeatures(PreflightContext const& ctx) return true; } -/** Performs early sanity checks on the txid and flags */ +/** + * Performs early sanity checks on the txid and flags + */ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask); namespace detail { -/** Checks the validity of the transactor signing key. +/** + * Checks the validity of the transactor signing key. * * Normally called from preflight1 with ctx.tx. */ NotTEC preflightCheckSigningKey(STObject const& sigObject, beast::Journal j); -/** Checks the special signing key state needed for simulation +/** + * Checks the special signing key state needed for simulation * * Normally called from preflight2 with ctx.tx. */ diff --git a/include/xrpl/tx/apply.h b/include/xrpl/tx/apply.h index 55d365c31b..8c11d870f1 100644 --- a/include/xrpl/tx/apply.h +++ b/include/xrpl/tx/apply.h @@ -16,88 +16,98 @@ namespace xrpl { class HashRouter; class ServiceRegistry; -/** Describes the pre-processing validity of a transaction. - - @see checkValidity, forceValidity -*/ +/** + * Describes the pre-processing validity of a transaction. + * + * @see checkValidity, forceValidity + */ enum class Validity { - /// Signature is bad. Didn't do local checks. + /** + * Signature is bad. Didn't do local checks. + */ SigBad, - /// Signature is good, but local checks fail. + /** + * Signature is good, but local checks fail. + */ SigGoodOnly, - /// Signature and local checks are good / passed. + /** + * Signature and local checks are good / passed. + */ Valid }; -/** Checks transaction signature and local checks. - - @return A `Validity` enum representing how valid the - `STTx` is and, if not `Valid`, a reason string. - - @note Results are cached internally, so tests will not be - repeated over repeated calls, unless cache expires. - - @return `std::pair`, where `.first` is the status, and - `.second` is the reason if appropriate. - - @see Validity -*/ +/** + * Checks transaction signature and local checks. + * + * @return A `Validity` enum representing how valid the + * `STTx` is and, if not `Valid`, a reason string. + * + * @note Results are cached internally, so tests will not be + * repeated over repeated calls, unless cache expires. + * + * @return `std::pair`, where `.first` is the status, and + * `.second` is the reason if appropriate. + * + * @see Validity + */ std::pair checkValidity(HashRouter& router, STTx const& tx, Rules const& rules); -/** Sets the validity of a given transaction in the cache. - - @warning Use with extreme care. - - @note Can only raise the validity to a more valid state, - and can not override anything cached bad. - - @see checkValidity, Validity -*/ +/** + * Sets the validity of a given transaction in the cache. + * + * @warning Use with extreme care. + * + * @note Can only raise the validity to a more valid state, + * and can not override anything cached bad. + * + * @see checkValidity, Validity + */ void forceValidity(HashRouter& router, uint256 const& txid, Validity validity); -/** Apply a transaction to an `OpenView`. - - This function is the canonical way to apply a transaction - to a ledger. It rolls the validation and application - steps into one function. To do the steps manually, the - correct calling order is: - @code{.cpp} - preflight -> preclaim -> doApply - @endcode - The result of one function must be passed to the next. - The `preflight` result can be safely cached and reused - asynchronously, but `preclaim` and `doApply` must be called - in the same thread and with the same view. - - @note Does not throw. - - For open ledgers, the `Transactor` will catch exceptions - and return `tefEXCEPTION`. For closed ledgers, the - `Transactor` will attempt to only charge a fee, - and return `tecFAILED_PROCESSING`. - - If the `Transactor` gets an exception while trying - to charge the fee, it will be caught and - turned into `tefEXCEPTION`. - - For network health, a `Transactor` makes its - best effort to at least charge a fee if the - ledger is closed. - - @param app The current running `Application`. - @param view The open ledger that the transaction - will attempt to be applied to. - @param tx The transaction to be checked. - @param flags `ApplyFlags` describing processing options. - @param journal A journal. - - @see preflight, preclaim, doApply - - @return A pair with the `TER` and a `bool` indicating - whether or not the transaction was applied. -*/ +/** + * Apply a transaction to an `OpenView`. + * + * This function is the canonical way to apply a transaction + * to a ledger. It rolls the validation and application + * steps into one function. To do the steps manually, the + * correct calling order is: + * @code + * preflight -> preclaim -> doApply + * @endcode + * The result of one function must be passed to the next. + * The `preflight` result can be safely cached and reused + * asynchronously, but `preclaim` and `doApply` must be called + * in the same thread and with the same view. + * + * @note Does not throw. + * + * For open ledgers, the `Transactor` will catch exceptions + * and return `tefEXCEPTION`. For closed ledgers, the + * `Transactor` will attempt to only charge a fee, + * and return `tecFAILED_PROCESSING`. + * + * If the `Transactor` gets an exception while trying + * to charge the fee, it will be caught and + * turned into `tefEXCEPTION`. + * + * For network health, a `Transactor` makes its + * best effort to at least charge a fee if the + * ledger is closed. + * + * @param app The current running `Application`. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * @param tx The transaction to be checked. + * @param flags `ApplyFlags` describing processing options. + * @param journal A journal. + * + * @see preflight, preclaim, doApply + * + * @return A pair with the `TER` and a `bool` indicating + * whether or not the transaction was applied. + */ ApplyResult apply( ServiceRegistry& registry, @@ -106,26 +116,34 @@ apply( ApplyFlags flags, beast::Journal journal); -/** Enum class for return value from `applyTransaction` - - @see applyTransaction -*/ +/** + * Enum class for return value from `applyTransaction` + * + * @see applyTransaction + */ enum class ApplyTransactionResult { - /// Applied to this ledger + /** + * Applied to this ledger + */ Success, - /// Should not be retried in this ledger + /** + * Should not be retried in this ledger + */ Fail, - /// Should be retried in this ledger + /** + * Should be retried in this ledger + */ Retry }; -/** Transaction application helper - - Provides more detailed logging and decodes the - correct behavior based on the `TER` type - - @see ApplyTransactionResult -*/ +/** + * Transaction application helper + * + * Provides more detailed logging and decodes the + * correct behavior based on the `TER` type + * + * @see ApplyTransactionResult + */ ApplyTransactionResult applyTransaction( ServiceRegistry& registry, diff --git a/include/xrpl/tx/applySteps.h b/include/xrpl/tx/applySteps.h index 3298e49192..bd495481f2 100644 --- a/include/xrpl/tx/applySteps.h +++ b/include/xrpl/tx/applySteps.h @@ -33,8 +33,9 @@ struct ApplyResult } }; -/** Return true if the transaction can claim a fee (tec), - and the `ApplyFlags` do not allow soft failures. +/** + * Return true if the transaction can claim a fee (tec), + * and the `ApplyFlags` do not allow soft failures. */ inline bool isTecClaimHardFail(TER ter, ApplyFlags flags) @@ -42,34 +43,51 @@ isTecClaimHardFail(TER ter, ApplyFlags flags) return isTecClaim(ter) && ((flags & TapRetry) == 0u); } -/** Class describing the consequences to the account - of applying a transaction if the transaction consumes - the maximum XRP allowed. -*/ +/** + * Class describing the consequences to the account + * of applying a transaction if the transaction consumes + * the maximum XRP allowed. + */ class TxConsequences { public: - /// Describes how the transaction affects subsequent - /// transactions + /** + * Describes how the transaction affects subsequent + * transactions + */ enum class Category { - /// Moves currency around, creates offers, etc. + /** + * Moves currency around, creates offers, etc. + */ Normal = 0, - /// Affects the ability of subsequent transactions - /// to claim a fee. Eg. `SetRegularKey` + /** + * Affects the ability of subsequent transactions + * to claim a fee. Eg. `SetRegularKey` + */ Blocker }; private: - /// Describes how the transaction affects subsequent - /// transactions + /** + * Describes how the transaction affects subsequent + * transactions + */ bool isBlocker_; - /// Transaction fee + /** + * Transaction fee + */ XRPAmount fee_; - /// Does NOT include the fee. + /** + * Does NOT include the fee. + */ XRPAmount potentialSpend_; - /// SeqProxy of transaction. + /** + * SeqProxy of transaction. + */ SeqProxy seqProx_; - /// Number of sequences consumed. + /** + * Number of sequences consumed. + */ std::uint32_t sequencesConsumed_; public: @@ -77,58 +95,84 @@ public: // Asserts if tesSUCCESS is passed. explicit TxConsequences(NotTEC pfResult); - /// Constructor if the STTx has no notable consequences for the TxQ. + /** + * Constructor if the STTx has no notable consequences for the TxQ. + */ explicit TxConsequences(STTx const& tx); - /// Constructor for a blocker. + /** + * Constructor for a blocker. + */ TxConsequences(STTx const& tx, Category category); - /// Constructor for an STTx that may consume more XRP than the fee. + /** + * Constructor for an STTx that may consume more XRP than the fee. + */ TxConsequences(STTx const& tx, XRPAmount potentialSpend); - /// Constructor for an STTx that consumes more than the usual sequences. + /** + * Constructor for an STTx that consumes more than the usual sequences. + */ TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed); - /// Copy constructor + /** + * Copy constructor + */ TxConsequences(TxConsequences const&) = default; - /// Copy assignment operator + /** + * Copy assignment operator + */ TxConsequences& operator=(TxConsequences const&) = default; - /// Move constructor + /** + * Move constructor + */ TxConsequences(TxConsequences&&) = default; - /// Move assignment operator + /** + * Move assignment operator + */ TxConsequences& operator=(TxConsequences&&) = default; - /// Fee + /** + * Fee + */ [[nodiscard]] XRPAmount fee() const { return fee_; } - /// Potential Spend + /** + * Potential Spend + */ [[nodiscard]] XRPAmount const& potentialSpend() const { return potentialSpend_; } - /// SeqProxy + /** + * SeqProxy + */ [[nodiscard]] SeqProxy seqProxy() const { return seqProx_; } - /// Sequences consumed + /** + * Sequences consumed + */ [[nodiscard]] std::uint32_t sequencesConsumed() const { return sequencesConsumed_; } - /// Returns true if the transaction is a blocker. + /** + * Returns true if the transaction is a blocker. + */ [[nodiscard]] bool isBlocker() const { @@ -145,32 +189,49 @@ public: } }; -/** Describes the results of the `preflight` check - - @note All members are const to make it more difficult - to "fake" a result without calling `preflight`. - @see preflight, preclaim, doApply, apply -*/ +/** + * Describes the results of the `preflight` check + * + * @note All members are const to make it more difficult + * to "fake" a result without calling `preflight`. + * @see preflight, preclaim, doApply, apply + */ struct PreflightResult { public: - /// From the input - the transaction + /** + * From the input - the transaction + */ STTx const& tx; - /// From the input - the batch identifier, if part of a batch + /** + * From the input - the batch identifier, if part of a batch + */ std::optional const parentBatchId; - /// From the input - the rules + /** + * From the input - the rules + */ Rules const rules; - /// Consequences of the transaction + /** + * Consequences of the transaction + */ TxConsequences const consequences; - /// From the input - the flags + /** + * From the input - the flags + */ ApplyFlags const flags; - /// From the input - the journal + /** + * From the input - the journal + */ beast::Journal const j; - /// Intermediate transaction result + /** + * Intermediate transaction result + */ NotTEC const ter; - /// Constructor + /** + * Constructor + */ template PreflightResult(Context const& ctx, std::pair const& result) : tx(ctx.tx) @@ -184,39 +245,58 @@ public: } PreflightResult(PreflightResult const&) = default; - /// Deleted copy assignment operator + /** + * Deleted copy assignment operator + */ PreflightResult& operator=(PreflightResult const&) = delete; }; -/** Describes the results of the `preclaim` check - - @note All members are const to make it more difficult - to "fake" a result without calling `preclaim`. - @see preflight, preclaim, doApply, apply -*/ +/** + * Describes the results of the `preclaim` check + * + * @note All members are const to make it more difficult + * to "fake" a result without calling `preclaim`. + * @see preflight, preclaim, doApply, apply + */ struct PreclaimResult { public: - /// From the input - the ledger view + /** + * From the input - the ledger view + */ ReadView const& view; - /// From the input - the transaction + /** + * From the input - the transaction + */ STTx const& tx; - /// From the input - the batch identifier, if part of a batch + /** + * From the input - the batch identifier, if part of a batch + */ std::optional const parentBatchId; - /// From the input - the flags + /** + * From the input - the flags + */ ApplyFlags const flags; - /// From the input - the journal + /** + * From the input - the journal + */ beast::Journal const j; - /// Intermediate transaction result + /** + * Intermediate transaction result + */ TER const ter; - /// Success flag - whether the transaction is likely to - /// claim a fee + /** + * Success flag - whether the transaction is likely to + * claim a fee + */ bool const likelyToClaimFee{}; - /// Constructor + /** + * Constructor + */ template PreclaimResult(Context const& ctx, TER ter) : view(ctx.view) @@ -230,27 +310,30 @@ public: } PreclaimResult(PreclaimResult const&) = default; - /// Deleted copy assignment operator + /** + * Deleted copy assignment operator + */ PreclaimResult& operator=(PreclaimResult const&) = delete; }; -/** Gate a transaction based on static information. - - The transaction is checked against all possible - validity constraints that do not require a ledger. - - @param app The current running `Application`. - @param rules The `Rules` in effect at the time of the check. - @param tx The transaction to be checked. - @param flags `ApplyFlags` describing processing options. - @param j A journal. - - @see PreflightResult, preclaim, doApply, apply - - @return A `PreflightResult` object containing, among - other things, the `TER` code. -*/ +/** + * Gate a transaction based on static information. + * + * The transaction is checked against all possible + * validity constraints that do not require a ledger. + * + * @param app The current running `Application`. + * @param rules The `Rules` in effect at the time of the check. + * @param tx The transaction to be checked. + * @param flags `ApplyFlags` describing processing options. + * @param j A journal. + * + * @see PreflightResult, preclaim, doApply, apply + * + * @return A `PreflightResult` object containing, among + * other things, the `TER` code. + */ /** @{ */ PreflightResult preflight( @@ -270,86 +353,90 @@ preflight( beast::Journal j); /** @} */ -/** Gate a transaction based on static ledger information. - - The transaction is checked against all possible - validity constraints that DO require a ledger. - - If preclaim succeeds, then the transaction is very - likely to claim a fee. This will determine if the - transaction is safe to relay without being applied - to the open ledger. - - "Succeeds" in this case is defined as returning a - `tes` or `tec`, since both lead to claiming a fee. - - @pre The transaction has been checked - and validated using `preflight` - - @param preflightResult The result of a previous - call to `preflight` for the transaction. - @param app The current running `Application`. - @param view The open ledger that the transaction - will attempt to be applied to. - - @see PreclaimResult, preflight, doApply, apply - - @return A `PreclaimResult` object containing, among - other things the `TER` code and the base fee value for - this transaction. -*/ +/** + * Gate a transaction based on static ledger information. + * + * The transaction is checked against all possible + * validity constraints that DO require a ledger. + * + * If preclaim succeeds, then the transaction is very + * likely to claim a fee. This will determine if the + * transaction is safe to relay without being applied + * to the open ledger. + * + * "Succeeds" in this case is defined as returning a + * `tes` or `tec`, since both lead to claiming a fee. + * + * @pre The transaction has been checked + * and validated using `preflight` + * + * @param preflightResult The result of a previous + * call to `preflight` for the transaction. + * @param app The current running `Application`. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * + * @see PreclaimResult, preflight, doApply, apply + * + * @return A `PreclaimResult` object containing, among + * other things the `TER` code and the base fee value for + * this transaction. + */ PreclaimResult preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view); -/** Compute only the expected base fee for a transaction. - - Base fees are transaction specific, so any calculation - needing them must get the base fee for each transaction. - - No validation is done or implied by this function. - - Caller is responsible for handling any exceptions. - Since none should be thrown, that will usually - mean terminating. - - @param view The current open ledger. - @param tx The transaction to be checked. - - @return The base fee. -*/ +/** + * Compute only the expected base fee for a transaction. + * + * Base fees are transaction specific, so any calculation + * needing them must get the base fee for each transaction. + * + * No validation is done or implied by this function. + * + * Caller is responsible for handling any exceptions. + * Since none should be thrown, that will usually + * mean terminating. + * + * @param view The current open ledger. + * @param tx The transaction to be checked. + * + * @return The base fee. + */ XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx); -/** Return the minimum fee that an "ordinary" transaction would pay. - - When computing the FeeLevel for a transaction the TxQ sometimes needs - the know what an "ordinary" or reference transaction would be required - to pay. - - @param view The current open ledger. - @param tx The transaction so the correct multisigner count is used. - - @return The base fee in XRPAmount. -*/ +/** + * Return the minimum fee that an "ordinary" transaction would pay. + * + * When computing the FeeLevel for a transaction the TxQ sometimes needs + * the know what an "ordinary" or reference transaction would be required + * to pay. + * + * @param view The current open ledger. + * @param tx The transaction so the correct multisigner count is used. + * + * @return The base fee in XRPAmount. + */ XRPAmount calculateDefaultBaseFee(ReadView const& view, STTx const& tx); -/** Apply a prechecked transaction to an OpenView. - - @pre The transaction has been checked - and validated using `preflight` and `preclaim` - - @param preclaimResult The result of a previous - call to `preclaim` for the transaction. - @param registry The service registry. - @param view The open ledger that the transaction - will attempt to be applied to. - - @see preflight, preclaim, apply - - @return A pair with the `TER` and a `bool` indicating - whether or not the transaction was applied. -*/ +/** + * Apply a prechecked transaction to an OpenView. + * + * @pre The transaction has been checked + * and validated using `preflight` and `preclaim` + * + * @param preclaimResult The result of a previous + * call to `preclaim` for the transaction. + * @param registry The service registry. + * @param view The open ledger that the transaction + * will attempt to be applied to. + * + * @see preflight, preclaim, apply + * + * @return A pair with the `TER` and a `bool` indicating + * whether or not the transaction was applied. + */ ApplyResult doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view); diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index d19f34635d..1239305e79 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -70,9 +71,18 @@ public: /** * @brief called for each ledger entry in the current transaction. * - * @param isDelete true if the SLE is being deleted - * @param before ledger entry before modification by the transaction - * @param after ledger entry after modification by the transaction + * @param isDelete true if the SLE is being deleted. + * @param before ledger entry before modification by the transaction. `before` will be null if + * the entry is new. + * @param after ledger entry after modification by the transaction. Always non-null. When + * deleting, `after` may differ from `before`. Whether that is important is up to the + * individual invariant check. + * + * @note `after` IS NEVER NULL. `isDelete` is the only correct way to check for deletions. + * Do not make logic or branching decisions on whether on `after` is set, because it will + * always be set. Treat a null `after` as a programming error (with XRPL_ASSERT). An + * invariant MAY check for null defensively, if it makes more sense, but an assertion is + * preferred for new invariants. */ void visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after); @@ -315,17 +325,26 @@ public: }; /** - * @brief Invariant: Token holder's trustline balance cannot be negative after - * Clawback. + * @brief Invariant: Token holder's trustline/MPT balance cannot be invalid + * after Clawback. * * We iterate all the trust lines affected by this transaction and ensure * that no more than one trustline is modified, and also holder's balance is - * non-negative. + * non-negative. When featureMPTokensV2 is enabled, also verify the holder's + * raw trustline/MPToken balance decreased by the clawed amount. */ class ValidClawback { + struct EntryChange + { + SLE::const_pointer before; + SLE::const_pointer after; + }; + std::uint32_t trustlinesChanged_ = 0; std::uint32_t mptokensChanged_ = 0; + EntryChange iou_; + EntryChange mpt_; public: void @@ -375,7 +394,8 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; -/** Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the +/** + * Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the * transaction applies. */ class ValidAmounts @@ -438,10 +458,12 @@ using InvariantChecks = std::tuple< ValidLoan, ValidVault, ValidConfidentialMPToken, - ValidMPTPayment, + ValidMPTBalanceChanges, ValidAmounts, ValidMPTTransfer, - ObjectHasPseudoAccount>; + ObjectHasPseudoAccount, + SponsorshipOwnerCountsMatch, + SponsorshipAccountCountMatchesField>; /** * @brief get a tuple of all invariant checks diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index 1d39fd13d8..5740cd5be2 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -31,16 +31,22 @@ class ValidMPTIssuance // MPToken by an issuer bool mptCreatedByIssuer_ = false; - /// sfReferenceHolding is intended to be set exactly once at vault - /// creation and immutable thereafter; true when that rule was violated. + /** + * sfReferenceHolding is intended to be set exactly once at vault + * creation and immutable thereafter; true when that rule was violated. + */ bool referenceHoldingSetOnCreate_ = false; - /// True when sfReferenceHolding was mutated on an existing MPTokenIssuance. + /** + * True when sfReferenceHolding was mutated on an existing MPTokenIssuance. + */ bool referenceHoldingMutated_ = false; - /// MPTokens and RippleStates deleted during apply. finalize() checks each - /// holder's AccountRoot to detect vault pseudo-account holdings deleted - /// outside VaultDelete. All these checks are gated on fixCleanup3_2_0. + /** + * MPTokens and RippleStates deleted during apply. finalize() checks each + * holder's AccountRoot to detect vault pseudo-account holdings deleted + * outside VaultDelete. All these checks are gated on fixCleanup3_2_0. + */ std::vector> deletedHoldings_; public: @@ -81,7 +87,7 @@ public: * OutstandingAmount after application equals OutstandingAmount before * application plus the net holder balance delta. */ -class ValidMPTPayment +class ValidMPTBalanceChanges { enum class Order { Before = 0, After = 1 }; struct MPTData diff --git a/include/xrpl/tx/invariants/SponsorshipInvariant.h b/include/xrpl/tx/invariants/SponsorshipInvariant.h new file mode 100644 index 0000000000..e664685c16 --- /dev/null +++ b/include/xrpl/tx/invariants/SponsorshipInvariant.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +/** + * @brief Invariant: Sponsored owner counts are balanced. + * + * The following checks are made for every transaction: + * - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals + * the sum of all per-account deltas of `sfSponsoringOwnerCount`. + * - Account OwnerCount must be greater than or equal to SponsoredOwnerCount. + * - The net delta of sponsored object owner counts (the owner-count + * magnitude of sponsored ledger entries) equals the net delta of + * `sfSponsoredOwnerCount`. + */ +class SponsorshipOwnerCountsMatch +{ + std::int64_t deltaSponsoredOwnerCount_ = 0; + std::int64_t deltaSponsoringOwnerCount_ = 0; + std::int64_t deltaSponsoredObjectOwnerCount_ = 0; + std::uint64_t ownerCountBelowSponsored_ = 0; + +public: + void + visitEntry(bool, SLE::const_ref, SLE::const_ref); + + [[nodiscard]] bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; +}; + +/** + * @brief Invariant: Sponsoring account relationships tracked consistently. + * + * The following check is made for every transaction: + * - The net delta of `sfSponsoringAccountCount` across all accounts equals + * the net delta of the count of ltACCOUNT_ROOT entries having + * `sfSponsor` present (presence transitions only: add/remove). + */ +class SponsorshipAccountCountMatchesField +{ + std::int64_t deltaSponsoringAccountCount_ = 0; + std::int64_t deltaSponsorFieldPresence_ = 0; + +public: + void + visitEntry(bool, SLE::const_ref, SLE::const_ref); + + [[nodiscard]] bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h index bc8246b234..136c6c4a25 100644 --- a/include/xrpl/tx/invariants/VaultInvariant.h +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -21,7 +21,7 @@ namespace xrpl { -/* +/** * @brief Invariants: Vault object and MPTokenIssuance for vault shares * * - vault deleted and vault created is empty @@ -96,7 +96,7 @@ private: * * @param vaultDelta Delta of the vault's asset balance for this transaction. * @param rules Active ledger rules (used to check the amendment). - * @returns The minimum scale to apply when rounding vault-related amounts. + * @return The minimum scale to apply when rounding vault-related amounts. */ [[nodiscard]] std::int32_t computeVaultMinScale(DeltaInfo const& vaultDelta, Rules const& rules) const; @@ -109,7 +109,7 @@ private: * to the vault asset held by @p id. * * @param id Account whose asset delta is requested. - * @returns The delta, or @c std::nullopt if the entry was not touched. + * @return The delta, or @c std::nullopt if the entry was not touched. */ [[nodiscard]] std::optional deltaAssets(AccountID const& id) const; @@ -124,8 +124,8 @@ private: * * @param tx The transaction being applied. * @param fee Fee charged by this transaction. - * @returns The fee-adjusted delta, or @c std::nullopt if the net delta is - * zero or the account entry was not touched. + * @return The fee-adjusted delta, or @c std::nullopt if the net delta is + * zero or the account entry was not touched. */ [[nodiscard]] std::optional deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const; @@ -138,7 +138,7 @@ private: * returned. * * @param id Account whose share delta is requested. - * @returns The delta, or @c std::nullopt if the entry was not touched. + * @return The delta, or @c std::nullopt if the entry was not touched. */ [[nodiscard]] std::optional deltaShares(AccountID const& id) const; @@ -147,8 +147,8 @@ private: * @brief Check whether a vault holds no assets. * * @param vault Snapshot of the vault to test. - * @returns @c true when both @c assetsAvailable and @c assetsTotal are - * zero. + * @return @c true when both @c assetsAvailable and @c assetsTotal are + * zero. */ [[nodiscard]] static bool isVaultEmpty(Vault const& vault); diff --git a/include/xrpl/tx/paths/AMMLiquidity.h b/include/xrpl/tx/paths/AMMLiquidity.h index bf62155b61..1904445554 100644 --- a/include/xrpl/tx/paths/AMMLiquidity.h +++ b/include/xrpl/tx/paths/AMMLiquidity.h @@ -17,7 +17,8 @@ namespace xrpl { template class AMMOffer; -/** AMMLiquidity class provides AMM offers to BookStep class. +/** + * AMMLiquidity class provides AMM offers to BookStep class. * The offers are generated in two ways. If there are multiple * paths specified to the payment transaction then the offers * are generated based on the Fibonacci sequence with @@ -59,7 +60,8 @@ public: AMMLiquidity& operator=(AMMLiquidity const&) = delete; - /** Generate AMM offer. Returns nullopt if clobQuality is provided + /** + * Generate AMM offer. Returns nullopt if clobQuality is provided * and it is better than AMM offer quality. Otherwise returns AMM offer. * If clobQuality is provided then AMM offer size is set based on the * quality. @@ -104,12 +106,14 @@ public: } private: - /** Fetches current AMM balances. + /** + * Fetches current AMM balances. */ [[nodiscard]] TAmounts fetchBalances(ReadView const& view) const; - /** Generate AMM offers with the offer size based on Fibonacci sequence. + /** + * Generate AMM offers with the offer size based on Fibonacci sequence. * The sequence corresponds to the payment engine iterations with AMM * liquidity. Iterations that don't consume AMM offers don't count. * The number of iterations with AMM offers is limited. @@ -119,7 +123,8 @@ private: [[nodiscard]] TAmounts generateFibSeqOffer(TAmounts const& balances) const; - /** Generate max offer. + /** + * Generate max offer. * If `fixAMMOverflowOffer` is active, the offer is generated as: * takerGets = 99% * balances.out takerPays = swapOut(takerGets). * Return nullopt if takerGets is 0 or takerGets == balances.out. diff --git a/include/xrpl/tx/paths/AMMOffer.h b/include/xrpl/tx/paths/AMMOffer.h index 40a9cf40b3..8e7ffedc10 100644 --- a/include/xrpl/tx/paths/AMMOffer.h +++ b/include/xrpl/tx/paths/AMMOffer.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -19,7 +20,8 @@ template class AMMLiquidity; class QualityFunction; -/** Represents synthetic AMM offer in BookStep. AMMOffer mirrors TOffer +/** + * Represents synthetic AMM offer in BookStep. AMMOffer mirrors TOffer * methods for use in generic BookStep methods. AMMOffer amounts * are changed indirectly in BookStep limiting steps. */ @@ -86,14 +88,16 @@ public: return consumed_; } - /** Limit out of the provided offer. If one-path then swapOut + /** + * Limit out of the provided offer. If one-path then swapOut * using current balances. If multi-path then ceil_out using * current quality. */ [[nodiscard]] TAmounts limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; - /** Limit in of the provided offer. If one-path then swapIn + /** + * Limit in of the provided offer. If one-path then swapIn * using current balances. If multi-path then ceil_in using * current quality. */ @@ -103,14 +107,18 @@ public: [[nodiscard]] QualityFunction getQualityFunc() const; - /** Send funds without incurring the transfer fee + /** + * Send funds without incurring the transfer fee */ template static TER send(Args&&... args) { return accountSend( - std::forward(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes); + std::forward(args)..., + SLE::pointer(), + WaiveTransferFee::Yes, + AllowMPTOverflow::Yes); } [[nodiscard]] bool @@ -127,7 +135,8 @@ public: return {ofrInRate, QUALITY_ONE}; } - /** Check the new pool product is greater or equal to the old pool + /** + * Check the new pool product is greater or equal to the old pool * product or if decreases then within some threshold. */ [[nodiscard]] bool diff --git a/include/xrpl/tx/paths/BookTip.h b/include/xrpl/tx/paths/BookTip.h index bad007ca5b..0a7e3c343e 100644 --- a/include/xrpl/tx/paths/BookTip.h +++ b/include/xrpl/tx/paths/BookTip.h @@ -11,10 +11,11 @@ namespace xrpl { class Logs; -/** Iterates and consumes raw offers in an order book. - Offers are presented from highest quality to lowest quality. This will - return all offers present including missing, invalid, unfunded, etc. -*/ +/** + * Iterates and consumes raw offers in an order book. + * Offers are presented from highest quality to lowest quality. This will + * return all offers present including missing, invalid, unfunded, etc. + */ class BookTip { private: @@ -28,7 +29,9 @@ private: Quality quality_{}; public: - /** Create the iterator. */ + /** + * Create the iterator. + */ BookTip(ApplyView& view, Book const& book); [[nodiscard]] uint256 const& @@ -55,10 +58,11 @@ public: return entry_; } - /** Erases the current offer and advance to the next offer. - Complexity: Constant - @return `true` if there is a next offer - */ + /** + * Erases the current offer and advance to the next offer. + * Complexity: Constant + * @return `true` if there is a next offer + */ bool step(beast::Journal j); }; diff --git a/include/xrpl/tx/paths/Flow.h b/include/xrpl/tx/paths/Flow.h index f73b9a3440..af056ce2fe 100644 --- a/include/xrpl/tx/paths/Flow.h +++ b/include/xrpl/tx/paths/Flow.h @@ -18,25 +18,25 @@ struct FlowDebugInfo; } // namespace path::detail /** - Make a payment from the src account to the dst account - - @param view Trust lines and balances - @param deliver Amount to deliver to the dst account - @param src Account providing input funds for the payment - @param dst Account receiving the payment - @param paths Set of paths to explore for liquidity - @param defaultPaths Include defaultPaths in the path set - @param partialPayment If the payment cannot deliver the entire - requested amount, deliver as much as possible, given the constraints - @param ownerPaysTransferFee If true then owner, not sender, pays fee - @param offerCrossing If Yes or Sell then flow is executing offer crossing, not - payments - @param limitQuality Do not use liquidity below this quality threshold - @param sendMax Do not spend more than this amount - @param j Journal to write journal messages to - @param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging - @return Actual amount in and out, and the result code -*/ + * Make a payment from the src account to the dst account + * + * @param view Trust lines and balances + * @param deliver Amount to deliver to the dst account + * @param src Account providing input funds for the payment + * @param dst Account receiving the payment + * @param paths Set of paths to explore for liquidity + * @param defaultPaths Include defaultPaths in the path set + * @param partialPayment If the payment cannot deliver the entire + * requested amount, deliver as much as possible, given the constraints + * @param ownerPaysTransferFee If true then owner, not sender, pays fee + * @param offerCrossing If Yes or Sell then flow is executing offer crossing, not + * payments + * @param limitQuality Do not use liquidity below this quality threshold + * @param sendMax Do not spend more than this amount + * @param j Journal to write journal messages to + * @param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging + * @return Actual amount in and out, and the result code + */ path::RippleCalc::Output flow( PaymentSandbox& view, diff --git a/include/xrpl/tx/paths/Offer.h b/include/xrpl/tx/paths/Offer.h index 164a933ba1..7f368fc2dd 100644 --- a/include/xrpl/tx/paths/Offer.h +++ b/include/xrpl/tx/paths/Offer.h @@ -44,38 +44,44 @@ public: TOffer(SLE::pointer entry, Quality quality); - /** Returns the quality of the offer. - Conceptually, the quality is the ratio of output to input currency. - The implementation calculates it as the ratio of input to output - currency (so it sorts ascending). The quality is computed at the time - the offer is placed, and never changes for the lifetime of the offer. - This is an important business rule that maintains accuracy when an - offer is partially filled; Subsequent partial fills will use the - original quality. - */ + /** + * Returns the quality of the offer. + * Conceptually, the quality is the ratio of output to input currency. + * The implementation calculates it as the ratio of input to output + * currency (so it sorts ascending). The quality is computed at the time + * the offer is placed, and never changes for the lifetime of the offer. + * This is an important business rule that maintains accuracy when an + * offer is partially filled; Subsequent partial fills will use the + * original quality. + */ [[nodiscard]] Quality quality() const noexcept { return quality_; } - /** Returns the account id of the offer's owner. */ + /** + * Returns the account id of the offer's owner. + */ [[nodiscard]] AccountID const& owner() const { return accountID_; } - /** Returns the in and out amounts. - Some or all of the out amount may be unfunded. - */ + /** + * Returns the in and out amounts. + * Some or all of the out amount may be unfunded. + */ [[nodiscard]] TAmounts const& amount() const { return amounts_; } - /** Returns `true` if no more funds can flow through this offer. */ + /** + * Returns `true` if no more funds can flow through this offer. + */ [[nodiscard]] bool fullyConsumed() const { @@ -86,7 +92,9 @@ public: return false; } - /** Adjusts the offer to indicate that we consumed some (or all) of it. */ + /** + * Adjusts the offer to indicate that we consumed some (or all) of it. + */ void consume(ApplyView& view, TAmounts const& consumed) { @@ -142,7 +150,8 @@ public: return {ofrInRate, ofrOutRate}; } - /** Check any required invariant. Limit order book offer + /** + * Check any required invariant. Limit order book offer * always returns true. */ [[nodiscard]] bool @@ -234,7 +243,8 @@ template TER TOffer::send(Args&&... args) { - return accountSend(std::forward(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes); + return accountSend( + std::forward(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes); } template diff --git a/include/xrpl/tx/paths/OfferStream.h b/include/xrpl/tx/paths/OfferStream.h index 8ecd495d1a..28eefb5d66 100644 --- a/include/xrpl/tx/paths/OfferStream.h +++ b/include/xrpl/tx/paths/OfferStream.h @@ -85,10 +85,11 @@ public: virtual ~TOfferStreamBase() = default; - /** Returns the offer at the tip of the order book. - Offers are always presented in decreasing quality. - Only valid if step() returned `true`. - */ + /** + * Returns the offer at the tip of the order book. + * Offers are always presented in decreasing quality. + * Only valid if step() returned `true`. + */ [[nodiscard]] TOffer& tip() const { @@ -96,13 +97,14 @@ public: return const_cast(this)->offer_; } - /** Advance to the next valid offer. - This automatically removes: - - Offers with missing ledger entries - - Offers found unfunded - - expired offers - @return `true` if there is a valid offer. - */ + /** + * Advance to the next valid offer. + * This automatically removes: + * - Offers with missing ledger entries + * - Offers found unfunded + * - expired offers + * @return `true` if there is a valid offer. + */ bool step(); @@ -114,23 +116,24 @@ public: } }; -/** Presents and consumes the offers in an order book. - - The `view_' ` `ApplyView` accumulates changes to the ledger. - The `cancelView_` is used to determine if an offer is found - unfunded or became unfunded. - The `permToRemove` collection identifies offers that should be - removed even if the strand associated with this OfferStream - is not applied. - - Certain invalid offers are added to the `permToRemove` collection: - - Offers with missing ledger entries - - Offers that expired - - Offers found unfunded: - An offer is found unfunded when the corresponding balance is zero - and the caller has not modified the balance. This is accomplished - by also looking up the balance in the cancel view. -*/ +/** + * Presents and consumes the offers in an order book. + * + * The `view_' ` `ApplyView` accumulates changes to the ledger. + * The `cancelView_` is used to determine if an offer is found + * unfunded or became unfunded. + * The `permToRemove` collection identifies offers that should be + * removed even if the strand associated with this OfferStream + * is not applied. + * + * Certain invalid offers are added to the `permToRemove` collection: + * - Offers with missing ledger entries + * - Offers that expired + * - Offers found unfunded: + * An offer is found unfunded when the corresponding balance is zero + * and the caller has not modified the balance. This is accomplished + * by also looking up the balance in the cancel view. + */ template class FlowOfferStream : public TOfferStreamBase { diff --git a/include/xrpl/tx/paths/RippleCalc.h b/include/xrpl/tx/paths/RippleCalc.h index 62c966d384..c3ee9165a6 100644 --- a/include/xrpl/tx/paths/RippleCalc.h +++ b/include/xrpl/tx/paths/RippleCalc.h @@ -20,11 +20,12 @@ namespace detail { struct FlowDebugInfo; } // namespace detail -/** RippleCalc calculates the quality of a payment path. - - Quality is the amount of input required to produce a given output along a - specified path - another name for this is exchange rate. -*/ +/** + * RippleCalc calculates the quality of a payment path. + * + * Quality is the amount of input required to produce a given output along a + * specified path - another name for this is exchange rate. + */ class RippleCalc { public: diff --git a/include/xrpl/tx/paths/detail/FlatSets.h b/include/xrpl/tx/paths/detail/FlatSets.h index c0fc8fa417..267c0499a2 100644 --- a/include/xrpl/tx/paths/detail/FlatSets.h +++ b/include/xrpl/tx/paths/detail/FlatSets.h @@ -4,11 +4,12 @@ namespace xrpl { -/** Given two flat sets dst and src, compute dst = dst union src - - @param dst set to store the resulting union, and also a source of elements - for the union - @param src second source of elements for the union +/** + * Given two flat sets dst and src, compute dst = dst union src + * + * @param dst set to store the resulting union, and also a source of elements + * for the union + * @param src second source of elements for the union */ template void diff --git a/include/xrpl/tx/paths/detail/Steps.h b/include/xrpl/tx/paths/detail/Steps.h index 7eb6b938a5..8ee37c026c 100644 --- a/include/xrpl/tx/paths/detail/Steps.h +++ b/include/xrpl/tx/paths/detail/Steps.h @@ -50,31 +50,31 @@ issues(DebtDirection dir) } /** - A step in a payment path - - There are five concrete step classes: - DirectStepI is an IOU step between accounts - BookStepII is an IOU/IOU offer book - BookStepIX is an IOU/XRP offer book - BookStepXI is an XRP/IOU offer book - XRPEndpointStep is the source or destination account for XRP - MPTEndpointStep is the source or destination account for MPT - - Amounts may be transformed through a step in either the forward or the - reverse direction. In the forward direction, the function `fwd` is used to - find the amount the step would output given an input amount. In the reverse - direction, the function `rev` is used to find the amount of input needed to - produce the desired output. - - Amounts are always transformed using liquidity with the same quality (quality - is the amount out/amount in). For example, a BookStep may use multiple offers - when executing `fwd` or `rev`, but all those offers will be from the same - quality directory. - - A step may not have enough liquidity to transform the entire requested - amount. Both `fwd` and `rev` return a pair of amounts (one for input amount, - one for output amount) that show how much of the requested amount the step - was actually able to use. + * A step in a payment path + * + * There are five concrete step classes: + * DirectStepI is an IOU step between accounts + * BookStepII is an IOU/IOU offer book + * BookStepIX is an IOU/XRP offer book + * BookStepXI is an XRP/IOU offer book + * XRPEndpointStep is the source or destination account for XRP + * MPTEndpointStep is the source or destination account for MPT + * + * Amounts may be transformed through a step in either the forward or the + * reverse direction. In the forward direction, the function `fwd` is used to + * find the amount the step would output given an input amount. In the reverse + * direction, the function `rev` is used to find the amount of input needed to + * produce the desired output. + * + * Amounts are always transformed using liquidity with the same quality (quality + * is the amount out/amount in). For example, a BookStep may use multiple offers + * when executing `fwd` or `rev`, but all those offers will be from the same + * quality directory. + * + * A step may not have enough liquidity to transform the entire requested + * amount. Both `fwd` and `rev` return a pair of amounts (one for input amount, + * one for output amount) that show how much of the requested amount the step + * was actually able to use. */ class Step { @@ -82,17 +82,17 @@ public: virtual ~Step() = default; /** - Find the amount we need to put into the step to get the requested out - subject to liquidity limits - - @param sb view with the strand's state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param ofrsToRm offers found unfunded or in an error state are added to - this collection - @param out requested step output - @return actual step input and output - */ + * Find the amount we need to put into the step to get the requested out + * subject to liquidity limits + * + * @param sb view with the strand's state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param ofrsToRm offers found unfunded or in an error state are added to + * this collection + * @param out requested step output + * @return actual step input and output + */ virtual std::pair rev(PaymentSandbox& sb, ApplyView& afView, @@ -100,17 +100,17 @@ public: EitherAmount const& out) = 0; /** - Find the amount we get out of the step given the input - subject to liquidity limits - - @param sb view with the strand's state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param ofrsToRm offers found unfunded or in an error state are added to - this collection - @param in requested step input - @return actual step input and output - */ + * Find the amount we get out of the step given the input + * subject to liquidity limits + * + * @param sb view with the strand's state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param ofrsToRm offers found unfunded or in an error state are added to + * this collection + * @param in requested step input + * @return actual step input and output + */ virtual std::pair fwd(PaymentSandbox& sb, ApplyView& afView, @@ -118,23 +118,23 @@ public: EitherAmount const& in) = 0; /** - Amount of currency computed coming into the Step the last time the - step ran in reverse. - */ + * Amount of currency computed coming into the Step the last time the + * step ran in reverse. + */ [[nodiscard]] virtual std::optional cachedIn() const = 0; /** - Amount of currency computed coming out of the Step the last time the - step ran in reverse. - */ + * Amount of currency computed coming out of the Step the last time the + * step ran in reverse. + */ [[nodiscard]] virtual std::optional cachedOut() const = 0; /** - If this step is DirectStepI (IOU->IOU direct step), return the src - account. This is needed for checkNoRipple. - */ + * If this step is DirectStepI (IOU->IOU direct step), return the src + * account. This is needed for checkNoRipple. + */ [[nodiscard]] virtual std::optional directStepSrcAcct() const { @@ -150,19 +150,19 @@ public: } /** - If this step is a DirectStepI and the src redeems to the dst, return - true, otherwise return false. If this step is a BookStep, return false if - the owner pays the transfer fee, otherwise return true. - - @param sb view with the strand's state of balances and offers - @param dir reverse -> called from rev(); forward -> called from fwd(). - */ + * If this step is a DirectStepI and the src redeems to the dst, return + * true, otherwise return false. If this step is a BookStep, return false if + * the owner pays the transfer fee, otherwise return true. + * + * @param sb view with the strand's state of balances and offers + * @param dir reverse -> called from rev(); forward -> called from fwd(). + */ [[nodiscard]] virtual DebtDirection debtDirection(ReadView const& sb, StrandDirection dir) const = 0; /** - If this step is a DirectStepI, return the quality in of the dst account. - */ + * If this step is a DirectStepI, return the quality in of the dst account. + */ [[nodiscard]] virtual std::uint32_t lineQualityIn(ReadView const&) const { @@ -170,22 +170,23 @@ public: } /** - Find an upper bound of quality for the step - - @param v view to query the ledger state from - @param prevStepDir Set to DebtDirection::redeems if the previous step redeems. - @return A pair. The first element is the upper bound of quality for the step, or std::nullopt - if the step is dry. The second element will be set to DebtDirection::redeems if this - steps redeems, DebtDirection:issues if this step issues. - @note It is an upper bound because offers on the books may be unfunded. If there is always a - funded offer at the tip of the book, then we could rename this `theoreticalQuality` - rather than `qualityUpperBound`. It could still differ from the actual quality, but - except for "dust" amounts, it should be a good estimate for the actual quality. - */ + * Find an upper bound of quality for the step + * + * @param v view to query the ledger state from + * @param prevStepDir Set to DebtDirection::redeems if the previous step redeems. + * @return A pair. The first element is the upper bound of quality for the step, or std::nullopt + * if the step is dry. The second element will be set to DebtDirection::redeems if this + * steps redeems, DebtDirection:issues if this step issues. + * @note It is an upper bound because offers on the books may be unfunded. If there is always a + * funded offer at the tip of the book, then we could rename this `theoreticalQuality` + * rather than `qualityUpperBound`. It could still differ from the actual quality, but + * except for "dust" amounts, it should be a good estimate for the actual quality. + */ [[nodiscard]] virtual std::pair, DebtDirection> qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const = 0; - /** Get QualityFunction. Used in one path optimization where + /** + * Get QualityFunction. Used in one path optimization where * the quality function is non-constant (has AMM) and there is * limitQuality. QualityFunction allows calculation of * required path output given requested limitQuality. @@ -195,12 +196,13 @@ public: [[nodiscard]] virtual std::pair, DebtDirection> getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const; - /** Return the number of offers consumed or partially consumed the last time - the step ran, including expired and unfunded offers. - - N.B. This this not the total number offers consumed by this step for the - entire payment, it is only the number the last time it ran. Offers may - be partially consumed multiple times during a payment. + /** + * Return the number of offers consumed or partially consumed the last time + * the step ran, including expired and unfunded offers. + * + * N.B. This this not the total number offers consumed by this step for the + * entire payment, it is only the number the last time it ran. Offers may + * be partially consumed multiple times during a payment. */ [[nodiscard]] virtual std::uint32_t offersUsed() const @@ -209,8 +211,8 @@ public: } /** - If this step is a BookStep, return the book. - */ + * If this step is a BookStep, return the book. + */ [[nodiscard]] virtual std::optional bookStepBook() const { @@ -218,15 +220,15 @@ public: } /** - Check if amount is zero - */ + * Check if amount is zero + */ [[nodiscard]] virtual bool isZero(EitherAmount const& out) const = 0; /** - Return true if the step should be considered inactive. - A strand that has additional liquidity may be marked inactive if a step - has consumed too many offers. + * Return true if the step should be considered inactive. + * A strand that has additional liquidity may be marked inactive if a step + * has consumed too many offers. */ [[nodiscard]] virtual bool inactive() const @@ -235,55 +237,59 @@ public: } /** - Return true if Out of lhs == Out of rhs. - */ + * Return true if Out of lhs == Out of rhs. + */ [[nodiscard]] virtual bool equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const = 0; /** - Return true if In of lhs == In of rhs. - */ + * Return true if In of lhs == In of rhs. + */ [[nodiscard]] virtual bool equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const = 0; /** - Check that the step can correctly execute in the forward direction - - @param sb view with the strands state of balances and offers - @param afView view the state of balances before the strand runs - this determines if an offer becomes unfunded or is found unfunded - @param in requested step input - @return first element is true if step is valid, second element is out - amount - */ + * Check that the step can correctly execute in the forward direction + * + * @param sb view with the strands state of balances and offers + * @param afView view the state of balances before the strand runs + * this determines if an offer becomes unfunded or is found unfunded + * @param in requested step input + * @return first element is true if step is valid, second element is out + * amount + */ virtual std::pair validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) = 0; - /** Return true if lhs == rhs. - - @param lhs Step to compare. - @param rhs Step to compare. - @return true if lhs == rhs. - */ + /** + * Return true if lhs == rhs. + * + * @param lhs Step to compare. + * @param rhs Step to compare. + * @return true if lhs == rhs. + */ friend bool operator==(Step const& lhs, Step const& rhs) { return lhs.equal(rhs); } - /** Return true if lhs != rhs. - - @param lhs Step to compare. - @param rhs Step to compare. - @return true if lhs != rhs. - */ + /** + * Return true if lhs != rhs. + * + * @param lhs Step to compare. + * @param rhs Step to compare. + * @return true if lhs != rhs. + */ friend bool operator!=(Step const& lhs, Step const& rhs) { return !(lhs == rhs); } - /** Streaming operator for a Step. */ + /** + * Streaming operator for a Step. + */ friend std::ostream& operator<<(std::ostream& stream, Step const& step) { @@ -308,7 +314,7 @@ Step::getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const return {std::nullopt, res.second}; } -/// @cond INTERNAL +/** @cond INTERNAL */ using Strand = std::vector>; inline std::uint32_t @@ -322,9 +328,9 @@ offersUsed(Strand const& strand) } return r; } -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ inline bool operator==(Strand const& lhs, Strand const& rhs) { @@ -337,21 +343,21 @@ operator==(Strand const& lhs, Strand const& rhs) } return true; } -/// @endcond +/** @endcond */ -/* - Normalize a path by inserting implied accounts and offers - - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param sendMax Optional asset to send. - @param path Liquidity sources to use for this strand of the payment. The path - contains an ordered collection of the offer books to use and - accounts to ripple through. - @return error code and normalized path -*/ +/** + * Normalize a path by inserting implied accounts and offers + * + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param sendMax Optional asset to send. + * @param path Liquidity sources to use for this strand of the payment. The path + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @return error code and normalized path + */ std::pair normalizePath( AccountID const& src, @@ -361,29 +367,29 @@ normalizePath( STPath const& path); /** - Create a Strand for the specified path - - @param sb view for trust lines, balances, and attributes like auth and freeze - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param limitQuality Offer crossing BookSteps use this value in an - optimization. If, during direct offer crossing, the - quality of the tip of the book drops below this value, - then evaluating the strand can stop. - @param sendMaxAsset Optional asset to send. - @param path Liquidity sources to use for this strand of the payment. The path - contains an ordered collection of the offer books to use and - accounts to ripple through. - @param ownerPaysTransferFee false -> charge sender; true -> charge offer - owner - @param offerCrossing false -> payment; true -> offer crossing - @param ammContext counts iterations with AMM offers - @param domainID the domain that order books will use - @param j Journal for logging messages - @return Error code and constructed Strand -*/ + * Create a Strand for the specified path + * + * @param sb view for trust lines, balances, and attributes like auth and freeze + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param limitQuality Offer crossing BookSteps use this value in an + * optimization. If, during direct offer crossing, the + * quality of the tip of the book drops below this value, + * then evaluating the strand can stop. + * @param sendMaxAsset Optional asset to send. + * @param path Liquidity sources to use for this strand of the payment. The path + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @param ownerPaysTransferFee false -> charge sender; true -> charge offer + * owner + * @param offerCrossing false -> payment; true -> offer crossing + * @param ammContext counts iterations with AMM offers + * @param domainID the domain that order books will use + * @param j Journal for logging messages + * @return Error code and constructed Strand + */ std::pair toStrand( ReadView const& sb, @@ -400,31 +406,31 @@ toStrand( beast::Journal j); /** - Create a Strand for each specified path (including the default path, if - indicated) - - @param sb View for trust lines, balances, and attributes like auth and freeze - @param src Account that is sending assets - @param dst Account that is receiving assets - @param deliver Asset the dst account will receive - (if issuer of deliver == dst, then accept any issuer) - @param limitQuality Offer crossing BookSteps use this value in an - optimization. If, during direct offer crossing, the - quality of the tip of the book drops below this value, - then evaluating the strand can stop. - @param sendMax Optional asset to send. - @param paths Paths to use to fulfill the payment. Each path in the pathset - contains an ordered collection of the offer books to use and - accounts to ripple through. - @param addDefaultPath Determines if the default path should be included - @param ownerPaysTransferFee false -> charge sender; true -> charge offer - owner - @param offerCrossing false -> payment; true -> offer crossing - @param ammContext counts iterations with AMM offers - @param domainID the domain that order books will use - @param j Journal for logging messages - @return error code and collection of strands -*/ + * Create a Strand for each specified path (including the default path, if + * indicated) + * + * @param sb View for trust lines, balances, and attributes like auth and freeze + * @param src Account that is sending assets + * @param dst Account that is receiving assets + * @param deliver Asset the dst account will receive + * (if issuer of deliver == dst, then accept any issuer) + * @param limitQuality Offer crossing BookSteps use this value in an + * optimization. If, during direct offer crossing, the + * quality of the tip of the book drops below this value, + * then evaluating the strand can stop. + * @param sendMax Optional asset to send. + * @param paths Paths to use to fulfill the payment. Each path in the pathset + * contains an ordered collection of the offer books to use and + * accounts to ripple through. + * @param addDefaultPath Determines if the default path should be included + * @param ownerPaysTransferFee false -> charge sender; true -> charge offer + * owner + * @param offerCrossing false -> payment; true -> offer crossing + * @param ammContext counts iterations with AMM offers + * @param domainID the domain that order books will use + * @param j Journal for logging messages + * @return error code and collection of strands + */ std::pair> toStrands( ReadView const& sb, @@ -441,7 +447,7 @@ toStrands( std::optional const& domainID, beast::Journal j); -/// @cond INTERNAL +/** @cond INTERNAL */ template struct StepImp : public Step { @@ -490,9 +496,9 @@ public: } friend TDerived; }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ // Thrown when unexpected errors occur class FlowException : public std::runtime_error { @@ -507,9 +513,9 @@ public: { } }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ // Check equal with tolerance bool checkNear(IOUAmount const& expected, IOUAmount const& actual); @@ -523,10 +529,10 @@ checkNear(XRPAmount const& expected, XRPAmount const& actual) { return expected == actual; } -/// @endcond +/** @endcond */ /** - Context needed to build Strand Steps and for error checking + * Context needed to build Strand Steps and for error checking */ struct StrandContext { @@ -541,25 +547,30 @@ struct StrandContext OfferCrossing const offerCrossing; ///< Yes/Sell if offer crossing, not payment bool const isDefaultPath; ///< true if Strand is default path size_t const strandSize; ///< Length of Strand - /** The previous step in the strand. Needed to check the no ripple - constraint + /** + * The previous step in the strand. Needed to check the no ripple + * constraint */ Step const* const prevStep = nullptr; - /** A strand may not include the same account node more than once - in the same currency. In a direct step, an account will show up - at most twice: once as a src and once as a dst (hence the two element - array). The strandSrc and strandDst will only show up once each. - */ + /** + * A strand may not include the same account node more than once + * in the same currency. In a direct step, an account will show up + * at most twice: once as a src and once as a dst (hence the two element + * array). The strandSrc and strandDst will only show up once each. + */ std::array, 2>& seenDirectAssets; - /** A strand may not include an offer that output the same issue more - than once - */ + /** + * A strand may not include an offer that output the same issue more + * than once + */ boost::container::flat_set& seenBookOuts; AMMContext& ammContext; std::optional domainID; // the domain the order book will use beast::Journal const j; - /** StrandContext constructor. */ + /** + * StrandContext constructor. + */ StrandContext( ReadView const& view, std::vector> const& strand, @@ -581,7 +592,7 @@ struct StrandContext beast::Journal j); ///< Journal for logging }; -/// @cond INTERNAL +/** @cond INTERNAL */ namespace test { // Needed for testing bool @@ -659,6 +670,6 @@ isDirectXrpToXrp(Strand const& strand) return false; } } -/// @endcond +/** @endcond */ } // namespace xrpl diff --git a/include/xrpl/tx/paths/detail/StrandFlow.h b/include/xrpl/tx/paths/detail/StrandFlow.h index fe657b2100..c932c49cca 100644 --- a/include/xrpl/tx/paths/detail/StrandFlow.h +++ b/include/xrpl/tx/paths/detail/StrandFlow.h @@ -39,7 +39,9 @@ namespace xrpl { -/** Result of flow() execution of a single Strand. */ +/** + * Result of flow() execution of a single Strand. + */ template struct StrandResult { @@ -56,7 +58,9 @@ struct StrandResult bool inactive = false; ///< Strand should not considered as a further ///< source of liquidity (dry) - /** Strand result constructor */ + /** + * Strand result constructor + */ StrandResult() = default; StrandResult( @@ -83,15 +87,15 @@ struct StrandResult }; /** - Request `out` amount from a strand - - @param baseView Trust lines and balances - @param strand Steps of Accounts to ripple through and offer books to use - @param maxIn Max amount of input allowed - @param out Amount of output requested from the strand - @param j Journal to write log messages to - @return Actual amount in and out from the strand, errors, offers to remove, - and payment sandbox + * Request `out` amount from a strand + * + * @param baseView Trust lines and balances + * @param strand Steps of Accounts to ripple through and offer books to use + * @param maxIn Max amount of input allowed + * @param out Amount of output requested from the strand + * @param j Journal to write log messages to + * @return Actual amount in and out from the strand, errors, offers to remove, + * and payment sandbox */ template StrandResult @@ -296,7 +300,7 @@ flow( } } -/// @cond INTERNAL +/** @cond INTERNAL */ template struct FlowResult { @@ -335,9 +339,9 @@ struct FlowResult { } }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ inline std::optional qualityUpperBound(ReadView const& v, Strand const& strand) { @@ -357,10 +361,11 @@ qualityUpperBound(ReadView const& v, Strand const& strand) } return q; }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL -/** Limit remaining out only if one strand and limitQuality is included. +/** @cond INTERNAL */ +/** + * Limit remaining out only if one strand and limitQuality is included. * Targets one path payment with AMM where the average quality is linear * and instant quality is quadratic function of output. Calculating quality * function for the whole strand enables figuring out required output @@ -428,9 +433,9 @@ limitOut( return remainingOut; return std::min(out, remainingOut); }; -/// @endcond +/** @endcond */ -/// @cond INTERNAL +/** @cond INTERNAL */ /* Track the non-dry strands flow will search the non-dry strands (stored in `cur_`) for the best @@ -545,28 +550,28 @@ public: return cur_.size(); } }; -/// @endcond +/** @endcond */ /** - Request `out` amount from a collection of strands - - Attempt to fulfill the payment by using liquidity from the strands in order - from least expensive to most expensive - - @param baseView Trust lines and balances - @param strands Each strand contains the steps of accounts to ripple through - and offer books to use - @param outReq Amount of output requested from the strand - @param partialPayment If true allow less than the full payment - @param offerCrossing If true offer crossing, not handling a standard payment - @param limitQuality If present, the minimum quality for any strand taken - @param sendMaxST If present, the maximum STAmount to send - @param j Journal to write journal messages to - @param ammContext counts iterations with AMM offers - @param flowDebugInfo If pointer is non-null, write flow debug info here - @return Actual amount in and out from the strands, errors, and payment - sandbox -*/ + * Request `out` amount from a collection of strands + * + * Attempt to fulfill the payment by using liquidity from the strands in order + * from least expensive to most expensive + * + * @param baseView Trust lines and balances + * @param strands Each strand contains the steps of accounts to ripple through + * and offer books to use + * @param outReq Amount of output requested from the strand + * @param partialPayment If true allow less than the full payment + * @param offerCrossing If true offer crossing, not handling a standard payment + * @param limitQuality If present, the minimum quality for any strand taken + * @param sendMaxST If present, the maximum STAmount to send + * @param j Journal to write journal messages to + * @param ammContext counts iterations with AMM offers + * @param flowDebugInfo If pointer is non-null, write flow debug info here + * @return Actual amount in and out from the strands, errors, and payment + * sandbox + */ template FlowResult flow( diff --git a/include/xrpl/tx/transactors/account/SignerListSet.h b/include/xrpl/tx/transactors/account/SignerListSet.h index da2274a1de..9f7872a18e 100644 --- a/include/xrpl/tx/transactors/account/SignerListSet.h +++ b/include/xrpl/tx/transactors/account/SignerListSet.h @@ -20,9 +20,9 @@ namespace xrpl { /** -See the README.md for an overview of the SignerListSet transaction that -this class implements. -*/ + * See the README.md for an overview of the SignerListSet transaction that + * this class implements. + */ class SignerListSet : public Transactor { private: diff --git a/include/xrpl/tx/transactors/dex/AMMBid.h b/include/xrpl/tx/transactors/dex/AMMBid.h index fa257696e8..9d8eb8578b 100644 --- a/include/xrpl/tx/transactors/dex/AMMBid.h +++ b/include/xrpl/tx/transactors/dex/AMMBid.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMBid implements AMM bid Transactor. +/** + * AMMBid implements AMM bid Transactor. * This is a mechanism for an AMM instance to auction-off * the trading advantages to users (arbitrageurs) at a discounted * TradingFee for a 24 hour slot. Any account that owns corresponding diff --git a/include/xrpl/tx/transactors/dex/AMMClawback.h b/include/xrpl/tx/transactors/dex/AMMClawback.h index 54eb3c9f27..3f3f2c421a 100644 --- a/include/xrpl/tx/transactors/dex/AMMClawback.h +++ b/include/xrpl/tx/transactors/dex/AMMClawback.h @@ -56,7 +56,8 @@ private: TER applyGuts(Sandbox& view); - /** Withdraw both assets by providing maximum amount of asset1, + /** + * Withdraw both assets by providing maximum amount of asset1, * asset2's amount will be calculated according to the current proportion. * Since it is two-asset withdrawal, tfee is omitted. * @param view diff --git a/include/xrpl/tx/transactors/dex/AMMContext.h b/include/xrpl/tx/transactors/dex/AMMContext.h index 65954044be..b878e4f10b 100644 --- a/include/xrpl/tx/transactors/dex/AMMContext.h +++ b/include/xrpl/tx/transactors/dex/AMMContext.h @@ -6,7 +6,8 @@ namespace xrpl { -/** Maintains AMM info per overall payment engine execution and +/** + * Maintains AMM info per overall payment engine execution and * individual iteration. * Only one instance of this class is created in Flow.cpp::flow(). * The reference is percolated through calls to AMMLiquidity class, @@ -84,7 +85,8 @@ public: return accountID_; } - /** Strand execution may fail. Reset the flag at the start + /** + * Strand execution may fail. Reset the flag at the start * of each payment engine iteration. */ void diff --git a/include/xrpl/tx/transactors/dex/AMMCreate.h b/include/xrpl/tx/transactors/dex/AMMCreate.h index 188af8d4ef..5260cf31cd 100644 --- a/include/xrpl/tx/transactors/dex/AMMCreate.h +++ b/include/xrpl/tx/transactors/dex/AMMCreate.h @@ -11,34 +11,35 @@ namespace xrpl { -/** AMMCreate implements Automatic Market Maker(AMM) creation Transactor. - * It creates a new AMM instance with two tokens. Any trader, or Liquidity - * Provider (LP), can create the AMM instance and receive in return shares - * of the AMM pool in the form of LPTokens. The number of tokens that LP gets - * are determined by LPTokens = sqrt(A * B), where A and B is the current - * composition of the AMM pool. LP can add (AMMDeposit) or withdraw - * (AMMWithdraw) tokens from AMM and - * AMM can be used transparently in the payment or offer crossing transactions. - * Trading fee is charged to the traders for the trades executed against - * AMM instance. The fee is added to the AMM pool and distributed to the LPs - * in proportion to the LPTokens upon liquidity removal. The fee can be voted - * on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour - * auction slot, which enables LP's to trade at zero trading fee. - * AMM instance creates AccountRoot object with disabled master key - * for book-keeping of XRP balance if one of the tokens - * is XRP, a trustline for each IOU token, a trustline to keep track - * of LPTokens, and ltAMM ledger object. AccountRoot ID is generated - * internally from the parent's hash. ltAMM's object ID is +/** + * AMMCreate implements Automatic Market Maker(AMM) creation Transactor. + * It creates a new AMM instance with two tokens. Any trader, or Liquidity + * Provider (LP), can create the AMM instance and receive in return shares + * of the AMM pool in the form of LPTokens. The number of tokens that LP gets + * are determined by LPTokens = sqrt(A * B), where A and B is the current + * composition of the AMM pool. LP can add (AMMDeposit) or withdraw + * (AMMWithdraw) tokens from AMM and + * AMM can be used transparently in the payment or offer crossing transactions. + * Trading fee is charged to the traders for the trades executed against + * AMM instance. The fee is added to the AMM pool and distributed to the LPs + * in proportion to the LPTokens upon liquidity removal. The fee can be voted + * on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour + * auction slot, which enables LP's to trade at zero trading fee. + * AMM instance creates AccountRoot object with disabled master key + * for book-keeping of XRP balance if one of the tokens + * is XRP, a trustline for each IOU token, a trustline to keep track + * of LPTokens, and ltAMM ledger object. AccountRoot ID is generated + * internally from the parent's hash. ltAMM's object ID is * hash{token1.currency, token1.issuer, token2.currency, token2.issuer}, where * issue1 < issue2. ltAMM object provides mapping from the hash to AccountRoot * ID and contains: AMMAccount - AMM AccountRoot ID. TradingFee - AMM voted * TradingFee. VoteSlots - Array of VoteEntry, contains fee vote information. - * AuctionSlot - Auction slot, contains discounted fee bid information. - * LPTokenBalance - LPTokens outstanding balance. - * AMMToken - currency/issuer information for AMM tokens. - * AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash - * to access AMM instance. - * @see [XLS30d:Creating AMM instance on + * AuctionSlot - Auction slot, contains discounted fee bid information. + * LPTokenBalance - LPTokens outstanding balance. + * AMMToken - currency/issuer information for AMM tokens. + * AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash + * to access AMM instance. + * @see [XLS30d:Creating AMM instance on * XRPL](https://github.com/XRPLF/XRPL-Standards/discussions/78) */ class AMMCreate : public Transactor @@ -62,7 +63,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to create the AMM instance. */ + /** + * Attempt to create the AMM instance. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/dex/AMMDelete.h b/include/xrpl/tx/transactors/dex/AMMDelete.h index 4a0905fe10..abf800c4d1 100644 --- a/include/xrpl/tx/transactors/dex/AMMDelete.h +++ b/include/xrpl/tx/transactors/dex/AMMDelete.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMDelete implements AMM delete transactor. This is a mechanism to +/** + * AMMDelete implements AMM delete transactor. This is a mechanism to * delete AMM in an empty state when the number of LP tokens is 0. * AMMDelete deletes the trustlines up to configured maximum. If all * trustlines are deleted then AMM ltAMM and root account are deleted. diff --git a/include/xrpl/tx/transactors/dex/AMMDeposit.h b/include/xrpl/tx/transactors/dex/AMMDeposit.h index b87db19b2d..2959f1b10a 100644 --- a/include/xrpl/tx/transactors/dex/AMMDeposit.h +++ b/include/xrpl/tx/transactors/dex/AMMDeposit.h @@ -20,7 +20,8 @@ namespace xrpl { class Sandbox; -/** AMMDeposit implements AMM deposit Transactor. +/** + * AMMDeposit implements AMM deposit Transactor. * The deposit transaction is used to add liquidity to the AMM instance pool, * thus obtaining some share of the instance's pools in the form of LPTokens. * If the trader deposits proportional values of both assets without changing @@ -92,7 +93,8 @@ private: std::pair applyGuts(Sandbox& view); - /** Deposit requested assets and token amount into LP account. + /** + * Deposit requested assets and token amount into LP account. * Return new total LPToken balance. * @param view * @param ammAccount @@ -121,7 +123,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Equal asset deposit (LPTokens) for the specified share of + /** + * Equal asset deposit (LPTokens) for the specified share of * the AMM instance pools. The trading fee is not charged. * @param view * @param ammAccount @@ -146,7 +149,8 @@ private: std::optional const& deposit2Min, std::uint16_t tfee); - /** Equal asset deposit (Asset1In, Asset2In) with the constraint on + /** + * Equal asset deposit (Asset1In, Asset2In) with the constraint on * the maximum amount of both assets that the trader is willing to deposit. * The trading fee is not charged. * @param view @@ -172,7 +176,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Single asset deposit (Asset1In) by the amount. + /** + * Single asset deposit (Asset1In) by the amount. * The trading fee is charged. * @param view * @param ammAccount @@ -193,7 +198,8 @@ private: std::optional const& lpTokensDepositMin, std::uint16_t tfee); - /** Single asset deposit (Asset1In, LPTokens) by the tokens. + /** + * Single asset deposit (Asset1In, LPTokens) by the tokens. * The trading fee is charged. * @param view * @param ammAccount @@ -214,7 +220,8 @@ private: STAmount const& lpTokensDeposit, std::uint16_t tfee); - /** Single asset deposit (Asset1In, EPrice) with two constraints. + /** + * Single asset deposit (Asset1In, EPrice) with two constraints. * The trading fee is charged. * @param view * @param ammAccount @@ -235,7 +242,8 @@ private: STAmount const& ePrice, std::uint16_t tfee); - /** Equal deposit in empty AMM state (LP tokens balance is 0) + /** + * Equal deposit in empty AMM state (LP tokens balance is 0) * @param view * @param ammAccount * @param amount requested asset1 deposit amount diff --git a/include/xrpl/tx/transactors/dex/AMMVote.h b/include/xrpl/tx/transactors/dex/AMMVote.h index 10ad284bb3..4d63a98534 100644 --- a/include/xrpl/tx/transactors/dex/AMMVote.h +++ b/include/xrpl/tx/transactors/dex/AMMVote.h @@ -11,7 +11,8 @@ namespace xrpl { -/** AMMVote implements AMM vote Transactor. +/** + * AMMVote implements AMM vote Transactor. * This transactor allows for the TradingFee of the AMM instance be a votable * parameter. Any account (LP) that holds the corresponding LPTokens can cast * a vote using the new AMMVote transaction. VoteSlots array in ltAMM object diff --git a/include/xrpl/tx/transactors/dex/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h index 8f6700037d..7004dd57c1 100644 --- a/include/xrpl/tx/transactors/dex/AMMWithdraw.h +++ b/include/xrpl/tx/transactors/dex/AMMWithdraw.h @@ -22,7 +22,8 @@ namespace xrpl { class Sandbox; -/** AMMWithdraw implements AMM withdraw Transactor. +/** + * AMMWithdraw implements AMM withdraw Transactor. * The withdraw transaction is used to remove liquidity from the AMM instance * pool, thus redeeming some share of the pools that one owns in the form * of LPTokens. If the trader withdraws proportional values of both assets @@ -96,7 +97,8 @@ public: ReadView const& view, beast::Journal const& j) override; - /** Equal-asset withdrawal (LPTokens) of some AMM instance pools + /** + * Equal-asset withdrawal (LPTokens) of some AMM instance pools * shares represented by the number of LPTokens . * The trading fee is not charged. * @param view @@ -129,7 +131,8 @@ public: XRPAmount const& priorBalance, beast::Journal const& journal); - /** Withdraw requested assets and token from AMM into LP account. + /** + * Withdraw requested assets and token from AMM into LP account. * Return new total LPToken balance and the withdrawn amounts for both * assets. * @param view @@ -173,15 +176,18 @@ public: beast::Journal const& journal); private: - /** Returns IgnoreFreeze when the withdrawer is the issuer of a pool - * asset (post-fixCleanup3_3_0), ZeroIfFrozen otherwise. */ + /** + * Returns IgnoreFreeze when the withdrawer is the issuer of a pool + * asset (post-fixCleanup3_3_0), ZeroIfFrozen otherwise. + */ [[nodiscard]] FreezeHandling issuerFreezeHandling() const; std::pair applyGuts(Sandbox& view); - /** Withdraw requested assets and token from AMM into LP account. + /** + * Withdraw requested assets and token from AMM into LP account. * Return new total LPToken balance. * @param view * @param ammSle AMM ledger entry @@ -205,7 +211,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Equal-asset withdrawal (LPTokens) of some AMM instance pools + /** + * Equal-asset withdrawal (LPTokens) of some AMM instance pools * shares represented by the number of LPTokens . * The trading fee is not charged. * @param view @@ -230,7 +237,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Withdraw both assets (Asset1Out, Asset2Out) with the constraints + /** + * Withdraw both assets (Asset1Out, Asset2Out) with the constraints * on the maximum amount of each asset that the trader is willing * to withdraw. The trading fee is not charged. * @param view @@ -255,7 +263,8 @@ private: STAmount const& amount2, std::uint16_t tfee); - /** Single asset withdrawal (Asset1Out) equivalent to the amount specified + /** + * Single asset withdrawal (Asset1Out) equivalent to the amount specified * in Asset1Out. The trading fee is charged. * @param view * @param ammAccount @@ -275,7 +284,8 @@ private: STAmount const& amount, std::uint16_t tfee); - /** Single asset withdrawal (Asset1Out, LPTokens) proportional + /** + * Single asset withdrawal (Asset1Out, LPTokens) proportional * to the share specified by tokens. The trading fee is charged. * @param view * @param ammAccount @@ -297,7 +307,8 @@ private: STAmount const& lpTokensWithdraw, std::uint16_t tfee); - /** Withdraw single asset (Asset1Out, EPrice) with two constraints. + /** + * Withdraw single asset (Asset1Out, EPrice) with two constraints. * The trading fee is charged. * @param view * @param ammAccount @@ -319,7 +330,9 @@ private: STAmount const& ePrice, std::uint16_t tfee); - /** Check from the flags if it's withdraw all */ + /** + * Check from the flags if it's withdraw all + */ static WithdrawAll isWithdrawAll(STTx const& tx); }; diff --git a/include/xrpl/tx/transactors/dex/OfferCreate.h b/include/xrpl/tx/transactors/dex/OfferCreate.h index a3bf7626f9..0ce34646dd 100644 --- a/include/xrpl/tx/transactors/dex/OfferCreate.h +++ b/include/xrpl/tx/transactors/dex/OfferCreate.h @@ -28,13 +28,17 @@ namespace xrpl { class PaymentSandbox; class Sandbox; -/** Transactor specialized for creating offers in the ledger. */ +/** + * Transactor specialized for creating offers in the ledger. + */ class OfferCreate : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Custom; - /** Construct a Transactor subclass that creates an offer in the ledger. */ + /** + * Construct a Transactor subclass that creates an offer in the ledger. + */ explicit OfferCreate(ApplyContext& ctx) : Transactor(ctx) { } @@ -48,15 +52,21 @@ public: static std::uint32_t getFlagsMask(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static NotTEC preflight(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static TER preclaim(PreclaimContext const& ctx); - /** Precondition: fee collection is likely. Attempt to create the offer. */ + /** + * Precondition: fee collection is likely. Attempt to create the offer. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h index c8a5584131..e641e03dd1 100644 --- a/include/xrpl/tx/transactors/lending/LoanManage.h +++ b/include/xrpl/tx/transactors/lending/LoanManage.h @@ -36,7 +36,8 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ static TER defaultLoan( @@ -47,7 +48,8 @@ public: Asset const& vaultAsset, beast::Journal j); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ static TER impairLoan( @@ -57,7 +59,8 @@ public: Asset const& vaultAsset, beast::Journal j); - /** Helper function that might be needed by other transactors + /** + * Helper function that might be needed by other transactors */ [[nodiscard]] static TER unimpairLoan( diff --git a/include/xrpl/tx/transactors/oracle/OracleDelete.h b/include/xrpl/tx/transactors/oracle/OracleDelete.h index f9e9230527..d44065d7bb 100644 --- a/include/xrpl/tx/transactors/oracle/OracleDelete.h +++ b/include/xrpl/tx/transactors/oracle/OracleDelete.h @@ -14,13 +14,13 @@ namespace xrpl { /** - Price Oracle is a system that acts as a bridge between - a blockchain network and the external world, providing off-chain price data - to decentralized applications (dApps) on the blockchain. This implementation - conforms to the requirements specified in the XLS-47d. - - The OracleDelete transactor implements the deletion of Oracle objects. -*/ + * Price Oracle is a system that acts as a bridge between + * a blockchain network and the external world, providing off-chain price data + * to decentralized applications (dApps) on the blockchain. This implementation + * conforms to the requirements specified in the XLS-47d. + * + * The OracleDelete transactor implements the deletion of Oracle objects. + */ class OracleDelete : public Transactor { diff --git a/include/xrpl/tx/transactors/oracle/OracleSet.h b/include/xrpl/tx/transactors/oracle/OracleSet.h index e95970923b..b6aed3f4fc 100644 --- a/include/xrpl/tx/transactors/oracle/OracleSet.h +++ b/include/xrpl/tx/transactors/oracle/OracleSet.h @@ -12,13 +12,13 @@ namespace xrpl { /** - Price Oracle is a system that acts as a bridge between - a blockchain network and the external world, providing off-chain price data - to decentralized applications (dApps) on the blockchain. This implementation - conforms to the requirements specified in the XLS-47d. - - The OracleSet transactor implements creating or updating Oracle objects. -*/ + * Price Oracle is a system that acts as a bridge between + * a blockchain network and the external world, providing off-chain price data + * to decentralized applications (dApps) on the blockchain. This implementation + * conforms to the requirements specified in the XLS-47d. + * + * The OracleSet transactor implements creating or updating Oracle objects. + */ class OracleSet : public Transactor { diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h index 5a07262a3b..6bd68a0142 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h @@ -26,7 +26,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to delete the Permissioned Domain. */ + /** + * Attempt to delete the Permissioned Domain. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h index 38de800284..cb5d341c50 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h @@ -29,7 +29,9 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Attempt to create the Permissioned Domain. */ + /** + * Attempt to create the Permissioned Domain. + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h b/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h new file mode 100644 index 0000000000..3310c995ae --- /dev/null +++ b/include/xrpl/tx/transactors/sponsor/SponsorshipSet.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class SponsorshipSet : public Transactor +{ +public: + static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx) + { + } + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static std::uint32_t + getFlagsMask(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; + + void + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h b/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h new file mode 100644 index 0000000000..cf0022ab72 --- /dev/null +++ b/include/xrpl/tx/transactors/sponsor/SponsorshipTransfer.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class SponsorshipTransfer : public Transactor +{ +public: + static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx) + { + } + + static std::uint32_t + getFlagsMask(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; + + void + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/system/TicketCreate.h b/include/xrpl/tx/transactors/system/TicketCreate.h index 2a1036c732..f249fd7db2 100644 --- a/include/xrpl/tx/transactors/system/TicketCreate.h +++ b/include/xrpl/tx/transactors/system/TicketCreate.h @@ -57,15 +57,21 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static NotTEC preflight(PreflightContext const& ctx); - /** Enforce constraints beyond those of the Transactor base class. */ + /** + * Enforce constraints beyond those of the Transactor base class. + */ static TER preclaim(PreclaimContext const& ctx); - /** Precondition: fee collection is likely. Attempt to create ticket(s). */ + /** + * Precondition: fee collection is likely. Attempt to create ticket(s). + */ TER doApply() override; diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 045911c7ae..5d35f65f44 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -76,7 +76,7 @@ public: beast::Journal const& j) override; static std::expected - create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); + create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args); }; } // namespace xrpl diff --git a/nix/docker/Dockerfile b/nix/docker/Dockerfile index 6d8980f897..7222cc8fa8 100644 --- a/nix/docker/Dockerfile +++ b/nix/docker/Dockerfile @@ -75,9 +75,13 @@ COPY bin/check-tools.sh /tmp/check-tools.sh RUN /tmp/check-tools.sh # Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones. -COPY nix/docker/test_files/cpp_sources/ /tmp/cpp_sources/ -COPY nix/docker/test_files/compile-cpp-sources.sh /tmp/compile-cpp-sources.sh -RUN /tmp/compile-cpp-sources.sh /tmp/cpp_sources /tmp/bins +COPY nix/docker/test_files/cpp/ /tmp/test_files/cpp/ +RUN /tmp/test_files/cpp/compile-sources.sh /tmp/test_files/cpp/sources /tmp/cpp-bins + +# Sanity-check that rustc is able to build binaries, including ones that rely on +# the runtime overflow check. +COPY nix/docker/test_files/rust/ /tmp/test_files/rust/ +RUN /tmp/test_files/rust/compile-sources.sh /tmp/test_files/rust/sources /tmp/rust-bins # Tester: start from a clean BASE_IMAGE, install sanitizer runtime libraries, # and run the compiled test binaries to verify they execute correctly. @@ -94,15 +98,18 @@ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] # Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed. COPY bin/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh -COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh -COPY --from=final /tmp/bins /tmp/bins +COPY nix/docker/test_files/cpp/run-binaries.sh /tmp/test_files/cpp/run-binaries.sh +COPY nix/docker/test_files/rust/run-binaries.sh /tmp/test_files/rust/run-binaries.sh +COPY --from=final /tmp/cpp-bins /tmp/cpp-bins +COPY --from=final /tmp/rust-bins /tmp/rust-bins RUN < as for name in +# {hello,panic,overflow}. + +set -eo pipefail + +bins_dir="${1:?usage: $0 }" + +failed_binaries=() + +# Run a binary and verify its exit code and output. +# Usage: run +function run() { + local binary="${1}" + local expected_output="${2}" + local expected_rc="${3}" + + local out_file + out_file="$(mktemp)" + + echo "=== Run ${binary} ===" + set +e + "${binary}" >"${out_file}" 2>&1 + local rc=$? + set -e + + cat "${out_file}" + + local failed=0 + if [ "${expected_rc}" = "nonzero" ]; then + if [ "${rc}" -eq 0 ]; then + echo "ERROR: expected non-zero exit code from ${binary}, got ${rc}" >&2 + failed=1 + fi + elif [ "${rc}" -ne "${expected_rc}" ]; then + echo "ERROR: expected exit code ${expected_rc} from ${binary}, got ${rc}" >&2 + failed=1 + fi + + if ! grep -q "${expected_output}" "${out_file}"; then + echo "ERROR: expected '${expected_output}' from ${binary}" >&2 + failed=1 + fi + + if [ "${failed}" -eq 0 ]; then + echo "OK: '${expected_output}' detected" + else + failed_binaries+=("${binary}") + fi +} + +declare -A expect=( + [hello]="Hello from main thread" + [panic]="explicit panic from test" + [overflow]="attempt to add with overflow" +) + +for name in hello panic overflow; do + binary="${bins_dir}/${name}" + + if [ "${name}" = "hello" ]; then + expected_rc=0 + else + expected_rc=nonzero + fi + run "${binary}" "${expect[$name]}" "${expected_rc}" +done + +if [ "${#failed_binaries[@]}" -gt 0 ]; then + echo "ERROR: the following binaries failed:" >&2 + printf ' %s\n' "${failed_binaries[@]}" >&2 + exit 1 +fi diff --git a/nix/docker/test_files/rust/sources/hello.rs b/nix/docker/test_files/rust/sources/hello.rs new file mode 100644 index 0000000000..78e32c17f3 --- /dev/null +++ b/nix/docker/test_files/rust/sources/hello.rs @@ -0,0 +1,16 @@ +use std::thread; + +fn main() { + const NUM_THREADS: usize = 10; + let mut handles = Vec::with_capacity(NUM_THREADS); + for id in 0..NUM_THREADS { + handles.push(thread::spawn(move || { + println!("Hello from thread {id}"); + })); + } + for handle in handles { + handle.join().expect("worker thread panicked"); + } + + println!("Hello from main thread"); +} diff --git a/nix/docker/test_files/rust/sources/overflow.rs b/nix/docker/test_files/rust/sources/overflow.rs new file mode 100644 index 0000000000..2a4a54472c --- /dev/null +++ b/nix/docker/test_files/rust/sources/overflow.rs @@ -0,0 +1,13 @@ +use std::hint::black_box; + +// Rust analogue of the C++ UBSan check: with overflow checks enabled the +// compiler inserts a runtime check that panics on signed integer overflow. +// `black_box` keeps the operands opaque so the addition is evaluated at +// runtime rather than being rejected by the compile-time overflow lint. +fn main() { + let max = black_box(i32::MAX); + let one = black_box(1); + println!("Current max: {max}"); + let overflowed = max + one; + println!("Overflowed result: {overflowed}"); +} diff --git a/nix/docker/test_files/rust/sources/panic.rs b/nix/docker/test_files/rust/sources/panic.rs new file mode 100644 index 0000000000..38779ff515 --- /dev/null +++ b/nix/docker/test_files/rust/sources/panic.rs @@ -0,0 +1,5 @@ +fn main() { + // Verify the panic runtime works: a panic must print its message to stderr + // and exit with a non-zero status (Rust's default panic exit code is 101). + panic!("explicit panic from test"); +} diff --git a/nix/packages.nix b/nix/packages.nix index bf10ee3712..41d7e97328 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -62,5 +62,15 @@ in runClangTidy vim zip + # Rust packages + cargo + cargo-audit + cargo-llvm-cov + cargo-nextest + clippy + corrosion + rust-analyzer + rustc + rustfmt ]; } diff --git a/sanitizers/suppressions/sanitizer-ignorelist.txt b/sanitizers/suppressions/sanitizer-ignorelist.txt index dc9a31f8e4..ffd9b4103a 100644 --- a/sanitizers/suppressions/sanitizer-ignorelist.txt +++ b/sanitizers/suppressions/sanitizer-ignorelist.txt @@ -29,7 +29,7 @@ src:test/beast/beast_PropertyStream_test.cpp src:src/test/app/Invariants_test.cpp # ASan false positive: stack-use-after-scope in ErrorCodes.h inline functions. -# When Clang inlines the StaticString overloads (e.g. invalid_field_error(StaticString)), +# When Clang inlines the StaticString overloads (e.g. invalidFieldError(StaticString)), # ASan scope-poisons the temporary std::string before the inlined callee finishes reading # through the const ref. This corrupts the coroutine stack and crashes the Simulate test. # See asan.supp comments for full explanation and planned fix. diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 4d7a821040..1f2c41809a 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #ifdef _MSC_VER @@ -31,74 +30,103 @@ namespace xrpl { thread_local Number::RoundingMode Number::mode = Number::RoundingMode::ToNearest; thread_local std::reference_wrapper Number::kRange = - MantissaRange::getMantissaRange(MantissaRange::MantissaScale::Large); + MantissaRange::Access::mantissaRange(MantissaRange::MantissaScale::Large330); -std::set const& -MantissaRange::getAllScales() +std::string +to_string(MantissaRange::MantissaScale const& scale) { - static std::set const kScales = { - MantissaRange::MantissaScale::Small, - MantissaRange::MantissaScale::LargeLegacy, - MantissaRange::MantissaScale::Large, - }; - return kScales; + switch (scale) + { + case MantissaRange::MantissaScale::Small: + return "Small"; + case MantissaRange::MantissaScale::LargeLegacy: + return "LargeLegacy"; + case MantissaRange::MantissaScale::Large320: + return "Large320"; + case MantissaRange::MantissaScale::Large330: + return "Large330"; + default: + throw std::runtime_error("Bad scale"); // LCOV_EXCL_LINE + } } -std::unordered_map const& -MantissaRange::getRanges() +std::string +to_string(Number::RoundingMode const& round) { - static auto const kMap = []() { - std::unordered_map map; - for (auto const scale : getAllScales()) - { - map.emplace(scale, scale); - } - - // Use these constexpr declarations to do static_asserts to verify the MantissaRanges are - // created correctly, but nothing else. - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Small}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000LL); - static_assert(kRange.max == 9'999'999'999'999'999LL); - static_assert(kRange.log == 15); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max < Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); - } - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::LargeLegacy}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000'000ULL); - static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); - static_assert(kRange.log == 18); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max > Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); - } - { - [[maybe_unused]] - constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Large}; - static_assert(isPowerOfTen(kRange.min)); - static_assert(kRange.min == 1'000'000'000'000'000'000ULL); - static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); - static_assert(kRange.log == 18); - static_assert(kRange.min < Number::kMaxRep); - static_assert(kRange.max > Number::kMaxRep); - static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled); - } - return map; - }(); - - return kMap; + switch (round) + { + case Number::RoundingMode::ToNearest: + return "ToNearest"; + case Number::RoundingMode::TowardsZero: + return "TowardsZero"; + case Number::RoundingMode::Downward: + return "Downward"; + case Number::RoundingMode::Upward: + return "Upward"; + default: + throw std::runtime_error("Bad rounding mode"); // LCOV_EXCL_LINE + } } -MantissaRange const& -MantissaRange::getMantissaRange(MantissaScale scale) +constexpr MantissaRange const& +MantissaRange::Access::mantissaRange(MantissaScale scale) { - return getRanges().at(scale); + static constexpr MantissaRange kSmall{MantissaScale::Small}; + static constexpr MantissaRange kLegacy{MantissaScale::LargeLegacy}; + static constexpr MantissaRange kLarge320{MantissaScale::Large320}; + static constexpr MantissaRange kLarge330{MantissaScale::Large330}; + + switch (scale) + { + case MantissaScale::Small: + return kSmall; + case MantissaScale::LargeLegacy: + return kLegacy; + case MantissaScale::Large320: + return kLarge320; + case MantissaScale::Large330: + return kLarge330; + } + throw std::logic_error("Unknown mantissa scale"); + + // static_asserts are checked at compile time, so it doesn't matter where in the function they + // are located. For readability of the main body, put them after it. + + // Small + static_assert(isPowerOfTen(kSmall.min)); + static_assert(kSmall.min == 1'000'000'000'000'000LL); + static_assert(kSmall.max == 9'999'999'999'999'999LL); + static_assert(kSmall.log == 15); + static_assert(kSmall.min < Number::kMaxRep); + static_assert(kSmall.max < Number::kMaxRep); + static_assert(kSmall.cuspRoundingFix == CuspRoundingFix::Disabled); + + // LargeLegacy + static_assert(isPowerOfTen(kLegacy.min)); + static_assert(kLegacy.min == 1'000'000'000'000'000'000ULL); + static_assert(kLegacy.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLegacy.log == 18); + static_assert(kLegacy.min < Number::kMaxRep); + static_assert(kLegacy.max > Number::kMaxRep); + static_assert(kLegacy.cuspRoundingFix == CuspRoundingFix::Disabled); + + // Large320 + static_assert(isPowerOfTen(kLarge320.min)); + static_assert(kLarge320.min == 1'000'000'000'000'000'000ULL); + static_assert(kLarge320.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLarge320.log == 18); + static_assert(kLarge320.min < Number::kMaxRep); + static_assert(kLarge320.max > Number::kMaxRep); + static_assert(kLarge320.cuspRoundingFix == CuspRoundingFix::Enabled320); + + // Large330 + static_assert(isPowerOfTen(kLarge330.min)); + static_assert(kLarge330.min == 1'000'000'000'000'000'000ULL); + static_assert(kLarge330.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kLarge330.log == 18); + static_assert(kLarge330.min < Number::kMaxRep); + static_assert(kLarge330.max > Number::kMaxRep); + static_assert(kLarge330.cuspRoundingFix == CuspRoundingFix::Enabled330); } Number::RoundingMode @@ -124,7 +152,7 @@ Number::setMantissaScale(MantissaRange::MantissaScale scale) { if (!MantissaRange::getAllScales().contains(scale)) logicError("Unknown mantissa scale"); - kRange = MantissaRange::getMantissaRange(scale); + kRange = MantissaRange::Access::mantissaRange(scale); } // Optimization equivalent to: @@ -155,15 +183,39 @@ divu10(uint128_t& u) return r; } -// Guard - -// The Guard class is used to temporarily add extra digits of -// precision to an operation. This enables the final result -// to be correctly rounded to the internal precision of Number. - template concept UnsignedMantissa = std::is_unsigned_v || std::is_same_v; +/** + * Guard + * + * The Guard class is used to temporarily add extra digits of + * precision to an operation. This enables the final result + * to be correctly rounded to the internal precision of Number. + * + * At its core, the Guard really only needs three pieces of information to determine how to round: + * 1. The rounding mode + * 2. The last digit dropped from the mantissa (i.e. the first digit after the decimal point). + * (first byte of digits_) + * 3. Whether any other non-zero digits were dropped from the mantissa. (remaining bytes of digits_ + * and xbit_) + * + * Upward and Downward rounding modes round the unsigned mantissa toward or away from zero + * depending on whether the sign is negative (sbit_). For positive values, Upward is away, and + * Downward is toward. For negative values, that's reversed. For simplicity, I'm going to describe + * the logic using "TowardZero" and "AwayFromZero". + * + * TowardZero is the easiest rounding mode. It always rounds down. digits_ and xbit_ are + * irrelevant. + * AwayFromZero is almost as simple. If both "digits_" and "xbit_" are zero (0), it rounds down. + * Else it rounds up. + * ToNearest is only a little more complicated. If the last dropped digit is < 5, then round + * down. If it is > 5, round up. If it is exactly 5, and there are _any_ other digits (the + * remainder of "digits_" or "xbit_"), round up, else round to even. + * + * The current implementation stores 16 digits in "digits_" so that digits can be "pop"ped back + * out if needed during subtraction (negative addition) operations. + */ class Number::Guard { std::uint64_t digits_{0}; // 16 decimal guard digits @@ -171,7 +223,21 @@ class Number::Guard std::uint8_t sbit_ : 1 {0}; // the sign of the guard digits public: - explicit Guard() = default; + internalrep const minMantissa; + internalrep const maxMantissa; + MantissaRange::CuspRoundingFix const cuspRoundingFix; + + explicit Guard( + internalrep const& minMantissa, + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFix) + : minMantissa(minMantissa), maxMantissa(maxMantissa), cuspRoundingFix(cuspRoundingFix) + { + } + + explicit Guard(MantissaRange const& range) : Guard(range.min, range.max, range.cuspRoundingFix) + { + } // set & test the sign bit void @@ -194,52 +260,70 @@ public: unsigned pop() noexcept; - /** Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in + // if true, there are no digits in the guard, including dropped digits (xbit_) + [[nodiscard]] bool + empty() const noexcept; + + /** + * Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in * this Guard. * * Substitute for: - push(mantissa % 10); - mantissa /= 10; - ++exponent; + * push(mantissa % 10); + * mantissa /= 10; + * ++exponent; */ template void doDropDigit(T& mantissa, int& exponent) noexcept; - // Indicate round direction: 1 is up, -1 is down, 0 is even - // This enables the client to round towards nearest, and on - // tie, round towards even. - [[nodiscard]] int - round() const noexcept; + // Modify the result to the correctly rounded value + template + void + doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location); // Modify the result to the correctly rounded value template void - doRoundUp( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa, - internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, - std::string location); - - // Modify the result to the correctly rounded value - template - void - doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); + doRoundDown(bool& negative, T& mantissa, int& exponent) const; // Modify the result to the correctly rounded value void doRound(rep& drops, std::string location) const; private: + template + void + pushOverflow(T mantissa); + + enum class Round { + // The result is exact. No rounding is needed. Only used if cuspRoundingFix is Enabled330 or + // higher. + Exact = -2, + // Round down. Since we use integer math, that usually means no change is needed. + // Exceptions are for when the result is between kMaxRep and kMaxRepUp (round to kMaxRep), + // or after subtraction where _any_ remainder will modify the result. The latter is what + // distinguishes Exact from Down. + Down = -1, + // The result was exactly half-way between two integers. This will round to even. + Even = 0, + // Round up. Always adds 1 (or subtracts 1 in some cases if cuspRoundingFix is not + // Enabled330) + Up = 1, + }; + + // Indicate round direction. See Round enum above. + // This enables the client to round towards nearest, and on + // tie, round towards even. + [[nodiscard]] Round + round() const noexcept; + void doPush(unsigned d) noexcept; template void - bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); + bringIntoRange(bool& negative, T& mantissa, int& exponent) const; }; inline void @@ -269,6 +353,7 @@ Number::Guard::isNegative() const noexcept inline void Number::Guard::doPush(unsigned d) noexcept { + XRPL_ASSERT(d < 10, "xrpl::Number::Guard::doPush : valid digit"); xbit_ = xbit_ || ((digits_ & 0x0000'0000'0000'000F) != 0); digits_ >>= 4; digits_ |= (d & 0x0000'0000'0000'000FULL) << 60; @@ -289,6 +374,12 @@ Number::Guard::pop() noexcept return d; } +inline bool +Number::Guard::empty() const noexcept +{ + return digits_ == 0 && !xbit_; +} + template void Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept @@ -310,64 +401,131 @@ Number::Guard::doDropDigit(uint128_t& mantissa, int& exponent) noexce ++exponent; } +template +void +Number::Guard::pushOverflow(T mantissa) +{ + XRPL_ASSERT(mantissa <= kMaxRepUp, "xrpl::Number::Guard::pushOverflow : valid mantissa"); + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa >= kMaxRep && + mantissa < kMaxRepUp) + { + // Special case rounding rules for the values in the range [kMaxRep, kMaxRepUp). + + auto constexpr spread = kMaxRepUp - kMaxRep; + static_assert(spread == 3); + + // Round in two steps. + + // The first step uses the digits _already_ in the Guard to possibly round the mantissa up. + // Ultimately, the purpose of this step is to capture rounding where the stored digits would + // change the decision without those digits. (e.g. From just _below_ the midpoint to just + // _above_ the midpoint for ToNearest, or from kMaxRep into the in-between for Upward. Make + // an exception if the final digit is 9, because it can only get larger, and we don't want + // to bump up to kMaxRepUp. + if (mantissa % 10 < 9) + { + // Intentionally use integer math to get the largest value under the midpoint. + auto constexpr kMidpoint = kMaxRep + (spread / 2); + static_assert(kMidpoint == kMaxRep + 1); + auto const r = round(); + if (r == Round::Up || (r == Round::Even && mantissa == kMidpoint)) + { + ++mantissa; + } + } + + // The second step scales the final digit of the updated mantissa proportionally, converting + // from (kMaxRep, kMaxRepUp) to (0 to 9]. It then pushes that scaled digit onto the guard as + // if it was a digit that got removed, but doesn't actually remove it. This method should be + // future-proof in case the number of mantissa bits ever changes. (Though for integer values + // of the form 2^(2^x-1), the spread will always be the same.) Effects: + // * For round to nearest + // * if the updated mantissa is below the midpoint, it'll round "down" to kMaxRep + // * if above the midpoint, it'll round "up" to kMaxRepUp + // * it can never be exactly at the midpoint, because kMaxRepUp is always even, and + // kMaxRep is always odd, so don't worry about that case. + // * For round upward, will round up to kMaxRepUp for positive values, down to kMaxRep for + // negative. + // * For round downward, does the opposite of upward. + // * For round toward zero, always rounds down to kMaxRep. + + auto const diff = mantissa - kMaxRep; + auto const digit = static_cast((diff * 10) / spread); + XRPL_ASSERT( + digit < 10u && digit != 5, "xrpl::Number::Guard::pushOverflow : valid overflow digit"); + + // Don't remove the digit from the mantissa, but add it to the guard as if it was. + push(digit); + } +} + // Returns: -// -1 if Guard is less than half -// 0 if Guard is exactly half -// 1 if Guard is greater than half -int +// Exact if Guard is _zero_, and appropriate amendments are enabled +// Down if Guard is less than half +// Even if Guard is exactly half +// Up if Guard is greater than half +Number::Guard::Round Number::Guard::round() const noexcept { - auto mode = Number::getround(); + // Local "mode" shadows and has the same value as the static thread_local "Number::mode". + // This ensures the overhead of loading the thread_local is only incurred once. + auto const mode = Number::getround(); + + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && empty()) + { + // No remainder + return Round::Exact; + } if (mode == RoundingMode::TowardsZero) - return -1; + return Round::Down; - if (mode == RoundingMode::Downward) + // Also Towards Zero + if ((mode == RoundingMode::Downward && !sbit_) || (mode == RoundingMode::Upward && sbit_)) { - if (sbit_) - { - if (digits_ > 0 || xbit_) - return 1; - } - return -1; + return Round::Down; } - if (mode == RoundingMode::Upward) + // Away from Zero. Since we checked sbit_ in the previous block, we don't need to check it + // again. + if (mode == RoundingMode::Downward || mode == RoundingMode::Upward) { - if (sbit_) - return -1; - if (digits_ > 0 || xbit_) - return 1; - return -1; + if (empty()) + return Round::Down; + return Round::Up; } + XRPL_ASSERT( + mode == RoundingMode::ToNearest, "xrpl::Number::Guard::Round : fallthrough to ToNearest"); // assume round to nearest if mode is not one of the predefined values if (digits_ > 0x5000'0000'0000'0000) - return 1; + return Round::Up; if (digits_ < 0x5000'0000'0000'0000) - return -1; + return Round::Down; if (xbit_) - return 1; - return 0; + return Round::Up; + return Round::Even; } template void -Number::Guard::bringIntoRange( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa) +Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent) const { // Bring mantissa back into the minMantissa / maxMantissa range AFTER - // rounding - if (mantissa < minMantissa) + // rounding. + if (mantissa < minMantissa && + (cuspRoundingFix < MantissaRange::CuspRoundingFix::Enabled330 || mantissa != 0)) { mantissa *= 10; --exponent; } - if (exponent < kMinExponent) + // mantissa should never be 0, but if it _is_ assert, but fall back to making the result kZero. + if (exponent < kMinExponent || + (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa == 0)) { + // Engineers: If you hit this assert, you probably did something wrong in the operation + // leading up to the rounding work. + XRPL_ASSERT(mantissa != 0, "xrpl::Number::Guard::bringIntoRange : valid mantissa"); static constexpr Number kZero = Number{}; negative = kZero.negative_; @@ -378,22 +536,17 @@ Number::Guard::bringIntoRange( template void -Number::Guard::doRoundUp( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa, - internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, - std::string location) +Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location) { - auto r = round(); - if (r == 1 || (r == 0 && (mantissa & 1) == 1)) + pushOverflow(mantissa); + + auto const r = round(); + if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1)) { - auto const safeToIncrement = [&maxMantissa](auto const& mantissa) { + auto const safeToIncrement = [this](auto const& mantissa) { return mantissa < maxMantissa && mantissa < kMaxRep; }; - if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled) + if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled) { // Ensure mantissa after incrementing fits within both the // min/maxMantissa range and is a valid "rep". @@ -404,25 +557,29 @@ Number::Guard::doRoundUp( } else { - // Incrementing the mantissa will require dividing, which will require rounding. So - // _don't_ increment the mantissa. Instead, divide and round recursively. It should - // be impossible to recurse more than once, because once the mantissa is divided by - // 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no - // chance of bringing it back over. - doDropDigit(mantissa, exponent); - XRPL_ASSERT_PARTS( - safeToIncrement(mantissa), - "xrpl::Number::Guard::doRoundUp", - "can't recurse more than once"); - doRoundUp( - negative, - mantissa, - exponent, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - location); - return; + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && + mantissa > kMaxRep && mantissa < kMaxRepUp) + { + // When rounding up a value in between kMaxRep, and kMaxRepUp, round to + // kMaxRepUp. Note that the decision for this rounding is dominated by the + // results of pushOverflow. + mantissa = kMaxRepUp; + } + else + { + // Incrementing the mantissa will require dividing, which will require rounding. + // So _don't_ increment the mantissa. Instead, divide and round recursively. It + // should be impossible to recurse more than once, because once the mantissa is + // divided by 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 + // will have no chance of bringing it back over. + doDropDigit(mantissa, exponent); + XRPL_ASSERT_PARTS( + safeToIncrement(mantissa), + "xrpl::Number::Guard::doRoundUp", + "can't recurse more than once"); + doRoundUp(negative, mantissa, exponent, location); + return; + } } } else @@ -440,38 +597,63 @@ Number::Guard::doRoundUp( } } } - bringIntoRange(negative, mantissa, exponent, minMantissa); + else if ( + cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa > kMaxRep && + mantissa < kMaxRepUp) + { + // When rounding down a value in between kMaxRep, and kMaxRepUp, round to kMaxRep. + // Note that the decision for this rounding is dominated by the results of pushOverflow. + mantissa = kMaxRep; + } + bringIntoRange(negative, mantissa, exponent); if (exponent > kMaxExponent) Throw(std::string(location)); } template void -Number::Guard::doRoundDown( - bool& negative, - T& mantissa, - int& exponent, - internalrep const& minMantissa) +Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent) const { + // Do not pushOverflow here. + auto r = round(); - if (r == 1 || (r == 0 && (mantissa & 1) == 1)) + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - --mantissa; - if (mantissa < minMantissa) + // If there was any remainder, subtract 1 from the result. This is sufficient to get the + // best rounding. + XRPL_ASSERT( + r == Round::Exact || mantissa > maxMantissa, + "xrpl::Number::Guard::doRoundDown : mantissa is expected size"); + if (r != Round::Exact) { - mantissa *= 10; - --exponent; + --mantissa; } } - bringIntoRange(negative, mantissa, exponent, minMantissa); + else + { + // Need to preserve the incorrect behavior until the fix amendment can be retired, + // because otherwise would risk an unplanned ledger fork. + if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1)) + { + --mantissa; + if (mantissa < minMantissa) + { + mantissa *= 10; + --exponent; + } + } + } + bringIntoRange(negative, mantissa, exponent); } // Modify the result to the correctly rounded value void Number::Guard::doRound(rep& drops, std::string location) const { + // Do not pushOverflow here. + auto r = round(); - if (r == 1 || (r == 0 && (drops & 1) == 1)) + if (r == Round::Up || (r == Round::Even && (drops & 1) == 1)) { if (drops >= kMaxRep) { @@ -486,6 +668,8 @@ Number::Guard::doRound(rep& drops, std::string location) const } ++drops; } + XRPL_ASSERT(drops >= 0, "xrpl::Number::Guard::doRound : positive magnitude"); + if (isNegative()) drops = -drops; } @@ -530,12 +714,14 @@ doNormalize( int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + MantissaRange::CuspRoundingFix cuspRoundingFix, bool dropped) { static constexpr auto kMinExponent = Number::kMinExponent; static constexpr auto kMaxExponent = Number::kMaxExponent; - static constexpr auto kMaxRep = Number::kMaxRep; + auto const repLimit = cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 + ? Number::kMaxRepUp + : Number::kMaxRep; using Guard = Number::Guard; @@ -553,7 +739,7 @@ doNormalize( m *= 10; --exponent; } - Guard g; + Guard g(minMantissa, maxMantissa, cuspRoundingFix); if (negative) g.setNegative(); if (dropped) @@ -585,27 +771,20 @@ doNormalize( // 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460. // mantissa() will return mantissa / 10, and exponent() will return // exponent + 1. - if (m > kMaxRep) + if (m > repLimit) { if (exponent >= kMaxExponent) throw std::overflow_error("Number::normalize 1.5"); g.doDropDigit(m, exponent); } // Before modification, m should be within the min/max range. After - // modification, it must be less than kMaxRep. In other words, the original - // value should have been no more than kMaxRep * 10. - // (kMaxRep * 10 > maxMantissa) - XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64"); + // modification, it must be less than repLimit. In other words, the original + // value should have been no more than repLimit * 10. + // (repLimit * 10 > maxMantissa) + XRPL_ASSERT_PARTS(m <= repLimit, "xrpl::doNormalize", "intermediate mantissa fits in limit"); mantissa = m; - g.doRoundUp( - negative, - mantissa, - exponent, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::normalize 2"); + g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2"); XRPL_ASSERT_PARTS( mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::doNormalize", @@ -620,13 +799,12 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { // Not used by every compiler version, and thus not necessarily // counted by coverage build // LCOV_EXCL_START - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); // LCOV_EXCL_STOP } @@ -638,13 +816,12 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { // Not used by every compiler version, and thus not necessarily // counted by coverage build // LCOV_EXCL_START - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); // LCOV_EXCL_STOP } @@ -656,16 +833,27 @@ Number::normalize( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFix) { - doNormalize( - negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false); } void Number::normalize(MantissaRange const& range) { - normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled); + normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFix); +} + +void +Number::normalize(Guard const& guard) +{ + normalize( + negative_, + mantissa_, + exponent_, + guard.minMantissa, + guard.maxMantissa, + guard.cuspRoundingFix); } // Copy the number, but set a new exponent. Because the mantissa doesn't change, @@ -719,46 +907,105 @@ Number::operator+=(Number const& y) bool const yn = y.negative_; uint128_t ym = y.mantissa_; auto ye = y.exponent_; - Guard g; + Guard g(kRange); + + auto const& minMantissa = g.minMantissa; + auto const& maxMantissa = g.maxMantissa; + auto const cuspRoundingFix = g.cuspRoundingFix; + + auto const repLimit = + cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep; + + // Bring the exponents of both values into agreement, so the mantissas are on the same scale + // and can be added directly together. + + auto const upperLimit = static_cast(g.minMantissa) * 1000; + // For the "adjust" lambda + // expandM / expandE: The values for which the mantissa will be expanded, and the exponent + // decreased to match. Mantissa won't be expanded beyond upperLimit. + // (37e8 == 37000e5 == 37000000e2) + // shrinkM / shrinkE: The values for which the mantissa will be shrunk, and exponent increased + // to match, if necessary. + auto const adjust = [&g, &upperLimit]( + uint128_t& expandM, int& expandE, uint128_t& shrinkM, int& shrinkE) { + // Adjust up and down until the exponents match + if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330) + { + // For Enabled330, there are three steps. + // 1. First, shrink the mantissa of shrinkM/shrinkE while shrinkM ends in 0. + while (shrinkE < expandE && shrinkM % 10 == 0) + { + g.doDropDigit(shrinkM, shrinkE); + } + + // 2. Then expand the mantissa of expandM/expandE, with a limit for expandM a few orders + // of magnitude above the MantissaRange. This will leave a few extra digits for rounding + // later, but nothing excessive. + while (shrinkE < expandE && expandE > kMinExponent && expandM < upperLimit) + { + expandM *= 10; + --expandE; + } + } + + // 3. Finally, shrink the mantissa of shrinkM/shrinkE until the exponents match. Any removed + // digits will be put into the Guard. This is the only step for non-Enabled330 modes. + while (shrinkE < expandE) + { + g.doDropDigit(shrinkM, shrinkE); + } + }; + + // Shrink the mantissa and raise the exponent of the value with the lower exponent. Store any + // dropped digits in the Guard. if (xe < ye) { if (xn) g.setNegative(); - do - { - g.doDropDigit(xm, xe); - } while (xe < ye); + + adjust(ym, ye, xm, xe); } else if (xe > ye) { if (yn) g.setNegative(); - do - { - g.doDropDigit(ym, ye); - } while (xe > ye); - } - auto const& range = kRange.get(); - auto const& minMantissa = range.min; - auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + adjust(xm, xe, ym, ye); + } + else if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330) + { + // Both values have the same exponent. + // Set the sign of the Guard based on the sign of the Number with the smallest + // unsigned _mantissa_ + if ((xm < ym && xn) || (ym < xm && yn)) + g.setNegative(); + } if (xn == yn) { xm += ym; - if (xm > maxMantissa || xm > kMaxRep) + + if (g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - g.doDropDigit(xm, xe); + // Don't do any adjustments for Enabled330. Normalize will take care of it + // Because of "adjust", the only way there can be data in the Guard is if we first grew + // the mantissa past the maxMantissa. Since we added here, it can only get bigger. + // If xm > maxMantissa, then doNormalize has all the data it needs from the last 3-4 + // digits, plus the "dropped" flag that will be passed in. + // If not, then the mantissa will only need to be padded out with 0s and won't need to + // round. + XRPL_ASSERT( + xm > maxMantissa || g.empty(), + "xrpl::Number::operator+ : rounding state expected after add"); + } + else + { + if (xm > maxMantissa || xm > repLimit) + { + g.doDropDigit(xm, xe); + } + g.doRoundUp(xn, xm, xe, "Number::addition overflow"); } - g.doRoundUp( - xn, - xm, - xe, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::addition overflow"); } else { @@ -772,19 +1019,67 @@ Number::operator+=(Number const& y) xe = ye; xn = yn; } - while (xm < minMantissa && xm * 10 <= kMaxRep) + if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330) { - xm *= 10; - xm -= g.pop(); - --xe; + // Because we subtracted, xm can have any number of digits from 1 up to + // upperLimit * 10, and g can be in any state. (Note that xm can't be zero, because that + // special case was tested earlier.) + + // Grow xm/xe and pull digits out of the Guard until xm reaches upperLimit, but stop if + // the Guard empties out, because no rounding will be necessary. This will ensure that + // normalize will have enough information to make an accurate rounding decision. + // (Normalize will pad a small mantissa back into range.) Note that if any digits were + // lost (xbit_), the Guard will never be empty, so xm will grow larger than upperLimit. + while (xm < upperLimit && !g.empty()) + { + xm *= 10; + xm -= g.pop(); + --xe; + } + XRPL_ASSERT( + xm > maxMantissa || g.empty(), + "xrpl::Number::operator+ : rounding state expected after subtract"); } - g.doRoundDown(xn, xm, xe, minMantissa); + else + { + // Grow xm/xe and pull digits out of the Guard until it's back in the + // minMantissa/maxMantissa range. + while (xm < minMantissa && xm * 10 <= repLimit) + { + xm *= 10; + xm -= g.pop(); + --xe; + } + } + // Rounding down can result in decrementing xm, based on whether there is any data left in + // the Guard (depending on cuspRoundingFix). Note that if that happens, then the Guard is + // not empty. For Enabled330, that will also result in the "dropped" flag being passed to + // doNormalize, which may result in the mantissa being incremented again. It doesn't matter + // what the dropped digits are, only that they exist. This is because subtracting one + // "overcorrects", so we know there are still trailing digits to be accounted for in the + // rounding. + // + // This works because + // 1. The rounding up will be done _after_ the mantissa is brought into range. It may not + // be in range right now, and + // 2. The "dropped" flag is only ever used as a tie-breaker, specifically when rounding + // away from zero, and the dropped digits are 0, or when rounding to nearest, and + // the dropped digits represent exactly 0.5. + g.doRoundDown(xn, xm, xe); } + doNormalize( + xn, + xm, + xe, + minMantissa, + maxMantissa, + cuspRoundingFix, + cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330 && !g.empty()); negative_ = xn; mantissa_ = static_cast(xm); exponent_ = xe; - normalize(range); + XRPL_ASSERT(isnormal(), "xrpl::Number::operator+= : result is normal"); return *this; } @@ -818,35 +1113,27 @@ Number::operator*=(Number const& y) auto ze = xe + ye; auto zs = xs * ys; bool zn = (zs == -1); - Guard g; + Guard g(kRange); if (zn) g.setNegative(); - auto const& range = kRange.get(); - auto const& minMantissa = range.min; - auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + auto const& maxMantissa = g.maxMantissa; + auto const repLimit = + g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep; - while (zm > maxMantissa || zm > kMaxRep) + while (zm > maxMantissa || zm > repLimit) { g.doDropDigit(zm, ze); } xm = static_cast(zm); xe = ze; - g.doRoundUp( - zn, - xm, - xe, - minMantissa, - maxMantissa, - cuspRoundingFixEnabled, - "Number::multiplication overflow : exponent is " + std::to_string(xe)); + g.doRoundUp(zn, xm, xe, "Number::multiplication overflow : exponent is " + std::to_string(xe)); negative_ = zn; mantissa_ = xm; exponent_ = xe; - normalize(range); + normalize(g); return *this; } @@ -882,7 +1169,7 @@ Number::operator/=(Number const& y) auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; - auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; + auto const cuspRoundingFix = range.cuspRoundingFix; // Division operates on two large integers (16-digit for small // mantissas, 19-digit for large) using integer math. If the values @@ -930,7 +1217,7 @@ Number::operator/=(Number const& y) // This is equivalent to if we had used an initial factor of 10^22, // a couple digits more than we actually need. // - // Stage 3: If there is still a remainder, and the CuspRoundingFix + // Stage 3: If there is still a remainder, and the cuspRoundingFix // is enabled, pass a flag indicating such to doNormalize. The Guard // in doNormalize will treat that flag as if non-zero digits had // been dropped from the mantissa when shrinking it into range. @@ -1014,14 +1301,14 @@ Number::operator/=(Number const& y) // rounding fix is enabled, flag if there is still // a remainder from stage 2. bool const useTrailingRemainder = - cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled; + cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled; if (useTrailingRemainder) { dropped = partialNumerator % dm != 0; } } } - doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped); + doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFix, dropped); negative_ = zp; mantissa_ = static_cast(zm); exponent_ = ze; @@ -1035,7 +1322,7 @@ operator rep() const { rep drops = mantissa(); int offset = exponent(); - Guard g; + Guard g(kRange); if (drops != 0) { if (negative_) @@ -1099,8 +1386,11 @@ to_string(Number const& amount) } std::string ret = negative ? "-" : ""; ret.append(std::to_string(mantissa)); - ret.append(1, 'e'); - ret.append(std::to_string(exponent)); + if (exponent != 0) + { + ret.append(1, 'e'); + ret.append(std::to_string(exponent)); + } return ret; } diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 7e1b56f87a..25e95b7fc5 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -33,10 +33,11 @@ namespace xrpl { -/** Mix-in to track when all pending I/O is complete. - Derived classes must be callable with this signature: - void asyncHandlersComplete() -*/ +/** + * Mix-in to track when all pending I/O is complete. + * Derived classes must be callable with this signature: + * void asyncHandlersComplete() + */ template class AsyncObject { @@ -51,10 +52,11 @@ public: XRPL_ASSERT(pending_.load() == 0, "xrpl::AsyncObject::~AsyncObject : nothing pending"); } - /** RAII container that maintains the count of pending I/O. - Bind this into the argument list of every handler passed - to an initiating function. - */ + /** + * RAII container that maintains the count of pending I/O. + * Bind this into the argument list of every handler passed + * to an initiating function. + */ class CompletionCounter { public: diff --git a/src/libxrpl/basics/base64.cpp b/src/libxrpl/basics/base64.cpp index 541ddd0839..c980a08669 100644 --- a/src/libxrpl/basics/base64.cpp +++ b/src/libxrpl/basics/base64.cpp @@ -76,32 +76,37 @@ getInverse() return &kTab[0]; } -/// Returns max chars needed to encode a base64 string +/** + * Returns max chars needed to encode a base64 string + */ constexpr std::size_t encodedSize(std::size_t n) { return 4 * ((n + 2) / 3); } -/// Returns max bytes needed to decode a base64 string +/** + * Returns max bytes needed to decode a base64 string + */ constexpr std::size_t decodedSize(std::size_t n) { return ((n / 4) * 3) + 2; } -/** Encode a series of octets as a padded, base64 string. - - The resulting string will not be null terminated. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `encoded_size(len)` bytes. - - @return The number of characters written to `out`. This - will exclude any null termination. -*/ +/** + * Encode a series of octets as a padded, base64 string. + * + * The resulting string will not be null terminated. + * + * @par Requires + * + * The memory pointed to by `out` points to valid memory + * of at least `encoded_size(len)` bytes. + * + * @return The number of characters written to `out`. This + * will exclude any null termination. + */ std::size_t encode(void* dest, void const* src, std::size_t len) { @@ -142,17 +147,18 @@ encode(void* dest, void const* src, std::size_t len) return out - static_cast(dest); } -/** Decode a padded base64 string into a series of octets. - - @par Requires - - The memory pointed to by `out` points to valid memory - of at least `decoded_size(len)` bytes. - - @return The number of octets written to `out`, and - the number of characters read from the input string, - expressed as a pair. -*/ +/** + * Decode a padded base64 string into a series of octets. + * + * @par Requires + * + * The memory pointed to by `out` points to valid memory + * of at least `decoded_size(len)` bytes. + * + * @return The number of octets written to `out`, and + * the number of characters read from the input string, + * expressed as a pair. + */ std::pair decode(void* dest, char const* src, std::size_t len) { diff --git a/src/libxrpl/basics/make_SSLContext.cpp b/src/libxrpl/basics/make_SSLContext.cpp index 165d36076b..3fca0c0d77 100644 --- a/src/libxrpl/basics/make_SSLContext.cpp +++ b/src/libxrpl/basics/make_SSLContext.cpp @@ -30,35 +30,37 @@ namespace xrpl { namespace openssl::detail { -/** The default strength of self-signed RSA certificates. - - Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still - considered acceptably secure. Generally, we would want to go above - and beyond such recommendations (e.g. by using 3072 or 4096 bits) - but there is a computational cost associated with that may not - be worth paying, considering that: - - - We regenerate a new ephemeral certificate and a securely generated - random private key every time the server is started; and - - There should not be any truly secure information (e.g. seeds or private - keys) that gets relayed to the server anyways over these RPCs. - - @note If you increase the number of bits you need to generate new - default DH parameters and update defaultDH accordingly. - * */ +/** + * The default strength of self-signed RSA certificates. + * + * Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still + * considered acceptably secure. Generally, we would want to go above + * and beyond such recommendations (e.g. by using 3072 or 4096 bits) + * but there is a computational cost associated with that may not + * be worth paying, considering that: + * + * - We regenerate a new ephemeral certificate and a securely generated + * random private key every time the server is started; and + * - There should not be any truly secure information (e.g. seeds or private + * keys) that gets relayed to the server anyways over these RPCs. + * + * @note If you increase the number of bits you need to generate new + * default DH parameters and update defaultDH accordingly. + */ int gDefaultRsaKeyBits = 2048; -/** The default DH parameters. - - These were generated using the OpenSSL command: `openssl dhparam 2048` - by Nik Bougalis on May, 29, 2022. - - It is safe to use this, but if you want you can generate different - parameters and put them here. There's no easy way to change this - via the config file at this time. - - @note If you increase the number of bits you need to update - defaultRSAKeyBits accordingly. +/** + * The default DH parameters. + * + * These were generated using the OpenSSL command: `openssl dhparam 2048` + * by Nik Bougalis on May, 29, 2022. + * + * It is safe to use this, but if you want you can generate different + * parameters and put them here. There's no easy way to change this + * via the config file at this time. + * + * @note If you increase the number of bits you need to update + * defaultRSAKeyBits accordingly. */ static constexpr char kDefaultDh[] = "-----BEGIN DH PARAMETERS-----\n" @@ -70,19 +72,20 @@ static constexpr char kDefaultDh[] = "9yqY3xXZID240RRcaJ25+U4lszFPqP+CEwIBAg==\n" "-----END DH PARAMETERS-----"; -/** The default list of ciphers we accept over TLS. - - Generally we include cipher suites that are part of TLS v1.2, but - we specifically exclude: - - - the DSS cipher suites (!DSS); - - cipher suites using pre-shared keys (!PSK); - - cipher suites that don't offer encryption (!eNULL); and - - cipher suites that don't offer authentication (!aNULL). - - @note Server administrators can override this default list, on either a - global or per-port basis, using the `ssl_ciphers` directive in the - config file. +/** + * The default list of ciphers we accept over TLS. + * + * Generally we include cipher suites that are part of TLS v1.2, but + * we specifically exclude: + * + * - the DSS cipher suites (!DSS); + * - cipher suites using pre-shared keys (!PSK); + * - cipher suites that don't offer encryption (!eNULL); and + * - cipher suites that don't offer authentication (!aNULL). + * + * @note Server administrators can override this default list, on either a + * global or per-port basis, using the `ssl_ciphers` directive in the + * config file. */ std::string const kDefaultCipherList = "TLSv1.2:!CBC:!DSS:!PSK:!eNULL:!aNULL"; diff --git a/src/libxrpl/core/detail/LoadMonitor.cpp b/src/libxrpl/core/detail/LoadMonitor.cpp index 95e0e7d3b9..92eff61285 100644 --- a/src/libxrpl/core/detail/LoadMonitor.cpp +++ b/src/libxrpl/core/detail/LoadMonitor.cpp @@ -101,10 +101,11 @@ LoadMonitor::addLoadSample(LoadEvent const& s) addSamples(1, latency); } -/* Add multiple samples - @param count The number of samples to add - @param latencyMS The total number of milliseconds -*/ +/** + * Add multiple samples + * @param count The number of samples to add + * @param latencyMS The total number of milliseconds + */ void LoadMonitor::addSamples(int count, std::chrono::milliseconds latency) { diff --git a/src/libxrpl/core/detail/Workers.cpp b/src/libxrpl/core/detail/Workers.cpp index 0d9c1afd26..abbbb2f25c 100644 --- a/src/libxrpl/core/detail/Workers.cpp +++ b/src/libxrpl/core/detail/Workers.cpp @@ -127,15 +127,11 @@ Workers::deleteWorkers(beast::LockFreeStack& stack) { Worker const* const worker = stack.popFront(); - if (worker != nullptr) - { - // This call blocks until the thread orderly exits - delete worker; - } - else - { + if (worker == nullptr) break; - } + + // This call blocks until the thread orderly exits + delete worker; } } diff --git a/src/libxrpl/crypto/RFC1751.cpp b/src/libxrpl/crypto/RFC1751.cpp index 41e29ee00c..4b17e1443c 100644 --- a/src/libxrpl/crypto/RFC1751.cpp +++ b/src/libxrpl/crypto/RFC1751.cpp @@ -379,14 +379,15 @@ RFC1751::etob(std::string& strData, std::vector vsHuman) return 1; } -/** Convert words separated by spaces into a 128 bit key in big-endian format. - - @return - 1 if succeeded - 0 if word not in dictionary - -1 if badly formed string - -2 if words are okay but parity is wrong. -*/ +/** + * Convert words separated by spaces into a 128 bit key in big-endian format. + * + * @return + * 1 if succeeded + * 0 if word not in dictionary + * -1 if badly formed string + * -2 if words are okay but parity is wrong. + */ int RFC1751::getKeyFromEnglish(std::string& strKey, std::string const& strHuman) { @@ -415,7 +416,8 @@ RFC1751::getKeyFromEnglish(std::string& strKey, std::string const& strHuman) return rc; } -/** Convert to human from a 128 bit key in big-endian format +/** + * Convert to human from a 128 bit key in big-endian format */ void RFC1751::getEnglishFromKey(std::string& strHuman, std::string const& strKey) diff --git a/src/libxrpl/json/Writer.cpp b/src/libxrpl/json/Writer.cpp index b21013cda7..4c922a0e33 100644 --- a/src/libxrpl/json/Writer.cpp +++ b/src/libxrpl/json/Writer.cpp @@ -199,15 +199,21 @@ private: // JSON collections are either arrays, or objects. struct Collection { - /** What type of collection are we in? */ + /** + * What type of collection are we in? + */ Writer::CollectionType type = Writer::CollectionType::Array; - /** Is this the first entry in a collection? - * If false, we have to emit a , before we write the next entry. */ + /** + * Is this the first entry in a collection? + * If false, we have to emit a , before we write the next entry. + */ bool isFirst = true; #ifndef NDEBUG - /** What tags have we already seen in this collection? */ + /** + * What tags have we already seen in this collection? + */ std::set tags{}; // NOLINT(readability-redundant-member-init) #endif }; diff --git a/src/libxrpl/json/json_value.cpp b/src/libxrpl/json/json_value.cpp index ce208418de..e7ebb04495 100644 --- a/src/libxrpl/json/json_value.cpp +++ b/src/libxrpl/json/json_value.cpp @@ -174,7 +174,8 @@ Value::CZString::isStaticString() const // ////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////// -/*! \internal Default constructor initialization must be equivalent to: +/** + * @internal Default constructor initialization must be equivalent to: * memset( this, 0, sizeof(Value) ) * This optimization is used in ValueInternalMap fast allocator. */ @@ -313,8 +314,7 @@ Value::~Value() case ValueType::Array: case ValueType::Object: - if (value_.mapVal != nullptr) - delete value_.mapVal; + delete value_.mapVal; break; // LCOV_EXCL_START @@ -794,7 +794,9 @@ Value::isConvertibleTo(ValueType other) const return false; // unreachable; } -/// Number of values in array or object +/** + * Number of values in array or object + */ Value::UInt Value::size() const { diff --git a/src/libxrpl/ledger/PaymentSandbox.cpp b/src/libxrpl/ledger/PaymentSandbox.cpp index 97e3e53cbf..a7a8962095 100644 --- a/src/libxrpl/ledger/PaymentSandbox.cpp +++ b/src/libxrpl/ledger/PaymentSandbox.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -170,7 +171,7 @@ DeferredCredits::issuerSelfDebitMPT( } void -DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next) +DeferredCredits::ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next) { auto const v = std::max(cur, next); auto r = ownerCounts_.emplace(id, v); @@ -181,7 +182,7 @@ DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_ } } -std::optional +std::optional DeferredCredits::ownerCount(AccountID const& id) const { auto i = ownerCounts_.find(id); @@ -391,10 +392,10 @@ PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_ return STAmount{issue}; } -std::uint32_t -PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const +OwnerCounts +PaymentSandbox::ownerCountHook(AccountID const& account, OwnerCounts const& count) const { - std::uint32_t result = count; + OwnerCounts result = count; for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_) { if (auto adj = curSB->tab_.ownerCount(account)) @@ -442,8 +443,8 @@ PaymentSandbox::issuerSelfDebitHookMPT( void PaymentSandbox::adjustOwnerCountHook( AccountID const& account, - std::uint32_t cur, - std::uint32_t next) + OwnerCounts const& cur, + OwnerCounts const& next) { tab_.ownerCount(account, cur, next); } diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index ebac76a754..8116f4f641 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -431,8 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx) TER doWithdraw( - ApplyView& view, - STTx const& tx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, @@ -440,23 +440,24 @@ doWithdraw( STAmount const& amount, beast::Journal j) { + auto const dstSle = ctx.view.read(keylet::account(dstAcct)); + // Create trust line or MPToken for the receiving account if (dstAcct == senderAcct) { - if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j); + if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j); !isTesSuccess(ter) && ter != tecDUPLICATE) return ter; } else { - auto dstSle = view.read(keylet::account(dstAcct)); - if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j)) + if (auto err = verifyDepositPreauth(ctx.tx, ctx.view, senderAcct, dstAcct, dstSle, j)) return err; } // Sanity check if (accountHolds( - view, + ctx.view, sourceAcct, amount.asset(), FreezeHandling::IgnoreFreeze, @@ -469,9 +470,18 @@ doWithdraw( // LCOV_EXCL_STOP } + // A reserve sponsor only covers tx.Account's own objects, so resolve the + // sponsor against the destination. accountSend can auto-create a holding + // for dstAcct; keying on the destination ensures a third-party destination's + // holding is never stamped with the tx's reserve sponsor. + auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, dstSle); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + // Move the funds directly from the broker's pseudo-account to the // dstAcct - return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes); + return accountSend( + ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes); } TER diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 1c4acc7bc4..faca4ebfb6 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,46 +44,226 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer) return false; } +namespace { + // An owner count cannot be negative. If adjustment would cause a negative // owner count, clamp the owner count at 0. Similarly for overflow. This // adjustment allows the ownerCount to be adjusted up or down in multiple steps. // If id != std::nullopt, then do error reporting. // // Returns adjusted owner count. -static std::uint32_t +std::uint32_t confineOwnerCount( - std::uint32_t current, - std::int32_t adjustment, + std::uint32_t currentOwnerCount, + std::int32_t ownerCountAdj, std::optional const& id = std::nullopt, beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) { - std::uint32_t adjusted{current + adjustment}; - if (adjustment > 0) + std::uint32_t totalOwnerCount{currentOwnerCount + ownerCountAdj}; + if (ownerCountAdj > 0) { // Overflow is well defined on unsigned - if (adjusted < current) + if (totalOwnerCount < currentOwnerCount) { + // LCOV_EXCL_START if (id) { JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!"; } - adjusted = std::numeric_limits::max(); + totalOwnerCount = std::numeric_limits::max(); + // LCOV_EXCL_STOP } } else { // Underflow is well defined on unsigned - if (adjusted > current) + if (totalOwnerCount > currentOwnerCount) { + // LCOV_EXCL_START if (id) { JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!"; } - adjusted = 0; + totalOwnerCount = 0; XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set"); + // LCOV_EXCL_STOP } } - return adjusted; + return totalOwnerCount; +} + +// Returns the number of account reserves funded by this account: 1 for itself (0 if sponsored by +// another account) plus the count of accounts it sponsors. +std::uint32_t +accountCountImpl(SLE::const_ref sle, std::int32_t accountCountAdj, beast::Journal j) +{ + bool const isSponsored = sle->isFieldPresent(sfSponsor); + std::int64_t const sponsoringAccountCount = sle->getFieldU32(sfSponsoringAccountCount); + std::int64_t const currentAccountCount = (isSponsored ? 0 : 1) + sponsoringAccountCount; + + std::int64_t totalAccountCount{currentAccountCount + accountCountAdj}; + if (totalAccountCount > std::numeric_limits::max()) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Reserve count exceeds max!"; + totalAccountCount = std::numeric_limits::max(); + // LCOV_EXCL_STOP + } + else if (totalAccountCount < 0) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::accountCountImpl : Reserve count set below 0"); + JLOG(j.fatal()) << "Reserve count set below 0"; + totalAccountCount = 0; + // LCOV_EXCL_STOP + } + + return totalAccountCount; +} + +std::uint32_t +adjustOwnerCountImpl( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& sfield, + AccountID const& accID, + std::int32_t ownerCountAdj, + beast::Journal j) +{ + std::uint32_t const currentOwnerCount = sle->at(sfield); + std::uint32_t const totalOwnerCount = + confineOwnerCount(currentOwnerCount, ownerCountAdj, accID, j); + sle->at(sfield) = totalOwnerCount; + view.update(sle); + return totalOwnerCount; +} + +void +adjustOwnerCountSigned( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::int32_t adjustment, + beast::Journal j) +{ + if (view.rules().enabled(featureSponsor)) + { + XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle"); + if (!accountSle) + return; // LCOV_EXCL_LINE + + auto const accountID = accountSle->getAccountID(sfAccount); + bool const validType = accountSle->getType() == ltACCOUNT_ROOT; + XRPL_ASSERT(validType, "xrpl::adjustOwnerCountSigned : valid account sle type"); + if (!validType) + return; // LCOV_EXCL_LINE + + XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCountSigned : nonzero adjustment input"); + + OwnerCounts const currentOwnerCount(accountSle); + OwnerCounts totalOwnerCount(currentOwnerCount); + + if (sponsorSle) + { + bool const validSponsorType = sponsorSle->getType() == ltACCOUNT_ROOT; + XRPL_ASSERT(validSponsorType, "xrpl::adjustOwnerCountSigned : valid sponsor sle type"); + if (!validSponsorType) + return; // LCOV_EXCL_LINE + auto const sponsorID = sponsorSle->getAccountID(sfAccount); + + totalOwnerCount.sponsored = adjustOwnerCountImpl( + view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j); + + { + OwnerCounts const sponsorCurrent(sponsorSle); + OwnerCounts sponsorAdjustment(sponsorCurrent); + sponsorAdjustment.sponsoring = adjustOwnerCountImpl( + view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j); + view.adjustOwnerCountHook(sponsorID, sponsorCurrent, sponsorAdjustment); + } + + auto sponsorshipSle = view.peek(keylet::sponsorship(sponsorID, accountID)); + if (sponsorshipSle && adjustment > 0) + { + // Only decrease the pre-funded ReserveCount on Sponsorship if we assign new + // objects. Removing/reassigning ownership of the object doesn't increase + // RemainingOwnerCount back. Don't call hook because this counter is not something + // that requires reserve (like other sf...OwnerCounts do). + adjustOwnerCountImpl( + view, sponsorshipSle, sfRemainingOwnerCount, sponsorID, -adjustment, j); + } + } + + totalOwnerCount.owner = + adjustOwnerCountImpl(view, accountSle, sfOwnerCount, accountID, adjustment, j); + view.adjustOwnerCountHook(accountID, currentOwnerCount, totalOwnerCount); + } + else + { + XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle"); + if (!accountSle) + return; + // the remaining are only asserts to preserve existing behavior + XRPL_ASSERT(sponsorSle == nullptr, "xrpl::adjustOwnerCountSigned : sponsor not enabled"); + XRPL_ASSERT( + accountSle->getType() == ltACCOUNT_ROOT, + "xrpl::adjustOwnerCountSigned : valid account sle type"); + XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input"); + std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)}; + AccountID const id = (*accountSle)[sfAccount]; + std::uint32_t const adjusted = confineOwnerCount(current, adjustment, id, j); + + OwnerCounts const currentOwnerCount(accountSle); + OwnerCounts finalOwnerCount(currentOwnerCount); + finalOwnerCount.owner = adjusted; + + view.adjustOwnerCountHook(id, currentOwnerCount, finalOwnerCount); + accountSle->at(sfOwnerCount) = adjusted; + view.update(accountSle); + } +} + +} // namespace + +std::uint32_t +ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj) +{ + XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::ownerCount : sle is account root"); + + AccountID const id = sle->getAccountID(sfAccount); + std::uint32_t const currentOwnerCount = sle->at(sfOwnerCount); + std::uint32_t const sponsoredOwnerCount = sle->at(sfSponsoredOwnerCount); + std::uint32_t const sponsoringOwnerCount = sle->at(sfSponsoringOwnerCount); + + XRPL_ASSERT( + currentOwnerCount >= sponsoredOwnerCount, + "xrpl::ownerCount : OwnerCount must be greater than or equal to SponsoredOwnerCount"); + + std::int64_t deltaCount = + static_cast(ownerCountAdj) - sponsoredOwnerCount + sponsoringOwnerCount; + + if (deltaCount > std::numeric_limits::max()) + { + // LCOV_EXCL_START + deltaCount = std::numeric_limits::max(); + JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, " + << "adjustment: " << ownerCountAdj + << ", sponsoredCount: " << sponsoredOwnerCount + << ", sponsoringOwnerCount: " << sponsoringOwnerCount; + // LCOV_EXCL_STOP + } + else if (deltaCount < std::numeric_limits::min()) + { + // LCOV_EXCL_START + deltaCount = std::numeric_limits::min(); + JLOG(j.fatal()) << "Account " << id << " delta count is below min, " + << "adjustment: " << ownerCountAdj + << ", sponsoredCount: " << sponsoredOwnerCount + << ", sponsoringCount: " << sponsoringOwnerCount; + // LCOV_EXCL_STOP + } + + return confineOwnerCount(currentOwnerCount, deltaCount); } XRPAmount @@ -91,12 +274,14 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, return beast::kZero; // Return balance minus reserve - std::uint32_t const ownerCount = - confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj); + std::uint32_t const currentOwnerCount = + confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle)).count(), ownerCountAdj); + std::uint32_t const currentAccountCount = accountCountImpl(sle, 0, j); // Pseudo-accounts have no reserve requirement - auto const reserve = - isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount); + auto const reserve = isPseudoAccount(sle) + ? XRPAmount{0} + : view.fees().accountReserve(currentOwnerCount, currentAccountCount); auto const fullBalance = sle->getFieldAmount(sfBalance); @@ -108,7 +293,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, << " amount=" << amount.getFullText() << " fullBalance=" << fullBalance.getFullText() << " balance=" << balance.getFullText() << " reserve=" << reserve - << " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj; + << " ownerCount=" << currentOwnerCount << " ownerCountAdj=" << ownerCountAdj; return amount.xrp(); } @@ -125,19 +310,193 @@ transferRate(ReadView const& view, AccountID const& issuer) } void -adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j) +increaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j) { - if (!sle) - return; - XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input"); - std::uint32_t const current{sle->getFieldU32(sfOwnerCount)}; - AccountID const id = (*sle)[sfAccount]; - std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); - view.adjustOwnerCountHook(id, current, adjusted); - sle->at(sfOwnerCount) = adjusted; - view.update(sle); + XRPL_ASSERT( + count != 0 && count <= std::numeric_limits::max(), + "xrpl::increaseOwnerCount : count in signed delta range"); + if (count == 0 || count > std::numeric_limits::max()) + return; // LCOV_EXCL_LINE + + adjustOwnerCountSigned(view, accountSle, sponsorSle, static_cast(count), j); } +void +increaseOwnerCount(ApplyViewContext ctx, SLE::ref accountSle, std::uint32_t count, beast::Journal j) +{ + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accountSle); + + // The sponsor's existence is validated by checkReserve/checkSponsor before + // any owner-count mutation, so loading it here cannot fail. + XRPL_ASSERT( + sponsorExp.has_value(), "xrpl::increaseOwnerCount : sponsor validated before mutation"); + + increaseOwnerCount(ctx.view, accountSle, sponsorExp ? *sponsorExp : SLE::pointer(), count, j); +} + +void +decreaseOwnerCount( + ApplyView& view, + SLE::ref accountSle, + SLE::ref sponsorSle, + std::uint32_t count, + beast::Journal j) +{ + XRPL_ASSERT( + count != 0 && count <= std::numeric_limits::max(), + "xrpl::decreaseOwnerCount : count in signed delta range"); + if (count == 0 || count > std::numeric_limits::max()) + return; // LCOV_EXCL_LINE + + adjustOwnerCountSigned(view, accountSle, sponsorSle, -static_cast(count), j); +} + +void +decreaseOwnerCountForObject( + ApplyView& view, + SLE::ref accountSle, + SLE::ref objectSle, + std::uint32_t count, + beast::Journal j) +{ + XRPL_ASSERT(objectSle, "xrpl::decreaseOwnerCountForObject : valid object sle"); + if (!objectSle) + return; // LCOV_EXCL_LINE + + bool const validObjectType = objectSle->getType() != ltACCOUNT_ROOT; + XRPL_ASSERT(validObjectType, "xrpl::decreaseOwnerCountForObject : valid object sle type"); + if (!validObjectType) + return; // LCOV_EXCL_LINE + + SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle); + decreaseOwnerCount(view, accountSle, sponsorSle, count, j); +} + +void +adjustLoanBrokerOwnerCount( + ApplyView& view, + SLE::ref brokerSle, + std::int32_t delta, + beast::Journal j) +{ + XRPL_ASSERT( + brokerSle && brokerSle->getType() == ltLOAN_BROKER, + "xrpl::adjustLoanBrokerOwnerCount : valid loan broker sle"); + if (!brokerSle || brokerSle->getType() != ltLOAN_BROKER) + return; // LCOV_EXCL_LINE + + XRPL_ASSERT(delta != 0, "xrpl::adjustLoanBrokerOwnerCount : nonzero delta input"); + if (delta == 0) + return; // LCOV_EXCL_LINE + + adjustOwnerCountImpl( + view, brokerSle, sfOwnerCount, brokerSle->getAccountID(sfAccount), delta, j); +} + +XRPAmount +accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj) +{ + XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::accountReserve : valid sle"); + + if (!view.rules().enabled(featureSponsor)) + { + XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::accountReserve : no account count delta"); + return view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1); + } + std::uint32_t const currentOwnerCount = ownerCount(sle, j, adj.ownerCountDelta); + std::uint32_t const currentAccountCount = accountCountImpl(sle, adj.accountCountDelta, j); + + return view.fees().accountReserve(currentOwnerCount, currentAccountCount); +} + +TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + SLE::const_ref sponsorSle, + Adjustment adj, + beast::Journal j, + TER insufReserveCode) +{ + // TODO: swap to assert after fixCleanup3_2_0 is retired + if (!accSle || accSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + XRPL_ASSERT( + !isTesSuccess(insufReserveCode), "xrpl::checkReserve : insufReserveCode is not tesSUCCESS"); + if (ctx.view.rules().enabled(featureSponsor)) + { + if (sponsorSle) + { + if (sponsorSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sle = ctx.view.read( + keylet::sponsorship( + sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount))); + + // A reserve-sponsored tx must carry a sponsor signature + // (cosigning path) and/or have a pre-existing sponsorship SLE + // (prefunded path). Absence of both is an internal invariant break. + if (isReserveSponsored(ctx.tx) && !sle && !ctx.tx.isFieldPresent(sfSponsorSignature)) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (sle) + { + auto const ownerCountAllowed = sle->getFieldU32(sfRemainingOwnerCount); + if (adj.ownerCountDelta > 0 && + ownerCountAllowed < static_cast(adj.ownerCountDelta)) + return insufReserveCode; + } + + auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance).xrp(); + XRPAmount const sponsorReserve = accountReserve(ctx.view, sponsorSle, j, adj); + + if (sponsorBalance < sponsorReserve) + return insufReserveCode; + } + else + { + XRPAmount const reserve = accountReserve(ctx.view, accSle, j, adj); + if (accBalance < reserve) + return insufReserveCode; + } + } + else + { + XRPL_ASSERT( + !sponsorSle, + "xrpl::checkReserve : featureSponsor disabled and sponsorSle not provided"); + XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::checkReserve : accountCountDelta is 0"); + auto const reserve = ctx.view.fees().accountReserve( + accSle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1); + if (accBalance < reserve) + return insufReserveCode; + } + return tesSUCCESS; +} + +TER +checkReserve( + ApplyViewContext ctx, + SLE::const_ref accSle, + XRPAmount accBalance, + Adjustment adj, + beast::Journal j) +{ + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accSle); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + return checkReserve(ctx, accSle, accBalance, *sponsorExp, adj, j); +} + +// ---------------------------------------------------- + AccountID pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey) { diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 6fc2faf03e..226ea100e9 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j) } if (isOwner) - adjustOwnerCount(view, sleAccount, -1, j); + decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j); return tesSUCCESS; }; diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 3c4e55a16e..6fe7328fa7 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -80,13 +81,9 @@ isAnyFrozen( return true; } - for (auto const& account : accounts) - { - if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth)) - return true; - } - - return false; + return std::ranges::any_of(accounts, [&](auto const& account) { + return isVaultPseudoAccountFrozen(view, account, mptIssue, depth); + }); } Rate @@ -125,29 +122,29 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue) [[nodiscard]] TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, beast::Journal journal) { auto const& mptID = mptIssue.getMptID(); - auto const mpt = view.peek(keylet::mptokenIssuance(mptID)); + auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID)); if (!mpt) return tefINTERNAL; // LCOV_EXCL_LINE if (mpt->isFlag(lsfMPTLocked)) return tefINTERNAL; // LCOV_EXCL_LINE - if (view.peek(keylet::mptoken(mptID, accountID))) + if (ctx.view.peek(keylet::mptoken(mptID, accountID))) return tecDUPLICATE; if (accountID == mptIssue.getIssuer()) return tesSUCCESS; - return authorizeMPToken(view, priorBalance, mptID, accountID, journal); + return authorizeMPToken(ctx, priorBalance, mptID, accountID, journal); } [[nodiscard]] TER authorizeMPToken( - ApplyView& view, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -155,7 +152,7 @@ authorizeMPToken( std::uint32_t flags, std::optional holderID) { - auto const sleAcct = view.peek(keylet::account(account)); + auto const sleAcct = ctx.view.peek(keylet::account(account)); if (!sleAcct) return tecINTERNAL; // LCOV_EXCL_LINE @@ -170,19 +167,19 @@ authorizeMPToken( if ((flags & tfMPTUnauthorize) != 0u) { auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); - auto const sleMpt = view.peek(mptokenKey); + auto const sleMpt = ctx.view.peek(mptokenKey); if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 || - (view.rules().enabled(fixCleanup3_1_3) && + (ctx.view.rules().enabled(fixCleanup3_1_3) && (*sleMpt)[~sfLockedAmount].valueOr(0) != 0)) return tecINTERNAL; // LCOV_EXCL_LINE - if (!view.dirRemove( + if (!ctx.view.dirRemove( keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false)) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleAcct, -1, journal); + decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal); - view.erase(sleMpt); + ctx.view.erase(sleMpt); return tesSUCCESS; } @@ -190,47 +187,57 @@ authorizeMPToken( // - add the new mptokenKey to the owner directory // - create the MPToken object for the holder + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + // The reserve that is required to create the MPToken. Note // that although the reserve increases with every item // an account owns, in the case of MPTokens we only // *enforce* a reserve if the user owns more than two // items. This is similar to the reserve requirements of trust lines. - std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); - XRPAmount const reserveCreate( - (uOwnerCount < 2) ? XRPAmount(beast::kZero) - : view.fees().accountReserve(uOwnerCount + 1)); - - if (priorBalance < reserveCreate) - return tecINSUFFICIENT_RESERVE; + // The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on + // the tx — the sponsor must always cover the reserve (via balance or prefunded + // budget), so this check always runs for sponsored transactions. + if (sponsorSle || ownerCount(sleAcct, journal) >= 2) + { + if (auto const ret = checkReserve( + ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return ret; + } // Defensive check before we attempt to create MPToken for the issuer - auto const mpt = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!mpt || mpt->getAccountID(sfIssuer) == account) { // LCOV_EXCL_START UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token"); - if (view.rules().enabled(featureLendingProtocol)) + if (ctx.view.rules().enabled(featureLendingProtocol)) return tecINTERNAL; // LCOV_EXCL_STOP } auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); auto mptoken = std::make_shared(mptokenKey); - if (auto ter = dirLink(view, account, mptoken)) + if (auto ter = dirLink(ctx.view, account, mptoken)) return ter; // LCOV_EXCL_LINE (*mptoken)[sfAccount] = account; (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; (*mptoken)[sfFlags] = 0; - view.insert(mptoken); + ctx.view.insert(mptoken); // Update owner count. - adjustOwnerCount(view, sleAcct, 1, journal); + increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal); + addSponsorToLedgerEntry(mptoken, sponsorSle); return tesSUCCESS; } - auto const sleMptIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!sleMptIssuance) return tecINTERNAL; // LCOV_EXCL_LINE @@ -240,7 +247,7 @@ authorizeMPToken( if (account != (*sleMptIssuance)[sfIssuer]) return tecINTERNAL; // LCOV_EXCL_LINE - auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID)); + auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID)); if (!sleMpt) return tecINTERNAL; // LCOV_EXCL_LINE @@ -263,13 +270,13 @@ authorizeMPToken( if (flagsIn != flagsOut) sleMpt->setFieldU32(sfFlags, flagsOut); - view.update(sleMpt); + ctx.view.update(sleMpt); return tesSUCCESS; } [[nodiscard]] TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -279,7 +286,7 @@ removeEmptyHolding( // a token does exist, it will get deleted. If not, return success. bool const accountIsIssuer = accountID == mptIssue.getIssuer(); auto const& mptID = mptIssue.getMptID(); - auto const mptoken = view.peek(keylet::mptoken(mptID, accountID)); + auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID)); if (!mptoken) return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND; // Unlike a trust line, if the account is the issuer, and the token has a @@ -287,7 +294,7 @@ removeEmptyHolding( // accounting out of balance, so fail. Since this should be impossible // anyway, I'm not going to put any effort into it. if (mptoken->at(sfMPTAmount) != 0 || - (view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0)) + (ctx.view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0)) return tecHAS_OBLIGATIONS; // Don't delete if the token still has confidential balances @@ -300,7 +307,7 @@ removeEmptyHolding( } return authorizeMPToken( - view, + ctx, {}, // priorBalance mptID, accountID, @@ -419,13 +426,13 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyView& view, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization beast::Journal j) { - auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID)); + auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID)); if (!sleIssuance) return tefINTERNAL; // LCOV_EXCL_LINE @@ -437,7 +444,7 @@ enforceMPTokenAuthorization( return tefINTERNAL; // LCOV_EXCL_LINE auto const keylet = keylet::mptoken(mptIssuanceID, account); - auto const sleToken = view.read(keylet); // NOTE: might be null + auto const sleToken = ctx.view.read(keylet); // NOTE: might be null auto const maybeDomainID = sleIssuance->at(~sfDomainID); bool expired = false; bool const authorizedByDomain = [&]() -> bool { @@ -445,7 +452,7 @@ enforceMPTokenAuthorization( if (!maybeDomainID.has_value()) return false; // LCOV_EXCL_LINE - auto const ter = verifyValidDomain(view, account, *maybeDomainID, j); + auto const ter = verifyValidDomain(ctx.view, account, *maybeDomainID, j); if (isTesSuccess(ter)) return true; if (ter == tecEXPIRED) @@ -500,7 +507,7 @@ enforceMPTokenAuthorization( maybeDomainID.has_value() && sleToken == nullptr, "xrpl::enforceMPTokenAuthorization : new MPToken for domain"); if (auto const err = authorizeMPToken( - view, + ctx, priorBalance, // priorBalance mptIssuanceID, // mptIssuanceID account, // account @@ -915,6 +922,7 @@ createMPToken( ApplyView& view, MPTID const& mptIssuanceID, AccountID const& account, + SLE::ref sponsorSle, std::uint32_t const flags) { auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); @@ -931,6 +939,8 @@ createMPToken( (*mptoken)[sfFlags] = flags; (*mptoken)[sfOwnerNode] = *ownerNode; + addSponsorToLedgerEntry(mptoken, sponsorSle); + view.insert(mptoken); return tesSUCCESS; @@ -941,6 +951,7 @@ checkCreateMPT( xrpl::ApplyView& view, xrpl::MPTIssue const& mptIssue, xrpl::AccountID const& holder, + SLE::ref sponsorSle, beast::Journal j) { if (mptIssue.getIssuer() == holder) @@ -950,7 +961,7 @@ checkCreateMPT( auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder); if (!view.exists(mptokenID)) { - if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0); + if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0); !isTesSuccess(err)) { return err; @@ -960,7 +971,8 @@ checkCreateMPT( { return tecINTERNAL; } - adjustOwnerCount(view, sleAcct, 1, j); + + increaseOwnerCount(view, sleAcct, sponsorSle, 1, j); } return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 6dca715a8c..589e49d335 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -258,7 +258,9 @@ changeTokenURI( return tesSUCCESS; } -/** Insert the token in the owner's token directory. */ +/** + * Insert the token in the owner's token directory. + */ TER insertToken(ApplyView& view, AccountID owner, STObject&& nft) { @@ -269,11 +271,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft) // the NFT. SLE::pointer const page = getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) { - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - 1, - beast::Journal{beast::Journal::getNullSink()}); + increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); }); if (!page) @@ -351,7 +349,9 @@ mergePages(ApplyView& view, SLE::ref p1, SLE::ref p2) return true; } -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) { @@ -364,7 +364,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) return removeToken(view, owner, nftokenID, page); } -/** Remove the token from the owner's token directory. */ +/** + * Remove the token from the owner's token directory. + */ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref curr) { @@ -411,21 +413,17 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->setFieldArray(sfNFTokens, arr); view.update(curr); - int cnt = 0; + std::uint32_t cnt = 0; if (prev && mergePages(view, prev, curr)) - cnt--; + ++cnt; if (next && mergePages(view, curr, next)) - cnt--; + ++cnt; if (cnt != 0) { - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - cnt, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); } return tesSUCCESS; @@ -460,11 +458,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->makeFieldAbsent(sfPreviousPageMin); } - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - -1, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); view.update(curr); view.erase(prev); @@ -502,7 +496,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view.erase(curr); - int cnt = 1; + uint32_t cnt = 1; // Since we're here, try to consolidate the previous and current pages // of the page we removed (if any) into one. mergePages() _should_ @@ -519,11 +513,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view.peek(Keylet(ltNFTOKEN_PAGE, next->key())))) cnt++; - adjustOwnerCount( - view, - view.peek(keylet::account(owner)), - -1 * cnt, - beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); return tesSUCCESS; } @@ -639,8 +629,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer) false)) return false; - adjustOwnerCount( - view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()}); + decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); view.erase(offer); return true; @@ -743,9 +732,11 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink)); if (!newPrev) { + // LCOV_EXCL_START Throw( "NFTokenPage directory for " + to_string(owner) + - " cannot be repaired. Unexpected link problem."); + " cannot be repaired. Unexpected link problem."); + // LCOV_EXCL_STOP } newPrev->at(sfNextPageMin) = nextPage->key(); view.update(newPrev); @@ -931,7 +922,7 @@ tokenOfferCreateApply( { Keylet const acctKeylet = keylet::account(acctID); if (auto const acct = view.read(acctKeylet); - priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) + priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1})) return tecINSUFFICIENT_RESERVE; auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value()); @@ -983,7 +974,7 @@ tokenOfferCreateApply( } // Update owner count. - adjustOwnerCount(view, view.peek(acctKeylet), 1, j); + increaseOwnerCount(view, acctID, {}, 1, j); return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/OfferHelpers.cpp b/src/libxrpl/ledger/helpers/OfferHelpers.cpp index 6e72b71564..caa6ffb8ae 100644 --- a/src/libxrpl/ledger/helpers/OfferHelpers.cpp +++ b/src/libxrpl/ledger/helpers/OfferHelpers.cpp @@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j) } } - adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j); + decreaseOwnerCountForObject(view, owner, sle, 1, j); view.erase(sle); diff --git a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index 03b68e8860..d5a0b25681 100644 --- a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -58,7 +58,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal XRPL_ASSERT( (*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount"); (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance]; - adjustOwnerCount(view, sle, -1, j); + decreaseOwnerCountForObject(view, sle, slep, 1, j); view.update(sle); // Remove PayChan from ledger diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 5a2995c030..868c9fb26d 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,7 @@ trustCreate( // Issuer should be the account being set. std::uint32_t uQualityIn, std::uint32_t uQualityOut, + SLE::ref sponsorSle, beast::Journal j) { JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", " @@ -281,7 +283,9 @@ trustCreate( } sleRippleState->setFieldU32(sfFlags, uFlags); - adjustOwnerCount(view, sleAccount, 1, j); + increaseOwnerCount(view, sleAccount, sponsorSle, 1, j); + + addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor); // ONLY: Create ripple balance. sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); @@ -317,6 +321,9 @@ trustDelete( return tefBAD_LEDGER; // LCOV_EXCL_LINE } + removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor); + removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor); + JLOG(j.trace()) << "trustDelete: Deleting ripple line: state"; view.erase(sleRippleState); @@ -369,11 +376,15 @@ updateTrustLine( { // VFALCO Where is the line being deleted? // Clear the reserve of the sender, possibly delete the line! - adjustOwnerCount(view, sle, -1, j); + auto const currentSponsor = + getLedgerEntryReserveSponsor(view, state, bSenderHigh ? sfHighSponsor : sfLowSponsor); + decreaseOwnerCount(view, sle, currentSponsor, 1, j); // Clear reserve flag. state->clearFlag(senderReserveFlag); + removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor); + // Balance is zero, receiver reserve is clear. if (!after && !state->isFlag(receiverReserveFlag)) return true; @@ -381,12 +392,14 @@ updateTrustLine( return false; } +// Only used in tests TER issueIOU( ApplyView& view, AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j) { XRPL_ASSERT( @@ -472,6 +485,7 @@ issueIOU( limit, 0, 0, + sponsorSle, j); } @@ -620,7 +634,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -632,30 +646,45 @@ addEmptyHolding( auto const& issuerId = issue.getIssuer(); auto const& currency = issue.currency; - if (isGlobalFrozen(view, issuerId)) + if (isGlobalFrozen(ctx.view, issuerId)) return tecFROZEN; // LCOV_EXCL_LINE auto const& srcId = issuerId; auto const& dstId = accountID; auto const high = srcId > dstId; auto const index = keylet::trustLine(srcId, dstId, currency); - auto const sleSrc = view.peek(keylet::account(srcId)); - auto const sleDst = view.peek(keylet::account(dstId)); + auto const sleSrc = ctx.view.peek(keylet::account(srcId)); + auto const sleDst = ctx.view.peek(keylet::account(dstId)); if (!sleDst || !sleSrc) return tefINTERNAL; // LCOV_EXCL_LINE if (!sleSrc->isFlag(lsfDefaultRipple)) return tecINTERNAL; // LCOV_EXCL_LINE // If the line already exists, don't create it again. - if (view.read(index)) + if (ctx.view.read(index)) return tecDUPLICATE; + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleDst); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + // Can the account cover the trust line reserve ? - std::uint32_t const ownerCount = sleDst->at(sfOwnerCount); - if (priorBalance < view.fees().accountReserve(ownerCount + 1)) - return tecNO_LINE_INSUF_RESERVE; + if (auto const ret = checkReserve( + ctx, + sleDst, + priorBalance, + sponsorSle, + {.ownerCountDelta = 1}, + journal, + tecNO_LINE_INSUF_RESERVE); + !isTesSuccess(ret)) + { + return ret; + } return trustCreate( - view, + ctx.view, high, srcId, dstId, @@ -669,19 +698,20 @@ addEmptyHolding( /*saLimit=*/STAmount{Issue{currency, dstId}}, /*uQualityIn=*/0, /*uQualityOut=*/0, + sponsorSle, journal); } TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal) { if (issue.native()) { - auto const sle = view.read(keylet::account(accountID)); + auto const sle = ctx.view.read(keylet::account(accountID)); if (!sle) return tecINTERNAL; // LCOV_EXCL_LINE @@ -696,7 +726,7 @@ removeEmptyHolding( // If the account is the issuer, then no line should exist. Check anyway. // If a line does exist, it will get deleted. If not, return success. bool const accountIsIssuer = accountID == issue.account; - auto const line = view.peek(keylet::trustLine(accountID, issue)); + auto const line = ctx.view.peek(keylet::trustLine(accountID, issue)); if (!line) return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND; if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero) @@ -706,33 +736,43 @@ removeEmptyHolding( if (line->isFlag(lsfLowReserve)) { // Clear reserve for low account. - auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer())); + auto sleLowAccount = ctx.view.peek(keylet::account(line->at(sfLowLimit)->getIssuer())); if (!sleLowAccount) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleLowAccount, -1, journal); + auto const currentLowSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfLowSponsor); + + decreaseOwnerCount(ctx.view, sleLowAccount, currentLowSponsor, 1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. line->clearFlag(lsfLowReserve); + removeSponsorFromLedgerEntry(line, sfLowSponsor); } if (line->isFlag(lsfHighReserve)) { // Clear reserve for high account. - auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer())); + auto sleHighAccount = ctx.view.peek(keylet::account(line->at(sfHighLimit)->getIssuer())); if (!sleHighAccount) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleHighAccount, -1, journal); + auto const currentHighSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfHighSponsor); + + decreaseOwnerCount(ctx.view, sleHighAccount, currentHighSponsor, 1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. line->clearFlag(lsfHighReserve); + removeSponsorFromLedgerEntry(line, sfHighSponsor); } return trustDelete( - view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal); + ctx.view, + line, + line->at(sfLowLimit)->getIssuer(), + line->at(sfHighLimit)->getIssuer(), + journal); } TER @@ -768,6 +808,9 @@ deleteAMMTrustLine( if (ammAccountID && (low != *ammAccountID && high != *ammAccountID)) return terNO_AMM; + auto const sponsorSle = + getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor); + if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter)) { JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline."; @@ -778,7 +821,7 @@ deleteAMMTrustLine( if (!sleState->isFlag(uFlags)) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j); + decreaseOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, 1, j); return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/SponsorHelpers.cpp b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp new file mode 100644 index 0000000000..7e0c041854 --- /dev/null +++ b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp @@ -0,0 +1,345 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +bool +isReserveSponsorAllowed(TxType txType) +{ + // Transaction types explicitly allow-listed for reserve sponsorship, for + // v1. Lazily-initialized function-local static: constructed once on first + // use, with no startup cost paid by clients that never call this. + static std::unordered_set const kReserveSponsorAllowed = { + ttDELEGATE_SET, + ttDEPOSIT_PREAUTH, + ttPAYMENT, + ttSIGNER_LIST_SET, + ttCHECK_CANCEL, + ttCHECK_CASH, + ttCHECK_CREATE, + ttESCROW_CANCEL, + ttESCROW_CREATE, + ttESCROW_FINISH, + ttPAYCHAN_CLAIM, + ttPAYCHAN_CREATE, + ttPAYCHAN_FUND, + ttCLAWBACK, + ttMPTOKEN_AUTHORIZE, + ttMPTOKEN_ISSUANCE_CREATE, + ttMPTOKEN_ISSUANCE_DESTROY, + ttMPTOKEN_ISSUANCE_SET, + ttTRUST_SET, + ttCREDENTIAL_ACCEPT, + ttCREDENTIAL_CREATE, + ttCREDENTIAL_DELETE, + ttACCOUNT_SET, + ttREGULAR_KEY_SET, + ttSPONSORSHIP_TRANSFER, + }; + return kReserveSponsorAllowed.contains(txType); +} + +std::optional +getTxReserveSponsorID(STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx)) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::getTxReserveSponsorID : sponsor exists + Sponsor enabled"); + return tx.getAccountID(sfSponsor); + } + return {}; +} + +std::expected +getTxReserveSponsor(ApplyViewContext ctx) +{ + auto const sponsorID = getTxReserveSponsorID(ctx.tx); + if (sponsorID) + { + XRPL_ASSERT( + ctx.view.rules().enabled(featureSponsor), + "xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled"); + auto sle = ctx.view.peek(keylet::account(*sponsorID)); + + // already checked in Transactor::checkSponsor + if (!sle) + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return sle; + } + return SLE::pointer(); +} + +std::expected +getTxReserveSponsor(ReadView const& view, STTx const& tx) +{ + auto const sponsorID = getTxReserveSponsorID(tx); + if (sponsorID) + { + XRPL_ASSERT( + view.rules().enabled(featureSponsor), + "xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled"); + auto sle = view.read(keylet::account(*sponsorID)); + + // already checked in Transactor::checkSponsor + if (!sle) + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return sle; + } + return SLE::pointer(); +} + +std::expected +getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle) +{ + // A reserve sponsor only covers tx.Account's own objects. + if (ctx.view.rules().enabled(fixCleanup3_2_0)) + { + XRPL_ASSERT( + accountSle && accountSle->getType() == ltACCOUNT_ROOT, + "xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type"); + } + else + { + XRPL_ASSERT( + accountSle && + ((accountSle->getType() == ltACCOUNT_ROOT) || (accountSle->getType() == ltESCROW)), + "xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type"); + } + + if (isPseudoAccount(accountSle) || accountSle->getAccountID(sfAccount) != ctx.tx[sfAccount]) + return SLE::pointer(); + return getTxReserveSponsor(ctx); +} + +std::optional +getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle && + ((sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor))), + "xrpl::getLedgerEntryReserveSponsorID : correct sfield"); + + if (sle->isFieldPresent(field)) + return sle->getAccountID(field); + return {}; +} + +SLE::pointer +getLedgerEntryReserveSponsor(ApplyView& view, SLE::const_ref sle, SF_ACCOUNT const& field) +{ + auto const sponsorID = getLedgerEntryReserveSponsorID(sle, field); + if (sponsorID) + { + XRPL_ASSERT( + view.rules().enabled(featureSponsor), + "xrpl::getLedgerEntryReserveSponsor : sponsor exists + Sponsor enabled"); + return view.peek(keylet::account(*sponsorID)); + } + return {}; +} + +void +addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor), + "addSponsorToLedgerEntry : Invalid field to the LedgerEntry"); + if (sponsorSle) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled"); + sle->setAccountID(field, sponsorSle->getAccountID(sfAccount)); + } +} + +void +addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field) +{ + // getTxReserveSponsor yields a null pointer when the tx is not + // reserve-sponsored, so addSponsorToLedgerEntry becomes a no-op then. The + // error case (tecINTERNAL) is an already-checked invariant; skip stamping. + auto const sponsorSle = getTxReserveSponsor(ctx); + if (sponsorSle && *sponsorSle) + { + XRPL_ASSERT( + ctx.view.rules().enabled(featureSponsor), + "xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled"); + addSponsorToLedgerEntry(sle, *sponsorSle, field); + } +} + +void +removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field) +{ + XRPL_ASSERT( + (sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) || + (sle->getType() != ltRIPPLE_STATE && field == sfSponsor), + "removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry"); + if (sle->isFieldPresent(field)) + { + XRPL_ASSERT( + getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access) + featureSponsor), + "xrpl::removeSponsorFromLedgerEntry : sponsor exists + Sponsor enabled"); + sle->makeFieldAbsent(field); + } +} + +bool +isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account) +{ + switch (sle.getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + return sle.getAccountID(sfAccount) == account; + case ltMPTOKEN_ISSUANCE: + return sle.getAccountID(sfIssuer) == account; + case ltSIGNER_LIST: { + auto const signerList = view.read(keylet::signerList(account)); + if (!signerList) + return false; + return signerList->key() == sle.key(); + } + case ltCREDENTIAL: { + auto const& ownerField = sle.isFlag(lsfAccepted) ? sfSubject : sfIssuer; + return sle.getAccountID(ownerField) == account; + } + case ltRIPPLE_STATE: { + if (sle.isFlag(lsfHighReserve)) + { + auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == account) + return true; + } + if (sle.isFlag(lsfLowReserve)) + { + auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == account) + return true; + } + // Reachable: the sponsee may be a third party or the side of the + // line that holds no reserve (e.g. the issuer). Callers map this + // to tecNO_PERMISSION. + return false; + } + default: + // LCOV_EXCL_START + UNREACHABLE("xrpl::isLedgerEntryOwner : object is not supported by sponsorship."); + return false; + // LCOV_EXCL_STOP + }; +} + +bool +isLedgerEntrySupportedBySponsorship(SLE const& sle) +{ + switch (sle.getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + case ltMPTOKEN_ISSUANCE: + case ltSIGNER_LIST: + case ltCREDENTIAL: + case ltRIPPLE_STATE: + return true; + default: + return false; + }; +} + +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle) +{ + switch (sle.getType()) + { + case ltORACLE: { + return calculateOracleReserve(sle.getFieldArray(sfPriceDataSeries)); + } + // Vaults require 2 owner counts (the vault and a pseudo-account) + case ltVAULT: + return 2; + case ltSIGNER_LIST: { + // Mirror SignerListSet's owner-count accounting so that create and + // delete agree. Modern lists (post-MultiSignReserve) carry the + // lsfOneOwnerCount flag and cost a single owner count. Legacy + // pre-MultiSignReserve lists cost 2 + signer_count owner counts + if (sle.isFlag(lsfOneOwnerCount)) + return 1; + return 2 + static_cast(sle.getFieldArray(sfSignerEntries).size()); + } + case ltACCOUNT_ROOT: + // LCOV_EXCL_START + UNREACHABLE("AccountRoots are not supported by object sponsorship."); + return 0; + // LCOV_EXCL_STOP + default: + return 1; + } +} + +SF_ACCOUNT const& +getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner) +{ + switch (sle.getType()) + { + case ltRIPPLE_STATE: { + if (sle.isFlag(lsfHighReserve)) + { + auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == owner) + return sfHighSponsor; + } + if (sle.isFlag(lsfLowReserve)) + { + auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == owner) + return sfLowSponsor; + } + // LCOV_EXCL_START + UNREACHABLE("xrpl::getLedgerEntrySponsorField : unknown owner for RippleState"); + return sfSponsor; + // LCOV_EXCL_STOP + } + default: + return sfSponsor; + } +} + +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 0ab97a4b60..79e10cdf79 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include +#include #include #include #include @@ -104,12 +106,9 @@ isAnyFrozen( std::initializer_list const& accounts, Issue const& issue) { - for (auto const& account : accounts) - { - if (isFrozen(view, account, issue.currency, issue.account)) - return true; - } - return false; + return std::ranges::any_of(accounts, [&](auto const& account) { + return isFrozen(view, account, issue.currency, issue.account); + }); } bool @@ -571,7 +570,7 @@ canAddHolding(ReadView const& view, Asset const& asset) TER addEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -579,21 +578,21 @@ addEmptyHolding( { return std::visit( [&](TIss const& issue) -> TER { - return addEmptyHolding(view, accountID, priorBalance, issue, journal); + return addEmptyHolding(ctx, accountID, priorBalance, issue, journal); }, asset.value()); } TER removeEmptyHolding( - ApplyView& view, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal) { return std::visit( [&](TIss const& issue) -> TER { - return removeEmptyHolding(view, accountID, issue, journal); + return removeEmptyHolding(ctx, accountID, issue, journal); }, asset.value()); } @@ -647,6 +646,7 @@ directSendNoFeeIOU( AccountID const& uReceiverID, STAmount const& saAmount, bool bCheckIssuer, + SLE::ref sponsorSle, beast::Journal j) { AccountID const& issuer = saAmount.getIssuer(); @@ -717,7 +717,12 @@ directSendNoFeeIOU( // Sender quality out is 0. { // Clear the reserve of the sender, possibly delete the line! - adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j); + auto const currentSponsor = getLedgerEntryReserveSponsor( + view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor); + decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j); + + removeSponsorFromLedgerEntry( + sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor); // Clear reserve flag. sleRippleState->clearFlag(senderReserveFlag); @@ -780,6 +785,7 @@ directSendNoFeeIOU( saReceiverLimit, 0, 0, + sponsorSle, j); } @@ -794,6 +800,7 @@ directSendNoLimitIOU( STAmount const& saAmount, STAmount& saActual, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee) { auto const& issuer = saAmount.getIssuer(); @@ -806,7 +813,8 @@ directSendNoLimitIOU( if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j); + auto const ter = + directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j); if (!isTesSuccess(ter)) return ter; saActual = saAmount; @@ -824,10 +832,12 @@ directSendNoLimitIOU( << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j); + TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j); if (tesSUCCESS == terResult) - terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j); + { + terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j); + } return terResult; } @@ -870,7 +880,8 @@ directSendNoLimitMultiIOU( if (senderID == issuer || receiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j); + if (auto const ter = + directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j); !isTesSuccess(ter)) return ter; actual += amount; @@ -894,14 +905,14 @@ directSendNoLimitMultiIOU( << to_string(receiverID) << " : deliver=" << amount.getFullText() << " cost=" << actual.getFullText(); - if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j)) + if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j)) return terResult; } if (senderID != issuer && takeFromSender) { if (TER const terResult = - directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j)) + directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j)) return terResult; } @@ -915,6 +926,7 @@ accountSendIOU( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee) { if (view.rules().enabled(fixAMMv1_1)) @@ -946,7 +958,8 @@ accountSendIOU( JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); - return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); + return directSendNoLimitIOU( + view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee); } /* XRP send which does not check reserve and can do pure adjustment. @@ -1475,7 +1488,7 @@ directSendNoFee( { return saAmount.asset().visit( [&](Issue const&) { - return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); + return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j); }, [&](MPTIssue const&) { XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer"); @@ -1490,12 +1503,13 @@ accountSend( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + SLE::ref sponsorSle, WaiveTransferFee waiveFee, AllowMPTOverflow allowOverflow) { return saAmount.asset().visit( [&](Issue const&) { - return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee); + return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee); }, [&](MPTIssue const&) { return accountSendMPT( diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index 7f4dca3ed1..23a48a3bf3 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -52,6 +53,7 @@ DatabaseRotatingImp::rotate( // callback finishes. Only then will the archive directory be // deleted. std::shared_ptr oldArchiveBackend; + std::uint64_t copyForwards = 0; { std::scoped_lock const lock(mutex_); @@ -62,11 +64,28 @@ DatabaseRotatingImp::rotate( newArchiveBackendName = archiveBackend_->getName(); writableBackend_ = std::move(newBackend); + + copyForwards = copyForwardCount_.exchange(0, std::memory_order_relaxed); + } + + if (copyForwards > 0) + { + JLOG(j_.warn()) << "Rotating: copied forward " << copyForwards + << " archive-served reads into the writable backend " + "during the rotation window"; } f(newWritableBackendName, newArchiveBackendName); } +void +DatabaseRotatingImp::setRotationInFlight(bool inFlight) +{ + rotationInFlight_.store(inFlight, std::memory_order_release); + JLOG(j_.debug()) << "Rotating: copy-forward on archive reads " + << (inFlight ? "enabled" : "disabled"); +} + std::string DatabaseRotatingImp::getName() const { @@ -177,9 +196,18 @@ DatabaseRotatingImp::fetchNodeObject( writable = writableBackend_; } - // Update writable backend with data from the archive backend - if (duplicate) + // Update writable backend with data from the archive backend. + // While a rotation is in flight, ordinary (duplicate == false) + // reads served by the archive are copied forward too: the + // archive is about to be deleted, and a body canonicalized + // into the cache after the freshen getKeys() snapshot would + // otherwise survive only in RAM once the archive is dropped. + if (duplicate || rotationInFlight_.load(std::memory_order_acquire)) + { + if (!duplicate) + copyForwardCount_.fetch_add(1, std::memory_order_relaxed); writable->store(nodeObject); + } } } diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index e36b13a2e1..0c76cb9938 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -81,7 +81,9 @@ public: { } - /** Returns the number of file descriptors the backend expects to need */ + /** + * Returns the number of file descriptors the backend expects to need + */ [[nodiscard]] int fdRequired() const override { diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index bcf4ba4a49..673b0daae0 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -419,7 +419,9 @@ public: storeBatch(batch); } - /** Returns the number of file descriptors the backend expects to need */ + /** + * Returns the number of file descriptors the backend expects to need + */ [[nodiscard]] int fdRequired() const override { diff --git a/src/libxrpl/protocol/AccountID.cpp b/src/libxrpl/protocol/AccountID.cpp index 6050144a8e..c6a5226566 100644 --- a/src/libxrpl/protocol/AccountID.cpp +++ b/src/libxrpl/protocol/AccountID.cpp @@ -21,7 +21,9 @@ namespace xrpl { namespace detail { -/** Caches the base58 representations of AccountIDs */ +/** + * Caches the base58 representations of AccountIDs + */ class AccountIdCache { private: diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index 059abe2996..2a8476dea8 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -154,7 +154,9 @@ public: uint256 registerFeature(std::string const& name, Supported support, VoteBehavior vote); - /** Tell FeatureCollections when registration is complete. */ + /** + * Tell FeatureCollections when registration is complete. + */ bool registrationIsDone(); @@ -167,30 +169,38 @@ public: std::string featureToName(uint256 const& f) const; - /** All amendments that are registered within the table. */ + /** + * All amendments that are registered within the table. + */ std::map const& allAmendments() const { return all_; } - /** Amendments that this server supports. - Whether they are enabled depends on the Rules defined in the validated - ledger */ + /** + * Amendments that this server supports. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& supportedAmendments() const { return supported_; } - /** Amendments that this server WON'T vote for by default. */ + /** + * Amendments that this server WON'T vote for by default. + */ std::size_t numDownVotedAmendments() const { return downVotes_; } - /** Amendments that this server WILL vote for by default. */ + /** + * Amendments that this server WILL vote for by default. + */ std::size_t numUpVotedAmendments() const { @@ -271,7 +281,9 @@ FeatureCollections::registerFeature(std::string const& name, Supported support, logicError("Duplicate feature registration"); } -/** Tell FeatureCollections when registration is complete. */ +/** + * Tell FeatureCollections when registration is complete. + */ bool FeatureCollections::registrationIsDone() { @@ -313,30 +325,38 @@ FeatureCollections gFeatureCollections; } // namespace -/** All amendments libxrpl knows of. */ +/** + * All amendments libxrpl knows of. + */ std::map const& allAmendments() { return gFeatureCollections.allAmendments(); } -/** Amendments that this server supports. - Whether they are enabled depends on the Rules defined in the validated - ledger */ +/** + * Amendments that this server supports. + * Whether they are enabled depends on the Rules defined in the validated + * ledger + */ std::map const& detail::supportedAmendments() { return gFeatureCollections.supportedAmendments(); } -/** Amendments that this server won't vote for by default. */ +/** + * Amendments that this server won't vote for by default. + */ std::size_t detail::numDownVotedAmendments() { return gFeatureCollections.numDownVotedAmendments(); } -/** Amendments that this server will vote for by default. */ +/** + * Amendments that this server will vote for by default. + */ std::size_t detail::numUpVotedAmendments() { @@ -365,7 +385,9 @@ retireFeature(std::string const& name) return registerFeature(name, Supported::Yes, VoteBehavior::Obsolete); } -/** Tell FeatureCollections when registration is complete. */ +/** + * Tell FeatureCollections when registration is complete. + */ bool registrationIsDone() { diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 76ff868f9e..95416d0f2a 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -32,23 +33,41 @@ namespace xrpl { -/** Type-specific prefix for calculating ledger indices. +// This list should include all of the keylet functions that take a single +// AccountID parameter. Declared in Indexes.h; defined here so the header need +// not include jss.h. +std::array, 6> const kDirectAccountKeylets{ + {{.function = &keylet::account, .expectedLEName = jss::AccountRoot, .includeInTests = false}, + {.function = &keylet::ownerDir, .expectedLEName = jss::DirectoryNode, .includeInTests = true}, + {.function = &keylet::signerList, .expectedLEName = jss::SignerList, .includeInTests = true}, + // It's normally impossible to create an item at nftpage_min, but + // test it anyway, since the invariant checks for it. + {.function = &keylet::nftokenPageMin, + .expectedLEName = jss::NFTokenPage, + .includeInTests = true}, + {.function = &keylet::nftokenPageMax, + .expectedLEName = jss::NFTokenPage, + .includeInTests = true}, + {.function = &keylet::did, .expectedLEName = jss::DID, .includeInTests = true}}}; - The identifier for a given object within the ledger is calculated based - on some object-specific parameters. To ensure that different types of - objects have different indices, even if they happen to use the same set - of parameters, we use "tagged hashing" by adding a type-specific prefix. - - @note These values are part of the protocol and *CANNOT* be arbitrarily - changed. If they were, on-ledger objects may no longer be able to - be located or addressed. - - Additions to this list are OK, but changing existing entries to - assign them a different values should never be needed. - - Entries that are removed should be moved to the bottom of the enum - and marked as [[deprecated]] to prevent accidental reuse. -*/ +/** + * Type-specific prefix for calculating ledger indices. + * + * The identifier for a given object within the ledger is calculated based + * on some object-specific parameters. To ensure that different types of + * objects have different indices, even if they happen to use the same set + * of parameters, we use "tagged hashing" by adding a type-specific prefix. + * + * @note These values are part of the protocol and *CANNOT* be arbitrarily + * changed. If they were, on-ledger objects may no longer be able to + * be located or addressed. + * + * Additions to this list are OK, but changing existing entries to + * assign them a different values should never be needed. + * + * Entries that are removed should be moved to the bottom of the enum + * and marked as [[deprecated]] to prevent accidental reuse. + */ enum class LedgerNameSpace : std::uint16_t { Account = 'a', DirNode = 'd', @@ -84,6 +103,7 @@ enum class LedgerNameSpace : std::uint16_t { Vault = 'V', LoanBroker = 'l', // lower-case L Loan = 'L', + Sponsorship = '>', // No longer used or supported. Left here to reserve the space to avoid accidental reuse. Contract [[deprecated]] = 'c', @@ -326,6 +346,12 @@ signerList(AccountID const& account) noexcept return signerList(account, 0); } +Keylet +sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept +{ + return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)}; +} + Keylet check(AccountID const& id, std::uint32_t seq) noexcept { diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 66b2822a42..0bdb217771 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats() {sfTxnSignature, SoeOptional}, {sfSigners, SoeOptional}, }); + + add(sfSponsorSignature.jsonName.cStr(), + sfSponsorSignature.getCode(), + { + {sfSigningPubKey, SoeOptional}, + {sfTxnSignature, SoeOptional}, + {sfSigners, SoeOptional}, + }); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/LedgerFormats.cpp b/src/libxrpl/protocol/LedgerFormats.cpp index 8b91bb7930..a29ea49266 100644 --- a/src/libxrpl/protocol/LedgerFormats.cpp +++ b/src/libxrpl/protocol/LedgerFormats.cpp @@ -15,6 +15,7 @@ LedgerFormats::getCommonFields() {sfLedgerIndex, SoeOptional}, {sfLedgerEntryType, SoeRequired}, {sfFlags, SoeRequired}, + {sfSponsor, SoeOptional}, }; return kCommonFields; } diff --git a/src/libxrpl/protocol/PublicKey.cpp b/src/libxrpl/protocol/PublicKey.cpp index 97948fcae3..cb6ea9e851 100644 --- a/src/libxrpl/protocol/PublicKey.cpp +++ b/src/libxrpl/protocol/PublicKey.cpp @@ -96,18 +96,19 @@ sliceToHex(Slice const& slice) return s; } -/** Determine whether a signature is canonical. - Canonical signatures are important to protect against signature morphing - attacks. - @param vSig the signature data - @param sigLen the length of the signature - @param strict_param whether to enforce strictly canonical semantics - - @note For more details please see: - https://xrpl.org/transaction-malleability.html - https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 - https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c -*/ +/** + * Determine whether a signature is canonical. + * Canonical signatures are important to protect against signature morphing + * attacks. + * @param vSig the signature data + * @param sigLen the length of the signature + * @param strict_param whether to enforce strictly canonical semantics + * + * @note For more details please see: + * https://xrpl.org/transaction-malleability.html + * https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + * https://github.com/sipa/bitcoin/commit/58bc86e37fda1aec270bccb3df6c20fbd2a6591c + */ std::optional ecdsaCanonicality(Slice const& sig) { diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index e0968ea868..197139027a 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -39,22 +39,30 @@ setCurrentTransactionRules(std::optional r) // Push the appropriate setting, instead of having the class pull every time // the value is needed. That could get expensive fast. - // If any new conditions with new amendments are added, those amendments must also be added to - // useRulesGuards. - bool const enableVaultNumbers = - !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol)); - bool const enableCuspRoundingFix = !r || r->enabled(fixCleanup3_2_0); - XRPL_ASSERT( - !r || useRulesGuards(*r) == (enableCuspRoundingFix || enableVaultNumbers), - "setCurrentTransactionRules : rule decisions match"); - // Declare the range this way to keep clang-tidy from complaining - auto const range = [enableCuspRoundingFix, enableVaultNumbers]() { - if (enableVaultNumbers) + auto const range = [&r]() { + // If any new conditions with new amendments are added to "enableLargeNumbers", those + // amendments must also be added to useRulesGuards. + bool const enableLargeNumbers = + !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol)); + // If enableLargeNumbers is true, then useRulesGuards must also return true. + // However, the reverse is not true. Other amendments can cause the rules guard to be used, + // even though large numbers are _not_ used. + XRPL_ASSERT( + !r || !enableLargeNumbers || useRulesGuards(*r), + "setCurrentTransactionRules : rule decisions match"); + + if (enableLargeNumbers) { - if (enableCuspRoundingFix) + static_assert( + MantissaRange::MantissaScale::Large == MantissaRange::MantissaScale::Large330); + if (!r || r->enabled(fixCleanup3_3_0)) { - return MantissaRange::MantissaScale::Large; + return MantissaRange::MantissaScale::Large330; + } + if (r->enabled(fixCleanup3_2_0)) + { + return MantissaRange::MantissaScale::Large320; } return MantissaRange::MantissaScale::LargeLegacy; } @@ -69,14 +77,14 @@ bool useRulesGuards(Rules const& rules) { // The list of amendments used here - to decide whether to create a RulesGuard - must be a - // superset of the list used to figure out which mantissa scale to use in - // setCurrentTransactionRules. Additional amendments can be added if desired. + // superset of the list used to determine "enableLargeNumbers" in setCurrentTransactionRules. + // Additional amendments can be added if desired. // // As soon as any one of these amendments is retired, this whole function can be removed, along // with createGuards, and any other callers, and the first set of guards can be created directly // at the call site, without using optional. - return rules.enabled(fixCleanup3_2_0) || rules.enabled(featureSingleAssetVault) || - rules.enabled(featureLendingProtocol); + return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) || + rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0); } void @@ -87,7 +95,8 @@ createGuards( { if (useRulesGuards(rules)) { - // raii classes for the current ledger rules. + // raii classes for the current ledger rules. If the rules are set, the MantissaRange will + // be updated, too. rulesGuard.emplace(rules); } else diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 79f7655869..7bf98f270c 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -88,7 +88,6 @@ STNumber::add(Serializer& s) const } else { -#if !NDEBUG // There are circumstances where an already-rounded Number is // serialized without being touched by a transactor, and thus // without an asset. We can't know if it's rounded, because it could @@ -96,11 +95,9 @@ STNumber::add(Serializer& s) const // Json. Regardless, the only time we should be serializing an // STNumber is when the scale is large. XRPL_ASSERT_PARTS( - Number::getMantissaScale() == MantissaRange::MantissaScale::LargeLegacy || - Number::getMantissaScale() == MantissaRange::MantissaScale::Large, + Number::getMantissaScale() != MantissaRange::MantissaScale::Small, "xrpl::STNumber::add", "STNumber only used with large mantissa scale"); -#endif } } @@ -258,8 +255,47 @@ numberFromJson(SField const& field, json::Value const& value) Throw("not a number"); } - return STNumber{ - field, Number{parts.negative, parts.mantissa, parts.exponent, Number::Normalized{}}}; + Number const num{parts.negative, parts.mantissa, parts.exponent, Number::Normalized{}}; + + // Canonicalize "parts" and "num" with each other by getting rid of trailing 0s until either the + // exponents match, or there are no more 0s. If the two results don't match exactly, then the + // value has been rounded one way or another, and should not be used, because it may lead to an + // unexpected result. canonicalizeParts is not to be confused with Number::canonicalize, because + // they have completely different goals. + auto canonicalizeParts = [](NumberParts p, int otherExponent) { + if (p.mantissa == 0) + return NumberParts{}; + + while (p.exponent < otherExponent && p.mantissa % 10 == 0) + { + p.mantissa /= 10; + ++p.exponent; + } + + return p; + }; + + auto const numberMantissa = num.mantissa(); + auto const numberExponent = num.exponent(); + + auto const canonicalParts = canonicalizeParts(parts, numberExponent); + + auto const canonicalNum = canonicalizeParts( + NumberParts{ + .mantissa = Number::externalToInternal(numberMantissa), + .exponent = numberExponent, + .negative = numberMantissa < 0, + }, + canonicalParts.exponent); + + if (canonicalParts.mantissa != canonicalNum.mantissa || + canonicalParts.exponent != canonicalNum.exponent || + canonicalParts.negative != canonicalNum.negative) + { + Throw("number cannot be represented"); + } + + return STNumber{field, num}; } } // namespace xrpl diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 869700baed..ab9ae7d4d7 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -634,16 +634,16 @@ STObject::getAccountID(SField const& field) const } AccountID -STObject::getFeePayer() const +STObject::getInitiator() const { - // If sfDelegate is present, the delegate account is the payer + // If sfDelegate is present, the delegate account is the initiator // note: if a delegate is specified, its authorization to act on behalf of the account is // enforced in `Transactor::invokeCheckPermission` // cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`) if (isFieldPresent(sfDelegate)) return getAccountID(sfDelegate); - // Default payer + // Default initiator return getAccountID(sfAccount); } @@ -710,7 +710,7 @@ STObject::getFieldNumber(SField const& field) const void STObject::set(std::unique_ptr v) { - set(std::move(*v.get())); + set(std::move(*v)); } void diff --git a/src/libxrpl/protocol/STPathSet.cpp b/src/libxrpl/protocol/STPathSet.cpp index d61f17ecc6..8987d05f1e 100644 --- a/src/libxrpl/protocol/STPathSet.cpp +++ b/src/libxrpl/protocol/STPathSet.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -159,13 +160,10 @@ STPathSet::isDefault() const bool STPath::hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const { - for (auto& p : path_) - { - if (p.getAccountID() == account && p.getPathAsset() == asset && p.getIssuerID() == issuer) - return true; - } - - return false; + return std::ranges::any_of(path_, [&](auto& p) { + return p.getAccountID() == account && p.getPathAsset() == asset && + p.getIssuerID() == issuer; + }); } json::Value diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 8438d3498b..be4ba2e8e5 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -271,6 +272,13 @@ STTx::checkSign(Rules const& rules) const return std::unexpected("Counterparty: " + ret.error()); } + if (isFieldPresent(sfSponsorSignature)) + { + auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature); + if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret) + return std::unexpected("Sponsor: " + ret.error()); + } + // Verify the batch signer signatures here too, so they are cached with the // rest of signature checking (checkValidity / SF_SIGGOOD) and stay out of // the transaction engine. Gated on a batch (batchTxnIds_ seated) that @@ -551,7 +559,7 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const // For delegated transactions sfDelegate is the account whose signer list is checked, // the delegate account itself can not be among the signers. auto const txnAccountID = - &sigObject != this ? std::nullopt : std::optional(getFeePayer()); + &sigObject != this ? std::nullopt : std::optional(getInitiator()); // We can ease the computational load inside the loop a bit by // pre-constructing part of the data that we hash. Fill a Serializer @@ -603,6 +611,15 @@ STTx::getBatchTransactionIDs() const return *batchTxnIds_; } +AccountID +STTx::getFeePayerID() const +{ + if (isFieldPresent(sfSponsor) && ((getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u)) + return at(sfSponsor); + + return getInitiator(); +} + //------------------------------------------------------------------------------ static bool diff --git a/src/libxrpl/protocol/SecretKey.cpp b/src/libxrpl/protocol/SecretKey.cpp index f33b1871e1..7713911ab4 100644 --- a/src/libxrpl/protocol/SecretKey.cpp +++ b/src/libxrpl/protocol/SecretKey.cpp @@ -99,23 +99,24 @@ deriveDeterministicRootKey(Seed const& seed) } //------------------------------------------------------------------------------ -/** Produces a sequence of secp256k1 key pairs. - - The reference implementation of the XRP Ledger uses a custom derivation - algorithm which enables the derivation of an entire family of secp256k1 - keypairs from a single 128-bit seed. The algorithm predates widely-used - standards like BIP-32 and BIP-44. - - Important note to implementers: - - Using this algorithm is not required: all valid secp256k1 keypairs will - work correctly. Third party implementations can use whatever mechanisms - they prefer. However, implementers of wallets or other tools that allow - users to use existing accounts should consider at least supporting this - derivation technique to make it easier for users to 'import' accounts. - - For more details, please check out: - https://xrpl.org/cryptographic-keys.html#secp256k1-key-derivation +/** + * Produces a sequence of secp256k1 key pairs. + * + * The reference implementation of the XRP Ledger uses a custom derivation + * algorithm which enables the derivation of an entire family of secp256k1 + * keypairs from a single 128-bit seed. The algorithm predates widely-used + * standards like BIP-32 and BIP-44. + * + * Important note to implementers: + * + * Using this algorithm is not required: all valid secp256k1 keypairs will + * work correctly. Third party implementations can use whatever mechanisms + * they prefer. However, implementers of wallets or other tools that allow + * users to use existing accounts should consider at least supporting this + * derivation technique to make it easier for users to 'import' accounts. + * + * For more details, please check out: + * https://xrpl.org/cryptographic-keys.html#secp256k1-key-derivation */ class Generator { @@ -177,7 +178,9 @@ public: secureErase(generator_.data(), generator_.size()); } - /** Generate the nth key pair. */ + /** + * Generate the nth key pair. + */ std::pair operator()(std::size_t ordinal) const { diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index a6f8192a2f..c2167d58ce 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -107,6 +107,7 @@ transResults() MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."), MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"), + MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -220,6 +221,7 @@ transResults() MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."), MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."), MAKE_ERROR(terLOCKED, "Fund is locked."), + MAKE_ERROR(terNO_PERMISSION, "No permission to perform requested operation."), MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."), }; diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index b926bdf0e5..e4d4c4b03c 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -30,6 +30,9 @@ TxFormats::getCommonFields() {sfSigners, SoeOptional}, // submit_multisigned {sfNetworkID, SoeOptional}, {sfDelegate, SoeOptional}, + {sfSponsor, SoeOptional}, + {sfSponsorFlags, SoeOptional}, + {sfSponsorSignature, SoeOptional}, }; return kCommonFields; } diff --git a/src/libxrpl/protocol/UintTypes.cpp b/src/libxrpl/protocol/UintTypes.cpp index 486c11ba45..e1b8895f44 100644 --- a/src/libxrpl/protocol/UintTypes.cpp +++ b/src/libxrpl/protocol/UintTypes.cpp @@ -64,7 +64,7 @@ to_string(Currency const& currency) bool toCurrency(Currency& currency, std::string const& code) { - if (code.empty() || (code.compare(systemCurrencyCode()) == 0)) + if (code.empty() || code == systemCurrencyCode()) { currency = beast::kZero; return true; diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index 21984c67e7..bd0f54c3ae 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -160,15 +160,16 @@ digest2(Args const&... args) return digest(digest(args...)); } -/** Calculate a 4-byte checksum of the data - - The checksum is calculated as the first 4 bytes - of the SHA256 digest of the message. This is added - to the base58 encoding of identifiers to detect - user error in data entry. - - @note This checksum algorithm is part of the client API -*/ +/** + * Calculate a 4-byte checksum of the data + * + * The checksum is calculated as the first 4 bytes + * of the SHA256 digest of the message. This is added + * to the base58 encoding of identifiers to detect + * user error in data entry. + * + * @note This checksum algorithm is part of the client API + */ static void checksum(void* out, void const* message, std::size_t size) { diff --git a/src/libxrpl/rdb/SociDB.cpp b/src/libxrpl/rdb/SociDB.cpp index 08c826b210..2c3fb1bde1 100644 --- a/src/libxrpl/rdb/SociDB.cpp +++ b/src/libxrpl/rdb/SociDB.cpp @@ -17,7 +17,7 @@ #include #include #include -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" #endif @@ -188,14 +188,15 @@ convert(std::string const& from, soci::blob& to) namespace { -/** Run a thread to checkpoint the write ahead log (wal) for - the given soci::session every 1000 pages. This is only implemented - for sqlite databases. - - Note: According to: https://www.sqlite.org/wal.html#ckpt this - is the default behavior of sqlite. We may be able to remove this - class. -*/ +/** + * Run a thread to checkpoint the write ahead log (wal) for + * the given soci::session every 1000 pages. This is only implemented + * for sqlite databases. + * + * Note: According to: https://www.sqlite.org/wal.html#ckpt this + * is the default behavior of sqlite. We may be able to remove this + * class. + */ class WALCheckpointer : public Checkpointer { @@ -341,6 +342,6 @@ makeCheckpointer( } // namespace xrpl -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic pop #endif diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 0df0430a5f..2483e6f6e1 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -919,17 +919,18 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter) return false; } -/** Replace a node with a shareable node. - - This code handles two cases: - - 1) An unshared, unshareable node needs to be made shareable - so immutable SHAMap's can have references to it. - 2) An unshareable node is shared. This happens when you make - a mutable snapshot of a mutable SHAMap. - - @note The node must have already been unshared by having the caller - first call SHAMapTreeNode::unshare(). +/** + * Replace a node with a shareable node. + * + * This code handles two cases: + * + * 1) An unshared, unshareable node needs to be made shareable + * so immutable SHAMap's can have references to it. + * 2) An unshareable node is shared. This happens when you make + * a mutable snapshot of a mutable SHAMap. + * + * @note The node must have already been unshared by having the caller + * first call SHAMapTreeNode::unshare(). */ SHAMapTreeNodePtr SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index 1f38049abe..cc30426f9d 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -300,10 +300,11 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn) mn.deferred = 0; } -/** Get a list of node IDs and hashes for nodes that are part of this SHAMap - but not available locally. The filter can hold alternate sources of - nodes that are not permanently stored locally -*/ +/** + * Get a list of node IDs and hashes for nodes that are part of this SHAMap + * but not available locally. The filter can hold alternate sources of + * nodes that are not permanently stored locally + */ std::vector> SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter) { @@ -720,7 +721,8 @@ SHAMap::deepCompare(SHAMap& other) const return true; } -/** Does this map have this inner node? +/** + * Does this map have this inner node? */ bool SHAMap::hasInnerNode(SHAMapNodeID const& targetNodeID, SHAMapHash const& targetNodeHash) const @@ -742,7 +744,8 @@ SHAMap::hasInnerNode(SHAMapNodeID const& targetNodeID, SHAMapHash const& targetN return (node->isInner()) && (node->getHash() == targetNodeHash); } -/** Does this map have this leaf node? +/** + * Does this map have this leaf node? */ bool SHAMap::hasLeafNode(uint256 const& tag, SHAMapHash const& targetNodeHash) const diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 0f6988543e..4b562692d7 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include +#include #include #include #include @@ -54,7 +56,9 @@ namespace xrpl { -/** Performs early sanity checks on the txid */ +/** + * Performs early sanity checks on the txid + */ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask) { @@ -109,7 +113,8 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask) namespace detail { -/** Checks the validity of the transactor signing key. +/** + * Checks the validity of the transactor signing key. * * Normally called from preflight1. */ @@ -167,7 +172,61 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J } // namespace detail -/** Performs early sanity checks on the account and fee fields */ +static NotTEC +preflight1Sponsor(PreflightContext const& ctx) +{ + bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor); + bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags); + bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature); + + if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor)) + return temDISABLED; + + if (hasSponsor != hasSponsorFlags) + { + JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch"; + return temINVALID_FLAG; + } + if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags)) + { + JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition"; + return temMALFORMED; + } + + if (hasSponsorFlags) + { + auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags); + if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0) + { + JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags"; + return temINVALID_FLAG; + } + + // Reserve sponsorship is only permitted for an explicit allow-list of + // transaction types, for v1. All other tx types reject spfSponsorReserve here. + if (isReserveSponsored(ctx.tx)) + { + if (!isReserveSponsorAllowed(ctx.tx.getTxnType())) + { + JLOG(ctx.j.debug()) + << "preflight1: spfSponsorReserve not allowed for this transaction type"; + return temINVALID_FLAG; + } + } + } + + if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == ctx.tx.getAccountID(sfAccount)) + { + JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the same as the account"; + return temMALFORMED; + } + + return tesSUCCESS; +} + +/** + * Performs early sanity checks on the account and fee fields + */ NotTEC Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask) { @@ -230,10 +289,15 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask) if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value()) return temINVALID_INNER_BATCH; + if (auto const ter = preflight1Sponsor(ctx); !isTesSuccess(ter)) + return ter; + return tesSUCCESS; } -/** Checks whether the signature appears valid */ +/** + * Checks whether the signature appears valid + */ NotTEC Transactor::preflight2(PreflightContext const& ctx) { @@ -343,6 +407,44 @@ Transactor::checkPermission( return tesSUCCESS; } +NotTEC +Transactor::checkSponsor(ReadView const& view, STTx const& tx) +{ + if (!tx.isFieldPresent(sfSponsor)) + return tesSUCCESS; + + // Reserve sponsorship with permissioned delegation is disallowed. + if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx)) + return temINVALID; + + if (!view.exists(keylet::account(tx.getAccountID(sfSponsor)))) + return terNO_ACCOUNT; + + // Skip Sponsorship existence checks if the sponsor has signed the transaction - this + // transaction is valid regardless of the Sponsorship object. + // The use of the Sponsorship object is properly handled in + // getFeePayer/checkReserve/increaseOwnerCount/decreaseOwnerCount. + if (tx.isFieldPresent(sfSponsorSignature)) + return tesSUCCESS; + + // If the transaction contains sfDelegate, the Sponsorship object should be + // between the sponsor and the delegate. + auto const sponsorshipSle = + view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator())); + + // sponsorship object missing for pre-funded (no co-signing) tx + if (!sponsorshipSle) + return terNO_PERMISSION; + + if (isFeeSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee)) + return terNO_PERMISSION; + + if (isReserveSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve)) + return terNO_PERMISSION; + + return tesSUCCESS; +} + XRPAmount Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) { @@ -351,6 +453,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) // The computation has two parts: // * The base fee, which is the same for most transactions. // * The additional cost of each multisignature on the transaction. + // * The additional cost of each multisignature on the sponsor. XRPAmount const baseFee = view.fees().base; // Each signer adds one more baseFee to the minimum required fee @@ -358,7 +461,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) std::size_t const signerCount = tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0; - return baseFee + (signerCount * baseFee); + std::size_t sponsorSignerCount = 0; + if (tx.isFieldPresent(sfSponsorSignature)) + { + auto const sponsorObj = tx.getFieldObject(sfSponsorSignature); + if (sponsorObj.isFieldPresent(sfSigners)) + sponsorSignerCount += sponsorObj.getFieldArray(sfSigners).size(); + } + + return baseFee + ((signerCount + sponsorSignerCount) * baseFee); } XRPAmount @@ -437,12 +548,51 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) if (feePaid == beast::kZero) return tesSUCCESS; - auto const id = ctx.tx.getFeePayer(); - auto const sle = ctx.view.read(keylet::account(id)); - if (!sle) - return terNO_ACCOUNT; + auto const feePayer = getFeePayer(ctx.view, ctx.tx); + auto const payerSle = ctx.view.read(feePayer.keylet); - auto const balance = (*sle)[sfBalance].xrp(); + if (!payerSle) + { + if (feePayer.type == FeePayerType::SponsorPreFunded) + { + // Sanity check: already checked in checkSponsor + return tefINTERNAL; // LCOV_EXCL_LINE + } + + return terNO_ACCOUNT; + } + + XRPAmount maxSpendable = beast::kZero; + + if (feePayer.type == FeePayerType::SponsorPreFunded) + { + if (payerSle->getType() != ltSPONSORSHIP) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (payerSle->isFieldPresent(feePayer.balanceField)) + maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + + if (payerSle->isFieldPresent(sfMaxFee)) + { + auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp(); + maxSpendable = std::min(maxSpendable, cap); + } + } + else + { + if (payerSle->getType() != ltACCOUNT_ROOT) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j); + maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve; + } + else + { + maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + } + } // NOTE: Because preclaim evaluates against a static readview, it // does not reflect fee deductions from other transactions paid by @@ -451,12 +601,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) // transactions, this check may pass optimistically. // The fee shortfall will be handled by the Transactor::reset mechanism, // which caps the fee to the remaining actual balance. - if (balance < feePaid) + if (maxSpendable < feePaid) { - JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance) + JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable) << " paid=" << to_string(feePaid); - if ((balance > beast::kZero) && !ctx.view.open()) + if ((maxSpendable > beast::kZero) && !ctx.view.open()) { // Closed ledger, non-zero balance, less than fee return tecINSUFF_FEE; @@ -473,16 +623,72 @@ Transactor::payFee() { auto const feePaid = ctx_.tx[sfFee].xrp(); - auto const feePayer = ctx_.tx.getFeePayer(); - auto const sle = view().peek(keylet::account(feePayer)); + auto const feePayer = getFeePayer(view(), ctx_.tx); + auto const sle = view().peek(feePayer.keylet); + + JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.id); + if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - // Deduct the fee, so it's not available during the transaction. - // Will only write the account back if the transaction succeeds. - sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid); - if (feePayer != accountID_) - view().update(sle); // done in `apply()` for the account + if (feePaid == beast::kZero) + return tesSUCCESS; + + XRPAmount balance = beast::kZero; + if (sle->isFieldPresent(feePayer.balanceField)) + { + balance = sle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return tefINTERNAL; // LCOV_EXCL_LINE + } + + // A co-signed sponsor pays the fee out of its own account balance, but must + // never be charged into its account reserve, and a pre-funded sponsorship's + // fee is capped by sfMaxFee. Mirror the spendable amount computed in + // checkFee() so both limits are enforced on the apply path too. + XRPAmount spendable = balance; + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(view(), sle, j_); + // max(balance - reserve, 0) with overflow handling + spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero; + } + else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee)) + { + auto const cap = sle->getFieldAmount(sfMaxFee).xrp(); + spendable = std::min(spendable, cap); + } + + // Only sponsor fee-payers reject here on insufficient funds. For an + // ordinary account, the fee falls through and is capped by reset(), which + // caps to the account's balance. That capping is wrong for sponsors: a + // co-signed sponsor would be charged into its own reserve, and a prefunded + // sponsorship's fee amount should be rejected rather than partially spent. + if (feePaid > spendable && + (feePayer.type == FeePayerType::SponsorPreFunded || + feePayer.type == FeePayerType::SponsorCoSigned)) + { + if ((spendable > beast::kZero) && !view().open()) + return tecINSUFF_FEE; + + return terINSUF_FEE_B; + } + + auto const feeAmountAfter = balance - feePaid; + + if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount) + { + // Because ltSponsorship.sfFeeAmount is soeOptional + sle->makeFieldAbsent(feePayer.balanceField); + } + else + { + sle->setFieldAmount(feePayer.balanceField, feeAmountAfter); + } + + view().update(sle); // VFALCO Should we call view().rawDestroyXRP() here as well? return tesSUCCESS; @@ -656,7 +862,7 @@ Transactor::ticketDelete( } // Update the Ticket owner's reserve. - adjustOwnerCount(view, sleAccount, -1, j); + decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j); // Remove Ticket from ledger. view.erase(sleTicket); @@ -750,6 +956,21 @@ Transactor::checkSign( return tesSUCCESS; } + if (sigObject.isFieldPresent(sfSponsorSignature)) + { + // Co-signed sponsorship + + // Sanity check: already checked in preflight1 + if (!sigObject.isFieldPresent(sfSponsor)) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorID = sigObject.getAccountID(sfSponsor); + auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature); + if (auto const ret = checkSign(view, flags, std::nullopt, sponsorID, sponsorSignature, j); + !isTesSuccess(ret)) + return ret; + } + // If the pk is empty and not simulate or simulate and signers, // then we must be multi-signing. if (sigObject.isFieldPresent(sfSigners)) @@ -1062,10 +1283,11 @@ removeDeletedTrustLines( } } -/** Reset the context, discarding any changes made and adjust the fee. - - @param fee The transaction fee to be charged. - @return A pair containing the transaction result and the actual fee charged. +/** + * Reset the context, discarding any changes made and adjust the fee. + * + * @param fee The transaction fee to be charged. + * @return A pair containing the transaction result and the actual fee charged. */ std::pair Transactor::reset(XRPAmount fee) @@ -1079,11 +1301,38 @@ Transactor::reset(XRPAmount fee) if (!txnAcct) return {tefINTERNAL, beast::kZero}; - auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer())); + auto const feePayer = getFeePayer(view(), ctx_.tx); + auto const payerSle = view().peek(feePayer.keylet); + if (!payerSle) return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE - auto const balance = payerSle->getFieldAmount(sfBalance).xrp(); + XRPAmount balance = beast::kZero; + if (payerSle->isFieldPresent(feePayer.balanceField)) + { + balance = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE + } + + if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee)) + { + auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp(); + fee = std::min(fee, cap); + } + + // A co-signed sponsor must never be charged into its own account reserve, + // so the fee is capped to the balance above the reserve rather than to the + // full balance. + XRPAmount spendable = balance; + if (feePayer.type == FeePayerType::SponsorCoSigned) + { + auto const sponsorReserve = accountReserve(view(), payerSle, j_); + // max(balance - reserve, 0) with overflow handling + spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero; + } // balance should have already been checked in checkFee / preFlight. XRPL_ASSERT( @@ -1093,8 +1342,8 @@ Transactor::reset(XRPAmount fee) // We retry/reject the transaction if the account balance is zero or // we're applying against an open ledger and the balance is less than // the fee - if (fee > balance) - fee = balance; + if (fee > spendable) + fee = spendable; // Since we reset the context, we need to charge the fee and update // the account's sequence number (or consume the Ticket) again. @@ -1102,7 +1351,17 @@ Transactor::reset(XRPAmount fee) // If for some reason we are unable to consume the ticket or sequence // then the ledger is corrupted. Rather than make things worse we // reject the transaction. - payerSle->setFieldAmount(sfBalance, balance - fee); + auto const feeAmountAfter = balance - fee; + if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount) + { + // Because ltSponsorship.sfFeeAmount is soeOptional + payerSle->makeFieldAbsent(feePayer.balanceField); + } + else + { + payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter); + } + TER const ter{consumeSeqProxy(txnAcct)}; XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS"); @@ -1116,6 +1375,48 @@ Transactor::reset(XRPAmount fee) return {ter, fee}; } +FeePayer +Transactor::getFeePayer(ReadView const& view, STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx)) + { + auto const sponsorID = tx.getAccountID(sfSponsor); + auto const sponseeID = tx.getInitiator(); + auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID); + + // if pre-funded sponsorship exists, prefer it + if (view.exists(sponsorshipKeylet)) + { + // pre funded + return FeePayer{ + .id = sponsorID, + .keylet = sponsorshipKeylet, + .balanceField = sfFeeAmount, + .type = FeePayerType::SponsorPreFunded}; + } + + // Checked in Transactor::checkSponsor + XRPL_ASSERT( + tx.isFieldPresent(sfSponsorSignature), + "xrpl::getFeePayer has sponsor signature without a sponsorship object"); + + // co-signed + return FeePayer{ + .id = sponsorID, + .keylet = keylet::account(sponsorID), + .balanceField = sfBalance, + .type = FeePayerType::SponsorCoSigned}; + } + + AccountID const payerID = tx.getInitiator(); + auto const payerAccountKeylet = keylet::account(payerID); + auto const payerType = + tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account; + + return FeePayer{ + .id = payerID, .keylet = payerAccountKeylet, .balanceField = sfBalance, .type = payerType}; +} + // The sole purpose of this function is to provide a convenient, named // location to set a breakpoint, to be used when replaying transactions. void diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index caaacfd010..5af4f621a7 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -181,6 +181,9 @@ invokePreclaim(PreclaimContext const& ctx) if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx)) return result; + if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx)) + return result; + if (NotTEC const result = Transactor::invokeCheckPermission(ctx.view, ctx.tx)) return result; diff --git a/src/libxrpl/tx/invariants/DirectoryInvariant.cpp b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp index 1624a19830..b850f02ab5 100644 --- a/src/libxrpl/tx/invariants/DirectoryInvariant.cpp +++ b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include #include namespace xrpl { @@ -88,17 +90,15 @@ ValidBookDirectory::finalize( return false; } - for (auto const& rootIndex : rootIndexes_) - { + return std::ranges::all_of(rootIndexes_, [&](auto const& rootIndex) { auto const root = view.read(Keylet(ltDIR_NODE, rootIndex)); if (!root) { JLOG(j.fatal()) << "Invariant failed: book directory root missing"; return false; } - } - - return true; + return true; + }); } } // namespace xrpl diff --git a/src/libxrpl/tx/invariants/FreezeInvariant.cpp b/src/libxrpl/tx/invariants/FreezeInvariant.cpp index 0293d42e97..0a604d4c39 100644 --- a/src/libxrpl/tx/invariants/FreezeInvariant.cpp +++ b/src/libxrpl/tx/invariants/FreezeInvariant.cpp @@ -16,6 +16,7 @@ #include #include +#include #include namespace xrpl { @@ -73,8 +74,8 @@ TransfersNotFrozen::finalize( */ [[maybe_unused]] bool const enforce = view.rules().enabled(featureDeepFreeze); - for (auto const& [issue, changes] : balanceChanges_) - { + return std::ranges::all_of(balanceChanges_, [&](auto const& entry) { + auto const& [issue, changes] = entry; auto const issuerSle = findIssuer(issue.account, view); // It should be impossible for the issuer to not be found, but check // just in case so xrpld doesn't crash in release. @@ -86,20 +87,11 @@ TransfersNotFrozen::finalize( enforce, "xrpl::TransfersNotFrozen::finalize : enforce " "invariant."); - if (enforce) - { - return false; - } - continue; + return !enforce; } - if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce)) - { - return false; - } - } - - return true; + return validateIssuerChanges(issuerSle, changes, tx, j, enforce); + }); } bool diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 32df44a96b..9b997e06dd 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -148,29 +148,51 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a if (isXRP((*before)[sfAmount])) drops_ -= (*before)[sfAmount].xrp().drops(); break; + case ltSPONSORSHIP: + if (before->isFieldPresent(sfFeeAmount)) + { + XRPL_ASSERT( + isXRP((*before)[sfFeeAmount]), + "XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP"); + drops_ -= (*before)[sfFeeAmount].xrp().drops(); + } + break; default: break; } } - if (after) + if (!after) { - switch (after->getType()) - { - case ltACCOUNT_ROOT: - drops_ += (*after)[sfBalance].xrp().drops(); - break; - case ltPAYCHAN: - if (!isDelete) - drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops(); - break; - case ltESCROW: - if (!isDelete && isXRP((*after)[sfAmount])) - drops_ += (*after)[sfAmount].xrp().drops(); - break; - default: - break; - } + // LCOV_EXCL_START + UNREACHABLE("xrpl::XRPNotCreated::visitEntry : after can't be null"); + return; + // LCOV_EXCL_STOP + } + switch (after->getType()) + { + case ltACCOUNT_ROOT: + drops_ += (*after)[sfBalance].xrp().drops(); + break; + case ltPAYCHAN: + if (!isDelete) + drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops(); + break; + case ltESCROW: + if (!isDelete && isXRP((*after)[sfAmount])) + drops_ += (*after)[sfAmount].xrp().drops(); + break; + case ltSPONSORSHIP: + if (!isDelete && after->isFieldPresent(sfFeeAmount)) + { + XRPL_ASSERT( + isXRP((*after)[sfFeeAmount]), + "XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP"); + drops_ += (*after)[sfFeeAmount].xrp().drops(); + } + break; + default: + break; } } @@ -467,7 +489,7 @@ AccountRootsDeletedClean::finalize( // feature is enabled. Enabled, or not, though, a fatal-level message will // be logged [[maybe_unused]] bool const enforce = view.rules().enabled(fixCleanup3_2_0) || - view.rules().enabled(featureSingleAssetVault) || + view.rules().enabled(featureSponsor) || view.rules().enabled(featureSingleAssetVault) || view.rules().enabled(featureLendingProtocol); auto const objectExists = [&view, enforce, &j](auto const& keylet) { @@ -515,6 +537,20 @@ AccountRootsDeletedClean::finalize( if (enforce) return false; } + // An account should not be deleted with sponsorship fields + if (after->isFieldPresent(sfSponsoredOwnerCount) || + after->isFieldPresent(sfSponsoringOwnerCount) || + after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor)) + { + JLOG(j.fatal()) << "Invariant failed: account deletion left " + "behind a sponsorship field"; + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize : " + "deleted account has no sponsorship fields"); + if (enforce) + return false; + } // Simple types for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets) { @@ -767,14 +803,49 @@ ValidNewAccountRoot::finalize( //------------------------------------------------------------------------------ +static std::optional +clawbackTrustLineBalanceInHolderTerms( + SLE::const_pointer const& sle, + AccountID const& holder, + AccountID const& issuer, + Currency const& currency) +{ + if (!sle) + return STAmount{Issue{currency, issuer}}; + + if (sle->getType() != ltRIPPLE_STATE || + sle->key() != keylet::trustLine(holder, issuer, currency).key) + { + return std::nullopt; + } + + STAmount balance = sle->getFieldAmount(sfBalance); + if (holder > issuer) + balance.negate(); + balance.get().account = issuer; + return balance; +} + void -ValidClawback::visitEntry(bool, SLE::const_ref before, SLE::const_ref) +ValidClawback::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (before && before->getType() == ltRIPPLE_STATE) + { trustlinesChanged_++; + iou_.before = before; + } + + if (!isDelete && after && after->getType() == ltRIPPLE_STATE) + iou_.after = after; if (before && before->getType() == ltMPTOKEN) + { mptokensChanged_++; + mpt_.before = before; + } + + if (!isDelete && after && after->getType() == ltMPTOKEN) + mpt_.after = after; } bool @@ -803,31 +874,109 @@ ValidClawback::finalize( } bool const mptV2Enabled = view.rules().enabled(featureMPTokensV2); + if (trustlinesChanged_ != 0 && mptokensChanged_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: trustline and MPToken both changed."; + if (mptV2Enabled) + return false; + } + if (trustlinesChanged_ == 1 || (mptV2Enabled && mptokensChanged_ == 1)) { - AccountID const issuer = tx.getAccountID(sfAccount); STAmount const& amount = tx.getFieldAmount(sfAmount); - AccountID const& holder = amount.getIssuer(); - STAmount const holderBalance = amount.asset().visit( + + return amount.asset().visit( [&](Issue const& issue) { - return accountHolds( + AccountID const issuer = tx.getAccountID(sfAccount); + AccountID const& holder = amount.getIssuer(); + STAmount const holderBalance = accountHolds( view, holder, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j); + + if (holderBalance.signum() < 0) + { + JLOG(j.fatal()) << "Invariant failed: trustline or MPT balance is negative"; + return false; + } + + if (!iou_.before) + { + JLOG(j.fatal()) + << "Invariant failed: trustline clawback changed the wrong line"; + return !mptV2Enabled; + } + + auto const beforeBalance = clawbackTrustLineBalanceInHolderTerms( + iou_.before, holder, issuer, issue.currency); + auto const afterBalance = clawbackTrustLineBalanceInHolderTerms( + iou_.after, holder, issuer, issue.currency); + if (!beforeBalance || !afterBalance) + { + JLOG(j.fatal()) + << "Invariant failed: trustline clawback changed the wrong line"; + return !mptV2Enabled; + } + + STAmount clawAmount = amount; + clawAmount.get().account = issuer; + if (clawAmount <= beast::kZero) + { + JLOG(j.fatal()) << "Invariant failed: trustline clawback amount is invalid"; + return !mptV2Enabled; + } + + if (*afterBalance > *beforeBalance || + (*beforeBalance - *afterBalance) != std::min(*beforeBalance, clawAmount)) + { + JLOG(j.fatal()) + << "Invariant failed: trustline clawback balance change is invalid"; + return !mptV2Enabled; + } + + return true; }, [&](MPTIssue const& issue) { - return accountHolds( - view, - holder, - issue, - FreezeHandling::IgnoreFreeze, - AuthHandling::IgnoreAuth, - j); - }); + auto const holder = tx[~sfHolder]; + if (!holder) + { + JLOG(j.fatal()) << "Invariant failed: MPT clawback missing holder"; + return !mptV2Enabled; + } - if (holderBalance.signum() < 0) - { - JLOG(j.fatal()) << "Invariant failed: trustline or MPT balance is negative"; - return false; - } + if (!mpt_.before || !mpt_.after) + { + JLOG(j.fatal()) << "Invariant failed: MPT clawback token is missing"; + return !mptV2Enabled; + } + + if (mpt_.before->getAccountID(sfAccount) != *holder || + mpt_.after->getAccountID(sfAccount) != *holder || + (*mpt_.before)[sfMPTokenIssuanceID] != issue.getMptID() || + (*mpt_.after)[sfMPTokenIssuanceID] != issue.getMptID()) + { + JLOG(j.fatal()) << "Invariant failed: MPT clawback changed the wrong token"; + return !mptV2Enabled; + } + + auto const before = mpt_.before->getFieldU64(sfMPTAmount); + auto const after = mpt_.after->getFieldU64(sfMPTAmount); + if (amount.negative() || amount.mantissa() == 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT clawback amount is invalid"; + return !mptV2Enabled; + } + auto const clawAmount = amount.mantissa(); + + // MPT balances are unsigned, so validate the raw holder + // debit instead of routing through accountHolds(). + if (after > before || (before - after) != std::min(before, clawAmount)) + { + JLOG(j.fatal()) + << "Invariant failed: MPT clawback balance change is invalid"; + return !mptV2Enabled; + } + + return true; + }); } } else @@ -881,8 +1030,10 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const // 1. Exactly one of the pseudo-account fields is set. // 2. The sequence number is not changed. // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth - // flags are set. + // flags are set. // 4. The RegularKey is not set. + // 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor + // fields are not set. { std::vector const& fields = getPseudoAccountFields(); @@ -908,6 +1059,12 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const { errors_.emplace_back("pseudo-account has a regular key"); } + if (after->isFieldPresent(sfSponsoredOwnerCount) || + after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) || + after->isFieldPresent(sfSponsoringAccountCount)) + { + errors_.emplace_back("pseudo-account has a sponsorship field"); + } } } } diff --git a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp index d239acd417..b70c02947f 100644 --- a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp +++ b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp @@ -15,6 +15,8 @@ #include #include +#include + namespace xrpl { void @@ -127,8 +129,8 @@ ValidLoanBroker::finalize( } } - for (auto const& [brokerID, broker] : brokers_) - { + return std::ranges::all_of(brokers_, [&](auto const& entry) { + auto const& [brokerID, broker] = entry; auto const& after = broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanBroker(brokerID)); @@ -204,8 +206,8 @@ ValidLoanBroker::finalize( return false; } } - } - return true; + return true; + }); } } // namespace xrpl diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index d8f7fcc27d..77c5ad781e 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -403,7 +403,7 @@ ValidMPTIssuance::finalize( } void -ValidMPTPayment::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) +ValidMPTBalanceChanges::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { if (overflow_) return; @@ -465,7 +465,7 @@ ValidMPTPayment::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) } bool -ValidMPTPayment::finalize( +ValidMPTBalanceChanges::finalize( STTx const& tx, TER const result, XRPAmount const, diff --git a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp new file mode 100644 index 0000000000..bdf37492f3 --- /dev/null +++ b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp @@ -0,0 +1,158 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +// Add new sponsorship-related invariants implementations +void +SponsorshipOwnerCountsMatch::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) +{ + auto getSponsored = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoredOwnerCount); + return 0; + }; + auto getSponsoring = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoringOwnerCount); + return 0; + }; + + auto getOwnerCount = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfOwnerCount); + return 0; + }; + + auto getSponsoredObjectOwnerCount = [&](SLE::const_ref sle) -> std::uint32_t { + if (!sle) + return 0; + switch (sle->getType()) + { + case ltACCOUNT_ROOT: + return 0; + case ltRIPPLE_STATE: { + // A trust line can be reserve-sponsored independently on each + // side, so it may contribute up to two sponsored owner counts. + uint32_t ownerCount = 0; + if (sle->isFieldPresent(sfHighSponsor)) + ownerCount++; + if (sle->isFieldPresent(sfLowSponsor)) + ownerCount++; + return ownerCount; + } + default: + // Every other supported type carries a single sfSponsor field + // and contributes its full owner-count magnitude only when it is + // sponsored. + if (!sle->isFieldPresent(sfSponsor)) + return 0; + return getLedgerEntryOwnerCount(*sle); + } + }; + + // The values are implicitly casted to std::int64_t to calculate deltas. + std::int64_t const beforeSponsored = getSponsored(before); + std::int64_t const afterSponsored = getSponsored(after); + std::int64_t const beforeSponsoring = getSponsoring(before); + std::int64_t const afterSponsoring = getSponsoring(after); + + std::int64_t const beforeSponsoredObjectOwnerCount = getSponsoredObjectOwnerCount(before); + std::int64_t const afterSponsoredObjectOwnerCount = + isDelete ? 0 : getSponsoredObjectOwnerCount(after); + + deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored); + deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring); + + deltaSponsoredObjectOwnerCount_ += + (afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount); + + if (getOwnerCount(after) < getSponsored(after)) + ownerCountBelowSponsored_ += 1; +} + +bool +SponsorshipOwnerCountsMatch::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) const +{ + if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_) + { + JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not " + "equal SponsoringOwnerCount delta."; + return false; + } + + if (ownerCountBelowSponsored_ > 0) + { + JLOG(j.fatal()) + << "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount."; + return false; + } + + if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_) + { + JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not " + "equal SponsoredOwnerCount delta."; + return false; + } + + return true; +} + +void +SponsorshipAccountCountMatchesField::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) +{ + auto getSponsoringAccountCount = [](SLE::const_ref sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoringAccountCount); + return 0; + }; + + auto hasSponsorField = [](SLE::const_ref sle) -> bool { + return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor); + }; + + std::int64_t const beforeCount = getSponsoringAccountCount(before); + std::int64_t const afterCount = getSponsoringAccountCount(after); + deltaSponsoringAccountCount_ += (afterCount - beforeCount); + + int const beforePresent = hasSponsorField(before) ? 1 : 0; + int const afterPresent = hasSponsorField(after) ? 1 : 0; + deltaSponsorFieldPresence_ += (afterPresent - beforePresent); +} + +bool +SponsorshipAccountCountMatchesField::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) const +{ + if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_) + { + JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not " + "match net delta of sfSponsor presence."; + return false; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index 84814977db..a9ba0ec874 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -222,7 +222,10 @@ ValidVault::deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const if (!ret.has_value() || !vaultAsset.native()) return ret; - if (auto const delegate = tx[~sfDelegate]; delegate.has_value() && *delegate != tx[sfAccount]) + // Only add the fee back if tx[sfAccount] actually paid it. When the fee is + // paid by someone else (a delegate or a fee sponsor), the + // account's XRP balance moved only by the vault amount. + if (tx.getFeePayerID() != tx[sfAccount]) return ret; ret->delta += fee.drops(); @@ -1044,10 +1047,8 @@ ValidVault::finalize( case ttLOAN_SET: case ttLOAN_MANAGE: - case ttLOAN_PAY: { - // TBD + case ttLOAN_PAY: return true; - } default: // LCOV_EXCL_START diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index adb73f126e..71902ce8b9 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -66,13 +66,14 @@ protected: bool const ownerPaysTransferFee_; // Mark as inactive (dry) if too many offers are consumed bool inactive_ = false; - /** Number of offers consumed or partially consumed the last time - the step ran, including expired and unfunded offers. - - N.B. This is not the total number offers consumed by this step for the - entire payment, it is only the number the last time it ran. Offers may - be partially consumed multiple times during a payment. - */ + /** + * Number of offers consumed or partially consumed the last time + * the step ran, including expired and unfunded offers. + * + * N.B. This is not the total number offers consumed by this step for the + * entire payment, it is only the number the last time it ran. Offers may + * be partially consumed multiple times during a payment. + */ std::uint32_t offersUsed_ = 0; // If set, AMM liquidity might be available // if AMM offer quality is better than CLOB offer @@ -731,7 +732,7 @@ BookStep::forEachOffer( // Create MPToken for the offer's owner. No need to check // for the reserve since the offer is removed if it is consumed. // Therefore, the owner count remains the same. - if (auto const err = checkCreateMPT(sb, assetIn.get(), owner, j_); + if (auto const err = checkCreateMPT(sb, assetIn.get(), owner, {}, j_); !isTesSuccess(err)) { return true; diff --git a/src/libxrpl/tx/paths/MPTEndpointStep.cpp b/src/libxrpl/tx/paths/MPTEndpointStep.cpp index a47cfa15a5..0a0f6a9f27 100644 --- a/src/libxrpl/tx/paths/MPTEndpointStep.cpp +++ b/src/libxrpl/tx/paths/MPTEndpointStep.cpp @@ -410,7 +410,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio // for the reserve since the offer doesn't go on the books // if crossed. Insufficient reserve is allowed if the offer // crossed. See CreateOffer::applyGuts() for reserve check. - if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err)) + if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, {}, j_); + !isTesSuccess(err)) { JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT"; resetCache(srcDebtDir); diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index 254733a618..0055fce403 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -35,7 +35,6 @@ #include namespace xrpl { - bool AccountDelete::checkExtraFeatures(PreflightContext const& ctx) { @@ -268,6 +267,15 @@ AccountDelete::preclaim(PreclaimContext const& ctx) if (cp) return tecHAS_OBLIGATIONS; + if (sleAccount->isFieldPresent(sfSponsor)) + { + if (dst != sleAccount->getAccountID(sfSponsor)) + return tecNO_SPONSOR_PERMISSION; + } + if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) || + sleAccount->isFieldPresent(sfSponsoringAccountCount)) + return tecHAS_OBLIGATIONS; + // We don't allow an account to be deleted if its sequence number // is within 256 of the current ledger. This prevents replay of old // transactions if this account is resurrected after it is deleted. @@ -394,6 +402,35 @@ AccountDelete::doApply() (*src)[sfBalance] = (*src)[sfBalance] - remainingBalance; ctx_.deliver(remainingBalance); + if (src->isFieldPresent(sfSponsor)) + { + auto const sponsorID = src->getAccountID(sfSponsor); + auto sponsorSle = view().peek(keylet::account(sponsorID)); + + if (!sponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount); + + XRPL_ASSERT( + sponsoringAccountCount != 0, + "xrpl::AccountDelete::doApply : sponsoring account count is present"); + if (sponsoringAccountCount == 0) + { + // sanity check + // Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be + // present with a value of 0. + return tefINTERNAL; // LCOV_EXCL_LINE + } + sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1; + view().update(sponsorSle); + + // Following line might look redundant, but without it, sfSponsor + // would end up remaining in after-ltAccountRoot during the + // InvariantCheck. + src->makeFieldAbsent(sfSponsor); + } + XRPL_ASSERT( (*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero"); diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 2f95d32664..799c292bfc 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -147,9 +148,7 @@ SignerListSet::preCompute() Transactor::preCompute(); } -// The return type is signed so it is compatible with the 3rd argument -// of adjustOwnerCount() (which must be signed). -static int +static std::uint32_t signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules) { // We always compute the full change in OwnerCount, taking into account: @@ -165,7 +164,7 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules) // units. A SignerList with 8 entries would cost 10 OwnerCount units. // // The static_cast should always be safe since entryCount should always - // be in the range from 1 to 32. + // be in the range from 1 to 32, so the result is always positive. // We've got a lot of room to grow. XRPL_ASSERT( entryCount >= STTx::kMinMultiSigners, @@ -196,12 +195,11 @@ removeSignersFromLedger( // There are two different ways that the OwnerCount could be managed. // If the lsfOneOwnerCount bit is set then remove just one owner count. // Otherwise use the pre-MultiSignReserve amendment calculation. - int removeFromOwnerCount = -1; + std::uint32_t removeFromOwnerCount = 1; if (!signers->isFlag(lsfOneOwnerCount)) { STArray const& actualList = signers->getFieldArray(sfSignerEntries); - removeFromOwnerCount = - signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1; + removeFromOwnerCount = signerCountBasedOwnerCountDelta(actualList.size(), view.rules()); } // Remove the node from the account directory. @@ -215,8 +213,8 @@ removeSignersFromLedger( // LCOV_EXCL_STOP } - adjustOwnerCount( - view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View")); + decreaseOwnerCountForObject( + view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View")); view.erase(signers); @@ -315,19 +313,20 @@ SignerListSet::replaceSignerList() if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - // Compute new reserve. Verify the account has funds to meet the reserve. - std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]}; - static constexpr int kAddedOwnerCount = 1; std::uint32_t const flags{lsfOneOwnerCount}; - XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)}; - // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with TicketCreate. - if (preFeeBalance_ < newReserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + {.ownerCountDelta = kAddedOwnerCount}, + ctx_.journal); + !isTesSuccess(ret)) + return ret; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared(signerListKeylet); @@ -349,7 +348,8 @@ SignerListSet::replaceSignerList() // If we succeeded, the new entry counts against the // creator's reserve. - adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, kAddedOwnerCount, viewJ); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), signerList); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index f3ea97a341..e03cb56fd5 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -238,35 +238,35 @@ claimHelper( } /** - 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. + * 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 { @@ -364,26 +364,27 @@ struct TransferHelperSubmittingAccountInfo 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. +/** + * 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 @@ -435,8 +436,7 @@ transferHelper( return tecINTERNAL; // LCOV_EXCL_LINE { - auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); - auto const reserve = psb.fees().accountReserve(ownerCount); + auto const reserve = accountReserve(psb, sleSrc, j); auto const availableBalance = [&]() -> STAmount { STAmount curBal = (*sleSrc)[sfBalance]; @@ -505,21 +505,28 @@ transferHelper( 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. -*/ +/** + * 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 */ + /** + * Remove the claim even if the transfer fails + */ RemoveClaim, - /** Keep the claim if the transfer fails */ + /** + * Keep the claim if the transfer fails + */ KeepClaim }; struct FinalizeClaimHelperResult { - /// TER for transfering the payment funds + /** + * TER for transfering the payment funds + */ std::optional mainFundsTer; // TER for transfering the reward funds std::optional rewardTer; @@ -563,33 +570,34 @@ struct FinalizeClaimHelperResult } }; -/** 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. -*/ +/** + * 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( @@ -726,24 +734,24 @@ finalizeClaimHelper( } // Remove the claim id from the ledger + decreaseOwnerCountForObject(outerSb, sleOwner, sleClaimID, 1, j); 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 -*/ +/** + * 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) { @@ -1028,7 +1036,7 @@ applyCreateAccountAttestations( // Check reserve auto const balance = (*sleDoor)[sfBalance]; - auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); + auto const reserve = accountReserve(psb, sleDoor, j, {.ownerCountDelta = 1}); if (balance < reserve) return std::unexpected(tecINSUFFICIENT_RESERVE); @@ -1137,7 +1145,7 @@ applyCreateAccountAttestations( return tecINTERNAL; // LCOV_EXCL_LINE // Reserve was already checked - adjustOwnerCount(psb, sleDoor, 1, j); + increaseOwnerCount(psb, sleDoor, {}, 1, j); psb.insert(createdSleClaimID); psb.update(sleDoor); } @@ -1439,7 +1447,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); + auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -1484,7 +1492,7 @@ XChainCreateBridge::doApply() (*sleBridge)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal); ctx_.view().insert(sleBridge); ctx_.view().update(sleAcct); @@ -1987,8 +1995,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); - + auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; } @@ -2046,7 +2053,7 @@ XChainCreateClaimID::doApply() (*sleClaimID)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal); ctx_.view().insert(sleClaimID); ctx_.view().update(sleBridge); diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index d966772191..ed60817224 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -91,8 +91,7 @@ CheckCancel::doApply() } // If we succeeded, update the check owner's reserve. - auto const sleSrc = view().peek(keylet::account(srcId)); - adjustOwnerCount(view(), sleSrc, -1, viewJ); + decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ); // Remove check from ledger. view().erase(sleCheck); diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index 80ef742f52..4e810d94cf 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -30,7 +32,7 @@ #include #include -#include +#include #include namespace xrpl { @@ -175,7 +177,7 @@ CheckCash::preclaim(PreclaimContext const& ctx) // once the check is cashed, since the check's reserve will no // longer be required. So, if we're dealing in XRP, we add one // reserve's worth to the available funds. - if (value.native()) + if (value.native() && !sleCheck->isFieldPresent(sfSponsor)) availableFunds += XRPAmount{ctx.view.fees().increment}; if (value > availableFunds) @@ -307,6 +309,8 @@ CheckCash::doApply() // LCOV_EXCL_STOP } + auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck); + // Preclaim already checked that source has at least the requested // funds. // @@ -335,7 +339,7 @@ CheckCash::doApply() // from src's directory, we allow them to send that additional // incremental reserve amount in the transfer. Hence the -1 // argument. - STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)}; + STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)}; // Now, how much do they need in order to be successful? STAmount const xrpDeliver{ @@ -385,14 +389,25 @@ CheckCash::doApply() STAmount const flowDeliver{ optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)}; + auto applyViewContext = ApplyViewContext({.view = psb, .tx = ctx_.tx}); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + // Check reserve. Return destination account SLE if enough reserve, // otherwise return nullptr. - auto checkReserve = [&]() -> SLE::pointer { + auto checkDstReserve = [&]() -> SLE::pointer { auto sleDst = psb.peek(keylet::account(accountID_)); // Can the account cover the trust line's or MPT reserve? - if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)}; - preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1)) + if (auto const ret = checkReserve( + applyViewContext, + sleDst, + preFeeBalance_, + *sponsorSle, + {.ownerCountDelta = 1}, + j_); + !isTesSuccess(ret)) { JLOG(j_.trace()) << "Trust line does not exist. " "Insufficient reserve to create line."; @@ -426,7 +441,7 @@ CheckCash::doApply() // a. this (destination) account and // b. issuing account (not sending account). - auto const sleDst = checkReserve(); + auto const sleDst = checkDstReserve(); if (sleDst == nullptr) return tecNO_LINE_INSUF_RESERVE; @@ -449,6 +464,7 @@ CheckCash::doApply() Issue(currency, accountID_), // limit of zero 0, // quality in 0, // quality out + *sponsorSle, // sponsor viewJ); // journal !isTesSuccess(ter)) { @@ -491,11 +507,12 @@ CheckCash::doApply() auto const mptokenKey = keylet::mptoken(mptID, accountID_); if (!psb.exists(mptokenKey)) { - auto sleDst = checkReserve(); + auto sleDst = checkDstReserve(); if (sleDst == nullptr) return tecINSUFFICIENT_RESERVE; - if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_); + if (auto const err = + checkCreateMPT(psb, mptID, accountID_, *sponsorSle, j_); !isTesSuccess(err)) { return err; @@ -581,7 +598,7 @@ CheckCash::doApply() } // If we succeeded, update the check owner's reserve. - adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ); + decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ); // Remove check from ledger. psb.erase(sleCheck); diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index 595bcc4ab1..cb1d81ba4a 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -194,13 +195,10 @@ CheckCreate::doApply() // A check counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } - + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, ctx_.journal); + !isTesSuccess(ret)) + return ret; // Note that we use the value from the sequence or ticket as the // Check sequence. For more explanation see comments in SeqProxy.h. std::uint32_t const seq = ctx_.tx.getSeqValue(); @@ -253,7 +251,9 @@ CheckCreate::doApply() sleCheck->setFieldU64(sfOwnerNode, *page); } // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sle, 1, viewJ); + + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index e0ebdd893a..c0ef7ea9e8 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -93,12 +93,19 @@ CredentialAccept::doApply() if (!sleSubject || !sleIssuer) return tefINTERNAL; // LCOV_EXCL_LINE - { - STAmount const reserve{ - view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)}; - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!txSponsorSle) + return txSponsorSle.error(); // LCOV_EXCL_LINE + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleSubject, + preFeeBalance_, + *txSponsorSle, + {.ownerCountDelta = 1}, + j_); + !isTesSuccess(ret)) + return ret; auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(accountID_, issuer, credType); @@ -115,10 +122,16 @@ CredentialAccept::doApply() } sleCred->setFieldU32(sfFlags, lsfAccepted); - view().update(sleCred); - adjustOwnerCount(view(), sleIssuer, -1, j_); - adjustOwnerCount(view(), sleSubject, 1, j_); + // Release the original creation sponsor from the credential (it covered + // the issuer's reserve), then assign the accept tx's sponsor (if any) so + // the credential reflects whoever is now covering the subject's reserve. + decreaseOwnerCountForObject(view(), sleIssuer, sleCred, 1, j_); + removeSponsorFromLedgerEntry(sleCred); + + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred); + increaseOwnerCount(ctx_.getApplyViewContext(), sleSubject, 1, j_); + view().update(sleCred); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index acca408a95..e902ee73a6 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -7,13 +7,13 @@ #include #include // IWYU pragma: keep #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -130,12 +130,10 @@ CredentialCreate::doApply() if (!sleIssuer) return tefINTERNAL; // LCOV_EXCL_LINE - { - STAmount const reserve{ - view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)}; - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sleIssuer, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; sleCred->setAccountID(sfSubject, subject); sleCred->setAccountID(sfIssuer, accountID_); @@ -153,7 +151,8 @@ CredentialCreate::doApply() return tecDIR_FULL; sleCred->setFieldU64(sfIssuerNode, *page); - adjustOwnerCount(view(), sleIssuer, 1, j_); + increaseOwnerCount(ctx_.getApplyViewContext(), sleIssuer, 1, j_); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred); } if (subject == accountID_) diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 32a51555b1..96e6c9e443 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -5,10 +5,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -98,11 +98,14 @@ DelegateSet::doApply() if (permissions.empty()) return tecINTERNAL; // LCOV_EXCL_LINE - STAmount const reserve{ - ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleOwner, + preFeeBalance_, + {.ownerCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ret)) + return ret; sle = std::make_shared(delegateKey); sle->setAccountID(sfAccount, accountID_); @@ -130,7 +133,8 @@ DelegateSet::doApply() (*sle)[sfDestinationNode] = *destPage; ctx_.view().insert(sle); - adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sleOwner, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sle); return tesSUCCESS; } @@ -170,7 +174,7 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j) if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); + decreaseOwnerCountForObject(view, sleOwner, sle, 1, j); view.erase(sle); diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 66d059c54d..7c7d35497a 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -332,17 +332,30 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou return err; } - if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err)) + if (auto const err = createMPToken(sb, mptID, accountId, {}, flags); + !isTesSuccess(err)) return err; // Don't adjust AMM owner count. // It's irrelevant for pseudo-account like AMM. return accountSend( - sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes); + sb, + account, + accountId, + amount, + ctx.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes); }, // Set AMM flag on AMM trustline [&](Issue const& issue) -> TER { if (auto const res = accountSend( - sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes)) + sb, + account, + accountId, + amount, + ctx.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes)) return res; // Set AMM flag on AMM trustline if (!isXRP(amount)) diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index ff7fec019a..0d1798babc 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -618,7 +618,13 @@ AMMDeposit::deposit( } auto res = accountSend( - view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes); + view, + accountID_, + ammAccount, + amountDepositActual, + ctx_.journal, + {}, // don't sponsor for AMM Trustline + WaiveTransferFee::Yes); if (!isTesSuccess(res)) { JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual; @@ -642,6 +648,7 @@ AMMDeposit::deposit( ammAccount, *amount2DepositActual, ctx_.journal, + {}, // don't sponsor for AMM Trustline WaiveTransferFee::Yes); if (!isTesSuccess(res)) { @@ -673,7 +680,8 @@ adjustLPTokensOut( return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes); } -/** Proportional deposit of pools assets in exchange for the specified +/** + * Proportional deposit of pools assets in exchange for the specified * amount of LPTokens. */ std::pair @@ -721,7 +729,8 @@ AMMDeposit::equalDepositTokens( } } -/** Proportional deposit of pool assets with the constraints on the maximum +/** + * Proportional deposit of pool assets with the constraints on the maximum * amount of each asset that the trader is willing to deposit. * a = (t/T) * A (1) * b = (t/T) * B (2) @@ -822,7 +831,8 @@ AMMDeposit::equalDepositLimit( return {tecAMM_FAILED, STAmount{}}; } -/** Single asset deposit of the amount of asset specified by Asset1In. +/** + * Single asset deposit of the amount of asset specified by Asset1In. * t = T * (b / B - x) / (1 + x) (3) * where * f1 = (1 - 0.5 * tfee) / (1 - tfee) @@ -870,7 +880,8 @@ AMMDeposit::singleDeposit( tfee); } -/** Single asset asset1 is deposited to obtain some share of +/** + * Single asset asset1 is deposited to obtain some share of * the AMM instance's pools represented by amount of LPTokens. * Use equation 4 to compute the amount of asset1 to be deposited, * given t represented by amount of LPTokens. Equation 4 solves @@ -908,7 +919,8 @@ AMMDeposit::singleDepositTokens( tfee); } -/** Single asset deposit with two constraints. +/** + * Single asset deposit with two constraints. * a. Amount of asset1 if specified (not 0) in Asset1In specifies the maximum * amount of asset1 that the trader is willing to deposit. * b. The effective-price of the LPToken traded out does not exceed diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index 14ed5a4646..2baa7edfb4 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -658,15 +659,15 @@ AMMWithdraw::withdraw( auto sleAccount = view.peek(keylet::account(account)); if (!sleAccount) return tecINTERNAL; // LCOV_EXCL_LINE - STAmount const balance = (*sleAccount)[sfBalance]; - std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); + auto const balance = (*sleAccount)[sfBalance]->xrp(); // See also TrustSet::doApply() and MPTokenAuthorize::authorize() XRPAmount const reserve( - (ownerCount < 2) ? XRPAmount(beast::kZero) - : view.fees().accountReserve(ownerCount + 1)); + (ownerCount(sleAccount, journal) < 2) + ? XRPAmount(beast::kZero) + : accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1})); - auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance; + auto const balanceAdj = isIssue ? std::max(priorBalance, balance) : priorBalance; if (balanceAdj < reserve) return tecINSUFFICIENT_RESERVE; } @@ -683,7 +684,7 @@ AMMWithdraw::withdraw( !isTesSuccess(err)) return err; - if (auto const err = checkCreateMPT(view, mptIssue, account, journal); + if (auto const err = checkCreateMPT(view, mptIssue, account, {}, journal); !isTesSuccess(err)) { return err; @@ -700,7 +701,7 @@ AMMWithdraw::withdraw( // Withdraw amountWithdraw auto res = accountSend( - view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes); + view, ammAccount, account, amountWithdrawActual, journal, {}, WaiveTransferFee::Yes); if (!isTesSuccess(res)) { // LCOV_EXCL_START @@ -719,7 +720,7 @@ AMMWithdraw::withdraw( return {res, STAmount{}, STAmount{}, STAmount{}}; res = accountSend( - view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes); + view, ammAccount, account, *amount2WithdrawActual, journal, {}, WaiveTransferFee::Yes); if (!isTesSuccess(res)) { // LCOV_EXCL_START @@ -759,7 +760,8 @@ adjustLPTokensIn( return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No); } -/** Proportional withdrawal of pool assets for the amount of LPTokens. +/** + * Proportional withdrawal of pool assets for the amount of LPTokens. */ std::pair AMMWithdraw::equalWithdrawTokens( @@ -823,7 +825,8 @@ AMMWithdraw::deleteAMMAccountIfEmpty( return {ter, true}; } -/** Proportional withdrawal of pool assets for the amount of LPTokens. +/** + * Proportional withdrawal of pool assets for the amount of LPTokens. */ std::tuple> AMMWithdraw::equalWithdrawTokens( @@ -909,7 +912,8 @@ AMMWithdraw::equalWithdrawTokens( // LCOV_EXCL_STOP } -/** All assets withdrawal with the constraints on the maximum amount +/** + * All assets withdrawal with the constraints on the maximum amount * of each asset that the trader is willing to withdraw. * a = (t/T) * A (5) * b = (t/T) * B (6) @@ -999,7 +1003,8 @@ AMMWithdraw::equalWithdrawLimit( tfee); } -/** Withdraw single asset equivalent to the amount specified in Asset1Out. +/** + * Withdraw single asset equivalent to the amount specified in Asset1Out. * t = T * (c - sqrt(c**2 - 4*R))/2 * where R = b/B, c = R*fee + 2 - fee * Use equation 7 to compute the t, given the amount in Asset1Out. @@ -1045,7 +1050,8 @@ AMMWithdraw::singleWithdraw( tfee); } -/** withdrawal of single asset specified in Asset1Out proportional +/** + * withdrawal of single asset specified in Asset1Out proportional * to the share represented by the amount of LPTokens. * Use equation 8 to compute the amount of asset1, given the redeemed t * represented by LPTokens. Let this be Y. @@ -1089,7 +1095,8 @@ AMMWithdraw::singleWithdrawTokens( return {tecAMM_FAILED, STAmount{}}; } -/** Withdraw single asset with two constraints. +/** + * Withdraw single asset with two constraints. * a. amount of asset1 if specified (not 0) in Asset1Out specifies the minimum * amount of asset1 that the trader is willing to withdraw. * b. The effective price of asset traded out does not exceed the amount diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index a915bb60d1..fb47cf0f97 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -833,9 +833,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) return {tefINTERNAL, false}; { - XRPAmount const reserve = - sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1); - + XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1}); if (preFeeBalance_ < reserve) { // If we are here, the signing account had an insufficient reserve @@ -869,7 +867,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) } // Update owner count. - adjustOwnerCount(sb, sleCreator, 1, viewJ); + increaseOwnerCount(sb, sleCreator, {}, 1, viewJ); JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : " << to_string(saTakerGets.asset()) diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index 90aa21d8a1..a2af4c1100 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -50,8 +50,7 @@ DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, b if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); - view.update(sleOwner); + decreaseOwnerCountForObject(view, sleOwner, sle, 1, j); // Remove object from ledger view.erase(sle); diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index 1392581bb0..2659460b8d 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -72,7 +72,8 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner) // Check reserve availability for new object creation { auto const balance = STAmount((*sleAccount)[sfBalance]).xrp(); - auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1); + auto const reserve = + accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -89,7 +90,7 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner) return tecDIR_FULL; // LCOV_EXCL_LINE (*sle)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal); + increaseOwnerCount(ctx.view(), sleAccount, {}, 1, ctx.journal); ctx.view().update(sleAccount); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 55a9133e1e..feed43d410 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -181,7 +181,7 @@ EscrowCancel::doApply() if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.view(), + ctx_.getApplyViewContext(), kParityRate, ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep, preFeeBalance_, @@ -209,8 +209,7 @@ EscrowCancel::doApply() } } - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); - ctx_.view().update(sle); + decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal); // Remove escrow from ledger ctx_.view().erase(slep); diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 531098bd1c..50f2e8b859 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -434,16 +435,33 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; - auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); - auto const balance = sle->getFieldAmount(sfBalance).xrp(); - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. + if (auto const ret = + checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; - // Check reserve and funds availability if (isXRP(amount)) { - if (balance < reserve + STAmount(amount).xrp()) + // Second check (XRP escrow only): after locking the escrowed + // amount, the source must still meet its own reserve floor. This is + // always the source's own balance against the source's own reserve — + // the sponsor's reserve was already validated above, and a sponsor + // never covers the locked funds. We compare directly (rather than via + // checkReserve) because that helper diverts to the sponsor's balance + // when a sponsor is present and would ignore the source's post-lock + // balance entirely. ownerCountDelta differs by case: + // - sponsored: 0 — sponsor covers the new owner increment, so the + // source only owes reserve for its current owners. + // - unsponsored: 1 — source owes reserve including the new increment. + auto const sourceReserve = accountReserve( + ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1}); + if (balance - STAmount(amount).xrp() < sourceReserve) return tecUNFUNDED; } @@ -535,7 +553,8 @@ EscrowCreate::doApply() } // increment owner count - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep); ctx_.view().update(sle); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 7f1f9ec078..8bc98c7aa8 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -340,6 +340,16 @@ EscrowFinish::doApply() } } + // With the Sponsor amendment, release the escrow reserve before delivery. + // Token delivery can auto-create a destination holding, and the same + // sponsor (or the same account, for a self-escrow) may cover both the + // escrow being removed and the holding being created. Without the + // amendment, keep the legacy order: releasing early changes the reserve + // arithmetic for self-escrows and would break consensus if not gated. + bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor); + if (sponsorEnabled) + decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); + STAmount const amount = slep->getFieldAmount(sfAmount); // Transfer amount to destination if (isXRP(amount)) @@ -359,7 +369,7 @@ EscrowFinish::doApply() if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.view(), + ctx_.getApplyViewContext(), lockedRate, sled, preFeeBalance_, @@ -389,10 +399,9 @@ EscrowFinish::doApply() ctx_.view().update(sled); - // Adjust source owner count - auto const sle = ctx_.view().peek(keylet::account(account)); - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); - ctx_.view().update(sle); + // Adjust source owner count (legacy position, pre-Sponsor) + if (!sponsorEnabled) + decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); // Remove escrow from ledger ctx_.view().erase(slep); diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 9bb84e878e..b914f3cf24 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -372,7 +372,7 @@ LoanBrokerCoverClawback::doApply() associateAsset(*sleBroker, vaultAsset); // Transfer assets from pseudo-account to depositor. - return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes); + return accountSend(view(), brokerPseudoID, account, clawAmount, j_, {}, WaiveTransferFee::Yes); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index 69b28b57af..09ab03347a 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -176,7 +176,7 @@ LoanBrokerCoverDeposit::doApply() // Transfer assets from depositor to pseudo-account. if (auto ter = - accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes)) + accountSend(view(), accountID_, brokerPseudoID, amount, j_, {}, WaiveTransferFee::Yes)) return ter; // Increase the LoanBroker's CoverAvailable by Amount diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 008857a4ad..498f3c99eb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -211,7 +211,14 @@ LoanBrokerCoverWithdraw::doApply() associateAsset(*broker, vaultAsset); - return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_); + return doWithdraw( + ctx_.getApplyViewContext(), + accountID_, + dstAcct, + brokerPseudoID, + preFeeBalance_, + amount, + j_); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 141cc2cf56..b36977d225 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -155,11 +155,11 @@ LoanBrokerDelete::doApply() { auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)}; if (auto const ter = accountSend( - view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes)) + view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes)) return ter; } - if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_)) + if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_)) return ter; auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID)); @@ -186,8 +186,6 @@ LoanBrokerDelete::doApply() view().erase(brokerPseudoSLE); - view().erase(broker); - { auto owner = view().peek(keylet::account(accountID_)); if (!owner) @@ -195,9 +193,11 @@ LoanBrokerDelete::doApply() // Decreases the owner count by two: one for the LoanBroker object, and // one for the pseudo-account. - adjustOwnerCount(view(), owner, -2, j_); + decreaseOwnerCountForObject(view(), owner, broker, 2, j_); } + view().erase(broker); + associateAsset(*broker, vaultAsset); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index ab1c8f22dc..e9c153404c 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -238,9 +238,8 @@ LoanBrokerSet::doApply() // Increases the owner count by two: one for the LoanBroker object, and // one for the pseudo-account. - adjustOwnerCount(view, owner, 2, j_); - auto const ownerCount = owner->at(sfOwnerCount); - if (preFeeBalance_ < view.fees().accountReserve(ownerCount)) + increaseOwnerCount(view, owner, {}, 2, j_); + if (preFeeBalance_ < accountReserve(view, owner, j_)) return tecINSUFFICIENT_RESERVE; auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID); @@ -249,7 +248,8 @@ LoanBrokerSet::doApply() auto& pseudo = *maybePseudo; auto pseudoId = pseudo->at(sfAccount); - if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) + if (auto ter = addEmptyHolding( + ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) return ter; // Initialize data fields: diff --git a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp index 3459119379..1a77489b4b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -107,9 +107,8 @@ LoanDelete::doApply() view.erase(loanSle); // Decrement the LoanBroker's owner count. - // The broker's owner count is solely for the number of outstanding loans, - // and is distinct from the broker's pseudo-account's owner count - adjustOwnerCount(view, brokerSle, -1, j_); + adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_); + // If there are no loans left, then any remaining debt must be forgiven, // because there is no other way to pay it back. if (brokerSle->at(sfOwnerCount) == 0) @@ -129,7 +128,7 @@ LoanDelete::doApply() } } // Decrement the borrower's owner count - adjustOwnerCount(view, borrowerSle, -1, j_); + decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_); // These associations shouldn't do anything, but do them just to be safe associateAsset(*loanSle, vaultAsset); diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index 830eb30272..a0aa948876 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -292,6 +292,7 @@ LoanManage::defaultLoan( vaultSle->at(sfAccount), STAmount{vaultAsset, defaultCovered}, j, + {}, WaiveTransferFee::Yes); } diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index fcda2a9fff..54ee85b186 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -625,7 +625,11 @@ LoanPay::doApply() { // The broker may have deleted their holding. Recreate it if needed if (auto const ter = addEmptyHolding( - view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, j_); + ctx_.getApplyViewContext(), + brokerPayee, + brokerPayeeSle->at(sfBalance).value().xrp(), + asset, + j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 1ac387d1b1..694d01c69f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx) auto const& tx = ctx.tx; + if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx)) + { + JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed."; + return temINVALID_FLAG; + } + // Special case for Batch inner transactions if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) && !tx.isFieldPresent(sfCounterparty)) @@ -512,12 +519,12 @@ LoanSet::doApply() } } - adjustOwnerCount(view, borrowerSle, 1, j_); + increaseOwnerCount(view, borrowerSle, {}, 1, j_); + { - auto const ownerCount = borrowerSle->at(sfOwnerCount); auto const balance = accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp(); - if (balance < view.fees().accountReserve(ownerCount)) + if (balance < accountReserve(view, borrowerSle, j_)) return tecINSUFFICIENT_RESERVE; } @@ -531,8 +538,9 @@ LoanSet::doApply() borrower == accountID_ || borrower == counterparty, "xrpl::LoanSet::doApply", "borrower signed transaction"); + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = addEmptyHolding( - view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_); + applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, and @@ -555,7 +563,11 @@ LoanSet::doApply() "broker owner signed transaction"); if (auto const ter = addEmptyHolding( - view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_); + applyViewContext, + brokerOwner, + brokerOwnerSle->at(sfBalance).value().xrp(), + vaultAsset, + j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, @@ -631,9 +643,7 @@ LoanSet::doApply() // Update the balances in the loan broker adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale); - // The broker's owner count is solely for the number of outstanding loans, - // and is distinct from the broker's pseudo-account's owner count - adjustOwnerCount(view, brokerSle, 1, j_); + adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_); loanSequenceProxy += 1; // The sequence should be extremely unlikely to roll over, but fail if it // does diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index 14bf3646c0..41bb051768 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -387,8 +388,7 @@ NFTokenAcceptOffer::transferNFToken( auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); if (buyerOwnerCountAfter > buyerOwnerCountBefore) { - if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter); - buyerBalance < reserve) + if (buyerBalance < accountReserve(view(), sleBuyer, j_)) return tecINSUFFICIENT_RESERVE; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index d8e5a7b235..5490c2de3a 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -331,12 +332,14 @@ NFTokenMint::doApply() // allows NFTs to be added to the page (and burn fees) without // requiring the reserve to be met each time. The reserve is // only managed when a new NFT page or sell offer is added. - if (auto const ownerCountAfter = - view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount); + auto const sleAccount = view().read(keylet::account(accountID_)); + if (!sleAccount) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) { - if (auto const reserve = view().fees().accountReserve(ownerCountAfter); - preFeeBalance_ < reserve) + if (preFeeBalance_ < accountReserve(view(), sleAccount, j_)) return tecINSUFFICIENT_RESERVE; } return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index 9a26816155..b4c12b9514 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,8 @@ #include #include +#include + namespace xrpl { NotTEC @@ -68,10 +71,8 @@ OracleDelete::deleteOracle( if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1; - - adjustOwnerCount(view, sleOwner, count, j); - + std::uint32_t const count = calculateOracleReserve(sle); + decreaseOwnerCountForObject(view, sleOwner, sle, count, j); view.erase(sle); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 12d826e54d..ead26824e5 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -150,8 +153,9 @@ OracleSet::preclaim(PreclaimContext const& ctx) if (!pairsDel.empty()) return tecTOKEN_PAIR_NOT_FOUND; - auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1; - auto const newCount = pairs.size() > 5 ? 2 : 1; + auto const oldCount = calculateOracleReserve(sle); + auto const newCount = calculateOracleReserve(pairs); + adjustReserve = newCount - oldCount; } else @@ -160,7 +164,7 @@ OracleSet::preclaim(PreclaimContext const& ctx) if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass)) return temMALFORMED; - adjustReserve = pairs.size() > 5 ? 2 : 1; + adjustReserve = calculateOracleReserve(pairs); } if (pairs.empty()) @@ -169,7 +173,7 @@ OracleSet::preclaim(PreclaimContext const& ctx) return tecARRAY_TOO_LARGE; auto const reserve = - ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve); + accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve}); auto const& balance = sleSetter->getFieldAmount(sfBalance); if (balance < reserve) @@ -179,11 +183,22 @@ OracleSet::preclaim(PreclaimContext const& ctx) } static bool -adjustOwnerCount(ApplyContext& ctx, int count) +adjustOracleOwnerCount(ApplyContext& ctx, int count) { + XRPL_ASSERT(std::abs(count) <= 2, "xrpl::adjustOracleOwnerCount abs(counter) <= 2"); + if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount]))) { - adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal); + if (count > 0) + { + increaseOwnerCount( + ctx.view(), sleAccount, {}, static_cast(count), ctx.journal); + } + else if (count < 0) + { + decreaseOwnerCount( + ctx.view(), sleAccount, {}, static_cast(-count), ctx.journal); + } return true; } @@ -228,7 +243,7 @@ OracleSet::doApply() priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset)); pairs.emplace(tokenPairKey(entry), std::move(priceData)); } - auto const oldCount = pairs.size() > 5 ? 2 : 1; + auto const oldCount = calculateOracleReserve(pairs); // update/add/delete pairs for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries)) { @@ -266,9 +281,10 @@ OracleSet::doApply() (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID]; } - auto const newCount = pairs.size() > 5 ? 2 : 1; - auto const adjust = newCount - oldCount; - if (adjust != 0 && !adjustOwnerCount(ctx_, adjust)) + auto const newCount = calculateOracleReserve(pairs); + int32_t const adjust = newCount - oldCount; + + if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust)) return tefINTERNAL; // LCOV_EXCL_LINE ctx_.view().update(sle); @@ -317,8 +333,8 @@ OracleSet::doApply() (*sle)[sfOwnerNode] = *page; - auto const count = series.size() > 5 ? 2 : 1; - if (!adjustOwnerCount(ctx_, count)) + auto const count = calculateOracleReserve(series); + if (!adjustOracleOwnerCount(ctx_, count)) return tefINTERNAL; // LCOV_EXCL_LINE ctx_.view().insert(sle); diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 7e950dc743..d3e2af86ef 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -7,13 +7,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -151,6 +151,7 @@ DepositPreauth::preclaim(PreclaimContext const& ctx) TER DepositPreauth::doApply() { + auto applyViewContext = ctx_.getApplyViewContext(); if (ctx_.tx.isFieldPresent(sfAuthorize)) { auto const sleOwner = view().peek(keylet::account(accountID_)); @@ -160,13 +161,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{ - view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -190,7 +188,8 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + increaseOwnerCount(applyViewContext, sleOwner, 1, j_); + addSponsorToLedgerEntry(applyViewContext, slePreauth); } else if (ctx_.tx.isFieldPresent(sfUnauthorize)) { @@ -207,13 +206,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{ - view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkReserve( + applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -251,7 +247,8 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + increaseOwnerCount(applyViewContext, sleOwner, 1, j_); + addSponsorToLedgerEntry(applyViewContext, slePreauth); } else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials)) { @@ -289,8 +286,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b if (!sleOwner) return tefINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); - + decreaseOwnerCountForObject(view, sleOwner, slePreauth, 1, j); // Remove DepositPreauth from ledger. view.erase(slePreauth); diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 6e3883572a..17c96a1919 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include +#include #include #include #include @@ -120,6 +122,21 @@ Payment::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT) return temDISABLED; + if (tx.isFlag(tfSponsorCreatedAccount)) + { + if (!ctx.rules.enabled(featureSponsor)) + return temDISABLED; + + if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality)) + return temINVALID_FLAG; + + if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths)) + return temINVALID; + + if (!dstAmount.native()) + return temBAD_AMOUNT; + } + if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; @@ -383,15 +400,28 @@ Payment::preclaim(PreclaimContext const& ctx) { // accountReserve is the minimum amount that an account can have. // Reserve is not scaled by load. - JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " - << "Insufficent payment to create account."; + if (!ctx.tx.isFlag(tfSponsorCreatedAccount)) + { + // The minimum amount when creating a Sponsored Account is 1 drop. + // Since the reserve is covered by the sponsor, you don't need to hold the + // 1-increment reserve yourself. + JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " + << "Insufficient payment to create account."; - // TODO: de-dupe - // Another transaction could create the account and then this - // transaction would succeed. - return tecNO_DST_INSUF_XRP; + // TODO: de-dupe + // Another transaction could create the account and then this + // transaction would succeed. + return tecNO_DST_INSUF_XRP; + } } } + else if (ctx.tx.isFlag(tfSponsorCreatedAccount)) + { + // The tfSponsorCreatedAccount flag is specific to account creation via + // sponsorship. If the destination account already exists, applying this + // flag is invalid. + return tecNO_SPONSOR_PERMISSION; + } else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) { // The tag is basically account-specific information we don't @@ -470,6 +500,27 @@ Payment::doApply() sleDst->setFieldU32(sfSequence, view().seq()); sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero)); + if (ctx_.tx.isFlag(tfSponsorCreatedAccount)) + { + auto const sponsor = view().peek(keylet::account(accountID_)); + if (!sponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const currentSponsoringAccountCount = + sponsor->getFieldU32(sfSponsoringAccountCount); + if (currentSponsoringAccountCount == std::numeric_limits::max()) + { + // LCOV_EXCL_START + JLOG(j_.fatal()) << "Sponsoring account count overflow for account " + << to_string(accountID_); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1); + + addSponsorToLedgerEntry(sleDst, sponsor); + view().update(sponsor); + } + view().insert(sleDst); } else @@ -636,16 +687,12 @@ Payment::doApply() if (!sleSrc) return tefINTERNAL; // LCOV_EXCL_LINE - // ownerCount is the number of entries in this ledger for this - // account that require a reserve. - auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); + // the number of reserves in this ledger for this account that require a + // reserve. + auto const reserve = accountReserve(view(), sleSrc, j_); - // This is the total reserve in drops. - auto const reserve = view().fees().accountReserve(ownerCount); - - // In a delegated payment, the fee payer is the delegated account, - // not the source account (accountID_). - bool const accountIsPayer = (ctx_.tx.getFeePayer() == accountID_); + // In a delegated / fee sponsored payment, the fee payer is not the source account (accountID_). + bool const accountIsPayer = ctx_.tx.getFeePayerID() == accountID_; // preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees // were charged. If source account is the fee payer, it must also cover the fee. diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 977da56861..b17430948a 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -77,9 +78,10 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; // Check reserve and funds availability + if (!ctx.view.rules().enabled(featureSponsor)) { auto const balance = (*sle)[sfBalance]; - auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1); + auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1, 1); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -131,6 +133,36 @@ PaymentChannelCreate::doApply() return tecEXPIRED; } + if (ctx_.view().rules().enabled(featureSponsor)) + { + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, j_); + !isTesSuccess(ret)) + return ret; + + // Second check: after locking sfAmount in the channel, the source + // must still meet its own reserve floor. This is always the + // source's own balance against the source's own reserve — the + // sponsor's reserve was already validated above, and a sponsor + // never covers the locked funds. We compare directly (rather than + // via checkReserve) because that helper diverts to the + // sponsor's balance when a sponsor is present and would ignore the + // source's post-lock balance entirely. ownerCountDelta differs by + // case: + // - sponsored: 0 — sponsor covers the new owner increment, so + // the source only owes reserve for its current owners. + // - unsponsored: 1 — source owes reserve including the new increment. + auto const sourceReserve = accountReserve( + ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1}); + if (preFeeBalance_ - ctx_.tx[sfAmount].xrp() < sourceReserve) + return tecUNFUNDED; + } + auto const dst = ctx_.tx[sfDestination]; // Create PayChan in ledger. @@ -178,7 +210,8 @@ PaymentChannelCreate::doApply() // Deduct owner's balance, increment owner count (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal); + addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep); ctx_.view().update(sle); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 4e3c5dd638..1d1cf07e25 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -87,13 +88,18 @@ PaymentChannelFund::doApply() { // Check reserve and funds availability - auto const balance = (*sle)[sfBalance]; - auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]); + STAmount const balance = (*sle)[sfBalance]; + if (auto const ret = checkReserve(ctx_.getApplyViewContext(), sle, balance.xrp(), {}, j_); + !isTesSuccess(ret)) + return ret; - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx_.tx[sfAmount]) + // After locking sfAmount in the channel, the source must still meet + // its own reserve floor. We compare directly (rather than via + // checkReserve) because that helper diverts to the sponsor's balance + // when a sponsor is present and would ignore the source's post-lock + // balance entirely. Funding an existing channel adds no owned object, + // so there is no owner-count delta. + if (balance < accountReserve(ctx_.view(), sle, j_) + ctx_.tx[sfAmount]) return tecUNFUNDED; } diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index 7eb3f282b9..dc1b323482 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -42,7 +42,9 @@ PermissionedDomainDelete::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -/** Attempt to delete the Permissioned Domain. */ +/** + * Attempt to delete the Permissioned Domain. + */ TER PermissionedDomainDelete::doApply() { @@ -65,7 +67,7 @@ PermissionedDomainDelete::doApply() XRPL_ASSERT( ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0, "xrpl::PermissionedDomainDelete::doApply : nonzero owner count"); - adjustOwnerCount(view(), ownerSle, -1, ctx_.journal); + decreaseOwnerCountForObject(view(), ownerSle, slePd, 1, ctx_.journal); view().erase(slePd); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 0e71ceada1..61ebdcf9c7 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -73,7 +73,9 @@ PermissionedDomainSet::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -/** Attempt to create the Permissioned Domain. */ +/** + * Attempt to create the Permissioned Domain. + */ TER PermissionedDomainSet::doApply() { @@ -106,7 +108,8 @@ PermissionedDomainSet::doApply() // Create new permissioned domain. // Check reserve availability for new object creation auto const balance = STAmount((*ownerSle)[sfBalance]).xrp(); - auto const reserve = ctx_.view().fees().accountReserve((*ownerSle)[sfOwnerCount] + 1); + auto const reserve = + accountReserve(ctx_.view(), ownerSle, ctx_.journal, {.ownerCountDelta = 1}); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -125,7 +128,7 @@ PermissionedDomainSet::doApply() slePd->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), ownerSle, 1, ctx_.journal); + increaseOwnerCount(view(), ownerSle, {}, 1, ctx_.journal); view().insert(slePd); } diff --git a/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp new file mode 100644 index 0000000000..2b6ab8cf15 --- /dev/null +++ b/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp @@ -0,0 +1,420 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +static bool +hasSponsorshipBudget( + SLE::const_ref sponsorshipSle, + std::optional const& feeAmount, + std::optional const& remainingOwnerCount) +{ + // A field the transaction omits keeps whatever the existing object holds, + // so fall back to the current SLE value when the tx does not set it. + bool const hasFeeAmount = feeAmount + ? *feeAmount > beast::kZero + : sponsorshipSle && (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) > beast::kZero; + + bool const hasRemainingOwnerCount = remainingOwnerCount + ? *remainingOwnerCount > 0 + : sponsorshipSle && (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0) > 0; + + return hasFeeAmount || hasRemainingOwnerCount; +} + +TxConsequences +SponsorshipSet::makeTxConsequences(PreflightContext const& ctx) +{ + auto const feeAmount = ctx.tx[~sfFeeAmount]; + return TxConsequences{ctx.tx, feeAmount.has_value() ? feeAmount->xrp() : beast::kZero}; +} + +std::uint32_t +SponsorshipSet::getFlagsMask(PreflightContext const& ctx) +{ + return tfSponsorshipSetMask; +} + +NotTEC +SponsorshipSet::preflight(PreflightContext const& ctx) +{ + if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForFee) && + ctx.tx.isFlag(tfSponsorshipClearRequireSignForFee)) + return temINVALID_FLAG; + if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForReserve) && + ctx.tx.isFlag(tfSponsorshipClearRequireSignForReserve)) + return temINVALID_FLAG; + + auto const account = ctx.tx.getAccountID(sfAccount); + bool const hasSponsor = ctx.tx.isFieldPresent(sfCounterpartySponsor); + bool const hasSponsee = ctx.tx.isFieldPresent(sfSponsee); + + // The transaction must specify either Sponsor or Sponsee, but not both. + if (hasSponsor == hasSponsee) + return temMALFORMED; + + auto const sponsorID = ctx.tx[~sfCounterpartySponsor].value_or(account); + auto const sponseeID = ctx.tx[~sfSponsee].value_or(account); + + if (sponsorID == sponseeID) + return temMALFORMED; + + if (ctx.tx.isFlag(tfDeleteObject)) + { + // Transactions deleting `Sponsorship` cannot set modification flags. + constexpr std::uint32_t kModifyFlags = tfSponsorshipSetRequireSignForFee | + tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForFee | + tfSponsorshipClearRequireSignForReserve; + + if ((ctx.tx.getFlags() & kModifyFlags) != 0u) + return temINVALID_FLAG; + + // Transactions deleting `Sponsorship` cannot include modification fields. + if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfRemainingOwnerCount) || + ctx.tx.isFieldPresent(sfMaxFee)) + return temMALFORMED; + } + else + { + // Both sponsor and sponsee can delete a Sponsorship object, but only + // the sponsor can create or update one. + if (account != sponsorID) + return temMALFORMED; + + // FeeAmount and MaxFee must be non-negative XRP amounts when present. + auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC { + if (!ctx.tx.isFieldPresent(field)) + return tesSUCCESS; + + auto const amount = ctx.tx.getFieldAmount(field); + + if (!isXRP(amount)) + return temBAD_AMOUNT; + + if (amount.xrp() < beast::kZero) + return temBAD_AMOUNT; + + return tesSUCCESS; + }; + + if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret)) + return ret; + + if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret)) + return ret; + } + + return tesSUCCESS; +} + +TER +SponsorshipSet::preclaim(PreclaimContext const& ctx) +{ + auto const sponsorID = ctx.tx[~sfCounterpartySponsor].value_or(ctx.tx[sfAccount]); + auto const sponseeID = ctx.tx[~sfSponsee].value_or(ctx.tx[sfAccount]); + + if (sponseeID == sponsorID) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorAccSle = ctx.view.read(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecNO_DST; + + auto const sponseeSle = ctx.view.read(keylet::account(sponseeID)); + if (!sponseeSle) + return tecNO_DST; + + // Pseudo-accounts cannot participate in sponsorship. + if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle)) + return tecNO_PERMISSION; + + auto const sponsorshipSle = ctx.view.read(keylet::sponsorship(sponsorID, sponseeID)); + + // Deleting a Sponsorship object requires the object to already exist. + if (ctx.tx.isFlag(tfDeleteObject) && !sponsorshipSle) + return tecNO_ENTRY; + + // Reject creating or updating a Sponsorship that would be left with no + // budget (neither a positive FeeAmount nor a positive RemainingOwnerCount). + // Such an object is unusable yet still consumes the sponsor's reserve. + if (!ctx.tx.isFlag(tfDeleteObject) && + !hasSponsorshipBudget(sponsorshipSle, ctx.tx[~sfFeeAmount], ctx.tx[~sfRemainingOwnerCount])) + return tecNO_PERMISSION; + + return tesSUCCESS; +} + +static TER +deleteSponsorship(ApplyView& view, SLE::ref sle, beast::Journal j) +{ + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorID = (*sle)[sfOwner]; + auto const sponseeID = (*sle)[sfSponsee]; + + // The sponsor owns the Sponsorship object, so deletion releases the + // sponsor's owner reserve. + auto sponsorAccSle = view.peek(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!view.dirRemove(keylet::ownerDir(sponsorID), (*sle)[sfOwnerNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsor."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + if (!view.dirRemove(keylet::ownerDir(sponseeID), (*sle)[sfSponseeNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsee."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + + decreaseOwnerCountForObject(view, sponsorAccSle, sle, 1, j); + + // Return any prefunded fee amount to the sponsor before erasing the object. + if (sle->isFieldPresent(sfFeeAmount)) + { + (*sponsorAccSle)[sfBalance] += sle->getFieldAmount(sfFeeAmount); + view.update(sponsorAccSle); + } + + view.erase(sle); + + return tesSUCCESS; +} + +TER +SponsorshipSet::doApply() +{ + auto const sponsorID = ctx_.tx[~sfCounterpartySponsor].value_or(accountID_); + auto const sponseeID = ctx_.tx[~sfSponsee].value_or(accountID_); + + if (sponseeID == sponsorID) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorAccSle = ctx_.view().peek(keylet::account(sponsorID)); + if (!sponsorAccSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!ctx_.view().exists(keylet::account(sponseeID))) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const sponsorKeylet = keylet::sponsorship(sponsorID, sponseeID); + auto const sponsorshipSle = ctx_.view().peek(sponsorKeylet); + + if (ctx_.tx.isFlag(tfDeleteObject)) + { + if (!sponsorshipSle) + return tecINTERNAL; // LCOV_EXCL_LINE + + return deleteSponsorship(ctx_.view(), sponsorshipSle, ctx_.journal); + } + + auto const feeAmount = ctx_.tx[~sfFeeAmount]; + auto const maxFee = ctx_.tx[~sfMaxFee]; + auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount]; + + bool const hasPositiveFeeAmount = feeAmount.has_value() && *feeAmount > beast::kZero; + + auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!reserveSponsorAccSle) + return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE + + if (!sponsorshipSle) + { + // Create a new Sponsorship object between the sponsor and sponsee. + auto newSle = std::make_shared(sponsorKeylet); + + (*newSle)[sfOwner] = sponsorID; + (*newSle)[sfSponsee] = sponseeID; + if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance]) + return tecUNFUNDED; + + STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance]; + if (hasPositiveFeeAmount) + sponsorBalanceAfterFee -= *feeAmount; + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sponsorAccSle, + sponsorBalanceAfterFee.xrp(), + *reserveSponsorAccSle, + {.ownerCountDelta = 1}, + ctx_.journal, + tecUNFUNDED); + !isTesSuccess(ret)) + { + return ret; + } + + if (hasPositiveFeeAmount) + { + // New object: FeeAmount starts absent, so deduct and record the full amount + (*newSle)[sfFeeAmount] = *feeAmount; + (*sponsorAccSle)[sfBalance] -= *feeAmount; + } + + if (maxFee && *maxFee > beast::kZero) + (*newSle)[sfMaxFee] = *maxFee; + if (remainingOwnerCount && *remainingOwnerCount > 0) + (*newSle)[sfRemainingOwnerCount] = *remainingOwnerCount; + + std::uint32_t flags = 0; + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee)) + flags |= lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve)) + flags |= lsfSponsorshipRequireSignForReserve; + + (*newSle)[sfFlags] = flags; + + auto const sponsorPage = view().dirInsert( + keylet::ownerDir(sponsorID), sponsorKeylet, describeOwnerDir(sponsorID)); + if (!sponsorPage) + return tecDIR_FULL; // LCOV_EXCL_LINE + (*newSle)[sfOwnerNode] = *sponsorPage; + + auto const sponseePage = view().dirInsert( + keylet::ownerDir(sponseeID), sponsorKeylet, describeOwnerDir(sponseeID)); + if (!sponseePage) + return tecDIR_FULL; // LCOV_EXCL_LINE + (*newSle)[sfSponseeNode] = *sponseePage; + + // NOLINTNEXTLINE(readability-suspicious-call-argument) + increaseOwnerCount(view(), sponsorAccSle, *reserveSponsorAccSle, 1, ctx_.journal); + addSponsorToLedgerEntry(newSle, *reserveSponsorAccSle); + + ctx_.view().insert(newSle); + return tesSUCCESS; + } + + // Update the existing Sponsorship object. + if (feeAmount) + { + auto const currentFeeAmount = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0}); + auto const feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount); + + if (feeAmountDelta > beast::kZero && feeAmountDelta > (*sponsorAccSle)[sfBalance]) + return tecUNFUNDED; + + // Move the FeeAmount delta between the sponsor balance and Sponsorship + // object. + if (feeAmountDelta != beast::kZero) + { + STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance]; + sponsorBalanceAfterFee -= feeAmountDelta; + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sponsorAccSle, + sponsorBalanceAfterFee.xrp(), + *reserveSponsorAccSle, + {}, + ctx_.journal, + tecUNFUNDED); + !isTesSuccess(ret)) + { + return ret; + } + + (*sponsorAccSle)[sfBalance] -= feeAmountDelta; + if (*feeAmount == beast::kZero) + { + (*sponsorshipSle).makeFieldAbsent(sfFeeAmount); + } + else + { + (*sponsorshipSle).setFieldAmount(sfFeeAmount, *feeAmount); + } + ctx_.view().update(sponsorAccSle); + } + } + + if (maxFee) + { + if (*maxFee == beast::kZero) + { + (*sponsorshipSle).makeFieldAbsent(sfMaxFee); + } + else + { + (*sponsorshipSle)[sfMaxFee] = *maxFee; + } + } + + if (remainingOwnerCount) + { + if (*remainingOwnerCount == 0) + { + sponsorshipSle->makeFieldAbsent(sfRemainingOwnerCount); + } + else + { + sponsorshipSle->at(sfRemainingOwnerCount) = *remainingOwnerCount; + } + } + + // Apply requested flag changes. + auto flags = sponsorshipSle->getFieldU32(sfFlags); + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee)) + flags |= lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForFee)) + flags &= ~lsfSponsorshipRequireSignForFee; + + if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve)) + flags |= lsfSponsorshipRequireSignForReserve; + + if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve)) + flags &= ~lsfSponsorshipRequireSignForReserve; + + if (flags != (*sponsorshipSle)[sfFlags]) + (*sponsorshipSle)[sfFlags] = flags; + + view().update(sponsorshipSle); + + return tesSUCCESS; +} + +void +SponsorshipSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) +{ +} + +bool +SponsorshipSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp new file mode 100644 index 0000000000..0e036649fd --- /dev/null +++ b/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp @@ -0,0 +1,542 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +// Increment an uint32 sponsor count field and update the SLE. +static TER +incrementSponsorCount( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& field, + std::uint32_t const delta) +{ + auto const currentValue = sle->getFieldU32(field); + if (std::numeric_limits::max() - currentValue < delta) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::incrementSponsorCount : sponsor field overflow"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + sle->at(field) = currentValue + delta; + view.update(sle); + return tesSUCCESS; +} + +// Decrement an uint32 sponsor count field and update the SLE. +static TER +decrementSponsorCount( + ApplyView& view, + SLE::ref sle, + SF_UINT32 const& field, + std::uint32_t const delta) +{ + auto const currentValue = sle->getFieldU32(field); + if (currentValue < delta) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::decrementSponsorCount : sponsor field underflow"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + sle->at(field) = currentValue - delta; + view.update(sle); + return tesSUCCESS; +} + +// Consume the sponsor's pre-funded reserve budget and lowers the Sponsorship +// object's RemainingOwnerCount. +static TER +decrementPrefundedReserveCount(ApplyView& view, SLE::ref sponsorshipSle, std::uint32_t const delta) +{ + if (delta == 0) + return tesSUCCESS; // LCOV_EXCL_LINE + + auto const currentReserveCount = sponsorshipSle->getFieldU32(sfRemainingOwnerCount); + if (currentReserveCount < delta) + { + // LCOV_EXCL_START + // Already verified by checkReserve (sufficient RemainingOwnerCount) + UNREACHABLE("xrpl::decrementPrefundedReserveCount : invalid reserve count"); + return tefINTERNAL; + // LCOV_EXCL_STOP + } + + sponsorshipSle->at(sfRemainingOwnerCount) = currentReserveCount - delta; + view.update(sponsorshipSle); + return tesSUCCESS; +} + +std::uint32_t +SponsorshipTransfer::getFlagsMask(PreflightContext const& ctx) +{ + return tfSponsorshipTransferMask; +} + +NotTEC +SponsorshipTransfer::preflight(PreflightContext const& ctx) +{ + static constexpr auto transferFlags = + tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd; + if (std::popcount(ctx.tx.getFlags() & transferFlags) != 1) + { + JLOG(ctx.j.debug()) << "preflight: Only one SponsorshipTransfer flag can be set per tx."; + return temINVALID_FLAG; + } + + if (ctx.tx.isFlag(tfSponsorshipCreate)) + { + // Creating sponsorship transfers an unsponsored target from the sponsee + // to a reserve sponsor identified by sfSponsor + spfSponsorReserve. + if (!ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsor must be present when creating sponsorship"; + return temMALFORMED; + } + + if (!isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) + << "preflight: spfSponsorReserve must be set when creating sponsorship"; + return temINVALID_FLAG; + } + + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsee must not be present when creating sponsorship"; + return temMALFORMED; + } + } + + if (ctx.tx.isFlag(tfSponsorshipReassign)) + { + // Reassigning sponsorship transfers an already sponsored target from its + // current reserve sponsor to the new sponsor identified by sfSponsor + + // spfSponsorReserve. + if (!ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsor must be present when reassigning sponsorship"; + return temMALFORMED; + } + + if (!isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) + << "preflight: spfSponsorReserve must be set when reassigning sponsorship"; + return temINVALID_FLAG; + } + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsee must not be present when reassigning sponsorship"; + return temMALFORMED; + } + } + + if (ctx.tx.isFlag(tfSponsorshipEnd)) + { + // Ending sponsorship removes reserve sponsorship from a sponsored target. + // The target is sfSponsee when provided; otherwise it is sfAccount. + if (ctx.tx.isFieldPresent(sfSponsor)) + { + JLOG(ctx.j.debug()) + << "preflight: sfSponsor must not be present when ending sponsorship"; + return temMALFORMED; + } + + // sfSponsorFlags should not be present if it is ending sponsorship. + if (ctx.tx.isFieldPresent(sfSponsorFlags)) + { + // Unreachable: reaching here means sfSponsor is absent, which is already checked above, + // and preflight1Sponsor already rejects sfSponsorFlags present without sfSponsor with + // temINVALID_FLAG. Keep this as a defensive check. + // LCOV_EXCL_START + UNREACHABLE( + "xrpl::SponsorshipTransfer::preflight : sfSponsorFlags present without sfSponsor " + "when ending sponsorship"); + return temINVALID_FLAG; + // LCOV_EXCL_STOP + } + + if (ctx.tx.isFieldPresent(sfSponsee) && + ctx.tx.getAccountID(sfSponsee) == ctx.tx.getAccountID(sfAccount)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be the same as the account"; + return temMALFORMED; + } + } + + // Account-level reserve sponsorship changes the reserve responsibility for + // the account itself, so the new sponsor must explicitly co-sign. Object-level + // sponsorship may use pre-funded reserve sponsorship instead. + bool const isCreateOrReassign = + ctx.tx.isFlag(tfSponsorshipCreate) || ctx.tx.isFlag(tfSponsorshipReassign); + auto const reserveSponsor = getTxReserveSponsorID(ctx.tx); + bool const isAccountReserveSponsorship = + isCreateOrReassign && reserveSponsor && !ctx.tx.isFieldPresent(sfObjectID); + + if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature)) + { + JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature"; + return temMALFORMED; + } + + return tesSUCCESS; +} + +TER +SponsorshipTransfer::preclaim(PreclaimContext const& ctx) +{ + auto const objectID = ctx.tx[~sfObjectID]; + auto const newSponsorSleExpected = getTxReserveSponsor(ctx.view, ctx.tx); + if (!newSponsorSleExpected) + return newSponsorSleExpected.error(); // LCOV_EXCL_LINE + auto const newSponsorSle = *newSponsorSleExpected; + + auto const account = ctx.tx[sfAccount]; + auto const sponseeID = ctx.tx[~sfSponsee].value_or(account); + auto const sponseeSle = ctx.view.read(keylet::account(sponseeID)); + if (!sponseeSle) + { + // If it is ending sponsorship, sfSponsee is user input, return terNO_ACCOUNT if it does not + // exist. + if (ctx.tx.isFieldPresent(sfSponsee)) + return terNO_ACCOUNT; + + // If it is creating or reassigning sponsorship, sfSponsee is the account itself, which is + // always present by the time preclaim runs. Return tecINTERNAL if it does not exist. + return tecINTERNAL; // LCOV_EXCL_LINE + } + + // Default setup with an account sponsorship transfer. If it is an object transfer, they will be + // overridden to the object SLE and its type-specific sponsor field: + // sfHighSponsor/sfLowSponsor for a RippleState, sfSponsor for other object types. + SLE::const_pointer targetSle = sponseeSle; + auto const* sponsorField = &sfSponsor; + + if (objectID.has_value()) + { + auto const objectSle = ctx.view.read(keylet::unchecked(*objectID)); + if (!objectSle) + return tecNO_ENTRY; + + if (!isLedgerEntrySupportedBySponsorship(*objectSle)) + return tecNO_PERMISSION; + + if (!isLedgerEntryOwner(ctx.view, *objectSle, sponseeID)) + return tecNO_PERMISSION; + + // Object transfer: the target is the object, and its sponsor field + // depends on the object type, a RippleState stores the sponsor in + // sfHighSponsor/sfLowSponsor, while other object type uses sfSponsor. + targetSle = objectSle; + sponsorField = &getLedgerEntrySponsorField(*objectSle, sponseeID); + } + + bool const isSponsored = targetSle->isFieldPresent(*sponsorField); + + if (ctx.tx.isFlag(tfSponsorshipCreate)) + { + // Creating a new sponsorship: needs a new reserve sponsor, and the + // target must not already be sponsored + if (!newSponsorSle || isSponsored) + return tecNO_PERMISSION; + } + else if (ctx.tx.isFlag(tfSponsorshipReassign)) + { + // Reassigning sponsorship: needs a new reserve sponsor, and the target must already + // be sponsored + if (!newSponsorSle || !isSponsored) + return tecNO_PERMISSION; + + // Reassigning to the current sponsor would change no state, but would + // still draw down the sponsor's pre-funded reserve budget (and its + // reserve headroom would be double-counted in checkReserve). + if (targetSle->getAccountID(*sponsorField) == ctx.tx.getAccountID(sfSponsor)) + return tecNO_PERMISSION; + } + else if (ctx.tx.isFlag(tfSponsorshipEnd)) + { + // Ending sponsorship: no new reserve sponsor, the target must be sponsored. + if (newSponsorSle || !isSponsored) + return tecNO_PERMISSION; + + // Only the sponsor or sponsee can end sponsorship. + auto const sponsor = targetSle->getAccountID(*sponsorField); + if (account != sponsor && account != sponseeID) + return tecNO_PERMISSION; + } + + return tesSUCCESS; +} + +TER +SponsorshipTransfer::doApply() +{ + auto const objectID = ctx_.tx[~sfObjectID]; + + auto const sponseeID = ctx_.tx[~sfSponsee].value_or(accountID_); + auto const sponseeSle = view().peek(keylet::account(sponseeID)); + if (!sponseeSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const balanceBeforeFee = [&](SLE::const_ref sle) -> XRPAmount { + if (sle->getAccountID(sfAccount) == accountID_) + return preFeeBalance_; + return sle->getFieldAmount(sfBalance).xrp(); + }; + + bool const isCreate = ctx_.tx.isFlag(tfSponsorshipCreate); + bool const isReassign = ctx_.tx.isFlag(tfSponsorshipReassign); + + if (objectID.has_value()) + { + // Transfer object sponsor + auto const objectSle = view().peek(keylet::unchecked(*objectID)); + if (!objectSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // preclaim established that the sponsee owns the object. + if (!isLedgerEntryOwner(view(), *objectSle, sponseeID)) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const ownerSle = view().peek(keylet::account(sponseeID)); + if (!ownerSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const ownerCountDelta = + static_cast(getLedgerEntryOwnerCount(*objectSle)); + auto const& sponsorField = getLedgerEntrySponsorField(*objectSle, sponseeID); + + if (isCreate || isReassign) + { + auto const newSponsor = ctx_.tx[~sfSponsor]; + XRPL_ASSERT( + newSponsor.has_value(), + "xrpl::SponsorshipTransfer::doApply : sfSponsor present for object sponsor " + "create/reassign"); + if (!newSponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const newSponsorID = *newSponsor; + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Check if new sponsor has sufficient balance + // NOLINTNEXTLINE(readability-suspicious-call-argument) + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + sponseeSle->getFieldAmount(sfBalance).xrp(), + newSponsorSle, + {.ownerCountDelta = ownerCountDelta}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + if (isCreate) + { + // Update owner's sponsored count + if (auto const ter = incrementSponsorCount( + view(), ownerSle, sfSponsoredOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + else if (isReassign) + { + auto const oldSponsorID = objectSle->getAccountID(sponsorField); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Decrement old sponsor's sponsoring count + if (auto const ter = decrementSponsorCount( + view(), oldSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + + // Increment new sponsor's sponsoring count + if (auto const ter = incrementSponsorCount( + view(), newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Object is now sponsored by new sponsor + objectSle->setAccountID(sponsorField, newSponsorID); + view().update(objectSle); + + auto const sponsorshipSle = view().peek(keylet::sponsorship(newSponsorID, sponseeID)); + if (sponsorshipSle) + { + // Update ReserveCount for sponsorship object if it exists + if (auto const ter = + decrementPrefundedReserveCount(view(), sponsorshipSle, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + } + else if (ctx_.tx.isFlag(tfSponsorshipEnd)) + { + // End object sponsor + auto const oldSponsorID = objectSle->getAccountID(sponsorField); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // The owner reclaims the reserve burden when the object is no longer sponsored. + // We do not check the sponsee's reserve here (via `checkReserve`) so that a sponsor can + // always end a sponsorship, even if the sponsee lacks sufficient reserve. + + // Decrement sponsored count + if (auto const ter = decrementSponsorCount( + view(), sponseeSle, sfSponsoredOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Decrement old sponsoring count + if (auto const ter = decrementSponsorCount( + view(), oldSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Remove sponsor from object + objectSle->makeFieldAbsent(sponsorField); + view().update(objectSle); + } + } + else + { + // Account-level sponsorship is always co-signed (preflight requires + // sfSponsorSignature), so there is no pre-funded budget to draw down here. + if (isCreate || isReassign) + { + auto const newSponsor = ctx_.tx[~sfSponsor]; + XRPL_ASSERT( + newSponsor.has_value(), + "xrpl::SponsorshipTransfer::doApply : sfSponsor present for account sponsor " + "create/reassign"); + if (!newSponsor) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const newSponsorID = *newSponsor; + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + sponseeSle->getFieldAmount(sfBalance).xrp(), + newSponsorSle, + {.accountCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + if (isReassign) + { + auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Decrement old sponsoring count + if (auto const ter = + decrementSponsorCount(view(), oldSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + + // Increment new sponsoring count + if (auto const ter = + incrementSponsorCount(view(), newSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + + // Account is now sponsored by new sponsor + sponseeSle->setAccountID(sfSponsor, newSponsorID); + view().update(sponseeSle); + } + else if (ctx_.tx.isFlag(tfSponsorshipEnd)) + { + // End account sponsor + auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor); + if (!oldSponsorID) + return tefINTERNAL; // LCOV_EXCL_LINE + auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); + if (!oldSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // The sponsee must be able to hold its own account reserve after + // the sponsorship is removed. + if (auto const ter = checkReserve( + ctx_.getApplyViewContext(), + sponseeSle, + balanceBeforeFee(sponseeSle), + SLE::pointer(), + {.accountCountDelta = 1}, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + sponseeSle->makeFieldAbsent(sfSponsor); + view().update(sponseeSle); + + // Decrement account sponsoring count + if (auto const ter = + decrementSponsorCount(view(), oldSponsorSle, sfSponsoringAccountCount, 1); + !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + } + } + + return tesSUCCESS; +} + +void +SponsorshipTransfer::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) +{ +} + +bool +SponsorshipTransfer::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index ba54c14b1e..dee465cb88 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -227,6 +228,16 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } + if (ctx.tx.isFieldPresent(sfSponsorFlags)) + { + if (isReserveSponsored(ctx.tx)) + { + JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:" + << "spfSponsorReserve is not allowed on outer Batch."; + return temINVALID_FLAG; + } + } + auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions); if (rawTxns.size() <= 1) { @@ -331,6 +342,14 @@ Batch::preflight(PreflightContext const& ctx) return ret; } } + if (stx.isFieldPresent(sfSponsorSignature)) + { + auto const sponsorSignature = stx.getFieldObject(sfSponsorSignature); + if (auto const ret = checkSignatureFields(sponsorSignature, hash, "sponsor signature ")) + { + return ret; + } + } // Check that the Fee is native asset (XRP) and zero if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::kZero) @@ -341,6 +360,10 @@ Batch::preflight(PreflightContext const& ctx) return temBAD_FEE; } + // Disallow fee sponsorship on Batch inner txs + if (stx.isFieldPresent(sfSponsor) && isFeeSponsored(stx)) + return temINVALID_FLAG; + auto const innerAccount = stx.getAccountID(sfAccount); if (auto const preflightResult = xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, TapBatch, ctx.j); @@ -422,7 +445,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx) { // A delegated inner is signed by the delegate, not the account holder, // so the delegate is the required signer when present. - AccountID const authorizer = rb.getFeePayer(); + AccountID const authorizer = rb.getInitiator(); // The outer account signs the batch itself, so it is never added to the // required signers. @@ -433,6 +456,10 @@ Batch::preflightSigValidated(PreflightContext const& ctx) if (auto const counterparty = rb[~sfCounterparty]; counterparty && counterparty != outerAccount) requiredSigners.push_back(*counterparty); + + if (auto const sponsor = rb.at(~sfSponsor); + sponsor && rb.isFieldPresent(sfSponsorSignature) && sponsor != outerAccount) + requiredSigners.push_back(*sponsor); } std::ranges::sort(requiredSigners); auto const dupes = std::ranges::unique(requiredSigners); diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index 73a72c217d..e19dc9fe96 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -75,13 +75,12 @@ TicketCreate::doApply() // check the starting balance because we want to allow dipping into the // reserve to pay fees. std::uint32_t const ticketCount = ctx_.tx[sfTicketCount]; - { - XRPAmount const reserve = - view().fees().accountReserve(sleAccountRoot->getFieldU32(sfOwnerCount) + ticketCount); - - if (preFeeBalance_ < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (preFeeBalance_ < accountReserve( + view(), + sleAccountRoot, + j_, + {.ownerCountDelta = static_cast(ticketCount)})) + return tecINSUFFICIENT_RESERVE; beast::Journal const viewJ{ctx_.registry.get().getJournal("View")}; @@ -105,6 +104,7 @@ TicketCreate::doApply() sleTicket->setAccountID(sfAccount, accountID_); sleTicket->setFieldU32(sfTicketSequence, curTicketSeq); + view().insert(sleTicket); auto const page = view().dirInsert( @@ -125,7 +125,7 @@ TicketCreate::doApply() sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount); // Every added Ticket counts against the creator's reserve. - adjustOwnerCount(view(), sleAccountRoot, ticketCount, viewJ); + increaseOwnerCount(view(), sleAccountRoot, {}, ticketCount, viewJ); // TicketCreate is the only transaction that can cause an account root's // Sequence field to increase by more than one. October 2018. diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 6db37515cb..0aeb6f33d1 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -164,7 +164,7 @@ MPTokenAuthorize::doApply() { auto const& tx = ctx_.tx; return authorizeMPToken( - ctx_.view(), + ctx_.getApplyViewContext(), preFeeBalance_, tx[sfMPTokenIssuanceID], accountID_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index e30127b688..aad1642f68 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -113,22 +114,35 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) } std::expected -MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args) +MPTokenIssuanceCreate::create( + ApplyViewContext ctx, + beast::Journal journal, + MPTCreateArgs const& args) { - auto const acct = view.peek(keylet::account(args.account)); + auto const acct = ctx.view.peek(keylet::account(args.account)); if (!acct) return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE - if (args.priorBalance && - *(args.priorBalance) < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return std::unexpected(tecINSUFFICIENT_RESERVE); + // A reserve sponsor only covers tx.Account's own objects. + auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, acct); + if (!sponsorExp) + return std::unexpected(sponsorExp.error()); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + + if (args.priorBalance) + { + if (auto const ret = checkReserve( + ctx, acct, *(args.priorBalance), sponsorSle, {.ownerCountDelta = 1}, journal); + !isTesSuccess(ret)) + return std::unexpected(ret); + } auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptId); // create the MPTokenIssuance { - auto const ownerNode = view.dirInsert( + auto const ownerNode = ctx.view.dirInsert( keylet::ownerDir(args.account), mptIssuanceKeylet, describeOwnerDir(args.account)); if (!ownerNode) @@ -166,7 +180,7 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate // populate this after the pseudo-account's MPToken / // RippleState has been installed. A missing holding here // would dangle the pointer and is a programmer error. - auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding)); + auto const sleHolding = ctx.view.read(keylet::unchecked(*args.referenceHolding)); if (!sleHolding) return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE auto const type = sleHolding->getType(); @@ -175,11 +189,13 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate (*mptIssuance)[sfReferenceHolding] = *args.referenceHolding; } - view.insert(mptIssuance); + addSponsorToLedgerEntry(mptIssuance, sponsorSle); + + ctx.view.insert(mptIssuance); } // Update owner count. - adjustOwnerCount(view, acct, 1, journal); + increaseOwnerCount(ctx.view, acct, sponsorSle, 1, journal); return mptId; } @@ -189,7 +205,7 @@ MPTokenIssuanceCreate::doApply() { auto const& tx = ctx_.tx; auto const result = create( - view(), + ctx_.getApplyViewContext(), j_, { .priorBalance = preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index c56697767b..05d39c3596 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -49,10 +49,9 @@ MPTokenIssuanceDestroy::doApply() if (!view().dirRemove(keylet::ownerDir(accountID_), (*mpt)[sfOwnerNode], mpt->key(), false)) return tefBAD_LEDGER; // LCOV_EXCL_LINE + decreaseOwnerCountForObject(view(), accountID_, mpt, 1, j_); view().erase(mpt); - adjustOwnerCount(view(), view().peek(keylet::account(accountID_)), -1, j_); - return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index e79e3cad5a..3d078ce825 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -297,8 +298,6 @@ TrustSet::doApply() if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount); - // The reserve that is required to create the line. Note // that although the reserve increases with every item // an account owns, in the case of trust lines we only @@ -317,9 +316,23 @@ TrustSet::doApply() // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. + auto const sponsorExp = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!sponsorExp) + return sponsorExp.error(); // LCOV_EXCL_LINE + auto const sponsorSle = *sponsorExp; + + auto getSponsor = [&sponsorSle, this](AccountID const& account) { + return (sponsorSle && account == accountID_) ? sponsorSle : SLE::pointer(); + }; + + // The "free-tier" shortcut (ownerCount < 2) only applies when there is no sponsor. + // With any sponsor on the tx, the sponsor must cover the reserve (via balance or + // prefunded budget), so the reserve check always runs. + bool const freeTrustLine = !sponsorSle && (ownerCount(sle, j_) < 2); + std::uint32_t const uOwnerCount = ownerCount(sle, j_); XRPAmount const reserveCreate( (uOwnerCount < 2) ? XRPAmount(beast::kZero) - : view().fees().accountReserve(uOwnerCount + 1)); + : accountReserve(view(), sle, j_, {.ownerCountDelta = 1})); std::uint32_t const uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0); std::uint32_t uQualityOut(bQualityOut ? ctx_.tx.getFieldU32(sfQualityOut) : 0); @@ -505,6 +518,11 @@ TrustSet::doApply() bool bReserveIncrease = false; + auto const currentHighSponsor = + getLedgerEntryReserveSponsor(view(), sleRippleState, sfHighSponsor); + auto const currentLowSponsor = + getLedgerEntryReserveSponsor(view(), sleRippleState, sfLowSponsor); + if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); @@ -512,10 +530,30 @@ TrustSet::doApply() if (bLowReserveSet && !bLowReserved) { + SLE::pointer const lowSponsor = getSponsor(uLowAccountID); + + if (view().rules().enabled(featureSponsor)) + { + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleLowAccount, + preFeeBalance_, + lowSponsor, + {.ownerCountDelta = 1}, + j_, + tecINSUF_RESERVE_LINE); + lowSponsor && !isTesSuccess(ret)) + { + return ret; + } + } + // Set reserve for low account. - adjustOwnerCount(view(), sleLowAccount, 1, viewJ); + increaseOwnerCount(view(), sleLowAccount, lowSponsor, 1, viewJ); uFlagsOut |= lsfLowReserve; + addSponsorToLedgerEntry(sleRippleState, lowSponsor, sfLowSponsor); + if (!bHigh) bReserveIncrease = true; } @@ -523,16 +561,41 @@ TrustSet::doApply() if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. - adjustOwnerCount(view(), sleLowAccount, -1, viewJ); + decreaseOwnerCount(view(), sleLowAccount, currentLowSponsor, 1, viewJ); uFlagsOut &= ~lsfLowReserve; + + removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor); } if (bHighReserveSet && !bHighReserved) { + SLE::pointer const highSponsor = getSponsor(uHighAccountID); + + // should be checked PreFunded Sponsor before increaseOwnerCount() + // For PreFunded sponsors, we need to check if there are sufficient reserves before + // calling increaseOwnerCount(). + if (view().rules().enabled(featureSponsor)) + { + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleHighAccount, + preFeeBalance_, + highSponsor, + {.ownerCountDelta = 1}, + j_, + tecINSUF_RESERVE_LINE); + highSponsor && !isTesSuccess(ret)) + { + return ret; + } + } + // Set reserve for high account. - adjustOwnerCount(view(), sleHighAccount, 1, viewJ); + increaseOwnerCount(view(), sleHighAccount, highSponsor, 1, viewJ); uFlagsOut |= lsfHighReserve; + addSponsorToLedgerEntry(sleRippleState, highSponsor, sfHighSponsor); + if (bHigh) bReserveIncrease = true; } @@ -540,34 +603,75 @@ TrustSet::doApply() if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. - adjustOwnerCount(view(), sleHighAccount, -1, viewJ); + decreaseOwnerCount(view(), sleHighAccount, currentHighSponsor, 1, viewJ); uFlagsOut &= ~lsfHighReserve; + + removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor); } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32(sfFlags, uFlagsOut); - if (bDefault || badCurrency() == currency) + if (view().rules().enabled(featureSponsor)) { - // Delete. + if (bDefault || badCurrency() == currency) + { + // Delete. - terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); - } - // Reserve is not scaled by load. - else if (bReserveIncrease && preFeeBalance_ < reserveCreate) - { - JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " - "add trust line."; + terResult = + trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); + } + // Reserve is not scaled by load + else if ( + auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + sponsorSle, + {}, + j_, + tecINSUF_RESERVE_LINE); + !freeTrustLine && bReserveIncrease && !isTesSuccess(ret)) + { + JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " + "add trust line."; - // Another transaction could provide XRP to the account and then - // this transaction would succeed. - terResult = tecINSUF_RESERVE_LINE; + // Another transaction could provide XRP to the account and then + // this transaction would succeed. + terResult = ret; + } + else + { + view().update(sleRippleState); + + JLOG(j_.trace()) << "Modify ripple line"; + } } else { - view().update(sleRippleState); + if (bDefault || badCurrency() == currency) + { + // Delete. - JLOG(j_.trace()) << "Modify ripple line"; + terResult = + trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); + } + // Reserve is not scaled by load. + else if (bReserveIncrease && preFeeBalance_ < reserveCreate) + { + JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " + "add trust line."; + + // Another transaction could provide XRP to the account and then + // this transaction would succeed. + terResult = tecINSUF_RESERVE_LINE; + } + else + { + view().update(sleRippleState); + + JLOG(j_.trace()) << "Modify ripple line"; + } } } // Line does not exist. @@ -582,8 +686,8 @@ TrustSet::doApply() JLOG(j_.trace()) << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } - else if (preFeeBalance_ < reserveCreate) // Reserve is not scaled by - // load. + // reserve is not scaled by load + else if (!view().rules().enabled(featureSponsor) && preFeeBalance_ < reserveCreate) { JLOG(j_.trace()) << "Delay transaction: Line does not exist. " "Insufficent reserve to create line."; @@ -592,6 +696,25 @@ TrustSet::doApply() // transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } + else if ( + auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sle, + preFeeBalance_, + sponsorSle, + {.ownerCountDelta = 1}, + j_, + tecNO_LINE_INSUF_RESERVE); + view().rules().enabled(featureSponsor) && !freeTrustLine && + !isTesSuccess(ret)) // Reserve is not scaled by load. + { + JLOG(j_.trace()) << "Delay transaction: Line does not exist. " + "Insufficent reserve to create line."; + + // Another transaction could create the account and then this + // transaction would succeed. + terResult = ret; + } else { // Zero balance in currency. @@ -617,6 +740,7 @@ TrustSet::doApply() saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut, + sponsorSle, viewJ); } diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index 541660c98f..d77286b667 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -389,8 +389,8 @@ VaultClawback::doApply() auto const& vaultAccount = vault->at(sfAccount); // Transfer shares from holder to vault. - if (auto const ter = - accountSend(view(), holder, vaultAccount, sharesDestroyed, j_, WaiveTransferFee::Yes); + if (auto const ter = accountSend( + view(), holder, vaultAccount, sharesDestroyed, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -399,7 +399,8 @@ VaultClawback::doApply() // Keep MPToken if holder is the vault owner. if (holder != vault->at(sfOwner)) { - if (auto const ter = removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_); + if (auto const ter = + removeEmptyHolding(ctx_.getApplyViewContext(), holder, sharesDestroyed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // @@ -425,7 +426,7 @@ VaultClawback::doApply() { // Transfer assets from vault to issuer. if (auto const ter = accountSend( - view(), vaultAccount, accountID_, assetsRecovered, j_, WaiveTransferFee::Yes); + view(), vaultAccount, accountID_, assetsRecovered, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index d262132b6f..e1f5873a89 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -146,6 +146,7 @@ VaultCreate::doApply() // we can consider downgrading them to `tef` or `tem`. auto const& tx = ctx_.tx; + auto applyViewContext = ctx_.getApplyViewContext(); auto const sequence = tx.getSeqValue(); auto const owner = view().peek(keylet::account(accountID_)); if (owner == nullptr) @@ -156,9 +157,8 @@ VaultCreate::doApply() if (auto ter = dirLink(view(), accountID_, vault)) return ter; // We will create Vault and PseudoAccount, hence increase OwnerCount by 2 - adjustOwnerCount(view(), owner, 2, j_); - auto const ownerCount = owner->at(sfOwnerCount); - if (preFeeBalance_ < view().fees().accountReserve(ownerCount)) + increaseOwnerCount(view(), owner, {}, 2, j_); + if (preFeeBalance_ < accountReserve(view(), owner, j_)) return tecINSUFFICIENT_RESERVE; auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID); @@ -168,7 +168,8 @@ VaultCreate::doApply() AccountID const pseudoId = pseudo->at(sfAccount); auto const asset = tx[sfAsset]; - if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter)) + if (auto ter = addEmptyHolding(applyViewContext, pseudoId, preFeeBalance_, asset, j_); + !isTesSuccess(ter)) return ter; std::uint8_t const scale = (asset.holds() || asset.native()) @@ -197,7 +198,7 @@ VaultCreate::doApply() : keylet::trustLine(pseudoId, asset.get()).key; }(); auto const maybeShare = MPTokenIssuanceCreate::create( - view(), + applyViewContext, j_, { .priorBalance = std::nullopt, @@ -243,8 +244,8 @@ VaultCreate::doApply() view().insert(vault); // Explicitly create MPToken for the vault owner - if (auto const err = - authorizeMPToken(view(), preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); + if (auto const err = authorizeMPToken( + applyViewContext, preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); !isTesSuccess(err)) return err; @@ -252,7 +253,13 @@ VaultCreate::doApply() if (tx.isFlag(tfVaultPrivate)) { if (auto const err = authorizeMPToken( - view(), preFeeBalance_, mptIssuanceID, pseudoId, ctx_.journal, {}, accountID_); + applyViewContext, + preFeeBalance_, + mptIssuanceID, + pseudoId, + ctx_.journal, + {}, + accountID_); !isTesSuccess(err)) return err; } diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index fa38ae278b..497a2f2465 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -96,13 +96,15 @@ TER VaultDelete::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE // Destroy the asset holding. auto asset = vault->at(sfAsset); - if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); !isTesSuccess(ter)) + if (auto ter = removeEmptyHolding(applyViewContext, vault->at(sfAccount), asset, j_); + !isTesSuccess(ter)) return ter; auto const& pseudoID = vault->at(sfAccount); @@ -130,7 +132,8 @@ VaultDelete::doApply() // Try to remove MPToken for vault shares for the vault owner if it exists. if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, accountID_))) { - if (auto const ter = removeEmptyHolding(view(), accountID_, MPTIssue(shareMPTID), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, MPTIssue(shareMPTID), j_); !isTesSuccess(ter)) { // LCOV_EXCL_START @@ -151,7 +154,7 @@ VaultDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_STOP } - adjustOwnerCount(view(), pseudoAcct, -1, j_); + decreaseOwnerCountForObject(view(), pseudoAcct, mpt, 1, j_); view().erase(mpt); @@ -210,7 +213,7 @@ VaultDelete::doApply() } // We are destroying Vault and PseudoAccount, hence decrease by 2 - adjustOwnerCount(view(), owner, -2, j_); + decreaseOwnerCountForObject(view(), owner, vault, 2, j_); // Destroy the vault. view().erase(vault); diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index d5eb80b84d..aa9cfc8537 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -23,6 +23,7 @@ #include #include +#include #include namespace xrpl { @@ -196,6 +197,7 @@ VaultDeposit::doApply() { bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); @@ -230,7 +232,7 @@ VaultDeposit::doApply() if (vault->isFlag(lsfVaultPrivate) && accountID_ != vault->at(sfOwner)) { if (auto const err = enforceMPTokenAuthorization( - ctx_.view(), mptIssuanceID, accountID_, preFeeBalance_, j_); + applyViewContext, mptIssuanceID, accountID_, preFeeBalance_, j_); !isTesSuccess(err)) return err; } @@ -240,7 +242,11 @@ VaultDeposit::doApply() if (!view().exists(keylet::mptoken(mptIssuanceID, accountID_))) { if (auto const err = authorizeMPToken( - view(), preFeeBalance_, mptIssuanceID->value(), accountID_, ctx_.journal); + applyViewContext, + preFeeBalance_, + mptIssuanceID->value(), + accountID_, + ctx_.journal); !isTesSuccess(err)) return err; } @@ -252,7 +258,7 @@ VaultDeposit::doApply() XRPL_ASSERT( accountID_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner"); if (auto const err = authorizeMPToken( - view(), + applyViewContext, preFeeBalance_, // priorBalance mptIssuanceID->value(), // mptIssuanceID sleIssuance->at(sfIssuer), // account @@ -319,7 +325,7 @@ VaultDeposit::doApply() // Transfer assets from depositor to vault. if (auto const ter = accountSend( - view(), accountID_, vaultAccount, assetsDeposited, j_, WaiveTransferFee::Yes); + view(), accountID_, vaultAccount, assetsDeposited, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -346,8 +352,8 @@ VaultDeposit::doApply() } // Transfer shares from vault to depositor. - if (auto const ter = - accountSend(view(), vaultAccount, accountID_, sharesCreated, j_, WaiveTransferFee::Yes); + if (auto const ter = accountSend( + view(), vaultAccount, accountID_, sharesCreated, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index ad91c55723..353b72c30d 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -194,6 +194,7 @@ TER VaultWithdraw::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE @@ -349,9 +350,10 @@ VaultWithdraw::doApply() view().update(vault); auto const& vaultAccount = vault->at(sfAccount); + // Transfer shares from depositor to vault. if (auto const ter = accountSend( - view(), accountID_, vaultAccount, sharesRedeemed, j_, WaiveTransferFee::Yes); + view(), accountID_, vaultAccount, sharesRedeemed, j_, {}, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -360,7 +362,8 @@ VaultWithdraw::doApply() // Keep MPToken if holder is the vault owner. if (accountID_ != vault->at(sfOwner)) { - if (auto const ter = removeEmptyHolding(view(), accountID_, sharesRedeemed.asset(), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, sharesRedeemed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // @@ -386,7 +389,7 @@ VaultWithdraw::doApply() auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_); return doWithdraw( - view(), ctx_.tx, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); + applyViewContext, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); } void diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index 3c4a62bfd3..74080e669c 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -31,7 +31,8 @@ namespace xrpl::test { -/** AMM Calculator. Uses AMM formulas to simulate the payment engine +/** + * AMM Calculator. Uses AMM formulas to simulate the payment engine * expected results. Assuming the formulas are correct some unit-tests can * be verified. Currently supported operations are: * - swapIn, find out given in. in can flow through multiple AMM/Offer steps. diff --git a/src/test/app/AMMExtendedMPT_test.cpp b/src/test/app/AMMExtendedMPT_test.cpp index e6dcc95733..f04ea39f2b 100644 --- a/src/test/app/AMMExtendedMPT_test.cpp +++ b/src/test/app/AMMExtendedMPT_test.cpp @@ -463,7 +463,7 @@ private: // Provide micro amounts to compensate for fees to make results round // nice. auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(2) + env.current()->fees().base * 3; + XRP(100) + env.current()->fees().accountReserve(2, 1) + env.current()->fees().base * 3; env.fund(startingXrp, gw_, alice_); env.fund(XRP(2'000), bob_); @@ -2296,8 +2296,10 @@ private: // 1,400e12 - 56.3368e12*1.25 = 1400e12 - 70.4210e12 = // 1329.5789e12GBP env.require(Balance(alice_, gbp(1'329'578'947'368'420))); - //// 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 - ///= 70.4210e12ETH + /** + * / 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 + * = 70.4210e12ETH + */ // 56.3368e12GBP is swapped in for 53.3322e12ETH BEAST_EXPECT(amm.expectBalances( gbp(1'056'336'842'105'264), eth(946'667'729'591'836), amm.tokens())); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index a0a7d0fb15..bb532b361a 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -546,7 +546,7 @@ private: // 1 for each trust limit == 3 (alice_ < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(3) + env.current()->fees().base * 4; + XRP(100) + env.current()->fees().accountReserve(3, 1) + env.current()->fees().base * 4; env.fund(startingXrp, gw1, gw2, gw3, localAlice); env.fund(XRP(2'000), localBob); @@ -2420,8 +2420,10 @@ private: // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP BEAST_EXPECT( expectHolding(env, alice_, STAmount{GBP, UINT64_C(1'329'578947368421), -12})); - //// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 - ///= 70.4210EUR + /** + * / 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 + * = 70.4210EUR + */ // 56.3368GBP is swapped in for 53.3322EUR BEAST_EXPECT(amm.expectBalances( STAmount{GBP, UINT64_C(1'056'336842105263), -12}, @@ -2435,8 +2437,10 @@ private: // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP BEAST_EXPECT( expectHolding(env, alice_, STAmount{GBP, UINT64_C(1'329'57894736842), -11})); - //// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 - ///= 70.4210EUR + /** + * / 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 + * = 70.4210EUR + */ // 56.3368GBP is swapped in for 53.3322EUR BEAST_EXPECT(amm.expectBalances( STAmount{GBP, UINT64_C(1'056'336842105264), -12}, diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index 4877406ca1..bf0bc5c7d7 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -1002,7 +1002,7 @@ private: // Insufficient reserve, XRP/MPT { - Env env(*this); + Env env(*this, features); auto const startingXrp = reserve(env, 4) + env.current()->fees().base * 4; env.fund(XRP(10'000), gw_); env.fund(XRP(10'000), alice_); diff --git a/src/test/app/AccountSet_test.cpp b/src/test/app/AccountSet_test.cpp index 362113aec2..d94fb66e50 100644 --- a/src/test/app/AccountSet_test.cpp +++ b/src/test/app/AccountSet_test.cpp @@ -1,4 +1,3 @@ - #include #include #include @@ -453,7 +452,7 @@ public: // We'll insert a replacement for the account root // with the higher (currently invalid) transfer rate. - auto replacement = std::make_shared(*sle, sle->key()); + auto replacement = std::make_shared(*sle); (*replacement)[sfTransferRate] = static_cast(transferRate * QUALITY_ONE); view.rawReplace(replacement); diff --git a/src/test/app/CheckMPT_test.cpp b/src/test/app/CheckMPT_test.cpp index 861a115fc9..66cc582201 100644 --- a/src/test/app/CheckMPT_test.cpp +++ b/src/test/app/CheckMPT_test.cpp @@ -409,7 +409,7 @@ class CheckMPT_test : public beast::unit_test::Suite // Insufficient reserve. Account const cheri{"cheri"}; - env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), cheri); env(check::create(cheri, bob, usd(50)), Fee(drops(env.current()->fees().base)), @@ -793,15 +793,6 @@ class CheckMPT_test : public beast::unit_test::Suite env(check::create(alice, bob, usd(125))); env.close(); - // alice writes another check that won't get cashed until the transfer - // rate changes so we can see the rate applies when the check is - // cashed, not when it is created. -#if 0 - uint256 const chkId120{getCheckIndex(alice, env.Seq(alice))}; - env(check::create(alice, bob, USD(120))); - env.close(); -#endif - // bob attempts to cash the check for face value. Should fail. env(check::cash(bob, chkId125, usd(125)), Ter(tecPATH_PARTIAL)); env.close(); @@ -816,21 +807,6 @@ class CheckMPT_test : public beast::unit_test::Suite env.require(Balance(bob, usd(0 + 100))); BEAST_EXPECT(checksOnAccount(env, alice).empty()); BEAST_EXPECT(checksOnAccount(env, bob).empty()); - -#if 0 - // Adjust gw's rate... - env(rate(gw, 1.2)); - env.close(); - - // bob cashes the second check for less than the face value. The new - // rate applies to the actual value transferred. - env(check::cash(bob, chkId120, USD(50))); - env.close(); - env.Require(Balance(alice, USD(1000 - 125 - 60))); - env.Require(Balance(bob, USD(0 + 100 + 50))); - BEAST_EXPECT(checksOnAccount(env, alice).size() == 0); - BEAST_EXPECT(checksOnAccount(env, bob).size() == 0); -#endif } void @@ -1411,7 +1387,8 @@ class CheckMPT_test : public beast::unit_test::Suite return acct.id(); } - /** Create MPTTester if it doesn't exist for the given MPT. + /** + * Create MPTTester if it doesn't exist for the given MPT. * Increment owners if created since it creates MPTokenIssuance */ MPT diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 3ad1e3fa4e..b19a9f6894 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -470,7 +470,7 @@ class Check_test : public beast::unit_test::Suite // Insufficient reserve. Account const cheri{"cheri"}; - env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), cheri); env.close(); env(check::create(cheri, bob, usd(50)), diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 456a53bc01..1f6ec012c8 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -638,8 +638,8 @@ struct Credentials_test : public beast::unit_test::Suite { Env env{*this, features}; - env.fund(drops(env.current()->fees().accountReserve(1)), issuer); - env.fund(drops(env.current()->fees().accountReserve(0)), subject); + env.fund(drops(env.current()->fees().accountReserve(1, 1)), issuer); + env.fund(drops(env.current()->fees().accountReserve(0, 1)), subject); env.close(); { diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 6f1d2ce7de..257ed33619 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -275,7 +275,7 @@ class Delegate_test : public beast::unit_test::Suite Account const bob{"bob"}; auto const txFee = env.current()->fees().base; - env.fund(env.current()->fees().accountReserve(0) + txFee, alice); + env.fund(env.current()->fees().accountReserve(0, 1) + txFee, alice); env.fund(XRP(100000), bob); env.close(); @@ -292,7 +292,7 @@ class Delegate_test : public beast::unit_test::Suite auto const txFee = env.current()->fees().base; - env.fund(env.current()->fees().accountReserve(1) + (txFee * 4), alice); + env.fund(env.current()->fees().accountReserve(1, 1) + (txFee * 4), alice); env.fund(XRP(100000), bob, carol); env.close(); @@ -318,8 +318,8 @@ class Delegate_test : public beast::unit_test::Suite Account const alice{"alice"}; Account const bob{"bob"}; - env.fund(drops(env.current()->fees().accountReserve(1)), alice); - env.fund(drops(env.current()->fees().accountReserve(2)), bob); + env.fund(drops(env.current()->fees().accountReserve(1, 1)), alice); + env.fund(drops(env.current()->fees().accountReserve(2, 1)), bob); env.close(); // alice gives bob permission @@ -422,7 +422,7 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const reserve = env.current()->fees().accountReserve(1); + auto const reserve = env.current()->fees().accountReserve(1, 1); auto const paymentAmount = XRP(1); auto const highFee = reserve + baseFee; BEAST_EXPECT(highFee > reserve); @@ -488,9 +488,9 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const baseReserve = env.current()->fees().accountReserve(0); + auto const baseReserve = env.current()->fees().accountReserve(0, 1); - env.fund(env.current()->fees().accountReserve(1) + baseFee + XRP(1), alice); + env.fund(env.current()->fees().accountReserve(1, 1) + baseFee + XRP(1), alice); env.fund(baseReserve, bob); env.fund(XRP(1000), carol); env.close(); @@ -523,7 +523,7 @@ class Delegate_test : public beast::unit_test::Suite Account const carol{"carol"}; auto const baseFee = env.current()->fees().base; - auto const reserve = env.current()->fees().accountReserve(1); + auto const reserve = env.current()->fees().accountReserve(1, 1); // Alice is funded with (reserve + baseFee): after DelegateSet she has // exactly 'reserve', which is insufficient to send XRP(10) while keeping @@ -2612,7 +2612,9 @@ class Delegate_test : public beast::unit_test::Suite {"CredentialDelete", featureCredentials}, {"NFTokenModify", featureDynamicNFT}, {"PermissionedDomainSet", featurePermissionedDomains}, - {"PermissionedDomainDelete", featurePermissionedDomains}}; + {"PermissionedDomainDelete", featurePermissionedDomains}, + {"SponsorshipSet", featureSponsor}, + }; // Can not delegate tx if any required feature disabled. { @@ -2747,7 +2749,7 @@ class Delegate_test : public beast::unit_test::Suite // DO NOT modify expectedDelegableCount unless all scenarios, including // edge cases, have been fully tested and verified. // ==================================================================== - std::size_t const expectedDelegableCount = 56; + std::size_t const expectedDelegableCount = 57; BEAST_EXPECTS( delegableCount == expectedDelegableCount, diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 3496e67b54..c75bdeaf3a 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -51,7 +51,7 @@ namespace xrpl::test { static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns true if acct has the lsfDepositAuth flag set. @@ -1025,7 +1025,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite { // not enough reserve Account const john{"john"}; - env.fund(env.current()->fees().accountReserve(0), john); + env.fund(env.current()->fees().accountReserve(0, 1), john); env.close(); auto jv = deposit::authCredentials(john, {{.issuer = issuer, .credType = credType}}); diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 666ee498bd..4015f5ddc8 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -937,7 +937,7 @@ struct EscrowToken_test : public beast::unit_test::Suite auto const expectedResult = env.current()->rules().enabled(fixCleanup3_2_0) ? Ter(tesSUCCESS) - : Ter(tefEXCEPTION); + : Ter(tefINTERNAL); env(escrow::cancel(alice, alice, seq), Fee(baseFee), expectedResult); env.close(); diff --git a/src/test/app/FlowMPT_test.cpp b/src/test/app/FlowMPT_test.cpp index 39d722da65..302e55a2cc 100644 --- a/src/test/app/FlowMPT_test.cpp +++ b/src/test/app/FlowMPT_test.cpp @@ -726,7 +726,7 @@ struct FlowMPT_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns the Offers on an account. diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 72e39ab890..8d5162394e 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -706,7 +706,7 @@ struct Flow_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } // Helper function that returns the Offers on an account. diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 79fdbb54ed..076e39a42b 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -101,7 +101,8 @@ class Invariants_test : public beast::unit_test::Suite return xrpl::test::jtx::testableAmendments() | fixCleanup3_1_3 | fixCleanup3_2_0; } - /** Run a specific test case to put the ledger into a state that will be + /** + * Run a specific test case to put the ledger into a state that will be * detected by an invariant. Simulates the actions of a transaction that * would violate an invariant. * @@ -337,7 +338,89 @@ class Invariants_test : public beast::unit_test::Suite // check. sleA1->at(sfBalance) = beast::kZero; BEAST_EXPECT(sleA1->at(sfOwnerCount) == 0); - adjustOwnerCount(ac.view(), sleA1, 1, ac.journal); + increaseOwnerCount(ac.view(), sleA1, {}, 1, ac.journal); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoredOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoringOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const a1Id = a1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1Id)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setFieldU32(sfSponsoringAccountCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setAccountID(sfSponsor, a2.id()); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + Env{*this, FeatureBitset{featureSponsor}}, + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sleA1 = ac.view().peek(keylet::account(a1.id())); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::kZero; + sleA1->setAccountID(sfSponsor, a2.id()); ac.view().erase(sleA1); @@ -1771,6 +1854,7 @@ class Invariants_test : public beast::unit_test::Suite "pseudo-account sequence changed" "pseudo-account flags are not set" "pseudo-account has a regular key" + "pseudo-account has a sponsorship field" */ struct Mod { @@ -1798,6 +1882,22 @@ class Invariants_test : public beast::unit_test::Suite .expectedFailure = "pseudo-account has a regular key", .func = [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); }, }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoredOwnerCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoringOwnerCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsoringAccountCount) = 1; }, + }, + { + .expectedFailure = "pseudo-account has a sponsorship field", + .func = [](SLE::pointer& sle) { sle->at(sfSponsor) = Account("sponsor").id(); }, + }, }); for (auto const& mod : mods) @@ -2658,7 +2758,7 @@ class Invariants_test : public beast::unit_test::Suite } void - testVault() + testVault() // NOLINT(readability-function-size) { using namespace test::jtx; @@ -4358,6 +4458,525 @@ class Invariants_test : public beast::unit_test::Suite return true; }); + // Invalid IOU clawback delta must fail once MPTokensV2 enforces before/after validation. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline clawback balance change is invalid"}}, + [issuer, usd](Account const& holder, Account const&, ApplyContext& ac) { + auto sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + if (!sle) + return false; + + STAmount balance{Issue{usd.currency, issuer.id()}, 80}; + if (holder.id() > issuer.id()) + balance.negate(); + sle->setFieldAmount(sfBalance, balance); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // Full IOU clawback may delete the trustline; missing after-SLE represents zero balance. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {}, + [issuer, usd](Account const& holder, Account const&, ApplyContext& ac) { + auto const sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + if (!sle) + return false; + + ac.view().erase(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 100}; + }}, + {tesSUCCESS, tesSUCCESS}); + } + + // Pre-MPTokensV2 invalid IOU clawback delta logs but remains non-enforcing. + { + Env env(*this, defaultAmendments() - featureMPTokensV2); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline clawback balance change is invalid"}}, + [issuer, usd](Account const& holder, Account const&, ApplyContext& ac) { + auto sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + if (!sle) + return false; + + STAmount balance{Issue{usd.currency, issuer.id()}, 80}; + if (holder.id() > issuer.id()) + balance.negate(); + sle->setFieldAmount(sfBalance, balance); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 10}; + }}, + {tesSUCCESS, tesSUCCESS}); + } + + // Invalid MPT clawback delta must fail when raw MPToken debit mismatches sfAmount. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: MPT clawback balance change is invalid"}}, + [id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + + sleToken->setFieldU64(sfMPTAmount, 80); + sleIssuance->setFieldU64(sfOutstandingAmount, 80); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfHolder] = holder.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // A clawback that mutates both IOU and MPT entries must fail under MPTokensV2. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline and MPToken both changed"}}, + [issuer, usd, id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleLine = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder.id())); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleLine || !sleToken || !sleIssuance) + return false; + + STAmount balance{Issue{usd.currency, issuer.id()}, 90}; + if (holder.id() > issuer.id()) + balance.negate(); + sleLine->setFieldAmount(sfBalance, balance); + sleToken->setFieldU64(sfMPTAmount, 90); + sleIssuance->setFieldU64(sfOutstandingAmount, 90); + ac.view().update(sleLine); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfHolder] = holder.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // Clawback that modifies a trustline other than the one implied by the + // tx amount: clawbackTrustLineBalanceInHolderTerms returns nullopt for + // the mismatched line. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + auto const eur = issuer["EUR"]; + env.trust(eur(100), holder); + env(pay(issuer, holder, eur(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline clawback changed the wrong line"}}, + [issuer, eur](Account const& holder, Account const&, ApplyContext& ac) { + auto sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), eur.currency)); + if (!sle) + return false; + STAmount balance{Issue{eur.currency, issuer.id()}, 90}; + if (holder.id() > issuer.id()) + balance.negate(); + sle->setFieldAmount(sfBalance, balance); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // Clawback leaving the holder's balance negative. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline or MPT balance is negative"}}, + [issuer, usd](Account const& holder, Account const&, ApplyContext& ac) { + auto sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + if (!sle) + return false; + // Make the holder's balance negative from their perspective. + STAmount balance{Issue{usd.currency, issuer.id()}, 80}; + if (holder.id() < issuer.id()) + balance.negate(); + sle->setFieldAmount(sfBalance, balance); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // IOU-amount clawback while only an MPToken changed: no trustline was + // recorded, so iou_.before is empty. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline clawback changed the wrong line"}}, + [id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + sleToken->setFieldU64(sfMPTAmount, 90); + sleIssuance->setFieldU64(sfOutstandingAmount, 90); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // Valid trustline change but a zero clawback amount. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + auto const usd = issuer["USD"]; + env.trust(usd(100), holder); + env(pay(issuer, holder, usd(100))); + env.close(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: trustline clawback amount is invalid"}}, + [issuer, usd](Account const& holder, Account const&, ApplyContext& ac) { + auto sle = + ac.view().peek(keylet::trustLine(holder.id(), issuer.id(), usd.currency)); + if (!sle) + return false; + STAmount balance{Issue{usd.currency, issuer.id()}, 90}; + if (holder.id() > issuer.id()) + balance.negate(); + sle->setFieldAmount(sfBalance, balance); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{Issue{usd.currency, holder.id()}, 0}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // MPT clawback tx missing the Holder field. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: MPT clawback missing holder"}}, + [id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + sleToken->setFieldU64(sfMPTAmount, 90); + sleIssuance->setFieldU64(sfOutstandingAmount, 90); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // MPT clawback where the holder's MPToken was deleted (after is empty). + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: MPT clawback token is missing"}}, + [id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + // Keep the issuance consistent after removing the token. + sleIssuance->setFieldU64(sfOutstandingAmount, 0); + ac.view().update(sleIssuance); + ac.view().erase(sleToken); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfHolder] = holder.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // MPT clawback that changed a different holder's MPToken. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + MPTTester const mpt( + {.env = env, + .issuer = issuer, + .holders = {holder, other}, + .pay = 100, + .maxAmt = 200}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: MPT clawback changed the wrong token"}}, + [id](Account const&, Account const& other, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, other)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + sleToken->setFieldU64(sfMPTAmount, 90); + sleIssuance->setFieldU64(sfOutstandingAmount, 190); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfHolder] = holder.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 10}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // Valid MPToken change but a zero MPT clawback amount. + { + Env env(*this, defaultAmendments()); + Account const issuer{"issuer"}; + Account const holder{"holder"}; + Account const other{"other"}; + env.fund(XRP(1'000), issuer, holder, other); + MPTTester const mpt( + {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100}); + auto const id = mpt.issuanceID(); + + doInvariantCheck( + std::move(env), + holder, + other, + {{"Invariant failed: MPT clawback amount is invalid"}}, + [id](Account const& holder, Account const&, ApplyContext& ac) { + auto const sleToken = ac.view().peek(keylet::mptoken(id, holder)); + auto const sleIssuance = ac.view().peek(keylet::mptokenIssuance(id)); + if (!sleToken || !sleIssuance) + return false; + sleToken->setFieldU64(sfMPTAmount, 90); + sleIssuance->setFieldU64(sfOutstandingAmount, 90); + ac.view().update(sleToken); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ + ttCLAWBACK, + [&](STObject& tx) { + tx[sfAccount] = issuer.id(); + tx[sfHolder] = holder.id(); + tx[sfAmount] = STAmount{MPTIssue{id}, 0}; + }}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + // More MPTokens created than expected std::array, 4> const tests = { std::make_pair(ttAMM_WITHDRAW, 2), @@ -4948,11 +5567,10 @@ class Invariants_test : public beast::unit_test::Suite std::vector values; }; - for (auto const mantissaScale : { - MantissaRange::MantissaScale::LargeLegacy, - MantissaRange::MantissaScale::Large, - }) + for (auto const mantissaScale : MantissaRange::getAllScales()) { + if (mantissaScale == MantissaRange::MantissaScale::Small) + continue; NumberMantissaScaleGuard const g{mantissaScale}; auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo { @@ -5065,6 +5683,111 @@ class Invariants_test : public beast::unit_test::Suite } } + void + testSponsorship() + { + using namespace test::jtx; + using namespace std::string_literals; + testcase("Sponsorship"); + { + auto const expectMessage = + "SponsoredOwnerCount does not equal SponsoringOwnerCount delta."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoredOwnerCount, 1); + ac.view().update(sle); + return true; + }); + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoringOwnerCount, 1); + ac.view().update(sle); + return true; + }); + } + + { + auto const expectMessage = + "OwnerCount must be greater than or equal to SponsoredOwnerCount."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfOwnerCount, 0); + sle->setFieldU32(sfSponsoredOwnerCount, 1); + ac.view().update(sle); + + auto const sle2 = ac.view().peek(keylet::account(a2.id())); + if (!sle2) + return false; + sle2->setFieldU32(sfSponsoringOwnerCount, 1); + ac.view().update(sle2); + return true; + }); + } + + { + auto const expectMessage = + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; + uint256 checkID; + + doInvariantCheck( + {{expectMessage}}, + [&](Account const&, Account const& a2, ApplyContext& ac) { + auto const check = ac.view().peek(keylet::check(checkID)); + if (!check) + return false; + check->setAccountID(sfSponsor, a2.id()); + ac.view().update(check); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject&) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&checkID](Account const& a1, Account const& a2, Env& env) { + checkID = keylet::check(a1.id(), env.seq(a1)).key; + env(check::create(a1, a2, XRP(1))); + return true; + }); + } + + { + auto const expectMessage = + "Invariant failed: Net delta of SponsoringAccountCount does " + "not match net delta of sfSponsor presence."; + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setFieldU32(sfSponsoringAccountCount, 1); + ac.view().update(sle); + return true; + }); + + doInvariantCheck( + {{expectMessage}}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + sle->setAccountID(sfSponsor, a2.id()); + ac.view().update(sle); + return true; + }); + } + } + void testObjectHasPseudoAccount() { @@ -5467,6 +6190,7 @@ public: testVaultComputeCoarsestScale(); testAMM(); testObjectHasPseudoAccount(); + testSponsorship(); } }; diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 3d0e546678..f8688899e0 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -29,13 +29,13 @@ namespace xrpl::test { class LedgerHistory_test : public beast::unit_test::Suite { public: - /** Generate a new ledger by hand, applying a specific close time offset - and optionally inserting a transaction. - - If prev is nullptr, then the genesis ledger is made and no offset or - transaction is applied. - - */ + /** + * Generate a new ledger by hand, applying a specific close time offset + * and optionally inserting a transaction. + * + * If prev is nullptr, then the genesis ledger is made and no offset or + * transaction is applied. + */ static std::shared_ptr makeLedger( std::shared_ptr const& prev, diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 2a4de18a9c..f6f85a0cca 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #include #include +#include #include #include #include @@ -1078,7 +1080,7 @@ class LoanBroker_test : public beast::unit_test::Suite } auto const amt = - env.balance(alice) - env.current()->fees().accountReserve(env.ownerCount(alice)); + env.balance(alice) - accountReserve(*env.current(), alice.id(), env.journal); env(pay(alice, issuer, amt)); // preclaim:: tecINSUFFICIENT_RESERVE @@ -1436,18 +1438,77 @@ class LoanBroker_test : public beast::unit_test::Suite env(tx2, Ter(temINVALID)); } + env.setParseFailureExpected(true); + try { - auto const dm = power(2, 63) - 1; - BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm)); - tx2[sfDebtMaximum] = dm; + tx2[sfDebtMaximum] = "9223372036854775808"; env(tx2, Ter(temINVALID)); + // should throw in parser + fail(); } - + catch (std::exception const& e) { - auto const dm = power(2, 63) - 3; - BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm)); - tx2[sfDebtMaximum] = dm; - env(tx2, Ter(tesSUCCESS)); + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.DebtMaximum' has invalid data."); + } + env.setParseFailureExpected(false); + + if (Number::getMantissaScale() >= MantissaRange::MantissaScale::Large330) + { + // For the Large330 scale, 2^63 rounds _down_ to Number::kMaxRep + { + auto const dm = power(2, 63); + BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(tesSUCCESS)); + } + + { + auto const dm = power(2, 63) + Number{1, -1}; + BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(tesSUCCESS)); + } + + { + auto const dm = power(2, 63) - 1; + BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(tesSUCCESS)); + } + + { + auto const dm = power(2, 63) - 3; + BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(tesSUCCESS)); + } + + { + auto const dm = power(2, 63) + 3; + BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(temINVALID)); + } + } + else + { + // For other scales, 2^63 rounds _up_ to Number::kMaxRepUp. Subtracting 1 rounds up + // again. + { + auto const dm = power(2, 63) - 1; + BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(temINVALID)); + } + + { + auto const dm = power(2, 63) - 3; + BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, Ter(tesSUCCESS)); + } } { diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 3e62af48ff..371fcae54f 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -322,7 +324,8 @@ protected: TenthBips32 const interestRate{}; }; - /** Helper class to compare the expected state of a loan and loan broker + /** + * Helper class to compare the expected state of a loan and loan broker * against the data in the ledger. */ struct VerifyLoanStatus @@ -342,7 +345,8 @@ protected: { } - /** Checks the expected broker state against the ledger + /** + * Checks the expected broker state against the ledger */ void checkBroker( @@ -413,7 +417,9 @@ protected: } } - /** Checks both the loan and broker expect states against the ledger */ + /** + * Checks both the loan and broker expect states against the ledger + */ void operator()( std::uint32_t previousPaymentDate, @@ -473,7 +479,9 @@ protected: } } - /** Checks both the loan and broker expect states against the ledger */ + /** + * Checks both the loan and broker expect states against the ledger + */ void operator()(LoanState const& state) const { @@ -539,7 +547,9 @@ protected: return {asset, keylet, vaultKeylet, params}; } - /// Get the state without checking anything + /** + * Get the state without checking anything + */ LoanState getCurrentState(jtx::Env const& env, BrokerInfo const& broker, Keylet const& loanKeylet) { @@ -567,8 +577,10 @@ protected: return LoanState{}; } - /// Get the state and check the values against the parameters used in - /// `lifecycle` + /** + * Get the state and check the values against the parameters used in + * `lifecycle` + */ LoanState getCurrentState( jtx::Env const& env, @@ -671,11 +683,11 @@ protected: case AssetType::MPT: { // Enough to cover initial fees if (!env.le(keylet::account(issuer))) - env.fund(env.current()->fees().accountReserve(10) * 10, issuer); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, issuer); if (!env.le(keylet::account(lender))) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(lender)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(lender)); if (!env.le(keylet::account(borrower))) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(borrower)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(borrower)); MPTTester mptt{env, issuer, kMptInitNoFund}; mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); @@ -760,11 +772,11 @@ protected: using namespace jtx; // Enough to cover initial fees - env.fund(env.current()->fees().accountReserve(10) * 10, issuer); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, issuer); if (lender != issuer) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(lender)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(lender)); if (borrower != issuer && borrower != lender) - env.fund(env.current()->fees().accountReserve(10) * 10, noripple(borrower)); + env.fund(env.current()->fees().accountReserve(10, 1) * 10, noripple(borrower)); describeLoan(env, brokerParams, loanParams, assetType, issuer, lender, borrower); @@ -841,11 +853,10 @@ protected: // Add extra for transaction fees and reserves, if appropriate, or a // tiny amount for the extra paid in each transaction auto const totalNeeded = state.totalValue + (serviceFee * state.paymentRemaining) + - (broker.asset.native() - ? Number( - baseFee * state.paymentRemaining + - env.current()->fees().accountReserve(env.ownerCount(borrower))) - : broker.asset(15).number()); + (broker.asset.native() ? Number( + baseFee * state.paymentRemaining + + accountReserve(*env.current(), borrower.id(), env.journal)) + : broker.asset(15).number()); auto const shortage = totalNeeded - borrowerBalance.number(); @@ -1241,7 +1252,8 @@ protected: PaymentParameters{.showStepBalances = true}); } - /** Runs through the complete lifecycle of a loan + /** + * Runs through the complete lifecycle of a loan * * 1. Create a loan. * 2. Test a bunch of transaction failure conditions. @@ -1559,7 +1571,8 @@ protected: return "Unknown"; } - /** Wrapper to run a series of lifecycle tests for a given asset and loan + /** + * Wrapper to run a series of lifecycle tests for a given asset and loan * amount * * Will be used in the future to vary the loan parameters. For now, it is @@ -3071,7 +3084,7 @@ protected: auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -4419,11 +4432,12 @@ protected: Account const lender{"lender"}; Account const issuer{"issuer"}; Account const borrower{"borrower"}; + Account const sponsor{"sponsor"}; auto const iou = issuer["IOU"]; auto testWrapper = [&](auto&& test) { Env env(*this); - env.fund(XRP(1'000), lender, issuer, borrower); + env.fund(XRP(1'000), lender, issuer, borrower, sponsor); env(trust(lender, iou(10'000'000))); env(pay(issuer, lender, iou(5'000'000))); BrokerInfo const brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)}; @@ -4438,6 +4452,15 @@ protected: BrokerInfo const& brokerInfo, jtx::Fee const& loanSetFee, Number const& debtMaximumRequest) { + for (auto const sponsorFlags : {spfSponsorReserve, spfSponsorReserve | spfSponsorFee}) + { + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sponsor::As(sponsor, sponsorFlags), + Sig(sfCounterpartySignature, lender), + loanSetFee, + Ter(temINVALID_FLAG)); + } + // first temBAD_SIGNER: TODO // invalid grace period { @@ -4528,8 +4551,8 @@ protected: BrokerInfo const& brokerInfo, jtx::Fee const& loanSetFee, Number const& debtMaximumRequest) { - auto const amt = env.balance(borrower) - - env.current()->fees().accountReserve(env.ownerCount(borrower)); + auto const amt = + env.balance(borrower) - accountReserve(*env.current(), borrower.id(), env.journal); env(pay(borrower, issuer, amt)); // tecINSUFFICIENT_RESERVE diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index bbf1c54ab1..befc46e2ae 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2108,6 +2108,15 @@ class MPToken_test : public beast::unit_test::Suite reward = STAmount{sfSignatureReward, usd(10)}; minAmount = STAmount{sfMinAccountCreateAmount, mpt}; } + // SponsorshipSet + { + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = alice.human(); + jv[sfSponsee.fieldName] = carol.human(); + jv[sfFeeAmount.fieldName] = mpt.getJson(JsonOptions::Values::None); + test(jv, sfFeeAmount.fieldName); + } } BEAST_EXPECT(txWithAmounts.empty()); } @@ -7563,7 +7572,7 @@ class MPToken_test : public beast::unit_test::Suite void testFixDoubleOwnerCount(FeatureBitset all) { - testcase("Fix Double adjustOwnerCount in AMMWithdraw"); + testcase("Fix Double OwnerCount in AMMWithdraw"); using namespace jtx; diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index 19bf58f247..7dd0b14fe5 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -1052,508 +1052,3 @@ BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDir, app, xrpl, 1); // // sp6JS7f14BuwFY8MwFe95Vpi9Znjs // - -// Sets of related accounts. -// -// Identifying the seeds of accounts that generate account IDs with the -// same low 32 bits takes a while. However several sets of accounts with -// that relationship have been located. In case these sets of accounts are -// needed for future testing scenarios they are recorded below. -#if 0 -34 account seeds that produce account IDs with low 32-bits 0x399187e9: - sp6JS7f14BuwFY8Mw5EYu5z86hKDL - sp6JS7f14BuwFY8Mw5PUAMwc5ygd7 - sp6JS7f14BuwFY8Mw5R3xUBcLSeTs - sp6JS7f14BuwFY8Mw5W6oS5sdC3oF - sp6JS7f14BuwFY8Mw5pYc3D9iuLcw - sp6JS7f14BuwFY8Mw5pfGVnhcdp3b - sp6JS7f14BuwFY8Mw6jS6RdEqXqrN - sp6JS7f14BuwFY8Mw6krt6AKbvRXW - sp6JS7f14BuwFY8Mw6mnVBQq7cAN2 - sp6JS7f14BuwFY8Mw8ECJxPjmkufQ - sp6JS7f14BuwFY8Mw8asgzcceGWYm - sp6JS7f14BuwFY8MwF6J3FXnPCgL8 - sp6JS7f14BuwFY8MwFEud2w5czv5q - sp6JS7f14BuwFY8MwFNxKVqJnx8P5 - sp6JS7f14BuwFY8MwFnTCXg3eRidL - sp6JS7f14BuwFY8Mwj47hv1vrDge6 - sp6JS7f14BuwFY8Mwj6TYekeeyukh - sp6JS7f14BuwFY8MwjFjsRDerz7jb - sp6JS7f14BuwFY8Mwjrj9mHTLBrcX - sp6JS7f14BuwFY8MwkKcJi3zMzAea - sp6JS7f14BuwFY8MwkYTDdnYRm9z4 - sp6JS7f14BuwFY8Mwkq8ei4D8uPNd - sp6JS7f14BuwFY8Mwm2pFruxbnJRd - sp6JS7f14BuwFY8MwmJV2ZnAjpC2g - sp6JS7f14BuwFY8MwmTFMPHQHfVYF - sp6JS7f14BuwFY8MwmkG2jXEgqiud - sp6JS7f14BuwFY8Mwms3xEh5tMDTw - sp6JS7f14BuwFY8MwmtipW4D8giZ9 - sp6JS7f14BuwFY8MwoRQBZm4KUUeE - sp6JS7f14BuwFY8MwoVey94QpXcrc - sp6JS7f14BuwFY8MwoZiuUoUTo3VG - sp6JS7f14BuwFY8MwonFFDLT4bHAZ - sp6JS7f14BuwFY8MwooGphD4hefBQ - sp6JS7f14BuwFY8MwoxDp3dmX6q5N - -34 account seeds that produce account IDs with low 32-bits 0x473f2c9a: - sp6JS7f14BuwFY8Mw53ktgqmv5Bmz - sp6JS7f14BuwFY8Mw5KPb2Kz7APFX - sp6JS7f14BuwFY8Mw5Xx4A6HRTPEE - sp6JS7f14BuwFY8Mw5y6qZFNAo358 - sp6JS7f14BuwFY8Mw6kdaBg1QrZfn - sp6JS7f14BuwFY8Mw8QmTfLMAZ5K1 - sp6JS7f14BuwFY8Mw8cbRRVcCEELr - sp6JS7f14BuwFY8Mw8gQvJebmxvDG - sp6JS7f14BuwFY8Mw8qPQurwu3P7Y - sp6JS7f14BuwFY8MwFS4PEVKmuPy5 - sp6JS7f14BuwFY8MwFUQM1rAsQ8tS - sp6JS7f14BuwFY8MwjJBZCkuwsRnM - sp6JS7f14BuwFY8MwjTdS8vZhX5E9 - sp6JS7f14BuwFY8MwjhSmWCbNhd25 - sp6JS7f14BuwFY8MwjwkpqwZsDBw9 - sp6JS7f14BuwFY8MwjyET4p6eqd5J - sp6JS7f14BuwFY8MwkMNAe4JhnG7E - sp6JS7f14BuwFY8MwkRRpnT93UWWS - sp6JS7f14BuwFY8MwkY9CvB22RvUe - sp6JS7f14BuwFY8Mwkhw9VxXqmTr7 - sp6JS7f14BuwFY8MwkmgaTat7eFa7 - sp6JS7f14BuwFY8Mwkq5SxGGv1oLH - sp6JS7f14BuwFY8MwmCBM5p5bTg6y - sp6JS7f14BuwFY8MwmmmXaVah64dB - sp6JS7f14BuwFY8Mwo7R7Cn614v9V - sp6JS7f14BuwFY8MwoCAG1na7GR2M - sp6JS7f14BuwFY8MwoDuPvJS4gG7C - sp6JS7f14BuwFY8MwoMMowSyPQLfy - sp6JS7f14BuwFY8MwoRqDiwTNsTBm - sp6JS7f14BuwFY8MwoWbBWtjpB7pg - sp6JS7f14BuwFY8Mwoi1AEeELGecF - sp6JS7f14BuwFY8MwopGP6Lo5byuj - sp6JS7f14BuwFY8MwoufkXGHp2VW8 - sp6JS7f14BuwFY8MwowGeagFQY32k - -34 account seeds that produce account IDs with low 32-bits 0x4d59f0d1: - sp6JS7f14BuwFY8Mw5CsNgH64zxK7 - sp6JS7f14BuwFY8Mw5Dg4wi2E344h - sp6JS7f14BuwFY8Mw5ErV949Zh2PX - sp6JS7f14BuwFY8Mw5p4nsQvEUE1s - sp6JS7f14BuwFY8Mw8LGnkbaP68Gn - sp6JS7f14BuwFY8Mw8aq6RCBc3iHo - sp6JS7f14BuwFY8Mw8bkWaGoKYT6e - sp6JS7f14BuwFY8Mw8qrCuXnzAXVj - sp6JS7f14BuwFY8MwFDKcPAHPHJTm - sp6JS7f14BuwFY8MwFUXJs4unfgNu - sp6JS7f14BuwFY8MwFj9Yv5LjshD9 - sp6JS7f14BuwFY8Mwj3H73nmq5UaC - sp6JS7f14BuwFY8MwjHSYShis1Yhk - sp6JS7f14BuwFY8MwjpfE1HVo8UP1 - sp6JS7f14BuwFY8Mwk6JE1SXUuiNc - sp6JS7f14BuwFY8MwkASgxEjEnFmU - sp6JS7f14BuwFY8MwkGNY8kg7R6RK - sp6JS7f14BuwFY8MwkHinNZ8SYBQu - sp6JS7f14BuwFY8MwkXLCW1hbhGya - sp6JS7f14BuwFY8MwkZ7mWrYK9YtU - sp6JS7f14BuwFY8MwkdFSqNB5DbKL - sp6JS7f14BuwFY8Mwm3jdBaCAx8H6 - sp6JS7f14BuwFY8Mwm3rk5hEwDRtY - sp6JS7f14BuwFY8Mwm77a2ULuwxu4 - sp6JS7f14BuwFY8MwmJpY7braKLaN - sp6JS7f14BuwFY8MwmKHQjG4XiZ6g - sp6JS7f14BuwFY8Mwmmv8Y3wyUDzs - sp6JS7f14BuwFY8MwmucFe1WgqtwG - sp6JS7f14BuwFY8Mwo1EjdU1bznZR - sp6JS7f14BuwFY8MwoJiqankkU5uR - sp6JS7f14BuwFY8MwoLnvQ6zdqbKw - sp6JS7f14BuwFY8MwoUGeJ319eu48 - sp6JS7f14BuwFY8MwoYf135tQjHP4 - sp6JS7f14BuwFY8MwogeF6M6SAyid - -34 account seeds that produce account IDs with low 32-bits 0xabb11898: - sp6JS7f14BuwFY8Mw5DgiYaNVSb1G - sp6JS7f14BuwFY8Mw5k6e94TMvuox - sp6JS7f14BuwFY8Mw5tTSN7KzYxiT - sp6JS7f14BuwFY8Mw61XV6m33utif - sp6JS7f14BuwFY8Mw87jKfrjiENCb - sp6JS7f14BuwFY8Mw8AFtxxFiRtJG - sp6JS7f14BuwFY8Mw8cosAVExzbeE - sp6JS7f14BuwFY8Mw8fmkQ63zE8WQ - sp6JS7f14BuwFY8Mw8iYSsxNbDN6D - sp6JS7f14BuwFY8Mw8wTZdGRJyyM1 - sp6JS7f14BuwFY8Mw8z7xEh3qBGr7 - sp6JS7f14BuwFY8MwFL5gpKQWZj7g - sp6JS7f14BuwFY8MwFPeZchXQnRZ5 - sp6JS7f14BuwFY8MwFSPxWSJVoU29 - sp6JS7f14BuwFY8MwFYyVkqX8kvRm - sp6JS7f14BuwFY8MwFcbVikUEwJvk - sp6JS7f14BuwFY8MwjF7NcZk1NctK - sp6JS7f14BuwFY8MwjJCwYr9zSfAv - sp6JS7f14BuwFY8MwjYa5yLkgCLuT - sp6JS7f14BuwFY8MwjenxuJ3TH2Bc - sp6JS7f14BuwFY8MwjriN7Ui11NzB - sp6JS7f14BuwFY8Mwk3AuoJNSEo34 - sp6JS7f14BuwFY8MwkT36hnRv8hTo - sp6JS7f14BuwFY8MwkTQixEXfi1Cr - sp6JS7f14BuwFY8MwkYJaZM1yTJBF - sp6JS7f14BuwFY8Mwkc4k1uo85qp2 - sp6JS7f14BuwFY8Mwkf7cFhF1uuxx - sp6JS7f14BuwFY8MwmCK2un99wb4e - sp6JS7f14BuwFY8MwmETztNHYu2Bx - sp6JS7f14BuwFY8MwmJws9UwRASfR - sp6JS7f14BuwFY8MwoH5PQkGK8tEb - sp6JS7f14BuwFY8MwoVXtP2yCzjJV - sp6JS7f14BuwFY8MwobxRXA9vsTeX - sp6JS7f14BuwFY8Mwos3pc5Gb3ihU - -34 account seeds that produce account IDs with low 32-bits 0xce627322: - sp6JS7f14BuwFY8Mw5Ck6i83pGNh3 - sp6JS7f14BuwFY8Mw5FKuwTxjAdH1 - sp6JS7f14BuwFY8Mw5FVKkEn6TkLH - sp6JS7f14BuwFY8Mw5NbQwLwHDd5v - sp6JS7f14BuwFY8Mw5X1dbz3msZaZ - sp6JS7f14BuwFY8Mw6qv6qaXNeP74 - sp6JS7f14BuwFY8Mw81SXagUeutCw - sp6JS7f14BuwFY8Mw84Ph7Qa8kwwk - sp6JS7f14BuwFY8Mw8Hp4gFyU3Qko - sp6JS7f14BuwFY8Mw8Kt8bAKredSx - sp6JS7f14BuwFY8Mw8XHK3VKRQ7v7 - sp6JS7f14BuwFY8Mw8eGyWxZGHY6v - sp6JS7f14BuwFY8Mw8iU5CLyHVcD2 - sp6JS7f14BuwFY8Mw8u3Zr26Ar914 - sp6JS7f14BuwFY8MwF2Kcdxtjzjv8 - sp6JS7f14BuwFY8MwFLmPWb6rbxNg - sp6JS7f14BuwFY8MwFUu8s7UVuxuJ - sp6JS7f14BuwFY8MwFYBaatwHxAJ8 - sp6JS7f14BuwFY8Mwjg6hFkeHwoqG - sp6JS7f14BuwFY8MwjjycJojy2ufk - sp6JS7f14BuwFY8MwkEWoxcSKGPXv - sp6JS7f14BuwFY8MwkMe7wLkEUsQT - sp6JS7f14BuwFY8MwkvyKLaPUc4FS - sp6JS7f14BuwFY8Mwm8doqXPKZmVQ - sp6JS7f14BuwFY8Mwm9r3No8yQ8Tx - sp6JS7f14BuwFY8Mwm9w6dks68W9B - sp6JS7f14BuwFY8MwmMPrv9sCdbpS - sp6JS7f14BuwFY8MwmPAvs3fcQNja - sp6JS7f14BuwFY8MwmS5jasapfcnJ - sp6JS7f14BuwFY8MwmU2L3qJEhnuA - sp6JS7f14BuwFY8MwoAQYmiBnW7fM - sp6JS7f14BuwFY8MwoBkkkXrPmkKF - sp6JS7f14BuwFY8MwonfmxPo6tkvC - sp6JS7f14BuwFY8MwouZFwhiNcYq6 - -34 account seeds that produce account IDs with low 32-bits 0xe29643e8: - sp6JS7f14BuwFY8Mw5EfAavcXAh2k - sp6JS7f14BuwFY8Mw5LhFjLkFSCVF - sp6JS7f14BuwFY8Mw5bRfEv5HgdBh - sp6JS7f14BuwFY8Mw5d6sPcKzypKN - sp6JS7f14BuwFY8Mw5rcqDtk1fACP - sp6JS7f14BuwFY8Mw5xkxRq1Notzv - sp6JS7f14BuwFY8Mw66fbkdw5WYmt - sp6JS7f14BuwFY8Mw6diEG8sZ7Fx7 - sp6JS7f14BuwFY8Mw6v2r1QhG7xc1 - sp6JS7f14BuwFY8Mw6zP6DHCTx2Fd - sp6JS7f14BuwFY8Mw8B3n39JKuFkk - sp6JS7f14BuwFY8Mw8FmBvqYw7uqn - sp6JS7f14BuwFY8Mw8KEaftb1eRwu - sp6JS7f14BuwFY8Mw8WJ1qKkegj9N - sp6JS7f14BuwFY8Mw8r8cAZEkq2BS - sp6JS7f14BuwFY8MwFKPxxwF65gZh - sp6JS7f14BuwFY8MwFKhaF8APcN5H - sp6JS7f14BuwFY8MwFN2buJn4BgYC - sp6JS7f14BuwFY8MwFUTe175MjP3x - sp6JS7f14BuwFY8MwFZhmRDb53NNb - sp6JS7f14BuwFY8MwFa2Azn5nU2WS - sp6JS7f14BuwFY8MwjNNt91hwgkn7 - sp6JS7f14BuwFY8MwjdiYt6ChACe7 - sp6JS7f14BuwFY8Mwk5qFVQ48Mmr9 - sp6JS7f14BuwFY8MwkGvCj7pNf1zG - sp6JS7f14BuwFY8MwkY9UcN2D2Fzs - sp6JS7f14BuwFY8MwkpGvSk9G9RyT - sp6JS7f14BuwFY8MwmGQ7nJf1eEzV - sp6JS7f14BuwFY8MwmQLjGsYdyAmV - sp6JS7f14BuwFY8MwmZ8usztKvikT - sp6JS7f14BuwFY8MwobyMLC2hQdFR - sp6JS7f14BuwFY8MwoiRtwUecZeJ5 - sp6JS7f14BuwFY8MwojHjKsUzj1KJ - sp6JS7f14BuwFY8Mwop29anGAjidU - -33 account seeds that produce account IDs with low 32-bits 0x115d0525: - sp6JS7f14BuwFY8Mw56vZeiBuhePx - sp6JS7f14BuwFY8Mw5BodF9tGuTUe - sp6JS7f14BuwFY8Mw5EnhC1cg84J7 - sp6JS7f14BuwFY8Mw5P913Cunr2BK - sp6JS7f14BuwFY8Mw5Pru7eLo1XzT - sp6JS7f14BuwFY8Mw61SLUC8UX2m8 - sp6JS7f14BuwFY8Mw6AsBF9TpeMpq - sp6JS7f14BuwFY8Mw84XqrBZkU2vE - sp6JS7f14BuwFY8Mw89oSU6dBk3KB - sp6JS7f14BuwFY8Mw89qUKCyDmyzj - sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm - sp6JS7f14BuwFY8Mw8LtW3VqrqMks - sp6JS7f14BuwFY8Mw8ZrAkJc2sHew - sp6JS7f14BuwFY8Mw8jpkYSNrD3ah - sp6JS7f14BuwFY8MwF2mshd786m3V - sp6JS7f14BuwFY8MwFHfXq9x5NbPY - sp6JS7f14BuwFY8MwFrjWq5LAB8NT - sp6JS7f14BuwFY8Mwj4asgSh6hQZd - sp6JS7f14BuwFY8Mwj7ipFfqBSRrE - sp6JS7f14BuwFY8MwjHqtcvGav8uW - sp6JS7f14BuwFY8MwjLp4sk5fmzki - sp6JS7f14BuwFY8MwjioHuYb3Ytkx - sp6JS7f14BuwFY8MwkRjHPXWi7fGN - sp6JS7f14BuwFY8MwkdVdPV3LjNN1 - sp6JS7f14BuwFY8MwkxUtVY5AXZFk - sp6JS7f14BuwFY8Mwm4jQzdfTbY9F - sp6JS7f14BuwFY8MwmCucYAqNp4iF - sp6JS7f14BuwFY8Mwo2bgdFtxBzpF - sp6JS7f14BuwFY8MwoGwD7v4U6qBh - sp6JS7f14BuwFY8MwoUczqFADMoXi - sp6JS7f14BuwFY8MwoY1xZeGd3gAr - sp6JS7f14BuwFY8MwomVCbfkv4kYZ - sp6JS7f14BuwFY8MwoqbrPSr4z13F - -33 account seeds that produce account IDs with low 32-bits 0x304033aa: - sp6JS7f14BuwFY8Mw5DaUP9agF5e1 - sp6JS7f14BuwFY8Mw5ohbtmPN4yGN - sp6JS7f14BuwFY8Mw5rRsA5fcoTAQ - sp6JS7f14BuwFY8Mw6zpYHMY3m6KT - sp6JS7f14BuwFY8Mw86BzQq4sTnoW - sp6JS7f14BuwFY8Mw8CCpnfvmGdV7 - sp6JS7f14BuwFY8Mw8DRjUDaBcFco - sp6JS7f14BuwFY8Mw8cL7GPo3zZN7 - sp6JS7f14BuwFY8Mw8y6aeYVtH6qt - sp6JS7f14BuwFY8MwFZR3PtVTCdUH - sp6JS7f14BuwFY8MwFcdcdbgz7m3s - sp6JS7f14BuwFY8MwjdnJDiUxEBRR - sp6JS7f14BuwFY8MwjhxWgSntqrFe - sp6JS7f14BuwFY8MwjrSHEhZ8CUM1 - sp6JS7f14BuwFY8MwjzkEeSTc9ZYf - sp6JS7f14BuwFY8MwkBZSk9JhaeCB - sp6JS7f14BuwFY8MwkGfwNY4i2iiU - sp6JS7f14BuwFY8MwknjtZd2oU2Ff - sp6JS7f14BuwFY8Mwkszsqd3ok9NE - sp6JS7f14BuwFY8Mwm58A81MAMvgZ - sp6JS7f14BuwFY8MwmiPTWysuDJCH - sp6JS7f14BuwFY8MwmxhiNeLfD76r - sp6JS7f14BuwFY8Mwo7SPdkwpGrFH - sp6JS7f14BuwFY8MwoANq4F1Sj3qH - sp6JS7f14BuwFY8MwoVjcHufAkd6L - sp6JS7f14BuwFY8MwoVxHBXdaxzhm - sp6JS7f14BuwFY8MwoZ2oTjBNfLpm - sp6JS7f14BuwFY8Mwoc9swzyotFVD - sp6JS7f14BuwFY8MwogMqVRwVEcQ9 - sp6JS7f14BuwFY8MwohMm7WxwnFqH - sp6JS7f14BuwFY8MwopUcpZHuF8BH - sp6JS7f14BuwFY8Mwor6rW6SS7tiB - sp6JS7f14BuwFY8MwoxyaqYz4Ngsb - -33 account seeds that produce account IDs with low 32-bits 0x42d4e09c: - sp6JS7f14BuwFY8Mw58NSZH9EaUxQ - sp6JS7f14BuwFY8Mw5JByk1pgPpL7 - sp6JS7f14BuwFY8Mw5YrJJuXnkHVB - sp6JS7f14BuwFY8Mw5kZe2ZzNSnKR - sp6JS7f14BuwFY8Mw6eXHTsbwi1U7 - sp6JS7f14BuwFY8Mw6gqN7HHDDKSh - sp6JS7f14BuwFY8Mw6zw8L1sSSR53 - sp6JS7f14BuwFY8Mw8E4WqSKKbksy - sp6JS7f14BuwFY8MwF3V9gemqJtND - sp6JS7f14BuwFY8Mwj4j46LHWZuY6 - sp6JS7f14BuwFY8MwjF5i8vh4Ezjy - sp6JS7f14BuwFY8MwjJZpEKgMpUAt - sp6JS7f14BuwFY8MwjWL7LfnzNUuh - sp6JS7f14BuwFY8Mwk7Y1csGuqAhX - sp6JS7f14BuwFY8MwkB1HVH17hN5W - sp6JS7f14BuwFY8MwkBntH7BZZupu - sp6JS7f14BuwFY8MwkEy4rMbNHG9P - sp6JS7f14BuwFY8MwkKz4LYesZeiN - sp6JS7f14BuwFY8MwkUrXyo9gMDPM - sp6JS7f14BuwFY8MwkV2hySsxej1G - sp6JS7f14BuwFY8MwkozhTVN12F9C - sp6JS7f14BuwFY8MwkpkzGB3sFJw5 - sp6JS7f14BuwFY8Mwks3zDZLGrhdn - sp6JS7f14BuwFY8MwktG1KCS7L2wW - sp6JS7f14BuwFY8Mwm1jVFsafwcYx - sp6JS7f14BuwFY8Mwm8hmrU6g5Wd6 - sp6JS7f14BuwFY8MwmFvstfRF7e2f - sp6JS7f14BuwFY8MwmeRohi6m5fs8 - sp6JS7f14BuwFY8MwmmU96RHUaRZL - sp6JS7f14BuwFY8MwoDFzteYqaUh4 - sp6JS7f14BuwFY8MwoPkTf5tDykPF - sp6JS7f14BuwFY8MwoSbMaDtiMoDN - sp6JS7f14BuwFY8MwoVL1vY1CysjR - -33 account seeds that produce account IDs with low 32-bits 0x9a8ebed3: - sp6JS7f14BuwFY8Mw5FnqmbciPvH6 - sp6JS7f14BuwFY8Mw5MBGbyMSsXLp - sp6JS7f14BuwFY8Mw5S4PnDyBdKKm - sp6JS7f14BuwFY8Mw6kcXpM2enE35 - sp6JS7f14BuwFY8Mw6tuuSMMwyJ44 - sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt - sp6JS7f14BuwFY8Mw8WwdgWkCHhEx - sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ - sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ - sp6JS7f14BuwFY8Mw8fdSwLCZWDFd - sp6JS7f14BuwFY8Mw8zuF6Fg65i1E - sp6JS7f14BuwFY8MwF2k7bihVfqes - sp6JS7f14BuwFY8MwF6X24WXGn557 - sp6JS7f14BuwFY8MwFMpn7strjekg - sp6JS7f14BuwFY8MwFSdy9sYVrwJs - sp6JS7f14BuwFY8MwFdMcLy9UkrXn - sp6JS7f14BuwFY8MwFdbwFm1AAboa - sp6JS7f14BuwFY8MwFdr5AhKThVtU - sp6JS7f14BuwFY8MwjFc3Q9YatvAw - sp6JS7f14BuwFY8MwjRXcNs1ozEXn - sp6JS7f14BuwFY8MwkQGUKL7v1FBt - sp6JS7f14BuwFY8Mwkamsoxx1wECt - sp6JS7f14BuwFY8Mwm3hus1dG6U8y - sp6JS7f14BuwFY8Mwm589M8vMRpXF - sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3 - sp6JS7f14BuwFY8MwmRfy8fer4QbL - sp6JS7f14BuwFY8MwmkkFx1HtgWRx - sp6JS7f14BuwFY8MwmwP9JFdKa4PS - sp6JS7f14BuwFY8MwoXWJLB3ciHfo - sp6JS7f14BuwFY8MwoYc1gTtT2mWL - sp6JS7f14BuwFY8MwogXtHH7FNVoo - sp6JS7f14BuwFY8MwoqYoA9P8gf3r - sp6JS7f14BuwFY8MwoujwMJofGnsA - -33 account seeds that produce account IDs with low 32-bits 0xa1dcea4a: - sp6JS7f14BuwFY8Mw5Ccov2N36QTy - sp6JS7f14BuwFY8Mw5CuSemVb5p7w - sp6JS7f14BuwFY8Mw5Ep8wpsTfpSz - sp6JS7f14BuwFY8Mw5WtutJc2H45M - sp6JS7f14BuwFY8Mw6vsDeaSKeUJZ - sp6JS7f14BuwFY8Mw83t5BPWUAzzF - sp6JS7f14BuwFY8Mw8FYGnK35mgkV - sp6JS7f14BuwFY8Mw8huo1x5pfKKJ - sp6JS7f14BuwFY8Mw8mPStxfMDrZa - sp6JS7f14BuwFY8Mw8yC3A7aQJytK - sp6JS7f14BuwFY8MwFCWCDmo9o3t8 - sp6JS7f14BuwFY8MwFjapa4gKxPhR - sp6JS7f14BuwFY8Mwj8CWtG29uw71 - sp6JS7f14BuwFY8MwjHyU5KpEMLVT - sp6JS7f14BuwFY8MwjMZSN7LZuWD8 - sp6JS7f14BuwFY8Mwja2TXJNBhKHU - sp6JS7f14BuwFY8Mwjf3xNTopHKTF - sp6JS7f14BuwFY8Mwjn5RAhedPeuM - sp6JS7f14BuwFY8MwkJdr4d6QoE8K - sp6JS7f14BuwFY8MwkmBryo3SUoLm - sp6JS7f14BuwFY8MwkrPdsc4tR8yw - sp6JS7f14BuwFY8Mwkttjcw2a65Fi - sp6JS7f14BuwFY8Mwm19n3rSaNx5S - sp6JS7f14BuwFY8Mwm3ryr4Xp2aQX - sp6JS7f14BuwFY8MwmBnDmgnJLB6B - sp6JS7f14BuwFY8MwmHgPjzrYjthq - sp6JS7f14BuwFY8MwmeV55DAnWKdd - sp6JS7f14BuwFY8Mwo49hK6BGrauT - sp6JS7f14BuwFY8Mwo56vfKY9aoWu - sp6JS7f14BuwFY8MwoU7tTTXLQTrh - sp6JS7f14BuwFY8MwoXpogSF2KaZB - sp6JS7f14BuwFY8MwoY9JYQAR16pc - sp6JS7f14BuwFY8MwoozLzKNAEXKM - -33 account seeds that produce account IDs with low 32-bits 0xbd2116db: - sp6JS7f14BuwFY8Mw5GrpkmPuA3Bw - sp6JS7f14BuwFY8Mw5r1sLoQJZDc6 - sp6JS7f14BuwFY8Mw68zzRmezLdd6 - sp6JS7f14BuwFY8Mw6jDSyaiF1mRp - sp6JS7f14BuwFY8Mw813wU9u5D6Uh - sp6JS7f14BuwFY8Mw8BBvpf2JFGoJ - sp6JS7f14BuwFY8Mw8F7zXxAiT263 - sp6JS7f14BuwFY8Mw8XG7WuVGHP2N - sp6JS7f14BuwFY8Mw8eyWrcz91cz6 - sp6JS7f14BuwFY8Mw8yNVKFVYyk9u - sp6JS7f14BuwFY8MwF2oA6ePqvZWP - sp6JS7f14BuwFY8MwF9VkcSNh3keq - sp6JS7f14BuwFY8MwFYsMWajgEf2j - sp6JS7f14BuwFY8Mwj3Gu43jYoJ4n - sp6JS7f14BuwFY8MwjJ5iRmYDHrW4 - sp6JS7f14BuwFY8MwjaUSSga93CiM - sp6JS7f14BuwFY8MwjxgLh2FY4Lvt - sp6JS7f14BuwFY8Mwk9hQdNZUgmTB - sp6JS7f14BuwFY8MwkcMXqtFp1sMx - sp6JS7f14BuwFY8MwkzZCDc56jsUB - sp6JS7f14BuwFY8Mwm5Zz7fP24Qym - sp6JS7f14BuwFY8MwmDWqizXSoJRG - sp6JS7f14BuwFY8MwmKHmkNYdMqqi - sp6JS7f14BuwFY8MwmRfAWHxWpGNK - sp6JS7f14BuwFY8MwmjCdXwyhphZ1 - sp6JS7f14BuwFY8MwmmukDAm1w6FL - sp6JS7f14BuwFY8Mwmmz2SzaR9TRH - sp6JS7f14BuwFY8Mwmz2z5mKHXzfn - sp6JS7f14BuwFY8Mwo2xNe5629r5k - sp6JS7f14BuwFY8MwoKy8tZxZrfJw - sp6JS7f14BuwFY8MwoLyQ9aMsq8Dm - sp6JS7f14BuwFY8MwoqqYkewuyZck - sp6JS7f14BuwFY8MwouvvhREVp6Pp - -33 account seeds that produce account IDs with low 32-bits 0xd80df065: - sp6JS7f14BuwFY8Mw5B7ERyhAfgHA - sp6JS7f14BuwFY8Mw5VuW3cF7bm2v - sp6JS7f14BuwFY8Mw5py3t1j7YbFT - sp6JS7f14BuwFY8Mw5qc84SzB6RHr - sp6JS7f14BuwFY8Mw5vGHW1G1hAy8 - sp6JS7f14BuwFY8Mw6gVa8TYukws6 - sp6JS7f14BuwFY8Mw8K9w1RoUAv1w - sp6JS7f14BuwFY8Mw8KvKtB7787CA - sp6JS7f14BuwFY8Mw8Y7WhRbuFzRq - sp6JS7f14BuwFY8Mw8cipw7inRmMn - sp6JS7f14BuwFY8MwFM5fAUNLNB13 - sp6JS7f14BuwFY8MwFSe1zAsht3X3 - sp6JS7f14BuwFY8MwFYNdigqQuHZM - sp6JS7f14BuwFY8MwjWkejj7V4V5Q - sp6JS7f14BuwFY8Mwjd2JGpsjvynq - sp6JS7f14BuwFY8Mwjg1xkducn751 - sp6JS7f14BuwFY8Mwjsp6LnaJvL1W - sp6JS7f14BuwFY8MwjvSbLc9593yH - sp6JS7f14BuwFY8Mwjw2h5wx7U6vZ - sp6JS7f14BuwFY8MwjxKUjtRsmPLH - sp6JS7f14BuwFY8Mwk1Yy8ginDfqv - sp6JS7f14BuwFY8Mwk2HrWhWwZP12 - sp6JS7f14BuwFY8Mwk4SsqiexvpWs - sp6JS7f14BuwFY8Mwk66zCs5ACpE6 - sp6JS7f14BuwFY8MwkCwx6vY97Nwh - sp6JS7f14BuwFY8MwknrbjnhTTWU8 - sp6JS7f14BuwFY8MwkokDy2ShRzQx - sp6JS7f14BuwFY8Mwm3BxnRPNxsuu - sp6JS7f14BuwFY8MwmY9EWdQQsFVr - sp6JS7f14BuwFY8MwmYTWjrDhmk8S - sp6JS7f14BuwFY8Mwo9skXt9Y5BVS - sp6JS7f14BuwFY8MwoZYKZybJ1Crp - sp6JS7f14BuwFY8MwoyXqkhySfSmF - -33 account seeds that produce account IDs with low 32-bits 0xe2e44294: - sp6JS7f14BuwFY8Mw53dmvTgNtBwi - sp6JS7f14BuwFY8Mw5Wrxsqn6WrXW - sp6JS7f14BuwFY8Mw5fGDT31RCXgC - sp6JS7f14BuwFY8Mw5nKRkubwrLWM - sp6JS7f14BuwFY8Mw5nXMajwKjriB - sp6JS7f14BuwFY8Mw5xZybggrC9NG - sp6JS7f14BuwFY8Mw5xea8f6dBMV5 - sp6JS7f14BuwFY8Mw5zDGofAHy5Lb - sp6JS7f14BuwFY8Mw6eado41rQNVG - sp6JS7f14BuwFY8Mw6yqKXQsQJPuU - sp6JS7f14BuwFY8Mw83MSN4FDzSGH - sp6JS7f14BuwFY8Mw8B3pUbzQqHe2 - sp6JS7f14BuwFY8Mw8WwRLnhBRvfk - sp6JS7f14BuwFY8Mw8hDBpKbpJwJX - sp6JS7f14BuwFY8Mw8jggRSZACe7M - sp6JS7f14BuwFY8Mw8mJRpU3qWbwC - sp6JS7f14BuwFY8MwFDnVozykN21u - sp6JS7f14BuwFY8MwFGGRGY9fctgv - sp6JS7f14BuwFY8MwjKznfChH9DQb - sp6JS7f14BuwFY8MwjbC5GvngRCk6 - sp6JS7f14BuwFY8Mwk3Lb7FPe1629 - sp6JS7f14BuwFY8MwkCeS41BwVrBD - sp6JS7f14BuwFY8MwkDnnvRyuWJ7d - sp6JS7f14BuwFY8MwkbkRNnzDEFpf - sp6JS7f14BuwFY8MwkiNhaVhGNk6v - sp6JS7f14BuwFY8Mwm1X4UJXRZx3p - sp6JS7f14BuwFY8Mwm7da9q5vfq7J - sp6JS7f14BuwFY8MwmPLqfBPrHw5H - sp6JS7f14BuwFY8MwmbJpxvVjEwm2 - sp6JS7f14BuwFY8MwoAVeA7ka37cD - sp6JS7f14BuwFY8MwoTFFTAwFKmVM - sp6JS7f14BuwFY8MwoYsne51VpDE3 - sp6JS7f14BuwFY8MwohLVnU1VTk5h - -#endif // 0 diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 844bb4ee87..acd54ae26a 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -452,7 +452,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite // Just for sanity's sake we'll check that the current value // of sfMintedNFTokens matches what we expect. - auto replacement = std::make_shared(*sle, sle->key()); + auto replacement = std::make_shared(*sle); if (replacement->getFieldU32(sfMintedNFTokens) != 1) return false; // Unexpected test conditions. diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index 800b14877a..d03b1b8e93 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -59,7 +59,7 @@ class OfferMPT_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } static std::uint32_t @@ -1794,7 +1794,8 @@ public: // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const base = env.current()->fees().base; - auto const startingXrp = XRP(100) + env.current()->fees().accountReserve(3) + base * 4; + auto const startingXrp = + XRP(100) + env.current()->fees().accountReserve(3, 1) + base * 4; env.fund(startingXrp, gw1, gw2, gw3, alice, bob); env.close(); @@ -1814,7 +1815,8 @@ public: env(offer(alice, usD1(200), XRP(200))); BEAST_EXPECT(env.balance(alice, usD1) == usD1(100)); - BEAST_EXPECT(env.balance(alice) == STAmount(env.current()->fees().accountReserve(3))); + BEAST_EXPECT( + env.balance(alice) == STAmount(env.current()->fees().accountReserve(3, 1))); BEAST_EXPECT(env.balance(bob, usD1) == usD1(400)); }; @@ -1865,7 +1867,7 @@ public: auto const bob = Account{"bob"}; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); @@ -1884,7 +1886,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryMPT(env, bob, usd); BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "400"); @@ -1904,7 +1906,7 @@ public: auto const bob = Account{"bob"}; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); @@ -1925,7 +1927,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryMPT(env, bob, usd); BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "300"); @@ -1946,7 +1948,7 @@ public: auto const base = env.current()->fees().base; auto const startingXrp = - XRP(100.1) + env.current()->fees().accountReserve(1) + base * 2; + XRP(100.1) + env.current()->fees().accountReserve(1, 1) + base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 2724a6474b..7fc7161e36 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -60,7 +60,7 @@ class OfferBaseUtil_test : public beast::unit_test::Suite static XRPAmount reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } static std::uint32_t @@ -1962,7 +1962,7 @@ public: // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) + // 1 for payment == 4 auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(3) + env.current()->fees().base * 4; + XRP(100) + env.current()->fees().accountReserve(3, 1) + env.current()->fees().base * 4; env.fund(startingXrp, gw1, gw2, gw3, alice, bob); env.close(); @@ -1985,7 +1985,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(3)).getText()); + STAmount(env.current()->fees().accountReserve(3, 1)).getText()); jrr = ledgerEntryState(env, bob, gw1, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400"); @@ -2045,7 +2045,7 @@ public: auto const usd = gw["USD"]; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); @@ -2066,7 +2066,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryState(env, bob, gw, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400"); @@ -2087,7 +2087,7 @@ public: auto const usd = gw["USD"]; auto const startingXrp = - XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + XRP(100) + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); @@ -2110,7 +2110,7 @@ public: jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - STAmount(env.current()->fees().accountReserve(1)).getText()); + STAmount(env.current()->fees().accountReserve(1, 1)).getText()); jrr = ledgerEntryState(env, bob, gw, "USD"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-300"); @@ -2131,8 +2131,8 @@ public: auto const xts = gw["XTS"]; auto const xxx = gw["XXX"]; - auto const startingXrp = - XRP(100.1) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + auto const startingXrp = XRP(100.1) + env.current()->fees().accountReserve(1, 1) + + env.current()->fees().base * 2; env.fund(startingXrp, gw, alice, bob); env.close(); diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index 7e7f5e9bd0..b8d9fb32d1 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -60,7 +60,7 @@ private: // Insufficient reserve { Env env(*this); - env.fund(env.current()->fees().accountReserve(0), owner); + env.fund(env.current()->fees().accountReserve(0, 1), owner); Oracle const oracle( env, {.owner = owner, @@ -71,7 +71,7 @@ private: { Env env(*this); env.fund( - env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner); + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2, owner); Oracle oracle( env, {.owner = owner, .fee = static_cast(env.current()->fees().base.drops())}); BEAST_EXPECT(oracle.exists()); @@ -640,7 +640,7 @@ private: Env env(*this); auto const baseFee = static_cast(env.current()->fees().base.drops()); env.fund( - env.current()->fees().accountReserve(1) + env.current()->fees().base * 2, owner); + env.current()->fees().accountReserve(1, 1) + env.current()->fees().base * 2, owner); Oracle oracle(env, {.owner = owner, .fee = baseFee}); oracle.set(UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee}); } diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 967f84f275..2a4b884668 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -126,12 +127,7 @@ class ElementComboIter [[nodiscard]] bool hasAny(std::initializer_list sb) const { - for (auto const s : sb) - { - if (has(s)) - return true; - } - return false; + return std::ranges::any_of(sb, [this](auto const s) { return has(s); }); } [[nodiscard]] size_t diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 8578b8a9ba..998b7b1c7f 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -82,13 +83,9 @@ class PermissionedDEX_test : public beast::unit_test::Suite return false; auto const& indexes = page->getFieldV256(sfIndexes); - for (auto const& index : indexes) - { - if (index == keylet::offer(account, offerSeq).key) - return true; - } - - return false; + return std::ranges::any_of(indexes, [&](auto const& index) { + return index == keylet::offer(account, offerSeq).key; + }); }; auto const sle = env.le(keylet::offer(account.id(), offerSeq)); diff --git a/src/test/app/ReducedOffer_test.cpp b/src/test/app/ReducedOffer_test.cpp index 47a2c1294c..dea197590c 100644 --- a/src/test/app/ReducedOffer_test.cpp +++ b/src/test/app/ReducedOffer_test.cpp @@ -144,20 +144,6 @@ public: Quality(Amounts{tweakedTakerPays, reducedTakerGets}).rate(); BEAST_EXPECT(tweakedRate > initialRate); } -#if 0 - std::cout << "Placed rate: " << initialRate - << "; in-ledger rate: " << inLedgerRate - << "; TakerPays: " << reducedTakerPays - << "; TakerGets: " << reducedTakerGets - << "; bob already got: " << bobGot << std::endl; -// #else - std::string_view filler = - inLedgerRate > initialRate ? "**" : " "; - std::cout << "| `" << reducedTakerGets << "` | `" - << reducedTakerPays << "` | `" << initialRate - << "` | " << filler << "`" << inLedgerRate << "`" - << filler << " |`" << std::endl; -#endif } // In preparation for the next iteration make sure the two @@ -275,21 +261,6 @@ public: Quality(Amounts{tweakedTakerPays, reducedTakerGets}).rate(); BEAST_EXPECT(tweakedRate > initialRate); } -#if 0 - std::cout << "Placed rate: " << initialRate - << "; in-ledger rate: " << inLedgerRate - << "; TakerPays: " << reducedTakerPays - << "; TakerGets: " << reducedTakerGets - << "; alice already got: " << aliceGot - << std::endl; -// #else - std::string_view filler = badRate ? "**" : " "; - std::cout << "| `" << reducedTakerGets << "` | `" - << reducedTakerPays << "` | `" << initialRate - << "` | " << filler << "`" << inLedgerRate << "`" - << filler << " | `" << aliceGot << "` |" - << std::endl; -#endif } // In preparation for the next iteration make sure the two @@ -463,13 +434,6 @@ public: { bool const bobOfferGone = !offerInLedger(env, bob, bobOfferSeq); STAmount const aliceBalanceUSD = env.balance(alice, usd); -#if 0 - std::cout - << "bob initial: " << initialBobUSD - << "; alice final: " << aliceBalanceUSD - << "; bob offer: " << bobOfferJson.toStyledString() - << std::endl; -#endif // Sanity check the ledger if alice got USD. if (aliceBalanceUSD.signum() > 0) { @@ -619,19 +583,6 @@ public: Quality(Amounts{aliceReducedOffer.in, tweakedTakerGets}).rate(); BEAST_EXPECT(tweakedRate > initialRate); } -#if 0 - std::cout << "Placed rate: " << initialRate - << "; in-ledger rate: " << inLedgerRate - << "; TakerPays: " << aliceReducedOffer.in - << "; TakerGets: " << aliceReducedOffer.out - << std::endl; -// #else - std::string_view filler = badRate ? "**" : " "; - std::cout << "| " << aliceReducedOffer.in << "` | `" - << aliceReducedOffer.out << "` | `" << initialRate - << "` | " << filler << "`" << inLedgerRate << "`" - << filler << std::endl; -#endif } // In preparation for the next iteration make sure all three diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp new file mode 100644 index 0000000000..f20aac68f9 --- /dev/null +++ b/src/test/app/Sponsor_test.cpp @@ -0,0 +1,5511 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 xrpl::test { + +static STAmount +accountReserve(jtx::Env& env, std::uint32_t count = 1) +{ + return env.current()->fees().reserve * count; +} + +static STAmount +reserve(jtx::Env& env, std::uint32_t count) +{ + return env.current()->fees().accountReserve(count, 1); +} + +static void +adjustAccountXRPBalance(jtx::Env& env, jtx::Account const& account, STAmount const& balanceTo) +{ + using namespace test::jtx; + XRPL_ASSERT(isXRP(balanceTo), "adjustAccountXRPBalance: balanceTo must be XRP"); + auto const currentBalance = env.balance(account); + if (currentBalance == balanceTo) + return; + + auto const baseFee = env.current()->fees().base; + if (currentBalance > balanceTo) + { + env(pay(account, env.master, currentBalance - balanceTo), + Fee(XRP(1)), + sponsor::As(env.master, spfSponsorFee), + Sig(sfSponsorSignature, env.master)); + } + else + { + env(pay(env.master, account, balanceTo - currentBalance), Fee(baseFee)); + } + + env.close(); +} + +class Sponsor_test : public beast::unit_test::Suite +{ +public: + void + testDisabled() + { + testcase("Disabled"); + using namespace test::jtx; + Env env{*this, testableAmendments() - featureSponsor}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + + // check Sponsor fields + auto const jt = noop(alice); + auto jt1 = jt; + jt1[sfSponsor.jsonName] = sponsor.human(); + env(jt1, Ter(temDISABLED)); + env(jt, Sig(sfSponsorSignature, sponsor), Ter(temDISABLED)); + + auto jt2 = jt; + jt2[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve; + env(jt2, Ter(temDISABLED)); + + // check Sponsor transactions + env(sponsor::transfer(alice, 0), Ter(temDISABLED)); + env(sponsor::set(sponsor, 0), Ter(temDISABLED)); + } + + void + testInvalidSponsorshipSet() + { + testcase("Invalid SponsorshipSet"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const noFunded("noFunded"); + Account const gw("gw"); + + auto const usd = gw["usd"]; + env.fund(XRP(10000), alice, sponsor, gw); + env.close(); + + // + // preflight + // + + // Invalid flags + { + env(sponsor::set(sponsor, ~tfSponsorshipSetMask - tfInnerBatchTxn), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForReserve | + tfSponsorshipClearRequireSignForReserve), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + + for (auto flag : + {tfSponsorshipSetRequireSignForFee, + tfSponsorshipClearRequireSignForFee, + tfSponsorshipSetRequireSignForReserve, + tfSponsorshipClearRequireSignForReserve}) + { + env(sponsor::set(sponsor, tfDeleteObject | flag), + sponsor::SponseeAcc(alice), + Ter(temINVALID_FLAG)); + } + } + + // invalid SponsorAccount / Sponsee + // Account = Sponsor + env(sponsor::set(alice, tfDeleteObject), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + // Account = Sponsee + env(sponsor::set(alice, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(temMALFORMED)); + // Both Sponsor and Sponsee are specified + env(sponsor::set(alice, 0), + sponsor::CounterpartySponsor(sponsor), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + + // Invalid feeAmount + for (auto const& amt : {XRP(-1), usd(1)}) + { + env(sponsor::set_fee(sponsor, 0, amt), sponsor::SponseeAcc(alice), Ter(temBAD_AMOUNT)); + } + // Invalid MaxFee + for (auto const& amt : {XRP(-1), usd(1)}) + { + env(sponsor::set_fee(sponsor, 0, XRP(1), amt), + sponsor::SponseeAcc(alice), + Ter(temBAD_AMOUNT)); + } + + // Invalid Delete operation + env(sponsor::set_reserve(sponsor, tfDeleteObject, 1), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + env(sponsor::set_fee(sponsor, tfDeleteObject, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + env(sponsor::set_max_fee(sponsor, tfDeleteObject, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + + // Invalid SponsorAccount with non-Delete operation + env(sponsor::set_reserve(sponsor, 0, 100), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + env(sponsor::set_fee(sponsor, 0, XRP(1), XRP(1)), + sponsor::CounterpartySponsor(alice), + Ter(temMALFORMED)); + + // + // preclaim + // + + // Invalid Sponsee + env(sponsor::set(sponsor, 0), sponsor::SponseeAcc(noFunded), Ter(tecNO_DST)); + env.close(); + + // Invalid Sponsor + env(sponsor::set(sponsor, tfDeleteObject), + sponsor::CounterpartySponsor(noFunded), + Ter(tecNO_DST)); + env.close(); + + // Invalid Delete operation (sponsorship not found) + env(sponsor::set(sponsor, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(tecNO_ENTRY)); + env.close(); + + // insufficient balance to sponsor Fee + adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve); + env(sponsor::set_fee(sponsor, 0, XRP(4)), sponsor::SponseeAcc(alice), Ter(tecUNFUNDED)); + env.close(); + + // insufficient reserve to create sponsorship + adjustAccountXRPBalance(env, sponsor, XRP(100) + XRP(1) + reserve(env, 1) - drops(1)); + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + + // FeeAmount + Fee > Balance + // Balance = 1000XRP, FeeAmount = 1001XRP + adjustAccountXRPBalance(env, sponsor, XRP(1000)); + env(sponsor::set_fee(sponsor, 0, XRP(1001)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + // Balance = 1000XRP, FeeAmount = 999XRP, Fee=2XRP + adjustAccountXRPBalance(env, sponsor, XRP(1000)); + env(sponsor::set_fee(sponsor, 0, XRP(999)), + sponsor::SponseeAcc(alice), + Fee(XRP(2)), + Ter(tecUNFUNDED)); + env.close(); + + // create sponsor to use above tests + // need feeAmount(1000) + Fee(1) + reserve(~250) = ~1251 + adjustAccountXRPBalance(env, sponsor, XRP(1000) + XRP(1) + reserve(env, 1)); + env(sponsor::set(sponsor, 0, 100, XRP(1000)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + // delta-based balance check + // After create: sponsor balance ~ 0, feeAmount = XRP(1000) + + // Decreasing feeAmount should succeed (refund, negative delta) + adjustAccountXRPBalance(env, sponsor, XRP(500)); + env(sponsor::set_fee(sponsor, 0, XRP(800)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + // balance was 500, delta = 800-1000 = -200 (refund), balance = 500+200-1 = 699 + + // Increasing feeAmount within delta budget should succeed + adjustAccountXRPBalance(env, sponsor, XRP(500)); + env(sponsor::set_fee(sponsor, 0, XRP(850)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + // balance was 500, delta = 850-800 = 50, balance = 500-50-1 = 449 + + // Increasing feeAmount where delta exceeds balance should fail + adjustAccountXRPBalance(env, sponsor, XRP(310)); + env(sponsor::set_fee(sponsor, 0, XRP(1200)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + + // Increasing feeAmount to reach insufficient reserve + auto const currentFeeAmount = env.le(keylet::sponsorship(sponsor.id(), alice.id())) + ->getFieldAmount(sfFeeAmount) + .xrp(); + adjustAccountXRPBalance(env, sponsor, XRP(310)); + env(sponsor::set_fee(sponsor, 0, currentFeeAmount + XRP(309)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecUNFUNDED)); + env.close(); + } + + void + testPseudoAccountSponsorship() + { + testcase("Pseudo account sponsorship"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const gw("gw"); + Account const sp("sponsor"); + + Asset const asset = gw["IOU"].asset(); + + env.fund(XRP(1000000), alice, bob, gw, sp); + env.close(); + + // Create a vault to get a pseudo account + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = alice, .asset = asset}); + env(tx); + env.close(); + + auto const vaultSle = env.le(keylet); + BEAST_EXPECT(vaultSle); + Account const pseudoAcc("vault", vaultSle->getAccountID(sfAccount)); + env.memoize(pseudoAcc); + + // Sponsee is a pseudo account -> tecNO_PERMISSION + env(sponsor::set(sp, 0, 100, XRP(100)), + sponsor::SponseeAcc(pseudoAcc), + Ter(tecNO_PERMISSION)); + env.close(); + + // Sponsor is a pseudo account -> tecNO_PERMISSION + // (submitted by bob with counterpartySponsor pointing to pseudo account) + env(sponsor::set(bob, tfDeleteObject), + sponsor::CounterpartySponsor(pseudoAcc), + Ter(tecNO_PERMISSION)); + env.close(); + } + + void + testSingleSigning() + { + testcase("Single signing"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const invalid("invalid"); + + env.fund(XRP(10000), alice, sponsor); + env.close(); + + // Signature doesn't exist + auto tx = noop(alice); + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = strHex(sponsor.pk().slice()); + + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // Invalid signature + tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "DEADBEEF"; + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // Signer account doesn't exist + env(noop(alice), + Fee(XRP(1)), + sponsor::As(invalid, spfSponsorReserve), + Sig(sfSponsorSignature, invalid), + Ter(terNO_ACCOUNT)); + + // Success + env(noop(alice), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + } + + void + testMultiSigning() + { + testcase("Multi signing"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const signer1("signer1"); + Account const signer2("signer2"); + + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}})); + env.close(); + + // Invalid signature + auto tx = noop(alice); + auto& signers1 = tx[sfSponsorSignature.jsonName][sfSigners.jsonName][0U][sfSigner.jsonName]; + signers1[sfAccount.jsonName] = signer1.human(); + signers1[sfSigningPubKey.jsonName] = strHex(signer1.pk().slice()); + signers1[sfTxnSignature.jsonName] = "DEADBEEF"; + env(tx, Fee(XRP(1)), sponsor::As(sponsor, spfSponsorReserve), Ter(telENV_RPC_FAILED)); + + // bob is not a multi-signing account. + env(noop(alice), + Fee(XRP(1)), + sponsor::As(bob, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1}), + Ter(tefNOT_MULTI_SIGNING)); + + env(noop(alice), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1}), + Ter(tesSUCCESS)); + env.close(); + + env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}})); + env.close(); + + // test calculateBaseFee for multisigned sponsor + auto const baseFee = env.current()->fees().base; + env(noop(alice), + Fee(baseFee + 2 * baseFee - 1), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1, signer2}), + Ter(telINSUF_FEE_P)); + + env(noop(alice), + Fee(baseFee + 2 * baseFee), + sponsor::As(sponsor, spfSponsorReserve), + Msig(sfSponsorSignature, {signer1, signer2}), + Ter(tesSUCCESS)); + } + + void + testInvalidSponsorField() + { + testcase("Invalid Sponsor Field"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const noFunded("noFunded"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + // Invalid Sponsor Account (Account = Sponsor.Account) + env(noop(alice), sponsor::As(alice, spfSponsorFee), Ter(temMALFORMED)); + + // Invalid Sponsor Account + // (SponsorSignature is specified but Sponsor.Account is not specified) + env(noop(alice), Sig(sfSponsorSignature, sponsor), Ter(temMALFORMED)); + + // Invalid Sponsor Account (Sponsor.Account doesn't exist) + env(noop(alice), sponsor::As(noFunded, spfSponsorReserve), Ter(terNO_ACCOUNT)); + env(noop(alice), + sponsor::As(noFunded, spfSponsorReserve), + Sig(sfSponsorSignature, noFunded), + Ter(terNO_ACCOUNT)); + + // Invalid Flags + env(noop(alice), + sponsor::As(sponsor, (spfSponsorFee | spfSponsorReserve) + 1), + Ter(temINVALID_FLAG)); + + // SponsorFlags=0 with valid sponsor (no sponsorship purpose) + env(noop(alice), sponsor::As(sponsor, 0), Ter(temINVALID_FLAG)); + + // no SponsorFlag with valid sponsor + auto tx = noop(alice); + tx[sfSponsor.jsonName] = sponsor.human(); + env(tx, Ter(temINVALID_FLAG)); + + // Invalid Flags without sponsor + tx = noop(alice); + tx[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve; + env(tx, Ter(temINVALID_FLAG)); + } + + void + testSimpleSponsorshipSet() + { + testcase("Simple SponsorshipSet"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + { + // create sponsorship + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve, + 100, + XRP(100), + XRP(1)), + Fee(XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForFee)); + BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForReserve)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(1)); + + // update sponsorship (decrement) + env(sponsor::set(sponsor, 0, 50, XRP(50), XRP(0.5)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 50); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(50)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(0.5)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(2)); + + // update sponsorship (increment) + env(sponsor::set(sponsor, 0, 200, XRP(200), XRP(2)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 200); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(200)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(2)); + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(3)); + + // delete from sponsor + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice), Fee(XRP(1)), Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - XRP(4)); + + env(sponsor::set( + sponsor, + tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve, + 100, + XRP(100), + XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // delete from sponsee + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + // Cannot create sponsorship with no fee or reserve budget. MaxFee + // and flags do not make a sponsorship object useful by themselves. + env(sponsor::set(sponsor, 0), sponsor::SponseeAcc(alice), Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(sponsor::set_max_fee(sponsor, 0, XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + // update sponsorship with non-zero value + env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), + sponsor::SponseeAcc(alice), + Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + + // update sponsorship flags + auto testFlagUpdate = [&](auto setFlag, auto clearFlag, auto ledgerFlag) { + env(sponsor::set(sponsor, setFlag), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFlag(ledgerFlag)); + + env(sponsor::set(sponsor, clearFlag), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFlag(ledgerFlag)); + }; + + testFlagUpdate( + tfSponsorshipSetRequireSignForFee, + tfSponsorshipClearRequireSignForFee, + lsfSponsorshipRequireSignForFee); + testFlagUpdate( + tfSponsorshipSetRequireSignForReserve, + tfSponsorshipClearRequireSignForReserve, + lsfSponsorshipRequireSignForReserve); + + // Cannot update sponsorship so both fee and reserve budgets are absent. + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tecNO_PERMISSION)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + } + + { + // Removing one budget field while the other remains keeps the + // Sponsorship valid. Starting state (from above): + // RemainingOwnerCount = 100, FeeAmount = XRP(100). + + // Remove only FeeAmount (set to 0); RemainingOwnerCount remains. + env(sponsor::set_fee(sponsor, 0, XRP(0)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100); + + // Re-add FeeAmount, then remove only RemainingOwnerCount; + // FeeAmount remains. + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 0), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + } + + { + // Update Sponsorship (FeeAmount) + // set empty FeeAmount + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + // add FeeAmount + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // Update Sponsorship (ReserveCount) + // set empty ReserveCount + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // add ReserveCount + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // delete Sponsorship (only with FeeAmount) + env(sponsor::set_fee(sponsor, 0, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + { + // delete Sponsorship (only with ReserveCount) + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + env(sponsor::del(alice), sponsor::CounterpartySponsor(sponsor), Ter(tesSUCCESS)); + env.close(); + } + } + + void + testPreFundAndCosign() + { + testcase("PreFund and Cosign"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + + { + // Both pre-funded and co-signed; the pre-funded value is used. + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + auto const checkSeq = env.seq(alice); + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99)); + + env(check::cancel(alice, keylet::check(alice, checkSeq).key), Ter(tesSUCCESS)); + env.close(); + + sle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); // not restored + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99)); + } + + { + // if pre-funded value is not enough, error + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, charlie, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 1, XRP(10), XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + // Fee insufficient + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(11)), + Ter(terINSUF_FEE_B)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 0), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + + // reserve insufficient + env(check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(XRP(1)), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + } + + void + testSponsoredFreeTierReserve() + { + testcase("Sponsored Free-Tier Reserve"); + using namespace test::jtx; + Account const alice("alice"); + Account const issuer("issuer"); + Account const sponsor("sponsor"); + + // Trust lines and MPTokens normally skip the reserve check when the + // holder's ownerCount < 2 (the "free-tier" / first-two-items shortcut). When the + // tx is sponsored, that shortcut must not apply — the sponsor must + // still cover the reserve. + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, issuer); + // Sponsor is funded just below the reserve required to cover a single + // sponsored item. + env.fund(reserve(env, 1) - drops(1), sponsor); + env.close(); + BEAST_EXPECT(ownerCount(env, alice) == 0); + + MPTTester mptt(env, issuer, {.fund = false}); + mptt.create(); + + // Free-tier trust line cosigned by an undercapitalized sponsor must + // fail — the holder's free-first-two-items shortcut does not let the + // sponsor skip the reserve check. + env(trust(alice, issuer["USD"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + // Free-tier MPTokenAuthorize must also fail for the same reason. + env(MPTTester::authorizeJV({.account = alice, .id = mptt.issuanceID()}), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + + void + testTransferSponsor() + { + testcase("Transfer Sponsor"); + using namespace test::jtx; + + // Verify preflight checks + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // Test invalid flags for SponsorshipTransfer + { + // Invalid flag is provided + env(sponsor::transfer( + alice, + (tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd) + 1), + Ter(temINVALID_FLAG)); + + // No SponsorshipTransfer flag is provided + env(sponsor::transfer(alice, 0), Ter(temINVALID_FLAG)); + + // Only one of the three valid flags can be set. + // Setting more than one flag is invalid + for (auto flag : { + tfSponsorshipCreate | tfSponsorshipReassign, + tfSponsorshipCreate | tfSponsorshipEnd, + tfSponsorshipReassign | tfSponsorshipEnd, + tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd, + }) + env(sponsor::transfer(alice, flag), Ter(temINVALID_FLAG)); + } + + // Malformed tests for tfSponsorshipCreate + { + // No sponsor field present + env(sponsor::transfer(alice, tfSponsorshipCreate), Ter(temMALFORMED)); + + // Sponsor field present without spfSponsorReserve + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorFee), + Ter(temINVALID_FLAG)); + + // Sponsee field present + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::SponseeAcc(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + } + + // Malformed tests for tfSponsorshipReassign + { + // No sponsor field present + env(sponsor::transfer(alice, tfSponsorshipReassign), Ter(temMALFORMED)); + + // Sponsor field present without spfSponsorReserve + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor, spfSponsorFee), + Ter(temINVALID_FLAG)); + + // Sponsee field present + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::SponseeAcc(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + } + + // Malformed tests for tfSponsorshipEnd + { + // Sponsor field present + env(sponsor::transfer(alice, tfSponsorshipEnd), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temMALFORMED)); + + // SponsorFlags field present + auto tx = sponsor::transfer(alice, tfSponsorshipEnd); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + env(tx, Ter(temINVALID_FLAG)); + + // Account = Sponsee + env(sponsor::transfer(alice, tfSponsorshipEnd), + sponsor::SponseeAcc(alice), + Ter(temMALFORMED)); + } + } + + { + // Invalid SponsorshipEnd permission (sponsor object/sponsor account) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + { + // sponsor object + env.fund(XRP(1000), charlie); + env.close(); + env(deposit::auth(alice, charlie), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const keylet = keylet::depositPreauth(alice, charlie); + env(sponsor::transfer(bob, tfSponsorshipEnd, keylet.key), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + } + { + // sponsor object + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + env(sponsor::transfer(bob, tfSponsorshipEnd), + sponsor::SponseeAcc(alice), + Ter(tecNO_PERMISSION)); + } + { + // The provided sfSponsee account does not exist + // when ending sponsorship. + Account const ghost("ghost"); // never funded, absent from ledger + env(sponsor::transfer(sponsor, tfSponsorshipEnd), + sponsor::SponseeAcc(ghost), + Ter(terNO_ACCOUNT)); + } + } + + { + // sponsor account + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + + // sfSponsor provided but sfSponsorSignature not provided + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(temMALFORMED)); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2)); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 1); + auto const sle1 = env.le(keylet::account(alice)); + BEAST_EXPECT(sle1->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id()); + + // transfer sponsor + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2)); + + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + BEAST_EXPECT( + !env.le(keylet::account(sponsor1))->isFieldPresent(sfSponsoringAccountCount)); + auto const sle2 = env.le(keylet::account(alice)); + BEAST_EXPECT(sle2->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id()); + + // Reassign to the current sponsor is a no-op and is rejected + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + + // sponsor 2 accounts + adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3)); + env(sponsor::transfer(bob, tfSponsorshipCreate), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + // dissolve sponsors + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd), Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, alice, accountReserve(env, 1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + auto const sle3 = env.le(keylet::account(alice)); + BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor)); + + env(sponsor::transfer(bob, tfSponsorshipEnd)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, bob) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringAccountCount)); + auto const sle4 = env.le(keylet::account(bob)); + BEAST_EXPECT(!sle4->isFieldPresent(sfSponsor)); + + // not sponsored + env(sponsor::transfer(bob, tfSponsorshipEnd), Ter(tecNO_PERMISSION)); + env.close(); + } + { + // dissolve account sponsorship from sponsor + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0); + } + + { + // sponsor object (co-signing) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1)); + adjustAccountXRPBalance(env, sponsor2, reserve(env, 1) - drops(1)); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(alice, sponsor1, drops(1))); + env.close(); + + // Invalid ObjectID (not found) + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet::check(alice, 0).key), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_ENTRY)); + env.close(); + + // Invalid Owner + env(sponsor::transfer(bob, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + // Reassign an object that is not sponsored yet + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + // Valid Owner + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + auto const sle1 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(sle1->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id()); + + // Create on an object that is already sponsored + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env.close(); + + // Reassign to the current sponsor is a no-op and is rejected + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1), + Ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor2, drops(1))); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + auto const sle2 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(sle2->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id()); + + // dissolve sponsor: ending an object sponsorship succeeds even + // when the sponsee lacks sufficient reserve to reclaim the object. + adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1)); + + env(sponsor::transfer(alice, tfSponsorshipEnd, checkId)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount)); + auto const sle3 = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor)); + + // Ending sponsorship on an object that is not sponsored (a ticket, + // which cannot be sponsored) is rejected. + adjustAccountXRPBalance(env, alice, reserve(env, 2)); + auto const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + env.close(); + auto ticketId = keylet::ticket(alice, ticketSeq + 1).key; + BEAST_EXPECT(env.le(keylet::unchecked(ticketId))); + env(sponsor::transfer(alice, tfSponsorshipEnd, ticketId), Ter(tecNO_PERMISSION)); + env.close(); + env(noop(alice), ticket::Use(ticketSeq + 1)); + env.close(); + } + { + // sponsor object (pre-funded + no ltSponsorship entry) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(terNO_PERMISSION)); + env.close(); + + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(terNO_PERMISSION)); + env.close(); + } + { + // sponsor object (pre-funded) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + // insufficient reserve count + env(sponsor::set_fee(sponsor1, 0, XRP(100)), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor1, spfSponsorReserve)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + auto checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor1.id()); + auto sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + + // Reassign to the current sponsor is rejected and must not draw + // down the pre-funded reserve budget + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor1, spfSponsorReserve), + Ter(tecNO_PERMISSION)); + env.close(); + + sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1); + + // transfer sponsor + env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor2.id()); + sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice)); + BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99); + auto sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99); + + // dissolve sponsor + adjustAccountXRPBalance(env, alice, reserve(env, 1)); + env(sponsor::transfer(alice, tfSponsorshipEnd, checkId)); + env.close(); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0); + BEAST_EXPECT( + !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount)); + checkSle = env.le(keylet::unchecked(checkId)); + BEAST_EXPECT(!checkSle->isFieldPresent(sfSponsor)); + sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99); + } + + { + // Dissolve object sponsorship from sponsor(no-ltSponsorship) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // not the owner of the object + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION)); + env.close(); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + { + // Dissolve object sponsorship from sponsor (with ltSponsorship) + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) == + 100); + + // not the owner of the object + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION)); + env.close(); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) == + 100); + } + + { + // sponsor trustline + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + env(trust(user, issuer["usd"](100))); + env.close(); + + auto const trustId = keylet::trustLine(user, issuer, currency); + BEAST_EXPECT(env.le(trustId)); + + // transfer sponsor + env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(trustId)); + + BEAST_EXPECT( + env.le(trustId)->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + + // dissolve sponsor + env(sponsor::transfer(user, tfSponsorshipEnd, trustId.key)); + env.close(); + + BEAST_EXPECT(env.le(trustId)); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfLowSponsor : sfHighSponsor)); + BEAST_EXPECT( + !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + } + } + + { + // invalid transfer + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // create owner dir + env(ticket::create(alice, 1)); + env.close(); + + // AccountRoot + // Amendments + // LedgerHashes + // FeeSettings + // NegativeUNL + // DirNode + auto const keylets = { + keylet::account(alice), + // keylet::amendments(), + keylet::skip(), + keylet::feeSettings(), + // keylet::negativeUNL(), + keylet::ownerDir(alice), + }; + for (auto const& keylet : keylets) + { + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_PERMISSION)); + } + } + + { + // existing owner objects that are outside the v1 SponsorshipTransfer + // object allow-list + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + auto const checkBlocked = [&](Account const& account, uint256 const& objectID) { + env(sponsor::transfer(account, tfSponsorshipCreate, objectID), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_PERMISSION)); + env.close(); + }; + + auto const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + env.close(); + auto const ticketID = keylet::ticket(alice, ticketSeq + 1).key; + BEAST_EXPECT(env.le(keylet::unchecked(ticketID))); + checkBlocked(alice, ticketID); + + env(did::setValid(alice)); + env.close(); + auto const didKeylet = keylet::did(alice.id()); + BEAST_EXPECT(env.le(didKeylet)); + checkBlocked(alice, didKeylet.key); + + env(token::mint(alice, 0u)); + env.close(); + auto const nftPageKeylet = keylet::nftokenPageMax(alice); + BEAST_EXPECT(env.le(nftPageKeylet)); + checkBlocked(alice, nftPageKeylet.key); + + Account const borrower("borrower"); + env.fund(XRP(1000000), borrower); + env.close(); + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset}); + env(vaultTx); + env.close(); + + env(vault.deposit( + {.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)})); + env.close(); + + auto const brokerKeylet = keylet::loanBroker(alice.id(), env.seq(alice)); + env(loanBroker::set(alice, vaultKeylet.key), + loanBroker::kDebtMaximum(xrpAsset(1000).value()), + loanBroker::kManagementFeeRate(TenthBips16{0}), + loanBroker::kCoverRateMinimum(TenthBips32{0}), + loanBroker::kCoverRateLiquidation(TenthBips32{0})); + env.close(); + + auto const loanKeylet = keylet::loan(brokerKeylet.key, 1); + env(loan::set(borrower, brokerKeylet.key, xrpAsset(100).value()), + Sig(sfCounterpartySignature, alice), + Fee(env.current()->fees().base * 2)); + env.close(); + BEAST_EXPECT(env.le(loanKeylet)); + checkBlocked(borrower, loanKeylet.key); + } + } + + void + testSponsorFee() + { + using namespace test::jtx; + + testcase("Sponsor Fee"); + + { + // co-signing + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob); + env.close(); + + { + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terNO_ACCOUNT)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + env.fund(XRP(1000), sponsor); + env.close(); + + { + // Sponsor pays the Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + env(pay(alice, bob, sendAmt), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + { + // insufficient balance to pay Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + { + // Fee is paid by Sponsor + // on context reset (tec error) + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto const feeAmt = XRP(10); + + env(pay(alice, bob, XRP(20000)), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + { + // A co-signed sponsor pays the fee from its own balance, but + // must never be charged into its own account reserve. With a + // balance of exactly reserve + fee the fee is still payable, + // charging the sponsor down to precisely its reserve. + auto const feeAmt = XRP(10); + adjustAccountXRPBalance(env, sponsor, reserve(env, 0) + feeAmt); + auto const sponsorBalance = env.balance(sponsor); + + env(noop(alice), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + BEAST_EXPECT(env.balance(sponsor) == reserve(env, 0)); + } + + { + // below reserve + adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve); + + env(noop(alice), + Fee(env.current()->fees().base), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + + env(noop(alice), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(terINSUF_FEE_B)); + env.close(); + } + } + + { + // pre funded + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + { + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(2000)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + } + + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(alice)); + env.close(); + + { + // Sponsor pays the Fee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + env(pay(alice, bob, sendAmt), Fee(feeAmt), sponsor::As(sponsor, spfSponsorFee)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt); + } + + { + // insufficient balance to pay Fee + { + // > FeeAmount + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(90) + drops(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terINSUF_FEE_B)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee); + } + // use all FeeAmount + { + // = FeeAmount + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(90)), + sponsor::As(sponsor, spfSponsorFee), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100)); + BEAST_EXPECT(env.balance(bob) == bobBalance + XRP(100)); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + + // reset FeeAmount and MaxFee + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::set_fee(sponsor, 0, XRP(10), XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + { + // > MaxFee + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(1) + drops(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terINSUF_FEE_B)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee); + } + } + + { + // Fee is paid by Sponsor + // on context reset (tec error) + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto sponsorBalance = env.balance(sponsor); + auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + auto const feeAmt = XRP(1); + + env(pay(alice, bob, XRP(20000)), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + BEAST_EXPECT( + sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt); + } + + // make sfFeeAmount absent if tec error and all Fee is paid + { + // reset FeeAmount and MaxFee + env(sponsor::del(sponsor), sponsor::SponseeAcc(alice)); + env(sponsor::set_fee(sponsor, 0, XRP(10)), sponsor::SponseeAcc(alice)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + auto sponsorAvailableFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice); + env(check::cancel(alice, uint256(1)), + Fee(sponsorAvailableFee), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecNO_ENTRY)); + env.close(); + BEAST_EXPECT( + !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + } + + // MaxFee cap is enforced in reset() for tec-failing transactions. + // On a closed ledger view (!view.open()), checkFee returns tecINSUFF_FEE when + // Fee > MaxFee (not terINSUF_FEE_B), triggering reset() + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const carol("sponsor"); + + env.fund(XRP(10000), alice, carol); + env.close(); + + // FeeAmount=1000 drops, MaxFee=10 drops + env(sponsor::set_fee(carol, 0, drops(1000), drops(10)), sponsor::SponseeAcc(alice)); + env.close(); + + // Apply directly against the closed ledger view (open_ = false) so that + // checkFee returns tecINSUFF_FEE and reset() is invoked. + OpenView overlay(&*env.closed()); + + auto jt = env.jt( + noop(alice), + Fee(drops(1000)), + Seq(env.seq(alice)), + sponsor::As(carol, spfSponsorFee)); + + auto const result = xrpl::apply(env.app(), overlay, *jt.stx, TapNone, env.journal); + BEAST_EXPECT(result.ter == tecINSUFF_FEE); + BEAST_EXPECT(result.applied); + + // Only MaxFee (10 drops) must be deducted, not the full 1000 drops. + auto const sle = overlay.read(keylet::sponsorship(carol.id(), alice.id())); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10) + } + + // LedgerStateFix charges an owner-reserve fee and can claim that fee + // while returning tecFAILED_PROCESSING. That path must be safe when the + // fee is pre-funded by a sponsorship object. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + env(ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecFAILED_PROCESSING)); + + if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + } + + // If preclaim saw spendable sponsored FeeAmount but the apply view no + // longer has it, the fee path should fail cleanly instead of throwing. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + OpenView overlay(&*env.closed()); + auto jt = env.jt( + ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee)); + + auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal); + BEAST_EXPECT(isTesSuccess(pf.ter)); + auto const pc = preclaim(pf, env.app(), overlay); + BEAST_EXPECT(isTesSuccess(pc.ter)); + + auto const original = overlay.read(keylet::sponsorship(sponsor, alice)); + if (BEAST_EXPECT(original)) + { + auto sle = std::make_shared(*original); + sle->makeFieldAbsent(sfFeeAmount); + overlay.rawReplace(sle); + } + + auto const result = doApply(pc, env.app(), overlay); + BEAST_EXPECT(result.ter == terINSUF_FEE_B); + BEAST_EXPECT(!result.applied); + } + + // test lsfSponsorshipRequireSignForFee + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + env(pay(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == + XRP(10)); + + // clear flag + env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // Payment is re-applied + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + + // RequireSignForFee: co-signing should succeed + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // pre-funded (no sig) should fail + env(pay(alice, bob, XRP(100)), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + env.close(); + + // co-signing (with sig) should succeed + env(pay(alice, bob, XRP(100)), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(9)); + } + } + + void + testSponsorAccount() + { + testcase("Sponsor Account"); + using namespace test::jtx; + + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + Account const sponsor3("sponsor3"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const dave("dave"); + Account const gw("gw"); + auto const usd = gw["usd"]; + + { + // Disabled + Env env{*this, testableAmendments() - featureSponsor}; + env.fund(XRP(10000), alice, sponsor); + env.close(); + env(pay(alice, bob, XRP(100)), Txflags(tfSponsorCreatedAccount), Ter(temDISABLED)); + env.close(); + } + + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, sponsor, sponsor2, sponsor3); + env.close(); + + // Invalid flags + for (auto flag : { + tfNoRippleDirect, + tfPartialPayment, + tfLimitQuality, + }) + { + env(pay(alice, bob, XRP(100)), + Txflags(tfSponsorCreatedAccount | flag), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Invalid amount(iou) + env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT)); + env.close(); + + // Sponsored account creation is reserve sponsorship and is only supported for direct XRP + // payments. + env(pay(alice, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Sendmax(usd(2)), + Ter(temINVALID)); + env.close(); + + env(pay(alice, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Path(~XRP), + Ter(temINVALID)); + env.close(); + + // Account is not sponsored by normal Sponsor specification + { + env(pay(alice, bob, drops(env.current()->fees().accountReserve(0, 1))), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const bobSle = env.le(keylet::account(bob)); + BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0); + } + + // Use tfSponsorCreatedAccount to sponsor an account + { + // to funded account + env(pay(sponsor2, bob, drops(1)), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tecNO_SPONSOR_PERMISSION)); + env.close(); + + BEAST_EXPECT(env.balance(sponsor2) == XRP(9999)); + + // to non-funded account / insufficient balance for reserve + env(pay(sponsor2, charlie, XRP(9999) - env.current()->fees().reserve + drops(1)), + Txflags(tfSponsorCreatedAccount), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + // to non-funded account + auto const sponsor2BalanceBefore = env.balance(sponsor2); + env(pay(sponsor2, charlie, drops(1)), Txflags(tfSponsorCreatedAccount), Fee(XRP(1))); + env.close(); + + auto const charlieSle = env.le(keylet::account(charlie)); + BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor)); + BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == sponsor2.id()); + BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1); + // verify sponsor balance decreased by payment + Fee + BEAST_EXPECT(env.balance(sponsor2) == sponsor2BalanceBefore - drops(1) - XRP(1)); + } + { + // insufficient reserve to sponsor acount + + auto const sendAmount = drops(1); + // 2 account reserve + send amount + auto const requireBalance = accountReserve(env, 2) + sendAmount; + adjustAccountXRPBalance(env, sponsor3, requireBalance - drops(1)); + env(pay(sponsor3, dave, sendAmount), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + adjustAccountXRPBalance(env, sponsor3, requireBalance); + env(pay(sponsor3, dave, sendAmount), + Txflags(tfSponsorCreatedAccount), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + } + } + + void + testRequireFlag() + { + using namespace test::jtx; + { + testcase("SponsorshipRequireSignForReserve"); + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_reserve(sponsor, tfSponsorshipSetRequireSignForReserve, 10), + sponsor::SponseeAcc(alice)); + env.close(); + + env(check::create(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(terNO_PERMISSION)); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // clear flag + env(sponsor::set_reserve(sponsor, tfSponsorshipClearRequireSignForReserve, 1), + sponsor::SponseeAcc(alice)); + env.close(); + + // CheckCreate is re-applied + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + { + testcase("SponsorshipRequireSignForFee"); + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // set flag + env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + env(check::create(alice, bob, XRP(100)), + Fee(XRP(10)), + sponsor::As(sponsor, spfSponsorFee), + Ter(terNO_PERMISSION)); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT( + env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == + XRP(10)); + + // clear flag + env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), + sponsor::SponseeAcc(alice)); + env.close(); + + // CheckCreate is re-applied + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount)); + } + } + + void + testSponsorReserveSimple(bool cosigning) + { + testcase("SponsorReserveSimple"); + using namespace test::jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 250), sponsor::SponseeAcc(alice)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 2) - drops(1)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + + env(check::create(alice, bob, XRP(100)), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tesSUCCESS)); + env.close(); + } + } + + // test helper for both cosigning and pre-funded sponsorship + template + void + testEachSponsorship( + test::jtx::Env& env, + bool cosigning, + jtx::Account const& sponsor, + jtx::Account const& sponsee, + uint32_t reserveCount, + uint32_t sponsorReserveCount, + TER insufficientReserveResult, + SubmitCallback callback, + std::optional> expected = std::nullopt) + { + using namespace test::jtx; + auto const sponseeOwnerCountBefore = ownerCount(env, sponsee); + auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee); + auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBefore = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor); + auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor); + + std::optional sponsorSig = + cosigning ? std::optional(Sig(sfSponsorSignature, sponsor)) : std::nullopt; + + auto const sponsorCurrentOwnerCount = ownerCount(env, sponsor) - + sponsoredOwnerCount(env, sponsor) + sponsoringOwnerCount(env, sponsor); + + auto submit = [&](TER ter) { + return [&, ter](json::Value const& jv, auto const&... fN) { + if (sponsorSig) + { + env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), *sponsorSig, Ter(ter)); + } + else + { + env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), Ter(ter)); + } + }; + }; + + // Insufficient Reserve + { + if (cosigning) + { + adjustAccountXRPBalance( + env, + sponsor, + reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount) - drops(1)); + } + else + { + // cleanup previous sponsorship + if (env.le(keylet::sponsorship(sponsor, sponsee))) + { + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env.close(); + } + + if (sponsorReserveCount > 1) + { + env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)), + sponsor::SponseeAcc(sponsee)); + } + else + { + // just create sponsor object + env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), + sponsor::SponseeAcc(sponsee)); + } + env.close(); + } + + // A failed sponsored create must not consume prefunded reserve or mutate owner counts. + auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee); + auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee); + auto const sponseeSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor); + auto const sponsorSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsor); + auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt && + sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount); + std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt + ? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount) + : 0; + + callback(env, submit(insufficientReserveResult)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt); + + auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT( + static_cast(sponsorshipSleAfterAttempt) == + static_cast(sponsorshipSleBeforeAttempt)); + if (sponsorshipSleAfterAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) == + reserveCountPresentBeforeAttempt); + if (reserveCountPresentBeforeAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) == + reserveCountBeforeAttempt); + } + } + } + + // Success + { + if (cosigning) + { + adjustAccountXRPBalance( + env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount)); + } + else + { + // reset sponsorship + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env(sponsor::set(sponsor, 0, sponsorReserveCount, XRP(1)), + sponsor::SponseeAcc(sponsee)); + env.close(); + } + callback(env, submit(tesSUCCESS)); + env.close(); + + if (!cosigning) + { + // Prefunded success consumes the reserved owner slot before cleanup. + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT( + !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) || + sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + + // cleanup sponsorship + env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); + env.close(); + } + } + + if (expected) + { + (*expected)(); + } + else + { + BEAST_EXPECT(ownerCount(env, sponsee) - sponseeOwnerCountBefore == reserveCount); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsee) - sponseeSponsoredOwnerCountBefore == + sponsorReserveCount); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore == + sponsorReserveCount); + } + }; + + void + testCheck(bool cosigning) + { + testcase("Check"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const gw("gw"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + auto const usd = gw["usd"]; + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor, sponsor2); + env.close(); + + // CheckCreate -> Check -> CheckCancel + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit(check::create(alice, bob, XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // Check + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + auto const keylet = keylet::check(alice, seq); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + // transfer sponsor + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); // Check + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // CheckCancel + env(check::cancel(alice, keylet.key)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // CheckCreate -> CheckCash + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + // CheckCash + auto const checkId2 = keylet::check(alice, seq2).key; + env(check::cash(bob, checkId2, XRP(1))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + // RippleState sponsor (CheckCashMakesTrustLine) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env.trust(usd(100), alice); + env.close(); + env(pay(gw, alice, usd(100))); + env.close(); + + // CheckCreate -> CheckCash + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, usd(1))); + }); + + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + auto const keylet = keylet::check(alice, seq2); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // CheckCash + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(check::cash(bob, keylet.key, usd(1))); }, + [&]() { + BEAST_EXPECT(ownerCount(env, alice) == 1); // RippleState + BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // the newly-created trust line carries the sponsor on + // bob's side, and no sponsor on the counterparty side + bool const bobLow = bob.id() < gw.id(); + auto const lineSle = env.le(keylet::trustLine(bob, gw, usd.currency)); + BEAST_EXPECT(lineSle); + if (lineSle) + { + BEAST_EXPECT( + lineSle->getAccountID(bobLow ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !lineSle->isFieldPresent(bobLow ? sfHighSponsor : sfLowSponsor)); + } + }); + } + + // MPT sponsor: cashing an MPT check creates a sponsored MPToken for + // the casher. Unlike the trust-line path, this returns + // tecINSUFFICIENT_RESERVE (not tecNO_LINE_INSUF_RESERVE) on shortfall. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), bob, sponsor, sponsor2); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + // CheckCreate (alice -> bob) paying the MPT + uint32_t seq2 = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq2 = env.seq(alice); + submit(check::create(alice, bob, mpt(1))); + }); + + auto const checkKeylet = keylet::check(alice, seq2); + BEAST_EXPECT(env.le(checkKeylet)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // CheckCash by bob (no MPToken yet) creates a sponsored MPToken + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(check::cash(bob, checkKeylet.key, mpt(1))); + }, + [&]() { + BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + }); + } + } + + void + testDelegate(bool cosigning) + { + testcase("Delegate"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // DelegateSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(delegate::set(alice, bob, {"Payment"})); + }); + + // the created Delegate object carries the sponsor + auto const keylet = keylet::delegate(alice, bob); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // delete + env(delegate::set(alice, bob, {})); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testDepositPreauth(bool cosigning) + { + testcase("DepositPreauth"); + using namespace test::jtx; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + auto const credType = std::string("credType"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor, sponsor2); + env.close(); + + // DepositPreauthSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(deposit::auth(alice, sponsor)); }); + + // the created DepositPreauth object carries the sponsor + auto const keylet = keylet::depositPreauth(alice, sponsor); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + // No sponsor signature here: this exercises the prefunded reassign path. + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle); + if (sponsor2Sle) + { + BEAST_EXPECT( + !sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) || + sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0); + } + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // DepositPreauthDelete + env(deposit::unauth(alice, sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor); + env.close(); + auto const authCreds = std::vector{ + {.issuer = sponsor, .credType = credType}}; + auto const preauthKeylet = keylet::depositPreauth( + alice.id(), + std::set>{ + {sponsor.id(), Slice(credType.data(), credType.size())}}); + + // Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch. + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env&, auto const& submit) { + submit(deposit::authCredentials(alice, authCreds)); + }); + + // Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object. + BEAST_EXPECT(env.le(preauthKeylet)); + BEAST_EXPECT(env.le(preauthKeylet)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + env(deposit::unauthCredentials(alice, authCreds)); + env.close(); + + BEAST_EXPECT(!env.le(preauthKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testCredential(bool cosigning) + { + testcase("Credential"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + auto const credType = std::string("test"); + + // Self-issued credential: alice creates for herself, sponsor covers reserve + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::create(alice, alice, credType)); + }); + + auto const credKeylet = credentials::keylet(alice, alice, credType); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor2.id()); + + // CredentialDelete + env(credentials::deleteCred(alice, alice, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // Issuer creates for subject; sponsorship transfers to subject on accept + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // alice (issuer) creates credential for bob (subject), sponsor covers alice's reserve + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::create(bob, alice, credType)); + }); + + // Before accept: alice (issuer) owns the credential + auto const credKeylet = credentials::keylet(bob, alice, credType); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + + // Bob accepts: issuer's sponsorship ends, bob becomes unsponsored owner + env(credentials::accept(bob, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); // original sponsorship ended + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); // bob owns it without a sponsor + + // CredentialDelete by subject + env(credentials::deleteCred(bob, bob, alice, credType)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + + // Issuer creates for subject; accept tx itself is reserve-sponsored + // This exercises the checkReserve() call in CredentialAccept::doApply() + // that guards the sponsor's reserve when featureSponsor is enabled. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // alice (issuer) creates credential for bob (subject) without sponsor + env(credentials::create(bob, alice, credType)); + env.close(); + + auto const credKeylet = credentials::keylet(bob, alice, credType); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // Bob accepts with a sponsored reserve; the first attempt uses an + // undercapitalized sponsor and must fail with tecINSUFFICIENT_RESERVE; + // the second attempt uses a properly funded sponsor and must succeed. + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit(credentials::accept(bob, alice, credType)); + }); + + // After successful accept: alice (issuer) no longer owns the credential, + // bob owns it and sponsor covers his reserve. + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id()); + + // Cleanup + env(credentials::deleteCred(bob, bob, alice, credType)); + env.close(); + } + } + + void + testEscrow(bool cosigning) + { + testcase("Escrow"); + using namespace test::jtx; + using namespace std::chrono_literals; + + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + { + // Native Escrow + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // EscrowCreate + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, XRP(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor2.id()); + + // EscrowFinish + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + Account const gw("gw"); + auto const usd = gw["usd"]; + { + // IOU Escrow + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // EscrowCreate + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + // EscrowFinish + testEachSponsorship( + env, + cosigning, + sponsor2, + bob, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { + submit( + escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150)); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + BEAST_EXPECT( + env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) == + sponsor2.id()); + } + { + // IOU EscrowFinish recycles reserve when the same sponsor backs + // the escrow being removed and the destination line being created. + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + } + + if (cosigning) + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + else + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + BEAST_EXPECT( + env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) == + sponsor.id()); + } + { + // IOU Escrow cancel re-creates the owner's trust line, and the + // cancel transaction's sponsor can cover that new line's reserve. + // Creating an IOU escrow moves the sender's balance to the issuer, + // so the sender can delete their now-zero trust line while the + // escrow is pending. Cancelling returns the funds, re-creating the + // line. + Env env{*this, testableAmendments()}; + + env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000000), alice); + env.close(); + env(pay(gw, alice, usd(10000))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // alice escrows her entire balance so the trust line can be removed + auto const cancelAfter = env.now() + 100s; + auto const seq = env.seq(alice); + env(escrow::create(alice, bob, usd(10000)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(cancelAfter)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // trust line + escrow + + // alice deletes her now-zero trust line while the escrow is pending + env(trust(alice, usd(0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow + + // advance the ledger past the cancel time + for (; env.now() < cancelAfter; env.close()) + { + } + + // EscrowCancel by alice re-creates her trust line; the sponsor + // covers the new line's reserve. On the insufficient-reserve + // attempt the whole transaction rolls back, leaving the escrow + // intact to be cancelled on the success attempt. + bool const aliceLow = alice.id() < gw.id(); + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(escrow::cancel(alice, alice, seq)); }, + [&]() { + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + auto const trustSle = env.le(keylet::trustLine(alice, gw, usd.currency)); + BEAST_EXPECT(trustSle); + if (trustSle) + { + BEAST_EXPECT( + trustSle->getAccountID(aliceLow ? sfLowSponsor : sfHighSponsor) == + sponsor.id()); + BEAST_EXPECT( + !trustSle->isFieldPresent(aliceLow ? sfHighSponsor : sfLowSponsor)); + } + BEAST_EXPECT(ownerCount(env, alice) == 1); // re-created trust line + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + }); + } + { + // MPT Escrow + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + // create Escrow from alice to bob + auto const seq = env.seq(alice); + env(escrow::create(alice, bob, mpt(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // finish Escrow: bob has no MPToken, so finishing creates one and + // the finish transaction's sponsor covers its reserve. The MPT + // create-token path returns tecINSUFFICIENT_RESERVE (not + // tecNO_LINE_INSUF_RESERVE) when the sponsor is underfunded. + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + submit( + escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(XRP(1))); + }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + } + { + // MPT EscrowFinish has the same reserve recycling behavior as IOU + // when it creates the destination MPToken. + Env env{*this, testableAmendments()}; + auto const baseFee = env.current()->fees().base; + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + MPTTester mptGw(env, gw, {.holders = {alice}}); + mptGw.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + auto const mpt = mptGw["MPT"]; + env(pay(gw, alice, mpt(10'000))); + env.close(); + + uint32_t seq = 0; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + seq = env.seq(alice); + submit( + escrow::create(alice, bob, mpt(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + }); + + BEAST_EXPECT( + env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsor) == sponsor.id()); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + } + + if (cosigning) + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + else + { + env(escrow::finish(bob, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + sponsor::As(sponsor, spfSponsorReserve), + Fee(baseFee * 150), + Ter(tesSUCCESS)); + } + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + BEAST_EXPECT( + env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) == + sponsor.id()); + } + + // A sponsored EscrowCreate must still verify that the source + // can fund the escrow amount and stay above its own base + // reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking escrowAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const escrowAmount = XRP(2); + auto const seq = env.seq(alice); + + if (cosigning) + { + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testMPToken(bool cosigning) + { + testcase("MPToken"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // MPTokenIssuanceCreate + json::Value jv = {}; + jv[sfAccount] = alice.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + MPTID mptid; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + mptid = makeMptID(env.seq(alice), alice.id()); + submit(jv); + }); + + // transfer sponsor + auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptid); + + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + // MPTokenAuthorize + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free mptoken + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + bob, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(jv); }); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + + // transfer sponsor + auto const mptTokenKeylet = keylet::mptoken(mptid, bob); + if (cosigning) + { + env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + + env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2); + + // MPTokenAuthorize Unauthorize + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + jv[sfFlags] = tfMPTUnauthorize; + env(jv); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + // MPTokenIssuanceDestroy + jv = {}; + jv[sfTransactionType] = jss::MPTokenIssuanceDestroy; + jv[sfAccount] = alice.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + env(jv); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + { + // check INSUFFICIENT_RESERVE for MPToken + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // MPTokenAuthorize + json::Value jv = {}; + jv[sfAccount] = alice.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + auto const mptid = makeMptID(env.seq(alice), alice.id()); + env(jv); + env.close(); + + // Create tickets so the sponsor is past free-tier reserve behavior. + std::uint32_t const ticketSeq{env.seq(sponsor) + 1}; + env(ticket::create(sponsor, 2)); + env.close(); + + jv = {}; + jv[sfTransactionType] = jss::MPTokenAuthorize; + jv[sfAccount] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(mptid); + // error (non-free mptoken) + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - drops(1)); + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + else + { + env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::SponseeAcc(bob)); + env.close(); + + env(jv, sponsor::As(sponsor, spfSponsorReserve), Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + + env(noop(sponsor), ticket::Use(ticketSeq)); + env.close(); + + // pass (free-tier mptoken for the holder, but the sponsor is still + // charged a reserve increment regardless of the ownerCount < 2 shortcut). + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + env(jv, sponsor::As(sponsor, spfSponsorReserve), Ter(tesSUCCESS)); + env.close(); + } + } + } + + void + testPayChan(bool cosigning) + { + testcase("PayChan"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // PayChanCreate + auto const pk = alice.pk(); + auto const settleDelay = 10s; + uint256 chan; + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { + chan = paychan::channel(alice, bob, env.seq(alice)); + submit(paychan::create(alice, bob, XRP(100), settleDelay, pk)); + }); + + // the created PayChannel object carries the sponsor + BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, chan), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipReassign, chan), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor2.id()); + + env.close(env.now() + settleDelay); + // PayChanClaim (delete PayChan) + env(paychan::claim(bob, chan), Txflags(tfClose)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // A sponsored PaymentChannelCreate must still verify that the + // source can fund the channel amount and stay above its own + // base reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking channelAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const pk = alice.pk(); + auto const settleDelay = 10s; + auto const channelAmount = XRP(2); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + + if (cosigning) + { + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testSignerList(bool cosigning) + { + testcase("SignerList"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + // SignerListSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(signers(alice, 1, {{bob, 1}})); }); + + // the created SignerList object carries the sponsor + BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor.id()); + + // transfer sponsor + if (cosigning) + { + // invalid signer list owner 1 + // account doesn't have signer list but specified signer list exists + env(sponsor::transfer(bob, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + // invalid signer list owner 2 + // account has signer list and specified signer list exists + env(signers(bob, 1, {{alice, 1}})); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(bob).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor2.id()); + + // Delete + env(signers(alice, NoneT())); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + // Legacy (pre-MultiSignReserve) SignerLists lack lsfOneOwnerCount and cost + // 2 + signer_count owner units, whereas modern lists cost 1. + void + testLegacySignerListReserve() + { + testcase("Legacy SignerList sponsorship reserve"); + using namespace test::jtx; + + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const dave("dave"); + Account const sponsor("sponsor"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, dave, sponsor); + env.close(); + + // Modern 3-signer list: weight 1, lsfOneOwnerCount set. + env(signers(alice, 1, {{bob, 1}, {carol, 1}, {dave, 1}})); + env.close(); + + auto const signerListKeylet = keylet::signerList(alice.id()); + auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id()); + std::uint32_t const legacyWeight = 5; // 2 + 3 signers + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // Pre-fund exactly the legacy weight + env(sponsor::set_reserve(sponsor, 0, legacyWeight), sponsor::SponseeAcc(alice)); + env.close(); + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == legacyWeight); + + // Synthesize a pre-MultiSignReserve list: clear lsfOneOwnerCount and + // restore the owner's OwnerCount to the legacy weight. + env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool { + auto signerList = std::make_shared(*view.read(signerListKeylet)); + auto account = std::make_shared(*view.read(keylet::account(alice.id()))); + signerList->clearFlag(lsfOneOwnerCount); + account->setFieldU32(sfOwnerCount, legacyWeight); + view.rawReplace(signerList); + view.rawReplace(account); + return true; + }); + if (auto const sle = env.le(signerListKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT((sle->getFlags() & lsfOneOwnerCount) == 0); + BEAST_EXPECT(ownerCount(env, alice) == legacyWeight); + + // Create must charge the full legacy weight (5), not 1: the bug bumped + // the counters by 1 and left 4 pre-funded units unspent. + env(sponsor::transfer(alice, tfSponsorshipCreate, signerListKeylet.key), + sponsor::As(sponsor, spfSponsorReserve)); + + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == legacyWeight); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == legacyWeight); + // All pre-funded units consumed (drained to absent). + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + + // Delete unwinds the legacy weight; create bumped by the same amount, so + // the counters return to 0 (the bug bumped by 1 -> underflow on delete). + env(signers(alice, NoneT())); + + BEAST_EXPECT(!env.le(signerListKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount)); + } + + void + testSponsoredTrustLineNoFreeReserve() + { + // An account with ownerCount < 2 may create its first trust lines even + // without meeting the reserve. In any case, the sponsor pays the full + // reserve in all cases, even for the sponsee's very first trust line. + testcase("Sponsored trust line gets no free-reserve exception"); + using namespace test::jtx; + + Account const issuer("issuer"); + Account const alice("alice"); + Account const sponsor("sponsor"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), issuer, alice, sponsor); + env.close(); + + auto const usd = issuer["usd"]; + auto const lineKeylet = keylet::trustLine(alice, issuer, usd.currency); + + // Sponsor funded for exactly its base reserve + adjustAccountXRPBalance(env, sponsor, reserve(env, 0)); + + // alice's ownerCount is 0, so an unsponsored first trust line would be + // free; but because it is sponsored, the reserve check is enforced + // against the sponsor, which is one increment short. + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + BEAST_EXPECT(!env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Give the sponsor has exactly one owner-reserve increment; the same + // sponsored first trust line now succeeds and the sponsor pays for it. + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, alice) == 1); + } + + void + testCoSignReserveBoundedBySponsorshipBudget() + { + // sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed + // reserve sponsorship fail -- with no fallback to the sponsor's balance. + testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const sponsor("sponsor"); + Account const sponsee("sponsee"); + env.fund(XRP(10000), sponsor, sponsee); + env.close(); + + // Prefund a FEE-only Sponsorship for the sponsee; ReserveCount + // defaults to 0. + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee)); + env.close(); + BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee))); + + // Sponsee creates a Check with the sponsor co-signing the reserve. The + // fee-only Sponsorship's has ReserveCount (0), so this fails + // with tecINSUFFICIENT_RESERVE + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0); + + // Bumping the Sponsorship's ReserveCount budget makes the same + // co-signed reserve sponsorship succeed, the budget is what gates it. + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee)); + env.close(); + + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1); + } + + void + testTrustSet(bool cosigning) + { + testcase("TrustSet"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const charlie("charlie"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + auto const validateSponsoredTrustline = + [&](std::shared_ptr const& sle, bool isIssuerHigh, Account const& sponsor) { + BEAST_EXPECT( + sle->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == sponsor.id()); + BEAST_EXPECT(!sle->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor)); + }; + + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + + // create and delete + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, charlie, sponsor, sponsor2); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + // create TrustLine + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free trustline + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + user, + 1, + 1, + tecNO_LINE_INSUF_RESERVE, + [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); }); + + auto const keylet = keylet::trustLine(user, issuer, currency); + + if (cosigning) + { + // invalid owner + env(sponsor::transfer(charlie, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + // invalid reserve owner + env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2), + Ter(tecNO_PERMISSION)); + env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(user)); + env.close(); + env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + } + + // delete TrustLine + env(trust(user, usd(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + BEAST_EXPECT(!env.le(keylet)); + } + + // update + for (bool const isIssuerHigh : {false, true}) + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); + env.close(); + + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + + auto const usd = issuer["usd"]; + auto const currency = usd.currency; + + // create TrustLine from issuer + env(trust(issuer, user["usd"](100))); + env.close(); + + BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency))); + + if (cosigning) + { + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + env(ticket::create(sponsor, 2)); // adjust for free trustline + env.close(); + } + + testEachSponsorship( + env, + cosigning, + sponsor, + user, + 1, + 1, + tecINSUF_RESERVE_LINE, + [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); }); + + auto const line = env.le(keylet::trustLine(user, issuer, currency)); + validateSponsoredTrustline(line, isIssuerHigh, sponsor); + + // update TrustLine from user to clear reserve + env(trust(user, usd(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency))); + + // remove TrustLine from issuer + env(trust(issuer, user["usd"](0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(user, issuer, currency))); + } + + // both High and Low sponsored + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // create TrustLines + env(trust(alice, bob["usd"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + env(trust(bob, alice["usd"](100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFlag(lsfHighReserve)); + BEAST_EXPECT(sle->isFlag(lsfLowReserve)); + BEAST_EXPECT(sle->getAccountID(sfHighSponsor) == sponsor.id()); + BEAST_EXPECT(sle->getAccountID(sfLowSponsor) == sponsor.id()); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + + // clear TrustLines + env(trust(alice, bob["usd"](0))); + env.close(); + env(trust(bob, alice["usd"](0))); + env.close(); + + sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency)); + BEAST_EXPECT(!sle); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testAccountDelete() + { + testcase("AccountDelete"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + { + // Delete Sponsor/Sponsee Account with ltSponsorship (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // set sponsor + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Ter(tesSUCCESS)); + env.close(); + + incLgrSeqForAccDel(env, sponsor); + + auto const keylet = keylet::sponsorship(sponsor, alice); + auto const sponsorObj = env.le(keylet); + BEAST_EXPECT(sponsorObj); + + // AccountDelete + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + } + + { + // Delete SponsoredAccount + Env env{*this, testableAmendments()}; + env.memoize(alice); + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + // create SponsoredAccount + env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount)); + env.close(); + + incLgrSeqForAccDel(env, alice); + + // AccountDelete: destination = non-sponsor + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION)); + + // The failed delete must leave the account sponsored by the original sponsor. + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + if (aliceSle) + BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id()); + + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1); + + incLgrSeqForAccDel(env, alice); + + // AccountDelete: destination = sponsor + env(acctdelete(alice, sponsor), Fee(requiredFee), Ter(tesSUCCESS)); + + auto const sponsorSle2 = env.le(keylet::account(sponsor)); + BEAST_EXPECT(!sponsorSle2->isFieldPresent(sfSponsoringAccountCount)); + } + + { + // Sponsor with sfSponsoringOwnerCount cannot delete (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + Account const gw("gw"); + env.fund(XRP(1000000), alice, bob, sponsor, gw); + env.close(); + + auto const usd = gw["usd"]; + + // Create a trust line for alice + env(trust(alice, usd(1000))); + env.close(); + + // Transfer reserve sponsorship of trust line to sponsor + auto const trustId = keylet::trustLine(alice, gw, usd.currency); + BEAST_EXPECT(env.le(trustId)); + + env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + // Verify sfSponsoringOwnerCount is set on sponsor + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount)); + auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount); + BEAST_EXPECT(sponsoringOwnerCount >= 1); + + incLgrSeqForAccDel(env, sponsor); + + // AccountDelete should fail + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-object count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount); + } + + { + // Sponsor with sfSponsoringAccountCount cannot delete (tecHAS_OBLIGATIONS) + Env env{*this, testableAmendments()}; + env.memoize(alice); + env.fund(XRP(1000000), bob, sponsor); + env.close(); + + // Create SponsoredAccount (sets sfSponsoringAccountCount on sponsor) + env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount)); + env.close(); + + // Verify sfSponsoringAccountCount is set on sponsor + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount)); + auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount); + BEAST_EXPECT(sponsoringAccountCount == 1); + + incLgrSeqForAccDel(env, sponsor); + + // AccountDelete should fail + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-account count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount); + } + + { + // Account with sponsored objects should be deletable + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); + env.close(); + + // Create sponsored delegate (a non-deletion-blocker) + env(deposit::auth(alice, bob), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + + { + auto const sponsorSle = env.le(keylet::account(sponsor)); + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 1); + BEAST_EXPECT(aliceSle->at(sfOwnerCount) == 1); + BEAST_EXPECT(aliceSle->at(sfSponsoredOwnerCount) == 1); + } + + incLgrSeqForAccDel(env, alice); + + // AccountDelete should succeed + { + auto const requiredFee = drops(env.current()->fees().increment); + env(acctdelete(alice, bob), Fee(requiredFee), Ter(tesSUCCESS)); + BEAST_EXPECT(!env.le(keylet::account(alice))); + auto const sponsorSle = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 0); + } + } + } + + void + testDelegatePermission() + { + testcase("DelegatePermission"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + // + // SponsorshipTransfer is not delegable. + // + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const keylet = keylet::check(alice, seq); + + env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key), + sponsor::As(bob, spfSponsorReserve), + Sig(sfSponsorSignature, bob), + delegate::As(carol), + Ter(temINVALID)); + + env(delegate::set(alice, carol, {"SponsorshipTransfer"}), Ter(temMALFORMED)); + } + // + // test send SponsorshipSet on other's behalf + // + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + + env(sponsor::set(alice, 0, 100, XRP(100)), + sponsor::SponseeAcc(bob), + delegate::As(carol), + Ter(terNO_DELEGATE_PERMISSION)); + + env(delegate::set(alice, carol, {"SponsorshipSet"})); + env.close(); + + env(sponsor::set(alice, 0, 100, XRP(100)), + sponsor::SponseeAcc(bob), + delegate::As(carol), + Ter(tesSUCCESS)); + env.close(); + } + } + + void + testDelegateBlockReserveSponsor() + { + testcase("Delegate Block Reserve Sponsor"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const sponsor("sponsor"); + + // Co-signed reserve sponsorship is blocked for delegated transactions. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"CheckCreate"})); + env.close(); + + env(check::create(alice, carol, XRP(1)), + delegate::As(bob), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID)); + } + + // Pre-funded reserve sponsorship is blocked for delegated transactions. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"CheckCreate"})); + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(bob)); + env.close(); + + env(check::create(alice, carol, XRP(1)), + delegate::As(bob), + sponsor::As(sponsor, spfSponsorReserve), + Ter(temINVALID)); + } + } + + void + testDelegateSponsorFeePayer() + { + testcase("Delegate Sponsor Fee Payer"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const sponsor("sponsor"); + + // Co-signing: the sponsor account pays the delegated transaction fee. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + auto const sponsorBalance = env.balance(sponsor); + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + + env(pay(alice, carol, sendAmt), + delegate::As(bob), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt); + // sponsor pays the fee + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt); + } + + // Pre-funded: the Sponsorship object for sponsorship(sponsor, delegate) + // pays the fee. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, carol, sponsor); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob)); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + auto const sponsorBalance = env.balance(sponsor); + auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob); + auto const sendAmt = XRP(100); + auto const feeAmt = XRP(10); + + // verify sponsorship(sponsor, alice) is not present, because we are testing + // sponsorship(sponsor, bob) will pay the fee. bob is sfDelegate. + BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))); + + env(pay(alice, carol, sendAmt), + delegate::As(bob), + Fee(feeAmt), + sponsor::As(sponsor, spfSponsorFee)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt); + BEAST_EXPECT(env.balance(sponsor) == sponsorBalance); + // sponsorship(sponsor, bob) pays the fee, bob is sfDelegate + BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt); + } + } + + void + testBatch() + { + testcase("Batch"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + // + // outer transaction + // + { + // test outer transaction with co-signing sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // does not affect reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Fee is paid by sponsor + BEAST_EXPECT(env.balance(alice) == XRP(1000)); + BEAST_EXPECT(env.balance(sponsor) == XRP(1000 - 1)); + } + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + // spfSponsorReserve on outer Batch is rejected + for (auto const flags : {spfSponsorReserve | spfSponsorFee, spfSponsorReserve}) + { + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(noop(alice), seq + 2), + sponsor::As(sponsor, flags), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID_FLAG)); + env.close(); + } + } + { + // test outer transaction with prefunded sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob); + env.fund(XRP(1001), sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Ter(tesSUCCESS)); + env.close(); + + // does not affect reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Fee is paid by sponsor object + BEAST_EXPECT(env.balance(alice) == XRP(1000)); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100 - 1)); + BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 100); + } + // + // Inner transaction + // + { + // test invalid Inner transaction with co-signing sponsor + Account const signerAccount("signer"); + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor, signerAccount); + env.close(); + + env(signers(sponsor, 1, {Signer(signerAccount, 1)})); + env.close(); + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + jt.jv.removeMember(sfTxnSignature.jsonName); + + auto const seq = env.seq(alice); + // should fail because Inner transaction cannot include SponsorSignature with + // TxnSignature + BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfTxnSignature.jsonName)); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNATURE)); + } + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + Msig(sfSponsorSignature, sponsor, signerAccount)); + jt.jv.removeMember(sfTxnSignature.jsonName); + + auto const seq = env.seq(alice); + // should fail because Inner transaction cannot include SponsorSignature with + // Signers + BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfSigners.jsonName)); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNER)); + } + + { + auto jt = env.jtnofill( + noop(alice), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + jt.jv.removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + auto const seq = env.seq(alice); + // should fail because BatchSigners does not have signer for SponsorSignature + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temBAD_SIGNER)); + } + } + + { + // test Inner transaction with prefunded sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob); + env.fund(XRP(1001), sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 100, XRP(100)), + sponsor::SponseeAcc(alice), + Fee(XRP(1)), + Ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + auto jt = env.jtnofill( + check::create(alice, bob, XRP(1)), sponsor::As(sponsor, spfSponsorReserve)); + // remove txn signature since it is filled by env.jtnofill() + jt.jv.removeMember(jss::TxnSignature); + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(jt.jv, seq + 2), + Ter(tesSUCCESS)); + env.close(); + + // affect sponsor reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Fee is paid by outer transaction originator (alice) + BEAST_EXPECT(env.balance(alice) == XRP(999)); + BEAST_EXPECT(env.balance(sponsor) == XRP(900)); + + // reserve count is decreased + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 99); + } + + { + // test Inner transaction with co-signing sponsor + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto jt = env.jtnofill( + check::create(alice, bob, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + // remove txn signature since it is filled by env.jtnofill() + jt.jv.removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName); + jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + auto const seq = env.seq(alice); + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(noop(alice), seq + 1), + batch::Inner(jt.jv, seq + 2), + batch::Sig(sponsor), + Ter(tesSUCCESS)); + env.close(); + + // affect sponsor reserve + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // Fee is paid by outer transaction originator (alice) + BEAST_EXPECT(env.balance(alice) == XRP(999)); + BEAST_EXPECT(env.balance(sponsor) == XRP(1000)); + } + + // Inner tx with sfSponsor + spfSponsorFee (pre-funded) is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + // Create pre-funded sponsorship + env(sponsor::set(sponsor, 0, 0, XRP(1)), sponsor::SponseeAcc(alice), Fee(XRP(1))); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + // Create inner transaction with sponsor fee + auto innerPay = pay(alice, bob, XRP(1)); + innerPay[sfSponsor.jsonName] = sponsor.human(); + innerPay[sfSponsorFlags.jsonName] = static_cast(spfSponsorFee); + + // Should be rejected with temBAD_FEE + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerPay, seq + 1), + batch::Inner(noop(alice), seq + 2), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Inner tx with sfSponsor + spfSponsorFee (co-signed) is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 1, 2); + + auto innerPay = pay(alice, bob, XRP(1)); + innerPay[sfSponsor.jsonName] = sponsor.human(); + innerPay[sfSponsorFlags.jsonName] = static_cast(spfSponsorFee); + + // Should be rejected with temBAD_FEE + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerPay, seq + 1), + batch::Inner(noop(alice), seq + 2), + batch::Sig(sponsor), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Inner tx with spfSponsorFee + spfSponsorReserve is rejected + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + auto innerTx = check::create(alice, bob, XRP(1)); + innerTx[sfSponsor.jsonName] = sponsor.human(); + innerTx[sfSponsorFlags.jsonName] = + static_cast(spfSponsorFee | spfSponsorReserve); + + // Should be rejected with temBAD_FEE (fee sponsorship check comes first) + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(innerTx, seq + 1), + batch::Inner(noop(alice), seq + 2), + Ter(temINVALID_FLAG)); + env.close(); + } + + // Outer batch tx with sponsor fee is allowed + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(bob); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + + // Outer batch with sponsor fee should work fine + env(batch::outer(bob, seq, batchFee, tfAllOrNothing), + batch::Inner(noop(bob), seq + 1), + batch::Inner(noop(bob), seq + 2), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // Sponsor paid the fee + BEAST_EXPECT(env.balance(bob) == XRP(1000)); + } + } + + // Verify that the central allow-list in preflight1Sponsor rejects + // spfSponsorReserve for transaction types that v1 does not permit. + void + testReserveSponsorGate() + { + testcase("Reserve sponsor allow-list gate"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 10, XRP(10)), sponsor::SponseeAcc(alice)); + env.close(); + + auto checkBlocked = [&](json::Value const& jv) { + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID_FLAG)); + }; + + checkBlocked(ticket::create(alice, 1)); + checkBlocked(offer(alice, XRP(100), bob["USD"](100))); + checkBlocked(did::setValid(alice)); + checkBlocked(token::mint(alice, 0u)); + checkBlocked(sponsor::set(alice, 0, 10, XRP(10))); + checkBlocked(acctdelete(alice, bob)); + checkBlocked(loan::set(alice, uint256(1), Number{1})); + } + + void + testSponsorReserve(bool cosigning) + { + testRequireFlag(); + testSponsorReserveSimple(cosigning); + testCheck(cosigning); + testCredential(cosigning); + testDelegate(cosigning); + testDepositPreauth(cosigning); + testEscrow(cosigning); + testMPToken(cosigning); + testPayChan(cosigning); + testSignerList(cosigning); + testTrustSet(cosigning); + } + + void + testZeroBalanceSponsoredPaymentFeePayerCheck() + { + // Zero-balance sponsored Payment: getFeePayer() consistency check + testcase("Sponsored Payment: minimal-balance account with sponsor-pays-fee"); + + using namespace jtx; + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const dest("dest"); + + auto const baseFee = env.current()->fees().base; + auto const baseReserve = env.current()->fees().reserve; + + // Fund sponsor and dest generously, alice with base reserve + 1 XRP for payment + env.fund(XRP(10000), sponsor, dest); + env.fund(baseReserve + XRP(1), alice); + env.close(); + + // Precondition: alice has base reserve + 1 XRP (enough for payment but not fee) + BEAST_EXPECT(env.balance(alice) == baseReserve + XRP(1)); + + // Alice tries to send a Payment to dest where sponsor pays the fee via spfSponsorFee. + // Passed even alice balance doesn't have enough to pay fee. + auto const preDest = env.balance(dest); + auto const preSponsor = env.balance(sponsor); + + // Alice sends 1 XRP to dest, sponsor pays the fee + env(pay(alice, dest, XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(baseFee)); + env.close(); + + // Payment succeeded + // Alice's balance decreased by 1 XRP (the payment amount, NOT the fee) + BEAST_EXPECT(env.balance(alice) == baseReserve); + + // Dest received 1 XRP + BEAST_EXPECT(env.balance(dest) == preDest + XRP(1)); + + // Sponsor paid the fee (NOT alice) + BEAST_EXPECT(env.balance(sponsor) == preSponsor - baseFee); + } + + void + testTrustSetCounterpartySponsorMisroute() + { + // TrustSet's modify path applies the tx-level reserve sponsor to whichever + // side has its reserve gate trip on this update, regardless of whether that + // side belongs to the tx submitter. trustCreate only sets the submitter's + // reserve flag and snapshots the counterparty's asfDefaultRipple state into + // the line's NoRipple bit; if the counterparty later toggles asfDefaultRipple + // (the canonical issuer flow), the line and account flags disagree and on + // the submitter's next TrustSet the counterparty-side gate fires. Sponsor still will be + // checked if it can be applied to that end of the trustLine. + + testcase("TrustSet modify with sponsor does not misroute onto counterparty side"); + + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice{"alice_t2178"}; + Account const bob{"bob_t2178"}; + Account const carol{"carol_t2178"}; + + // Fund without auto-setting asfDefaultRipple + env.fund(XRP(100'000), alice, bob, carol); + env.close(); + + // Determine account ordering + bool const aliceIsHigh = alice.id() > bob.id(); + + // To trigger the bug, we need the COUNTERPARTY's reserve gate to trip + // We use issuer/holder terminology where: + // - holder creates the trust line (their reserve is set first) + // - issuer enables DefaultRipple after (creates flag mismatch) + // - holder's second TrustSet triggers issuer's reserve gate + + auto const issuer = aliceIsHigh ? bob : alice; + auto const holder = aliceIsHigh ? alice : bob; + auto const usd = issuer["USD"]; + + // Issuer must NOT have DefaultRipple set initially + // Clear it explicitly (env.fund may have set it) + env(fclear(issuer, asfDefaultRipple)); + env.close(); + + // Holder creates the trust line first (holder's reserve flag is set) + // At this point, issuer does NOT have DefaultRipple set, so + // the NoRipple bit on issuer's side is set according to issuer's current flag + env(trust(holder, usd(1'000))); + env.close(); + + // Issuer now enables asfDefaultRipple (canonical issuer flow) + // This creates a mismatch: issuer's account flag says DefaultRipple=true + // but the trust line's NoRipple bit on issuer's side is still set + env(fset(issuer, asfDefaultRipple)); + env.close(); + + SF_ACCOUNT const& issuerSponsorField = aliceIsHigh ? sfLowSponsor : sfHighSponsor; + SF_ACCOUNT const& holderSponsorField = aliceIsHigh ? sfHighSponsor : sfLowSponsor; + + auto const lineKey = keylet::trustLine(alice, bob, usd.currency); + auto const sleLineBefore = env.le(lineKey); + if (!BEAST_EXPECT(sleLineBefore)) + return; + BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfLowSponsor)); + BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfHighSponsor)); + + auto const carolBefore = sponsoringOwnerCount(env, carol); + BEAST_EXPECT(carolBefore == 0); + auto const issuerSponsoredBefore = sponsoredOwnerCount(env, issuer); + BEAST_EXPECT(issuerSponsoredBefore == 0); + + // Holder modifies the trust line with Carol as sponsor + // This should trigger the issuer's reserve gate because of the DefaultRipple mismatch + // Carol (sponsor) should NOT be applied to issuer's side (issuer != tx submitter) + env(trust(holder, usd(2'000)), + sponsor::As(carol, spfSponsorReserve), + Sig(sfSponsorSignature, carol), + Ter(tesSUCCESS)); + env.close(); + + auto const sleLineAfter = env.le(lineKey); + if (!BEAST_EXPECT(sleLineAfter)) + return; + + // Carol only agreed to back the holder, not the issuer + BEAST_EXPECT(!sleLineAfter->isFieldPresent(issuerSponsorField)); + + // Holder's side also has no sponsor because holder's reserve flag was + // already set on the FIRST TrustSet (no sponsor in scope then) + BEAST_EXPECT(!sleLineAfter->isFieldPresent(holderSponsorField)); + + // Carol's sponsoring count should remain unchanged (no misroute) + auto const carolAfter = sponsoringOwnerCount(env, carol); + BEAST_EXPECT(carolAfter == carolBefore); + + // Issuer's sponsored count should remain unchanged (no misroute) + auto const issuerSponsoredAfter = sponsoredOwnerCount(env, issuer); + BEAST_EXPECT(issuerSponsoredAfter == issuerSponsoredBefore); + } + + void + testSelfEscrowFinishReserveGate() + { + testcase("Self-escrow finish reserve order gated by amendment"); + using namespace test::jtx; + using namespace std::chrono_literals; + + // Finishing a self-escrow (source == destination) whose trust line + // was deleted while the escrow was outstanding auto-creates the line, + // and the outcome of that reserve check depends on whether the escrow + // reserve is released before delivery (Sponsor) or after (legacy). + // With the source's balance in the one-increment window + // [reserve(1), reserve(2)), the legacy order requires reserve(2) and + // fails, while the Sponsor order requires reserve(1) and succeeds. + auto runTest = [&](FeatureBitset features, TER expected) { + Account const alice("alice"); + Account const gw("gw"); + auto const usd = gw["usd"]; + + Env env{*this, features}; + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(10000), alice, gw); + env.close(); + + env(fset(gw, asfAllowTrustLineLocking)); + env.close(); + + env.trust(usd(1000), alice); + env.close(); + env(pay(gw, alice, usd(100))); + env.close(); + + // Escrow alice's entire USD balance to herself. The escrowed + // IOUs return to the issuer, zeroing the line balance. + auto const seq = env.seq(alice); + env(escrow::create(alice, alice, usd(100)), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s)); + env.close(); + + // Delete the now-empty trust line. Both accounts have + // DefaultRipple set (jtx fund does that), so a plain limit-0 + // TrustSet returns the line to its default state. + env(trust(alice, usd(0))); + env.close(); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow + + // Put alice's balance in the window. Pay the excess away + // directly: adjustAccountXRPBalance needs the Sponsor amendment. + STAmount const target = reserve(env, 1) + XRP(1); + env(pay(alice, env.master, env.balance(alice) - target - baseFee), Fee(baseFee)); + env.close(); + BEAST_EXPECT(env.balance(alice) == target); + + env(escrow::finish(alice, alice, seq), + escrow::kCondition(escrow::kCb1), + escrow::kFulfillment(escrow::kFb1), + Fee(baseFee * 150), + Ter(expected)); + env.close(); + + if (expected == tesSUCCESS) + { + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(env.balance(alice, usd) == usd(100)); + BEAST_EXPECT(ownerCount(env, alice) == 1); // the new line + } + else + { + BEAST_EXPECT(env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency))); + BEAST_EXPECT(ownerCount(env, alice) == 1); // still the escrow + } + }; + + // Pre-amendment: legacy order — the escrow still counts against the + // reserve while the auto-created line is checked. + runTest(testableAmendments() - featureSponsor, tecNO_LINE_INSUF_RESERVE); + + // Post-amendment: the escrow reserve is recycled into the new line. + runTest(testableAmendments(), tesSUCCESS); + } + + void + testFeeSponsoredVaultInvariant() + { + // The ValidVault invariant checks that the vault's balance and the + // depositor's balance change by equal amounts. For XRP vaults it adds the + // fee back into the depositor's balance change: normally the depositor + // pays the fee, so their balance drops by (deposit amount + fee), and + // adding the fee back leaves just the deposit amount to compare against + // the vault. But when a fee sponsor pays, the depositor's balance drops + // by only the deposit amount, so the fee must NOT be added back or the + // equal-amount check fails. + testcase("Fee-sponsored VaultDeposit/VaultWithdraw pass ValidVault invariant"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset}); + env(vaultTx); + env.close(); + + // Control: the same deposit shape, unsponsored, succeeds. + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}), + Ter(tesSUCCESS)); + env.close(); + + // Fee-sponsored (co-signed) deposit succeeds. + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + // The same helper (deltaAssetsTxAccount) drives the withdraw path, so a + // fee-sponsored withdrawal back to the depositor's own account also + // passes on the destination side. + env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(50)}), + Fee(XRP(1)), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + } + + void + testSponsoredObjectDeletionRefund() + { + // Deleting a co-signed reserve-sponsored object must + // refund the sponsor's SponsoringOwnerCount back to zero. + testcase("Sponsored object deletion refunds sponsor owner count"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const sponsor("sponsor"); + env.fund(XRP(100000), sponsor); + env.close(); + + // Sponsored Check deletion - verify decreaseOwnerCountForObject + // (used by CheckCancel) reads the object's sfSponsor field and refunds + // the sponsor's owner count. + { + testcase(" — Check deletion"); + + Account const checkOwner("check_owner"); + Account const dest("check_dest"); + env.fund(XRP(100000), checkOwner, dest); + env.close(); + + // Create a check with co-signed reserve sponsorship. This bumps the + // sponsor's sfSponsoringOwnerCount rather than the check owner's. + auto const checkSeq = env.seq(checkOwner); + env(check::create(checkOwner, dest, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountBefore == 1); // check costs 1 owner count + + // Cancel (delete) the check. + env(check::cancel(checkOwner, keylet::check(checkOwner, checkSeq).key)); + env.close(); + + auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded + } + + // Sponsored TrustSet (trust line) deletion - verify the sponsor + // refund works when a sponsored trust line is deleted + { + testcase(" — TrustLine deletion"); + + Account const issuer("issuer"); + Account const holder("holder"); + env.fund(XRP(100000), issuer, holder); + env.close(); + + auto const usd = issuer["USD"]; + + // Create trust line with sponsorship on the holder side + env(trust(holder, usd(1000)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountBefore == 1); // trust line costs 1 + + // Delete the trust line by clearing it to default + env(trust(holder, usd(0))); + env.close(); + + auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor); + BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded + } + } + +protected: + void + testSponsor() + { + testDisabled(); + testInvalidSponsorshipSet(); + testPseudoAccountSponsorship(); + + testSingleSigning(); + testMultiSigning(); + + testInvalidSponsorField(); + + testSimpleSponsorshipSet(); + + testPreFundAndCosign(); + testSponsoredFreeTierReserve(); + + testTransferSponsor(); + testLegacySignerListReserve(); + testSponsorFee(); + testSponsorAccount(); + + testAccountDelete(); + + testDelegatePermission(); + testDelegateBlockReserveSponsor(); + testDelegateSponsorFeePayer(); + + testBatch(); + + testSponsoredTrustLineNoFreeReserve(); + testCoSignReserveBoundedBySponsorshipBudget(); + testReserveSponsorGate(); + + testZeroBalanceSponsoredPaymentFeePayerCheck(); + testTrustSetCounterpartySponsorMisroute(); + testSelfEscrowFinishReserveGate(); + + testFeeSponsoredVaultInvariant(); + testSponsoredObjectDeletionRefund(); + } + + void + testTxSponsor(bool cosigning) + { + testSponsorReserve(cosigning); + } + +public: + void + run() override + { + testSponsor(); + } +}; + +class SponsorTxCosigning_test : public Sponsor_test +{ + void + run() override + { + testTxSponsor(true); + } +}; + +class SponsorTxPrefunded_test : public Sponsor_test +{ + void + run() override + { + testTxSponsor(false); + } +}; + +BEAST_DEFINE_TESTSUITE(Sponsor, app, xrpl); +BEAST_DEFINE_TESTSUITE(SponsorTxCosigning, app, xrpl); +BEAST_DEFINE_TESTSUITE(SponsorTxPrefunded, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index 626cdbe44d..f14afd5990 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -47,9 +47,11 @@ namespace xrpl { class Ticket_test : public beast::unit_test::Suite { - /// @brief Validate metadata for a successful TicketCreate transaction. - /// - /// @param env current jtx env (tx and meta are extracted using it) + /** + * @brief Validate metadata for a successful TicketCreate transaction. + * + * @param env current jtx env (tx and meta are extracted using it) + */ void checkTicketCreateMeta(test::jtx::Env& env) { @@ -233,11 +235,13 @@ class Ticket_test : public beast::unit_test::Suite BEAST_EXPECT(*ticketSeqs.rbegin() == acctRootFinalSeq - 1); } - /// @brief Validate metadata for a ticket using transaction. - /// - /// The transaction may have been successful or failed with a tec. - /// - /// @param env current jtx env (tx and meta are extracted using it) + /** + * @brief Validate metadata for a ticket using transaction. + * + * The transaction may have been successful or failed with a tec. + * + * @param env current jtx env (tx and meta are extracted using it) + */ void checkTicketConsumeMeta(test::jtx::Env& env) { @@ -503,7 +507,7 @@ class Ticket_test : public beast::unit_test::Suite Account const alice{"alice"}; // Fund alice not quite enough to make the reserve for a Ticket. - env.fund(env.current()->fees().accountReserve(1) - drops(1), alice); + env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), alice); env.close(); env(ticket::create(alice, 1), Ter(tecINSUFFICIENT_RESERVE)); @@ -511,7 +515,8 @@ class Ticket_test : public beast::unit_test::Suite env.require(Owners(alice, 0), tickets(alice, 0)); // Give alice enough to exactly meet the reserve for one Ticket. - env(pay(env.master, alice, env.current()->fees().accountReserve(1) - env.balance(alice))); + env(pay( + env.master, alice, env.current()->fees().accountReserve(1, 1) - env.balance(alice))); env.close(); env(ticket::create(alice, 1)); @@ -524,7 +529,7 @@ class Ticket_test : public beast::unit_test::Suite env( pay(env.master, alice, - env.current()->fees().accountReserve(250) - drops(1) - env.balance(alice))); + env.current()->fees().accountReserve(250, 1) - drops(1) - env.balance(alice))); env.close(); // alice doesn't quite have the reserve for a total of 250 @@ -535,7 +540,8 @@ class Ticket_test : public beast::unit_test::Suite // Give alice enough so she can make the reserve for all 250 // Tickets. - env(pay(env.master, alice, env.current()->fees().accountReserve(250) - env.balance(alice))); + env(pay( + env.master, alice, env.current()->fees().accountReserve(250, 1) - env.balance(alice))); env.close(); std::uint32_t const ticketSeq{env.seq(alice) + 1}; diff --git a/src/test/app/TrustSet_test.cpp b/src/test/app/TrustSet_test.cpp index 15abc3bd06..e24373fd21 100644 --- a/src/test/app/TrustSet_test.cpp +++ b/src/test/app/TrustSet_test.cpp @@ -191,7 +191,7 @@ public: auto const txFee = env.current()->fees().base; auto const baseReserve = env.current()->fees().reserve; - auto const threelineReserve = env.current()->fees().accountReserve(3); + auto const threelineReserve = env.current()->fees().accountReserve(3, 1); env.fund(XRP(10000), gwA, gwB, assistor); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 91eb26da7e..3175e742d9 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -2335,6 +2337,43 @@ public: BEAST_EXPECT(env.balance(alice) == drops(5)); } + void + testSponsorTxCannotQueue() + { + using namespace jtx; + testcase("disallow sponsored transaction from being queued"); + + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); + + auto sponsor = Account("sponsor"); + auto sponsee = Account("sponsee"); + auto filler = Account("filler"); + + env.fund(XRP(50000), noripple(sponsor, sponsee)); + env.close(); + env.fund(XRP(50000), noripple(filler)); + env.close(); + + fillQueue(env, filler); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions are not allowed to be queued. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(telCAN_NOT_QUEUE)); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions may still apply directly if they pay the + // open ledger fee. They just cannot be held in the queue. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(openLedgerCost(env)), + Ter(tesSUCCESS)); + checkMetrics(*this, env, 0, 6, 5, 3); + } + void testDelegateTxCannotQueue() { @@ -4698,6 +4737,7 @@ public: testBlockersSeq(); testBlockersTicket(); testInFlightBalance(); + testSponsorTxCannotQueue(); testDelegateTxCannotQueue(); testConsequences(); } diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 8077c21863..60228f6723 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -2596,7 +2596,7 @@ private: } void - testQuorumDisabled() + testQuorumDisabled() // NOLINT(readability-function-size) { testcase("Test quorum disabled"); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 3877e08f7e..617820c89c 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ #include #include +#include #include #include #include @@ -1804,7 +1806,7 @@ class Vault_test : public beast::unit_test::Suite auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -2686,7 +2688,7 @@ class Vault_test : public beast::unit_test::Suite auto const [acctReserve, incReserve] = [this]() -> std::pair { Env const env{*this, testableAmendments()}; return { - env.current()->fees().accountReserve(0).drops() / kDropsPerXrp.drops(), + env.current()->fees().accountReserve(0, 1).drops() / kDropsPerXrp.drops(), env.current()->fees().increment.drops() / kDropsPerXrp.drops()}; }(); @@ -5245,11 +5247,15 @@ class Vault_test : public beast::unit_test::Suite auto const maxInt64 = std::to_string(std::numeric_limits::max()); BEAST_EXPECT(maxInt64 == "9223372036854775807"); - // Naming things is hard auto const maxInt64Plus1 = std::to_string( static_cast(std::numeric_limits::max()) + 1); BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808"); + // Naming things is hard + auto const maxInt64Plus2 = std::to_string( + static_cast(std::numeric_limits::max()) + 2); + BEAST_EXPECT(maxInt64Plus2 == "9223372036854775809"); + auto const initialXRP = to_string(kInitialXrp); BEAST_EXPECT(initialXRP == "100000000000000000"); @@ -5276,25 +5282,58 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); - tx[sfAssetsMaximum] = maxInt64Plus1; - env(tx, Ter(tefEXCEPTION)); - env.close(); + // There are several parse failures expected in this function, so just disable it once. + env.setParseFailureExpected(true); + try + { + tx[sfAssetsMaximum] = maxInt64Plus1; + env(tx, Ter(tefEXCEPTION)); + env.close(); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } + + try + { + tx[sfAssetsMaximum] = maxInt64Plus2; + env(tx, Ter(tefEXCEPTION)); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } - // This value will be rounded - auto const insertAt = maxInt64Plus1.size() - 3; - auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." + - maxInt64Plus1.substr(insertAt); // (max int64+1) / 1000 - BEAST_EXPECT(decimalTest == "9223372036854775.808"); - tx[sfAssetsMaximum] = decimalTest; auto const newKeylet = keylet::vault(owner.id(), env.seq(owner)); - env(tx); - env.close(); + try + { + auto const insertAt = maxInt64Plus2.size() - 3; + auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." + + maxInt64Plus2.substr(insertAt); // (max int64+2) / 1000 + BEAST_EXPECT(decimalTest == "9223372036854775.809"); + tx[sfAssetsMaximum] = decimalTest; + env(tx); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } auto const vaultSle = env.le(newKeylet); - if (!BEAST_EXPECT(vaultSle)) - return; - - BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 9223372036854776); + BEAST_EXPECT(!vaultSle); } { @@ -5328,25 +5367,41 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); - tx[sfAssetsMaximum] = maxInt64Plus1; - env(tx, Ter(tefEXCEPTION)); - env.close(); + try + { + tx[sfAssetsMaximum] = maxInt64Plus2; + env(tx, Ter(tefEXCEPTION)); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } - // This value will be rounded - auto const insertAt = maxInt64Plus1.size() - 1; - auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." + - maxInt64Plus1.substr(insertAt); // (max int64+1) / 10 - BEAST_EXPECT(decimalTest == "922337203685477580.8"); - tx[sfAssetsMaximum] = decimalTest; auto const newKeylet = keylet::vault(owner.id(), env.seq(owner)); - env(tx); - env.close(); + try + { + auto const insertAt = maxInt64Plus2.size() - 1; + auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." + + maxInt64Plus2.substr(insertAt); // (max int64+2) / 10 + BEAST_EXPECT(decimalTest == "922337203685477580.9"); + tx[sfAssetsMaximum] = decimalTest; + env(tx); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } auto const vaultSle = env.le(newKeylet); - if (!BEAST_EXPECT(vaultSle)) - return; - - BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 922337203685477581); + BEAST_EXPECT(!vaultSle); } { @@ -5373,9 +5428,22 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); - tx[sfAssetsMaximum] = maxInt64Plus1; - env(tx); - env.close(); + // Since several tests are expected to have parser failures, leave this flag set for the + // remainder of this function. + env.setParseFailureExpected(true); + try + { + tx[sfAssetsMaximum] = maxInt64Plus2; + env(tx); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } tx[sfAssetsMaximum] = "1000000000000000e80"; env.close(); @@ -5385,22 +5453,27 @@ class Vault_test : public beast::unit_test::Suite // These values will be rounded to 15 significant digits { - auto const insertAt = maxInt64Plus1.size() - 1; - auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." + - maxInt64Plus1.substr(insertAt); // (max int64+1) / 10 - BEAST_EXPECT(decimalTest == "922337203685477580.8"); - tx[sfAssetsMaximum] = decimalTest; auto const newKeylet = keylet::vault(owner.id(), env.seq(owner)); - env(tx); - env.close(); + try + { + auto const insertAt = maxInt64Plus2.size() - 1; + auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." + + maxInt64Plus2.substr(insertAt); // (max int64+2) / 10 + BEAST_EXPECT(decimalTest == "922337203685477580.9"); + tx[sfAssetsMaximum] = decimalTest; + env(tx); + // should throw in parser + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."); + } auto const vaultSle = env.le(newKeylet); - if (!BEAST_EXPECT(vaultSle)) - return; - - BEAST_EXPECT( - (vaultSle->at(sfAssetsMaximum) == - Number{9223372036854776, 2, Number::Normalized{}})); + BEAST_EXPECT(!vaultSle); } { tx[sfAssetsMaximum] = "9223372036854775807e40"; // max int64 * 10^40 @@ -5927,8 +6000,9 @@ class Vault_test : public beast::unit_test::Suite token->setFieldVL(*field, gMakeZeroBuffer(kEcGamalEncryptedTotalLength)); sb.update(token); + auto const dummyTx = *env.jt(noop(holder)).stx; BEAST_EXPECT( - removeEmptyHolding(sb, holder.id(), MPTIssue(mpt.issuanceID()), j) == + removeEmptyHolding({sb, dummyTx}, holder.id(), MPTIssue(mpt.issuanceID()), j) == tecHAS_OBLIGATIONS); BEAST_EXPECT(sb.peek(tokenKeylet) != nullptr); } diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 6c8377d8e3..d1f9cd2722 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -141,7 +141,7 @@ struct SEnv XRPAmount reserve(std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } XRPAmount @@ -370,7 +370,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj XRPAmount reserve(std::uint32_t count) { - return XEnv(*this).env.current()->fees().accountReserve(count); + return XEnv(*this).env.current()->fees().accountReserve(count, 1); } XRPAmount @@ -3923,12 +3923,10 @@ private: [[nodiscard]] bool verify() const { - for (auto const& [acct, state] : accounts) - { - if (!state.verify(env, acct)) - return false; - } - return true; + return std::ranges::all_of(accounts, [&](auto const& entry) { + auto const& [acct, state] = entry; + return state.verify(env, acct); + }); } struct BridgeCounters diff --git a/src/test/beast/IPEndpoint_test.cpp b/src/test/beast/IPEndpoint_test.cpp index b1cd4709b4..bc04087891 100644 --- a/src/test/beast/IPEndpoint_test.cpp +++ b/src/test/beast/IPEndpoint_test.cpp @@ -305,6 +305,15 @@ public: BEAST_EXPECT(!isLoopback(ep)); BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep)); + // unspecified IPv6 (::) + ep = Endpoint(AddressV6{}); + BEAST_EXPECT(isUnspecified(ep)); + BEAST_EXPECT(!isPublic(ep)); + BEAST_EXPECT(!isPrivate(ep)); + BEAST_EXPECT(!isMulticast(ep)); + BEAST_EXPECT(!isLoopback(ep)); + BEAST_EXPECTS(to_string(ep) == "::", to_string(ep)); + { ep = Endpoint::fromString("192.0.2.112"); BEAST_EXPECT(!isUnspecified(ep)); diff --git a/src/test/beast/define_print.cpp b/src/test/beast/define_print.cpp index 569b06ca67..e6b24e5cf2 100644 --- a/src/test/beast/define_print.cpp +++ b/src/test/beast/define_print.cpp @@ -15,7 +15,9 @@ namespace beast::unit_test { -/** A suite that prints the list of globally defined suites. */ +/** + * A suite that prints the list of globally defined suites. + */ class print_test : public Suite { public: diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 92a4c67e32..45f58d16ba 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -1034,16 +1034,6 @@ public: // slow ledger is generated UndoDelay undoDelay{behind}; sim.collectors.add(undoDelay); - -#if 0 - // Have all beast::journal output printed to stdout - for (Peer* p : network) - p->sink.threshold(beast::Severity::All); - - // Print ledger accept and fully validated events to stdout - StreamCollector sc{std::cout}; - sim.collectors.add(sc); -#endif // Run the simulation for 100 seconds of simulation time with std::chrono::nanoseconds const simDuration = 100s; diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index 1def09db13..437ad81ee0 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -21,7 +21,8 @@ namespace xrpl::test { -/** In progress simulations for diversifying and distributing validators +/** + * In progress simulations for diversifying and distributing validators */ class DistributedValidators_test : public beast::unit_test::Suite { diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 0ddf1bf82f..a4eb7bc087 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -519,17 +519,18 @@ class LedgerTrie_test : public beast::unit_test::Suite // Changing largestSeq perspective changes preferred branch { - /** Build the tree below with initial tip support annotated - A - / \ - B(1) C(1) - / | | - H D F(1) - | - E(2) - | - G - */ + /** + * Build the tree below with initial tip support annotated + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(2) + * | + * G + */ LedgerTrie t; LedgerHistoryHelper h; t.insert(h["ab"]); @@ -548,17 +549,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{4})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** One of E advancing to G doesn't change anything - A - / \ - B(1) C(1) - / | | - H D F(1) - | - E(1) - | - G(1) - */ + /** + * One of E advancing to G doesn't change anything + * A + * / \ + * B(1) C(1) + * / | | + * H D F(1) + * | + * E(1) + * | + * G(1) + */ t.remove(h["abde"]); t.insert(h["abdeg"]); @@ -570,17 +572,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** C advancing to H does advance the seq 3 preferred ledger - A - / \ - B(1) C - / | | - H(1)D F(1) - | - E(1) - | - G(1) - */ + /** + * C advancing to H does advance the seq 3 preferred ledger + * A + * / \ + * B(1) C + * / | | + * H(1)D F(1) + * | + * E(1) + * | + * G(1) + */ t.remove(h["ac"]); t.insert(h["abh"]); @@ -592,17 +595,18 @@ class LedgerTrie_test : public beast::unit_test::Suite BEAST_EXPECT(t.getPreferred(Seq{5})->id == h["a"].id()); // NOLINTEND(bugprone-unchecked-optional-access) - /** F advancing to E also moves the preferred ledger forward - A - / \ - B(1) C - / | | - H(1)D F - | - E(2) - | - G(1) - */ + /** + * F advancing to E also moves the preferred ledger forward + * A + * / \ + * B(1) C + * / | | + * H(1)D F + * | + * E(2) + * | + * G(1) + */ t.remove(h["acf"]); t.insert(h["abde"]); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 17c48b1ccc..e98a0e1e88 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -124,7 +124,7 @@ backend=sqlite } /** - Write an xrpld config file and remove when done. + * Write an xrpld config file and remove when done. */ class FileCfgGuard : public xrpl::detail::FileDirGuard { @@ -235,7 +235,7 @@ more-xrpl-validators.net } /** - Write a validators.txt file and remove when done. + * Write a validators.txt file and remove when done. */ class ValidatorsTxtGuard : public detail::FileDirGuard { diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index d37569d6cd..373ec66cd1 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -229,63 +229,7 @@ public: fail(); } // There are too many issues when working with soci::row and - // boost::tuple. DO NOT USE soci row! I had a set of workarounds to - // make soci row less error prone, I'm keeping these tests in case I - // try to add soci::row and boost::tuple back into soci. -#if 0 - try - { - std::int32_t ig = 0; - std::uint32_t uig = 0; - std::int64_t big = 0; - std::uint64_t ubig = 0; - soci::row r; - s << "SELECT I, UI, BI, UBI from STT", soci::into (r); - ig = r.get(0); - uig = r.get(1); - big = r.get(2); - ubig = r.get(3); - BEAST_EXPECT(ig == id[0] && uig == uid[0] && big == bid[0] && - ubig == ubid[0]); - } - catch (std::exception&) - { - fail (); - } - try - { - std::int32_t ig = 0; - std::uint32_t uig = 0; - std::int64_t big = 0; - std::uint64_t ubig = 0; - soci::row r; - s << "SELECT I, UI, BI, UBI from STT", soci::into (r); - ig = r.get("I"); - uig = r.get("UI"); - big = r.get("BI"); - ubig = r.get("UBI"); - BEAST_EXPECT(ig == id[0] && uig == uid[0] && big == bid[0] && - ubig == ubid[0]); - } - catch (std::exception&) - { - fail (); - } - try - { - boost::tuple d; - s << "SELECT I, UI, BI, UBI from STT", soci::into (d); - BEAST_EXPECT(get<0>(d) == id[0] && get<1>(d) == uid[0] && - get<2>(d) == bid[0] && get<3>(d) == ubid[0]); - } - catch (std::exception&) - { - fail (); - } -#endif + // boost::tuple. DO NOT USE soci row! } { namespace bfs = boost::filesystem; diff --git a/src/test/csf/BasicNetwork.h b/src/test/csf/BasicNetwork.h index 4b88592128..0428475504 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/test/csf/BasicNetwork.h @@ -6,59 +6,59 @@ #include namespace xrpl::test::csf { -/** Peer to peer network simulator. - - The network is formed from a set of Peer objects representing - vertices and configurable connections representing edges. - The caller is responsible for creating the Peer objects ahead - of time. - - Peer objects cannot be destroyed once the BasicNetwork is - constructed. To handle peers going online and offline, - callers can simply disconnect all links and reconnect them - later. Connections are directed, one end is the inbound - Peer and the other is the outbound Peer. - - Peers may send messages along their connections. To simulate - the effects of latency, these messages can be delayed by a - configurable duration set when the link is established. - Messages always arrive in the order they were sent on a - particular connection. - - A message is modeled using a lambda function. The caller - provides the code to execute upon delivery of the message. - If a Peer is disconnected, all messages pending delivery - at either end of the connection will not be delivered. - - When creating the Peer set, the caller needs to provide a - Scheduler object for managing the timing and delivery - of messages. After constructing the network, and establishing - connections, the caller uses the scheduler's step* functions - to drive messages through the network. - - The graph of peers and connections is internally represented - using Digraph. Clients have - const access to that graph to perform additional operations not - directly provided by BasicNetwork. - - Peer Requirements: - - Peer should be a lightweight type, cheap to copy - and/or move. A good candidate is a simple pointer to - the underlying user defined type in the simulation. - - Expression Type Requirements - ---------- ---- ------------ - P Peer - u, v Values of type P - P u(v) CopyConstructible - u.~P() Destructible - u == v bool EqualityComparable - u < v bool LessThanComparable - std::hash

class std::hash is defined for P - ! u bool true if u is not-a-peer - -*/ +/** + * Peer to peer network simulator. + * + * The network is formed from a set of Peer objects representing + * vertices and configurable connections representing edges. + * The caller is responsible for creating the Peer objects ahead + * of time. + * + * Peer objects cannot be destroyed once the BasicNetwork is + * constructed. To handle peers going online and offline, + * callers can simply disconnect all links and reconnect them + * later. Connections are directed, one end is the inbound + * Peer and the other is the outbound Peer. + * + * Peers may send messages along their connections. To simulate + * the effects of latency, these messages can be delayed by a + * configurable duration set when the link is established. + * Messages always arrive in the order they were sent on a + * particular connection. + * + * A message is modeled using a lambda function. The caller + * provides the code to execute upon delivery of the message. + * If a Peer is disconnected, all messages pending delivery + * at either end of the connection will not be delivered. + * + * When creating the Peer set, the caller needs to provide a + * Scheduler object for managing the timing and delivery + * of messages. After constructing the network, and establishing + * connections, the caller uses the scheduler's step* functions + * to drive messages through the network. + * + * The graph of peers and connections is internally represented + * using Digraph. Clients have + * const access to that graph to perform additional operations not + * directly provided by BasicNetwork. + * + * Peer Requirements: + * + * Peer should be a lightweight type, cheap to copy + * and/or move. A good candidate is a simple pointer to + * the underlying user defined type in the simulation. + * + * Expression Type Requirements + * ---------- ---- ------------ + * P Peer + * u, v Values of type P + * P u(v) CopyConstructible + * u.~P() Destructible + * u == v bool EqualityComparable + * u < v bool LessThanComparable + * std::hash

class std::hash is defined for P + * ! u bool true if u is not-a-peer + */ template class BasicNetwork { @@ -92,79 +92,84 @@ public: BasicNetwork(Scheduler& s); - /** Connect two peers. - - The link is directed, with `from` establishing - the outbound connection and `to` receiving the - incoming connection. - - Preconditions: - - from != to (self connect disallowed). - - A link between from and to does not - already exist (duplicates disallowed). - - Effects: - - Creates a link between from and to. - - @param `from` The source of the outgoing connection - @param `to` The recipient of the incoming connection - @param `delay` The time delay of all delivered messages - @return `true` if a new connection was established - */ + /** + * Connect two peers. + * + * The link is directed, with `from` establishing + * the outbound connection and `to` receiving the + * incoming connection. + * + * Preconditions: + * + * from != to (self connect disallowed). + * + * A link between from and to does not + * already exist (duplicates disallowed). + * + * Effects: + * + * Creates a link between from and to. + * + * @param `from` The source of the outgoing connection + * @param `to` The recipient of the incoming connection + * @param `delay` The time delay of all delivered messages + * @return `true` if a new connection was established + */ bool connect(Peer const& from, Peer const& to, duration const& delay = std::chrono::seconds{0}); - /** Break a link. - - Effects: - - If a connection is present, both ends are - disconnected. - - Any pending messages on the connection - are discarded. - - @return `true` if a connection was broken. - */ + /** + * Break a link. + * + * Effects: + * + * If a connection is present, both ends are + * disconnected. + * + * Any pending messages on the connection + * are discarded. + * + * @return `true` if a connection was broken. + */ bool disconnect(Peer const& peer1, Peer const& peer2); - /** Send a message to a peer. - - Preconditions: - - A link exists between from and to. - - Effects: - - If the link is not broken when the - link's `delay` time has elapsed, - the function will be invoked with - no arguments. - - @note Its the caller's responsibility to - ensure that the body of the function performs - activity consistent with `from`'s receipt of - a message from `to`. - */ + /** + * Send a message to a peer. + * + * Preconditions: + * + * A link exists between from and to. + * + * Effects: + * + * If the link is not broken when the + * link's `delay` time has elapsed, + * the function will be invoked with + * no arguments. + * + * @note Its the caller's responsibility to + * ensure that the body of the function performs + * activity consistent with `from`'s receipt of + * a message from `to`. + */ template void send(Peer const& from, Peer const& to, Function&& f); - /** Return the range of active links. - - @return A random access range over Digraph::Edge instances - */ + /** + * Return the range of active links. + * + * @return A random access range over Digraph::Edge instances + */ auto links(Peer const& from) { return links_.outEdges(from); } - /** Return the underlying digraph + /** + * Return the underlying digraph */ [[nodiscard]] Digraph const& graph() const diff --git a/src/test/csf/CollectorRef.h b/src/test/csf/CollectorRef.h index 5bc55c7515..3aef4d617f 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/test/csf/CollectorRef.h @@ -12,45 +12,45 @@ namespace xrpl::test::csf { -/** Holds a type-erased reference to an arbitrary collector. - - A collector is any class that implements - - on(NodeID, SimTime, Event) - - for all events emitted by a Peer. - - This class is used to type-erase the actual collector used by each peer in - the simulation. The idea is to compose complicated and typed collectors - using the helpers in collectors.h, then only type erase at the higher-most - level when adding to the simulation. - - The example code below demonstrates the reason for storing the collector - as a reference. The collector's lifetime will generally be longer than - the simulation; perhaps several simulations are run for a single collector - instance. The collector potentially stores lots of data as well, so the - simulation needs to point to the single instance, rather than requiring - collectors to manage copying that data efficiently in their design. - - @code - // Initialize a specific collector that might write to a file. - SomeFancyCollector collector{"out.file"}; - - // Setup your simulation - Sim sim(trustgraph, topology, collector); - - // Run the simulation - sim.run(100); - - // do any reported related to the collector - collector.report(); - - @endcode - - @note If a new event type is added, it needs to be added to the interfaces - below. - -*/ +/** + * Holds a type-erased reference to an arbitrary collector. + * + * A collector is any class that implements + * + * on(NodeID, SimTime, Event) + * + * for all events emitted by a Peer. + * + * This class is used to type-erase the actual collector used by each peer in + * the simulation. The idea is to compose complicated and typed collectors + * using the helpers in collectors.h, then only type erase at the higher-most + * level when adding to the simulation. + * + * The example code below demonstrates the reason for storing the collector + * as a reference. The collector's lifetime will generally be longer than + * the simulation; perhaps several simulations are run for a single collector + * instance. The collector potentially stores lots of data as well, so the + * simulation needs to point to the single instance, rather than requiring + * collectors to manage copying that data efficiently in their design. + * + * @code + * // Initialize a specific collector that might write to a file. + * SomeFancyCollector collector{"out.file"}; + * + * // Setup your simulation + * Sim sim(trustgraph, topology, collector); + * + * // Run the simulation + * sim.run(100); + * + * // do any reported related to the collector + * collector.report(); + * + * @endcode + * + * @note If a new event type is added, it needs to be added to the interfaces + * below. + */ class CollectorRef { using tp = SimTime; @@ -296,16 +296,17 @@ public: } }; -/** A container of CollectorRefs - - A set of CollectorRef instances that process the same events. An event is - processed by collectors in the order the collectors were added. - - This class type-erases the collector instances. By contract, the - Collectors/collectors class/helper in collectors.h are not type erased and - offer an opportunity for type transformations and combinations with - improved compiler optimizations. -*/ +/** + * A container of CollectorRefs + * + * A set of CollectorRef instances that process the same events. An event is + * processed by collectors in the order the collectors were added. + * + * This class type-erases the collector instances. By contract, the + * Collectors/collectors class/helper in collectors.h are not type erased and + * offer an opportunity for type transformations and combinations with + * improved compiler optimizations. + */ class CollectorRefs { std::vector collectors_; diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 82ed713561..b1b49404b5 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -21,16 +21,16 @@ struct NoEdgeData namespace test::csf { -/** Directed graph - -Basic directed graph that uses an adjacency list to represent out edges. - -Instances of Vertex uniquely identify vertices in the graph. Instances of -EdgeData is any data to store in the edge connecting two vertices. - -Both Vertex and EdgeData should be lightweight and cheap to copy. - -*/ +/** + * Directed graph + * + * Basic directed graph that uses an adjacency list to represent out edges. + * + * Instances of Vertex uniquely identify vertices in the graph. Instances of + * EdgeData is any data to store in the edge connecting two vertices. + * + * Both Vertex and EdgeData should be lightweight and cheap to copy. + */ template class Digraph { @@ -42,41 +42,42 @@ class Digraph Links empty_; public: - /** Connect two vertices - - @param source The source vertex - @param target The target vertex - @param e The edge data - @return true if the edge was created - - */ + /** + * Connect two vertices + * + * @param source The source vertex + * @param target The target vertex + * @param e The edge data + * @return true if the edge was created + */ bool connect(Vertex source, Vertex target, EdgeData e) { return graph_[source].emplace(target, e).second; } - /** Connect two vertices using default constructed edge data - - @param source The source vertex - @param target The target vertex - @return true if the edge was created - - */ + /** + * Connect two vertices using default constructed edge data + * + * @param source The source vertex + * @param target The target vertex + * @return true if the edge was created + */ bool connect(Vertex source, Vertex target) { return connect(source, target, EdgeData{}); } - /** Disconnect two vertices - - @param source The source vertex - @param target The target vertex - @return true if an edge was removed - - If source is not connected to target, this function does nothing. - */ + /** + * Disconnect two vertices + * + * @param source The source vertex + * @param target The target vertex + * @return true if an edge was removed + * + * If source is not connected to target, this function does nothing. + */ bool disconnect(Vertex source, Vertex target) { @@ -88,13 +89,13 @@ public: return false; } - /** Return edge data between two vertices - - @param source The source vertex - @param target The target vertex - @return optional which is std::nullopt if no edge exists - - */ + /** + * Return edge data between two vertices + * + * @param source The source vertex + * @param target The target vertex + * @return optional which is std::nullopt if no edge exists + */ [[nodiscard]] std::optional edge(Vertex source, Vertex target) const { @@ -108,23 +109,25 @@ public: return std::nullopt; } - /** Check if two vertices are connected - - @param source The source vertex - @param target The target vertex - @return true if the source has an out edge to target - */ + /** + * Check if two vertices are connected + * + * @param source The source vertex + * @param target The target vertex + * @return true if the source has an out edge to target + */ [[nodiscard]] bool connected(Vertex source, Vertex target) const { return edge(source, target) != std::nullopt; } - /** Range over vertices in the graph - - @return A boost transformed range over the vertices with out edges in - the graph - */ + /** + * Range over vertices in the graph + * + * @return A boost transformed range over the vertices with out edges in + * the graph + */ [[nodiscard]] auto outVertices() const { @@ -132,10 +135,11 @@ public: graph_, [](Graph::value_type const& v) { return v.first; }); } - /** Range over target vertices - - @param source The source vertex - @return A boost transformed range over the target vertices of source. + /** + * Range over target vertices + * + * @param source The source vertex + * @return A boost transformed range over the target vertices of source. */ [[nodiscard]] auto outVertices(Vertex source) const @@ -148,7 +152,8 @@ public: return boost::adaptors::transform(empty_, transform); } - /** Vertices and data associated with an Edge + /** + * Vertices and data associated with an Edge */ struct Edge { @@ -157,12 +162,13 @@ public: EdgeData data; }; - /** Range of out edges - - @param source The source vertex - @return A boost transformed range of Edge type for all out edges of - source. - */ + /** + * Range of out edges + * + * @param source The source vertex + * @return A boost transformed range of Edge type for all out edges of + * source. + */ [[nodiscard]] auto outEdges(Vertex source) const { @@ -177,11 +183,12 @@ public: return boost::adaptors::transform(empty_, transform); } - /** Vertex out-degree - - @param source The source vertex - @return The number of outgoing edges from source - */ + /** + * Vertex out-degree + * + * @param source The source vertex + * @return The number of outgoing edges from source + */ [[nodiscard]] std::size_t outDegree(Vertex source) const { @@ -191,14 +198,15 @@ public: return 0; } - /** Save GraphViz dot file - - Save a GraphViz dot description of the graph - @param fileName The output file (creates) - @param vertexName A invocable T vertexName(Vertex const &) that - returns the name target use for the vertex in the file - T must be ostream-able - */ + /** + * Save GraphViz dot file + * + * Save a GraphViz dot description of the graph + * @param fileName The output file (creates) + * @param vertexName A invocable T vertexName(Vertex const &) that + * returns the name target use for the vertex in the file + * T must be ostream-able + */ template void saveDot(std::ostream& out, VertexName&& vertexName) const diff --git a/src/test/csf/Histogram.h b/src/test/csf/Histogram.h index 60cb9a132e..62791c10f3 100644 --- a/src/test/csf/Histogram.h +++ b/src/test/csf/Histogram.h @@ -8,17 +8,16 @@ namespace xrpl::test::csf { -/** Basic histogram. - - Histogram for a type `T` that satisfies - - Default construction: T{} - - Comparison : T a, b; bool res = a < b - - Addition: T a, b; T c = a + b; - - Multiplication : T a, std::size_t b; T c = a * b; - - Division: T a; std::size_t b; T c = a/b; - - -*/ +/** + * Basic histogram. + * + * Histogram for a type `T` that satisfies + * - Default construction: T{} + * - Comparison : T a, b; bool res = a < b + * - Addition: T a, b; T c = a + b; + * - Multiplication : T a, std::size_t b; T c = a * b; + * - Division: T a; std::size_t b; T c = a/b; + */ template > class Histogram { @@ -28,7 +27,9 @@ class Histogram std::size_t samples_ = 0; public: - /** Insert an sample */ + /** + * Insert an sample + */ void insert(T const& s) { @@ -36,35 +37,45 @@ public: ++samples_; } - /** The number of samples */ + /** + * The number of samples + */ [[nodiscard]] std::size_t size() const { return samples_; } - /** The number of distinct samples (bins) */ + /** + * The number of distinct samples (bins) + */ [[nodiscard]] std::size_t numBins() const { return counts_.size(); } - /** Minimum observed value */ + /** + * Minimum observed value + */ [[nodiscard]] T minValue() const { return counts_.empty() ? T{} : counts_.begin()->first; } - /** Maximum observed value */ + /** + * Maximum observed value + */ [[nodiscard]] T maxValue() const { return counts_.empty() ? T{} : counts_.rbegin()->first; } - /** Histogram average */ + /** + * Histogram average + */ [[nodiscard]] T avg() const { @@ -80,12 +91,13 @@ public: return tmp / samples_; } - /** Calculate the given percentile of the distribution. - - @param p Percentile between 0 and 1, e.g. 0.50 is 50-th percentile - If the percentile falls between two bins, uses the nearest bin. - @return The given percentile of the distribution - */ + /** + * Calculate the given percentile of the distribution. + * + * @param p Percentile between 0 and 1, e.g. 0.50 is 50-th percentile + * If the percentile falls between two bins, uses the nearest bin. + * @return The given percentile of the distribution + */ [[nodiscard]] T percentile(float p) const { diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 9d29704172..79bffec9cb 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -42,24 +42,26 @@ namespace xrpl::test::csf { namespace bc = boost::container; -/** A single peer in the simulation. - - This is the main work-horse of the consensus simulation framework and is - where many other components are integrated. The peer - - - Implements the Callbacks required by Consensus - - Manages trust & network connections with other peers - - Issues events back to the simulation based on its actions for analysis - by Collectors - - Exposes most internal state for forcibly simulating arbitrary scenarios -*/ +/** + * A single peer in the simulation. + * + * This is the main work-horse of the consensus simulation framework and is + * where many other components are integrated. The peer + * + * - Implements the Callbacks required by Consensus + * - Manages trust & network connections with other peers + * - Issues events back to the simulation based on its actions for analysis + * by Collectors + * - Exposes most internal state for forcibly simulating arbitrary scenarios + */ struct Peer { - /** Basic wrapper of a proposed position taken by a peer. - - For real consensus, this would add additional data for serialization - and signing. For simulation, nothing extra is needed. - */ + /** + * Basic wrapper of a proposed position taken by a peer. + * + * For real consensus, this would add additional data for serialization + * and signing. For simulation, nothing extra is needed. + */ class Position { public: @@ -89,16 +91,21 @@ struct Peer Proposal proposal_; }; - /** Simulated delays in internal peer processing. + /** + * Simulated delays in internal peer processing. */ struct ProcessingDelays { - //! Delay in consensus calling doAccept to accepting and issuing - //! validation - //! TODO: This should be a function of the number of transactions + /** + * Delay in consensus calling doAccept to accepting and issuing + * validation + * TODO: This should be a function of the number of transactions + */ std::chrono::milliseconds ledgerAccept{0}; - //! Delay in processing validations from remote peers + /** + * Delay in processing validations from remote peers + */ std::chrono::milliseconds recvValidation{0}; // Return the receive delay for message type M, default is no delay @@ -122,7 +129,8 @@ struct Peer { }; - /** Generic Validations adaptor that simply ignores recently stale + /** + * Generic Validations adaptor that simply ignores recently stale * validations */ class ValAdaptor @@ -165,7 +173,9 @@ struct Peer } }; - //! Type definitions for generic consensus + /** + * Type definitions for generic consensus + */ using Ledger_t = Ledger; using NodeID_t = PeerID; using NodeKey_t = PeerKey; @@ -174,74 +184,114 @@ struct Peer using Result = ConsensusResult; using NodeKey = Validation::NodeKey; - //! Logging support that prefixes messages with the peer ID + /** + * Logging support that prefixes messages with the peer ID + */ beast::WrappedSink sink; beast::Journal j; - //! Generic consensus + /** + * Generic consensus + */ Consensus consensus; - //! Our unique ID + /** + * Our unique ID + */ PeerID id; - //! Current signing key + /** + * Current signing key + */ PeerKey key; - //! The oracle that manages unique ledgers + /** + * The oracle that manages unique ledgers + */ LedgerOracle& oracle; - //! Scheduler of events + /** + * Scheduler of events + */ Scheduler& scheduler; - //! Handle to network for sending messages + /** + * Handle to network for sending messages + */ BasicNetwork& net; - //! Handle to Trust graph of network + /** + * Handle to Trust graph of network + */ TrustGraph& trustGraph; - //! openTxs that haven't been closed in a ledger yet + /** + * openTxs that haven't been closed in a ledger yet + */ TxSetType openTxs; - //! The last ledger closed by this node + /** + * The last ledger closed by this node + */ Ledger lastClosedLedger; - //! Ledgers this node has closed or loaded from the network + /** + * Ledgers this node has closed or loaded from the network + */ hash_map ledgers; - //! Validations from trusted nodes + /** + * Validations from trusted nodes + */ Validations validations; - //! The most recent ledger that has been fully validated by the network from - //! the perspective of this Peer + /** + * The most recent ledger that has been fully validated by the network from + * the perspective of this Peer + */ Ledger fullyValidatedLedger; //------------------------------------------------------------------------- // Store most network messages; these could be purged if memory use ever // becomes problematic - //! Map from Ledger::ID to vector of Positions with that ledger - //! as the prior ledger + /** + * Map from Ledger::ID to vector of Positions with that ledger + * as the prior ledger + */ bc::flat_map> peerPositions; - //! TxSet associated with a TxSet::ID + /** + * TxSet associated with a TxSet::ID + */ bc::flat_map txSets; // Ledgers/TxSets we are acquiring and when that request times out bc::flat_map acquiringLedgers; bc::flat_map acquiringTxSets; - //! The number of ledgers this peer has completed + /** + * The number of ledgers this peer has completed + */ int completedLedgers = 0; - //! The number of ledgers this peer should complete before stopping to run + /** + * The number of ledgers this peer should complete before stopping to run + */ int targetLedgers = std::numeric_limits::max(); - //! Skew of time relative to the common scheduler clock + /** + * Skew of time relative to the common scheduler clock + */ std::chrono::seconds clockSkew{0}; - //! Simulated delays to use for internal processing + /** + * Simulated delays to use for internal processing + */ ProcessingDelays delays; - //! Whether to simulate running as validator or a tracking node + /** + * Whether to simulate running as validator or a tracking node + */ bool runAsValidator = true; // TODO: Consider removing these two, they are only a convenience for tests @@ -259,20 +309,22 @@ struct Peer // Simulation parameters ConsensusParms consensusParms; - //! The collectors to report events to + /** + * The collectors to report events to + */ CollectorRefs& collectors; - /** Constructor - - @param i Unique PeerID - @param s Simulation Scheduler - @param o Simulation Oracle - @param n Simulation network - @param tg Simulation trust graph - @param c Simulation collectors - @param jIn Simulation journal - - */ + /** + * Constructor + * + * @param i Unique PeerID + * @param s Simulation Scheduler + * @param o Simulation Oracle + * @param n Simulation network + * @param tg Simulation trust graph + * @param c Simulation collectors + * @param jIn Simulation journal + */ Peer( PeerID i, Scheduler& s, @@ -302,9 +354,10 @@ struct Peer trustGraph.trust(this, this); } - /** Schedule the provided callback in `when` duration, but if - `when` is 0, call immediately - */ + /** + * Schedule the provided callback in `when` duration, but if + * `when` is 0, call immediately + */ template void schedule(std::chrono::nanoseconds when, T&& what) @@ -360,22 +413,19 @@ struct Peer bool trusts(PeerID const& oId) { - for (auto const p : trustGraph.trustedPeers(this)) - { - if (p->id == oId) - return true; - } - return false; + return std::ranges::any_of( + trustGraph.trustedPeers(this), [&oId](auto const p) { return p->id == oId; }); } - /** Create network connection - - Creates a new outbound connection to another Peer if none exists - - @param o The peer with the inbound connection - @param dur The fixed delay for messages between the two Peers - @return Whether the connection was created. - */ + /** + * Create network connection + * + * Creates a new outbound connection to another Peer if none exists + * + * @param o The peer with the inbound connection + * @param dur The fixed delay for messages between the two Peers + * @return Whether the connection was created. + */ bool connect(Peer& o, SimDuration dur) @@ -383,13 +433,14 @@ struct Peer return net.connect(this, &o, dur); } - /** Remove a network connection - - Removes a connection between peers if one exists - - @param o The peer we disconnect from - @return Whether the connection was removed - */ + /** + * Remove a network connection + * + * Removes a connection between peers if one exists + * + * @param o The peer we disconnect from + * @return Whether the connection was removed + */ bool disconnect(Peer& o) { @@ -653,7 +704,9 @@ struct Peer //-------------------------------------------------------------------------- // Validation members - /** Add a trusted validation and return true if it is worth forwarding */ + /** + * Add a trusted validation and return true if it is worth forwarding + */ bool addTrustedValidation(Validation v) { @@ -670,7 +723,9 @@ struct Peer return true; } - /** Check if a new ledger can be deemed fully validated */ + /** + * Check if a new ledger can be deemed fully validated + */ void checkFullyValidated(Ledger const& ledger) { @@ -872,7 +927,9 @@ struct Peer //-------------------------------------------------------------------------- // Simulation "driver" members - //! Heartbeat timer call + /** + * Heartbeat timer call + */ void timerEntry() { @@ -939,16 +996,17 @@ struct Peer // TODO: Make this more robust hash_map txInjections; - /** Inject non-consensus Tx - - Injects a transactionsinto the ledger following prevLedger's sequence - number. - - @param prevLedger The ledger we are building the new ledger on top of - @param src The Consensus TxSet - @return Consensus TxSet with inject transactions added if prevLedger.seq - matches a previously registered Tx. - */ + /** + * Inject non-consensus Tx + * + * Injects a transactionsinto the ledger following prevLedger's sequence + * number. + * + * @param prevLedger The ledger we are building the new ledger on top of + * @param src The Consensus TxSet + * @return Consensus TxSet with inject transactions added if prevLedger.seq + * matches a previously registered Tx. + */ TxSet injectTxs(Ledger prevLedger, TxSet const& src) { diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index e5efecac34..1c31209ef3 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -17,14 +17,15 @@ namespace xrpl::test::csf { -/** A group of simulation Peers - - A PeerGroup is a convenient handle for logically grouping peers together, - and then creating trust or network relations for the group at large. Peer - groups may also be combined to build out more complex structures. - - The PeerGroup provides random access style iterators and operator[] -*/ +/** + * A group of simulation Peers + * + * A PeerGroup is a convenient handle for logically grouping peers together, + * and then creating trust or network relations for the group at large. Peer + * groups may also be combined to build out more complex structures. + * + * The PeerGroup provides random access style iterators and operator[] + */ class PeerGroup { using peers_type = std::vector; @@ -102,12 +103,13 @@ public: return peers_.size(); } - /** Establish trust - - Establish trust from all peers in this group to all peers in o - - @param o The group of peers to trust - */ + /** + * Establish trust + * + * Establish trust from all peers in this group to all peers in o + * + * @param o The group of peers to trust + */ void trust(PeerGroup const& o) { @@ -120,12 +122,13 @@ public: } } - /** Revoke trust - - Revoke trust from all peers in this group to all peers in o - - @param o The group of peers to untrust - */ + /** + * Revoke trust + * + * Revoke trust from all peers in this group to all peers in o + * + * @param o The group of peers to untrust + */ void untrust(PeerGroup const& o) { @@ -138,16 +141,15 @@ public: } } - /** Establish network connection - - Establish outbound connections from all peers in this group to all peers - in o. If a connection already exists, no new connection is established. - - @param o The group of peers to connect to (will get inbound connections) - @param delay The fixed messaging delay for all established connections - - - */ + /** + * Establish network connection + * + * Establish outbound connections from all peers in this group to all peers + * in o. If a connection already exists, no new connection is established. + * + * @param o The group of peers to connect to (will get inbound connections) + * @param delay The fixed messaging delay for all established connections + */ void connect(PeerGroup const& o, SimDuration delay) { @@ -162,12 +164,13 @@ public: } } - /** Destroy network connection - - Destroy connections from all peers in this group to all peers in o - - @param o The group of peers to disconnect from - */ + /** + * Destroy network connection + * + * Destroy connections from all peers in this group to all peers in o + * + * @param o The group of peers to disconnect from + */ void disconnect(PeerGroup const& o) { @@ -180,14 +183,15 @@ public: } } - /** Establish trust and network connection - - Establish trust and create a network connection with fixed delay - from all peers in this group to all peers in o - - @param o The group of peers to trust and connect to - @param delay The fixed messaging delay for all established connections - */ + /** + * Establish trust and network connection + * + * Establish trust and create a network connection with fixed delay + * from all peers in this group to all peers in o + * + * @param o The group of peers to trust and connect to + * @param delay The fixed messaging delay for all established connections + */ void trustAndConnect(PeerGroup const& o, SimDuration delay) { @@ -195,15 +199,15 @@ public: connect(o, delay); } - /** Establish network connections based on trust relations - - For each peers in this group, create outbound network connection - to the set of peers it trusts. If a connection already exists, it is - not recreated. - - @param delay The fixed messaging delay for all established connections - - */ + /** + * Establish network connections based on trust relations + * + * For each peers in this group, create outbound network connection + * to the set of peers it trusts. If a connection already exists, it is + * not recreated. + * + * @param delay The fixed messaging delay for all established connections + */ void connectFromTrust(SimDuration delay) { @@ -253,25 +257,25 @@ public: } }; -/** Randomly generate peer groups according to ranks. - - Generates random peer groups based on a provided ranking of peers. This - mimics a process of randomly generating UNLs, where more "important" peers - are more likely to appear in a UNL. - - `numGroups` subgroups are generated by randomly sampling without without - replacement from peers according to the `ranks`. - - - - @param peers The group of peers - @param ranks The relative importance of each peer, must match the size of - peers. Higher relative rank means more likely to be sampled. - @param numGroups The number of peer link groups to generate - @param sizeDist The distribution that determines the size of a link group - @param g The uniform random bit generator - -*/ +/** + * Randomly generate peer groups according to ranks. + * + * Generates random peer groups based on a provided ranking of peers. This + * mimics a process of randomly generating UNLs, where more "important" peers + * are more likely to appear in a UNL. + * + * `numGroups` subgroups are generated by randomly sampling without without + * replacement from peers according to the `ranks`. + * + * + * + * @param peers The group of peers + * @param ranks The relative importance of each peer, must match the size of + * peers. Higher relative rank means more likely to be sampled. + * @param numGroups The number of peer link groups to generate + * @param sizeDist The distribution that determines the size of a link group + * @param g The uniform random bit generator + */ template std::vector randomRankedGroups( @@ -295,10 +299,11 @@ randomRankedGroups( return groups; } -/** Generate random trust groups based on peer rankings. - - @see randomRankedGroups for descriptions of the arguments -*/ +/** + * Generate random trust groups based on peer rankings. + * + * @see randomRankedGroups for descriptions of the arguments + */ template void randomRankedTrust( @@ -318,10 +323,11 @@ randomRankedTrust( } } -/** Generate random network groups based on peer rankings. - - @see randomRankedGroups for descriptions of the arguments -*/ +/** + * Generate random network groups based on peer rankings. + * + * @see randomRankedGroups for descriptions of the arguments + */ template void randomRankedConnect( diff --git a/src/test/csf/Proposal.h b/src/test/csf/Proposal.h index 06486daed3..ecf430ae8d 100644 --- a/src/test/csf/Proposal.h +++ b/src/test/csf/Proposal.h @@ -7,9 +7,10 @@ #include namespace xrpl::test::csf { -/** Proposal is a position taken in the consensus process and is represented - directly from the generic types. -*/ +/** + * Proposal is a position taken in the consensus process and is represented + * directly from the generic types. + */ using Proposal = ConsensusProposal; } // namespace xrpl::test::csf diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index 3fc187e1e7..b1ac2bb5d4 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -12,17 +12,18 @@ namespace xrpl::test::csf { -/** Simulated discrete-event scheduler. - - Simulates the behavior of events using a single common clock. - - An event is modeled using a lambda function and is scheduled to occur at a - specific time. Events may be canceled using a token returned when the - event is scheduled. - - The caller uses one or more of the step, stepOne, stepFor, stepUntil and - stepWhile functions to process scheduled events. -*/ +/** + * Simulated discrete-event scheduler. + * + * Simulates the behavior of events using a single common clock. + * + * An event is modeled using a lambda function and is scheduled to occur at a + * specific time. Events may be canceled using a token returned when the + * event is scheduled. + * + * The caller uses one or more of the step, stepOne, stepFor, stepUntil and + * stepWhile functions to process scheduled events. + */ class Scheduler { public: @@ -135,116 +136,127 @@ public: Scheduler(); - /** Return the clock. (aged_containers want a non-const ref =( */ + /** + * Return the clock. (aged_containers want a non-const ref =( + */ clock_type& clock() const; - /** Return the current network time. - - @note The epoch is unspecified - */ + /** + * Return the current network time. + * + * @note The epoch is unspecified + */ time_point now() const; // Used to cancel timers struct CancelToken; - /** Schedule an event at a specific time - - Effects: - - When the network time is reached, - the function will be called with - no arguments. - */ + /** + * Schedule an event at a specific time + * + * Effects: + * + * When the network time is reached, + * the function will be called with + * no arguments. + */ template CancelToken at(time_point const& when, Function&& f); - /** Schedule an event after a specified duration passes - - Effects: - - When the specified time has elapsed, - the function will be called with - no arguments. - */ + /** + * Schedule an event after a specified duration passes + * + * Effects: + * + * When the specified time has elapsed, + * the function will be called with + * no arguments. + */ template CancelToken in(duration const& delay, Function&& f); - /** Cancel a timer. - - Preconditions: - - `token` was the return value of a call - timer() which has not yet been invoked. - */ + /** + * Cancel a timer. + * + * Preconditions: + * + * `token` was the return value of a call + * timer() which has not yet been invoked. + */ void cancel(CancelToken const& token); - /** Run the scheduler for up to one event. - - Effects: - - The clock is advanced to the time - of the last delivered event. - - @return `true` if an event was processed. - */ + /** + * Run the scheduler for up to one event. + * + * Effects: + * + * The clock is advanced to the time + * of the last delivered event. + * + * @return `true` if an event was processed. + */ bool stepOne(); - /** Run the scheduler until no events remain. - - Effects: - - The clock is advanced to the time - of the last event. - - @return `true` if an event was processed. - */ + /** + * Run the scheduler until no events remain. + * + * Effects: + * + * The clock is advanced to the time + * of the last event. + * + * @return `true` if an event was processed. + */ bool step(); - /** Run the scheduler while a condition is true. - - Function takes no arguments and will be called - repeatedly after each event is processed to - decide whether to continue. - - Effects: - - The clock is advanced to the time - of the last delivered event. - - @return `true` if any event was processed. - */ + /** + * Run the scheduler while a condition is true. + * + * Function takes no arguments and will be called + * repeatedly after each event is processed to + * decide whether to continue. + * + * Effects: + * + * The clock is advanced to the time + * of the last delivered event. + * + * @return `true` if any event was processed. + */ template bool stepWhile(Function&& func); - /** Run the scheduler until the specified time. - - Effects: - - The clock is advanced to the - specified time. - - @return `true` if any event remain. - */ + /** + * Run the scheduler until the specified time. + * + * Effects: + * + * The clock is advanced to the + * specified time. + * + * @return `true` if any event remain. + */ bool stepUntil(time_point const& until); - /** Run the scheduler until time has elapsed. - - Effects: - - The clock is advanced by the - specified duration. - - @return `true` if any event remain. - */ + /** + * Run the scheduler until time has elapsed. + * + * Effects: + * + * The clock is advanced by the + * specified duration. + * + * @return `true` if any event remain. + */ template bool stepFor(std::chrono::duration const& amount); diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index d2a63f6507..94d26d5e06 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -21,7 +21,9 @@ namespace xrpl::test::csf { -/** Sink that prepends simulation time to messages */ +/** + * Sink that prepends simulation time to messages + */ class BasicSink : public beast::Journal::Sink { Scheduler::clock_type const& clock_; @@ -65,28 +67,29 @@ public: TrustGraph trustGraph; CollectorRefs collectors; - /** Create a simulation - - Creates a new simulation. The simulation has no peers, no trust links - and no network connections. - - */ + /** + * Create a simulation + * + * Creates a new simulation. The simulation has no peers, no trust links + * and no network connections. + */ // NOLINTNEXTLINE(bugprone-random-generator-seed): fixed seed for reproducible test Sim() : sink{scheduler.clock()}, j{sink}, net{scheduler} { } - /** Create a new group of peers. - - Creates a new group of peers. The peers do not have any trust relations - or network connections by default. Those must be configured by the - client. - - @param numPeers The number of peers in the group - @return PeerGroup representing these new peers - - @note This increases the number of peers in the simulation by numPeers. - */ + /** + * Create a new group of peers. + * + * Creates a new group of peers. The peers do not have any trust relations + * or network connections by default. Those must be configured by the + * client. + * + * @param numPeers The number of peers in the group + * @return PeerGroup representing these new peers + * + * @note This increases the number of peers in the simulation by numPeers. + */ PeerGroup createGroup(std::size_t numPeers) { @@ -109,48 +112,57 @@ public: return res; } - //! The number of peers in the simulation + /** + * The number of peers in the simulation + */ std::size_t size() const { return peers_.size(); } - /** Run consensus protocol to generate the provided number of ledgers. - - Has each peer run consensus until it closes `ledgers` more ledgers. - - @param ledgers The number of additional ledgers to close - */ + /** + * Run consensus protocol to generate the provided number of ledgers. + * + * Has each peer run consensus until it closes `ledgers` more ledgers. + * + * @param ledgers The number of additional ledgers to close + */ void run(int ledgers); - /** Run consensus for the given duration */ + /** + * Run consensus for the given duration + */ void run(SimDuration const& dur); - /** Check whether all peers in the group are synchronized. - - Nodes in the group are synchronized if they share the same last - fully validated and last generated ledger. - */ + /** + * Check whether all peers in the group are synchronized. + * + * Nodes in the group are synchronized if they share the same last + * fully validated and last generated ledger. + */ static bool synchronized(PeerGroup const& g); - /** Check whether all peers in the network are synchronized + /** + * Check whether all peers in the network are synchronized */ bool synchronized() const; - /** Calculate the number of branches in the group. - - A branch occurs if two nodes in the group have fullyValidatedLedgers - that are not on the same chain of ledgers. - */ + /** + * Calculate the number of branches in the group. + * + * A branch occurs if two nodes in the group have fullyValidatedLedgers + * that are not on the same chain of ledgers. + */ std::size_t branches(PeerGroup const& g) const; - /** Calculate the number of branches in the network + /** + * Calculate the number of branches in the network */ std::size_t branches() const; diff --git a/src/test/csf/TrustGraph.h b/src/test/csf/TrustGraph.h index a10e318706..d46a887364 100644 --- a/src/test/csf/TrustGraph.h +++ b/src/test/csf/TrustGraph.h @@ -10,13 +10,14 @@ namespace xrpl::test::csf { -/** Trust graph - - Trust is a directed relationship from a node i to node j. - If node i trusts node j, then node i has node j in its UNL. - This class wraps a digraph representing the trust relationships for all - peers in the simulation. -*/ +/** + * Trust graph + * + * Trust is a directed relationship from a node i to node j. + * If node i trusts node j, then node i has node j in its UNL. + * This class wraps a digraph representing the trust relationships for all + * peers in the simulation. + */ template class TrustGraph { @@ -25,7 +26,8 @@ class TrustGraph Graph graph_; public: - /** Create an empty trust graph + /** + * Create an empty trust graph */ TrustGraph() = default; @@ -35,29 +37,30 @@ public: return graph_; } - /** Create trust - - Establish trust between Peer `from` and Peer `to`; as if `from` put `to` - in its UNL. - - @param from The peer granting trust - @param to The peer receiving trust - - */ + /** + * Create trust + * + * Establish trust between Peer `from` and Peer `to`; as if `from` put `to` + * in its UNL. + * + * @param from The peer granting trust + * @param to The peer receiving trust + */ void trust(Peer const& from, Peer const& to) { graph_.connect(from, to); } - /** Remove trust - - Revoke trust from Peer `from` to Peer `to`; as if `from` removed `to` - from its UNL. - - @param from The peer revoking trust - @param to The peer being revoked - */ + /** + * Remove trust + * + * Revoke trust from Peer `from` to Peer `to`; as if `from` removed `to` + * from its UNL. + * + * @param from The peer revoking trust + * @param to The peer being revoked + */ void untrust(Peer const& from, Peer const& to) { @@ -71,19 +74,21 @@ public: return graph_.connected(from, to); } - /** Range over trusted peers - - @param a The node granting trust - @return boost transformed range over nodes `a` trusts, i.e. the nodes - in its UNL - */ + /** + * Range over trusted peers + * + * @param a The node granting trust + * @return boost transformed range over nodes `a` trusts, i.e. the nodes + * in its UNL + */ [[nodiscard]] auto trustedPeers(Peer const& a) const { return graph_.outVertices(a); } - /** An example of nodes that fail the whitepaper no-forking condition + /** + * An example of nodes that fail the whitepaper no-forking condition */ struct ForkInfo { @@ -133,9 +138,10 @@ public: return res; } - /** Check whether this trust graph satisfies the whitepaper no-forking - condition - */ + /** + * Check whether this trust graph satisfies the whitepaper no-forking + * condition + */ [[nodiscard]] bool canFork(double quorum) const { diff --git a/src/test/csf/Tx.h b/src/test/csf/Tx.h index 75254f865a..0412d346f3 100644 --- a/src/test/csf/Tx.h +++ b/src/test/csf/Tx.h @@ -17,7 +17,9 @@ namespace xrpl::test::csf { -//! A single transaction +/** + * A single transaction + */ class Tx { public: @@ -56,11 +58,15 @@ private: ID id_; }; -//!------------------------------------------------------------------------- -//! All sets of Tx are represented as a flat_set for performance. +/** + * ------------------------------------------------------------------------- + * All sets of Tx are represented as a flat_set for performance. + */ using TxSetType = boost::container::flat_set; -//! TxSet is a set of transactions to consider including in the ledger +/** + * TxSet is a set of transactions to consider including in the ledger + */ class TxSet { public: @@ -135,10 +141,11 @@ public: return id_; } - /** @return Map of Tx::ID that are missing. True means - it was in this set and not other. False means - it was in the other set and not this - */ + /** + * @return Map of Tx::ID that are missing. True means + * it was in this set and not other. False means + * it was in the other set and not this + */ [[nodiscard]] std::map compare(TxSet const& other) const { @@ -160,10 +167,14 @@ public: } private: - //! The set contains the actual transactions + /** + * The set contains the actual transactions + */ TxSetType txs_; - //! The unique ID of this tx set + /** + * The unique ID of this tx set + */ ID id_{}; }; diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 73b6784534..0b9fc94890 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -16,15 +16,17 @@ struct PeerIDTag; //< Uniquely identifies a peer using PeerID = TaggedInteger; -/** The current key of a peer - - Eventually, the second entry in the pair can be used to model ephemeral - keys. Right now, the convention is to have the second entry 0 as the - master key. -*/ +/** + * The current key of a peer + * + * Eventually, the second entry in the pair can be used to model ephemeral + * keys. Right now, the convention is to have the second entry 0 as the + * master key. + */ using PeerKey = std::pair; -/** Validation of a specific ledger by a specific Peer. +/** + * Validation of a specific ledger by a specific Peer. */ class Validation { diff --git a/src/test/csf/collectors.h b/src/test/csf/collectors.h index 1100f6c690..f85854e5dd 100644 --- a/src/test/csf/collectors.h +++ b/src/test/csf/collectors.h @@ -32,12 +32,13 @@ namespace xrpl::test::csf { // This file contains helper functions for composing different collectors // and also defines several standard collectors available for simulations. -/** Group of collectors. - - Presents a group of collectors as a single collector which process an event - by calling each collector sequentially. This is analogous to CollectorRefs - in CollectorRef.h, but does *not* erase the type information of the combined - collectors. +/** + * Group of collectors. + * + * Presents a group of collectors as a single collector which process an event + * by calling each collector sequentially. This is analogous to CollectorRefs + * in CollectorRef.h, but does *not* erase the type information of the combined + * collectors. */ template class Collectors @@ -59,10 +60,11 @@ class Collectors } public: - /** Constructor - - @param cs References to the collectors to call together - */ + /** + * Constructor + * + * @param cs References to the collectors to call together + */ Collectors(Cs&... cs) : cs_(std::tie(cs...)) { } @@ -75,7 +77,9 @@ public: } }; -/** Create an instance of Collectors */ +/** + * Create an instance of Collectors + */ template Collectors makeCollectors(Cs&... cs) @@ -83,14 +87,15 @@ makeCollectors(Cs&... cs) return Collectors(cs...); } -/** Maintain an instance of a Collector per peer - - For each peer that emits events, this class maintains a corresponding - instance of CollectorType, only forwarding events emitted by the peer to - the related instance. - - CollectorType should be default constructible. -*/ +/** + * Maintain an instance of a Collector per peer + * + * For each peer that emits events, this class maintains a corresponding + * instance of CollectorType, only forwarding events emitted by the peer to + * the related instance. + * + * CollectorType should be default constructible. + */ template struct CollectByNode { @@ -115,7 +120,9 @@ struct CollectByNode } }; -/** Collector which ignores all events */ +/** + * Collector which ignores all events + */ struct NullCollector { template @@ -125,7 +132,9 @@ struct NullCollector } }; -/** Tracks the overall duration of a simulation */ +/** + * Tracks the overall duration of a simulation + */ struct SimDurationCollector { bool init = false; @@ -148,15 +157,16 @@ struct SimDurationCollector } }; -/** Tracks the submission -> accepted -> validated evolution of transactions. - - This collector tracks transactions through the network by monitoring the - *first* time the transaction is seen by any node in the network, or - seen by any node's accepted or fully validated ledger. - - If transactions submitted to the network do not have unique IDs, this - collector will not track subsequent submissions. -*/ +/** + * Tracks the submission -> accepted -> validated evolution of transactions. + * + * This collector tracks transactions through the network by monitoring the + * *first* time the transaction is seen by any node in the network, or + * seen by any node's accepted or fully validated ledger. + * + * If transactions submitted to the network do not have unique IDs, this + * collector will not track subsequent submissions. + */ struct TxCollector { // Counts @@ -381,12 +391,12 @@ struct TxCollector } }; -/** Tracks the accepted -> validated evolution of ledgers. - - This collector tracks ledgers through the network by monitoring the - *first* time the ledger is accepted or fully validated by ANY node. - -*/ +/** + * Tracks the accepted -> validated evolution of ledgers. + * + * This collector tracks ledgers through the network by monitoring the + * *first* time the ledger is accepted or fully validated by ANY node. + */ struct LedgerCollector { std::size_t accepted{0}; @@ -580,11 +590,12 @@ struct LedgerCollector } }; -/** Write out stream of ledger activity - - Writes information about every accepted and fully-validated ledger to a - provided std::ostream. -*/ +/** + * Write out stream of ledger activity + * + * Writes information about every accepted and fully-validated ledger to a + * provided std::ostream. + */ struct StreamCollector { std::ostream& out; @@ -611,11 +622,12 @@ struct StreamCollector } }; -/** Saves information about Jumps for closed and fully validated ledgers. A - jump occurs when a node closes/fully validates a new ledger that is not the - immediate child of the prior closed/fully validated ledgers. This includes - jumps across branches and jumps ahead in the same branch of ledger history. -*/ +/** + * Saves information about Jumps for closed and fully validated ledgers. A + * jump occurs when a node closes/fully validates a new ledger that is not the + * immediate child of the prior closed/fully validated ledgers. This includes + * jumps across branches and jumps ahead in the same branch of ledger history. + */ struct JumpCollector { struct Jump diff --git a/src/test/csf/events.h b/src/test/csf/events.h index dfedd2627a..2cf4fd9e9b 100644 --- a/src/test/csf/events.h +++ b/src/test/csf/events.h @@ -29,58 +29,81 @@ namespace xrpl::test::csf { // CollectorRef.f defines a type-erased holder for arbitrary Collectors. If // any new events are added, the interface there needs to be updated. -/** A value to be flooded to all other peers starting from this peer. +/** + * A value to be flooded to all other peers starting from this peer. */ template struct Share { - //! Event that is shared + /** + * Event that is shared + */ V val; }; -/** A value relayed to another peer as part of flooding +/** + * A value relayed to another peer as part of flooding */ template struct Relay { - //! Peer relaying to + /** + * Peer relaying to + */ PeerID to; - //! The value to relay + /** + * The value to relay + */ V val; }; -/** A value received from another peer as part of flooding +/** + * A value received from another peer as part of flooding */ template struct Receive { - //! Peer that sent the value + /** + * Peer that sent the value + */ PeerID from; - //! The received value + /** + * The received value + */ V val; }; -/** A transaction submitted to a peer */ +/** + * A transaction submitted to a peer + */ struct SubmitTx { - //! The submitted transaction + /** + * The submitted transaction + */ Tx tx; }; -/** Peer starts a new consensus round +/** + * Peer starts a new consensus round */ struct StartRound { - //! The preferred ledger for the start of consensus + /** + * The preferred ledger for the start of consensus + */ Ledger::ID bestLedger{}; - //! The prior ledger on hand + /** + * The prior ledger on hand + */ Ledger prevLedger; }; -/** Peer closed the open ledger +/** + * Peer closed the open ledger */ struct CloseLedger { @@ -91,7 +114,9 @@ struct CloseLedger TxSetType txs; }; -//! Peer accepted consensus results +/** + * Peer accepted consensus results + */ struct AcceptLedger { // The newly created ledger @@ -101,7 +126,9 @@ struct AcceptLedger Ledger prior; }; -//! Peer detected a wrong prior ledger during consensus +/** + * Peer detected a wrong prior ledger during consensus + */ struct WrongPrevLedger { // ID of wrong ledger we had @@ -110,14 +137,20 @@ struct WrongPrevLedger Ledger::ID right; }; -//! Peer fully validated a new ledger +/** + * Peer fully validated a new ledger + */ struct FullyValidateLedger { - //! The new fully validated ledger + /** + * The new fully validated ledger + */ Ledger ledger; - //! The prior fully validated ledger - //! This is a jump if prior.id() != ledger.parentID() + /** + * The prior fully validated ledger + * This is a jump if prior.id() != ledger.parentID() + */ Ledger prior; }; diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 4873519dee..09f5fa54de 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -22,27 +22,28 @@ namespace xrpl::test::csf { -/** A ledger is a set of observed transactions and a sequence number - identifying the ledger. - - Peers in the consensus process are trying to agree on a set of transactions - to include in a ledger. For simulation, each transaction is a single - integer and the ledger is the set of observed integers. This means future - ledgers have prior ledgers as subsets, e.g. - - Ledger 0 : {} - Ledger 1 : {1,4,5} - Ledger 2 : {1,2,4,5,10} - .... - - Ledgers are immutable value types. All ledgers with the same sequence - number, transactions, close time, etc. will have the same ledger ID. The - LedgerOracle class below manages ID assignments for a simulation and is the - only way to close and create a new ledger. Since the parent ledger ID is - part of type, this also means ledgers with distinct histories will have - distinct ids, even if they have the same set of transactions, sequence - number and close time. -*/ +/** + * A ledger is a set of observed transactions and a sequence number + * identifying the ledger. + * + * Peers in the consensus process are trying to agree on a set of transactions + * to include in a ledger. For simulation, each transaction is a single + * integer and the ledger is the set of observed integers. This means future + * ledgers have prior ledgers as subsets, e.g. + * + * Ledger 0 : {} + * Ledger 1 : {1,4,5} + * Ledger 2 : {1,2,4,5,10} + * .... + * + * Ledgers are immutable value types. All ledgers with the same sequence + * number, transactions, close time, etc. will have the same ledger ID. The + * LedgerOracle class below manages ID assignments for a simulation and is the + * only way to close and create a new ledger. Since the parent ledger ID is + * part of type, this also means ledgers with distinct histories will have + * distinct ids, even if they have the same set of transactions, sequence + * number and close time. + */ class Ledger { friend class LedgerOracle; @@ -74,21 +75,31 @@ private: // Resolution used to determine close time NetClock::duration closeTimeResolution = kLedgerDefaultTimeResolution; - //! When the ledger closed (up to closeTimeResolution) + /** + * When the ledger closed (up to closeTimeResolution) + */ NetClock::time_point closeTime; - //! Whether consensus agreed on the close time + /** + * Whether consensus agreed on the close time + */ bool closeTimeAgree = true; - //! Parent ledger id + /** + * Parent ledger id + */ ID parentID{0}; - //! Parent ledger close time + /** + * Parent ledger close time + */ NetClock::time_point parentCloseTime; - //! IDs of this ledgers ancestors. Since each ledger already has unique - //! ancestors based on the parentID, this member is not needed for any - //! of the operators below. + /** + * IDs of this ledgers ancestors. Since each ledger already has unique + * ancestors based on the parentID, this member is not needed for any + * of the operators below. + */ std::vector ancestors; [[nodiscard]] auto @@ -199,16 +210,20 @@ public: return instance_->txs; } - /** Determine whether ancestor is really an ancestor of this ledger */ + /** + * Determine whether ancestor is really an ancestor of this ledger + */ [[nodiscard]] bool isAncestor(Ledger const& ancestor) const; - /** Return the id of the ancestor with the given seq (if exists/known) + /** + * Return the id of the ancestor with the given seq (if exists/known) */ ID operator[](Seq seq) const; - /** Return the sequence number of the first mismatching ancestor + /** + * Return the sequence number of the first mismatching ancestor */ friend Ledger::Seq mismatch(Ledger const& a, Ledger const& o); @@ -227,7 +242,8 @@ private: Instance const* instance_; }; -/** Oracle maintaining unique ledgers for a simulation. +/** + * Oracle maintaining unique ledgers for a simulation. */ class LedgerOracle { @@ -246,18 +262,21 @@ class LedgerOracle public: LedgerOracle(); - /** Find the ledger with the given ID */ + /** + * Find the ledger with the given ID + */ [[nodiscard]] std::optional lookup(Ledger::ID const& id) const; - /** Accept the given txs and generate a new ledger - - @param curr The current ledger - @param txs The transactions to apply to the current ledger - @param closeTimeResolution Resolution used in determining close time - @param consensusCloseTime The consensus agreed close time, no valid time - if 0 - */ + /** + * Accept the given txs and generate a new ledger + * + * @param curr The current ledger + * @param txs The transactions to apply to the current ledger + * @param closeTimeResolution Resolution used in determining close time + * @param consensusCloseTime The consensus agreed close time, no valid time + * if 0 + */ Ledger accept( Ledger const& curr, @@ -272,38 +291,39 @@ public: return accept(curr, TxSetType{tx}, curr.closeTimeResolution(), curr.closeTime() + 1s); } - /** Determine the number of distinct branches for the set of ledgers. - - Ledgers A and B are on different branches if A != B, A is not an - ancestor of B and B is not an ancestor of A, e.g. - - /--> A - O - \--> B - */ + /** + * Determine the number of distinct branches for the set of ledgers. + * + * Ledgers A and B are on different branches if A != B, A is not an + * ancestor of B and B is not an ancestor of A, e.g. + * + * /--> A + * O + * \--> B + */ static std::size_t branches(std::set const& ledgers); }; -/** Helper for writing unit tests with controlled ledger histories. - - This class allows clients to refer to distinct ledgers as strings, where - each character in the string indicates a unique ledger. It enforces the - uniqueness at runtime, but this simplifies creation of alternate ledger - histories, e.g. - - HistoryHelper hh; - hh["a"] - hh["ab"] - hh["ac"] - hh["abd"] - - Creates a history like - b - d - / - a - c - -*/ +/** + * Helper for writing unit tests with controlled ledger histories. + * + * This class allows clients to refer to distinct ledgers as strings, where + * each character in the string indicates a unique ledger. It enforces the + * uniqueness at runtime, but this simplifies creation of alternate ledger + * histories, e.g. + * + * HistoryHelper hh; + * hh["a"] + * hh["ab"] + * hh["ac"] + * hh["abd"] + * + * Creates a history like + * b - d + * / + * a - c + */ struct LedgerHistoryHelper { LedgerOracle oracle; @@ -316,11 +336,12 @@ struct LedgerHistoryHelper ledgers[""] = Ledger{Ledger::MakeGenesis{}}; } - /** Get or create the ledger with the given string history. - - Creates any necessary intermediate ledgers, but asserts if - a letter is re-used (e.g. "abc" then "adc" would assert) - */ + /** + * Get or create the ledger with the given string history. + * + * Creates any necessary intermediate ledgers, but asserts if + * a letter is re-used (e.g. "abc" then "adc" would assert) + */ Ledger const& operator[](std::string const& s) { diff --git a/src/test/csf/random.h b/src/test/csf/random.h index aa20c73a10..f8df253642 100644 --- a/src/test/csf/random.h +++ b/src/test/csf/random.h @@ -8,15 +8,16 @@ namespace xrpl::test::csf { -/** Return a randomly shuffled copy of vector based on weights w. - - @param v The set of values - @param w The set of weights of each value - @param g A pseudo-random number generator - @return A vector with entries randomly sampled without replacement - from the original vector based on the provided weights. - I.e. res[0] comes from sample v[i] with weight w[i]/suk_ w[k] -*/ +/** + * Return a randomly shuffled copy of vector based on weights w. + * + * @param v The set of values + * @param w The set of weights of each value + * @param g A pseudo-random number generator + * @return A vector with entries randomly sampled without replacement + * from the original vector based on the provided weights. + * I.e. res[0] comes from sample v[i] with weight w[i]/suk_ w[k] + */ template std::vector randomWeightedShuffle(std::vector v, std::vector w, G& g) @@ -34,14 +35,15 @@ randomWeightedShuffle(std::vector v, std::vector w, G& g) return v; } -/** Generate a vector of random samples - - @param size the size of the sample - @param dist the distribution to sample - @param g the pseudo-random number generator - - @return vector of samples -*/ +/** + * Generate a vector of random samples + * + * @param size the size of the sample + * @param dist the distribution to sample + * @param g the pseudo-random number generator + * + * @return vector of samples + */ template std::vector sample(std::size_t size, RandomNumberDistribution dist, Generator& g) @@ -51,13 +53,14 @@ sample(std::size_t size, RandomNumberDistribution dist, Generator& g) return res; } -/** Invocable that returns random samples from a range according to a discrete - distribution - - Given a pair of random access iterators begin and end, each call to the - instance of Selector returns a random entry in the range (begin,end) - according to the weights provided at construction. -*/ +/** + * Invocable that returns random samples from a range according to a discrete + * distribution + * + * Given a pair of random access iterators begin and end, each call to the + * instance of Selector returns a random entry in the range (begin,end) + * according to the weights provided at construction. + */ template class Selector { @@ -66,12 +69,13 @@ class Selector Generator g_; public: - /** Constructor - @param first Random access iterator to the start of the range - @param last Random access iterator to the end of the range - @param w Vector of weights of size list-first - @param g the pseudo-random number generator - */ + /** + * Constructor + * @param first Random access iterator to the start of the range + * @param last Random access iterator to the end of the range + * @param w Vector of weights of size list-first + * @param g the pseudo-random number generator + */ Selector(RAIter first, RAIter last, std::vector const& w, Generator& g) : first_{first}, last_{last}, dd_{w.begin(), w.end()}, g_{g} { @@ -100,7 +104,8 @@ makeSelector(Iter first, Iter last, std::vector const& w, Generator& g) //------------------------------------------------------------------------------ // Additional distributions of interest not defined in -/** Constant "distribution" that always returns the same value +/** + * Constant "distribution" that always returns the same value */ class ConstantDistribution { @@ -119,11 +124,12 @@ public: } }; -/** Power-law distribution with PDF - - P(x) = (x/xmin)^-a - - for a >= 1 and xmin >= 1 +/** + * Power-law distribution with PDF + * + * P(x) = (x/xmin)^-a + * + * for a >= 1 and xmin >= 1 */ class PowerLawDistribution { diff --git a/src/test/csf/submitters.h b/src/test/csf/submitters.h index a71c849fb7..160d0bcd9f 100644 --- a/src/test/csf/submitters.h +++ b/src/test/csf/submitters.h @@ -13,7 +13,9 @@ namespace xrpl::test::csf { // Submitters are classes for simulating submission of transactions to the // network -/** Represents rate as a count/duration */ +/** + * Represents rate as a count/duration + */ struct Rate { std::size_t count; @@ -26,23 +28,24 @@ struct Rate } }; -/** Submits transactions to a specified peer - - Submits successive transactions beginning at start, then spaced according - to successive calls of distribution(), until stop. - - @tparam Distribution is a `UniformRandomBitGenerator` from the STL that - is used by random distributions to generate random samples - @tparam Generator is an object with member - - T operator()(Generator &g) - - which generates the delay T in SimDuration units to the next - transaction. For the current definition of SimDuration, this is - currently the number of nanoseconds. Submitter internally casts - arithmetic T to SimDuration::rep units to allow using standard - library distributions as a Distribution. -*/ +/** + * Submits transactions to a specified peer + * + * Submits successive transactions beginning at start, then spaced according + * to successive calls of distribution(), until stop. + * + * @tparam Distribution is a `UniformRandomBitGenerator` from the STL that + * is used by random distributions to generate random samples + * @tparam Generator is an object with member + * + * T operator()(Generator &g) + * + * which generates the delay T in SimDuration units to the next + * transaction. For the current definition of SimDuration, this is + * currently the number of nanoseconds. Submitter internally casts + * arithmetic T to SimDuration::rep units to allow using standard + * library distributions as a Distribution. + */ template class Submitter { diff --git a/src/test/csf/timers.h b/src/test/csf/timers.h index 2f86fe7729..4f13b21b25 100644 --- a/src/test/csf/timers.h +++ b/src/test/csf/timers.h @@ -12,7 +12,8 @@ namespace xrpl::test::csf { // Timers are classes that schedule repeated events and are mostly independent // of simulation-specific details. -/** Gives heartbeat of simulation to signal simulation progression +/** + * Gives heartbeat of simulation to signal simulation progression */ class HeartbeatTimer { diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 605f15f812..68b6d9f745 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -131,7 +131,8 @@ struct ClawbackArg std::optional err = std::nullopt; }; -/** Convenience class to test AMM functionality. +/** + * Convenience class to test AMM functionality. */ class AMM { @@ -187,7 +188,8 @@ public: STAmount const& asset2, std::uint16_t const& tfee); - /** Send amm_info RPC command + /** + * Send amm_info RPC command */ [[nodiscard]] json::Value ammRpcInfo( @@ -209,7 +211,8 @@ public: bool ignoreParams, unsigned apiVersion) const; - /** Verify the AMM balances. + /** + * Verify the AMM balances. */ [[nodiscard]] bool expectBalances( @@ -218,7 +221,8 @@ public: IOUAmount const& lpt, std::optional const& account = std::nullopt) const; - /** Get AMM balances for the token pair. + /** + * Get AMM balances for the token pair. */ [[nodiscard]] std::tuple balances( diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 971cc5db84..9385313225 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -103,7 +103,8 @@ public: } protected: - /** testAMM() funds 30,000XRP and 30,000IOU + /** + * testAMM() funds 30,000XRP and 30,000IOU * for each non-XRP asset to Alice and Carol */ void diff --git a/src/test/jtx/AbstractClient.h b/src/test/jtx/AbstractClient.h index 7c7107ca79..f9d8de0768 100644 --- a/src/test/jtx/AbstractClient.h +++ b/src/test/jtx/AbstractClient.h @@ -20,21 +20,24 @@ public: AbstractClient& operator=(AbstractClient const&) = delete; - /** Submit a command synchronously. - - The arguments to the function and the returned JSON - are in a normalized format, the same whether the client - is using the JSON-RPC over HTTP/S or WebSocket transport. - - @param cmd The command to execute - @param params json::Value of null or object type - with zero or more key/value pairs. - @return The server response in normalized format. - */ + /** + * Submit a command synchronously. + * + * The arguments to the function and the returned JSON + * are in a normalized format, the same whether the client + * is using the JSON-RPC over HTTP/S or WebSocket transport. + * + * @param cmd The command to execute + * @param params json::Value of null or object type + * with zero or more key/value pairs. + * @return The server response in normalized format. + */ virtual json::Value invoke(std::string const& cmd, json::Value const& params = {}) = 0; - /// Get RPC 1.0 or RPC 2.0 + /** + * Get RPC 1.0 or RPC 2.0 + */ [[nodiscard]] virtual unsigned version() const = 0; }; diff --git a/src/test/jtx/Account.h b/src/test/jtx/Account.h index 264a39f08b..60d07beebd 100644 --- a/src/test/jtx/Account.h +++ b/src/test/jtx/Account.h @@ -14,7 +14,9 @@ namespace xrpl::test::jtx { class IOU; -/** Immutable cryptographic account descriptor. */ +/** + * Immutable cryptographic account descriptor. + */ class Account { private: @@ -24,7 +26,9 @@ private: }; public: - /** The master account. */ + /** + * The master account. + */ static Account const kMaster; Account() = delete; @@ -35,7 +39,9 @@ public: Account& operator=(Account&&) = default; - /** Create an account from a simple string name. */ + /** + * Create an account from a simple string name. + */ /** @{ */ Account(std::string name, KeyType type = KeyType::Secp256k1); @@ -50,63 +56,79 @@ public: /** @} */ - /** Create an Account from an account ID. Should only be used when the - * secret key is unavailable, such as for pseudo-accounts. */ + /** + * Create an Account from an account ID. Should only be used when the + * secret key is unavailable, such as for pseudo-accounts. + */ explicit Account(std::string name, AccountID const& id); enum class AcctStringType { Base58Seed, Other }; - /** Create an account from a base58 seed string. Throws on invalid seed. */ + /** + * Create an account from a base58 seed string. Throws on invalid seed. + */ Account(AcctStringType stringType, std::string base58SeedStr); - /** Return the name */ + /** + * Return the name + */ [[nodiscard]] std::string const& name() const { return name_; } - /** Return the public key. */ + /** + * Return the public key. + */ [[nodiscard]] PublicKey const& pk() const { return pk_; } - /** Return the secret key. */ + /** + * Return the secret key. + */ [[nodiscard]] SecretKey const& sk() const { return sk_; } - /** Returns the Account ID. - - The Account ID is the uint160 hash of the public key. - */ + /** + * Returns the Account ID. + * + * The Account ID is the uint160 hash of the public key. + */ [[nodiscard]] AccountID id() const { return id_; } - /** Returns the human readable public key. */ + /** + * Returns the human readable public key. + */ [[nodiscard]] std::string const& human() const { return human_; } - /** Implicit conversion to AccountID. - - This allows passing an Account - where an AccountID is expected. - */ + /** + * Implicit conversion to AccountID. + * + * This allows passing an Account + * where an AccountID is expected. + */ operator AccountID() const { return id_; } - /** Returns an IOU for the specified gateway currency. */ + /** + * Returns an IOU for the specified gateway currency. + */ IOU operator[](std::string const& s) const; diff --git a/src/test/jtx/CheckMessageLogs.h b/src/test/jtx/CheckMessageLogs.h index fc46f41671..24b2c24a73 100644 --- a/src/test/jtx/CheckMessageLogs.h +++ b/src/test/jtx/CheckMessageLogs.h @@ -9,7 +9,8 @@ namespace xrpl::test { -/** Log manager that searches for a specific message substring +/** + * Log manager that searches for a specific message substring */ class CheckMessageLogs : public Logs { @@ -41,12 +42,13 @@ class CheckMessageLogs : public Logs }; public: - /** Constructor - - @param msg The message string to search for - @param pFound Pointer to the variable to set to true if the message is - found - */ + /** + * Constructor + * + * @param msg The message string to search for + * @param pFound Pointer to the variable to set to true if the message is + * found + */ CheckMessageLogs(std::string msg, bool* pFound) : Logs{beast::Severity::Debug}, msg_{std::move(msg)}, pFound_{pFound} { diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index ba28d19738..7e22cdd571 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -55,14 +55,15 @@ namespace xrpl::test::jtx { -/** Wrapper that captures std::source_location when implicitly constructed. - This solves the problem of combining std::source_location with variadic - templates. The std::source_location default argument is evaluated at the - call site when the wrapper is constructed via implicit conversion. - - This is a template struct that holds the value directly, allowing implicit - conversion without template argument deduction issues via CTAD. -*/ +/** + * Wrapper that captures std::source_location when implicitly constructed. + * This solves the problem of combining std::source_location with variadic + * templates. The std::source_location default argument is evaluated at the + * call site when the wrapper is constructed via implicit conversion. + * + * This is a template struct that holds the value directly, allowing implicit + * conversion without template argument deduction issues via CTAD. + */ template struct WithSourceLocation { @@ -77,7 +78,9 @@ struct WithSourceLocation } }; -/** Designate accounts as no-ripple in Env::fund */ +/** + * Designate accounts as no-ripple in Env::fund + */ template std::array noripple(Account const& account, Args const&... args) @@ -151,7 +154,9 @@ public: //------------------------------------------------------------------------------ -/** A transaction testing environment. */ +/** + * A transaction testing environment. + */ class Env { public: @@ -159,7 +164,9 @@ public: Account const& master = Account::kMaster; - /// Used by parseResult() and postConditions() + /** + * Used by parseResult() and postConditions() + */ struct ParsedResult { std::optional ter; @@ -308,11 +315,12 @@ public: return *bundle_.timeKeeper; } - /** Returns the current network time - - @note This is manually advanced when ledgers - close or by callers. - */ + /** + * Returns the current network time + * + * @note This is manually advanced when ledgers + * close or by callers. + */ NetClock::time_point // NOLINTNEXTLINE(readability-make-member-function-const) now() @@ -320,7 +328,9 @@ public: return timeKeeper().now(); } - /** Returns the connected client. */ + /** + * Returns the connected client. + */ AbstractClient& // NOLINTNEXTLINE(readability-make-member-function-const) client() @@ -328,11 +338,12 @@ public: return *bundle_.client; } - /** Execute an RPC command. - - The command is examined and used to build - the correct JSON as per the arguments. - */ + /** + * Execute an RPC command. + * + * The command is examined and used to build + * the correct JSON as per the arguments. + */ template json::Value rpc(unsigned apiVersion, @@ -354,61 +365,64 @@ public: json::Value rpc(std::string const& cmd, Args&&... args); - /** Returns the current ledger. - - This is a non-modifiable snapshot of the - open ledger at the moment of the call. - Transactions applied after the call to open() - will not be visible. - - */ + /** + * Returns the current ledger. + * + * This is a non-modifiable snapshot of the + * open ledger at the moment of the call. + * Transactions applied after the call to open() + * will not be visible. + */ [[nodiscard]] std::shared_ptr current() const { return app().getOpenLedger().current(); } - /** Returns the last closed ledger. - - The open ledger is built on top of the - last closed ledger. When the open ledger - is closed, it becomes the new closed ledger - and a new open ledger takes its place. - */ + /** + * Returns the last closed ledger. + * + * The open ledger is built on top of the + * last closed ledger. When the open ledger + * is closed, it becomes the new closed ledger + * and a new open ledger takes its place. + */ std::shared_ptr closed(); - /** Close and advance the ledger. - - The resulting close time will be different and - greater than the previous close time, and at or - after the passed-in close time. - - Effects: - - Creates a new closed ledger from the last - closed ledger. - - All transactions that made it into the open - ledger are applied to the closed ledger. - - The Application network time is set to - the close time of the resulting ledger. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The resulting close time will be different and + * greater than the previous close time, and at or + * after the passed-in close time. + * + * Effects: + * + * Creates a new closed ledger from the last + * closed ledger. + * + * All transactions that made it into the open + * ledger are applied to the closed ledger. + * + * The Application network time is set to + * the close time of the resulting ledger. + * + * @return true if no error, false if error + */ bool close( NetClock::time_point closeTime, std::optional consensusDelay = std::nullopt); - /** Close and advance the ledger. - - The time is calculated as the duration from - the previous ledger closing time. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The time is calculated as the duration from + * the previous ledger closing time. + * + * @return true if no error, false if error + */ template bool close(std::chrono::duration const& elapsed) @@ -417,13 +431,14 @@ public: return close(now() + elapsed); } - /** Close and advance the ledger. - - The time is calculated as five seconds from - the previous ledger closing time. - - @return true if no error, false if error - */ + /** + * Close and advance the ledger. + * + * The time is calculated as five seconds from + * the previous ledger closing time. + * + * @return true if no error, false if error + */ bool close() { @@ -431,34 +446,35 @@ public: return close(std::chrono::seconds(5)); } - /** Close and advance the ledger, then synchronize with the server's - io_context to ensure all async operations initiated by the close have - been started. - - This function performs the same ledger close as close(), but additionally - ensures that all tasks posted to the server's io_context (such as - WebSocket subscription message sends) have been initiated before returning. - - What it guarantees: - - All async operations posted before syncClose() have been STARTED - - For WebSocket sends: async_write_some() has been called - - The actual I/O completion may still be pending (async) - - What it does NOT guarantee: - - Async operations have COMPLETED - - WebSocket messages have been received by clients - - However, for localhost connections, the remaining latency is typically - microseconds, making tests reliable - - Use this instead of close() when: - - Test code immediately checks for subscription messages - - Race conditions between test and worker threads must be avoided - - Deterministic test behavior is required - - @param timeout Maximum time to wait for the barrier task to execute - @return true if close succeeded and barrier executed within timeout, - false otherwise - */ + /** + * Close and advance the ledger, then synchronize with the server's + * io_context to ensure all async operations initiated by the close have + * been started. + * + * This function performs the same ledger close as close(), but additionally + * ensures that all tasks posted to the server's io_context (such as + * WebSocket subscription message sends) have been initiated before returning. + * + * What it guarantees: + * - All async operations posted before syncClose() have been STARTED + * - For WebSocket sends: async_write_some() has been called + * - The actual I/O completion may still be pending (async) + * + * What it does NOT guarantee: + * - Async operations have COMPLETED + * - WebSocket messages have been received by clients + * - However, for localhost connections, the remaining latency is typically + * microseconds, making tests reliable + * + * Use this instead of close() when: + * - Test code immediately checks for subscription messages + * - Race conditions between test and worker threads must be avoided + * - Deterministic test behavior is required + * + * @param timeout Maximum time to wait for the barrier task to execute + * @return true if close succeeded and barrier executed within timeout, + * false otherwise + */ [[nodiscard]] bool syncClose(std::chrono::steady_clock::duration timeout = std::chrono::seconds{1}) { @@ -473,16 +489,19 @@ public: return result && status == std::future_status::ready; } - /** Turn on JSON tracing. - With no arguments, trace all - */ + /** + * Turn on JSON tracing. + * With no arguments, trace all + */ void trace(int howMany = -1) { trace_ = howMany; } - /** Turn off JSON tracing. */ + /** + * Turn off JSON tracing. + */ void notrace() { @@ -495,7 +514,9 @@ public: parseFailureExpected_ = b; } - /** Turn off signature checks. */ + /** + * Turn off signature checks. + */ void disableSigs() { @@ -516,11 +537,15 @@ public: return retries_; } - /** Associate AccountID with account. */ + /** + * Associate AccountID with account. + */ void memoize(Account const& account); - /** Returns the Account given the AccountID. */ + /** + * Returns the Account given the AccountID. + */ /** @{ */ [[nodiscard]] Account const& lookup(AccountID const& id) const; @@ -529,51 +554,84 @@ public: lookup(std::string const& base58ID) const; /** @} */ - /** Returns the XRP balance on an account. - Returns 0 if the account does not exist. - */ + /** + * Returns the XRP balance on an account. + * Returns 0 if the account does not exist. + */ [[nodiscard]] PrettyAmount balance(Account const& account) const; - /** Returns the next sequence number on account. - Exceptions: - Throws if the account does not exist - */ + /** + * Returns the next sequence number on account. + * + * @throws if the account does not exist + */ [[nodiscard]] std::uint32_t seq(Account const& account) const; - /** Return the balance on an account. - Returns 0 if the trust line does not exist. - */ + /** + * Return the balance on an account. + * Returns 0 if the trust line does not exist. + */ // VFALCO NOTE This should return a unit-less amount [[nodiscard]] PrettyAmount balance(Account const& account, Asset const& asset) const; - /** Returns the IOU limit on an account. - Returns 0 if the trust line does not exist. - */ + /** + * Returns the IOU limit on an account. + * Returns 0 if the trust line does not exist. + */ [[nodiscard]] PrettyAmount limit(Account const& account, Issue const& issue) const; - /** Return the number of objects owned by an account. + /** + * Return the number of objects owned by an account. * Returns 0 if the account does not exist. */ [[nodiscard]] std::uint32_t ownerCount(Account const& account) const; - /** Return an account root. - @return empty if the account does not exist. - */ + /** + * Return the number of sponsored objects owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoredOwnerCount(Account const& account) const; + + /** + * Return the number of sponsoring objects owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoringOwnerCount(Account const& account) const; + + /** + * Return the number of sponsoring accounts owned by an account. + * + * @throws if the account does not exist. + */ + [[nodiscard]] std::uint32_t + sponsoringAccountCount(Account const& account) const; + + /** + * Return an account root. + * @return empty if the account does not exist. + */ [[nodiscard]] SLE::const_pointer le(Account const& account) const; - /** Return a ledger entry. - @return empty if the ledger entry does not exist - */ + /** + * Return a ledger entry. + * @return empty if the ledger entry does not exist + */ [[nodiscard]] SLE::const_pointer le(Keylet const& k) const; - /** Create a JTx from parameters. */ + /** + * Create a JTx from parameters. + */ template JTx jt(JsonValue&& jv, FN const&... fN) @@ -585,7 +643,9 @@ public: return jt; } - /** Create a JTx from parameters. */ + /** + * Create a JTx from parameters. + */ template JTx jtnofill(JsonValue&& jv, FN const&... fN) @@ -597,9 +657,10 @@ public: return jt; } - /** Create JSON from parameters. - This will apply funclets and autofill. - */ + /** + * Create JSON from parameters. + * This will apply funclets and autofill. + */ template json::Value json(JsonValue&& jv, FN const&... fN) @@ -608,11 +669,12 @@ public: return std::move(tj.jv); } - /** Check a set of requirements. - - The requirements are formed - from condition functors. - */ + /** + * Check a set of requirements. + * + * The requirements are formed + * from condition functors. + */ template void require(Args const&... args) @@ -620,29 +682,33 @@ public: jtx::required(args...)(*this); } - /** Gets the TER result and `didApply` flag from a RPC Json result object. + /** + * Gets the TER result and `didApply` flag from a RPC Json result object. */ static ParsedResult parseResult(json::Value const& jr); - /** Submit an existing JTx. - This calls postconditions. - */ + /** + * Submit an existing JTx. + * This calls postconditions. + */ virtual void submit(JTx const& jt, std::source_location const& loc = std::source_location::current()); - /** Use the submit RPC command with a provided JTx object. - This calls postconditions. - */ + /** + * Use the submit RPC command with a provided JTx object. + * This calls postconditions. + */ void signAndSubmit( JTx const& jt, json::Value params = json::ValueType::Null, std::source_location const& loc = std::source_location::current()); - /** Check expected postconditions - of JTx submission. - */ + /** + * Check expected postconditions + * of JTx submission. + */ void postconditions( JTx const& jt, @@ -650,7 +716,9 @@ public: json::Value const& jr = json::Value(), std::source_location const& loc = std::source_location::current()); - /** Apply funclets and submit. */ + /** + * Apply funclets and submit. + */ /** @{ */ template Env& @@ -683,38 +751,42 @@ public: } /** @} */ - /** Return the TER for the last JTx. */ + /** + * Return the TER for the last JTx. + */ [[nodiscard]] TER ter() const { return ter_; } - /** Return metadata for the last JTx. + /** + * Return metadata for the last JTx. * - * NOTE: this has a side effect of closing the open ledger. - * The ledger will only be closed if it includes transactions. + * NOTE: this has a side effect of closing the open ledger. + * The ledger will only be closed if it includes transactions. * - * Effects: + * Effects: * - * The open ledger is closed as if by a call - * to close(). The metadata for the last - * transaction ID, if any, is returned. + * The open ledger is closed as if by a call + * to close(). The metadata for the last + * transaction ID, if any, is returned. */ std::shared_ptr meta(); - /** Return the tx data for the last JTx. - - Effects: - - The tx data for the last transaction - ID, if any, is returned. No side - effects. - - @note Only necessary for JTx submitted - with via sign-and-submit method. - */ + /** + * Return the tx data for the last JTx. + * + * Effects: + * + * The tx data for the last transaction + * ID, if any, is returned. No side + * effects. + * + * @note Only necessary for JTx submitted + * with via sign-and-submit method. + */ [[nodiscard]] std::shared_ptr tx() const; @@ -749,32 +821,33 @@ private: } public: - /** Create a new account with some XRP. - - These convenience functions are for easy set-up - of the environment, they bypass fee, seq, and sig - settings. The XRP is transferred from the master - account. - - Preconditions: - The account must not already exist - - Effects: - The asfDefaultRipple on the account is set, - and the sequence number is incremented, unless - the account is wrapped with a call to noripple. - - The account's XRP balance is set to amount. - - Generates a test that the balance is set. - - @param amount The amount of XRP to transfer to - each account. - - @param args A heterogeneous list of accounts to fund - or calls to noripple with lists of accounts - to fund. - */ + /** + * Create a new account with some XRP. + * + * These convenience functions are for easy set-up + * of the environment, they bypass fee, seq, and sig + * settings. The XRP is transferred from the master + * account. + * + * Preconditions: + * The account must not already exist + * + * Effects: + * The asfDefaultRipple on the account is set, + * and the sequence number is incremented, unless + * the account is wrapped with a call to noripple. + * + * The account's XRP balance is set to amount. + * + * Generates a test that the balance is set. + * + * @param amount The amount of XRP to transfer to + * each account. + * + * @param args A heterogeneous list of accounts to fund + * or calls to noripple with lists of accounts + * to fund. + */ template void fund(STAmount const& amount, Arg const& arg, Args const&... args) @@ -784,23 +857,24 @@ public: fund(amount, args...); } - /** Establish trust lines. - - These convenience functions are for easy set-up - of the environment, they bypass fee, seq, and sig - settings. - - Preconditions: - The account must already exist - - Effects: - A trust line is added for the account. - The account's sequence number is incremented. - The account is refunded for the transaction fee - to set the trust line. - - The refund comes from the master account. - */ + /** + * Establish trust lines. + * + * These convenience functions are for easy set-up + * of the environment, they bypass fee, seq, and sig + * settings. + * + * Preconditions: + * The account must already exist + * + * Effects: + * A trust line is added for the account. + * The account's sequence number is incremented. + * The account is refunded for the transaction fee + * to set the trust line. + * + * The refund comes from the master account. + */ /** @{ */ void trust(STAmount const& amount, Account const& account); @@ -814,10 +888,11 @@ public: } /** @} */ - /** Create a STTx from a JTx without sanitizing - Use to inject bogus values into test transactions by first - editing the JSON. - */ + /** + * Create a STTx from a JTx without sanitizing + * Use to inject bogus values into test transactions by first + * editing the JSON. + */ std::shared_ptr ust(JTx const& jt); @@ -841,13 +916,14 @@ protected: virtual void autofill(JTx& jt); - /** Create a STTx from a JTx - The framework requires that JSON is valid. - On a parse error, the JSON is logged and - an exception thrown. - Throws: - ParseError - */ + /** + * Create a STTx from a JTx + * The framework requires that JSON is valid. + * On a parse error, the JSON is logged and + * an exception thrown. + * + * @throws ParseError + */ std::shared_ptr st(JTx const& jt); diff --git a/src/test/jtx/Env_ss.h b/src/test/jtx/Env_ss.h index 16e1cdbc82..ca298f0069 100644 --- a/src/test/jtx/Env_ss.h +++ b/src/test/jtx/Env_ss.h @@ -10,10 +10,11 @@ namespace xrpl::test::jtx { -/** A transaction testing environment wrapper. - Transactions submitted in sign-and-submit mode - by default. -*/ +/** + * A transaction testing environment wrapper. + * Transactions submitted in sign-and-submit mode + * by default. + */ class EnvSs { private: diff --git a/src/test/jtx/JSONRPCClient.h b/src/test/jtx/JSONRPCClient.h index 2864cbbdc8..10e567e91f 100644 --- a/src/test/jtx/JSONRPCClient.h +++ b/src/test/jtx/JSONRPCClient.h @@ -8,7 +8,9 @@ namespace xrpl::test { -/** Returns a client using JSON-RPC over HTTP/S. */ +/** + * Returns a client using JSON-RPC over HTTP/S. + */ std::unique_ptr makeJSONRPCClient(Config const& cfg, unsigned rpcVersion = 2); diff --git a/src/test/jtx/JTx.h b/src/test/jtx/JTx.h index da105e01aa..794334bec7 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -19,9 +19,10 @@ namespace xrpl::test::jtx { class Env; -/** Execution context for applying a JSON transaction. - This augments the transaction with various settings. -*/ +/** + * Execution context for applying a JSON transaction. + * This augments the transaction with various settings. + */ struct JTx { json::Value jv; @@ -37,7 +38,7 @@ struct JTx // Functions that sign the transaction from the Account std::vector> mainSigners; // Functions that sign something else after the mainSigners, such as - // sfCounterpartySignature + // sfCounterpartySignature and sfSponsorSignature std::vector> postSigners; JTx() = default; @@ -63,10 +64,11 @@ struct JTx return jv[key]; } - /** Return a property if it exists - - @return nullptr if the Prop does not exist - */ + /** + * Return a property if it exists + * + * @return nullptr if the Prop does not exist + */ /** @{ */ template Prop* @@ -93,10 +95,11 @@ struct JTx } /** @} */ - /** Set a property - If the property already exists, - it is replaced. - */ + /** + * Set a property + * If the property already exists, + * it is replaced. + */ /** @{ */ void set(std::unique_ptr p) diff --git a/src/test/jtx/Oracle.h b/src/test/jtx/Oracle.h index 2c296ba705..d0fe8104e5 100644 --- a/src/test/jtx/Oracle.h +++ b/src/test/jtx/Oracle.h @@ -102,7 +102,8 @@ struct RemoveArg // validation {close-maxLastUpdateTimeDelta,close+maxLastUpdateTimeDelta}. static constexpr std::chrono::seconds kTestStartTime = kEpochOffset + std::chrono::seconds(10'000); -/** Oracle class facilitates unit-testing of the Price Oracle feature. +/** + * Oracle class facilitates unit-testing of the Price Oracle feature. * It defines functions to create, update, and delete the Oracle object, * to query for various states, and to call APIs. */ diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h index fa3f0d40f9..42d391c46f 100644 --- a/src/test/jtx/PathSet.h +++ b/src/test/jtx/PathSet.h @@ -18,7 +18,8 @@ namespace xrpl::test { -/** Count offer +/** + * Count offer */ inline std::size_t countOffers( @@ -52,7 +53,8 @@ countOffers( return count; } -/** An offer exists +/** + * An offer exists */ inline bool isOffer( @@ -64,7 +66,8 @@ isOffer( return countOffers(env, account, takerPays, takerGets) > 0; } -/** An offer exists +/** + * An offer exists */ inline bool isOffer(jtx::Env& env, jtx::Account const& account, Asset const& takerPays, Asset const& takerGets) diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index cb2193db99..e7a2808f07 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -51,11 +51,12 @@ namespace xrpl::test::jtx { -/** Generic helper class for helper classes that set a field on a JTx. - - Not every helper will be able to use this because of conversions and other - issues, but for classes where it's straightforward, this can simplify things. -*/ +/** + * Generic helper class for helper classes that set a field on a JTx. + * + * Not every helper will be able to use this because of conversions and other + * issues, but for classes where it's straightforward, this can simplify things. + */ template < class SField, // NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC @@ -306,7 +307,8 @@ using valueUnitWrapper = JTxFieldWrapper using simpleField = JTxFieldWrapper>; -/** General field definitions, or fields used in multiple transaction namespaces +/** + * General field definitions, or fields used in multiple transaction namespaces */ auto const kData = JTxFieldWrapper(sfData); @@ -379,6 +381,18 @@ checkArraySize(json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +// Helper function that returns the sponsored owner count on an account. +std::uint32_t +sponsoredOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account); + +// Helper function that returns the sponsoring owner count on an account. +std::uint32_t +sponsoringOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account); + +// Helper function that returns the sponsoring account count on an account. +std::uint32_t +sponsoringAccountCount(test::jtx::Env const& env, test::jtx::Account const& account); + [[nodiscard]] inline bool checkVL(Slice const& result, std::string const& expected) @@ -450,12 +464,8 @@ same(STPathSet const& st1, Args const&... args) if (st1.size() != st2.size()) return false; - for (auto const& p : st2) - { - if (std::ranges::find(st1, p) == st1.end()) - return false; - } - return true; + return std::ranges::all_of( + st2, [&st1](auto const& p) { return std::ranges::find(st1, p) != st1.end(); }); } json::Value @@ -741,7 +751,9 @@ equal(Strand const& strand, Args&&... args) /***************************************************************/ namespace check { -/** Create a check. */ +/** + * Create a check. + */ template requires std::is_same_v json::Value @@ -961,7 +973,9 @@ pay(AccountID const& account, } // namespace loan -/** Set Expiration on a JTx. */ +/** + * Set Expiration on a JTx. + */ class Expiration { private: @@ -980,7 +994,9 @@ public: } }; -/** Set SourceTag on a JTx. */ +/** + * Set SourceTag on a JTx. + */ class SourceTag { private: @@ -998,7 +1014,9 @@ public: } }; -/** Set DestinationTag on a JTx. */ +/** + * Set DestinationTag on a JTx. + */ class DestTag { private: diff --git a/src/test/jtx/WSClient.h b/src/test/jtx/WSClient.h index 4aa41e072c..ee5a46e9ae 100644 --- a/src/test/jtx/WSClient.h +++ b/src/test/jtx/WSClient.h @@ -18,18 +18,24 @@ namespace xrpl::test { class WSClient : public AbstractClient { public: - /** Retrieve a message. */ + /** + * Retrieve a message. + */ virtual std::optional getMsg(std::chrono::milliseconds const& timeout = std::chrono::milliseconds{0}) = 0; - /** Retrieve a message that meets the predicate criteria. */ + /** + * Retrieve a message that meets the predicate criteria. + */ virtual std::optional findMsg( std::chrono::milliseconds const& timeout, std::function pred) = 0; }; -/** Returns a client operating through WebSockets/S. */ +/** + * Returns a client operating through WebSockets/S. + */ std::unique_ptr makeWSClient( Config const& cfg, diff --git a/src/test/jtx/acctdelete.h b/src/test/jtx/acctdelete.h index 3f8b9f1ed2..935a97ac65 100644 --- a/src/test/jtx/acctdelete.h +++ b/src/test/jtx/acctdelete.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Delete account. If successful transfer remaining XRP to dest. */ +/** + * Delete account. If successful transfer remaining XRP to dest. + */ json::Value acctdelete(Account const& account, Account const& dest); diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index db7dbd1cc1..57a4502db9 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -69,10 +69,11 @@ struct None // could change that value (however unlikely). constexpr XRPAmount kJtxDropsPerXrp{1'000'000}; -/** Represents an XRP, IOU, or MPT quantity - This customizes the string conversion and supports - XRP conversions from integer and floating point. -*/ +/** + * Represents an XRP, IOU, or MPT quantity + * This customizes the string conversion and supports + * XRP conversions from integer and floating point. + */ struct PrettyAmount { private: @@ -91,7 +92,9 @@ public: { } - /** drops */ + /** + * drops + */ template PrettyAmount(T v) requires(sizeof(T) >= sizeof(int) && std::is_integral_v && std::is_signed_v) @@ -99,7 +102,9 @@ public: { } - /** drops */ + /** + * drops + */ template PrettyAmount(T v) requires(sizeof(T) >= sizeof(int) && std::is_unsigned_v) @@ -107,7 +112,9 @@ public: { } - /** drops */ + /** + * drops + */ PrettyAmount(XRPAmount v) : amount_(v) { } @@ -253,11 +260,12 @@ struct BookSpec struct XrpT { - /** Implicit conversion to Issue. - - This allows passing XRP where - an Issue is expected. - */ + /** + * Implicit conversion to Issue. + * + * This allows passing XRP where + * an Issue is expected. + */ operator Issue() const { return xrpIssue(); @@ -273,11 +281,12 @@ struct XrpT return true; } - /** Returns an amount of XRP as PrettyAmount, - which is trivially convertible to STAmount - - @param v The number of XRP (not drops) - */ + /** + * Returns an amount of XRP as PrettyAmount, + * which is trivially convertible to STAmount + * + * @param v The number of XRP (not drops) + */ /** @{ */ template PrettyAmount @@ -288,11 +297,12 @@ struct XrpT return {TOut{v} * kJtxDropsPerXrp}; } - /** Returns an amount of XRP as PrettyAmount, - which is trivially convertible to STAmount - - @param v The Number of XRP (not drops). May be fractional. - */ + /** + * Returns an amount of XRP as PrettyAmount, + * which is trivially convertible to STAmount + * + * @param v The Number of XRP (not drops). May be fractional. + */ PrettyAmount operator()(Number v) const { @@ -321,7 +331,9 @@ struct XrpT } /** @} */ - /** Returns None-of-XRP */ + /** + * Returns None-of-XRP + */ None operator()(NoneT) const { @@ -335,19 +347,21 @@ struct XrpT } }; -/** Converts to XRP Issue or STAmount. - - Examples: - XRP Converts to the XRP Issue - XRP(10) Returns STAmount of 10 XRP -*/ +/** + * Converts to XRP Issue or STAmount. + * + * Examples: + * XRP Converts to the XRP Issue + * XRP(10) Returns STAmount of 10 XRP + */ extern XrpT const XRP; // NOLINT(readability-identifier-naming) -/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. - - Example: - drops(10) Returns PrettyAmount of 10 drops -*/ +/** + * Returns an XRP PrettyAmount, which is trivially convertible to STAmount. + * + * Example: + * drops(10) Returns PrettyAmount of 10 drops + */ template PrettyAmount drops(Integer i) @@ -356,11 +370,12 @@ drops(Integer i) return {i}; } -/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. - -Example: -drops(view->fee().basefee) Returns PrettyAmount of 10 drops -*/ +/** + * Returns an XRP PrettyAmount, which is trivially convertible to STAmount. + * + * Example: + * drops(view->fee().basefee) Returns PrettyAmount of 10 drops + */ inline PrettyAmount drops(XRPAmount i) { @@ -383,13 +398,14 @@ struct EpsilonT static EpsilonT const kEpsilon; -/** Converts to IOU Issue or STAmount. - - Examples: - IOU Converts to the underlying Issue - IOU(10) Returns STAmount of 10 of - the underlying Issue. -*/ +/** + * Converts to IOU Issue or STAmount. + * + * Examples: + * IOU Converts to the underlying Issue + * IOU(10) Returns STAmount of 10 of + * the underlying Issue. + */ class IOU { public: @@ -417,11 +433,12 @@ public: return issue().integral(); } - /** Implicit conversion to Issue or Asset. - - This allows passing an IOU - value where an Issue or Asset is expected. - */ + /** + * Implicit conversion to Issue or Asset. + * + * This allows passing an IOU + * value where an Issue or Asset is expected. + */ operator Issue() const { return issue(); @@ -453,7 +470,9 @@ public: // VFALCO TODO // STAmount operator()(char const* s) const; - /** Returns None-of-Issue */ + /** + * Returns None-of-Issue + */ None operator()(NoneT) const { @@ -472,13 +491,14 @@ operator<<(std::ostream& os, IOU const& iou); //------------------------------------------------------------------------------ -/** Converts to MPT Issue or STAmount. - - Examples: - MPT Converts to the underlying Issue - MPT(10) Returns STAmount of 10 of - the underlying MPT -*/ +/** + * Converts to MPT Issue or STAmount. + * + * Examples: + * MPT Converts to the underlying Issue + * MPT(10) Returns STAmount of 10 of + * the underlying MPT + */ class MPT { public: @@ -504,7 +524,8 @@ public: return issuanceID; } - /** Explicit conversion to MPTIssue or asset. + /** + * Explicit conversion to MPTIssue or asset. */ [[nodiscard]] xrpl::MPTIssue mptIssue() const @@ -522,11 +543,12 @@ public: return true; } - /** Implicit conversion to MPTIssue or asset. - - This allows passing an MPT - value where an MPTIssue is expected. - */ + /** + * Implicit conversion to MPTIssue or asset. + * + * This allows passing an MPT + * value where an MPTIssue is expected. + */ operator xrpl::MPTIssue() const { return mptIssue(); @@ -558,7 +580,9 @@ public: PrettyAmount operator()(detail::EpsilonMultiple) const; - /** Returns None-of-Issue */ + /** + * Returns None-of-Issue + */ None operator()(NoneT) const { @@ -583,7 +607,9 @@ struct AnyT operator()(STAmount const& sta) const; }; -/** Amount specifier with an option for any issuer. */ +/** + * Amount specifier with an option for any issuer. + */ struct AnyAmount { bool isAny; @@ -618,9 +644,10 @@ AnyT::operator()(STAmount const& sta) const return AnyAmount(sta, this); } -/** Returns an amount representing "any issuer" - @note With respect to what the recipient will accept -*/ +/** + * Returns an amount representing "any issuer" + * @note With respect to what the recipient will accept + */ extern AnyT const kAny; } // namespace test::jtx diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 3f39e9f0fb..9d7937369b 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -11,14 +11,15 @@ namespace xrpl::test::jtx { -/** A balance matches. - - This allows "none" which means either the account - doesn't exist (no XRP) or the trust line does not - exist. If an amount is specified, the SLE must - exist even if the amount is 0, or else the test - fails. -*/ +/** + * A balance matches. + * + * This allows "none" which means either the account + * doesn't exist (no XRP) or the trust line does not + * exist. If an amount is specified, the SLE must + * exist even if the amount is 0, or else the test + * fails. + */ class Balance { private: diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index bb1deec1a2..140fc84ab1 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -18,7 +18,9 @@ #include #include -/** @brief Helpers for constructing Batch test transactions. */ +/** + * @brief Helpers for constructing Batch test transactions. + */ namespace xrpl::test::jtx::batch { /** @@ -57,7 +59,9 @@ calcConfidentialBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32 json::Value outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags); -/** @brief Adds an inner Batch transaction to a JTx and autofills it. */ +/** + * @brief Adds an inner Batch transaction to a JTx and autofills it. + */ class Inner { private: @@ -106,7 +110,9 @@ public: } }; -/** @brief Sets the Batch transaction signers on a JTx. */ +/** + * @brief Sets the Batch transaction signers on a JTx. + */ class Sig { public: @@ -129,7 +135,9 @@ public: operator()(Env&, JTx& jt) const; }; -/** @brief Sets a nested multi-signature for a Batch transaction on a JTx. */ +/** + * @brief Sets a nested multi-signature for a Batch transaction on a JTx. + */ class Msig { public: diff --git a/src/test/jtx/check.h b/src/test/jtx/check.h index f66d802247..a5d1f079c4 100644 --- a/src/test/jtx/check.h +++ b/src/test/jtx/check.h @@ -12,14 +12,20 @@ namespace xrpl::test::jtx { -/** Check operations. */ +/** + * Check operations. + */ namespace check { -/** Cash a check requiring that a specific amount be delivered. */ +/** + * Cash a check requiring that a specific amount be delivered. + */ json::Value cash(jtx::Account const& dest, uint256 const& checkId, STAmount const& amount); -/** Type used to specify DeliverMin for cashing a check. */ +/** + * Type used to specify DeliverMin for cashing a check. + */ struct DeliverMin { STAmount value; @@ -28,17 +34,23 @@ struct DeliverMin } }; -/** Cash a check requiring that at least a minimum amount be delivered. */ +/** + * Cash a check requiring that at least a minimum amount be delivered. + */ json::Value cash(jtx::Account const& dest, uint256 const& checkId, DeliverMin const& atLeast); -/** Cancel a check. */ +/** + * Cancel a check. + */ json::Value cancel(jtx::Account const& dest, uint256 const& checkId); } // namespace check -/** Match the number of checks on the account. */ +/** + * Match the number of checks on the account. + */ using checks = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 29256e37bd..db5014bc07 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Sets the DeliverMin on a JTx. */ +/** + * Sets the DeliverMin on a JTx. + */ class DeliverMin { private: diff --git a/src/test/jtx/deposit.h b/src/test/jtx/deposit.h index 5ba12648e2..3039167cee 100644 --- a/src/test/jtx/deposit.h +++ b/src/test/jtx/deposit.h @@ -10,14 +10,20 @@ #include #include -/** Deposit preauthorize operations */ +/** + * Deposit preauthorize operations + */ namespace xrpl::test::jtx::deposit { -/** Preauthorize for deposit. Invoke as deposit::auth. */ +/** + * Preauthorize for deposit. Invoke as deposit::auth. + */ json::Value auth(Account const& account, Account const& auth); -/** Remove pre-authorization for deposit. Invoke as deposit::unauth. */ +/** + * Remove pre-authorization for deposit. Invoke as deposit::unauth. + */ json::Value unauth(Account const& account, Account const& unauth); diff --git a/src/test/jtx/did.h b/src/test/jtx/did.h index 30c89fd879..da10b476e8 100644 --- a/src/test/jtx/did.h +++ b/src/test/jtx/did.h @@ -10,7 +10,9 @@ #include -/** DID operations. */ +/** + * DID operations. + */ namespace xrpl::test::jtx::did { json::Value @@ -19,7 +21,9 @@ set(jtx::Account const& account); json::Value setValid(jtx::Account const& account); -/** Sets the optional DIDDocument on a DIDSet. */ +/** + * Sets the optional DIDDocument on a DIDSet. + */ class Document { private: @@ -37,7 +41,9 @@ public: } }; -/** Sets the optional URI on a DIDSet. */ +/** + * Sets the optional URI on a DIDSet. + */ class Uri { private: @@ -55,7 +61,9 @@ public: } }; -/** Sets the optional Data on a DIDSet. */ +/** + * Sets the optional Data on a DIDSet. + */ class Data { private: diff --git a/src/test/jtx/directory.h b/src/test/jtx/directory.h index 13473f949e..294cfcc43c 100644 --- a/src/test/jtx/directory.h +++ b/src/test/jtx/directory.h @@ -13,7 +13,9 @@ #include #include -/** Directory operations. */ +/** + * Directory operations. + */ namespace xrpl::test::jtx::directory { enum class Error { @@ -25,14 +27,16 @@ enum class Error { AdjustmentError }; -/// Move the position of the last page in the user's directory on open ledger to -/// newLastPage. Requirements: -/// - directory must have at least two pages (root and one more) -/// - adjust should be used to update owner nodes of the objects affected -/// - newLastPage must be greater than index of the last page in the directory -/// -/// Use this to test tecDIR_FULL errors in open ledger. -/// NOTE: effects will be DISCARDED on env.close() +/** + * Move the position of the last page in the user's directory on open ledger to + * newLastPage. Requirements: + * - directory must have at least two pages (root and one more) + * - adjust should be used to update owner nodes of the objects affected + * - newLastPage must be greater than index of the last page in the directory + * + * Use this to test tecDIR_FULL errors in open ledger. + * NOTE: effects will be DISCARDED on env.close() + */ auto bumpLastPage( Env& env, @@ -40,10 +44,12 @@ bumpLastPage( Keylet directory, std::function adjust) -> std::expected; -/// Implementation of adjust for the most common ledger entry, i.e. one where -/// page index is stored in sfOwnerNode (and only there). Pass this function -/// to bumpLastPage if the last page of directory has only objects -/// of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.) +/** + * Implementation of adjust for the most common ledger entry, i.e. one where + * page index is stored in sfOwnerNode (and only there). Pass this function + * to bumpLastPage if the last page of directory has only objects + * of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.) + */ bool adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page); diff --git a/src/test/jtx/domain.h b/src/test/jtx/domain.h index ebcfdb662a..c6061c9795 100644 --- a/src/test/jtx/domain.h +++ b/src/test/jtx/domain.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the domain on a JTx. */ +/** + * Set the domain on a JTx. + */ class Domain { private: diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index 0079605637..1f920fca58 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -17,18 +17,22 @@ getEnvLocalhostAddr() return gEnvUseIPv4 ? "127.0.0.1" : "::1"; } -/// @brief initializes a config object for use with jtx::Env -/// -/// @param config the configuration object to be initialized +/** + * @brief initializes a config object for use with jtx::Env + * + * @param config the configuration object to be initialized + */ extern void setupConfigForUnitTests(Config& config); namespace jtx { -/// @brief creates and initializes a default -/// configuration for jtx::Env -/// -/// @return unique_ptr to Config instance +/** + * @brief creates and initializes a default + * configuration for jtx::Env + * + * @return unique_ptr to Config instance + */ inline std::unique_ptr envconfig() { @@ -37,18 +41,20 @@ envconfig() return p; } -/// @brief creates and initializes a default configuration for jtx::Env and -/// invokes the provided function/lambda with the configuration object. -/// -/// @param modfunc callable function or lambda to modify the default config. -/// The first argument to the function must be std::unique_ptr to -/// xrpl::Config. The function takes ownership of the unique_ptr and -/// relinquishes ownership by returning a unique_ptr. -/// -/// @param args additional arguments that will be passed to -/// the config modifier function (optional) -/// -/// @return unique_ptr to Config instance +/** + * @brief creates and initializes a default configuration for jtx::Env and + * invokes the provided function/lambda with the configuration object. + * + * @param modfunc callable function or lambda to modify the default config. + * The first argument to the function must be std::unique_ptr to + * xrpl::Config. The function takes ownership of the unique_ptr and + * relinquishes ownership by returning a unique_ptr. + * + * @param args additional arguments that will be passed to + * the config modifier function (optional) + * + * @return unique_ptr to Config instance + */ template std::unique_ptr envconfig(F&& modfunc, Args&&... args) @@ -56,14 +62,16 @@ envconfig(F&& modfunc, Args&&... args) return modfunc(envconfig(), std::forward(args)...); } -/// @brief adjust config so no admin ports are enabled -/// -/// this is intended for use with envconfig, as in -/// envconfig(noAdmin) -/// -/// @param cfg config instance to be modified -/// -/// @return unique_ptr to Config instance +/** + * @brief adjust config so no admin ports are enabled + * + * this is intended for use with envconfig, as in + * envconfig(noAdmin) + * + * @param cfg config instance to be modified + * + * @return unique_ptr to Config instance + */ std::unique_ptr noAdmin(std::unique_ptr); std::unique_ptr secureGateway(std::unique_ptr); @@ -74,61 +82,71 @@ std::unique_ptr secureGatewayLocalnet(std::unique_ptr); std::unique_ptr singleThreadIo(std::unique_ptr); -/// @brief adjust configuration with params needed to be a validator -/// -/// this is intended for use with envconfig, as in -/// envconfig(validator, myseed) -/// -/// @param cfg config instance to be modified -/// @param seed seed string for use in secret key generation. A fixed default -/// value will be used if this string is empty -/// -/// @return unique_ptr to Config instance +/** + * @brief adjust configuration with params needed to be a validator + * + * this is intended for use with envconfig, as in + * envconfig(validator, myseed) + * + * @param cfg config instance to be modified + * @param seed seed string for use in secret key generation. A fixed default + * value will be used if this string is empty + * + * @return unique_ptr to Config instance + */ std::unique_ptr validator(std::unique_ptr, std::string const&); -/// @brief add a grpc address and port to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server. If this function is not called, grpc server will not start -/// -/// -/// @param cfg config instance to be modified +/** + * @brief add a grpc address and port to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server. If this function is not called, grpc server will not start + * + * + * @param cfg config instance to be modified + */ std::unique_ptr addGrpcConfig(std::unique_ptr); -/// @brief add a grpc address, port and secureGateway to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server. If this function is not called, grpc server will not start -/// -/// -/// @param cfg config instance to be modified +/** + * @brief add a grpc address, port and secureGateway to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server. If this function is not called, grpc server will not start + * + * + * @param cfg config instance to be modified + */ std::unique_ptr addGrpcConfigWithSecureGateway(std::unique_ptr, std::string const& secureGateway); -/// @brief add a grpc address, port and TLS certificate/key paths to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with TLS enabled. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file +/** + * @brief add a grpc address, port and TLS certificate/key paths to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with TLS enabled. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + */ std::unique_ptr addGrpcConfigWithTLS( std::unique_ptr, std::string const& certPath, std::string const& keyPath); -/// @brief add a grpc address, port and TLS certificate/key/client CA paths to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with mutual TLS (client certificate verification) enabled. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file -/// @param clientCAPath path to SSL client CA certificate file for mTLS +/** + * @brief add a grpc address, port and TLS certificate/key/client CA paths to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with mutual TLS (client certificate verification) enabled. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + * @param clientCAPath path to SSL client CA certificate file for mTLS + */ std::unique_ptr addGrpcConfigWithTLSAndClientCA( std::unique_ptr, @@ -136,15 +154,17 @@ addGrpcConfigWithTLSAndClientCA( std::string const& keyPath, std::string const& clientCAPath); -/// @brief add a grpc address, port and TLS with server cert chain to config -/// -/// This is intended for use with envconfig, for tests that require a grpc -/// server with TLS enabled and intermediate CA certificates. -/// -/// @param cfg config instance to be modified -/// @param certPath path to SSL certificate file -/// @param keyPath path to SSL private key file -/// @param certChainPath path to SSL intermediate CA certificate(s) file +/** + * @brief add a grpc address, port and TLS with server cert chain to config + * + * This is intended for use with envconfig, for tests that require a grpc + * server with TLS enabled and intermediate CA certificates. + * + * @param cfg config instance to be modified + * @param certPath path to SSL certificate file + * @param keyPath path to SSL private key file + * @param certChainPath path to SSL intermediate CA certificate(s) file + */ std::unique_ptr addGrpcConfigWithTLSAndCertChain( std::unique_ptr, diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index 728b2e3643..68737c2e35 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -13,7 +13,9 @@ #include #include -/** Escrow operations. */ +/** + * Escrow operations. + */ namespace xrpl::test::jtx::escrow { json::Value @@ -70,10 +72,14 @@ std::array const kCb3 = { 0x3F, 0xA6, 0x3B, 0x1B, 0x60, 0x6F, 0x2D, 0x26, 0x4A, 0x2D, 0x85, 0x7B, 0xE8, 0xA0, 0x9C, 0x1D, 0xFD, 0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}}; -/** Set the "FinishAfter" time tag on a JTx */ +/** + * Set the "FinishAfter" time tag on a JTx + */ auto const kFinishTime = JTxFieldWrapper(sfFinishAfter); -/** Set the "CancelAfter" time tag on a JTx */ +/** + * Set the "CancelAfter" time tag on a JTx + */ auto const kCancelTime = JTxFieldWrapper(sfCancelAfter); auto const kCondition = JTxFieldWrapper(sfCondition); diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index ad3002dc75..3754036479 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -13,7 +13,9 @@ namespace xrpl::test::jtx { -/** Set the fee on a JTx. */ +/** + * Set the fee on a JTx. + */ class Fee { private: diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index 83470f9a1b..5bbe3c8d12 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -97,18 +97,24 @@ namespace test::jtx { // JSON generators -/** Add and/or remove flag. */ +/** + * Add and/or remove flag. + */ json::Value fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); -/** Remove account flag. */ +/** + * Remove account flag. + */ inline json::Value fclear(Account const& account, std::uint32_t off) { return fset(account, 0, off); } -/** Match set account flags */ +/** + * Match set account flags + */ class Flags : private xrpl::detail::FlagsHelper { private: @@ -124,7 +130,9 @@ public: operator()(Env& env) const; }; -/** Match clear account flags */ +/** + * Match clear account flags + */ class Nflags : private xrpl::detail::FlagsHelper { private: diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index 5c815d4226..8effce288e 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -326,13 +326,10 @@ AMM::expectAuctionSlot(std::vector const& authAccounts) const { return expectAuctionSlot( [&](std::uint32_t, std::optional, IOUAmount const&, STArray const& accounts) { - for (auto const& account : accounts) - { - if (std::ranges::find(authAccounts, account.getAccountID(sfAccount)) == - authAccounts.end()) - return false; - } - return true; + return std::ranges::all_of(accounts, [&](auto const& account) { + return std::ranges::find(authAccounts, account.getAccountID(sfAccount)) != + authAccounts.end(); + }); }); } diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 46c5e44bd3..3b4aae20e2 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -197,7 +197,7 @@ AMMTestBase::testAMM(std::function const& cb, TestAM XRPAmount AMMTest::reserve(jtx::Env& env, std::uint32_t count) { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); } XRPAmount diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index db45deb6de..3f6aca9fcb 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -271,6 +271,33 @@ Env::ownerCount(Account const& account) const return sle->getFieldU32(sfOwnerCount); } +std::uint32_t +Env::sponsoredOwnerCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoredOwnerCount); +} + +std::uint32_t +Env::sponsoringOwnerCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoringOwnerCount); +} + +std::uint32_t +Env::sponsoringAccountCount(Account const& account) const +{ + auto const sle = le(account); + if (!sle) + Throw("missing account root"); + return sle->getFieldU32(sfSponsoringAccountCount); +} + std::uint32_t Env::seq(Account const& account) const { diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 95c8a68436..4d3869b4f9 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -94,6 +94,24 @@ ownerCount(Env const& env, Account const& account) return env.ownerCount(account); } +std::uint32_t +sponsoredOwnerCount(Env const& env, Account const& account) +{ + return env.sponsoredOwnerCount(account); +} + +std::uint32_t +sponsoringOwnerCount(Env const& env, Account const& account) +{ + return env.sponsoringOwnerCount(account); +} + +std::uint32_t +sponsoringAccountCount(Env const& env, Account const& account) +{ + return env.sponsoringAccountCount(account); +} + /* Path finding */ /******************************************************************************/ void diff --git a/src/test/jtx/impl/dids.cpp b/src/test/jtx/impl/dids.cpp index d82250ca34..9c27ea21e3 100644 --- a/src/test/jtx/impl/dids.cpp +++ b/src/test/jtx/impl/dids.cpp @@ -7,7 +7,9 @@ #include #include -/** DID operations. */ +/** + * DID operations. + */ namespace xrpl::test::jtx::did { json::Value diff --git a/src/test/jtx/impl/directory.cpp b/src/test/jtx/impl/directory.cpp index 9fb06e25df..5eaf0be196 100644 --- a/src/test/jtx/impl/directory.cpp +++ b/src/test/jtx/impl/directory.cpp @@ -18,7 +18,9 @@ #include #include -/** Directory operations. */ +/** + * Directory operations. + */ namespace xrpl::test::jtx::directory { auto diff --git a/src/test/jtx/impl/escrow.cpp b/src/test/jtx/impl/escrow.cpp index 007f492849..61c260a5d0 100644 --- a/src/test/jtx/impl/escrow.cpp +++ b/src/test/jtx/impl/escrow.cpp @@ -14,7 +14,9 @@ #include -/** Escrow operations. */ +/** + * Escrow operations. + */ namespace xrpl::test::jtx::escrow { json::Value diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 66c5a4f27d..d948042bda 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -64,7 +64,8 @@ Msig::operator()(Env& env, JTx& jt) const { auto const mySigners = signers; auto callback = [subField = subField, mySigners, &env](Env&, JTx& jtx) { - // Where to put the signature. Supports sfCounterPartySignature. + // Where to put the signature. Supports sfCounterPartySignature and + // sfSponsorSignature. auto& sigObject = subField ? jtx[*subField] : jtx.jv; // The signing pub key is only required at the top level. diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 2ff93757f0..ed10ca6930 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -44,6 +44,24 @@ Owners::operator()(Env& env) const env.test.expect(env.le(account_)->getFieldU32(sfOwnerCount) == value_); } +void +SponsoredOwners::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoredOwnerCount) == value_); +} + +void +SponsoringOwners::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoringOwnerCount) == value_); +} + +void +SponsoringAccountCount::operator()(Env& env) const +{ + env.test.expect(env.le(account_)->getFieldU32(sfSponsoringAccountCount) == value_); +} + } // namespace test::jtx } // namespace xrpl diff --git a/src/test/jtx/impl/sig.cpp b/src/test/jtx/impl/sig.cpp index 7140fad3de..e0123073b1 100644 --- a/src/test/jtx/impl/sig.cpp +++ b/src/test/jtx/impl/sig.cpp @@ -18,7 +18,7 @@ Sig::operator()(Env&, JTx& jt) const // VFALCO Inefficient pre-C++14 auto const account = *account_; auto callback = [subField = subField_, account](Env&, JTx& jtx) { - // Where to put the signature. Supports sfCounterPartySignature. + // Where to put the signature. Supports sfCounterPartySignature and sfSponsorSignature. auto& sigObject = subField ? jtx[*subField] : jtx.jv; jtx::sign(jtx.jv, account, sigObject); diff --git a/src/test/jtx/impl/sponsor.cpp b/src/test/jtx/impl/sponsor.cpp new file mode 100644 index 0000000000..cdf68800f5 --- /dev/null +++ b/src/test/jtx/impl/sponsor.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::test::jtx::sponsor { + +json::Value +set(jtx::Account const& account, + uint32_t flags, + std::optional const reserveCount, + std::optional const feeAmount, + std::optional const maxFee) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = flags; + if (reserveCount) + jv[sfRemainingOwnerCount.jsonName] = *reserveCount; + if (feeAmount) + jv[sfFeeAmount.jsonName] = feeAmount->getJson(JsonOptions::Values::None); + if (maxFee) + jv[sfMaxFee.jsonName] = maxFee->getJson(JsonOptions::Values::None); + return jv; +} + +json::Value +del(jtx::Account const& account) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = tfDeleteObject; + return jv; +} + +json::Value +transfer(jtx::Account const& account, uint32_t flags, std::optional const& index) +{ + json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipTransfer; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = flags; + if (index) + jv[sfObjectID.jsonName] = to_string(*index); + return jv; +} + +void +CounterpartySponsor::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfCounterpartySponsor.jsonName] = sponsor_.human(); +} + +void +SponseeAcc::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfSponsee.jsonName] = sponsee_.human(); +} + +void +As::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfSponsor.jsonName] = sponsor_.human(); + jt.jv[sfSponsorFlags.jsonName] = flags_; +} + +json::Value +ledgerEntry(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee) +{ + json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::sponsorship][jss::sponsor] = sponsor.human(); + jvParams[jss::sponsorship][jss::sponsee] = sponsee.human(); + return env.rpc("json", "ledger_entry", to_string(jvParams)); +} + +STAmount +sponsorshipFeeBalance(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee) +{ + return env.le(keylet::sponsorship(sponsor, sponsee))->getFieldAmount(sfFeeAmount).xrp(); +} + +} // namespace xrpl::test::jtx::sponsor diff --git a/src/test/jtx/jtx_json.h b/src/test/jtx/jtx_json.h index b22d4c30e1..1ef5c155a8 100644 --- a/src/test/jtx/jtx_json.h +++ b/src/test/jtx/jtx_json.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Inject raw JSON. */ +/** + * Inject raw JSON. + */ class Json { private: diff --git a/src/test/jtx/ledgerStateFix.h b/src/test/jtx/ledgerStateFix.h index dd1ac19f04..2fe5c8accc 100644 --- a/src/test/jtx/ledgerStateFix.h +++ b/src/test/jtx/ledgerStateFix.h @@ -5,14 +5,20 @@ #include #include -/** LedgerStateFix operations. */ +/** + * LedgerStateFix operations. + */ namespace xrpl::test::jtx::ledgerStateFix { -/** Repair the links in an NFToken directory. */ +/** + * Repair the links in an NFToken directory. + */ json::Value nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); -/** Repair sfExchangeRate on a book directory's first page. */ +/** + * Repair sfExchangeRate on a book directory's first page. + */ json::Value bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir); diff --git a/src/test/jtx/memo.h b/src/test/jtx/memo.h index cea4923d38..4e342816ca 100644 --- a/src/test/jtx/memo.h +++ b/src/test/jtx/memo.h @@ -8,11 +8,12 @@ namespace xrpl::test::jtx { -/** Add a memo to a JTx. - - If a memo already exists, the new - memo is appended to the array. -*/ +/** + * Add a memo to a JTx. + * + * If a memo already exists, the new + * memo is appended to the array. + */ class Memo { private: diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 1a7fe94785..c6532ab14a 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -65,7 +65,9 @@ gMakeZeroBuffer(std::size_t size) return b; } -/** @brief Test helper that checks MPT flag settings after creation. */ +/** + * @brief Test helper that checks MPT flag settings after creation. + */ class MptFlags { private: @@ -86,7 +88,9 @@ public: operator()(Env& env) const; }; -/** @brief Test helper that checks MPT issuance or holder balances. */ +/** + * @brief Test helper that checks MPT issuance or holder balances. + */ class MptBalance { private: @@ -104,7 +108,9 @@ public: operator()(Env& env) const; }; -/** @brief Test helper that accepts any condition supplied by a callback. */ +/** + * @brief Test helper that accepts any condition supplied by a callback. + */ class RequireAny { private: @@ -121,7 +127,9 @@ public: using Holders = std::vector; -/** @brief Arguments for building an MPTokenIssuanceCreate test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceCreate test transaction. + */ struct MPTCreate { static inline std::vector allHolders = {}; @@ -145,7 +153,9 @@ struct MPTCreate std::optional err = std::nullopt; }; -/** @brief Arguments for initializing funded MPT test accounts and issuance. */ +/** + * @brief Arguments for initializing funded MPT test accounts and issuance. + */ struct MPTInit { // Default-initialized so designated-initializer call sites that omit @@ -161,7 +171,9 @@ struct MPTInit }; static MPTInit const kMptInitNoFund{.fund = false}; -/** @brief Full constructor arguments for MPTTester initialization. */ +/** + * @brief Full constructor arguments for MPTTester initialization. + */ struct MPTInitDef { Env& env; @@ -179,7 +191,9 @@ struct MPTInitDef std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenIssuanceDestroy test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceDestroy test transaction. + */ struct MPTDestroy { std::optional issuer = std::nullopt; @@ -190,7 +204,9 @@ struct MPTDestroy std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenAuthorize test transaction. */ +/** + * @brief Arguments for building an MPTokenAuthorize test transaction. + */ struct MPTAuthorize { std::optional account = std::nullopt; @@ -202,7 +218,9 @@ struct MPTAuthorize std::optional err = std::nullopt; }; -/** @brief Arguments for building an MPTokenIssuanceSet test transaction. */ +/** + * @brief Arguments for building an MPTokenIssuanceSet test transaction. + */ struct MPTSet { std::optional account = std::nullopt; @@ -222,7 +240,9 @@ struct MPTSet std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTConvert test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTConvert test transaction. + */ struct MPTConvert { std::optional account = std::nullopt; @@ -250,7 +270,9 @@ struct MPTConvert std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTMergeInbox test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTMergeInbox test transaction. + */ struct MPTMergeInbox { std::optional account = std::nullopt; @@ -264,7 +286,9 @@ struct MPTMergeInbox std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTSend test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTSend test transaction. + */ struct MPTConfidentialSend { std::optional account = std::nullopt; @@ -293,7 +317,9 @@ struct MPTConfidentialSend std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTConvertBack test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTConvertBack test transaction. + */ struct MPTConvertBack { std::optional account = std::nullopt; @@ -316,7 +342,9 @@ struct MPTConvertBack std::optional err = std::nullopt; }; -/** @brief Arguments for building a ConfidentialMPTClawback test transaction. */ +/** + * @brief Arguments for building a ConfidentialMPTClawback test transaction. + */ struct MPTConfidentialClawback { std::optional account = std::nullopt; @@ -339,16 +367,24 @@ struct MPTConfidentialClawback */ struct PedersenProofParams { - /** @brief The Pedersen commitment used by the proof. */ + /** + * @brief The Pedersen commitment used by the proof. + */ Buffer const pedersenCommitment; - /** @brief Either the spending balance or the value being transferred. */ + /** + * @brief Either the spending balance or the value being transferred. + */ uint64_t const amt; - /** @brief The encrypted amount linked to the Pedersen commitment. */ + /** + * @brief The encrypted amount linked to the Pedersen commitment. + */ Buffer const encryptedAmt; - /** @brief The blinding factor used to create the Pedersen commitment. */ + /** + * @brief The blinding factor used to create the Pedersen commitment. + */ Buffer const blindingFactor; }; @@ -365,13 +401,19 @@ struct PedersenProofParams */ struct ConfidentialSendChainState { - /** @brief Decrypted spending balance after the previous send. */ + /** + * @brief Decrypted spending balance after the previous send. + */ std::uint64_t spending; - /** @brief Encrypted spending balance after the previous send. */ + /** + * @brief Encrypted spending balance after the previous send. + */ Buffer encSpending; - /** @brief sfConfidentialBalanceVersion after the previous send. */ + /** + * @brief sfConfidentialBalanceVersion after the previous send. + */ std::uint32_t version; }; diff --git a/src/test/jtx/multisign.h b/src/test/jtx/multisign.h index c90e28537a..65e39f971d 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -20,7 +20,9 @@ namespace xrpl::test::jtx { -/** A signer in a SignerList */ +/** + * A signer in a SignerList + */ struct Signer { std::uint32_t weight; @@ -36,24 +38,31 @@ struct Signer json::Value signers(Account const& account, std::uint32_t quorum, std::vector const& v); -/** Remove a signer list. */ +/** + * Remove a signer list. + */ json::Value signers(Account const& account, NoneT); //------------------------------------------------------------------------------ -/** Set a multisignature on a JTx. */ +/** + * Set a multisignature on a JTx. + */ class Msig { public: std::vector signers; - /** Alternative transaction object field in which to place the signer list. + /** + * Alternative transaction object field in which to place the signer list. * * subField is only supported if an account_ is provided as well. */ SField const* const subField = nullptr; - /// Used solely as a convenience placeholder for ctors that do _not_ specify - /// a subfield. + /** + * Used solely as a convenience placeholder for ctors that do _not_ specify + * a subfield. + */ static constexpr SField const* kTopLevel = nullptr; Msig(SField const* subField, std::vector signers) @@ -103,7 +112,9 @@ public: //------------------------------------------------------------------------------ -/** The number of signer lists matches. */ +/** + * The number of signer lists matches. + */ using siglists = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/noop.h b/src/test/jtx/noop.h index c38dfdde25..2ca97a1916 100644 --- a/src/test/jtx/noop.h +++ b/src/test/jtx/noop.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** The null transaction. */ +/** + * The null transaction. + */ inline json::Value noop(Account const& account) { diff --git a/src/test/jtx/offer.h b/src/test/jtx/offer.h index f3e7277933..140e16da83 100644 --- a/src/test/jtx/offer.h +++ b/src/test/jtx/offer.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Create an offer. */ +/** + * Create an offer. + */ json::Value offer( Account const& account, @@ -17,7 +19,9 @@ offer( STAmount const& takerGets, std::uint32_t flags = 0); -/** Cancel an offer. */ +/** + * Cancel an offer. + */ json::Value offerCancel(Account const& account, std::uint32_t offerSeq); diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index 0fb38b407c..1e12603429 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -48,7 +48,9 @@ public: } }; -/** Match the number of items in the account's owner directory */ +/** + * Match the number of items in the account's owner directory + */ class Owners { private: @@ -64,13 +66,79 @@ public: operator()(Env& env) const; }; -/** Match the number of trust lines in the account's owner directory */ +/** + * Match the account's SponsoredOwnerCount field: the number of owned + * objects whose reserve is sponsored by another account. + */ +class SponsoredOwners +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoredOwners(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the account's SponsoringOwnerCount field: the number of objects + * (owned by other accounts) whose reserve this account sponsors. + */ +class SponsoringOwners +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoringOwners(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the account's SponsoringAccountCount field: the number of accounts + * whose base reserve this account sponsors. + */ +class SponsoringAccountCount +{ +private: + Account account_; + std::uint32_t value_; + +public: + SponsoringAccountCount(Account account, std::uint32_t value) + : account_(std::move(account)), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** + * Match the number of trust lines in the account's owner directory + */ using lines = OwnerCount; -/** Match the number of offers in the account's owner directory */ +/** + * Match the number of offers in the account's owner directory + */ using offers = OwnerCount; -/** Match the number of MPToken in the account's owner directory */ +/** + * Match the number of MPToken in the account's owner directory + */ using mptokens = OwnerCount; } // namespace test::jtx diff --git a/src/test/jtx/paths.h b/src/test/jtx/paths.h index b141300a66..07d0117f8f 100644 --- a/src/test/jtx/paths.h +++ b/src/test/jtx/paths.h @@ -16,7 +16,9 @@ class STPath; namespace test::jtx { -/** Set Paths, SendMax on a JTx. */ +/** + * Set Paths, SendMax on a JTx. + */ class Paths { private: @@ -36,10 +38,11 @@ public: //------------------------------------------------------------------------------ -/** Add a path. - - If no paths are present, a new one is created. -*/ +/** + * Add a path. + * + * If no paths are present, a new one is created. + */ class Path { private: diff --git a/src/test/jtx/pay.h b/src/test/jtx/pay.h index fa193e7b02..04ccf27f70 100644 --- a/src/test/jtx/pay.h +++ b/src/test/jtx/pay.h @@ -8,7 +8,9 @@ namespace xrpl::test::jtx { -/** Create a payment. */ +/** + * Create a payment. + */ json::Value pay(AccountID const& account, AccountID const& to, AnyAmount amount); json::Value diff --git a/src/test/jtx/prop.h b/src/test/jtx/prop.h index e85751e4c9..24dca21ca0 100644 --- a/src/test/jtx/prop.h +++ b/src/test/jtx/prop.h @@ -8,7 +8,9 @@ namespace xrpl::test::jtx { -/** Set a property on a JTx. */ +/** + * Set a property on a JTx. + */ template struct Prop { diff --git a/src/test/jtx/quality.h b/src/test/jtx/quality.h index c7896fadf9..a15d319b40 100644 --- a/src/test/jtx/quality.h +++ b/src/test/jtx/quality.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Sets the literal QualityIn on a trust JTx. */ +/** + * Sets the literal QualityIn on a trust JTx. + */ class QualityIn { private: @@ -22,7 +24,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the QualityIn on a trust JTx. */ +/** + * Sets the QualityIn on a trust JTx. + */ class QualityInPercent { private: @@ -35,7 +39,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the literal QualityOut on a trust JTx. */ +/** + * Sets the literal QualityOut on a trust JTx. + */ class QualityOut { private: @@ -50,7 +56,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the QualityOut on a trust JTx as a percentage. */ +/** + * Sets the QualityOut on a trust JTx as a percentage. + */ class QualityOutPercent { private: diff --git a/src/test/jtx/rate.h b/src/test/jtx/rate.h index f76c2dd538..f8caf39fd8 100644 --- a/src/test/jtx/rate.h +++ b/src/test/jtx/rate.h @@ -6,7 +6,9 @@ namespace xrpl::test::jtx { -/** Set a transfer rate. */ +/** + * Set a transfer rate. + */ json::Value rate(Account const& account, double multiplier); diff --git a/src/test/jtx/regkey.h b/src/test/jtx/regkey.h index 676633b2b7..8aa5bd9709 100644 --- a/src/test/jtx/regkey.h +++ b/src/test/jtx/regkey.h @@ -7,11 +7,15 @@ namespace xrpl::test::jtx { -/** Disable the regular key. */ +/** + * Disable the regular key. + */ json::Value regkey(Account const& account, DisabledT); -/** Set a regular key. */ +/** + * Set a regular key. + */ json::Value regkey(Account const& account, Account const& signer); diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index 19e161b938..eba20b613c 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -23,7 +23,9 @@ requireArgs(test::jtx::requires_t& vec, Cond const& cond, Args const&... args) namespace test::jtx { -/** Compose many condition functors into one */ +/** + * Compose many condition functors into one + */ template require_t required(Args const&... args) @@ -36,12 +38,13 @@ required(Args const&... args) }; } -/** Check a set of conditions. - - The conditions are checked after a JTx is - applied, and only if the resulting TER - matches the expected TER. -*/ +/** + * Check a set of conditions. + * + * The conditions are checked after a JTx is + * applied, and only if the resulting TER + * matches the expected TER. + */ class Require { private: diff --git a/src/test/jtx/rpc.h b/src/test/jtx/rpc.h index bc05450909..9bd99c15f8 100644 --- a/src/test/jtx/rpc.h +++ b/src/test/jtx/rpc.h @@ -12,9 +12,10 @@ namespace xrpl::test::jtx { -/** Set the expected result code for a JTx - The test will fail if the code doesn't match. -*/ +/** + * Set the expected result code for a JTx + * The test will fail if the code doesn't match. + */ class Rpc { private: @@ -24,13 +25,17 @@ private: std::optional errorException_; public: - /// If there's an error code, we expect an error message + /** + * If there's an error code, we expect an error message + */ explicit Rpc(ErrorCodeI code, std::optional m = {}) : code_(code), errorMessage_(std::move(m)) { } - /// If there is not a code, we expect an exception message + /** + * If there is not a code, we expect an exception message + */ explicit Rpc(std::string error, std::optional exceptionMessage = {}) : error_(error), errorException_(std::move(exceptionMessage)) { diff --git a/src/test/jtx/sendmax.h b/src/test/jtx/sendmax.h index 1241d76b91..672959d1aa 100644 --- a/src/test/jtx/sendmax.h +++ b/src/test/jtx/sendmax.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Sets the SendMax on a JTx. */ +/** + * Sets the SendMax on a JTx. + */ class Sendmax { private: diff --git a/src/test/jtx/seq.h b/src/test/jtx/seq.h index 956c0a77e8..323fb33bb2 100644 --- a/src/test/jtx/seq.h +++ b/src/test/jtx/seq.h @@ -9,7 +9,9 @@ namespace xrpl::test::jtx { -/** Set the sequence number on a JTx. */ +/** + * Set the sequence number on a JTx. + */ struct Seq { private: diff --git a/src/test/jtx/sig.h b/src/test/jtx/sig.h index 76f0a34dff..1f20dcf942 100644 --- a/src/test/jtx/sig.h +++ b/src/test/jtx/sig.h @@ -11,26 +11,31 @@ namespace xrpl::test::jtx { -/** Set the regular signature on a JTx. - @note For multisign, use msig. -*/ +/** + * Set the regular signature on a JTx. + * @note For multisign, use msig. + */ class Sig { private: bool manual_ = true; - /** Alternative transaction object field in which to place the signature. + /** + * Alternative transaction object field in which to place the signature. * * subField is only supported if an account_ is provided as well. */ SField const* const subField_ = nullptr; - /** Account that will generate the signature. + /** + * Account that will generate the signature. * * If not provided, no signature will be added by this helper. See also * Env::autofillSig. */ std::optional account_; - /// Used solely as a convenience placeholder for ctors that do _not_ specify - /// a subfield. + /** + * Used solely as a convenience placeholder for ctors that do _not_ specify + * a subfield. + */ static constexpr SField const* kTopLevel = nullptr; public: diff --git a/src/test/jtx/sponsor.h b/src/test/jtx/sponsor.h new file mode 100644 index 0000000000..43d55d7246 --- /dev/null +++ b/src/test/jtx/sponsor.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test::jtx::sponsor { + +json::Value +set(jtx::Account const& account, + std::uint32_t flags, + std::optional const reserveCount = std::nullopt, + std::optional const feeAmount = std::nullopt, + std::optional const maxFee = std::nullopt); + +inline json::Value +set_fee( + jtx::Account const& account, + std::uint32_t flags, + STAmount feeAmount, + std::optional maxFee = std::nullopt) +{ + return set(account, flags, std::nullopt, std::move(feeAmount), std::move(maxFee)); +} + +inline json::Value +set_reserve(jtx::Account const& account, std::uint32_t flags, std::uint32_t reserveCount) +{ + return set(account, flags, reserveCount); +} + +inline json::Value +set_max_fee(jtx::Account const& account, std::uint32_t flags, STAmount maxFee) +{ + return set(account, flags, std::nullopt, std::nullopt, std::move(maxFee)); +} + +json::Value +del(jtx::Account const& account); + +json::Value +transfer( + jtx::Account const& account, + uint32_t flags, + std::optional const& index = std::nullopt); + +struct CounterpartySponsor +{ +private: + jtx::Account sponsor_; + +public: + CounterpartySponsor(jtx::Account account) : sponsor_(std::move(account)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct SponseeAcc +{ +private: + jtx::Account sponsee_; + +public: + SponseeAcc(jtx::Account account) : sponsee_(std::move(account)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct As +{ +private: + jtx::Account sponsor_; + std::uint32_t flags_; + +public: + As(jtx::Account account, std::uint32_t flags = 0) : sponsor_(std::move(account)), flags_(flags) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +json::Value +ledgerEntry(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee); + +STAmount +sponsorshipFeeBalance(jtx::Env& env, jtx::Account const& sponsor, jtx::Account const& sponsee); + +} // namespace xrpl::test::jtx::sponsor diff --git a/src/test/jtx/tag.h b/src/test/jtx/tag.h index 77870367a9..b9a10e55f4 100644 --- a/src/test/jtx/tag.h +++ b/src/test/jtx/tag.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the destination tag on a JTx*/ +/** + * Set the destination tag on a JTx + */ struct Dtag { private: @@ -22,7 +24,9 @@ public: operator()(Env&, JTx& jt) const; }; -/** Set the source tag on a JTx*/ +/** + * Set the source tag on a JTx + */ struct Stag { private: diff --git a/src/test/jtx/tags.h b/src/test/jtx/tags.h index 4f4afbd1fa..1915c57b5c 100644 --- a/src/test/jtx/tags.h +++ b/src/test/jtx/tags.h @@ -20,7 +20,9 @@ struct DisabledT }; static DisabledT const kDisabled; -/** Used for Fee() calls that use an owner reserve increment */ +/** + * Used for Fee() calls that use an owner reserve increment + */ struct IncrementT { IncrementT() = default; diff --git a/src/test/jtx/ter.h b/src/test/jtx/ter.h index 880711dca0..0ac65e62ee 100644 --- a/src/test/jtx/ter.h +++ b/src/test/jtx/ter.h @@ -10,9 +10,10 @@ namespace xrpl::test::jtx { -/** Set the expected result code for a JTx - The test will fail if the code doesn't match. -*/ +/** + * Set the expected result code for a JTx + * The test will fail if the code doesn't match. + */ class Ter { private: diff --git a/src/test/jtx/ticket.h b/src/test/jtx/ticket.h index 1035be7674..9c9a698eaa 100644 --- a/src/test/jtx/ticket.h +++ b/src/test/jtx/ticket.h @@ -18,14 +18,20 @@ namespace xrpl::test::jtx { without changing the base declarations. */ -/** Ticket operations */ +/** + * Ticket operations + */ namespace ticket { -/** Create one of more tickets */ +/** + * Create one of more tickets + */ json::Value create(Account const& account, std::uint32_t count); -/** Set a ticket sequence on a JTx. */ +/** + * Set a ticket sequence on a JTx. + */ class Use { private: @@ -42,7 +48,9 @@ public: } // namespace ticket -/** Match the number of tickets on the account. */ +/** + * Match the number of tickets on the account. + */ using tickets = OwnerCount; } // namespace xrpl::test::jtx diff --git a/src/test/jtx/token.h b/src/test/jtx/token.h index 97f968fdfc..e94cfd2e12 100644 --- a/src/test/jtx/token.h +++ b/src/test/jtx/token.h @@ -16,11 +16,15 @@ namespace xrpl::test::jtx::token { -/** Mint an NFToken. */ +/** + * Mint an NFToken. + */ json::Value mint(jtx::Account const& account, std::uint32_t tokenTaxon = 0); -/** Sets the optional TransferFee on an NFTokenMint. */ +/** + * Sets the optional TransferFee on an NFTokenMint. + */ class XferFee { private: @@ -35,7 +39,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Issuer on an NFTokenMint. */ +/** + * Sets the optional Issuer on an NFTokenMint. + */ class Issuer { private: @@ -50,7 +56,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional URI on an NFTokenMint. */ +/** + * Sets the optional URI on an NFTokenMint. + */ class Uri { private: @@ -65,7 +73,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional amount field on an NFTokenMint. */ +/** + * Sets the optional amount field on an NFTokenMint. + */ class Amount { private: @@ -80,7 +90,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Get the next NFTokenID that will be issued. */ +/** + * Get the next NFTokenID that will be issued. + */ uint256 getNextID( jtx::Env const& env, @@ -89,7 +101,9 @@ getNextID( std::uint16_t flags = 0, std::uint16_t xferFee = 0); -/** Get the NFTokenID for a particular nftSequence. */ +/** + * Get the NFTokenID for a particular nftSequence. + */ uint256 getID( jtx::Env const& env, @@ -99,15 +113,21 @@ getID( std::uint16_t flags = 0, std::uint16_t xferFee = 0); -/** Burn an NFToken. */ +/** + * Burn an NFToken. + */ json::Value burn(jtx::Account const& account, uint256 const& nftokenID); -/** Create an NFTokenOffer. */ +/** + * Create an NFTokenOffer. + */ json::Value createOffer(jtx::Account const& account, uint256 const& nftokenID, STAmount const& amount); -/** Sets the optional Owner on an NFTokenOffer. */ +/** + * Sets the optional Owner on an NFTokenOffer. + */ class Owner { private: @@ -122,7 +142,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Expiration field on an NFTokenOffer. */ +/** + * Sets the optional Expiration field on an NFTokenOffer. + */ class Expiration { private: @@ -137,7 +159,9 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Sets the optional Destination field on an NFTokenOffer. */ +/** + * Sets the optional Destination field on an NFTokenOffer. + */ class Destination { private: @@ -152,14 +176,18 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Cancel NFTokenOffers. */ +/** + * Cancel NFTokenOffers. + */ json::Value cancelOffer(jtx::Account const& account, std::initializer_list const& nftokenOffers = {}); json::Value cancelOffer(jtx::Account const& account, std::vector const& nftokenOffers); -/** Sets the optional RootIndex field when canceling NFTokenOffers. */ +/** + * Sets the optional RootIndex field when canceling NFTokenOffers. + */ class RootIndex { private: @@ -174,22 +202,30 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Accept an NFToken buy offer. */ +/** + * Accept an NFToken buy offer. + */ json::Value acceptBuyOffer(jtx::Account const& account, uint256 const& offerIndex); -/** Accept an NFToken sell offer. */ +/** + * Accept an NFToken sell offer. + */ json::Value acceptSellOffer(jtx::Account const& account, uint256 const& offerIndex); -/** Broker two NFToken offers. */ +/** + * Broker two NFToken offers. + */ json::Value brokerOffers( jtx::Account const& account, uint256 const& buyOfferIndex, uint256 const& sellOfferIndex); -/** Sets the optional NFTokenBrokerFee field in a brokerOffer transaction. */ +/** + * Sets the optional NFTokenBrokerFee field in a brokerOffer transaction. + */ class BrokerFee { private: @@ -204,15 +240,21 @@ public: operator()(Env&, JTx& jtx) const; }; -/** Set the authorized minter on an account root. */ +/** + * Set the authorized minter on an account root. + */ json::Value setMinter(jtx::Account const& account, jtx::Account const& minter); -/** Clear any authorized minter from an account root. */ +/** + * Clear any authorized minter from an account root. + */ json::Value clearMinter(jtx::Account const& account); -/** Modify an NFToken. */ +/** + * Modify an NFToken. + */ json::Value modify(jtx::Account const& account, uint256 const& nftokenID); diff --git a/src/test/jtx/trust.h b/src/test/jtx/trust.h index 034f80fcec..235780c825 100644 --- a/src/test/jtx/trust.h +++ b/src/test/jtx/trust.h @@ -10,11 +10,15 @@ namespace xrpl::test::jtx { -/** Modify a trust line. */ +/** + * Modify a trust line. + */ json::Value trust(Account const& account, STAmount const& amount, std::uint32_t flags = 0); -/** Change flags on a trust line. */ +/** + * Change flags on a trust line. + */ json::Value trust(Account const& account, STAmount const& amount, Account const& peer, std::uint32_t flags); diff --git a/src/test/jtx/txflags.h b/src/test/jtx/txflags.h index 7f5b31b2ac..c940681d82 100644 --- a/src/test/jtx/txflags.h +++ b/src/test/jtx/txflags.h @@ -7,7 +7,9 @@ namespace xrpl::test::jtx { -/** Set the flags on a JTx. */ +/** + * Set the flags on a JTx. + */ class Txflags { private: diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index f1cf3f7ae8..289afec8b3 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -13,7 +13,9 @@ namespace xrpl::test::jtx { -/** Thrown when parse fails. */ +/** + * Thrown when parse fails. + */ struct ParseError : std::logic_error { template @@ -22,35 +24,44 @@ struct ParseError : std::logic_error } }; -/** Convert JSON to STObject. - This throws on failure, the JSON must be correct. - @note Testing malformed JSON is beyond the scope of - this set of unit test routines. -*/ +/** + * Convert JSON to STObject. + * This throws on failure, the JSON must be correct. + * @note Testing malformed JSON is beyond the scope of + * this set of unit test routines. + */ STObject parse(json::Value const& jv); -/** Sign automatically into a specific Json field of the jv object. - @note This only works on accounts with multi-signing off. -*/ +/** + * Sign automatically into a specific Json field of the jv object. + * @note This only works on accounts with multi-signing off. + */ void sign(json::Value& jv, Account const& account, json::Value& sigObject); -/** Sign automatically. - @note This only works on accounts with multi-signing off. -*/ +/** + * Sign automatically. + * @note This only works on accounts with multi-signing off. + */ void sign(json::Value& jv, Account const& account); -/** Set the fee automatically. */ +/** + * Set the fee automatically. + */ void fillFee(json::Value& jv, ReadView const& view); -/** Set the sequence number automatically. */ +/** + * Set the sequence number automatically. + */ void fillSeq(json::Value& jv, ReadView const& view); -/** Given an xrpld unit test rpc command, return the corresponding JSON. */ +/** + * Given an xrpld unit test rpc command, return the corresponding JSON. + */ json::Value cmdToJSONRPC(std::vector const& args, beast::Journal j, unsigned int apiVersion); } // namespace xrpl::test::jtx diff --git a/src/test/jtx/vault.h b/src/test/jtx/vault.h index 4e6b90fe1f..e72eae89b7 100644 --- a/src/test/jtx/vault.h +++ b/src/test/jtx/vault.h @@ -27,7 +27,9 @@ struct Vault std::nullopt; // NOLINT(readability-redundant-member-init) }; - /** Return a VaultCreate transaction and the Vault's expected keylet. */ + /** + * Return a VaultCreate transaction and the Vault's expected keylet. + */ [[nodiscard]] std::tuple create(CreateArgs const& args) const; diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index f59b75091a..75f7410f94 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -1,17 +1,23 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -19,7 +25,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -236,7 +244,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j); - BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS); + BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, {}, j) == tesSUCCESS); BEAST_EXPECT( accountHolds( pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) == @@ -330,7 +338,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite }; auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount { - return env.current()->fees().accountReserve(count); + return env.current()->fees().accountReserve(count, 1); }; Env env(*this, features); @@ -389,6 +397,217 @@ class PaymentSandbox_test : public beast::unit_test::Suite BEAST_EXPECT(balance.getIssuer() == usd.account.id()); } + void + testOwnerCountHook(FeatureBitset features) + { + // Test that PaymentSandbox::adjustOwnerCountHook and ownerCountHook + // correctly track and return the maximum owner counts during a payment. + testcase("ownerCountHook"); + + using namespace jtx; + Env env(*this, features); + Account const alice("alice"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), alice, sponsor); + env.close(); + + ApplyViewImpl av(&*env.current(), TapNone); + PaymentSandbox sb(&av); + + // Test basic owner count hook without sponsor + { + auto const aliceSle = sb.peek(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + + OwnerCounts const initial(aliceSle); + OwnerCounts updated = initial; + updated.owner = initial.owner + 2; + + // Simulate adjusting owner count + sb.adjustOwnerCountHook(alice, initial, updated); + + // ownerCountHook should return the max value + OwnerCounts const retrieved = sb.ownerCountHook(alice, initial); + BEAST_EXPECT(retrieved.owner == updated.owner); + BEAST_EXPECT(retrieved.sponsored == updated.sponsored); + BEAST_EXPECT(retrieved.sponsoring == updated.sponsoring); + } + + // Test owner count hook with sponsor-related counts + { + auto const sponsorSle = sb.peek(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSle); + + OwnerCounts const sponsorInitial(sponsorSle); + OwnerCounts sponsorUpdated = sponsorInitial; + sponsorUpdated.owner = sponsorInitial.owner + 1; + sponsorUpdated.sponsoring = sponsorInitial.sponsoring + 1; + + sb.adjustOwnerCountHook(sponsor, sponsorInitial, sponsorUpdated); + + OwnerCounts const sponsorRetrieved = sb.ownerCountHook(sponsor, sponsorInitial); + BEAST_EXPECT(sponsorRetrieved.owner == sponsorUpdated.owner); + BEAST_EXPECT(sponsorRetrieved.sponsoring == sponsorUpdated.sponsoring); + } + + // Test with stacked PaymentSandboxes + { + PaymentSandbox sb2(&sb); + + auto const aliceSle = sb2.peek(keylet::account(alice)); + OwnerCounts const current(aliceSle); + OwnerCounts further = current; + further.owner = current.owner + 3; + + sb2.adjustOwnerCountHook(alice, current, further); + + // The nested sandbox should see the max from both levels + OwnerCounts const retrieved = sb2.ownerCountHook(alice, OwnerCounts()); + BEAST_EXPECT(retrieved.owner >= further.owner); + } + + // Test that max logic works correctly + { + auto const aliceSle = sb.peek(keylet::account(alice)); + OwnerCounts const current(aliceSle); + OwnerCounts lower = current; + lower.owner = (current.owner > 0) ? current.owner - 1 : 0; + + // Adjusting to a lower value + sb.adjustOwnerCountHook(alice, current, lower); + + // Should still return the higher value seen previously + OwnerCounts const retrieved = sb.ownerCountHook(alice, OwnerCounts()); + BEAST_EXPECT(retrieved.owner >= lower.owner); + } + } + + void + testOwnerCountWithTransaction(FeatureBitset features) + { + // Test that owner count hooks work correctly during actual transactions. + // This verifies that when transactions modify owner counts (by creating + // or deleting ledger objects), the hooks properly track these changes. + testcase( + std::string("ownerCountWithTransaction") + + (features[featureSponsor] ? " with sponsor" : " without sponsor")); + + using namespace jtx; + + auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount { + return env.current()->fees().accountReserve(count, 1); + }; + + Env env(*this, features); + Account const gw("gw"); + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + auto const usd = gw["USD"]; + + // Fund accounts. Alice starts with exactly enough for base reserve + 2 objects + env.fund(XRP(10000), gw, bob, sponsor); + env.fund(reserve(env, 3) + XRP(100), alice); // Base + 2 objects + extra for fees + env.close(); + + // Verify initial state - no owner count + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(ownerCount(env, bob) == 0); + + // Create a trust line - this increases owner count + env(trust(alice, usd(1000))); + env.close(); + + // alice now has 1 object (owner count = 1) + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // Create an offer - this further increases owner count + env(trust(bob, usd(1000))); + env(pay(gw, alice, usd(100))); + env.close(); + + auto const aliceOfferSeq = env.seq(alice); // Capture the sequence before creating offer + env(offer(alice, usd(50), XRP(50))); + env.close(); + + // alice now has 2 objects (trust line + offer) + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // If sponsor feature is enabled, test sponsorship transfer + if (features[featureSponsor]) + { + auto const trustId = keylet::trustLine(alice, gw, usd.currency); + BEAST_EXPECT(env.le(trustId)); + + // Transfer sponsorship - sponsor now sponsors alice's trust line + env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + // alice still has 2 objects but 1 is sponsored + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + // sponsor's sponsoring count should increase + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + // Verify alice's available balance respects the reserve + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + + auto const aliceBalance = aliceSle->getFieldAmount(sfBalance); + // With sponsor, 1 object is sponsored so only 1 counts for reserve + auto const aliceReserve = reserve(env, features[featureSponsor] ? 1 : 2); + + // alice should have limited available balance after accounting for reserve + auto const available = aliceBalance.xrp() - aliceReserve; + if (features[featureSponsor]) + { + // With sponsor, alice has more available (1 sponsored object = less reserve) + BEAST_EXPECT(available > XRP(150)); + } + else + { + BEAST_EXPECT(available < XRP(150)); // Most of the balance is in reserve + } + + // Try to send nearly all balance - should fail due to reserve in both cases + auto const tooMuch = aliceBalance.xrp() - XRP(1); + env(pay(alice, bob, tooMuch), Ter(tecUNFUNDED_PAYMENT)); + env.close(); + + // Verify owner count hasn't changed + BEAST_EXPECT(ownerCount(env, alice) == 2); + + // Cancel the offer - this decreases owner count + env(offerCancel(alice, aliceOfferSeq)); + env.close(); + + // alice now has 1 object (just the trust line) + BEAST_EXPECT(ownerCount(env, alice) == 1); + + if (features[featureSponsor]) + { + // Verify sponsored count stayed the same (trust line is still sponsored) + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + + // Now alice should have more available balance (less reserve needed) + auto const aliceSle2 = env.le(keylet::account(alice)); + auto const aliceBalance2 = aliceSle2->getFieldAmount(sfBalance); + // With sponsor, trust line is still sponsored so 0 objects for reserve + // Without sponsor, 1 object for reserve + auto const aliceReserve2 = reserve(env, features[featureSponsor] ? 0 : 1); + auto const available2 = aliceBalance2.xrp() - aliceReserve2; + + // available2 should be greater than available (less reserve needed) + BEAST_EXPECT(available2 > available); + } + public: void run() override @@ -399,11 +618,16 @@ public: testTinyBalance(features); testReserve(features); testBalanceHook(features); + testOwnerCountHook(features); }; using namespace jtx; auto const sa = testableAmendments(); testAll(sa - featurePermissionedDEX); testAll(sa); + + // Test owner count with transactions + testOwnerCountWithTransaction(sa - featureSponsor); + testOwnerCountWithTransaction(sa); } }; diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index a1245ec963..235e76501f 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -20,13 +20,14 @@ namespace xrpl::NodeStore { -/** Binary function that satisfies the strict-weak-ordering requirement. - - This compares the hashes of both objects and returns true if - the first hash is considered to go before the second. - - @see std::sort -*/ +/** + * Binary function that satisfies the strict-weak-ordering requirement. + * + * This compares the hashes of both objects and returns true if + * the first hash is considered to go before the second. + * + * @see std::sort + */ struct LessThan { bool @@ -37,7 +38,9 @@ struct LessThan } }; -/** Returns `true` if objects are identical. */ +/** + * Returns `true` if objects are identical. + */ inline bool isSame(std::shared_ptr const& lhs, std::shared_ptr const& rhs) { diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index 1c296ca44c..1f282d9d7f 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -692,9 +692,6 @@ public: #if XRPL_ROCKSDB_AVAILABLE ";type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256," "file_size_mb=8,file_size_mult=2" -#endif -#if 0 - ";type=memory|path=NodeStore" #endif ; diff --git a/src/test/overlay/cluster_test.cpp b/src/test/overlay/cluster_test.cpp index 6c2114b7de..0a51f98594 100644 --- a/src/test/overlay/cluster_test.cpp +++ b/src/test/overlay/cluster_test.cpp @@ -150,7 +150,7 @@ public: { auto member = c->member(node); BEAST_EXPECT(static_cast(member)); - BEAST_EXPECT(member->compare(name) == 0); // NOLINT(bugprone-unchecked-optional-access) + BEAST_EXPECT(*member == name); // NOLINT(bugprone-unchecked-optional-access) } // Updating the name (non-empty doesn't go to empty) @@ -159,7 +159,7 @@ public: { auto member = c->member(node); BEAST_EXPECT(static_cast(member)); - BEAST_EXPECT(member->compare(name) == 0); // NOLINT(bugprone-unchecked-optional-access) + BEAST_EXPECT(*member == name); // NOLINT(bugprone-unchecked-optional-access) } // Updating the name (non-empty updates to new non-empty) @@ -168,8 +168,7 @@ public: { auto member = c->member(node); BEAST_EXPECT(static_cast(member)); - BEAST_EXPECT( - member->compare("test") == 0); // NOLINT(bugprone-unchecked-optional-access) + BEAST_EXPECT(*member == "test"); // NOLINT(bugprone-unchecked-optional-access) } } diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 433595522d..2f42313037 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -64,7 +64,8 @@ static constexpr std::uint32_t kMaxPeers = 10; static constexpr std::uint32_t kMaxValidators = 10; static constexpr std::uint32_t kMaxMessages = 200000; -/** Simulate two entities - peer directly connected to the server +/** + * Simulate two entities - peer directly connected to the server * (via squelch in PeerSim) and PeerImp (via Overlay) */ class PeerPartial : public Peer @@ -192,7 +193,9 @@ public: } }; -/** Manually advanced clock. */ +/** + * Manually advanced clock. + */ class ManualClock { public: @@ -238,7 +241,9 @@ private: inline static time_point kNow = time_point(seconds(0)); }; -/** Simulate server's OverlayImpl */ +/** + * Simulate server's OverlayImpl + */ class Overlay { public: @@ -260,7 +265,8 @@ public: class Validator; -/** Simulate link from a validator to a peer directly connected +/** + * Simulate link from a validator to a peer directly connected * to the server. */ class Link @@ -317,7 +323,9 @@ private: bool up_{true}; }; -/** Simulate Validator */ +/** + * Simulate Validator + */ class Validator { using Links = std::unordered_map; @@ -400,14 +408,18 @@ public: } } - /** Send to specific peers */ + /** + * Send to specific peers + */ void send(std::vector peers, SquelchCB f) { forLinks(peers, [&](Link& link, MessageSPtr m) { link.send(m, f); }); } - /** Send to all peers */ + /** + * Send to all peers + */ void send(SquelchCB f) { @@ -478,7 +490,9 @@ public: sid = 0; } - /** Local Peer (PeerImp) */ + /** + * Local Peer (PeerImp) + */ void onMessage(MessageSPtr const& m, SquelchCB f) override { @@ -491,7 +505,9 @@ public: {}, *validator, id(), f); // NOLINT(bugprone-unchecked-optional-access) } - /** Remote Peer (Directly connected Peer) */ + /** + * Remote Peer (Directly connected Peer) + */ void onMessage(protocol::TMSquelch const& squelch) override { @@ -836,19 +852,18 @@ public: } } - /** Is peer in Selected state in any of the slots */ + /** + * Is peer in Selected state in any of the slots + */ bool isSelected(Peer::id_t id) { - for (auto& v : validators_) - { - if (overlay_.isSelected(v, id)) - return true; - } - return false; + return std::ranges::any_of( + validators_, [&](auto& v) { return overlay_.isSelected(v, id); }); } - /** Check if there are peers to unsquelch - peer is in Selected + /** + * Check if there are peers to unsquelch - peer is in Selected * state in any of the slots and there are peers in Squelched state * in those slots. */ @@ -892,7 +907,9 @@ protected: std::cout << std::endl; } - /** Send squelch (if duration is set) or unsquelch (if duration not set) */ + /** + * Send squelch (if duration is set) or unsquelch (if duration not set) + */ static Peer::id_t sendSquelch( PublicKey const& validator, @@ -930,7 +947,8 @@ protected: bool handled = false; }; - /** Randomly brings the link between a validator and a peer down. + /** + * Randomly brings the link between a validator and a peer down. * Randomly disconnects a peer. Those events are generated one at a time. */ void @@ -1108,7 +1126,8 @@ protected: f(log); } - /** Initial counting round: three peers receive message "faster" then + /** + * Initial counting round: three peers receive message "faster" then * others. Once the message count for the three peers reaches threshold * the rest of the peers are squelched and the slot for the given validator * is in Selected state. @@ -1119,7 +1138,8 @@ protected: doTest("Initial Round", log, [this](bool log) { BEAST_EXPECT(propagateAndSquelch(log)); }); } - /** Receiving message from squelched peer too soon should not change the + /** + * Receiving message from squelched peer too soon should not change the * slot's state to Counting. */ void @@ -1130,7 +1150,8 @@ protected: }); } - /** Receiving message from squelched peer should change the + /** + * Receiving message from squelched peer should change the * slot's state to Counting. */ void @@ -1142,7 +1163,9 @@ protected: }); } - /** Propagate enough messages to generate one squelch event */ + /** + * Propagate enough messages to generate one squelch event + */ bool propagateAndSquelch(bool log, bool purge = true, bool resetClock = true) { @@ -1176,7 +1199,9 @@ protected: return n == 1 && res; } - /** Send fewer message so that squelch event is not generated */ + /** + * Send fewer message so that squelch event is not generated + */ bool propagateNoSquelch( bool log, @@ -1203,7 +1228,8 @@ protected: return !squelched && res; } - /** Receiving a message from new peer should change the + /** + * Receiving a message from new peer should change the * slot's state to Counting. */ void @@ -1216,8 +1242,10 @@ protected: }); } - /** Selected peer disconnects. Should change the state to counting and - * unsquelch squelched peers. */ + /** + * Selected peer disconnects. Should change the state to counting and + * unsquelch squelched peers. + */ void testSelectedPeerDisconnects(bool log) { @@ -1235,8 +1263,10 @@ protected: }); } - /** Selected peer stops relaying. Should change the state to counting and - * unsquelch squelched peers. */ + /** + * Selected peer stops relaying. Should change the state to counting and + * unsquelch squelched peers. + */ void testSelectedPeerStopsRelaying(bool log) { @@ -1255,7 +1285,8 @@ protected: }); } - /** Squelched peer disconnects. Should not change the state to counting. + /** + * Squelched peer disconnects. Should not change the state to counting. */ void testSquelchedPeerDisconnects(bool log) diff --git a/src/test/peerfinder/PeerFinder_test.cpp b/src/test/peerfinder/PeerFinder_test.cpp index c4f129c1a3..cf91800951 100644 --- a/src/test/peerfinder/PeerFinder_test.cpp +++ b/src/test/peerfinder/PeerFinder_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace xrpl::PeerFinder { @@ -64,8 +65,9 @@ public: void asyncConnect(beast::IP::Endpoint const& ep, Handler&& handler) { + // NOLINTNEXTLINE(misc-const-correctness) boost::system::error_code ec; - handler(ep, ep, ec); + handler(ec); } }; @@ -368,6 +370,259 @@ public: } } + void + testIsValidAddress() + { + testcase("is_valid_address"); + TestStore store; + TestChecker checker; + TestStopwatch clock; + Logic logic(clock, store, checker, journal_); + + auto const pass = [&](std::string const& s) { + BEAST_EXPECT(logic.isValidAddress(beast::IP::Endpoint::fromString(s))); + }; + auto const fail = [&](std::string const& s) { + BEAST_EXPECT(!logic.isValidAddress(beast::IP::Endpoint::fromString(s))); + }; + + // Invalid: port 0 + fail("65.0.0.1:0"); + + // --- IPv4 ranges --- + // For each range: 1 before (pass), first (fail), last (fail), + // 1 after (pass) + + // 0.0.0.0/8 - "This network" + // No "before" - nothing before 0.0.0.0 + fail("0.0.0.0:8080"); + fail("0.255.255.255:8080"); + pass("1.0.0.0:8080"); + + // 10.0.0.0/8 - Private (RFC 1918) + pass("9.255.255.255:8080"); + fail("10.0.0.0:8080"); + fail("10.255.255.255:8080"); + pass("11.0.0.0:8080"); + + // 100.64.0.0/10 - Shared Address Space / CGNAT (RFC 6598) + pass("100.63.255.255:8080"); + fail("100.64.0.0:8080"); + fail("100.127.255.255:8080"); + pass("100.128.0.0:8080"); + + // 127.0.0.0/8 - Loopback + pass("126.255.255.255:8080"); + fail("127.0.0.0:8080"); + fail("127.255.255.255:8080"); + pass("128.0.0.0:8080"); + + // 169.254.0.0/16 - Link-local + pass("169.253.255.255:8080"); + fail("169.254.0.0:8080"); + fail("169.254.255.255:8080"); + pass("169.255.0.0:8080"); + + // 172.16.0.0/12 - Private (RFC 1918) + pass("172.15.255.255:8080"); + fail("172.16.0.0:8080"); + fail("172.31.255.255:8080"); + pass("172.32.0.0:8080"); + + // 192.0.0.0/24 - IETF Protocol Assignments (RFC 6890) + pass("191.255.255.255:8080"); + fail("192.0.0.0:8080"); + fail("192.0.0.255:8080"); + pass("192.0.1.0:8080"); + + // 192.0.2.0/24 - TEST-NET-1 (RFC 5737) + pass("192.0.1.255:8080"); + fail("192.0.2.0:8080"); + fail("192.0.2.255:8080"); + pass("192.0.3.0:8080"); + + // 192.88.99.0/24 - 6to4 Relay Anycast (RFC 7526) + pass("192.88.98.255:8080"); + fail("192.88.99.0:8080"); + fail("192.88.99.255:8080"); + pass("192.88.100.0:8080"); + + // 192.168.0.0/16 - Private (RFC 1918) + pass("192.167.255.255:8080"); + fail("192.168.0.0:8080"); + fail("192.168.255.255:8080"); + pass("192.169.0.0:8080"); + + // 198.18.0.0/15 - Benchmarking (RFC 2544) + pass("198.17.255.255:8080"); + fail("198.18.0.0:8080"); + fail("198.19.255.255:8080"); + pass("198.20.0.0:8080"); + + // 198.51.100.0/24 - TEST-NET-2 (RFC 5737) + pass("198.51.99.255:8080"); + fail("198.51.100.0:8080"); + fail("198.51.100.255:8080"); + pass("198.51.101.0:8080"); + + // 203.0.113.0/24 - TEST-NET-3 (RFC 5737) + pass("203.0.112.255:8080"); + fail("203.0.113.0:8080"); + fail("203.0.113.255:8080"); + pass("203.0.114.0:8080"); + + // 224.0.0.0/4 - Multicast + pass("223.255.255.255:8080"); + fail("224.0.0.0:8080"); + fail("239.255.255.255:8080"); + // 240.0.0.0 (after multicast) is also blocked (reserved) + + // 240.0.0.0/4 - Reserved (RFC 1112) + // 239.255.255.255 (before reserved) is also blocked (multicast) + fail("240.0.0.0:8080"); + fail("255.255.255.255:8080"); + + // --- IPv6 ranges --- + + // ::1 - Loopback (single address) + fail("[::1]:8080"); + + // :: - Unspecified (single address) + fail("[::]:8080"); + + // fc00::/7 - Unique Local Address (ULA) + pass("[fb00::1]:8080"); + fail("[fc00::1]:8080"); + fail("[fdff::1]:8080"); + pass("[fe00::1]:8080"); + + // fe80::/10 - Link-local + pass("[fe7f::1]:8080"); + fail("[fe80::1]:8080"); + fail("[febf::1]:8080"); + pass("[fec0::1]:8080"); + + // ff00::/8 - Multicast + pass("[feff::1]:8080"); + fail("[ff00::1]:8080"); + fail("[ffff::1]:8080"); + // No "after" - ffff:... is the highest IPv6 range + + // 100::/64 - Discard prefix (RFC 6666) + pass("[ff::1]:8080"); + fail("[100::]:8080"); + fail("[100::ffff:ffff:ffff:ffff]:8080"); + pass("[100:0:0:1::1]:8080"); + + // 2001::/32 - IETF Protocol Assignments / Teredo (RFC 4380) + pass("[2000:ffff::1]:8080"); + fail("[2001::]:8080"); + fail("[2001:0:ffff::1]:8080"); + pass("[2001:1::1]:8080"); + + // 2001:20::/28 - ORCHIDv2 (RFC 7343) + pass("[2001:1f::1]:8080"); + fail("[2001:20::1]:8080"); + fail("[2001:2f::1]:8080"); + pass("[2001:30::1]:8080"); + + // 2001:db8::/32 - Documentation (RFC 3849) + pass("[2001:db7::1]:8080"); + fail("[2001:db8::1]:8080"); + fail("[2001:db8:ffff::1]:8080"); + pass("[2001:db9::1]:8080"); + + // 2002::/16 - 6to4 (RFC 3056, deprecated) + pass("[2001:ffff::1]:8080"); + fail("[2002::1]:8080"); + fail("[2002:ffff::1]:8080"); + pass("[2003::1]:8080"); + + // --- IPv6 v4-mapped (delegates to IPv4 checks) --- + fail("[::ffff:10.0.0.1]:8080"); + fail("[::ffff:100.64.0.1]:8080"); + fail("[::ffff:169.254.1.1]:8080"); + fail("[::ffff:192.0.2.1]:8080"); + fail("[::ffff:198.18.0.1]:8080"); + fail("[::ffff:224.0.0.1]:8080"); + fail("[::ffff:240.0.0.1]:8080"); + + // --- Valid public addresses --- + pass("8.8.8.8:443"); + pass("65.0.0.1:8080"); + pass("[2001:4860:4860::8888]:8080"); + pass("[2606:4700:4700::1111]:8080"); + } + + void + testVerifyEndpoints() + { + // Helper that sets up a Logic instance, creates and activates a slot, + // then calls on_endpoints with the given list and returns the + // livecache size afterwards. + auto run = [&](bool verifyEndpoints, Endpoints eps) -> std::size_t { + TestStore store; + TestChecker checker; + TestStopwatch clock; + Logic logic(clock, store, checker, journal_); + { + Config c; + c.autoConnect = false; + c.listeningPort = 1024; + c.ipLimit = 2; + c.verifyEndpoints = verifyEndpoints; + logic.config(c); + } + + auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5"); + auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024"); + + auto const [slot, r] = logic.newOutboundSlot(remote); + BEAST_EXPECT(slot != nullptr); + BEAST_EXPECT(r == Result::Success); + BEAST_EXPECT(logic.onConnected(slot, local)); + + PublicKey const pk(randomKeyPair(KeyType::Secp256k1).first); + BEAST_EXPECT(logic.activate(slot, pk, false) == Result::Success); + + logic.onEndpoints(slot, std::move(eps)); + + auto const size = logic.livecache.size(); + logic.onClosed(slot); + return size; + }; + + { + testcase("verify_endpoints enabled"); + + // Valid public addresses + Endpoints eps; + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.1:5"), 1); + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.2:6"), 1); + // Invalid: private address + eps.emplace_back(beast::IP::Endpoint::fromString("10.0.0.1:5"), 1); + // Invalid: port 0 + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.3:0"), 1); + + // With verification enabled, only the 2 valid endpoints survive + BEAST_EXPECT(run(true, eps) == 2); + } + { + testcase("verify_endpoints disabled"); + + Endpoints eps; + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.1:5"), 1); + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.2:6"), 1); + // Private address — kept when verification is off + eps.emplace_back(beast::IP::Endpoint::fromString("10.0.0.1:5"), 1); + // Port 0 — kept when verification is off + eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.3:0"), 1); + + // Without verification, all 4 endpoints survive + BEAST_EXPECT(run(false, eps) == 4); + } + } + void testOnConnectedSelfConnection() { @@ -524,6 +779,8 @@ public: testActivateInboundDisabled(); testAddFixedPeerNoPort(); testOnConnectedSelfConnection(); + testIsValidAddress(); + testVerifyEndpoints(); } }; diff --git a/src/test/protocol/Issue_test.cpp b/src/test/protocol/Issue_test.cpp index 44a36b7dcf..a6a1fdd341 100644 --- a/src/test/protocol/Issue_test.cpp +++ b/src/test/protocol/Issue_test.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -863,6 +865,101 @@ public: //-------------------------------------------------------------------------- + void + testIssueFromJson() + { + testcase("issueFromJson"); + + // Valid XRP — no issuer field + { + json::Value jv; + jv[jss::currency] = "XRP"; + auto const issue = issueFromJson(jv); + BEAST_EXPECT(isXRP(issue)); + } + + // Valid IOU — legitimate issuer + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + auto const issue = issueFromJson(jv); + BEAST_EXPECT(!isXRP(issue)); + BEAST_EXPECT(issue.account != noAccount()); + } + + // noAccount() is the MPT sentinel in binary serialization - must be + // rejected + try + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = to_string(noAccount()); + issueFromJson(jv); + fail("noAccount() accepted as IOU issuer"); + } + catch (...) + { + pass(); + } + + // xrpAccount() is the XRP sentinel (all zeros) - must be rejected + // as IOU issuer + try + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = to_string(xrpAccount()); + issueFromJson(jv); + fail("xrpAccount() accepted as IOU issuer"); + } + catch (...) + { + pass(); + } + + // Invalid base58 — must be rejected + try + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = "not_a_valid_address"; + issueFromJson(jv); + fail("invalid base58 accepted as IOU issuer"); + } + catch (...) + { + pass(); + } + + // Non-XRP currency with no issuer field — must be rejected + try + { + json::Value jv; + jv[jss::currency] = "USD"; + issueFromJson(jv); + fail("missing issuer accepted"); + } + catch (...) + { + pass(); + } + + // XRP with an issuer field — must be rejected + try + { + json::Value jv; + jv[jss::currency] = "XRP"; + jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + issueFromJson(jv); + fail("XRP with issuer accepted"); + } + catch (...) + { + pass(); + } + } + void run() override { @@ -897,6 +994,9 @@ public: // --- testIssueDomainSets(); testIssueDomainMaps(); + + // --- + testIssueFromJson(); } }; diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index 720fafa8f2..f6c5a94752 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -537,51 +537,6 @@ public: { // VFALCO TODO There are no actual tests here, just printed output? // Change this to actually do something. - -#if 0 - beginTestCase ("rounding "); - - std::uint64_t value = 25000000000000000ull; - int offset = -14; - canonicalizeRound (false, value, offset, true); - - STAmount one (noIssue(), 1); - STAmount two (noIssue(), 2); - STAmount three (noIssue(), 3); - - STAmount oneThird1 = divRound (one, three, noIssue(), false); - STAmount oneThird2 = divide (one, three, noIssue()); - STAmount oneThird3 = divRound (one, three, noIssue(), true); - log << oneThird1; - log << oneThird2; - log << oneThird3; - - STAmount twoThird1 = divRound (two, three, noIssue(), false); - STAmount twoThird2 = divide (two, three, noIssue()); - STAmount twoThird3 = divRound (two, three, noIssue(), true); - log << twoThird1; - log << twoThird2; - log << twoThird3; - - STAmount oneA = mulRound (oneThird1, three, noIssue(), false); - STAmount oneB = multiply (oneThird2, three, noIssue()); - STAmount oneC = mulRound (oneThird3, three, noIssue(), true); - log << oneA; - log << oneB; - log << oneC; - - STAmount fourThirdsB = twoThird2 + twoThird2; - log << fourThirdsA; - log << fourThirdsB; - log << fourThirdsC; - - STAmount dripTest1 = mulRound (twoThird2, two, xrpIssue (), false); - STAmount dripTest2 = multiply (twoThird2, two, xrpIssue ()); - STAmount dripTest3 = mulRound (twoThird2, two, xrpIssue (), true); - log << dripTest1; - log << dripTest2; - log << dripTest3; -#endif } void diff --git a/src/test/protocol/STIssue_test.cpp b/src/test/protocol/STIssue_test.cpp index 1d6d750355..b7cc944e6b 100644 --- a/src/test/protocol/STIssue_test.cpp +++ b/src/test/protocol/STIssue_test.cpp @@ -1,15 +1,24 @@ #include +#include #include // IWYU pragma: keep +#include + +#include #include #include +#include +#include #include #include #include #include #include #include +#include + +#include namespace xrpl::test { @@ -137,12 +146,143 @@ public: "000000000000000000000000000000000000000000000002"); } + void + testNoAccountIssuerRpc() + { + testcase("noAccount issuer rejected via RPC sign"); + + using namespace jtx; + Env env{*this, envconfig([](std::unique_ptr cfg) { + cfg->loadFromString("[signing_support]\ntrue"); + return cfg; + })}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + json::Value txJson; + txJson[jss::TransactionType] = "AMMDelete"; + txJson[jss::Account] = alice.human(); + txJson[jss::Asset][jss::currency] = "USD"; + txJson[jss::Asset][jss::issuer] = to_string(noAccount()); + txJson[jss::Asset2][jss::currency] = "XRP"; + + json::Value req; + req[jss::tx_json] = txJson; + req[jss::secret] = alice.name(); + + auto const result = env.rpc("json", "sign", to_string(req))[jss::result]; + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data."); + } + + void + testNoAccountIssuer() + { + testcase("noAccount issuer rejection"); + + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = to_string(noAccount()); + + try + { + issueFromJson(sfAsset, jv); + fail("issueFromJson accepted noAccount() as IOU issuer"); + } + catch (...) + { + pass(); + } + } + + { + Serializer s; + s.addBitString(toCurrency("USD")); + s.addBitString(noAccount()); + SerialIter iter(s.slice()); + + try + { + STIssue const stissue(iter, sfAsset); + fail( + "STIssue deserialization of [USD][noAccount()] should " + "throw"); + } + catch (...) + { + pass(); + } + } + } + + void + testXrpAccountIssuerRpc() + { + testcase("xrpAccount issuer rejected via RPC sign"); + + using namespace jtx; + Env env{*this, envconfig([](std::unique_ptr cfg) { + cfg->loadFromString("[signing_support]\ntrue"); + return cfg; + })}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + json::Value txJson; + txJson[jss::TransactionType] = "AMMDelete"; + txJson[jss::Account] = alice.human(); + txJson[jss::Asset][jss::currency] = "USD"; + txJson[jss::Asset][jss::issuer] = to_string(xrpAccount()); + txJson[jss::Asset2][jss::currency] = "XRP"; + + json::Value req; + req[jss::tx_json] = txJson; + req[jss::secret] = alice.name(); + + auto const result = env.rpc("json", "sign", to_string(req))[jss::result]; + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Field 'tx_json.Asset' has invalid data."); + } + + void + testXrpAccountIssuer() + { + testcase("xrpAccount issuer rejection"); + + { + json::Value jv; + jv[jss::currency] = "USD"; + jv[jss::issuer] = to_string(xrpAccount()); + + try + { + issueFromJson(sfAsset, jv); + fail("issueFromJson accepted xrpAccount() as IOU issuer"); + } + catch (...) + { + pass(); + } + } + } + void run() override { // compliments other unit tests to ensure complete coverage testConstructor(); testCompare(); + testNoAccountIssuerRpc(); + testNoAccountIssuer(); + testXrpAccountIssuerRpc(); + testXrpAccountIssuer(); } }; diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 5c9c3fd83c..74792e0a70 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -101,6 +102,39 @@ struct STNumber_test : public beast::unit_test::Suite BEAST_EXPECT(numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0)); { + auto const parseNumber = [](std::string const& boundary) { + return numberFromJson(sfNumber, boundary); + }; + auto const expectParseThrows = [this, &parseNumber](std::string const& boundary) { + try + { + parseNumber(boundary); + fail(); + } + catch (std::exception const& e) + { + BEAST_EXPECT(std::string(e.what()) == "number cannot be represented"); + } + }; + + // Small rejects this; large scales parse it as 9223372036854775800e-1. + auto constexpr positiveBoundary = "922337203685477580"; + auto constexpr negativeBoundary = "-922337203685477580"; + if (Number::getMantissaScale() == MantissaRange::MantissaScale::Small) + { + expectParseThrows(positiveBoundary); + expectParseThrows(negativeBoundary); + } + else + { + BEAST_EXPECT( + parseNumber(positiveBoundary) == + STNumber(sfNumber, Number{922'337'203'685'477'580, 0})); + BEAST_EXPECT( + parseNumber(negativeBoundary) == + STNumber(sfNumber, Number{-922'337'203'685'477'580, 0})); + } + NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); // maxint64 9,223,372,036,854,775,807 auto const maxInt = std::to_string(std::numeric_limits::max()); @@ -108,23 +142,19 @@ struct STNumber_test : public beast::unit_test::Suite auto const minInt = std::to_string(std::numeric_limits::min()); if (Number::getMantissaScale() == MantissaRange::MantissaScale::Small) { - BEAST_EXPECT( - numberFromJson(sfNumber, maxInt) == - STNumber(sfNumber, Number{9'223'372'036'854'775, 3})); - BEAST_EXPECT( - numberFromJson(sfNumber, minInt) == - STNumber(sfNumber, Number{-9'223'372'036'854'775, 3})); + // min/maxInt can't be exactly represented with the small mantissa, so they + // don't parse, and are expected to throw. + expectParseThrows(maxInt); + expectParseThrows(minInt); } else { + // with large mantissas, maxint is fine BEAST_EXPECT( - numberFromJson(sfNumber, maxInt) == + parseNumber(maxInt) == STNumber(sfNumber, Number{9'223'372'036'854'775'807, 0})); - BEAST_EXPECT( - numberFromJson(sfNumber, minInt) == - STNumber( - sfNumber, - Number{true, 9'223'372'036'854'775'808ULL, 0, Number::Normalized{}})); + // but minint's mantissa is > kMaxRep, and so rounds, and thus can't be parsed + expectParseThrows(minInt); } } diff --git a/src/test/protocol/STParsedJSON_test.cpp b/src/test/protocol/STParsedJSON_test.cpp index 24981053f5..c2030627c7 100644 --- a/src/test/protocol/STParsedJSON_test.cpp +++ b/src/test/protocol/STParsedJSON_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -1979,7 +1980,7 @@ class STParsedJSON_test : public beast::unit_test::Suite json::Value j; json::Value obj(json::ValueType::Object); json::Value* current = &obj; - for (int i = 0; i < 63; ++i) + for (std::size_t i = 1; i < kMaxParsedJsonDepth; ++i) { json::Value const next(json::ValueType::Object); (*current)[sfTransactionMetaData] = next; @@ -1998,7 +1999,7 @@ class STParsedJSON_test : public beast::unit_test::Suite json::Value j; json::Value obj(json::ValueType::Object); json::Value* current = &obj; - for (int i = 0; i < 64; ++i) + for (std::size_t i = 1; i <= kMaxParsedJsonDepth; ++i) { json::Value const next(json::ValueType::Object); (*current)[sfTransactionMetaData] = next; @@ -2153,6 +2154,191 @@ class STParsedJSON_test : public beast::unit_test::Suite } } + void + testArrayBoundsChecking() + { + testcase("Array bounds checking"); + + auto const limitStr = std::to_string(kMaxParsedJsonArraySize) + " elements per field."; + + // parseArray rejects oversized STI_ARRAY (SignerEntries) + { + json::Value jv; + json::Value entries(json::ValueType::Array); + for (std::size_t i = 0; i <= kMaxParsedJsonArraySize; ++i) + { + json::Value entry; + entry["SignerEntry"]["Account"] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + entry["SignerEntry"]["SignerWeight"] = 1; + entries.append(entry); + } + jv[sfSignerEntries] = entries; + + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT( + parsed.error[jss::error_message] == + "Field 'test.SignerEntries' exceeds allowed JSON array size of " + limitStr); + } + + // parseObject rejects oversized STI_VECTOR256 (Amendments) + { + json::Value jv; + json::Value amendments(json::ValueType::Array); + std::string const hash(64, '0'); + for (std::size_t i = 0; i <= kMaxParsedJsonArraySize; ++i) + amendments.append(hash); + jv[sfAmendments] = amendments; + + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT( + parsed.error[jss::error_message] == + "Field 'test.Amendments' exceeds allowed JSON array size of " + limitStr); + } + + // parseObject accepts exactly kMaxParsedJsonArraySize STI_VECTOR256 (Amendments) + { + json::Value jv; + json::Value amendments(json::ValueType::Array); + std::string const hash(64, '0'); + for (std::size_t i = 0; i < kMaxParsedJsonArraySize; ++i) + amendments.append(hash); + jv[sfAmendments] = amendments; + + STParsedJSONObject const parsed("test", jv); + BEAST_EXPECT(parsed.object); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + auto const arrSize = parsed.object->getFieldV256(sfAmendments).size(); + BEAST_EXPECT(arrSize == kMaxParsedJsonArraySize); + } + + // parseObject rejects oversized STI_PATHSET (outer array) + { + json::Value jv; + json::Value paths(json::ValueType::Array); + for (std::size_t i = 0; i <= kMaxParsedJsonArraySize; ++i) + { + json::Value path(json::ValueType::Array); + json::Value hop; + hop["account"] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + path.append(hop); + paths.append(path); + } + jv[sfPaths] = paths; + + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT( + parsed.error[jss::error_message] == + "Field 'test.Paths' exceeds allowed JSON array size of " + limitStr); + } + + // parseObject accepts exactly kMaxParsedJsonArraySize STI_PATHSET (outer array) + { + json::Value jv; + json::Value paths(json::ValueType::Array); + for (std::size_t i = 0; i < kMaxParsedJsonArraySize; ++i) + { + json::Value path(json::ValueType::Array); + json::Value hop; + hop["account"] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + path.append(hop); + paths.append(path); + } + jv[sfPaths] = paths; + + STParsedJSONObject const parsed("test", jv); + BEAST_EXPECT(parsed.object); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + auto const arrSize = parsed.object->getFieldPathSet(sfPaths).size(); + BEAST_EXPECT(arrSize == kMaxParsedJsonArraySize); + } + + // parseObject rejects oversized STI_PATHSET (inner path hop array) + { + json::Value jv; + json::Value paths(json::ValueType::Array); + json::Value path(json::ValueType::Array); + json::Value hop; + hop["account"] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + for (std::size_t i = 0; i <= kMaxParsedJsonArraySize; ++i) + path.append(hop); + paths.append(path); + jv[sfPaths] = paths; + + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT( + parsed.error[jss::error_message] == + "Field 'test.Paths[0]' exceeds allowed JSON array size of " + limitStr); + } + + // parseObject accepts exactly kMaxParsedJsonArraySize hops in a single STI_PATHSET path + { + json::Value jv; + json::Value paths(json::ValueType::Array); + json::Value path(json::ValueType::Array); + json::Value hop; + hop["account"] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + for (std::size_t i = 0; i < kMaxParsedJsonArraySize; ++i) + path.append(hop); + paths.append(path); + jv[sfPaths] = paths; + + STParsedJSONObject const parsed("test", jv); + BEAST_EXPECT(parsed.object); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + auto const arrSize = parsed.object->getFieldPathSet(sfPaths)[0].size(); + BEAST_EXPECT(arrSize == kMaxParsedJsonArraySize); + } + + // parseArray accepts exactly kMaxParsedJsonArraySize Memos (boundary) + { + json::Value jv; + json::Value memos(json::ValueType::Array); + for (std::size_t i = 0; i < kMaxParsedJsonArraySize; ++i) + { + json::Value memo; + memo["Memo"] = json::Value(json::ValueType::Object); + memo["Memo"]["MemoData"] = "00"; + memos.append(memo); + } + jv[sfMemos] = memos; + + STParsedJSONObject const parsed("test", jv); + BEAST_EXPECT(parsed.object); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + auto const arrSize = parsed.object->getFieldArray(sfMemos).size(); + BEAST_EXPECT(arrSize == kMaxParsedJsonArraySize); + } + + // parseArray rejects one more than kMaxParsedJsonArraySize Memos + { + json::Value jv; + json::Value memos(json::ValueType::Array); + for (std::size_t i = 0; i <= kMaxParsedJsonArraySize; ++i) + { + json::Value memo; + memo["Memo"] = json::Value(json::ValueType::Object); + memo["Memo"]["MemoData"] = "00"; + memos.append(memo); + } + jv[sfMemos] = memos; + + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT( + parsed.error[jss::error_message] == + "Field 'test.Memos' exceeds allowed JSON array size of " + limitStr); + } + } + void testEdgeCases() { @@ -2420,6 +2606,7 @@ class STParsedJSON_test : public beast::unit_test::Suite testNumber(); testObject(); testArray(); + testArrayBoundsChecking(); testEdgeCases(); } }; diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 31b20b37d4..c656c97a4c 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -9,8 +9,11 @@ #include // IWYU pragma: keep #include #include +#include +#include #include #include +#include #include #include @@ -22,7 +25,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -32,6 +37,7 @@ #include #include #include +#include #include namespace xrpl::test { @@ -200,6 +206,15 @@ public: resp[jss::result][jss::error_message] == "Invalid field 'limit', not unsigned integer."); } + // test error on sponsored param not a boolean + { + json::Value params; + params[jss::account] = bob.human(); + params[jss::sponsored] = "true"; + auto resp = env.rpc("json", "account_objects", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == "Invalid field 'sponsored', not boolean."); + } // test errors on marker { Account const gw{"G"}; @@ -605,6 +620,7 @@ public: BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0)); BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0)); BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0)); + BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::sponsorship), 0)); // we expect invalid field type reported for the following types BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments))); @@ -926,6 +942,30 @@ public: BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1); } + { + // Create a sponsorship + env(sponsor::set(alice, tfSponsorshipSetRequireSignForFee, 200, XRP(100), drops(10)), + sponsor::SponseeAcc(gw)); + env.close(); + + // Find the sponsorship. + for (auto const& acct : {alice, gw}) + { + json::Value const resp = acctObjs(acct, jss::sponsorship); + BEAST_EXPECT(acctObjsIsSize(resp, 1)); + + auto const& sponsorship = resp[jss::result][jss::account_objects][0u]; + + BEAST_EXPECT(sponsorship[sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(sponsorship[sfSponsee.jsonName] == gw.human()); + BEAST_EXPECT( + sponsorship[sfFlags.jsonName].asUInt() == tfSponsorshipSetRequireSignForFee); + BEAST_EXPECT(sponsorship[sfRemainingOwnerCount.jsonName].asUInt() == 200); + BEAST_EXPECT(sponsorship[sfFeeAmount.jsonName].asUInt() == 100000000); + BEAST_EXPECT(sponsorship[sfMaxFee.jsonName].asUInt() == 10); + } + } + { // See how "deletion_blockers_only" handles gw's directory. json::Value params; @@ -940,7 +980,8 @@ public: jss::NFTokenPage.cStr(), jss::RippleState.cStr(), jss::PayChannel.cStr(), - jss::PermissionedDomain.cStr()}; + jss::PermissionedDomain.cStr(), + jss::Sponsorship.cStr()}; std::ranges::sort(v); return v; }(); @@ -1350,6 +1391,313 @@ public: } } + void + testSponsoredFilter() + { + testcase("SponsoredFilter"); + using namespace jtx; + + Env env(*this, testableAmendments()); + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor1("sponsor1"); + Account const gw("gw"); + auto const usd = gw["USD"]; + + env.fund(XRP(10000), alice, bob, sponsor1, gw); + env.close(); + + // Helper to call account_objects with sponsored filter + auto acctObjsSponsored = [](Env& testEnv, + AccountID const& acct, + bool sponsored, + std::optional const& type = std::nullopt) { + json::Value params; + params[jss::account] = to_string(acct); + params[jss::sponsored] = sponsored; + if (type) + params[jss::type] = *type; + params[jss::ledger_index] = "validated"; + return testEnv.rpc("json", "account_objects", to_string(params)); + }; + + // Create a trust line for bob (not sponsored) + env(trust(bob, usd(1000))); + env.close(); + + // sponsored=true should not find any objects for bob (doesn't have any sponsored objects) + { + auto const resp = acctObjsSponsored(env, bob.id(), true); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + + // Now sponsor bob's trust line + auto const trustId = keylet::trustLine(bob, gw, usd.currency); + if (!BEAST_EXPECT(env.le(trustId))) + return; + + env(sponsor::transfer(bob, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor1, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor1)); + env.close(); + + // Verify trust line has sponsor field + { + auto const sle = env.le(trustId); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfHighSponsor) || sle->isFieldPresent(sfLowSponsor)); + } + + // sponsored=true on bob should include the sponsored trust line + { + auto const resp = acctObjsSponsored(env, bob.id(), true); + auto const& objs = resp[jss::result][jss::account_objects]; + if (!BEAST_EXPECT(objs.size() == 1)) + return; + + auto const& obj = objs[0u]; + BEAST_EXPECT(obj[sfLedgerEntryType.jsonName] == jss::RippleState); + BEAST_EXPECT( + obj.isMember(sfHighSponsor.jsonName) || obj.isMember(sfLowSponsor.jsonName)); + } + + // sponsored=false on bob should NOT include the sponsored trust line + { + auto const resp = acctObjsSponsored(env, bob.id(), false); + BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0); + } + + // A trust line sponsored on either side is classified as sponsored + // for both parties. + { + Env env(*this, testableAmendments()); + Account const issuer("issuer"); + Account const user("user"); + Account const sponsor("sponsor"); + auto const usd = issuer["USD"]; + + env.fund(XRP(10000), issuer, user, sponsor); + env.close(); + + env(trust(issuer, user["USD"](100))); + env.close(); + + env(trust(user, usd(100))); + env.close(); + + auto const trustId = keylet::trustLine(user, issuer, usd.currency); + if (!BEAST_EXPECT(env.le(trustId))) + return; + + env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + auto const line = env.le(trustId); + if (!BEAST_EXPECT(line)) + return; + + auto const userIsHigh = line->getFieldAmount(sfHighLimit).getIssuer() == user.id(); + auto const& userSponsorField = userIsHigh ? sfHighSponsor : sfLowSponsor; + auto const& issuerSponsorField = userIsHigh ? sfLowSponsor : sfHighSponsor; + + BEAST_EXPECT(line->isFieldPresent(userSponsorField)); + BEAST_EXPECT(!line->isFieldPresent(issuerSponsorField)); + + { + auto const resp = acctObjsSponsored(env, user.id(), true, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState); + } + { + auto const resp = acctObjsSponsored(env, user.id(), false, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + { + auto const resp = acctObjsSponsored(env, issuer.id(), true, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState); + } + { + auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + } + + // A sponsored Check is classified as sponsored in both the writer's + // and the destination's results. + { + Env env(*this, testableAmendments()); + Account const owner("owner"); + Account const dest("dest"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), owner, dest, sponsor); + env.close(); + + auto const checkSeq = env.seq(owner); + env(check::create(owner, dest, XRP(1))); + env.close(); + + auto const checkId = keylet::check(owner, checkSeq); + if (!BEAST_EXPECT(env.le(checkId))) + return; + + env(sponsor::transfer(owner, tfSponsorshipCreate, checkId.key), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + { + auto const sle = env.le(checkId); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFieldPresent(sfSponsor)); + } + + for (auto const& acct : {owner.id(), dest.id()}) + { + { + auto const resp = acctObjsSponsored(env, acct, true, jss::check); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Check); + } + { + auto const resp = acctObjsSponsored(env, acct, false, jss::check); + BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0); + } + } + } + + // A Sponsorship object is visible to both sides. + { + Env env(*this, testableAmendments()); + Account const owner("owner"); + Account const sponsee("sponsee"); + + env.fund(XRP(10000), owner, sponsee); + env.close(); + + env(sponsor::set(owner, 0, 100, XRP(100)), sponsor::SponseeAcc(sponsee)); + env.close(); + + auto const sponsorshipKeylet = keylet::sponsorship(owner, sponsee); + if (!BEAST_EXPECT(env.le(sponsorshipKeylet))) + return; + + { + auto const resp = acctObjsSponsored(env, owner.id(), false, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); + } + { + auto const resp = acctObjsSponsored(env, sponsee.id(), false, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + if (BEAST_EXPECT(objs.size() == 1)) + BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); + } + { + auto const resp = acctObjsSponsored(env, owner.id(), true, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + { + auto const resp = acctObjsSponsored(env, sponsee.id(), true, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); + } + } + } + + void + testAccountObjectDoesntShowCancelledOffers() + { + testcase("AccountObjectDoesntShowCancelledOffers"); + + using namespace jtx; + Env env(*this); + + Account const alice{"alice"}; + Account const bob{"bob"}; + auto const eur = bob["EUR"]; + env.fund(XRP(10000), alice, bob); + env.close(); + + auto const rpcAccountObjects = [&](std::optional limit = std::nullopt) { + json::Value params; + params[jss::account] = alice.human(); + if (limit.has_value()) + { + params[jss::limit] = *limit; + } + return env.rpc("json", "account_objects", to_string(params)); + }; + + auto const numEntries = 33; + std::vector seqs; + seqs.reserve(numEntries); + for ([[maybe_unused]] auto _ : std::ranges::iota_view{0, numEntries}) + { + json::Value params; + params[jss::secret] = toBase58(generateSeed("alice")); + params[jss::tx_json] = offer(alice, eur(1), XRP(2)); + auto const res = env.rpc("json", "submit", to_string(params))[jss::result]; + BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS"); + seqs.push_back(env.seq(alice)); + } + + auto res = rpcAccountObjects(); + BEAST_EXPECT(res[jss::result][jss::account_objects].size() == numEntries); + BEAST_EXPECT(not res[jss::result].isMember(jss::limit)); + BEAST_EXPECT(not res[jss::result].isMember(jss::marker)); + + for (auto const s : std::views::all(seqs) | std::views::take(numEntries - 1)) + { + json::Value params; + params[jss::secret] = toBase58(generateSeed("alice")); + params[jss::tx_json] = offerCancel(alice, s - 1); + auto const res = env.rpc("json", "submit", to_string(params))[jss::result]; + BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS"); + } + + res = rpcAccountObjects(); + BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1); + BEAST_EXPECT(not res[jss::result].isMember(jss::limit)); + BEAST_EXPECT(not res[jss::result].isMember(jss::marker)); + + { + json::Value params; + params[jss::secret] = toBase58(generateSeed("alice")); + json::Value txJson; + txJson[jss::TransactionType] = jss::NFTokenMint; + txJson[jss::Account] = to_string(alice.id()); + txJson["NFTokenTaxon"] = 1; + params[jss::tx_json] = txJson; + auto const res = env.rpc("json", "submit", to_string(params))[jss::result]; + BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS"); + } + env.close(); + + res = rpcAccountObjects(); + BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 2); + BEAST_EXPECT(not res[jss::result].isMember(jss::limit)); + BEAST_EXPECT(not res[jss::result].isMember(jss::marker)); + + res = rpcAccountObjects(1); + BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1); + BEAST_EXPECT(res[jss::result][jss::limit].asUInt() == 1); + BEAST_EXPECT(res[jss::result].isMember(jss::marker)); + } + void run() override { @@ -1360,6 +1708,8 @@ public: testNFTsMarker(); testAccountNFTs(); testAccountObjectMarker(); + testSponsoredFilter(); + testAccountObjectDoesntShowCancelledOffers(); } }; diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index ace8743e1e..f1fbc2871b 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -5,6 +5,7 @@ #include #include // IWYU pragma: keep #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +47,7 @@ #include #include #include +#include #include #include @@ -773,7 +777,7 @@ class AccountTx_test : public beast::unit_test::Suite // All it takes is a large enough XRP payment to resurrect // becky's account. Try too small a payment. - env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - drops(1)), + env(pay(alice, becky, drops(env.current()->fees().accountReserve(0, 1)) - drops(1)), Ter(tecNO_DST_INSUF_XRP)); env.close(); @@ -888,6 +892,502 @@ class AccountTx_test : public beast::unit_test::Suite checkAliceAcctTx(9, jss::Payment); } + void + testDelegation() + { + testcase("Delegation Filtering"); + + using namespace test::jtx; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + env.fund(XRP(10000), alice, bob, carol); + env.close(); + + // Normal TX: Alice pays Carol (Signed by Alice's Master Key) + env(pay(alice, carol, XRP(10))); + env.close(); + + // Setup Delegation: Alice allows Bob to sign Payments for her + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + // Delegated TX: Alice pays Bob (Signed by Bob using Delegation) + env(pay(alice, bob, XRP(20)), delegate::As(bob)); + env.close(); + + // Normal TX: Bob pays Carol (Signed by Bob for himself) + env(pay(bob, carol, XRP(30))); + env.close(); + + auto const countTxs = [&](AccountID const& account, + json::Value const& delegateParams, + std::optional const limit = std::nullopt) -> int { + int count = 0; + json::Value marker; + bool haveMarker = false; + int pages = 0; + + while (true) + { + json::Value params; + params[jss::account] = toBase58(account); + params[jss::ledger_index_min] = -1; + params[jss::ledger_index_max] = -1; + + if (!delegateParams.isNull()) + params[jss::delegate] = delegateParams; + if (limit) + params[jss::limit] = *limit; + if (haveMarker) + params[jss::marker] = marker; + + auto const res = env.rpc("json", "account_tx", to_string(params)); + auto const& result = res[jss::result]; + + if (result.isMember(jss::transactions)) + count += result[jss::transactions].size(); + + if (!limit || !result.isMember(jss::marker)) + break; + + marker = result[jss::marker]; + haveMarker = true; + ++pages; + if (!BEAST_EXPECT(pages < 20)) + break; + } + + return count; + }; + + auto const checkError = [&](json::Value const& delegateParams, + std::string const& errToken) { + json::Value params; + params[jss::account] = alice.human(); + params[jss::delegate] = delegateParams; + auto res = env.rpc("json", "account_tx", to_string(params)); + BEAST_EXPECT(res[jss::result][jss::error] == errToken); + }; + + // Filter: Delegatee. Expects TX #2 (Signed by Bob) + { + json::Value p; + p[jss::delegate_filter] = "actor"; + BEAST_EXPECT(countTxs(alice.id(), p) == 1); + } + + // Filter: Delegatee + Counterparty Bob. Expects TX #2. + { + json::Value p; + p[jss::delegate_filter] = "actor"; + p[jss::counter_party] = bob.human(); + BEAST_EXPECT(countTxs(alice.id(), p) == 1); + } + + // Filter: Delegatee + Counterparty Carol. Expects 0. + { + json::Value p; + p[jss::delegate_filter] = "actor"; + p[jss::counter_party] = carol.human(); + BEAST_EXPECT(countTxs(alice.id(), p) == 0); + } + + // Filter: Delegator. Expects TX #2. + // (Bob signed it, but Alice is the owner). + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + BEAST_EXPECT(countTxs(bob.id(), p) == 1); + } + + // Filter: Delegator + Counterparty Alice. Expects TX #2. + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + p[jss::counter_party] = alice.human(); + BEAST_EXPECT(countTxs(bob.id(), p) == 1); + } + + // Filter: Authorizer. Expect: None. + // TX #2 has sfDelegate present, but Alice is the delegator/owner, not + // the delegate signer + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + BEAST_EXPECT(countTxs(alice.id(), p) == 0); + } + + // Query Bob (Signer), Filter: Delegator, Counterparty: Carol + // Expect: None (Alice is Owner, not Carol) + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + p[jss::counter_party] = carol.human(); + BEAST_EXPECT(countTxs(bob.id(), p) == 0); + } + + // Query Bob (Signer), Filter: Delegatee + // Expect: None. Bob did not employ a delegatee for his own TXs (TX C). + { + json::Value p; + p[jss::delegate_filter] = "actor"; + BEAST_EXPECT(countTxs(bob.id(), p) == 0); + } + + // "delegate" is not an object (e.g., string) + { + json::Value const p = "not_an_object"; + checkError(p, "invalidParams"); + } + + // Missing "delegate_filter" inside object + { + json::Value const p(json::ValueType::Object); + checkError(p, "invalidParams"); + } + + // "delegate_filter" is not a string (e.g., int) + { + json::Value p; + p[jss::delegate_filter] = 123; + checkError(p, "invalidParams"); + } + + // "delegate_filter" has invalid value + { + json::Value p; + p[jss::delegate_filter] = "random_string"; + checkError(p, "invalidParams"); + } + + // "counterparty" is not a string + { + json::Value p; + p[jss::delegate_filter] = "actor"; + p[jss::counter_party] = 123; + checkError(p, "invalidParams"); + } + + // "counterparty" is malformed base58 + { + json::Value p; + p[jss::delegate_filter] = "actor"; + p[jss::counter_party] = "not_an_account"; + checkError(p, "actMalformed"); + } + + // Multi-signed non-delegated TX: Alice pays Carol via multi-sig. + // sfDelegate is absent and sfSigningPubKey is empty — the filter + // must skip it without crashing. + { + Account const daria{"daria"}; + Account const edward{"edward"}; + env.fund(XRP(1000), daria, edward); + env.close(); + env(signers(alice, 2, {{daria, 1}, {edward, 1}})); + env.close(); + env(pay(alice, carol, XRP(1)), + Fee(drops(env.current()->fees().increment * 2)), + Msig(daria, edward)); + env.close(); + + // Alice's actor filter should still see only the 1 delegated tx, + // not the multi-signed one. + json::Value p; + p[jss::delegate_filter] = "actor"; + BEAST_EXPECT(countTxs(alice.id(), p) == 1); + } + + // Regular-key-signed non-delegated TX: Alice pays Bob, signed by Bob + // as Alice's regular key. This must not be treated as delegation. + { + env(regkey(alice, bob)); + env.close(); + env(pay(alice, bob, XRP(1))); + env.close(); + + json::Value actorFilter; + actorFilter[jss::delegate_filter] = "actor"; + BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1); + // limit: 1 forces pagination past newer non-delegated rows. + BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1); + + actorFilter[jss::counter_party] = bob.human(); + BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1); + BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1); + + json::Value authorizerFilter; + authorizerFilter[jss::delegate_filter] = "authorizer"; + BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1); + BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1); + + authorizerFilter[jss::counter_party] = alice.human(); + BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1); + BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1); + } + + // Pagination marker/delegate-filter consistency. A marker returned by a + // delegate-filtered query carries a `delegate` flag and is only valid + // for a follow-up request that repeats the filter; mixing the two + // marker conventions must be rejected with invalidParams. + { + json::Value actorFilter; + actorFilter[jss::delegate_filter] = "actor"; + + // Obtain a delegate-filtered marker (limit 1 forces pagination). + json::Value dp; + dp[jss::account] = alice.human(); + dp[jss::ledger_index_min] = -1; + dp[jss::ledger_index_max] = -1; + dp[jss::delegate] = actorFilter; + dp[jss::limit] = 1; + auto const dRes = env.rpc("json", "account_tx", to_string(dp)); + BEAST_EXPECT(dRes[jss::result].isMember(jss::marker)); + json::Value const delegateMarker = dRes[jss::result][jss::marker]; + BEAST_EXPECT( + delegateMarker.isMember(jss::delegate) && delegateMarker[jss::delegate].asBool()); + + // Reusing a delegate marker without the delegate filter is rejected. + { + json::Value p; + p[jss::account] = alice.human(); + p[jss::ledger_index_min] = -1; + p[jss::ledger_index_max] = -1; + p[jss::limit] = 1; + p[jss::marker] = delegateMarker; + auto const r = env.rpc("json", "account_tx", to_string(p)); + BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams"); + } + + // Obtain a non-delegate marker; it must not carry the flag. + json::Value np; + np[jss::account] = alice.human(); + np[jss::ledger_index_min] = -1; + np[jss::ledger_index_max] = -1; + np[jss::limit] = 1; + auto const nRes = env.rpc("json", "account_tx", to_string(np)); + BEAST_EXPECT(nRes[jss::result].isMember(jss::marker)); + json::Value const normalMarker = nRes[jss::result][jss::marker]; + BEAST_EXPECT(!normalMarker.isMember(jss::delegate)); + + // Reusing a non-delegate marker with a delegate filter is rejected. + { + json::Value p; + p[jss::account] = alice.human(); + p[jss::ledger_index_min] = -1; + p[jss::ledger_index_max] = -1; + p[jss::limit] = 1; + p[jss::delegate] = actorFilter; + p[jss::marker] = normalMarker; + auto const r = env.rpc("json", "account_tx", to_string(p)); + BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams"); + } + } + } + + void + testDelegationMultiSign() + { + testcase("Delegation filter with multi-signed delegatee"); + using namespace test::jtx; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + Account const daria{"daria"}; + Account const edward{"edward"}; + + env.fund(XRP(10000), alice, bob, carol, daria, edward); + env.close(); + + // Bob's identity is established via multi-sig (daria + edward) + env(signers(bob, 2, {{daria, 1}, {edward, 1}})); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + // Delegated tx: Alice pays Carol, Bob signs via multi-sig + env(pay(alice, carol, XRP(10)), Fee(XRP(1)), delegate::As(bob), Msig(daria, edward)); + env.close(); + + auto const countTxs = [&](AccountID const& account, + json::Value const& delegateParams) -> int { + json::Value params; + params[jss::account] = toBase58(account); + params[jss::ledger_index_min] = -1; + params[jss::ledger_index_max] = -1; + params[jss::delegate] = delegateParams; + + auto const res = env.rpc("json", "account_tx", to_string(params)); + + if (res[jss::result].isMember(jss::transactions)) + return res[jss::result][jss::transactions].size(); + return 0; + }; + + // Alice (owner) finds the tx with actor filter + { + json::Value p; + p[jss::delegate_filter] = "actor"; + BEAST_EXPECT(countTxs(alice.id(), p) == 1); + } + + // Alice (owner) + counterparty Bob finds the tx + { + json::Value p; + p[jss::delegate_filter] = "actor"; + p[jss::counter_party] = bob.human(); + BEAST_EXPECT(countTxs(alice.id(), p) == 1); + } + + // Bob (delegatee) finds the tx with authorizer filter + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + BEAST_EXPECT(countTxs(bob.id(), p) == 1); + } + + // Bob (delegatee) + counterparty Alice finds the tx + { + json::Value p; + p[jss::delegate_filter] = "authorizer"; + p[jss::counter_party] = alice.human(); + BEAST_EXPECT(countTxs(bob.id(), p) == 1); + } + } + + void + testDelegationMarkerWithinPage() + { + testcase("Delegation filter marker within a single query page"); + + using namespace test::jtx; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + env.fund(XRP(10000), alice, bob, carol); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const startLedger = env.closed()->header().seq + 1; + + env(pay(alice, carol, XRP(1)), delegate::As(bob)); + env.close(); + env(pay(alice, carol, XRP(1)), delegate::As(bob)); + env.close(); + + json::Value p; + p[jss::delegate_filter] = "actor"; + + json::Value params; + params[jss::account] = alice.human(); + params[jss::ledger_index_min] = startLedger; + params[jss::ledger_index_max] = -1; + params[jss::delegate] = p; + params[jss::limit] = 1; + + auto const res = env.rpc("json", "account_tx", to_string(params)); + auto const& result = res[jss::result]; + + // The first page emits only the first delegated payment. (as page limit is set to 1) + BEAST_EXPECT(result[jss::transactions].size() == 1); + BEAST_EXPECT(result.isMember(jss::marker)); + + // Following the marker resumes right after the first payment and + // returns the second one. + json::Value page2 = params; + page2[jss::marker] = result[jss::marker]; + auto const res2 = env.rpc("json", "account_tx", to_string(page2)); + BEAST_EXPECT(res2[jss::result][jss::transactions].size() == 1); + } + + void + testSponsorship() + { + // test all sponsored transactions are in sponsor and sponsee's account + // tx list + testcase("Sponsorship"); + + using namespace test::jtx; + Env env(*this); + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + env.fund(XRP(10000), alice, sponsor, sponsor2); + env.close(); + + // check the latest sponsorship-related txn is in account tx list + auto const checkTx = [&](Account const& account, json::StaticString txType) { + json::Value params; + params[jss::account] = account.human(); + params[jss::limit] = 100; + auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result]; + + auto const& tx0(jv[jss::transactions][0u][jss::tx]); + BEAST_EXPECT(tx0[jss::TransactionType] == txType); + + std::string const txHash{ + env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()}; + BEAST_EXPECT(tx0[jss::hash] == txHash); + }; + + // fee sponsorship + env(noop(alice), sponsor::As(sponsor, spfSponsorFee), Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::AccountSet); + checkTx(sponsor, jss::AccountSet); + + // set sponsor + env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); + env.close(); + checkTx(alice, jss::SponsorshipSet); + checkTx(sponsor, jss::SponsorshipSet); + + // create an object with sponsor + auto const checkId = keylet::check(alice, env.seq(alice)).key; + env(check::create(alice, sponsor, XRP(1)), sponsor::As(sponsor, spfSponsorReserve)); + env.close(); + checkTx(alice, jss::CheckCreate); + checkTx(sponsor, jss::CheckCreate); + + // transfer object sponsorship + env(sponsor::transfer(alice, tfSponsorshipReassign, checkId), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + checkTx(alice, jss::SponsorshipTransfer); + checkTx(sponsor, jss::SponsorshipTransfer); + checkTx(sponsor2, jss::SponsorshipTransfer); + + // delete the sponsored object + env(check::cancel(alice, checkId), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::CheckCancel); + checkTx(sponsor, jss::CheckCancel); + checkTx(sponsor2, jss::CheckCancel); + + // account sponsorship + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + checkTx(alice, jss::SponsorshipTransfer); + checkTx(sponsor, jss::SponsorshipTransfer); + } + public: void run() override @@ -896,6 +1396,10 @@ public: testContents(); testAccountDelete(); testMPT(); + testDelegation(); + testDelegationMultiSign(); + testDelegationMarkerWithinPage(); + testSponsorship(); } }; BEAST_DEFINE_TESTSUITE(AccountTx, rpc, xrpl); diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index c7ef5cef2d..7adb5a4518 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,8 @@ std::vector> gMappings{ {jss::oracle_document_id, FieldType::UInt32Field}, {jss::owner, FieldType::AccountField}, {jss::seq, FieldType::UInt32Field}, + {jss::sponsor, FieldType::AccountField}, + {jss::sponsee, FieldType::AccountField}, {jss::subject, FieldType::AccountField}, {jss::ticket_seq, FieldType::UInt32Field}, }; @@ -107,7 +110,7 @@ getFieldType(json::StaticString fieldName) return it->second; } - Throw("`mappings` is missing field " + std::string(fieldName.cStr())); + Throw("`gMappings` is missing field " + std::string(fieldName.cStr())); } std::string @@ -1884,6 +1887,59 @@ class LedgerEntry_test : public beast::unit_test::Suite runLedgerEntryTest(env, jss::signer_list); } + void + testSponsorship() + { + testcase("Sponsorship"); + + using namespace test::jtx; + + Env env{*this}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + env(sponsor::set(alice, 0, 100), sponsor::SponseeAcc(bob)); + env.close(); + std::string const ledgerHash{to_string(env.closed()->header().hash)}; + auto const sponsorshipIndex = to_string(keylet::sponsorship(alice.id(), bob.id()).key); + + { + // Request by sponsor and sponsee. + json::Value jvParams; + jvParams[jss::sponsorship][jss::sponsor] = alice.human(); + jvParams[jss::sponsorship][jss::sponsee] = bob.human(); + jvParams[jss::ledger_hash] = ledgerHash; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Request by index. + json::Value jvParams; + jvParams[jss::sponsorship] = sponsorshipIndex; + jvParams[jss::ledger_hash] = ledgerHash; + json::Value const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Check all malformed cases. + runLedgerEntryTest( + env, + jss::sponsorship, + { + {.fieldName = jss::sponsor, .malformedErrorMsg = "malformedSponsor"}, + {.fieldName = jss::sponsee, .malformedErrorMsg = "malformedSponsee"}, + }); + } + } + void testTicket() { @@ -2232,7 +2288,9 @@ class LedgerEntry_test : public beast::unit_test::Suite } } - /// Test the ledger entry types that don't take parameters + /** + * Test the ledger entry types that don't take parameters + */ void testFixed() { @@ -2248,7 +2306,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env.close(); - /** Verifies that the RPC result has the expected data + /** + * Verifies that the RPC result has the expected data * * @param good: Indicates that the request should have succeeded * and returned a ledger object of `expectedType` type. @@ -2283,7 +2342,8 @@ class LedgerEntry_test : public beast::unit_test::Suite } }; - /** Runs a series of tests for a given fixed-position ledger + /** + * Runs a series of tests for a given fixed-position ledger * entry. * * @param field: The Json request field to use. @@ -2441,7 +2501,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env.close(); - /** Verifies that the RPC result has the expected data + /** + * Verifies that the RPC result has the expected data * * @param good: Indicates that the request should have succeeded * and returned a ledger object of `expectedType` type. @@ -2481,7 +2542,8 @@ class LedgerEntry_test : public beast::unit_test::Suite } }; - /** Runs a series of tests for a given ledger index. + /** + * Runs a series of tests for a given ledger index. * * @param ledger: The ledger index value of the "hashes" request * parameter. May not necessarily be a number. @@ -2676,6 +2738,7 @@ public: testPayChan(); testRippleState(); testSignerList(); + testSponsorship(); testTicket(); testDID(); testInvalidOracleLedgerEntry(); diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index af56a9e9ba..3a2c957691 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -258,10 +258,12 @@ class LedgerRPC_test : public beast::unit_test::Suite BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u); } - /// @brief ledger RPC requests as a way to drive - /// input options to lookupLedger. The point of this test is - /// coverage for lookupLedger, not so much the ledger - /// RPC request. + /** + * @brief ledger RPC requests as a way to drive + * input options to lookupLedger. The point of this test is + * coverage for lookupLedger, not so much the ledger + * RPC request. + */ void testLookupLedger() { diff --git a/src/test/rpc/ServerDefinitions_test.cpp b/src/test/rpc/ServerDefinitions_test.cpp index bf345e6fdf..0cf6b315e0 100644 --- a/src/test/rpc/ServerDefinitions_test.cpp +++ b/src/test/rpc/ServerDefinitions_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -61,14 +62,12 @@ public: // check exception SFields { auto const fieldExists = [&](std::string name) { - for (auto& field : result[jss::result][jss::FIELDS]) - { - if (field[0u].asString() == name) - { - return true; - } - } - return false; + auto& fields = result[jss::result][jss::FIELDS]; + // json::Value is not a std::ranges range, so the iterator form is used. + // NOLINTNEXTLINE(modernize-use-ranges) + return std::any_of(fields.begin(), fields.end(), [&](auto& field) { + return field[0u].asString() == name; + }); }; BEAST_EXPECT(fieldExists("Generic")); BEAST_EXPECT(fieldExists("Invalid")); diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 0d00360a58..16df733a66 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -364,6 +364,37 @@ class Simulate_test : public beast::unit_test::Suite auto const resp = env.rpc("json", "simulate", to_string(params)); BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'."); } + { + // Non-object SponsorSignature field + json::Value params; + json::Value txJson = json::ValueType::Object; + txJson[jss::TransactionType] = jss::AccountSet; + txJson[jss::Account] = env.master.human(); + txJson[sfSponsorSignature] = ""; + params[jss::tx_json] = txJson; + + auto const resp = env.rpc("json", "simulate", to_string(params)); + BEAST_EXPECT( + resp[jss::result][jss::error_message] == + "Invalid field 'SponsorSignature', not object."); + } + { + // Invalid SponsorSignature.Signers field + json::Value params; + json::Value txJson = json::ValueType::Object; + txJson[jss::TransactionType] = jss::AccountSet; + txJson[jss::Account] = env.master.human(); + json::Value sponsorSignature = json::ValueType::Object; + sponsorSignature[sfSigners] = "1"; + txJson[sfSponsorSignature] = sponsorSignature; + params[jss::tx_json] = txJson; + + auto const resp = env.rpc("json", "simulate", to_string(params)); + BEAST_EXPECTS( + resp[jss::result][jss::error_message] == + "Invalid field 'tx.SponsorSignature.Signers'.", + resp.toStyledString()); + } { // Invalid transaction json::Value params; @@ -561,6 +592,75 @@ class Simulate_test : public beast::unit_test::Suite // test without autofill testTx(env, tx, validateOutput); } + + { + // autofill sponsor signature + + auto validateOutput = [&](json::Value const& resp, json::Value const& tx) { + auto result = resp[jss::result]; + checkBasicReturnValidity( + result, tx, env.seq(env.master), env.current()->fees().base); + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob))) + { + json::Value const metadata = getJsonMetadata(result); + + if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName))) + { + BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 2); + + auto node = metadata[sfAffectedNodes.jsonName][0u]; + if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName))) + { + auto modifiedNode = node[sfModifiedNode]; + BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot"); + auto previousFields = modifiedNode[sfPreviousFields]; + BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName)); + } + + auto node2 = metadata[sfAffectedNodes.jsonName][1u]; + if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName))) + { + auto modifiedNode = node2[sfModifiedNode]; + BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot"); + + auto previousFields = modifiedNode[sfPreviousFields]; + BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName)); + } + } + BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0); + BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + } + }; + + Account const sponsor("sponsor"); + env.fund(XRP(10000), sponsor); + env.close(); + + json::Value tx; + + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::AccountSet; + tx[sfDomain.jsonName] = kNewDomain; + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + tx[sfSponsorSignature.jsonName] = json::ValueType::Object; + + // test with autofill + testTx(env, tx, validateOutput); + + tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = ""; + tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; + + // test without autofill + testTx(env, tx, validateOutput); + } } void @@ -763,6 +863,58 @@ class Simulate_test : public beast::unit_test::Suite } } + void + testSuccessfulSponsoredTransactionMultisigned() + { + testcase("Successful sponsored multi-signed transaction"); + + using namespace jtx; + Env env(*this); + Account const sponsor("sponsor"); + Account const signer("signer"); + env.fund(XRP(10000), sponsor, signer); + env.close(); + + env(signers(sponsor, 1, {{signer, 1}})); + env.close(); + + auto validateOutput = [&](json::Value const& resp, json::Value const& tx) { + auto const result = resp[jss::result]; + // Verifies Fee autofill counts nested sponsor-signature signers. + auto const expectedFee = env.current()->fees().base * 2; + checkBasicReturnValidity(result, tx, env.seq(env.master), expectedFee); + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob))) + { + json::Value const metadata = getJsonMetadata(result); + BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + } + }; + + json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::AccountSet; + tx[sfDomain] = "123ABC"; + tx[sfSponsor.jsonName] = sponsor.human(); + tx[sfSponsorFlags.jsonName] = spfSponsorFee; + tx[sfSponsorSignature.jsonName] = json::ValueType::Object; + tx[sfSponsorSignature.jsonName][sfSigners.jsonName] = json::ValueType::Array; + + json::Value signerObj; + signerObj[sfSigner][jss::Account] = signer.human(); + tx[sfSponsorSignature.jsonName][sfSigners.jsonName].append(signerObj); + + // Leave Fee unset so simulate must autofill it after sponsor signer normalization. + BEAST_EXPECT(!tx.isMember(jss::Fee)); + testTx(env, tx, validateOutput, false); + } + void testTransactionSigningFailure() { @@ -1195,6 +1347,7 @@ public: testTransactionNonTecFailure(); testTransactionTecFailure(); testSuccessfulTransactionMultisigned(); + testSuccessfulSponsoredTransactionMultisigned(); testTransactionSigningFailure(); testInvalidSingleAndMultiSigningTransaction(); testMultisignedBadPubKey(); diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index c3bf707ec4..4dae475b63 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -880,6 +881,125 @@ class Transaction_test : public beast::unit_test::Suite } } + void + testSignForNetworkIDValidation() + { + testcase("SignFor NetworkID validation"); + using namespace test::jtx; + + Account const owner{"owner"}; + Account const signer{"signer"}; + + auto makeConfig = [](std::uint32_t networkID) { + return envconfig([networkID](std::unique_ptr cfg) { + cfg->networkId = networkID; + return cfg; + }); + }; + + auto setupEnv = [&](Env& env) { + env.fund(XRP(10'000), owner, signer); + env.close(); + env(signers(owner, 1, {{signer, 1}})); + env.close(); + }; + + auto makeTx = [&](Env& env) { + json::Value tx; + tx[jss::TransactionType] = jss::AccountSet; + tx[jss::Account] = owner.human(); + tx[jss::Sequence] = env.seq(owner); + tx[jss::Fee] = "100"; + tx[jss::SigningPubKey] = ""; + return tx; + }; + + auto signFor = [&](Env& env, json::Value const& tx) { + json::Value signReq; + signReq[jss::tx_json] = tx; + signReq[jss::account] = signer.human(); + signReq[jss::secret] = signer.name(); + return env.rpc("json", "sign_for", to_string(signReq))[jss::result]; + }; + + // Test case: NetworkID < 1024 - field is not required + { + Env env{*this, makeConfig(500)}; + setupEnv(env); + + auto tx = makeTx(env); + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::status] == "success"); + BEAST_EXPECT(!result[jss::tx_json].isMember(jss::NetworkID)); + } + + // Test case: NetworkID > 1024 - missing NetworkID field + { + Env env{*this, makeConfig(2040)}; + setupEnv(env); + + auto tx = makeTx(env); + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Missing field 'tx_json.NetworkID'."); + } + + // Test case: NetworkID > 1024 - NetworkID field is not a number + { + Env env{*this, makeConfig(2040)}; + setupEnv(env); + + auto tx = makeTx(env); + tx[jss::NetworkID] = "not_a_number"; + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'."); + } + + // Test case: NetworkID > 1024 - NetworkID field is not integral + { + Env env{*this, makeConfig(2040)}; + setupEnv(env); + + auto tx = makeTx(env); + tx[jss::NetworkID] = 2040.1; + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'."); + } + + // Test case: NetworkID > 1024 - NetworkID field is different from + // actual NetworkID + { + Env env{*this, makeConfig(2040)}; + setupEnv(env); + + auto tx = makeTx(env); + tx[jss::NetworkID] = 9999; + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::error] == "invalidParams"); + BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'."); + } + + // Test case: NetworkID > 1024 - NetworkID field is correct + { + Env env{*this, makeConfig(2040)}; + setupEnv(env); + + auto tx = makeTx(env); + tx[jss::NetworkID] = 2040; + auto result = signFor(env, tx); + + BEAST_EXPECT(result[jss::status] == "success"); + BEAST_EXPECT(result[jss::tx_json][jss::NetworkID].asUInt() == 2040); + } + } + public: void run() override @@ -889,6 +1009,8 @@ public: FeatureBitset const all{testableAmendments()}; testWithFeats(all); + + testSignForNetworkIDValidation(); } void diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 4a9c12c96b..60ea622616 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -202,8 +202,6 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (ec) return; } - - return; } void @@ -218,7 +216,6 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En auto ip = env.app().config()[Sections::kPortWs].get(Keys::kIp); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) doRequest(yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec); - return; } void @@ -235,7 +232,6 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En auto const ip = env.app().config()[Sections::kPortRpc].get(Keys::kIp); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) doRequest(yield, makeHTTPRequest(*ip, *port, body, fields), *ip, *port, secure, resp, ec); - return; } static auto diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index 3fc5d015b0..b583f821a4 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -15,8 +15,8 @@ namespace xrpl::detail { /** - Create a directory and remove it when it's done -*/ + * Create a directory and remove it when it's done + */ class DirGuard { protected: @@ -93,8 +93,8 @@ public: }; /** - Write a file in a directory and remove when done -*/ + * Write a file in a directory and remove when done + */ class FileDirGuard : public DirGuard { protected: diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 3390014cb8..c17fdf3bf0 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -199,7 +199,8 @@ namespace test { //------------------------------------------------------------------------------ -/** Manager for children running unit tests +/** + * Manager for children running unit tests */ class MultiRunnerParent : private detail::MultiRunnerBase { @@ -234,7 +235,8 @@ public: //------------------------------------------------------------------------------ -/** A class to run a subset of unit tests +/** + * A class to run a subset of unit tests */ class MultiRunnerChild : public beast::unit_test::Runner, private detail::MultiRunnerBase diff --git a/src/test/unit_test/utils.h b/src/test/unit_test/utils.h index 677bbff31b..d4b1e5f7f4 100644 --- a/src/test/unit_test/utils.h +++ b/src/test/unit_test/utils.h @@ -6,9 +6,11 @@ namespace xrpl::test { -/// Compare two SecretKey objects for equality. -/// SecretKey::operator== is deleted, so a named function is used -/// to avoid member-function lookup shadowing free-function overloads. +/** + * Compare two SecretKey objects for equality. + * SecretKey::operator== is deleted, so a named function is used + * to avoid member-function lookup shadowing free-function overloads. + */ inline bool equal(SecretKey const& lhs, SecretKey const& rhs) { diff --git a/src/tests/libxrpl/basics/Buffer.cpp b/src/tests/libxrpl/basics/Buffer.cpp index 5b34585fcf..9cdf610282 100644 --- a/src/tests/libxrpl/basics/Buffer.cpp +++ b/src/tests/libxrpl/basics/Buffer.cpp @@ -83,7 +83,7 @@ TEST_F(BufferTest, buffer) x = b0; EXPECT_EQ(x, b0); EXPECT_TRUE(sane(x)); -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-assign-overloaded" #endif @@ -95,7 +95,7 @@ TEST_F(BufferTest, buffer) EXPECT_EQ(y, b3); EXPECT_TRUE(sane(y)); -#if defined(__clang__) +#ifdef __clang__ #pragma clang diagnostic pop #endif } diff --git a/src/tests/libxrpl/basics/Number.cpp b/src/tests/libxrpl/basics/Number.cpp index e8c4e4209f..36e1b4a700 100644 --- a/src/tests/libxrpl/basics/Number.cpp +++ b/src/tests/libxrpl/basics/Number.cpp @@ -48,6 +48,22 @@ fmt(BigInt const& value) return out; } +BigInt +toBigInt(Number const& n) +{ + BigInt v = n.mantissa(); + auto e = n.exponent(); + + for (; e > 0; --e) + v *= 10; + for (; e < 0; ++e) + { + EXPECT_EQ(v % 10, 0); + v /= 10; + } + return v; +} + template static T pow10(int n) @@ -166,6 +182,35 @@ TEST(NumberTest, limits) caught = true; } EXPECT_TRUE(caught); + + if (scale == MantissaRange::MantissaScale::Large330) + { + // Normalization with the other scales, including the older large mantissa scales, will + // overflow. + Number const bigNum{Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}}; + // The display of large exponents won't go above kMaxExponent + EXPECT_EQ(to_string(bigNum), "9223372036854775810e32768") << bigNum; + // Perhaps surprisingly, this is ok, because the exponent range is related to when the + // number is _normalized_, and for mantissas > kMaxRep, the accessors return values that + // are not normalized. + EXPECT_EQ(bigNum.mantissa(), 922337203685477581ULL) << bigNum.mantissa(); + EXPECT_EQ(bigNum.exponent(), 32769) << bigNum.exponent(); + } + else + { + try + { + Number{Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}}; + ADD_FAILURE(); + } + catch (std::overflow_error const& e) + { + std::string const expected = + (scale == MantissaRange::MantissaScale::Small ? "Number::normalize 1" + : "Number::normalize 1.5"); + EXPECT_EQ(e.what(), expected) << e.what(); + } + } } } @@ -177,28 +222,39 @@ TEST(NumberTest, add) auto const scale = Number::getMantissaScale(); - using Case = std::tuple; - auto const cSmall = std::to_array( - {{Number{1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{-6'555'555'555'555'555, -29}, - Number{-1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{-9'999'999'999'999'344, -16}}, - {Number{-6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'344, -16}}, - {Number{}, Number{5}, Number{5}}, - {Number{5}, Number{}, Number{5}}, - {Number{5'555'555'555'555'555, -32768}, - Number{-5'555'555'555'555'554, -32768}, - Number{0}}, - {Number{-9'999'999'999'999'999, -31}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'990, -16}}}); + EXPECT_EQ(Number::getround(), Number::RoundingMode::ToNearest) + << to_string(Number::getround()); + + using Case = std::tuple; + // TODO: Move these to the blocks where they're used + auto const cSmall = std::to_array({ + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'066, -15}, + __LINE__}, + {Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}, + Number{-1'000'000'000'000'066, -15}, + __LINE__}, + {Number{-1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{-9'999'999'999'999'344, -16}, + __LINE__}, + {Number{-6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'344, -16}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, + {Number{5}, Number{}, Number{5}, __LINE__}, + {Number{5'555'555'555'555'555, -32768}, + Number{-5'555'555'555'555'554, -32768}, + Number{0}, + __LINE__}, + {Number{-9'999'999'999'999'999, -31}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'990, -16}, + __LINE__}, + }); auto const cLarge = std::to_array( // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items from C @@ -206,45 +262,57 @@ TEST(NumberTest, add) { {Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'065'556, -18}}, + Number{1'000'000'000'000'065'556, -18}, + __LINE__}, {Number{-1'000'000'000'000'000, -15}, Number{-6'555'555'555'555'555, -29}, - Number{-1'000'000'000'000'065'556, -18}}, + Number{-1'000'000'000'000'065'556, -18}, + __LINE__}, {Number{-1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{-6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, - {Number{}, Number{5}, Number{5}}, - {Number{5}, Number{}, Number{5}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, + {Number{5}, Number{}, Number{5}, __LINE__}, {Number{5'555'555'555'555'555'000, -32768}, Number{-5'555'555'555'555'554'000, -32768}, - Number{0}}, + Number{0}, + __LINE__}, {Number{-9'999'999'999'999'999, -31}, Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'990, -16}}, + Number{9'999'999'999'999'990, -16}, + __LINE__}, // Items from cSmall expanded for the larger mantissa {Number{1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -35}, - Number{1'000'000'000'000'000'066, -18}}, + Number{1'000'000'000'000'000'066, -18}, + __LINE__}, {Number{-1'000'000'000'000'000'000, -18}, Number{-6'555'555'555'555'555'555, -35}, - Number{-1'000'000'000'000'000'066, -18}}, + Number{-1'000'000'000'000'000'066, -18}, + __LINE__}, {Number{-1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -35}, - Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{-6'555'555'555'555'555'555, -35}, Number{1'000'000'000'000'000'000, -18}, - Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}}, - {Number{}, Number{5}, Number{5}}, + Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{}, Number{5}, Number{5}, __LINE__}, {Number{5'555'555'555'555'555'555, -32768}, Number{-5'555'555'555'555'555'554, -32768}, - Number{0}}, + Number{0}, + __LINE__}, {Number{true, 9'999'999'999'999'999'999ULL, -37, Number::Normalized{}}, Number{1'000'000'000'000'000'000, -18}, - Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}}, - {Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}}, + Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}, __LINE__}, // Test extremes { // Each Number operand rounds up, so the actual mantissa is @@ -252,6 +320,7 @@ TEST(NumberTest, add) Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}}, Number{2, 19}, + __LINE__, }, { // Does not round. Mantissas are going to be > maxRep, so if @@ -262,21 +331,28 @@ TEST(NumberTest, add) Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}}, + __LINE__, }, }); auto const cLargeLegacy = std::to_array({ - {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}}, + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__}, + }); + auto const cLarge320 = std::to_array({ + {Number{Number::kMaxRep}, + Number{6, -1}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, }); auto const cLargeCorrected = std::to_array({ - {Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}}, + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep}, __LINE__}, }); auto test = [](auto const& c) { - for (auto const& [x, y, z] : c) + for (auto const& [x, y, z, line] : c) { auto const result = x + y; std::stringstream ss; ss << x << " + " << y << " = " << result << ". Expected: " << z; - EXPECT_EQ(result, z) << ss.str(); + EXPECT_EQ(result, z) << ss.str() << " Line: " << line; } }; if (scale == MantissaRange::MantissaScale::Small) @@ -290,9 +366,28 @@ TEST(NumberTest, add) { test(cLargeLegacy); } + else if (scale == MantissaRange::MantissaScale::Large320) + { + test(cLarge320); + } else { test(cLargeCorrected); + + // This has to be created in this block, because normalization with the other + // scales, including the older large mantissa scales, will overflow. + Number const bigResult{ + Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}}; + auto const cBigNums = std::to_array({ + { + // Add 3 to the mantissa to avoid rounding + Number::max(), + Number{3, Number::kMaxExponent}, + bigResult, + __LINE__, + }, + }); + test(cBigNums); } } { @@ -319,80 +414,150 @@ TEST(NumberTest, sub) auto const scale = Number::getMantissaScale(); - using Case = std::tuple; + using Case = std::tuple; auto const cSmall = std::to_array( {{Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{9'999'999'999'999'344, -16}}, + Number{9'999'999'999'999'344, -16}, + __LINE__}, {Number{6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{-9'999'999'999'999'344, -16}}, - {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}}, + Number{-9'999'999'999'999'344, -16}, + __LINE__}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'001, -15}, - Number{-1'000'000'000'000'000, -30}}, + Number{-1'000'000'000'000'000, -30}, + __LINE__}, {Number{1'000'000'000'000'001, -15}, Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -30}}}); - auto const cLarge = std::to_array( + Number{1'000'000'000'000'000, -30}, + __LINE__}}); + auto const cLargeAll = std::to_array( // Note that items with extremely large mantissas need to be // calculated, because otherwise they overflow uint64. Items from C // with larger mantissa { {Number{1'000'000'000'000'000, -15}, Number{6'555'555'555'555'555, -29}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{6'555'555'555'555'555, -29}, Number{1'000'000'000'000'000, -15}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, - {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'001, -15}, - Number{-1'000'000'000'000'000, -30}}, + Number{-1'000'000'000'000'000, -30}, + __LINE__}, {Number{1'000'000'000'000'001, -15}, Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -30}}, + Number{1'000'000'000'000'000, -30}, + __LINE__}, // Items from cSmall expanded for the larger mantissa {Number{1'000'000'000'000'000'000, -18}, Number{6'555'555'555'555'555'555, -32}, - Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{6'555'555'555'555'555'555, -32}, Number{1'000'000'000'000'000'000, -18}, - Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}}, + Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}, + __LINE__}, {Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'000, -18}, - Number{0}}, + Number{0}, + __LINE__}, {Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'001, -18}, - Number{-1'000'000'000'000'000'000, -36}}, + Number{-1'000'000'000'000'000'000, -36}, + __LINE__}, {Number{1'000'000'000'000'000'001, -18}, Number{1'000'000'000'000'000'000, -18}, - Number{1'000'000'000'000'000'000, -36}}, - {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}}, - {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, - Number{1, 0}, - Number{(Number::kMaxRep / 10) + 1, 1}}, - {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, - Number{3, 0}, - Number{Number::kMaxRep}}, - {power(2, 63), Number{3, 0}, Number{Number::kMaxRep}}, + Number{1'000'000'000'000'000'000, -36}, + __LINE__}, + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}, __LINE__}, }); + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items from C + // with larger mantissa + auto const cLarge = std::to_array({ + // Anything larger than kMaxRep rounds up + {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, + Number{1, 0}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, + {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, + Number{3, 0}, + Number{Number::kMaxRep}, + __LINE__}, + {Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}}, + Number{1, 0}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, + {Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}}, + Number{3, 0}, + Number{Number::kMaxRep}, + __LINE__}, + {power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__}, + }); + auto const cLarge330 = std::to_array({ + // kMaxRep + 1 is below the half-way point, so it rounds down to kMaxRep when the Number + // is created. + {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, + Number{1, 0}, + Number{Number::kMaxRep - 1}, + __LINE__}, + {Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}}, + Number{3, 0}, + Number{Number::kMaxRep - 3}, + __LINE__}, + // kMaxRepUp -1 is above the half-way point, so it rounds up to kMaxRepUp when the + // Number is created. Subtracting 1 from that rounds up again. A little non-intuitive. + {Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}}, + Number{1, 0}, + Number{(Number::kMaxRep / 10) + 1, 1}, + __LINE__}, + // Subtracting 3 gets back down to kMaxRep + {Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}}, + Number{3, 0}, + Number{Number::kMaxRep}, + __LINE__}, + // 2^63 is the same as kMaxRep+1 + {power(2, 63), Number{3, 0}, Number{Number::kMaxRep - 3}, __LINE__}, + }); auto test = [](auto const& c) { - for (auto const& [x, y, z] : c) + for (auto const& [x, y, z, line] : c) { auto const result = x - y; std::stringstream ss; ss << x << " - " << y << " = " << result << ". Expected: " << z; - EXPECT_EQ(result, z) << ss.str(); + EXPECT_EQ(result, z) << ss.str() << " Line: " << line; } }; - if (scale == MantissaRange::MantissaScale::Small) + switch (scale) { - test(cSmall); - } - else - { - test(cLarge); + case MantissaRange::MantissaScale::Small: + test(cSmall); + break; + case MantissaRange::MantissaScale::LargeLegacy: + case MantissaRange::MantissaScale::Large320: + test(cLargeAll); + test(cLarge); + break; + case MantissaRange::MantissaScale::Large330: + test(cLargeAll); + test(cLarge330); + break; + default: + ADD_FAILURE(); + break; } } } @@ -1309,38 +1474,39 @@ TEST(NumberTest, to_string) auto const scale = Number::getMantissaScale(); - auto test = [](Number const& n, std::string const& expected) { + auto test = [](Number const& n, std::string const& expected, int line) { auto const result = to_string(n); std::stringstream ss; ss << "to_string(" << result << "). Expected: " << expected; - EXPECT_EQ(result, expected) << ss.str(); + EXPECT_EQ(result, expected) << ss.str() << " Line: " << line; }; - test(Number(-2, 0), "-2"); - test(Number(0, 0), "0"); - test(Number(2, 0), "2"); - test(Number(25, -3), "0.025"); - test(Number(-25, -3), "-0.025"); - test(Number(25, 1), "250"); - test(Number(-25, 1), "-250"); - test(Number(2, 20), "2e20"); - test(Number(-2, -20), "-2e-20"); + test(Number(-2, 0), "-2", __LINE__); + test(Number(0, 0), "0", __LINE__); + test(Number(2, 0), "2", __LINE__); + test(Number(25, -3), "0.025", __LINE__); + test(Number(-25, -3), "-0.025", __LINE__); + test(Number(25, 1), "250", __LINE__); + test(Number(-25, 1), "-250", __LINE__); + test(Number(2, 20), "2e20", __LINE__); + test(Number(-2, -20), "-2e-20", __LINE__); // Test the edges // ((exponent < -(25)) || (exponent > -(5))))) // or ((exponent < -(28)) || (exponent > -(8))))) - test(Number(2, -10), "0.0000000002"); - test(Number(2, -11), "2e-11"); + test(Number(2, -10), "0.0000000002", __LINE__); + test(Number(2, -11), "2e-11", __LINE__); - test(Number(-2, 10), "-20000000000"); - test(Number(-2, 11), "-2e11"); + test(Number(-2, 10), "-20000000000", __LINE__); + test(Number(-2, 11), "-2e11", __LINE__); + test(Number(-2, 11) - 1, "-200000000001", __LINE__); switch (scale) { case MantissaRange::MantissaScale::Small: - test(Number::min(), "1e-32753"); - test(Number::max(), "9999999999999999e32768"); - test(Number::lowest(), "-9999999999999999e32768"); + test(Number::min(), "1e-32753", __LINE__); + test(Number::max(), "9999999999999999e32768", __LINE__); + test(Number::lowest(), "-9999999999999999e32768", __LINE__); { NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); @@ -1348,65 +1514,132 @@ TEST(NumberTest, to_string) EXPECT_EQ(maxMantissa, (9'999'999'999'999'999)); test( Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()}, - "9999999999999999"); + "9999999999999999", + __LINE__); test( Number{true, (maxMantissa * 1000) + 999, -3, Number::Normalized()}, - "-9999999999999999"); + "-9999999999999999", + __LINE__); - test(Number{std::numeric_limits::max(), -3}, "9223372036854775"); + test( + Number{std::numeric_limits::max(), -3}, + "9223372036854775", + __LINE__); test( -(Number{std::numeric_limits::max(), -3}), - "-9223372036854775"); + "-9223372036854775", + __LINE__); test( - Number{std::numeric_limits::min(), 0}, "-9223372036854775e3"); + Number{std::numeric_limits::min(), 0}, + "-9223372036854775e3", + __LINE__); test( -(Number{std::numeric_limits::min(), 0}), - "9223372036854775e3"); + "9223372036854775e3", + __LINE__); } break; - case MantissaRange::MantissaScale::LargeLegacy: - case MantissaRange::MantissaScale::Large: + default: // Test the edges // ((exponent < -(28)) || (exponent > -(8))))) - test(Number::min(), "1e-32750"); - test(Number::max(), "9223372036854775807e32768"); - test(Number::lowest(), "-9223372036854775807e32768"); + test(Number::min(), "1e-32750", __LINE__); + test(Number::max(), "9223372036854775807e32768", __LINE__); + test(Number::lowest(), "-9223372036854775807e32768", __LINE__); { NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); EXPECT_EQ((maxMantissa), (9'999'999'999'999'999'999ULL)); test( - Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990"); + Number{false, maxMantissa, 0, Number::Normalized{}}, + "9999999999999999990", + __LINE__); test( - Number{true, maxMantissa, 0, Number::Normalized{}}, "-9999999999999999990"); + Number{true, maxMantissa, 0, Number::Normalized{}}, + "-9999999999999999990", + __LINE__); test( - Number{std::numeric_limits::max(), 0}, "9223372036854775807"); + Number{std::numeric_limits::max(), 0}, + "9223372036854775807", + __LINE__); test( -(Number{std::numeric_limits::max(), 0}), - "-9223372036854775807"); + "-9223372036854775807", + __LINE__); - // Because the absolute value of min is larger than max, it - // will be scaled down to fit under max. Since we're - // rounding towards zero, the 8 at the end is dropped. - test( - Number{std::numeric_limits::min(), 0}, - "-9223372036854775800"); - test( - -(Number{std::numeric_limits::min(), 0}), - "9223372036854775800"); + switch (scale) + { + case MantissaRange::MantissaScale::Large330: + // Because the absolute value of min() is larger than max(), it + // will be rounded down toward max() + test( + Number{std::numeric_limits::min(), 0}, + "-9223372036854775807", + __LINE__); + test( + -(Number{std::numeric_limits::min(), 0}), + "9223372036854775807", + __LINE__); + break; + default: + // Because the absolute value of min() is larger than max(), it + // will be scaled down to fit under max(). Since we're + // rounding towards zero, the 8 at the end is dropped. + test( + Number{std::numeric_limits::min(), 0}, + "-9223372036854775800", + __LINE__); + test( + -(Number{std::numeric_limits::min(), 0}), + "9223372036854775800", + __LINE__); + break; + } } + switch (scale) + { + case MantissaRange::MantissaScale::Large330: + // Rounding to nearest, since the mantissa is below the halfway point from + // kMaxRep to kMaxRepUp, it will be rounded down to kMaxRep + test( + Number{std::numeric_limits::max(), 0} + 1, + "9223372036854775807", + __LINE__); + test( + -(Number{std::numeric_limits::max(), 0} + 1), + "-9223372036854775807", + __LINE__); + break; + default: + // Rounding to nearest, since the mantissa is bigger than kMaxRep, the 8 + // will be dropped, and since that is bigger than 5, the result will be + // rounded up from 0 to 1. + test( + Number{std::numeric_limits::max(), 0} + 1, + "9223372036854775810", + __LINE__); + test( + -(Number{std::numeric_limits::max(), 0} + 1), + "-9223372036854775810", + __LINE__); + break; + } + // Rounding to nearest, will be rounded up to kMaxRepUp, but for different reasons + // depending on the scale. If older than "Large", it rounds up for the same reason + // "+1" rounds up. For "Large", since the mantissa is above the halfway point from + // kMaxRep to kMaxRepUp, it will be rounded up to kMaxRepUp. test( - Number{std::numeric_limits::max(), 0} + 1, "9223372036854775810"); + Number{std::numeric_limits::max(), 0} + 2, + "9223372036854775810", + __LINE__); test( - -(Number{std::numeric_limits::max(), 0} + 1), - "-9223372036854775810"); + -(Number{std::numeric_limits::max(), 0} + 2), + "-9223372036854775810", + __LINE__); break; - default: - EXPECT_TRUE(false); } } } @@ -1787,29 +2020,27 @@ TEST(NumberTest, upward_rounding_produces_value_not_below_exact_at_k_max_rep_cus BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); // What Number actually stored. - BigInt storedValue = BigInt(product.mantissa()); - for (int i = 0; i < product.exponent(); ++i) - storedValue *= 10; + BigInt const storedValue = toBigInt(product); BigInt const signedDifference = storedValue - exactProduct; auto const message = [&] { std::ostringstream os; - os << "\n" - << " a = " << fmt(BigInt(kAValue)) << "\n" + os << " a = " << fmt(BigInt(kAValue)) << "\n" << " b = " << fmt(BigInt(kBValue)) << "\n" << " exact a*b = " << fmt(exactProduct) << "\n" << " stored = " << fmt(storedValue) << "\n" << " stored - exact = " << fmt(signedDifference) << "\n" << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n" << " stored.mantissa = " << product.mantissa() << "\n" - << " stored.exponent = " << product.exponent() << "\n"; + << " stored.exponent = " << product.exponent() << "\n\n"; return os.str(); }; switch (scale) { - case MantissaRange::MantissaScale::Large: + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: EXPECT_TRUE(signedDifference >= 0) << message(); EXPECT_TRUE(signedDifference < pow10(product.exponent())) << message(); EXPECT_EQ(product.mantissa(), (std::numeric_limits::max() / 10) + 1); @@ -1882,22 +2113,22 @@ TEST(NumberTest, upward_division_returns_value_not_below_exact_on_large_scale) auto const message = [&] { std::ostringstream os; - os << "\n" - << " a = " << kAValue << "\n" + os << " a = " << kAValue << "\n" << " b = " << kBValue << "\n" << " exact a/b = " << fmt(exact) << "\n" << " stored a/b = " << fmt(stored) << "\n" << " stored - exact = " << fmt(diff) << " (negative => Upward gave value BELOW truth)\n" << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; + << " quotient.exponent = " << quotient.exponent() << "\n\n"; return os.str(); }; // Upward invariant: stored >= exact. Bug: stored < exact. switch (scale) { - case MantissaRange::MantissaScale::Large: + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: EXPECT_TRUE(stored >= exact) << message(); EXPECT_TRUE(diff < pow10(quotient.exponent())) << message(); break; @@ -1939,22 +2170,22 @@ TEST(NumberTest, downward_division_returns_value_not_above_exact_on_large_scale) auto const message = [&] { std::ostringstream os; - os << "\n" - << " a = " << kAValue << "\n" + os << " a = " << kAValue << "\n" << " b = " << kBValue << "\n" << " exact a/b = " << fmt(exact) << "\n" << " stored a/b = " << fmt(stored) << "\n" << " stored - exact = " << fmt(diff) << " (positive => Downward gave value ABOVE truth)\n" << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; + << " quotient.exponent = " << quotient.exponent() << "\n\n"; return os.str(); }; - // Downward invariant: stored <= exact. Bug: stored > exact. + // invariant: stored <= exact. Bug: stored > exact. switch (scale) { - case MantissaRange::MantissaScale::Large: + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: EXPECT_TRUE(stored <= exact) << message(); EXPECT_TRUE(diff > -pow10(quotient.exponent())) << message(); break; @@ -2006,22 +2237,22 @@ TEST(NumberTest, to_nearest_division_uses_dropped_digits_on_large_scale) auto const message = [&] { std::ostringstream os; - os << "\n" - << " a = " << kAValue << "\n" + os << " a = " << kAValue << "\n" << " b = " << kBValue << "\n" << " exact a/b = " << fmt(exact) << "\n" << " stored a/b = " << fmt(stored) << "\n" << " stored - exact = " << fmt(diff) << " (negative => ToNearest gave value BELOW truth)\n" << " quotient.mantissa = " << quotient.mantissa() << "\n" - << " quotient.exponent = " << quotient.exponent() << "\n"; + << " quotient.exponent = " << quotient.exponent() << "\n\n"; return os.str(); }; - // ToNearest should account for dropped digits beyond the visible "5". + // invariant: stored >= exact. Bug: stored < exact. switch (scale) { - case MantissaRange::MantissaScale::Large: + case MantissaRange::MantissaScale::Large320: + case MantissaRange::MantissaScale::Large330: EXPECT_TRUE(stored >= exact) << message(); EXPECT_TRUE(diff < pow10(quotient.exponent())) << message(); break; @@ -2039,4 +2270,627 @@ TEST(NumberTest, to_nearest_division_uses_dropped_digits_on_large_scale) } } +TEST(NumberTest, subtraction_rounding) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + + auto const exp = Number::mantissaLog(); + // SubCase is + // * offset: offset from exp + // * extraB: whether to include 1e"exp" in "b" + // * aString: expected string value for "a" + // * bString: expected string value for "b" + // There aren't too many valid combinations for test cases here. If extraB is true, + // offset can really only be 2, because any larger and the mantissa can't be represented + // without loss. Offset can't be less than 2, or there's no error. + using SubCase = std::tuple; + auto const c = std::to_array({ + {2, + true, + scale == MantissaRange::MantissaScale::Small ? "100000000000000000" + : "100000000000000000000", + scale == MantissaRange::MantissaScale::Small ? "-1000000000000001" + : "-1000000000000000001"}, + {2, + false, + scale == MantissaRange::MantissaScale::Small ? "100000000000000000" + : "100000000000000000000", + "-1"}, + {30, + false, + scale == MantissaRange::MantissaScale::Small + ? "1000000000000000000000000000000000000000000000" + : "1000000000000000000000000000000000000000000000000", + "-1"}, + }); + + for (auto const& [offset, extraB, aString, bString] : c) + { + Number const a{1LL, exp + offset}; + Number const b{-((extraB ? Number{1, exp} : kNumZero) + 1)}; + + auto const bigA = toBigInt(a); + auto const bigB = toBigInt(b); + + EXPECT_EQ(bigA, BigInt{aString}); + EXPECT_EQ(bigB, BigInt{bString}); + + auto construct = [&a, &b](Number::RoundingMode r) { + NumberRoundModeGuard const roundGuard{r}; + auto const sum = a + b; + BigInt const stored = toBigInt(sum); + return std::make_pair(r, std::make_pair(stored, sum)); + }; + + BigInt const exact = bigA + bigB; + + auto const sums = [&]() { + std::map> r; + r.emplace(construct(Number::RoundingMode::TowardsZero)); + r.emplace(construct(Number::RoundingMode::Upward)); + r.emplace(construct(Number::RoundingMode::Downward)); + r.emplace(construct(Number::RoundingMode::ToNearest)); + return r; + }(); + + auto const message = [&](auto const& r, auto const& sum) { + std::ostringstream os; + os << " a = " << a << " (" << fmt(bigA) << ")\n b = " << b + << " (" << fmt(bigB) << ")\n exact a + b = " << fmt(exact) << "\n"; + + auto const diff = sum.first - exact; + auto const rLabel = to_string(r); + os << std::string(15 - rLabel.length(), ' ') << rLabel << " = " << fmt(sum.first) + << "\n difference = " << fmt(diff) << "\n\n"; + + return os.str(); + }; + + auto const expectedExponent = + offset - (scale == MantissaRange::MantissaScale::Small && extraB ? 1 : 0); + auto const epsilon = pow10(expectedExponent); + for (auto const& [r, sum] : sums) + { + auto diff = sum.first - exact; + switch (scale) + { + case MantissaRange::MantissaScale::Small: + case MantissaRange::MantissaScale::LargeLegacy: + case MantissaRange::MantissaScale::Large320: { + // Without the fix, all the results but one round up + if (r == Number::RoundingMode::Downward) + { + // Downward works because the Guard sign is negative, and Downward + // returns Up instead of Down if negative and there's a remainder, + // whereas TowardsZero always returns Down. + EXPECT_LT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum); + } + else + { + EXPECT_GT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, 1) << message(r, sum); + } + break; + } + default: { + EXPECT_LE(sum.second.exponent(), expectedExponent) << message(r, sum); + switch (r) + { + case Number::RoundingMode::Upward: + case Number::RoundingMode::ToNearest: + EXPECT_GT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, 1) << message(r, sum); + break; + default: + EXPECT_LT(sum.first, exact) << message(r, sum); + EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum); + } + } + } + } + } + } +} + +TEST(NumberTest, normalization_cusp_tonearest_and_downward) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + + constexpr auto kMaxRep = Number::kMaxRep; + + // Both ToNearest and Downward should round to `below` + auto constexpr actual = static_cast(kMaxRep) + 1; + Number const below{static_cast(kMaxRep), 0}; + Number const above{false, static_cast(kMaxRep) + 3, 0, Number::Normalized{}}; + + auto construct = [](Number::RoundingMode mode) { + NumberRoundModeGuard const roundGuard{mode}; + return Number(false, actual, 0, Number::Normalized{}); + }; + Number const upward = construct(Number::RoundingMode::Upward); + + Number const toNearest = construct(Number::RoundingMode::ToNearest); + + Number const downward = construct(Number::RoundingMode::Downward); + + auto message = [&] { + std::ostringstream log; + log << " actual = " << actual << " (kMaxRep + 1)\n" + << " below = " << below << " (kMaxRep, distance 1)\n" + << " above = " << above << " (kMaxRep + 3, distance 2)\n" + << " Upward = " << upward << "\n" + << " ToNearest = " << toNearest << "\n" + << " Downward = " << downward << "\n\n"; + return log.str(); + }; + + switch (scale) + { + case MantissaRange::MantissaScale::Small: + // With the small mantissa, everything but Downward rounds UP, including the + // reference values, "above" and "below" + + EXPECT_EQ(below, above) << message(); + EXPECT_EQ(upward, above) << message(); + EXPECT_EQ(toNearest, above) << message(); + + EXPECT_LT(downward, below) << message(); + + break; + + case MantissaRange::MantissaScale::LargeLegacy: + case MantissaRange::MantissaScale::Large320: + // Upward round UP + EXPECT_EQ(upward, above) << message(); + + // ToNearest rounds UP when the DOWN neighbor is strictly closer + EXPECT_EQ(toNearest, above) << message(); + EXPECT_GT(toNearest, below) << message(); + + // Downward undershoots: it returns a value below `below` + EXPECT_LT(downward, below) << message(); + + // Both should have given the same answer, but they differ + EXPECT_GT(toNearest, downward) << message(); + + break; + default: + // Covers "Large" and any newly added scales + + // Upward round UP + EXPECT_EQ(upward, above) << message(); + + // ToNearest rounds to the strictly closer DOWN neighbor + EXPECT_NE(toNearest, above) << message(); + EXPECT_EQ(toNearest, below) << message(); + + // Downward also rounds to `below` + EXPECT_EQ(downward, below) << message(); + + // ToNearest rounds to downward + EXPECT_EQ(toNearest, downward) << message(); + break; + } + } +} + +TEST(NumberTest, number_add_directed_sign_wrong) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + { + // Two negative numbers with the same exponent + Number const a{-6, Number::mantissaLog()}; + Number const b{a - 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); + + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) + { + EXPECT_EQ(exact, BigInt{"-12000000000000003"}); + } + else + { + EXPECT_EQ(exact, BigInt{"-12000000000000000003"}); + } + + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } + + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + if (scale == MantissaRange::MantissaScale::Large330) + { + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 + } + else + { + EXPECT_GT(valueDown, exact) + << message(); // Downward rounded toward zero (too high) + EXPECT_LT(valueUp, exact) << message(); // Upward rounded toward -inf (too low) + } + } + + { + // Positive control: the same magnitudes with a positive result round + Number const pa{6, Number::mantissaLog()}; + Number const pb{pa + 3}; + EXPECT_TRUE(pa.exponent() == pb.exponent() && abs(pb) > abs(pa)); + BigInt const pexact = toBigInt(pa) + toBigInt(pb); // 12'000'000'000'000'000'003 + + Number pdown, pup; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + pdown = pa + pb; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + pup = pa + pb; + } + auto const valuePDown = toBigInt(pdown); + auto const valuePUp = toBigInt(pup); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(pexact) << "\n downward = " << fmt(valuePDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valuePUp) + << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valuePDown, pexact) << message(); // correct for positive results + EXPECT_GE(valuePUp, pexact) << message(); + } + + { + // Mixed sign numbers with the same exponent: negative second value + Number const a{1, Number::mantissaLog()}; + Number const b{Number{-9, Number::mantissaLog()} - 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); + + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) + { + EXPECT_EQ(exact, BigInt{"-8000000000000003"}); + } + else + { + EXPECT_EQ(exact, BigInt{"-8000000000000000003"}); + } + + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } + + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 + } + + { + // Mixed sign numbers with the same exponent: negative first value + Number const a{-1, Number::mantissaLog()}; + Number const b{Number{9, Number::mantissaLog()} + 3}; + EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a)); + + BigInt const exact = toBigInt(a) + toBigInt(b); + if (scale == MantissaRange::MantissaScale::Small) + { + EXPECT_EQ(exact, BigInt{"8000000000000003"}); + } + else + { + EXPECT_EQ(exact, BigInt{"8000000000000000003"}); + } + + Number down, up; + { + NumberRoundModeGuard const g{Number::RoundingMode::Downward}; + down = a + b; + } + { + NumberRoundModeGuard const g{Number::RoundingMode::Upward}; + up = a + b; + } + + auto const valueDown = toBigInt(down); + auto const valueUp = toBigInt(up); + auto message = [&] { + std::ostringstream log; + log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown) + << " (correct rounding: <= exact)" + << "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n"; + return log.str(); + }; + + EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero + EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0 + } + } +} + +TEST(NumberTest, number_add_to_nearest_picks_farther) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest}; + + auto const scale = Number::getMantissaScale(); + + // Case is + using Case = std::pair; + + auto const c = std::to_array({ + {Number{5'175'909'259'972'499'745LL, 22}, -1'074'951'375'311'646'003}, + {Number{1}, -1'074'956'551'220'905'975}, + {Number{1, 10}, -1'074'956'551'220'905'975}, + {Number{1, 20}, -1'074'956'551'220'905'975}, + {Number{1, 27}, -1'074'956'551'220'905'975}, + {Number{1, 28}, -1'074'956'551'220'905'974}, + {Number{1, 31}, -1'074'956'551'220'904'975}, + }); + + for (auto const& [y, expectedQ] : c) + { + Number const x{-1'074'956'551'220'905'975LL, 28}; + Number const res = x + y; + + BigInt const exact = toBigInt(x) + toBigInt(y); + BigInt const vres = toBigInt(res); + + BigInt ulp = 1; + for (int i = 0; i < res.exponent(); ++i) + ulp *= 10; + + BigInt const q = (exact - ulp / 2) / ulp; + Number const normalizedExact{static_cast(q), res.exponent()}; + BigInt const norm = toBigInt(normalizedExact); + + auto message = [&](auto const& comp) { + std::ostringstream log; + log << fmt(q) + " != " + fmt(comp) << "\n" + << " x = " << x << "\n y = " << y + << "\n exact = " << fmt(exact) + << "\n result (x + y) = " << fmt(vres) + << "\n normalize(exact) = " << fmt(norm) << "\n\n"; + return log.str(); + }; + + if (scale == MantissaRange::MantissaScale::Small) + { + auto const comp = toBigInt(Number{expectedQ, -3}); + EXPECT_EQ(q, comp) << message(comp); + } + else + { + EXPECT_EQ(q, expectedQ) << message(BigInt(expectedQ)); + } + EXPECT_EQ(normalizedExact, res); + } + } +} + +TEST(NumberTest, number_cusp_rounding_with_fractional_parts) +{ + for (auto const mantissaScale : MantissaRange::getAllScales()) + { + NumberMantissaScaleGuard const mg{mantissaScale}; + + auto const scale = Number::getMantissaScale(); + + Number const below{static_cast(Number::kMaxRep), 0}; + Number const above{false, Number::kMaxRepUp, 0, Number::Normalized{}}; + + auto header = [&] { + std::ostringstream log; + log << "Scale: " << to_string(mantissaScale) << ", Below: " << below + << ", Above: " << above << "\n"; + return log.str(); + }; + + auto const zeroPointFour = Number(4, -1); + auto const zeroPointFive = Number(5, -1); + auto const zeroPointSix = Number(6, -1); + auto const onePointFour = Number(14, -1); + auto const onePointFive = Number(15, -1); + auto const onePointSix = Number(16, -1); + auto const twoPointFour = Number(24, -1); + auto const twoPointFive = Number(25, -1); + auto const twoPointSix = Number(26, -1); + + auto const operands = std::to_array({ + zeroPointFour, + zeroPointFive, + zeroPointSix, + onePointFour, + onePointFive, + onePointSix, + twoPointFour, + twoPointFive, + twoPointSix, + }); + + auto const modes = std::to_array({ + Number::RoundingMode::ToNearest, + Number::RoundingMode::TowardsZero, + Number::RoundingMode::Downward, + Number::RoundingMode::Upward, + }); + + // Addition cases test kMaxRep + Operand + for (auto const& mode : modes) + { + for (auto const& operand : operands) + { + NumberRoundModeGuard const rg{mode}; + + auto const expectedValue = [&]() { + // Returns "above" by default. The checks here are for exceptions. + if (scale >= MantissaRange::MantissaScale::Large330) + { + if (mode == Number::RoundingMode::ToNearest && operand < onePointFive) + return below; + if (mode == Number::RoundingMode::TowardsZero || + mode == Number::RoundingMode::Downward) + return below; + } + if (scale == MantissaRange::MantissaScale::Large320) + { + if (mode == Number::RoundingMode::ToNearest) + { + if (operand < zeroPointFive) + return below; + } + if (mode == Number::RoundingMode::TowardsZero || + mode == Number::RoundingMode::Downward) + { + if (operand >= onePointFour) + return below - 7; + return below; + } + } + if (scale == MantissaRange::MantissaScale::LargeLegacy) + { + if (mode == Number::RoundingMode::ToNearest) + { + if (operand < zeroPointFive) + return below; + if (operand <= zeroPointSix) + return below - 7; + } + if (mode == Number::RoundingMode::TowardsZero || + mode == Number::RoundingMode::Downward) + { + if (operand >= onePointFour) + return below - 7; + return below; + } + if (mode == Number::RoundingMode::Upward && operand <= zeroPointSix) + return below - 7; + } + if (scale == MantissaRange::MantissaScale::Small && + mode == Number::RoundingMode::Upward) + return above + 1000; + return above; + }(); + + Number const actual = below + operand; + + auto message = [&] { + std::stringstream ss; + ss << header() << "kMaxRep + " << operand << " rounded " << to_string(mode) + << " to " << actual << ". Expected: " << expectedValue; + return ss.str(); + }; + EXPECT_EQ(actual, expectedValue) << message(); + } + } + + // Subtraction cases test kMaxRepUp - Operand + for (auto const& mode : modes) + { + for (auto const& operand : operands) + { + NumberRoundModeGuard const rg{mode}; + + auto const expectedValue = [&]() { + if (scale >= MantissaRange::MantissaScale::Large330) + { + if (mode == Number::RoundingMode::ToNearest && operand > onePointFive) + return below; + if (mode == Number::RoundingMode::TowardsZero || + mode == Number::RoundingMode::Downward) + return below; + } + if (scale == MantissaRange::MantissaScale::LargeLegacy || + scale == MantissaRange::MantissaScale::Large320) + { + if (mode == Number::RoundingMode::ToNearest) + { + if (operand >= twoPointSix) + return below; + } + if (mode == Number::RoundingMode::TowardsZero) + { + if (operand >= onePointFour) + return below - 7; + } + if (mode == Number::RoundingMode::Downward) + { + if (operand <= onePointSix) + return below - 7; + return below; + } + } + if (scale == MantissaRange::MantissaScale::Small) + { + if (mode == Number::RoundingMode::Downward) + return below - 1000; + if (mode == Number::RoundingMode::Upward) + return below; + } + return above; + }(); + + Number const actual = above - operand; + + auto message = [&] { + std::stringstream ss; + ss << header() << "kMaxRepUp - " << operand << " rounded " << to_string(mode) + << " to " << actual << ". Expected: " << expectedValue; + return ss.str(); + }; + EXPECT_EQ(actual, expectedValue) << message(); + } + } + } +} + } // namespace xrpl diff --git a/src/tests/libxrpl/basics/base_uint_test.cpp b/src/tests/libxrpl/basics/base_uint_test.cpp index 365b43930c..c9bfc35c94 100644 --- a/src/tests/libxrpl/basics/base_uint_test.cpp +++ b/src/tests/libxrpl/basics/base_uint_test.cpp @@ -122,6 +122,73 @@ struct BaseUintTest : public ::testing::Test } }; +using BaseUintDeathTest = BaseUintTest; + +TEST_F(BaseUintDeathTest, fromRaw_size_mismatch) +{ + // ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts. Rather than twist + // these tests into knots to make them work, just skip them. +#ifdef ENABLE_VOIDSTAR + GTEST_SKIP() << "ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts."; +#else + auto smallConstruct = [] { + // Container smaller than the base_uint (8 bytes vs 12 bytes for + // test96). Only the first 8 bytes are copied; the remaining 4 bytes + // stay zero. + Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8}; + BaseUInt96 const result = BaseUInt96::fromRaw(tooSmall); + auto const resultText = to_string(result); + EXPECT_EQ(resultText, "010203040506070800000000") << resultText; + }; + EXPECT_DEBUG_DEATH(smallConstruct(), "input size match"); + + auto largeConstruct = [] { + // Container larger than the base_uint (16 bytes vs 12 bytes for + // test96). Only the first 12 bytes are copied; the extra bytes are + // ignored. + Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + BaseUInt96 const result = BaseUInt96::fromRaw(tooBig); + auto const resultText = to_string(result); + EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText; + }; + EXPECT_DEBUG_DEATH(largeConstruct(), "input size match"); + + auto smallCopy = [] { + // Container smaller than the base_uint (8 bytes vs 12 bytes for + // test96). Only the first 8 bytes are copied; the remaining 4 bytes + // stay zero. + Blob const tooSmall{1, 2, 3, 4, 5, 6, 7, 8}; + BaseUInt96 result{}; + --result; + { + auto const originalText = to_string(result); + EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText; + } + result = tooSmall; + auto const resultText = to_string(result); + EXPECT_EQ(resultText, "010203040506070800000000") << resultText; + }; + EXPECT_DEBUG_DEATH(smallCopy(), "input size match"); + + auto const largeCopy = [] { + // Container larger than the base_uint (16 bytes vs 12 bytes for + // test96). Only the first 12 bytes are copied; the extra bytes are + // ignored. + Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + BaseUInt96 result{}; + --result; + { + auto const originalText = to_string(result); + EXPECT_EQ(originalText, "FFFFFFFFFFFFFFFFFFFFFFFF") << originalText; + } + result = tooBig; + auto const resultText = to_string(result); + EXPECT_EQ(resultText, "0102030405060708090A0B0C") << resultText; + }; + EXPECT_DEBUG_DEATH(largeCopy(), "input size match"); +#endif +} + TEST_F(BaseUintTest, base_uint) { static_assert(!std::is_constructible_v>); @@ -198,6 +265,19 @@ TEST_F(BaseUintTest, base_uint) EXPECT_EQ(d, 0); } + { + // There are several ways to create a zero. beast::kZero is tested above. Test some + // others. + BaseUInt96 const z1; + EXPECT_EQ(z1, z) << to_string(z1); + + BaseUInt96 const z2{}; + EXPECT_EQ(z2, z) << to_string(z2); + + BaseUInt96 const z3{0u}; + EXPECT_EQ(z3, z) << to_string(z3); + } + BaseUInt96 n{z}; n++; EXPECT_EQ(n, BaseUInt96(1)); @@ -271,25 +351,6 @@ TEST_F(BaseUintTest, base_uint) static_assert(BaseUInt96("000000000000000000000001").signum() == 1); static_assert(BaseUInt96("800000000000000000000000").signum() == 1); -// Everything within the #if should fail during compilation. -#if 0 - // Too few characters - static_assert(BaseUInt96("00000000000000000000000").signum() == 0); - - // Too many characters - static_assert(BaseUInt96("0000000000000000000000000").signum() == 0); - - // Non-hex characters - static_assert(BaseUInt96("00000000000000000000000 ").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000/").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000:").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000@").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000G").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000`").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000g").signum() == 1); - static_assert(BaseUInt96("00000000000000000000000~").signum() == 1); -#endif // 0 - // Using the constexpr constructor in a non-constexpr context // with an error in the parsing throws an exception. { diff --git a/src/tests/libxrpl/helpers/Account.h b/src/tests/libxrpl/helpers/Account.h index a92497f2c3..ca2bf68afb 100644 --- a/src/tests/libxrpl/helpers/Account.h +++ b/src/tests/libxrpl/helpers/Account.h @@ -38,35 +38,45 @@ public: */ explicit Account(std::string_view name, KeyType type = KeyType::Secp256k1); - /** @brief Return the human-readable name. */ + /** + * @brief Return the human-readable name. + */ [[nodiscard]] std::string const& name() const noexcept { return name_; } - /** @brief Return the AccountID. */ + /** + * @brief Return the AccountID. + */ [[nodiscard]] AccountID const& id() const noexcept { return id_; } - /** @brief Return the public key. */ + /** + * @brief Return the public key. + */ [[nodiscard]] PublicKey const& pk() const noexcept { return keyPair_.first; } - /** @brief Return the secret key. */ + /** + * @brief Return the secret key. + */ [[nodiscard]] SecretKey const& sk() const noexcept { return keyPair_.second; } - /** @brief Implicit conversion to AccountID. */ + /** + * @brief Implicit conversion to AccountID. + */ operator AccountID const&() const noexcept { return id_; diff --git a/src/tests/libxrpl/helpers/TestFamily.h b/src/tests/libxrpl/helpers/TestFamily.h index a5ff34a111..1a11d3bb68 100644 --- a/src/tests/libxrpl/helpers/TestFamily.h +++ b/src/tests/libxrpl/helpers/TestFamily.h @@ -20,11 +20,12 @@ namespace xrpl::test { -/** Test implementation of Family for unit tests. - - Uses an in-memory NodeStore database and simple caches. - The missingNode methods throw since tests shouldn't encounter missing nodes. -*/ +/** + * Test implementation of Family for unit tests. + * + * Uses an in-memory NodeStore database and simple caches. + * The missingNode methods throw since tests shouldn't encounter missing nodes. + */ class TestFamily : public Family { private: @@ -109,7 +110,9 @@ public: (*tnCache_).reset(); } - /** Access the test clock for time manipulation in tests. */ + /** + * Access the test clock for time manipulation in tests. + */ TestStopwatch& clock() { diff --git a/src/tests/libxrpl/helpers/TestServiceRegistry.h b/src/tests/libxrpl/helpers/TestServiceRegistry.h index 0108f886d3..5475b54dc6 100644 --- a/src/tests/libxrpl/helpers/TestServiceRegistry.h +++ b/src/tests/libxrpl/helpers/TestServiceRegistry.h @@ -23,7 +23,9 @@ namespace xrpl::test { -/** Logs implementation that creates TestSink instances. */ +/** + * Logs implementation that creates TestSink instances. + */ class TestLogs : public Logs { public: @@ -38,7 +40,9 @@ public: } }; -/** Simple NetworkIDService implementation for tests. */ +/** + * Simple NetworkIDService implementation for tests. + */ class TestNetworkIDService final : public NetworkIDService { public: @@ -56,14 +60,15 @@ private: std::uint32_t networkID_; }; -/** Test implementation of ServiceRegistry for unit tests. - - This class provides real implementations for services that can be - instantiated from libxrpl (such as Logs, io_context, caches), and - throws std::logic_error for services that require the full Application. - - Tests can subclass this to provide additional services they need. -*/ +/** + * Test implementation of ServiceRegistry for unit tests. + * + * This class provides real implementations for services that can be + * instantiated from libxrpl (such as Logs, io_context, caches), and + * throws std::logic_error for services that require the full Application. + * + * Tests can subclass this to provide additional services they need. + */ class TestServiceRegistry : public ServiceRegistry { TestLogs logs_{beast::Severity::Warning}; diff --git a/src/tests/libxrpl/helpers/TxTest.h b/src/tests/libxrpl/helpers/TxTest.h index cb75cd5ee0..98198e45f8 100644 --- a/src/tests/libxrpl/helpers/TxTest.h +++ b/src/tests/libxrpl/helpers/TxTest.h @@ -157,10 +157,10 @@ allFeatures(); */ struct TxResult { - TER ter; /**< The transaction engine result code. */ - bool applied; /**< Whether the transaction was applied to the ledger. */ - std::optional metadata; /**< Transaction metadata, if available. */ - std::shared_ptr tx; /**< Pointer to the submitted transaction. */ + TER ter; ///< The transaction engine result code. + bool applied; ///< Whether the transaction was applied to the ledger. + std::optional metadata; ///< Transaction metadata, if available. + std::shared_ptr tx; ///< Pointer to the submitted transaction. }; /** @@ -360,10 +360,14 @@ private: std::shared_ptr closedLedger_; std::shared_ptr openLedger_; - /** Transactions submitted to the open ledger, for canonical reordering on close. */ + /** + * Transactions submitted to the open ledger, for canonical reordering on close. + */ std::vector> pendingTxs_; - /** Current time (can be advanced arbitrarily for testing). */ + /** + * Current time (can be advanced arbitrarily for testing). + */ NetClock::time_point now_; }; diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp index e967b614b7..17c80899f9 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp @@ -40,6 +40,9 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) auto const mintedNFTokensValue = canonical_UINT32(); auto const burnedNFTokensValue = canonical_UINT32(); auto const firstNFTokenSequenceValue = canonical_UINT32(); + auto const sponsoredOwnerCountValue = canonical_UINT32(); + auto const sponsoringOwnerCountValue = canonical_UINT32(); + auto const sponsoringAccountCountValue = canonical_UINT32(); auto const aMMIDValue = canonical_UINT256(); auto const vaultIDValue = canonical_UINT256(); auto const loanBrokerIDValue = canonical_UINT256(); @@ -67,6 +70,9 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) builder.setMintedNFTokens(mintedNFTokensValue); builder.setBurnedNFTokens(burnedNFTokensValue); builder.setFirstNFTokenSequence(firstNFTokenSequenceValue); + builder.setSponsoredOwnerCount(sponsoredOwnerCountValue); + builder.setSponsoringOwnerCount(sponsoringOwnerCountValue); + builder.setSponsoringAccountCount(sponsoringAccountCountValue); builder.setAMMID(aMMIDValue); builder.setVaultID(vaultIDValue); builder.setLoanBrokerID(loanBrokerIDValue); @@ -228,6 +234,30 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasFirstNFTokenSequence()); } + { + auto const& expected = sponsoredOwnerCountValue; + auto const actualOpt = entry.getSponsoredOwnerCount(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfSponsoredOwnerCount"); + EXPECT_TRUE(entry.hasSponsoredOwnerCount()); + } + + { + auto const& expected = sponsoringOwnerCountValue; + auto const actualOpt = entry.getSponsoringOwnerCount(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfSponsoringOwnerCount"); + EXPECT_TRUE(entry.hasSponsoringOwnerCount()); + } + + { + auto const& expected = sponsoringAccountCountValue; + auto const actualOpt = entry.getSponsoringAccountCount(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfSponsoringAccountCount"); + EXPECT_TRUE(entry.hasSponsoringAccountCount()); + } + { auto const& expected = aMMIDValue; auto const actualOpt = entry.getAMMID(); @@ -285,6 +315,9 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) auto const mintedNFTokensValue = canonical_UINT32(); auto const burnedNFTokensValue = canonical_UINT32(); auto const firstNFTokenSequenceValue = canonical_UINT32(); + auto const sponsoredOwnerCountValue = canonical_UINT32(); + auto const sponsoringOwnerCountValue = canonical_UINT32(); + auto const sponsoringAccountCountValue = canonical_UINT32(); auto const aMMIDValue = canonical_UINT256(); auto const vaultIDValue = canonical_UINT256(); auto const loanBrokerIDValue = canonical_UINT256(); @@ -311,6 +344,9 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) sle->at(sfMintedNFTokens) = mintedNFTokensValue; sle->at(sfBurnedNFTokens) = burnedNFTokensValue; sle->at(sfFirstNFTokenSequence) = firstNFTokenSequenceValue; + sle->at(sfSponsoredOwnerCount) = sponsoredOwnerCountValue; + sle->at(sfSponsoringOwnerCount) = sponsoringOwnerCountValue; + sle->at(sfSponsoringAccountCount) = sponsoringAccountCountValue; sle->at(sfAMMID) = aMMIDValue; sle->at(sfVaultID) = vaultIDValue; sle->at(sfLoanBrokerID) = loanBrokerIDValue; @@ -566,6 +602,45 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfFirstNFTokenSequence"); } + { + auto const& expected = sponsoredOwnerCountValue; + + auto const fromSleOpt = entryFromSle.getSponsoredOwnerCount(); + auto const fromBuilderOpt = entryFromBuilder.getSponsoredOwnerCount(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfSponsoredOwnerCount"); + expectEqualField(expected, *fromBuilderOpt, "sfSponsoredOwnerCount"); + } + + { + auto const& expected = sponsoringOwnerCountValue; + + auto const fromSleOpt = entryFromSle.getSponsoringOwnerCount(); + auto const fromBuilderOpt = entryFromBuilder.getSponsoringOwnerCount(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfSponsoringOwnerCount"); + expectEqualField(expected, *fromBuilderOpt, "sfSponsoringOwnerCount"); + } + + { + auto const& expected = sponsoringAccountCountValue; + + auto const fromSleOpt = entryFromSle.getSponsoringAccountCount(); + auto const fromBuilderOpt = entryFromBuilder.getSponsoringAccountCount(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfSponsoringAccountCount"); + expectEqualField(expected, *fromBuilderOpt, "sfSponsoringAccountCount"); + } + { auto const& expected = aMMIDValue; @@ -697,6 +772,12 @@ TEST(AccountRootTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getBurnedNFTokens().has_value()); EXPECT_FALSE(entry.hasFirstNFTokenSequence()); EXPECT_FALSE(entry.getFirstNFTokenSequence().has_value()); + EXPECT_FALSE(entry.hasSponsoredOwnerCount()); + EXPECT_FALSE(entry.getSponsoredOwnerCount().has_value()); + EXPECT_FALSE(entry.hasSponsoringOwnerCount()); + EXPECT_FALSE(entry.getSponsoringOwnerCount().has_value()); + EXPECT_FALSE(entry.hasSponsoringAccountCount()); + EXPECT_FALSE(entry.getSponsoringAccountCount().has_value()); EXPECT_FALSE(entry.hasAMMID()); EXPECT_FALSE(entry.getAMMID().has_value()); EXPECT_FALSE(entry.hasVaultID()); diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/RippleStateTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/RippleStateTests.cpp index a51ce55f6f..de0769793b 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/RippleStateTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/RippleStateTests.cpp @@ -31,6 +31,8 @@ TEST(RippleStateTests, BuilderSettersRoundTrip) auto const highNodeValue = canonical_UINT64(); auto const highQualityInValue = canonical_UINT32(); auto const highQualityOutValue = canonical_UINT32(); + auto const highSponsorValue = canonical_ACCOUNT(); + auto const lowSponsorValue = canonical_ACCOUNT(); RippleStateBuilder builder{ balanceValue, @@ -46,6 +48,8 @@ TEST(RippleStateTests, BuilderSettersRoundTrip) builder.setHighNode(highNodeValue); builder.setHighQualityIn(highQualityInValue); builder.setHighQualityOut(highQualityOutValue); + builder.setHighSponsor(highSponsorValue); + builder.setLowSponsor(lowSponsorValue); builder.setLedgerIndex(index); builder.setFlags(0x1u); @@ -134,6 +138,22 @@ TEST(RippleStateTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasHighQualityOut()); } + { + auto const& expected = highSponsorValue; + auto const actualOpt = entry.getHighSponsor(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfHighSponsor"); + EXPECT_TRUE(entry.hasHighSponsor()); + } + + { + auto const& expected = lowSponsorValue; + auto const actualOpt = entry.getLowSponsor(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfLowSponsor"); + EXPECT_TRUE(entry.hasLowSponsor()); + } + EXPECT_TRUE(entry.hasLedgerIndex()); auto const ledgerIndex = entry.getLedgerIndex(); ASSERT_TRUE(ledgerIndex.has_value()); @@ -158,6 +178,8 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip) auto const highNodeValue = canonical_UINT64(); auto const highQualityInValue = canonical_UINT32(); auto const highQualityOutValue = canonical_UINT32(); + auto const highSponsorValue = canonical_ACCOUNT(); + auto const lowSponsorValue = canonical_ACCOUNT(); auto sle = std::make_shared(RippleState::entryType, index); @@ -172,6 +194,8 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip) sle->at(sfHighNode) = highNodeValue; sle->at(sfHighQualityIn) = highQualityInValue; sle->at(sfHighQualityOut) = highQualityOutValue; + sle->at(sfHighSponsor) = highSponsorValue; + sle->at(sfLowSponsor) = lowSponsorValue; RippleStateBuilder builderFromSle{sle}; EXPECT_TRUE(builderFromSle.validate()); @@ -310,6 +334,32 @@ TEST(RippleStateTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfHighQualityOut"); } + { + auto const& expected = highSponsorValue; + + auto const fromSleOpt = entryFromSle.getHighSponsor(); + auto const fromBuilderOpt = entryFromBuilder.getHighSponsor(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfHighSponsor"); + expectEqualField(expected, *fromBuilderOpt, "sfHighSponsor"); + } + + { + auto const& expected = lowSponsorValue; + + auto const fromSleOpt = entryFromSle.getLowSponsor(); + auto const fromBuilderOpt = entryFromBuilder.getLowSponsor(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfLowSponsor"); + expectEqualField(expected, *fromBuilderOpt, "sfLowSponsor"); + } + EXPECT_EQ(entryFromSle.getKey(), index); EXPECT_EQ(entryFromBuilder.getKey(), index); } @@ -384,5 +434,9 @@ TEST(RippleStateTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getHighQualityIn().has_value()); EXPECT_FALSE(entry.hasHighQualityOut()); EXPECT_FALSE(entry.getHighQualityOut().has_value()); + EXPECT_FALSE(entry.hasHighSponsor()); + EXPECT_FALSE(entry.getHighSponsor().has_value()); + EXPECT_FALSE(entry.hasLowSponsor()); + EXPECT_FALSE(entry.getLowSponsor().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/SponsorshipTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/SponsorshipTests.cpp new file mode 100644 index 0000000000..e1d9ff15b9 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/SponsorshipTests.cpp @@ -0,0 +1,329 @@ +// Auto-generated unit tests for ledger entry Sponsorship + + +#include + +#include + +#include +#include +#include + +#include + +namespace xrpl::ledger_entries { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed for both the +// builder's STObject and the wrapper's SLE. +TEST(SponsorshipTests, BuilderSettersRoundTrip) +{ + uint256 const index{1u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const ownerValue = canonical_ACCOUNT(); + auto const sponseeValue = canonical_ACCOUNT(); + auto const feeAmountValue = canonical_AMOUNT(); + auto const maxFeeValue = canonical_AMOUNT(); + auto const remainingOwnerCountValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const sponseeNodeValue = canonical_UINT64(); + + SponsorshipBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + ownerValue, + sponseeValue, + ownerNodeValue, + sponseeNodeValue + }; + + builder.setFeeAmount(feeAmountValue); + builder.setMaxFee(maxFeeValue); + builder.setRemainingOwnerCount(remainingOwnerCountValue); + + builder.setLedgerIndex(index); + builder.setFlags(0x1u); + + EXPECT_TRUE(builder.validate()); + + auto const entry = builder.build(index); + + EXPECT_TRUE(entry.validate()); + + { + auto const& expected = previousTxnIDValue; + auto const actual = entry.getPreviousTxnID(); + expectEqualField(expected, actual, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + auto const actual = entry.getPreviousTxnLgrSeq(); + expectEqualField(expected, actual, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = ownerValue; + auto const actual = entry.getOwner(); + expectEqualField(expected, actual, "sfOwner"); + } + + { + auto const& expected = sponseeValue; + auto const actual = entry.getSponsee(); + expectEqualField(expected, actual, "sfSponsee"); + } + + { + auto const& expected = ownerNodeValue; + auto const actual = entry.getOwnerNode(); + expectEqualField(expected, actual, "sfOwnerNode"); + } + + { + auto const& expected = sponseeNodeValue; + auto const actual = entry.getSponseeNode(); + expectEqualField(expected, actual, "sfSponseeNode"); + } + + { + auto const& expected = feeAmountValue; + auto const actualOpt = entry.getFeeAmount(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfFeeAmount"); + EXPECT_TRUE(entry.hasFeeAmount()); + } + + { + auto const& expected = maxFeeValue; + auto const actualOpt = entry.getMaxFee(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfMaxFee"); + EXPECT_TRUE(entry.hasMaxFee()); + } + + { + auto const& expected = remainingOwnerCountValue; + auto const actualOpt = entry.getRemainingOwnerCount(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount"); + EXPECT_TRUE(entry.hasRemainingOwnerCount()); + } + + EXPECT_TRUE(entry.hasLedgerIndex()); + auto const ledgerIndex = entry.getLedgerIndex(); + ASSERT_TRUE(ledgerIndex.has_value()); + EXPECT_EQ(*ledgerIndex, index); + EXPECT_EQ(entry.getKey(), index); +} + +// 2 & 4) Start from an SLE, set fields directly on it, construct a builder +// from that SLE, build a new wrapper, and verify all fields (and validate()). +TEST(SponsorshipTests, BuilderFromSleRoundTrip) +{ + uint256 const index{2u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const ownerValue = canonical_ACCOUNT(); + auto const sponseeValue = canonical_ACCOUNT(); + auto const feeAmountValue = canonical_AMOUNT(); + auto const maxFeeValue = canonical_AMOUNT(); + auto const remainingOwnerCountValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const sponseeNodeValue = canonical_UINT64(); + + auto sle = std::make_shared(Sponsorship::entryType, index); + + sle->at(sfPreviousTxnID) = previousTxnIDValue; + sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; + sle->at(sfOwner) = ownerValue; + sle->at(sfSponsee) = sponseeValue; + sle->at(sfFeeAmount) = feeAmountValue; + sle->at(sfMaxFee) = maxFeeValue; + sle->at(sfRemainingOwnerCount) = remainingOwnerCountValue; + sle->at(sfOwnerNode) = ownerNodeValue; + sle->at(sfSponseeNode) = sponseeNodeValue; + + SponsorshipBuilder builderFromSle{sle}; + EXPECT_TRUE(builderFromSle.validate()); + + auto const entryFromBuilder = builderFromSle.build(index); + + Sponsorship entryFromSle{sle}; + EXPECT_TRUE(entryFromBuilder.validate()); + EXPECT_TRUE(entryFromSle.validate()); + + { + auto const& expected = previousTxnIDValue; + + auto const fromSle = entryFromSle.getPreviousTxnID(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnID(); + + expectEqualField(expected, fromSle, "sfPreviousTxnID"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + + auto const fromSle = entryFromSle.getPreviousTxnLgrSeq(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq(); + + expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = ownerValue; + + auto const fromSle = entryFromSle.getOwner(); + auto const fromBuilder = entryFromBuilder.getOwner(); + + expectEqualField(expected, fromSle, "sfOwner"); + expectEqualField(expected, fromBuilder, "sfOwner"); + } + + { + auto const& expected = sponseeValue; + + auto const fromSle = entryFromSle.getSponsee(); + auto const fromBuilder = entryFromBuilder.getSponsee(); + + expectEqualField(expected, fromSle, "sfSponsee"); + expectEqualField(expected, fromBuilder, "sfSponsee"); + } + + { + auto const& expected = ownerNodeValue; + + auto const fromSle = entryFromSle.getOwnerNode(); + auto const fromBuilder = entryFromBuilder.getOwnerNode(); + + expectEqualField(expected, fromSle, "sfOwnerNode"); + expectEqualField(expected, fromBuilder, "sfOwnerNode"); + } + + { + auto const& expected = sponseeNodeValue; + + auto const fromSle = entryFromSle.getSponseeNode(); + auto const fromBuilder = entryFromBuilder.getSponseeNode(); + + expectEqualField(expected, fromSle, "sfSponseeNode"); + expectEqualField(expected, fromBuilder, "sfSponseeNode"); + } + + { + auto const& expected = feeAmountValue; + + auto const fromSleOpt = entryFromSle.getFeeAmount(); + auto const fromBuilderOpt = entryFromBuilder.getFeeAmount(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfFeeAmount"); + expectEqualField(expected, *fromBuilderOpt, "sfFeeAmount"); + } + + { + auto const& expected = maxFeeValue; + + auto const fromSleOpt = entryFromSle.getMaxFee(); + auto const fromBuilderOpt = entryFromBuilder.getMaxFee(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfMaxFee"); + expectEqualField(expected, *fromBuilderOpt, "sfMaxFee"); + } + + { + auto const& expected = remainingOwnerCountValue; + + auto const fromSleOpt = entryFromSle.getRemainingOwnerCount(); + auto const fromBuilderOpt = entryFromBuilder.getRemainingOwnerCount(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfRemainingOwnerCount"); + expectEqualField(expected, *fromBuilderOpt, "sfRemainingOwnerCount"); + } + + EXPECT_EQ(entryFromSle.getKey(), index); + EXPECT_EQ(entryFromBuilder.getKey(), index); +} + +// 3) Verify wrapper throws when constructed from wrong ledger entry type. +TEST(SponsorshipTests, WrapperThrowsOnWrongEntryType) +{ + uint256 const index{3u}; + + // Build a valid ledger entry of a different type + // Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq + // Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(Sponsorship{wrongEntry.getSle()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong ledger entry type. +TEST(SponsorshipTests, BuilderThrowsOnWrongEntryType) +{ + uint256 const index{4u}; + + // Build a valid ledger entry of a different type + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(SponsorshipBuilder{wrongEntry.getSle()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(SponsorshipTests, OptionalFieldsReturnNullopt) +{ + uint256 const index{3u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const ownerValue = canonical_ACCOUNT(); + auto const sponseeValue = canonical_ACCOUNT(); + auto const ownerNodeValue = canonical_UINT64(); + auto const sponseeNodeValue = canonical_UINT64(); + + SponsorshipBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + ownerValue, + sponseeValue, + ownerNodeValue, + sponseeNodeValue + }; + + auto const entry = builder.build(index); + + // Verify optional fields are not present + EXPECT_FALSE(entry.hasFeeAmount()); + EXPECT_FALSE(entry.getFeeAmount().has_value()); + EXPECT_FALSE(entry.hasMaxFee()); + EXPECT_FALSE(entry.getMaxFee().has_value()); + EXPECT_FALSE(entry.hasRemainingOwnerCount()); + EXPECT_FALSE(entry.getRemainingOwnerCount().has_value()); +} +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipSetTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipSetTests.cpp new file mode 100644 index 0000000000..dce8cfca3f --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipSetTests.cpp @@ -0,0 +1,261 @@ +// Auto-generated unit tests for transaction SponsorshipSet + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsSponsorshipSetTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSet")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const counterpartySponsorValue = canonical_ACCOUNT(); + auto const sponseeValue = canonical_ACCOUNT(); + auto const feeAmountValue = canonical_AMOUNT(); + auto const maxFeeValue = canonical_AMOUNT(); + auto const remainingOwnerCountValue = canonical_UINT32(); + + SponsorshipSetBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setCounterpartySponsor(counterpartySponsorValue); + builder.setSponsee(sponseeValue); + builder.setFeeAmount(feeAmountValue); + builder.setMaxFee(maxFeeValue); + builder.setRemainingOwnerCount(remainingOwnerCountValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = counterpartySponsorValue; + auto const actualOpt = tx.getCounterpartySponsor(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCounterpartySponsor should be present"; + expectEqualField(expected, *actualOpt, "sfCounterpartySponsor"); + EXPECT_TRUE(tx.hasCounterpartySponsor()); + } + + { + auto const& expected = sponseeValue; + auto const actualOpt = tx.getSponsee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present"; + expectEqualField(expected, *actualOpt, "sfSponsee"); + EXPECT_TRUE(tx.hasSponsee()); + } + + { + auto const& expected = feeAmountValue; + auto const actualOpt = tx.getFeeAmount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFeeAmount should be present"; + expectEqualField(expected, *actualOpt, "sfFeeAmount"); + EXPECT_TRUE(tx.hasFeeAmount()); + } + + { + auto const& expected = maxFeeValue; + auto const actualOpt = tx.getMaxFee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMaxFee should be present"; + expectEqualField(expected, *actualOpt, "sfMaxFee"); + EXPECT_TRUE(tx.hasMaxFee()); + } + + { + auto const& expected = remainingOwnerCountValue; + auto const actualOpt = tx.getRemainingOwnerCount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfRemainingOwnerCount should be present"; + expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount"); + EXPECT_TRUE(tx.hasRemainingOwnerCount()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsSponsorshipSetTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSetFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const counterpartySponsorValue = canonical_ACCOUNT(); + auto const sponseeValue = canonical_ACCOUNT(); + auto const feeAmountValue = canonical_AMOUNT(); + auto const maxFeeValue = canonical_AMOUNT(); + auto const remainingOwnerCountValue = canonical_UINT32(); + + // Build an initial transaction + SponsorshipSetBuilder initialBuilder{ + accountValue, + sequenceValue, + feeValue + }; + + initialBuilder.setCounterpartySponsor(counterpartySponsorValue); + initialBuilder.setSponsee(sponseeValue); + initialBuilder.setFeeAmount(feeAmountValue); + initialBuilder.setMaxFee(maxFeeValue); + initialBuilder.setRemainingOwnerCount(remainingOwnerCountValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + SponsorshipSetBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = counterpartySponsorValue; + auto const actualOpt = rebuiltTx.getCounterpartySponsor(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCounterpartySponsor should be present"; + expectEqualField(expected, *actualOpt, "sfCounterpartySponsor"); + } + + { + auto const& expected = sponseeValue; + auto const actualOpt = rebuiltTx.getSponsee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present"; + expectEqualField(expected, *actualOpt, "sfSponsee"); + } + + { + auto const& expected = feeAmountValue; + auto const actualOpt = rebuiltTx.getFeeAmount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFeeAmount should be present"; + expectEqualField(expected, *actualOpt, "sfFeeAmount"); + } + + { + auto const& expected = maxFeeValue; + auto const actualOpt = rebuiltTx.getMaxFee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMaxFee should be present"; + expectEqualField(expected, *actualOpt, "sfMaxFee"); + } + + { + auto const& expected = remainingOwnerCountValue; + auto const actualOpt = rebuiltTx.getRemainingOwnerCount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfRemainingOwnerCount should be present"; + expectEqualField(expected, *actualOpt, "sfRemainingOwnerCount"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsSponsorshipSetTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(SponsorshipSet{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsSponsorshipSetTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(SponsorshipSetBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsSponsorshipSetTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipSetNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + + SponsorshipSetBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasCounterpartySponsor()); + EXPECT_FALSE(tx.getCounterpartySponsor().has_value()); + EXPECT_FALSE(tx.hasSponsee()); + EXPECT_FALSE(tx.getSponsee().has_value()); + EXPECT_FALSE(tx.hasFeeAmount()); + EXPECT_FALSE(tx.getFeeAmount().has_value()); + EXPECT_FALSE(tx.hasMaxFee()); + EXPECT_FALSE(tx.getMaxFee().has_value()); + EXPECT_FALSE(tx.hasRemainingOwnerCount()); + EXPECT_FALSE(tx.getRemainingOwnerCount().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipTransferTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipTransferTests.cpp new file mode 100644 index 0000000000..c8521b7b20 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/SponsorshipTransferTests.cpp @@ -0,0 +1,198 @@ +// Auto-generated unit tests for transaction SponsorshipTransfer + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsSponsorshipTransferTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransfer")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const objectIDValue = canonical_UINT256(); + auto const sponseeValue = canonical_ACCOUNT(); + + SponsorshipTransferBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setObjectID(objectIDValue); + builder.setSponsee(sponseeValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = objectIDValue; + auto const actualOpt = tx.getObjectID(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfObjectID should be present"; + expectEqualField(expected, *actualOpt, "sfObjectID"); + EXPECT_TRUE(tx.hasObjectID()); + } + + { + auto const& expected = sponseeValue; + auto const actualOpt = tx.getSponsee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present"; + expectEqualField(expected, *actualOpt, "sfSponsee"); + EXPECT_TRUE(tx.hasSponsee()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsSponsorshipTransferTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransferFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const objectIDValue = canonical_UINT256(); + auto const sponseeValue = canonical_ACCOUNT(); + + // Build an initial transaction + SponsorshipTransferBuilder initialBuilder{ + accountValue, + sequenceValue, + feeValue + }; + + initialBuilder.setObjectID(objectIDValue); + initialBuilder.setSponsee(sponseeValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + SponsorshipTransferBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = objectIDValue; + auto const actualOpt = rebuiltTx.getObjectID(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfObjectID should be present"; + expectEqualField(expected, *actualOpt, "sfObjectID"); + } + + { + auto const& expected = sponseeValue; + auto const actualOpt = rebuiltTx.getSponsee(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfSponsee should be present"; + expectEqualField(expected, *actualOpt, "sfSponsee"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsSponsorshipTransferTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(SponsorshipTransfer{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsSponsorshipTransferTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(SponsorshipTransferBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsSponsorshipTransferTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::Secp256k1, generateSeed("testSponsorshipTransferNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + + SponsorshipTransferBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasObjectID()); + EXPECT_FALSE(tx.getObjectID().has_value()); + EXPECT_FALSE(tx.hasSponsee()); + EXPECT_FALSE(tx.getSponsee().has_value()); +} + +} diff --git a/src/xrpld/app/consensus/RCLCensorshipDetector.h b/src/xrpld/app/consensus/RCLCensorshipDetector.h index 4318a7b4ff..6d0e20031e 100644 --- a/src/xrpld/app/consensus/RCLCensorshipDetector.h +++ b/src/xrpld/app/consensus/RCLCensorshipDetector.h @@ -51,11 +51,12 @@ private: public: RCLCensorshipDetector() = default; - /** Add transactions being proposed for the current consensus round. - - @param proposed The set of transactions that we are initially proposing - for this round. - */ + /** + * Add transactions being proposed for the current consensus round. + * + * @param proposed The set of transactions that we are initially proposing + * for this round. + */ void propose(TxIDSeqVec proposed) { @@ -74,19 +75,20 @@ public: tracker_ = std::move(proposed); } - /** Determine which transactions made it and perform censorship detection. - - This function is called when the server is proposing and a consensus - round it participated in completed. - - @param accepted The set of transactions that the network agreed - should be included in the ledger being built. - @param pred A predicate invoked for every transaction we've proposed - but which hasn't yet made it. The predicate must be - callable as: - bool pred(TxID const&, Sequence) - It must return true for entries that should be removed. - */ + /** + * Determine which transactions made it and perform censorship detection. + * + * This function is called when the server is proposing and a consensus + * round it participated in completed. + * + * @param accepted The set of transactions that the network agreed + * should be included in the ledger being built. + * @param pred A predicate invoked for every transaction we've proposed + * but which hasn't yet made it. The predicate must be + * callable as: + * bool pred(TxID const&, Sequence) + * It must return true for entries that should be removed. + */ template void check(std::vector accepted, Predicate&& pred) @@ -108,11 +110,12 @@ public: tracker_.erase(i, tracker_.end()); } - /** Removes all elements from the tracker - - Typically, this function might be called after we reconnect to the - network following an outage, or after we start tracking the network. - */ + /** + * Removes all elements from the tracker + * + * Typically, this function might be called after we reconnect to the + * network following an outage, or after we start tracking the network. + */ void reset() { diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index 2abc881adc..4abf77f578 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -955,7 +955,9 @@ RCLConsensus::gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet) } } -//! @see Consensus::simulate +/** + * @see Consensus::simulate + */ void RCLConsensus::simulate( diff --git a/src/xrpld/app/consensus/RCLConsensus.h b/src/xrpld/app/consensus/RCLConsensus.h index 359f6b8009..4ffe18a7a8 100644 --- a/src/xrpld/app/consensus/RCLConsensus.h +++ b/src/xrpld/app/consensus/RCLConsensus.h @@ -43,11 +43,13 @@ class LocalTxs; class LedgerMaster; class ValidatorKeys; -/** Manages the generic consensus algorithm for use by the RCL. +/** + * Manages the generic consensus algorithm for use by the RCL. */ class RCLConsensus { - /** Warn for transactions that haven't been included every so many ledgers. + /** + * Warn for transactions that haven't been included every so many ledgers. */ static constexpr unsigned int kCensorshipWarnInternal = 15; @@ -126,12 +128,13 @@ class RCLConsensus return mode_; } - /** Called before kicking off a new consensus round. - - @param prevLedger Ledger that will be prior ledger for next round - @param nowTrusted the new validators - @return Whether we enter the round proposing - */ + /** + * Called before kicking off a new consensus round. + * + * @param prevLedger Ledger that will be prior ledger for next round + * @param nowTrusted the new validators + * @return Whether we enter the round proposing + */ bool preStartRound(RCLCxLedger const& prevLedger, hash_set const& nowTrusted); @@ -147,14 +150,16 @@ class RCLConsensus std::size_t laggards(Ledger_t::Seq const seq, hash_set& trustedKeys) const; - /** Whether I am a validator. + /** + * Whether I am a validator. * * @return whether I am a validator. */ bool validator() const; - /** Update operating mode based on current peer positions. + /** + * Update operating mode based on current peer positions. * * If our current ledger has no agreement from the network, * then we cannot be in the omFULL mode. @@ -164,7 +169,8 @@ class RCLConsensus void updateOperatingMode(std::size_t const positions) const; - /** Consensus simulation parameters + /** + * Consensus simulation parameters */ ConsensusParms const& parms() const @@ -186,129 +192,142 @@ class RCLConsensus // changing state until a future call to startRound. friend class Consensus; - /** Attempt to acquire a specific ledger. - - If not available, asynchronously acquires from the network. - - @param hash The ID/hash of the ledger acquire - @return Optional ledger, will be seated if we locally had the ledger - */ + /** + * Attempt to acquire a specific ledger. + * + * If not available, asynchronously acquires from the network. + * + * @param hash The ID/hash of the ledger acquire + * @return Optional ledger, will be seated if we locally had the ledger + */ std::optional acquireLedger(LedgerHash const& hash); - /** Share the given proposal with all peers - - @param peerPos The peer position to share. + /** + * Share the given proposal with all peers + * + * @param peerPos The peer position to share. */ void share(RCLCxPeerPos const& peerPos); - /** Share disputed transaction to peers. - - Only share if the provided transaction hasn't been shared recently. - - @param tx The disputed transaction to share. - */ + /** + * Share disputed transaction to peers. + * + * Only share if the provided transaction hasn't been shared recently. + * + * @param tx The disputed transaction to share. + */ void share(RCLCxTx const& tx); - /** Acquire the transaction set associated with a proposal. - - If the transaction set is not available locally, will attempt - acquire it from the network. - - @param setId The transaction set ID associated with the proposal - @return Optional set of transactions, seated if available. - */ + /** + * Acquire the transaction set associated with a proposal. + * + * If the transaction set is not available locally, will attempt + * acquire it from the network. + * + * @param setId The transaction set ID associated with the proposal + * @return Optional set of transactions, seated if available. + */ std::optional acquireTxSet(RCLTxSet::ID const& setId); - /** Whether the open ledger has any transactions + /** + * Whether the open ledger has any transactions */ bool hasOpenTransactions() const; - /** Number of proposers that have validated the given ledger - - @param h The hash of the ledger of interest - @return the number of proposers that validated a ledger - */ + /** + * Number of proposers that have validated the given ledger + * + * @param h The hash of the ledger of interest + * @return the number of proposers that validated a ledger + */ std::size_t proposersValidated(LedgerHash const& h) const; - /** Number of proposers that have validated a ledger descended from - requested ledger. - - @param ledger The current working ledger - @param h The hash of the preferred working ledger - @return The number of validating peers that have validated a ledger - descended from the preferred working ledger. - */ + /** + * Number of proposers that have validated a ledger descended from + * requested ledger. + * + * @param ledger The current working ledger + * @param h The hash of the preferred working ledger + * @return The number of validating peers that have validated a ledger + * descended from the preferred working ledger. + */ std::size_t proposersFinished(RCLCxLedger const& ledger, LedgerHash const& h) const; - /** Propose the given position to my peers. - - @param proposal Our proposed position - */ + /** + * Propose the given position to my peers. + * + * @param proposal Our proposed position + */ void propose(RCLCxPeerPos::Proposal const& proposal); - /** Share the given tx set to peers. - - @param txns The TxSet to share. - */ + /** + * Share the given tx set to peers. + * + * @param txns The TxSet to share. + */ void share(RCLTxSet const& txns); - /** Get the ID of the previous ledger/last closed ledger(LCL) on the - network - - @param ledgerID ID of previous ledger used by consensus - @param ledger Previous ledger consensus has available - @param mode Current consensus mode - @return The id of the last closed network - - @note ledgerID may not match ledger.id() if we haven't acquired - the ledger matching ledgerID from the network + /** + * Get the ID of the previous ledger/last closed ledger(LCL) on the + * network + * + * @param ledgerID ID of previous ledger used by consensus + * @param ledger Previous ledger consensus has available + * @param mode Current consensus mode + * @return The id of the last closed network + * + * @note ledgerID may not match ledger.id() if we haven't acquired + * the ledger matching ledgerID from the network */ uint256 getPrevLedger(uint256 ledgerID, RCLCxLedger const& ledger, ConsensusMode mode); - /** Notified of change in consensus mode - - @param before The prior consensus mode - @param after The new consensus mode - */ + /** + * Notified of change in consensus mode + * + * @param before The prior consensus mode + * @param after The new consensus mode + */ void onModeChange(ConsensusMode before, ConsensusMode after); - /** Close the open ledger and return initial consensus position. - - @param ledger the ledger we are changing to - @param closeTime When consensus closed the ledger - @param mode Current consensus mode - @return Tentative consensus result - */ + /** + * Close the open ledger and return initial consensus position. + * + * @param ledger the ledger we are changing to + * @param closeTime When consensus closed the ledger + * @param mode Current consensus mode + * @return Tentative consensus result + */ Result onClose( RCLCxLedger const& ledger, NetClock::time_point const& closeTime, ConsensusMode mode); - /** Process the accepted ledger. - - @param result The result of consensus - @param prevLedger The closed ledger consensus worked from - @param closeResolution The resolution used in agreeing on an - effective closeTime - @param rawCloseTimes The unrounded closetimes of ourself and our - peers - @param mode Our participating mode at the time consensus was - declared - @param consensusJson Json representation of consensus state - @param validating whether this is a validator - */ + /** + * Process the accepted ledger. + * + * @param result The result of consensus + * @param prevLedger The closed ledger consensus worked from + * @param closeResolution The resolution used in agreeing on an + * effective closeTime + * @param rawCloseTimes The unrounded closetimes of ourself and our + * peers + * @param mode Our participating mode at the time consensus was + * declared + * @param consensusJson Json representation of consensus state + * @param validating whether this is a validator + */ void onAccept( Result const& result, @@ -319,11 +338,12 @@ class RCLConsensus json::Value&& consensusJson, bool const validating); - /** Process the accepted ledger that was a result of simulation/force - accept. - - @ref onAccept - */ + /** + * Process the accepted ledger that was a result of simulation/force + * accept. + * + * @ref onAccept + */ void onForceAccept( Result const& result, @@ -333,18 +353,20 @@ class RCLConsensus ConsensusMode const& mode, json::Value&& consensusJson); - /** Notify peers of a consensus state change - - @param ne Event type for notification - @param ledger The ledger at the time of the state change - @param haveCorrectLCL Whether we believe we have the correct LCL. - */ + /** + * Notify peers of a consensus state change + * + * @param ne Event type for notification + * @param ledger The ledger at the time of the state change + * @param haveCorrectLCL Whether we believe we have the correct LCL. + */ void notify(protocol::NodeEvent ne, RCLCxLedger const& ledger, bool haveCorrectLCL); - /** Accept a new ledger based on the given transactions. - - @ref onAccept + /** + * Accept a new ledger based on the given transactions. + * + * @ref onAccept */ void doAccept( @@ -355,27 +377,28 @@ class RCLConsensus ConsensusMode const& mode, json::Value&& consensusJson); - /** Build the new last closed ledger. - - Accept the given the provided set of consensus transactions and - build the last closed ledger. Since consensus just agrees on which - transactions to apply, but not whether they make it into the closed - ledger, this function also populates retriableTxs with those that - can be retried in the next round. - - @param previousLedger Prior ledger building upon - @param retriableTxs On entry, the set of transactions to apply to - the ledger; on return, the set of transactions - to retry in the next round. - @param closeTime The time the ledger closed - @param closeTimeCorrect Whether consensus agreed on close time - @param closeResolution Resolution used to determine consensus close - time - @param roundTime Duration of this consensus round - @param failedTxs Populate with transactions that we could not - successfully apply. - @return The newly built ledger - */ + /** + * Build the new last closed ledger. + * + * Accept the given the provided set of consensus transactions and + * build the last closed ledger. Since consensus just agrees on which + * transactions to apply, but not whether they make it into the closed + * ledger, this function also populates retriableTxs with those that + * can be retried in the next round. + * + * @param previousLedger Prior ledger building upon + * @param retriableTxs On entry, the set of transactions to apply to + * the ledger; on return, the set of transactions + * to retry in the next round. + * @param closeTime The time the ledger closed + * @param closeTimeCorrect Whether consensus agreed on close time + * @param closeResolution Resolution used to determine consensus close + * time + * @param roundTime Duration of this consensus round + * @param failedTxs Populate with transactions that we could not + * successfully apply. + * @return The newly built ledger + */ RCLCxLedger buildLCL( RCLCxLedger const& previousLedger, @@ -386,22 +409,25 @@ class RCLConsensus std::chrono::milliseconds roundTime, std::set& failedTxs); - /** Validate the given ledger and share with peers as necessary - - @param ledger The ledger to validate - @param txns The consensus transaction set - @param proposing Whether we were proposing transactions while - generating this ledger. If we are not proposing, - a validation can still be sent to inform peers that - we know we aren't fully participating in consensus - but are still around and trying to catch up. - */ + /** + * Validate the given ledger and share with peers as necessary + * + * @param ledger The ledger to validate + * @param txns The consensus transaction set + * @param proposing Whether we were proposing transactions while + * generating this ledger. If we are not proposing, + * a validation can still be sent to inform peers that + * we know we aren't fully participating in consensus + * but are still around and trying to catch up. + */ void validate(RCLCxLedger const& ledger, RCLTxSet const& txns, bool proposing); }; public: - //! Constructor + /** + * Constructor + */ RCLConsensus( Application& app, std::unique_ptr&& feeVote, @@ -417,35 +443,42 @@ public: RCLConsensus& operator=(RCLConsensus const&) = delete; - //! Whether we are validating consensus ledgers. + /** + * Whether we are validating consensus ledgers. + */ bool validating() const { return adaptor_.validating(); } - //! Get the number of proposing peers that participated in the previous - //! round. + /** + * Get the number of proposing peers that participated in the previous + * round. + */ std::size_t prevProposers() const { return adaptor_.prevProposers(); } - /** Get duration of the previous round. - - The duration of the round is the establish phase, measured from closing - the open ledger to accepting the consensus result. - - @return Last round duration in milliseconds - */ + /** + * Get duration of the previous round. + * + * The duration of the round is the establish phase, measured from closing + * the open ledger to accepting the consensus result. + * + * @return Last round duration in milliseconds + */ std::chrono::milliseconds prevRoundTime() const { return adaptor_.prevRoundTime(); } - //! @see Consensus::mode + /** + * @see Consensus::mode + */ ConsensusMode mode() const { @@ -458,12 +491,15 @@ public: return consensus_.phase(); } - //! @see Consensus::getJson + /** + * @see Consensus::getJson + */ json::Value getJson(bool full) const; - /** Adjust the set of trusted validators and kick-off the next round of - consensus. For more details, @see Consensus::startRound + /** + * Adjust the set of trusted validators and kick-off the next round of + * consensus. For more details, @see Consensus::startRound */ void startRound( @@ -474,17 +510,23 @@ public: hash_set const& nowTrusted, std::unique_ptr const& clog); - //! @see Consensus::timerEntry + /** + * @see Consensus::timerEntry + */ void timerEntry( NetClock::time_point const& now, std::unique_ptr const& clog = {}); - //! @see Consensus::gotTxSet + /** + * @see Consensus::gotTxSet + */ void gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet); - // @see Consensus::prevLedgerID + /** + * @see Consensus::prevLedgerID + */ RCLCxLedger::ID prevLedgerID() const { @@ -492,13 +534,17 @@ public: return consensus_.prevLedgerID(); } - //! @see Consensus::simulate + /** + * @see Consensus::simulate + */ void simulate( NetClock::time_point const& now, std::optional consensusDelay); - //! @see Consensus::proposal + /** + * @see Consensus::proposal + */ bool peerProposal(NetClock::time_point const& now, RCLCxPeerPos const& newProposal); @@ -519,7 +565,8 @@ private: beast::Journal const j_; }; -/** Collects logging information. +/** + * Collects logging information. * * Eases correlating multiple data points together to * help follow flow of a complex activity, such as diff --git a/src/xrpld/app/consensus/RCLCxLedger.h b/src/xrpld/app/consensus/RCLCxLedger.h index 89f70f9add..f9f27d2322 100644 --- a/src/xrpld/app/consensus/RCLCxLedger.h +++ b/src/xrpld/app/consensus/RCLCxLedger.h @@ -14,95 +14,119 @@ namespace xrpl { -/** Represents a ledger in RCLConsensus. - - RCLCxLedger is a thin wrapper over `std::shared_ptr`. -*/ +/** + * Represents a ledger in RCLConsensus. + * + * RCLCxLedger is a thin wrapper over `std::shared_ptr`. + */ class RCLCxLedger { public: - //! Unique identifier of a ledger + /** + * Unique identifier of a ledger + */ using ID = LedgerHash; - //! Sequence number of a ledger + /** + * Sequence number of a ledger + */ using Seq = LedgerIndex; - /** Default constructor - - TODO: This may not be needed if we ensure RCLConsensus is handed a valid - ledger in its constructor. Its bad now because other members are not - checking whether the ledger is valid. - */ + /** + * Default constructor + * + * TODO: This may not be needed if we ensure RCLConsensus is handed a valid + * ledger in its constructor. Its bad now because other members are not + * checking whether the ledger is valid. + */ RCLCxLedger() = default; - /** Constructor - - @param l The ledger to wrap. - */ + /** + * Constructor + * + * @param l The ledger to wrap. + */ RCLCxLedger(std::shared_ptr l) : ledger{std::move(l)} { } - //! Sequence number of the ledger. + /** + * Sequence number of the ledger. + */ [[nodiscard]] Seq const& seq() const { return ledger->header().seq; } - //! Unique identifier (hash) of this ledger. + /** + * Unique identifier (hash) of this ledger. + */ [[nodiscard]] ID const& id() const { return ledger->header().hash; } - //! Unique identifier (hash) of this ledger's parent. + /** + * Unique identifier (hash) of this ledger's parent. + */ [[nodiscard]] ID const& parentID() const { return ledger->header().parentHash; } - //! Resolution used when calculating this ledger's close time. + /** + * Resolution used when calculating this ledger's close time. + */ [[nodiscard]] NetClock::duration closeTimeResolution() const { return ledger->header().closeTimeResolution; } - //! Whether consensus process agreed on close time of the ledger. + /** + * Whether consensus process agreed on close time of the ledger. + */ [[nodiscard]] bool closeAgree() const { return xrpl::getCloseAgree(ledger->header()); } - //! The close time of this ledger + /** + * The close time of this ledger + */ [[nodiscard]] NetClock::time_point closeTime() const { return ledger->header().closeTime; } - //! The close time of this ledger's parent. + /** + * The close time of this ledger's parent. + */ [[nodiscard]] NetClock::time_point parentCloseTime() const { return ledger->header().parentCloseTime; } - //! JSON representation of this ledger. + /** + * JSON representation of this ledger. + */ [[nodiscard]] json::Value getJson() const { return xrpl::getJson({*ledger, {}}); } - /** The ledger instance. - - TODO: Make this shared_ptr .. requires ability to create - a new ledger from a readView? - */ + /** + * The ledger instance. + * + * TODO: Make this shared_ptr .. requires ability to create + * a new ledger from a readView? + */ std::shared_ptr ledger; }; } // namespace xrpl diff --git a/src/xrpld/app/consensus/RCLCxPeerPos.h b/src/xrpld/app/consensus/RCLCxPeerPos.h index e73ac3b532..050bdf6d36 100644 --- a/src/xrpld/app/consensus/RCLCxPeerPos.h +++ b/src/xrpld/app/consensus/RCLCxPeerPos.h @@ -18,26 +18,28 @@ namespace xrpl { -/** A peer's signed, proposed position for use in RCLConsensus. - - Carries a ConsensusProposal signed by a peer. Provides value semantics - but manages shared storage of the peer position internally. -*/ +/** + * A peer's signed, proposed position for use in RCLConsensus. + * + * Carries a ConsensusProposal signed by a peer. Provides value semantics + * but manages shared storage of the peer position internally. + */ class RCLCxPeerPos { public: //< The type of the proposed position using Proposal = ConsensusProposal; - /** Constructor - - Constructs a signed peer position. - - @param publicKey Public key of the peer - @param signature Signature provided with the proposal - @param suppress Unique id used for hash router suppression - @param proposal The consensus proposal - */ + /** + * Constructor + * + * Constructs a signed peer position. + * + * @param publicKey Public key of the peer + * @param signature Signature provided with the proposal + * @param suppress Unique id used for hash router suppression + * @param proposal The consensus proposal + */ RCLCxPeerPos( PublicKey const& publicKey, @@ -45,25 +47,33 @@ public: uint256 const& suppress, Proposal const& proposal); // trivially copyable - //! Verify the signing hash of the proposal + /** + * Verify the signing hash of the proposal + */ bool checkSign() const; - //! Signature of the proposal (not necessarily verified) + /** + * Signature of the proposal (not necessarily verified) + */ Slice signature() const { return {signature_.data(), signature_.size()}; } - //! Public key of peer that sent the proposal + /** + * Public key of peer that sent the proposal + */ PublicKey const& publicKey() const { return publicKey_; } - //! Unique id used by hash router to suppress duplicates + /** + * Unique id used by hash router to suppress duplicates + */ uint256 const& suppressionID() const { @@ -76,7 +86,9 @@ public: return proposal_; } - //! JSON representation of proposal + /** + * JSON representation of proposal + */ json::Value getJson() const; @@ -105,22 +117,23 @@ private: } }; -/** Calculate a unique identifier for a signed proposal. - - The identifier is based on all the fields that contribute to the signature, - as well as the signature itself. The "last closed ledger" field may be - omitted, but the signer will compute the signature as if this field was - present. Recipients of the proposal will inject the last closed ledger in - order to validate the signature. If the last closed ledger is left out, then - it is considered as all zeroes for the purposes of signing. - - @param proposeHash The hash of the proposed position - @param previousLedger The hash of the ledger the proposal is based upon - @param proposeSeq Sequence number of the proposal - @param closeTime Close time of the proposal - @param publicKey Signer's public key - @param signature Proposal signature -*/ +/** + * Calculate a unique identifier for a signed proposal. + * + * The identifier is based on all the fields that contribute to the signature, + * as well as the signature itself. The "last closed ledger" field may be + * omitted, but the signer will compute the signature as if this field was + * present. Recipients of the proposal will inject the last closed ledger in + * order to validate the signature. If the last closed ledger is left out, then + * it is considered as all zeroes for the purposes of signing. + * + * @param proposeHash The hash of the proposed position + * @param previousLedger The hash of the ledger the proposal is based upon + * @param proposeSeq Sequence number of the proposal + * @param closeTime Close time of the proposal + * @param publicKey Signer's public key + * @param signature Proposal signature + */ uint256 proposalUniqueId( uint256 const& proposeHash, diff --git a/src/xrpld/app/consensus/RCLCxTx.h b/src/xrpld/app/consensus/RCLCxTx.h index f174a2fd54..110ef14e1d 100644 --- a/src/xrpld/app/consensus/RCLCxTx.h +++ b/src/xrpld/app/consensus/RCLCxTx.h @@ -12,54 +12,69 @@ namespace xrpl { -/** Represents a transaction in RCLConsensus. - - RCLCxTx is a thin wrapper over the SHAMapItem that corresponds to the - transaction. -*/ +/** + * Represents a transaction in RCLConsensus. + * + * RCLCxTx is a thin wrapper over the SHAMapItem that corresponds to the + * transaction. + */ class RCLCxTx { public: - //! Unique identifier/hash of transaction + /** + * Unique identifier/hash of transaction + */ using ID = uint256; - /** Constructor - - @param txn The transaction to wrap - */ + /** + * Constructor + * + * @param txn The transaction to wrap + */ RCLCxTx(boost::intrusive_ptr txn) : tx(std::move(txn)) { } - //! The unique identifier/hash of the transaction + /** + * The unique identifier/hash of the transaction + */ [[nodiscard]] ID const& id() const { return tx->key(); } - //! The SHAMapItem that represents the transaction. + /** + * The SHAMapItem that represents the transaction. + */ boost::intrusive_ptr tx; }; -/** Represents a set of transactions in RCLConsensus. - - RCLTxSet is a thin wrapper over a SHAMap that stores the set of - transactions. -*/ +/** + * Represents a set of transactions in RCLConsensus. + * + * RCLTxSet is a thin wrapper over a SHAMap that stores the set of + * transactions. + */ class RCLTxSet { public: - //! Unique identifier/hash of the set of transactions + /** + * Unique identifier/hash of the set of transactions + */ using ID = uint256; - //! The type that corresponds to a single transaction + /** + * The type that corresponds to a single transaction + */ using Tx = RCLCxTx; //< Provide a mutable view of a TxSet class MutableTxSet { friend class RCLTxSet; - //! The SHAMap representing the transactions. + /** + * The SHAMap representing the transactions. + */ std::shared_ptr map_; public: @@ -67,22 +82,24 @@ public: { } - /** Insert a new transaction into the set. - - @param t The transaction to insert. - @return Whether the transaction took place. - */ + /** + * Insert a new transaction into the set. + * + * @param t The transaction to insert. + * @return Whether the transaction took place. + */ bool insert(Tx const& t) { return map_->addItem(SHAMapNodeType::TnTransactionNm, t.tx); } - /** Remove a transaction from the set. - - @param entry The ID of the transaction to remove. - @return Whether the transaction was removed. - */ + /** + * Remove a transaction from the set. + * + * @param entry The ID of the transaction to remove. + * @return Whether the transaction was removed. + */ bool erase(Tx::ID const& entry) { @@ -90,66 +107,73 @@ public: } }; - /** Constructor - - @param m SHAMap to wrap - */ + /** + * Constructor + * + * @param m SHAMap to wrap + */ RCLTxSet(std::shared_ptr m) : map{std::move(m)} { XRPL_ASSERT(map, "xrpl::RCLTxSet::MutableTxSet::RCLTxSet : non-null input"); } - /** Constructor from a previously created MutableTxSet - - @param m MutableTxSet that will become fixed + /** + * Constructor from a previously created MutableTxSet + * + * @param m MutableTxSet that will become fixed */ RCLTxSet(MutableTxSet const& m) : map{m.map_->snapShot(false)} { } - /** Test if a transaction is in the set. - - @param entry The ID of transaction to test. - @return Whether the transaction is in the set. - */ + /** + * Test if a transaction is in the set. + * + * @param entry The ID of transaction to test. + * @return Whether the transaction is in the set. + */ [[nodiscard]] bool exists(Tx::ID const& entry) const { return map->hasItem(entry); } - /** Lookup a transaction. - - @param entry The ID of the transaction to find. - @return A shared pointer to the SHAMapItem. - - @note Since find may not succeed, this returns a - `std::shared_ptr` rather than a Tx, which - cannot refer to a missing transaction. The generic consensus - code uses the shared_ptr semantics to know whether the find - was successful and properly creates a Tx as needed. - */ + /** + * Lookup a transaction. + * + * @param entry The ID of the transaction to find. + * @return A shared pointer to the SHAMapItem. + * + * @note Since find may not succeed, this returns a + * `std::shared_ptr` rather than a Tx, which + * cannot refer to a missing transaction. The generic consensus + * code uses the shared_ptr semantics to know whether the find + * was successful and properly creates a Tx as needed. + */ [[nodiscard]] boost::intrusive_ptr const& find(Tx::ID const& entry) const { return map->peekItem(entry); } - //! The unique ID/hash of the transaction set + /** + * The unique ID/hash of the transaction set + */ [[nodiscard]] ID id() const { return map->getHash().asUInt256(); } - /** Find transactions not in common between this and another transaction - set. - - @param j The set to compare with - @return Map of transactions in this set and `j` but not both. The key - is the transaction ID and the value is a bool of the transaction - exists in this set. - */ + /** + * Find transactions not in common between this and another transaction + * set. + * + * @param j The set to compare with + * @return Map of transactions in this set and `j` but not both. The key + * is the transaction ID and the value is a bool of the transaction + * exists in this set. + */ [[nodiscard]] std::map compare(RCLTxSet const& j) const { @@ -171,7 +195,9 @@ public: return ret; } - //! The SHAMap representing the transactions. + /** + * The SHAMap representing the transactions. + */ std::shared_ptr map; }; } // namespace xrpl diff --git a/src/xrpld/app/consensus/RCLValidations.h b/src/xrpld/app/consensus/RCLValidations.h index e8a1996204..7eadaf0dff 100644 --- a/src/xrpld/app/consensus/RCLValidations.h +++ b/src/xrpld/app/consensus/RCLValidations.h @@ -27,10 +27,11 @@ class Application; enum class BypassAccept : bool { No = false, Yes }; -/** Wrapper over STValidation for generic Validation code - - Wraps an STValidation for compatibility with the generic validation code. -*/ +/** + * Wrapper over STValidation for generic Validation code + * + * Wraps an STValidation for compatibility with the generic validation code. + */ class RCLValidation { std::shared_ptr val_; @@ -39,57 +40,72 @@ public: using NodeKey = xrpl::PublicKey; using NodeID = xrpl::NodeID; - /** Constructor - - @param v The validation to wrap. - */ + /** + * Constructor + * + * @param v The validation to wrap. + */ RCLValidation(std::shared_ptr v) : val_{std::move(v)} { } - /// Validated ledger's hash + /** + * Validated ledger's hash + */ [[nodiscard]] uint256 ledgerID() const { return val_->getLedgerHash(); } - /// Validated ledger's sequence number (0 if none) + /** + * Validated ledger's sequence number (0 if none) + */ [[nodiscard]] std::uint32_t seq() const { return val_->getFieldU32(sfLedgerSequence); } - /// Validation's signing time + /** + * Validation's signing time + */ [[nodiscard]] NetClock::time_point signTime() const { return val_->getSignTime(); } - /// Validated ledger's first seen time + /** + * Validated ledger's first seen time + */ [[nodiscard]] NetClock::time_point seenTime() const { return val_->getSeenTime(); } - /// Public key of validator that published the validation + /** + * Public key of validator that published the validation + */ [[nodiscard]] PublicKey key() const { return val_->getSignerPublic(); } - /// NodeID of validator that published the validation + /** + * NodeID of validator that published the validation + */ [[nodiscard]] NodeID nodeID() const { return val_->getNodeID(); } - /// Whether the validation is considered trusted. + /** + * Whether the validation is considered trusted. + */ [[nodiscard]] bool trusted() const { @@ -108,28 +124,36 @@ public: val_->setUntrusted(); } - /// Whether the validation is full (not-partial) + /** + * Whether the validation is full (not-partial) + */ [[nodiscard]] bool full() const { return val_->isFull(); } - /// Get the load fee of the validation if it exists + /** + * Get the load fee of the validation if it exists + */ [[nodiscard]] std::optional loadFee() const { return ~(*val_)[~sfLoadFee]; } - /// Get the cookie specified in the validation (0 if not set) + /** + * Get the cookie specified in the validation (0 if not set) + */ [[nodiscard]] std::uint64_t cookie() const { return (*val_)[sfCookie]; } - /// Extract the underlying STValidation being wrapped + /** + * Extract the underlying STValidation being wrapped + */ [[nodiscard]] std::shared_ptr unwrap() const { @@ -137,15 +161,16 @@ public: } }; -/** Wraps a ledger instance for use in generic Validations LedgerTrie. - - The LedgerTrie models a ledger's history as a map from Seq -> ID. Any - two ledgers that have the same ID for a given Seq have the same ID for - all earlier sequences (e.g. shared ancestry). In practice, a ledger only - conveniently has the prior 256 ancestor hashes available. For - RCLValidatedLedger, we treat any ledgers separated by more than 256 Seq as - distinct. -*/ +/** + * Wraps a ledger instance for use in generic Validations LedgerTrie. + * + * The LedgerTrie models a ledger's history as a map from Seq -> ID. Any + * two ledgers that have the same ID for a given Seq have the same ID for + * all earlier sequences (e.g. shared ancestry). In practice, a ledger only + * conveniently has the prior 256 ancestor hashes available. For + * RCLValidatedLedger, we treat any ledgers separated by more than 256 Seq as + * distinct. + */ class RCLValidatedLedger { public: @@ -160,24 +185,31 @@ public: RCLValidatedLedger(std::shared_ptr const& ledger, beast::Journal j); - /// The sequence (index) of the ledger + /** + * The sequence (index) of the ledger + */ [[nodiscard]] Seq seq() const; - /// The ID (hash) of the ledger + /** + * The ID (hash) of the ledger + */ [[nodiscard]] ID id() const; - /** Lookup the ID of the ancestor ledger - - @param s The sequence (index) of the ancestor - @return The ID of this ledger's ancestor with that sequence number or - ID{0} if one was not determined - */ + /** + * Lookup the ID of the ancestor ledger + * + * @param s The sequence (index) of the ancestor + * @return The ID of this ledger's ancestor with that sequence number or + * ID{0} if one was not determined + */ ID operator[](Seq const& s) const; - /// Find the sequence number of the earliest mismatching ancestor + /** + * Find the sequence number of the earliest mismatching ancestor + */ friend Seq mismatch(RCLValidatedLedger const& a, RCLValidatedLedger const& b); @@ -191,11 +223,12 @@ private: beast::Journal j_; }; -/** Generic validations adaptor class for RCL - - Manages storing and writing stale RCLValidations to the sqlite DB and - acquiring validated ledgers from the network. -*/ +/** + * Generic validations adaptor class for RCL + * + * Manages storing and writing stale RCLValidations to the sqlite DB and + * acquiring validated ledgers from the network. + */ class RCLValidationsAdaptor { public: @@ -206,12 +239,15 @@ public: RCLValidationsAdaptor(Application& app, beast::Journal j); - /** Current time used to determine if validations are stale. + /** + * Current time used to determine if validations are stale. */ [[nodiscard]] NetClock::time_point now() const; - /** Attempt to acquire the ledger with given id from the network */ + /** + * Attempt to acquire the ledger with given id from the network + */ std::optional acquire(LedgerHash const& id); @@ -226,18 +262,21 @@ private: beast::Journal j_; }; -/// Alias for RCL-specific instantiation of generic Validations +/** + * Alias for RCL-specific instantiation of generic Validations + */ using RCLValidations = Validations; -/** Handle a new validation - - Also sets the trust status of a validation based on the validating node's - public key and this node's current UNL. - - @param app Application object containing validations and ledgerMaster - @param val The validation to add - @param source Name associated with validation used in logging -*/ +/** + * Handle a new validation + * + * Also sets the trust status of a validation based on the validating node's + * public key and this node's current UNL. + * + * @param app Application object containing validations and ledgerMaster + * @param val The validation to add + * @param source Name associated with validation used in logging + */ void handleNewValidation( Application& app, diff --git a/src/xrpld/app/ledger/AbstractFetchPackContainer.h b/src/xrpld/app/ledger/AbstractFetchPackContainer.h index 3adc435bd6..6298ddda0d 100644 --- a/src/xrpld/app/ledger/AbstractFetchPackContainer.h +++ b/src/xrpld/app/ledger/AbstractFetchPackContainer.h @@ -7,20 +7,22 @@ namespace xrpl { -/** An interface facilitating retrieval of fetch packs without - an application or ledgermaster object. -*/ +/** + * An interface facilitating retrieval of fetch packs without + * an application or ledgermaster object. + */ class AbstractFetchPackContainer { public: virtual ~AbstractFetchPackContainer() = default; - /** Retrieves partial ledger data of the corresponding hash from peers.` - - @param nodeHash The 256-bit hash of the data to fetch. - @return `std::nullopt` if the hash isn't cached, - otherwise, the hash associated data. - */ + /** + * Retrieves partial ledger data of the corresponding hash from peers.` + * + * @param nodeHash The 256-bit hash of the data to fetch. + * @return `std::nullopt` if the hash isn't cached, + * otherwise, the hash associated data. + */ virtual std::optional getFetchPack(uint256 const& nodeHash) = 0; }; diff --git a/src/xrpld/app/ledger/AcceptedLedger.h b/src/xrpld/app/ledger/AcceptedLedger.h index ec83839d7a..6e42d611d4 100644 --- a/src/xrpld/app/ledger/AcceptedLedger.h +++ b/src/xrpld/app/ledger/AcceptedLedger.h @@ -11,14 +11,15 @@ namespace xrpl { -/** A ledger that has become irrevocable. - - An accepted ledger is a ledger that has a sufficient number of - validations to convince the local server that it is irrevocable. - - The existence of an accepted ledger implies all preceding ledgers - are accepted. -*/ +/** + * A ledger that has become irrevocable. + * + * An accepted ledger is a ledger that has a sufficient number of + * validations to convince the local server that it is irrevocable. + * + * The existence of an accepted ledger implies all preceding ledgers + * are accepted. + */ /* VFALCO TODO digest this terminology clarification: Closed and accepted refer to ledgers that have not passed the validation threshold yet. Once they pass the threshold, they are diff --git a/src/xrpld/app/ledger/BuildLedger.h b/src/xrpld/app/ledger/BuildLedger.h index faa800daa7..22e33cabc0 100644 --- a/src/xrpld/app/ledger/BuildLedger.h +++ b/src/xrpld/app/ledger/BuildLedger.h @@ -16,21 +16,22 @@ class Ledger; class LedgerReplay; class SHAMap; -/** Build a new ledger by applying consensus transactions - - Build a new ledger by applying a set of transactions accepted as part of - consensus. - - @param parent The ledger to apply transactions to - @param closeTime The time the ledger closed - @param closeTimeCorrect Whether consensus agreed on close time - @param closeResolution Resolution used to determine consensus close time - @param app Handle to application instance - @param txs On entry, transactions to apply; on exit, transactions that must - be retried in next round. - @param failedTxs Populated with transactions that failed in this round - @param j Journal to use for logging - @return The newly built ledger +/** + * Build a new ledger by applying consensus transactions + * + * Build a new ledger by applying a set of transactions accepted as part of + * consensus. + * + * @param parent The ledger to apply transactions to + * @param closeTime The time the ledger closed + * @param closeTimeCorrect Whether consensus agreed on close time + * @param closeResolution Resolution used to determine consensus close time + * @param app Handle to application instance + * @param txs On entry, transactions to apply; on exit, transactions that must + * be retried in next round. + * @param failedTxs Populated with transactions that failed in this round + * @param j Journal to use for logging + * @return The newly built ledger */ std::shared_ptr buildLedger( @@ -43,15 +44,16 @@ buildLedger( std::set& failedTxs, beast::Journal j); -/** Build a new ledger by replaying transactions - - Build a new ledger by replaying transactions accepted into a prior ledger. - - @param replayData Data of the ledger to replay - @param applyFlags Flags to use when applying transactions - @param app Handle to application instance - @param j Journal to use for logging - @return The newly built ledger +/** + * Build a new ledger by replaying transactions + * + * Build a new ledger by replaying transactions accepted into a prior ledger. + * + * @param replayData Data of the ledger to replay + * @param applyFlags Flags to use when applying transactions + * @param app Handle to application instance + * @param j Journal to use for logging + * @return The newly built ledger */ std::shared_ptr buildLedger( diff --git a/src/xrpld/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h index 5f9f0e1baf..d8a9ddf46b 100644 --- a/src/xrpld/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -59,14 +59,18 @@ public: void update(std::uint32_t seq); - /** Returns true if we got all the data. */ + /** + * Returns true if we got all the data. + */ bool isComplete() const { return complete_; } - /** Returns false if we failed to get the data. */ + /** + * Returns false if we failed to get the data. + */ bool isFailed() const { @@ -95,7 +99,9 @@ public: using neededHash_t = std::pair; - /** Return a json::ValueType::Object. */ + /** + * Return a json::ValueType::Object. + */ json::Value getJson(int); diff --git a/src/xrpld/app/ledger/InboundLedgers.h b/src/xrpld/app/ledger/InboundLedgers.h index 65b2db7d8e..e288201c66 100644 --- a/src/xrpld/app/ledger/InboundLedgers.h +++ b/src/xrpld/app/ledger/InboundLedgers.h @@ -20,10 +20,11 @@ namespace xrpl { -/** Manages the lifetime of inbound ledgers. - - @see InboundLedger -*/ +/** + * Manages the lifetime of inbound ledgers. + * + * @see InboundLedger + */ class InboundLedgers { public: @@ -68,11 +69,15 @@ public: virtual json::Value getInfo() = 0; - /** Returns the rate of historical ledger fetches per minute. */ + /** + * Returns the rate of historical ledger fetches per minute. + */ virtual std::size_t fetchRate() = 0; - /** Called when a complete ledger is obtained. */ + /** + * Called when a complete ledger is obtained. + */ virtual void onLedgerFetched() = 0; diff --git a/src/xrpld/app/ledger/InboundTransactions.h b/src/xrpld/app/ledger/InboundTransactions.h index d9799d9ad0..a961d04c33 100644 --- a/src/xrpld/app/ledger/InboundTransactions.h +++ b/src/xrpld/app/ledger/InboundTransactions.h @@ -18,7 +18,8 @@ namespace xrpl { class Application; -/** Manages the acquisition and lifetime of transaction sets. +/** + * Manages the acquisition and lifetime of transaction sets. */ class InboundTransactions @@ -33,7 +34,8 @@ public: virtual ~InboundTransactions() = 0; - /** Find and return a transaction set, or nullptr if it is missing. + /** + * Find and return a transaction set, or nullptr if it is missing. * * @param setHash The transaction set ID (digest of the SHAMap root node). * @param acquire Whether to fetch the transaction set from the network if @@ -44,7 +46,8 @@ public: virtual std::shared_ptr getSet(uint256 const& setHash, bool acquire) = 0; - /** Add a transaction set from a LedgerData message. + /** + * Add a transaction set from a LedgerData message. * * @param setHash The transaction set ID (digest of the SHAMap root node). * @param peer The peer that sent the message. @@ -56,7 +59,8 @@ public: std::shared_ptr peer, std::shared_ptr message) = 0; - /** Add a transaction set. + /** + * Add a transaction set. * * @param setHash The transaction set ID (should match set.getHash()). * @param set The transaction set. @@ -66,7 +70,8 @@ public: virtual void giveSet(uint256 const& setHash, std::shared_ptr const& set, bool acquired) = 0; - /** Informs the container if a new consensus round + /** + * Informs the container if a new consensus round */ virtual void newRound(std::uint32_t seq) = 0; diff --git a/src/xrpld/app/ledger/LedgerCleaner.h b/src/xrpld/app/ledger/LedgerCleaner.h index fd693d6bec..9dc35d463f 100644 --- a/src/xrpld/app/ledger/LedgerCleaner.h +++ b/src/xrpld/app/ledger/LedgerCleaner.h @@ -10,7 +10,9 @@ namespace xrpl { -/** Check the ledger/transaction databases to make sure they have continuity */ +/** + * Check the ledger/transaction databases to make sure they have continuity + */ class LedgerCleaner : public beast::PropertyStream::Source { protected: @@ -27,16 +29,17 @@ public: virtual void stop() = 0; - /** Start a long running task to clean the ledger. - The ledger is cleaned asynchronously, on an implementation defined - thread. This function call does not block. The long running task - will be stopped by a call to stop(). - - Thread safety: - Safe to call from any thread at any time. - - @param parameters A Json object with configurable parameters. - */ + /** + * Start a long running task to clean the ledger. + * The ledger is cleaned asynchronously, on an implementation defined + * thread. This function call does not block. The long running task + * will be stopped by a call to stop(). + * + * Thread safety: + * Safe to call from any thread at any time. + * + * @param parameters A Json object with configurable parameters. + */ virtual void clean(json::Value const& parameters) = 0; }; diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index b7e1772942..8734faa1fb 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -502,7 +502,8 @@ LedgerHistory::validatedLedger( entry->validatedConsensusHash = consensusHash; } -/** Ensure ledgers_by_hash_ doesn't have the wrong hash for a particular index +/** + * Ensure ledgers_by_hash_ doesn't have the wrong hash for a particular index */ bool LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash) diff --git a/src/xrpld/app/ledger/LedgerHistory.h b/src/xrpld/app/ledger/LedgerHistory.h index 3fb6e345cf..922b354c32 100644 --- a/src/xrpld/app/ledger/LedgerHistory.h +++ b/src/xrpld/app/ledger/LedgerHistory.h @@ -19,43 +19,53 @@ namespace xrpl { // VFALCO TODO Rename to OldLedgers ? -/** Retains historical ledgers. */ +/** + * Retains historical ledgers. + */ class LedgerHistory { public: LedgerHistory(beast::insight::Collector::ptr const& collector, Application& app); - /** Track a ledger - @return `true` if the ledger was already tracked - */ + /** + * Track a ledger + * @return `true` if the ledger was already tracked + */ bool insert(std::shared_ptr const& ledger, bool validated); - /** Get the ledgers_by_hash cache hit rate - @return the hit rate - */ + /** + * Get the ledgers_by_hash cache hit rate + * @return the hit rate + */ float getCacheHitRate() { return ledgersByHash_.getHitRate(); } - /** Get a ledger given its sequence number */ + /** + * Get a ledger given its sequence number + */ std::shared_ptr getLedgerBySeq(LedgerIndex ledgerIndex); - /** Retrieve a ledger given its hash */ + /** + * Retrieve a ledger given its hash + */ std::shared_ptr getLedgerByHash(LedgerHash const& ledgerHash); - /** Get a ledger's hash given its sequence number - @param ledgerIndex The sequence number of the desired ledger - @return The hash of the specified ledger - */ + /** + * Get a ledger's hash given its sequence number + * @param ledgerIndex The sequence number of the desired ledger + * @return The hash of the specified ledger + */ LedgerHash getLedgerHash(LedgerIndex ledgerIndex); - /** Remove stale cache entries + /** + * Remove stale cache entries */ void sweep() @@ -64,21 +74,26 @@ public: consensusValidated_.sweep(); } - /** Report that we have locally built a particular ledger */ + /** + * Report that we have locally built a particular ledger + */ void builtLedger(std::shared_ptr const&, uint256 const& consensusHash, json::Value); - /** Report that we have validated a particular ledger */ + /** + * Report that we have validated a particular ledger + */ void validatedLedger( std::shared_ptr const&, std::optional const& consensusHash); - /** 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 `false` if the mapping was repaired - */ + /** + * 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 `false` if the mapping was repaired + */ bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash); @@ -86,16 +101,17 @@ public: clearLedgerCachePrior(LedgerIndex seq); private: - /** Log details in the case where we build one ledger but - validate a different one. - @param built The hash of the ledger we built - @param valid The hash of the ledger we deemed fully valid - @param builtConsensusHash The hash of the consensus transaction for the - ledger we built - @param validatedConsensusHash The hash of the validated ledger's - consensus transaction set - @param consensus The status of the consensus round - */ + /** + * Log details in the case where we build one ledger but + * validate a different one. + * @param built The hash of the ledger we built + * @param valid The hash of the ledger we deemed fully valid + * @param builtConsensusHash The hash of the consensus transaction for the + * ledger we built + * @param validatedConsensusHash The hash of the validated ledger's + * consensus transaction set + * @param consensus The status of the consensus round + */ void handleMismatch( LedgerHash const& built, diff --git a/src/xrpld/app/ledger/LedgerHolder.h b/src/xrpld/app/ledger/LedgerHolder.h index 3e70544bfc..da365e5e31 100644 --- a/src/xrpld/app/ledger/LedgerHolder.h +++ b/src/xrpld/app/ledger/LedgerHolder.h @@ -14,12 +14,13 @@ namespace xrpl { // VFALCO NOTE This class can be replaced with atomic> -/** Hold a ledger in a thread-safe way. - - VFALCO TODO The constructor should require a valid ledger, this - way the object always holds a value. We can use the - genesis ledger in all cases. -*/ +/** + * Hold a ledger in a thread-safe way. + * + * VFALCO TODO The constructor should require a valid ledger, this + * way the object always holds a value. We can use the + * genesis ledger in all cases. + */ class LedgerHolder : public CountedObject { public: diff --git a/src/xrpld/app/ledger/LedgerMaster.h b/src/xrpld/app/ledger/LedgerMaster.h index efd8c15e20..32163fd57b 100644 --- a/src/xrpld/app/ledger/LedgerMaster.h +++ b/src/xrpld/app/ledger/LedgerMaster.h @@ -108,10 +108,11 @@ public: void setFullLedger(std::shared_ptr const& ledger, bool isSynchronous, bool isCurrent); - /** Check the sequence number and parent close time of a - ledger against our clock and last validated ledger to - see if it can be the network's current ledger - */ + /** + * Check the sequence number and parent close time of a + * ledger against our clock and last validated ledger to + * see if it can be the network's current ledger + */ bool canBeCurrent(std::shared_ptr const& ledger); @@ -124,38 +125,44 @@ public: std::string getCompleteLedgers(); - /** Apply held transactions to the open ledger - This is normally called as we close the ledger. - The open ledger remains open to handle new transactions - until a new open ledger is built. - */ + /** + * Apply held transactions to the open ledger + * This is normally called as we close the ledger. + * The open ledger remains open to handle new transactions + * until a new open ledger is built. + */ void applyHeldTransactions(); - /** Get the next transaction held for a particular account if any. - This is normally called when a transaction for that account is - successfully applied to the open ledger so the next transaction - can be resubmitted without waiting for ledger close. - */ + /** + * Get the next transaction held for a particular account if any. + * This is normally called when a transaction for that account is + * successfully applied to the open ledger so the next transaction + * can be resubmitted without waiting for ledger close. + */ std::shared_ptr popAcctTransaction(std::shared_ptr const& tx); - /** Get a ledger's hash by sequence number using the cache + /** + * Get a ledger's hash by sequence number using the cache */ uint256 getHashBySeq(std::uint32_t index); - /** Walk to a ledger's hash using the skip list */ + /** + * Walk to a ledger's hash using the skip list + */ std::optional walkHashBySeq(std::uint32_t index, InboundLedger::Reason reason); - /** Walk the chain of ledger hashes to determine the hash of the - ledger with the specified index. The referenceLedger is used as - the base of the chain and should be fully validated and must not - precede the target index. This function may throw if nodes - from the reference ledger or any prior ledger are not present - in the node store. - */ + /** + * Walk the chain of ledger hashes to determine the hash of the + * ledger with the specified index. The referenceLedger is used as + * the base of the chain and should be fully validated and must not + * precede the target index. This function may throw if nodes + * from the reference ledger or any prior ledger are not present + * in the node store. + */ std::optional walkHashBySeq( std::uint32_t index, @@ -255,7 +262,9 @@ public: std::size_t getFetchPackCacheSize() const; - //! Whether we have ever fully validated a ledger. + /** + * Whether we have ever fully validated a ledger. + */ bool haveValidated() { diff --git a/src/xrpld/app/ledger/LedgerPersistence.h b/src/xrpld/app/ledger/LedgerPersistence.h index f466c32296..e2e442cb30 100644 --- a/src/xrpld/app/ledger/LedgerPersistence.h +++ b/src/xrpld/app/ledger/LedgerPersistence.h @@ -14,15 +14,16 @@ namespace xrpl { class ServiceRegistry; struct Fees; -/** Save, or arrange to save, a fully-validated ledger. - - @param registry The service registry providing access to required services. - @param ledger The fully-validated ledger to save. - @param isSynchronous If true, wait for the save to complete. - @param isCurrent If true, the ledger is the current validated ledger. - - @return false on error. -*/ +/** + * Save, or arrange to save, a fully-validated ledger. + * + * @param registry The service registry providing access to required services. + * @param ledger The fully-validated ledger to save. + * @param isSynchronous If true, wait for the save to complete. + * @param isCurrent If true, the ledger is the current validated ledger. + * + * @return false on error. + */ bool pendSaveValidated( ServiceRegistry& registry, @@ -30,15 +31,16 @@ pendSaveValidated( bool isSynchronous, bool isCurrent); -/** Make ledger using info loaded from database. - - @param info Ledger information. - @param rules Rules to use (may be overwritten by setup()). - @param fees Fees to use (may be overwritten by setup()). - @param registry Service registry for dependency injection. - @param acquire Acquire the ledger if not found locally. - @return Shared pointer to the ledger. -*/ +/** + * Make ledger using info loaded from database. + * + * @param info Ledger information. + * @param rules Rules to use (may be overwritten by setup()). + * @param fees Fees to use (may be overwritten by setup()). + * @param registry Service registry for dependency injection. + * @param acquire Acquire the ledger if not found locally. + * @return Shared pointer to the ledger. + */ std::shared_ptr loadLedgerHelper( LedgerHeader const& info, @@ -47,15 +49,16 @@ loadLedgerHelper( ServiceRegistry& registry, bool acquire); -/** Load a ledger by its sequence number. - - @param ledgerIndex The sequence number of the ledger to load. - @param rules Rules to use (may be overwritten by setup()). - @param fees Fees to use (may be overwritten by setup()). - @param registry Service registry for dependency injection. - @param acquire Acquire the ledger if not found locally. - @return Shared pointer to the ledger, or nullptr if not found. -*/ +/** + * Load a ledger by its sequence number. + * + * @param ledgerIndex The sequence number of the ledger to load. + * @param rules Rules to use (may be overwritten by setup()). + * @param fees Fees to use (may be overwritten by setup()). + * @param registry Service registry for dependency injection. + * @param acquire Acquire the ledger if not found locally. + * @return Shared pointer to the ledger, or nullptr if not found. + */ std::shared_ptr loadByIndex( std::uint32_t ledgerIndex, @@ -64,15 +67,16 @@ loadByIndex( ServiceRegistry& registry, bool acquire = true); -/** Load a ledger by its hash. - - @param ledgerHash The hash of the ledger to load. - @param rules Rules to use (may be overwritten by setup()). - @param fees Fees to use (may be overwritten by setup()). - @param registry Service registry for dependency injection. - @param acquire Acquire the ledger if not found locally. - @return Shared pointer to the ledger, or nullptr if not found. -*/ +/** + * Load a ledger by its hash. + * + * @param ledgerHash The hash of the ledger to load. + * @param rules Rules to use (may be overwritten by setup()). + * @param fees Fees to use (may be overwritten by setup()). + * @param registry Service registry for dependency injection. + * @param acquire Acquire the ledger if not found locally. + * @return Shared pointer to the ledger, or nullptr if not found. + */ std::shared_ptr loadByHash( uint256 const& ledgerHash, @@ -81,13 +85,14 @@ loadByHash( ServiceRegistry& registry, bool acquire = true); -/** Fetch the ledger with the highest sequence contained in the database. - - @param rules Rules to use (may be overwritten by setup()). - @param fees Fees to use (may be overwritten by setup()). - @param registry Service registry for dependency injection. - @return Tuple of (ledger, sequence, hash), or empty if not found. -*/ +/** + * Fetch the ledger with the highest sequence contained in the database. + * + * @param rules Rules to use (may be overwritten by setup()). + * @param fees Fees to use (may be overwritten by setup()). + * @param registry Service registry for dependency injection. + * @return Tuple of (ledger, sequence, hash), or empty if not found. + */ std::tuple, std::uint32_t, uint256> getLatestLedger(Rules const& rules, Fees const& fees, ServiceRegistry& registry); diff --git a/src/xrpld/app/ledger/LedgerReplay.h b/src/xrpld/app/ledger/LedgerReplay.h index 2dc4911ade..6a2da92007 100644 --- a/src/xrpld/app/ledger/LedgerReplay.h +++ b/src/xrpld/app/ledger/LedgerReplay.h @@ -25,7 +25,8 @@ public: std::shared_ptr replay, std::map>&& orderedTxns); - /** @return The parent of the ledger to replay + /** + * @return The parent of the ledger to replay */ [[nodiscard]] std::shared_ptr const& parent() const @@ -33,7 +34,8 @@ public: return parent_; } - /** @return The ledger to replay + /** + * @return The ledger to replay */ [[nodiscard]] std::shared_ptr const& replay() const @@ -41,7 +43,8 @@ public: return replay_; } - /** @return Transactions in the order they should be replayed + /** + * @return Transactions in the order they should be replayed */ [[nodiscard]] std::map> const& orderedTxns() const diff --git a/src/xrpld/app/ledger/LedgerReplayTask.h b/src/xrpld/app/ledger/LedgerReplayTask.h index 09329761c1..d908a36fc0 100644 --- a/src/xrpld/app/ledger/LedgerReplayTask.h +++ b/src/xrpld/app/ledger/LedgerReplayTask.h @@ -64,7 +64,9 @@ public: bool update(uint256 const& hash, std::uint32_t seq, std::vector const& sList); - /** check if this task can be merged into an existing task */ + /** + * check if this task can be merged into an existing task + */ [[nodiscard]] bool canMergeInto(TaskParameter const& existingTask) const; }; @@ -87,7 +89,9 @@ public: ~LedgerReplayTask() override; - /** Start the task */ + /** + * Start the task + */ void init(); @@ -105,7 +109,9 @@ public: return parameter_; } - /** return if the task is finished */ + /** + * return if the task is finished + */ bool finished() const; diff --git a/src/xrpld/app/ledger/LedgerReplayer.h b/src/xrpld/app/ledger/LedgerReplayer.h index d44289121c..6feb187df6 100644 --- a/src/xrpld/app/ledger/LedgerReplayer.h +++ b/src/xrpld/app/ledger/LedgerReplayer.h @@ -78,7 +78,9 @@ public: void replay(InboundLedger::Reason r, uint256 const& finishLedgerHash, std::uint32_t totalNumLedgers); - /** Create LedgerDeltaAcquire subtasks for the LedgerReplayTask task */ + /** + * Create LedgerDeltaAcquire subtasks for the LedgerReplayTask task + */ void createDeltas(std::shared_ptr task); @@ -102,7 +104,9 @@ public: LedgerHeader const& info, std::map>&& txns); - /** Remove completed tasks */ + /** + * Remove completed tasks + */ void sweep(); diff --git a/src/xrpld/app/ledger/LedgerToJson.h b/src/xrpld/app/ledger/LedgerToJson.h index c9939fd2f4..1eac4d68f1 100644 --- a/src/xrpld/app/ledger/LedgerToJson.h +++ b/src/xrpld/app/ledger/LedgerToJson.h @@ -44,17 +44,22 @@ struct LedgerFill std::optional closeTime; }; -/** Given a Ledger and options, fill a json::Value with a - description of the ledger. +/** + * Given a Ledger and options, fill a json::Value with a + * description of the ledger. */ void addJson(json::Value&, LedgerFill const&); -/** Return a new json::Value representing the ledger with given options.*/ +/** + * Return a new json::Value representing the ledger with given options. + */ json::Value getJson(LedgerFill const&); -/** Copy all the keys and values from one object into another. */ +/** + * Copy all the keys and values from one object into another. + */ void copyFrom(json::Value& to, json::Value const& from); diff --git a/src/xrpld/app/ledger/OpenLedger.h b/src/xrpld/app/ledger/OpenLedger.h index 3e0577a9be..4a0aa105f9 100644 --- a/src/xrpld/app/ledger/OpenLedger.h +++ b/src/xrpld/app/ledger/OpenLedger.h @@ -36,7 +36,9 @@ using OrderedTxs = CanonicalTXSet; //------------------------------------------------------------------------------ -/** Represents the open ledger. */ +/** + * Represents the open ledger. + */ class OpenLedger { private: @@ -47,17 +49,18 @@ private: std::shared_ptr current_; public: - /** Signature for modification functions. - - The modification function is called during - apply and modify with an OpenView to accumulate - changes and the Journal to use for logging. - - A return value of `true` informs OpenLedger - that changes were made. Always returning - `true` won't cause harm, but it may be - sub-optimal. - */ + /** + * Signature for modification functions. + * + * The modification function is called during + * apply and modify with an OpenView to accumulate + * changes and the Journal to use for logging. + * + * A return value of `true` informs OpenLedger + * that changes were made. Always returning + * `true` won't cause harm, but it may be + * sub-optimal. + */ using modify_type = std::function; OpenLedger() = delete; @@ -65,90 +68,95 @@ public: OpenLedger& operator=(OpenLedger const&) = delete; - /** Create a new open ledger object. - - @param ledger A closed ledger - */ + /** + * Create a new open ledger object. + * + * @param ledger A closed ledger + */ explicit OpenLedger( std::shared_ptr const& ledger, CachedSLEs& cache, beast::Journal journal); - /** Returns `true` if there are no transactions. - - The behavior of ledger closing can be different - depending on whether or not transactions exist - in the open ledger. - - @note The value returned is only meaningful for - that specific instant in time. An open, - empty ledger can become non empty from - subsequent modifications. Caller is - responsible for synchronizing the meaning of - the return value. - */ + /** + * Returns `true` if there are no transactions. + * + * The behavior of ledger closing can be different + * depending on whether or not transactions exist + * in the open ledger. + * + * @note The value returned is only meaningful for + * that specific instant in time. An open, + * empty ledger can become non empty from + * subsequent modifications. Caller is + * responsible for synchronizing the meaning of + * the return value. + */ bool empty() const; - /** Returns a view to the current open ledger. - - Thread safety: - Can be called concurrently from any thread. - - Effects: - The caller is given ownership of a - non-modifiable snapshot of the open ledger - at the time of the call. - */ + /** + * Returns a view to the current open ledger. + * + * Thread safety: + * Can be called concurrently from any thread. + * + * Effects: + * The caller is given ownership of a + * non-modifiable snapshot of the open ledger + * at the time of the call. + */ std::shared_ptr current() const; - /** Modify the open ledger - - Thread safety: - Can be called concurrently from any thread. - - If `f` returns `true`, the changes made in the - OpenView will be published to the open ledger. - - @return `true` if the open view was changed - */ + /** + * Modify the open ledger + * + * Thread safety: + * Can be called concurrently from any thread. + * + * If `f` returns `true`, the changes made in the + * OpenView will be published to the open ledger. + * + * @return `true` if the open view was changed + */ bool modify(modify_type const& f); - /** Accept a new ledger. - - Thread safety: - Can be called concurrently from any thread. - - Effects: - - A new open view based on the accepted ledger - is created, and the list of retriable - transactions is optionally applied first - depending on the value of `retriesFirst`. - - The transactions in the current open view - are applied to the new open view. - - The list of local transactions are applied - to the new open view. - - The optional modify function f is called - to perform further modifications to the - open view, atomically. Changes made in - the modify function are not visible to - callers until accept() returns. - - Any failed, retriable transactions are left - in `retries` for the caller. - - The current view is atomically set to the - new open view. - - @param rules The rules for the open ledger - @param ledger A new closed ledger - */ + /** + * Accept a new ledger. + * + * Thread safety: + * Can be called concurrently from any thread. + * + * Effects: + * + * A new open view based on the accepted ledger + * is created, and the list of retriable + * transactions is optionally applied first + * depending on the value of `retriesFirst`. + * + * The transactions in the current open view + * are applied to the new open view. + * + * The list of local transactions are applied + * to the new open view. + * + * The optional modify function f is called + * to perform further modifications to the + * open view, atomically. Changes made in + * the modify function are not visible to + * callers until accept() returns. + * + * Any failed, retriable transactions are left + * in `retries` for the caller. + * + * The current view is atomically set to the + * new open view. + * + * @param rules The rules for the open ledger + * @param ledger A new closed ledger + */ void accept( Application& app, @@ -162,11 +170,12 @@ public: modify_type const& f = {}); private: - /** Algorithm for applying transactions. - - This has the retry logic and ordering semantics - used for consensus and building the open ledger. - */ + /** + * Algorithm for applying transactions. + * + * This has the retry logic and ordering semantics + * used for consensus and building the open ledger. + */ template static void apply( diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.h b/src/xrpld/app/ledger/OrderBookDBImpl.h index d57d051cce..5f436a6946 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.h +++ b/src/xrpld/app/ledger/OrderBookDBImpl.h @@ -20,19 +20,22 @@ namespace xrpl { -/** Configuration for OrderBookDB */ +/** + * Configuration for OrderBookDB + */ struct OrderBookDBConfig { int pathSearchMax; bool standalone; }; -/** Create an OrderBookDB instance. - - @param registry Service registry for accessing other services - @param config Configuration parameters - @return A new OrderBookDB instance -*/ +/** + * Create an OrderBookDB instance. + * + * @param registry Service registry for accessing other services + * @param config Configuration parameters + * @return A new OrderBookDB instance + */ std::unique_ptr makeOrderBookDb(ServiceRegistry& registry, OrderBookDBConfig const& config); diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index d11e0610ba..e9c01c7133 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -81,15 +81,16 @@ buildLedgerImpl( return built; } -/** Apply a set of consensus transactions to a ledger. - - @param app Handle to application - @param txns the set of transactions to apply, - @param failed set of transactions that failed to apply - @param view ledger to apply to - @param j Journal for logging - @return number of transactions applied; transactions to retry left in txns -*/ +/** + * Apply a set of consensus transactions to a ledger. + * + * @param app Handle to application + * @param txns the set of transactions to apply, + * @param failed set of transactions that failed to apply + * @param view ledger to apply to + * @param j Journal for logging + * @return number of transactions applied; transactions to retry left in txns + */ std::size_t applyTransactions( diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index 4d34f60374..627a5d574f 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -336,7 +336,8 @@ InboundLedger::tryDB(NodeStore::Database& srcDB) } } -/** Called with a lock by the PeerSet when the timer expires +/** + * Called with a lock by the PeerSet when the timer expires */ void InboundLedger::onTimer(bool wasProgress, ScopedLockType&) @@ -385,7 +386,9 @@ InboundLedger::onTimer(bool wasProgress, ScopedLockType&) } } -/** Add more peers to the set, if possible */ +/** + * Add more peers to the set, if possible + */ void InboundLedger::addPeers() { @@ -454,7 +457,8 @@ InboundLedger::done() }); } -/** Request more nodes, perhaps from a specific peer +/** + * Request more nodes, perhaps from a specific peer */ void InboundLedger::trigger(std::shared_ptr const& peer, TriggerReason reason) @@ -769,9 +773,10 @@ InboundLedger::filterNodes( recentNodes_.insert(n.second); } -/** Take ledger header data - Call with a lock -*/ +/** + * Take ledger header data + * Call with a lock + */ // data must not have hash prefix bool InboundLedger::takeHeader(std::string const& data) @@ -815,9 +820,10 @@ InboundLedger::takeHeader(std::string const& data) return true; } -/** Process node data received from a peer - Call with a lock -*/ +/** + * Process node data received from a peer + * Call with a lock + */ void InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& san) { @@ -911,9 +917,10 @@ InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& } } -/** Process AS root node received from a peer - Call with a lock -*/ +/** + * Process AS root node received from a peer + * Call with a lock + */ bool InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san) { @@ -937,9 +944,10 @@ InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san) return san.isGood(); } -/** Process AS root node received from a peer - Call with a lock -*/ +/** + * Process AS root node received from a peer + * Call with a lock + */ bool InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san) { @@ -994,9 +1002,10 @@ InboundLedger::getNeededHashes() return ret; } -/** Stash a TMLedgerData received from a peer for later processing - Returns 'true' if we need to dispatch -*/ +/** + * Stash a TMLedgerData received from a peer for later processing + * Returns 'true' if we need to dispatch + */ bool InboundLedger::gotData( std::weak_ptr peer, @@ -1016,9 +1025,10 @@ InboundLedger::gotData( return true; } -/** Process one TMLedgerData - Returns the number of useful nodes -*/ +/** + * Process one TMLedgerData + * Returns the number of useful nodes + */ // VFALCO NOTE, it is not necessary to pass the entire Peer, // we can get away with just a Resource::Consumer endpoint. // @@ -1193,9 +1203,10 @@ struct PeerDataCounts }; } // namespace detail -/** Process pending TMLedgerData - Query the a random sample of the 'best' peers -*/ +/** + * Process pending TMLedgerData + * Query the a random sample of the 'best' peers + */ void InboundLedger::runData() { diff --git a/src/xrpld/app/ledger/detail/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp index 07daa7560e..dc361694cf 100644 --- a/src/xrpld/app/ledger/detail/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -70,7 +70,9 @@ public: { } - /** @callgraph */ + /** + * @callgraph + */ std::shared_ptr acquire(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason reason) override { @@ -182,7 +184,8 @@ public: // means "We got some data from an inbound ledger" // VFALCO TODO Remove the dependency on the Peer object. - /** We received a TMLedgerData from a peer. + /** + * We received a TMLedgerData from a peer. */ bool gotLedgerData( @@ -236,12 +239,13 @@ public: return recentFailures_.find(h) != recentFailures_.end(); } - /** We got some data for a ledger we are no longer acquiring Since we paid - the price to receive it, we might as well stash it in case we need it. - - Nodes are received in wire format and must be stashed/hashed in prefix - format - */ + /** + * We got some data for a ledger we are no longer acquiring Since we paid + * the price to receive it, we might as well stash it in case we need it. + * + * Nodes are received in wire format and must be stashed/hashed in prefix + * format + */ void gotStaleData(std::shared_ptr packetPtr) override { diff --git a/src/xrpld/app/ledger/detail/InboundTransactions.cpp b/src/xrpld/app/ledger/detail/InboundTransactions.cpp index d744075869..9b50a1584f 100644 --- a/src/xrpld/app/ledger/detail/InboundTransactions.cpp +++ b/src/xrpld/app/ledger/detail/InboundTransactions.cpp @@ -119,7 +119,8 @@ public: return {}; } - /** We received a TMLedgerData from a peer. + /** + * We received a TMLedgerData from a peer. */ void gotData( diff --git a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp index b96f01e577..d3ece3c036 100644 --- a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp +++ b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp @@ -260,13 +260,14 @@ private: return hash ? *hash : beast::kZero; // kludge } - /** Process a single ledger - @param ledgerIndex The index of the ledger to process. - @param ledgerHash The known correct hash of the ledger. - @param doNodes Ensure all ledger nodes are in the node db. - @param doTxns Reprocess (account) transactions to SQL databases. - @return `true` if the ledger was cleaned. - */ + /** + * Process a single ledger + * @param ledgerIndex The index of the ledger to process. + * @param ledgerHash The known correct hash of the ledger. + * @param doNodes Ensure all ledger nodes are in the node db. + * @param doTxns Reprocess (account) transactions to SQL databases. + * @return `true` if the ledger was cleaned. + */ bool doLedger( LedgerIndex const& ledgerIndex, @@ -320,11 +321,12 @@ private: return true; } - /** Returns the hash of the specified ledger. - @param ledgerIndex The index of the desired ledger. - @param referenceLedger [out] An optional known good subsequent ledger. - @return The hash of the ledger. This will be all-bits-zero if not found. - */ + /** + * Returns the hash of the specified ledger. + * @param ledgerIndex The index of the desired ledger. + * @param referenceLedger [out] An optional known good subsequent ledger. + * @return The hash of the ledger. This will be all-bits-zero if not found. + */ LedgerHash getHash(LedgerIndex const& ledgerIndex, std::shared_ptr& referenceLedger) { @@ -373,7 +375,9 @@ private: return ledgerHash; } - /** Run the ledger cleaner. */ + /** + * Run the ledger cleaner. + */ void doLedgerCleaner() { diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index bab0dca827..2bd83b0f18 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -455,11 +455,12 @@ LedgerMaster::storeLedger(std::shared_ptr ledger) return ledgerHistory_.insert(ledger, validated); } -/** Apply held transactions to the open ledger - This is normally called as we close the ledger. - The open ledger remains open to handle new transactions - until a new open ledger is built. -*/ +/** + * Apply held transactions to the open ledger + * This is normally called as we close the ledger. + * The open ledger remains open to handle new transactions + * until a new open ledger is built. + */ void LedgerMaster::applyHeldTransactions() { @@ -710,7 +711,8 @@ LedgerMaster::tryFill(std::shared_ptr ledger) } } -/** Request a fetch pack to get to the specified ledger +/** + * Request a fetch pack to get to the specified ledger */ void LedgerMaster::getFetchPack(LedgerIndex missing, InboundLedger::Reason reason) @@ -1081,7 +1083,9 @@ LedgerMaster::checkAccept(std::shared_ptr const& ledger) } } -/** Report that the consensus process built a particular ledger */ +/** + * Report that the consensus process built a particular ledger + */ void LedgerMaster::consensusBuilt( std::shared_ptr const& ledger, @@ -1511,7 +1515,8 @@ LedgerMaster::newOrderBookDB() return newPFWork("PthFindOBDB", ml); } -/** A thread needs to be dispatched to handle pathfinding work of some kind. +/** + * A thread needs to be dispatched to handle pathfinding work of some kind. */ bool LedgerMaster::newPFWork(char const* name, std::unique_lock&) @@ -1996,30 +2001,31 @@ LedgerMaster::gotFetchPack(bool progress, std::uint32_t seq) } } -/** Populate a fetch pack with data from the map the recipient wants. - - A recipient may or may not have the map that they are asking for. If - they do, we can optimize the transfer by not including parts of the - map that they are already have. - - @param have The map that the recipient already has (if any). - @param cnt The maximum number of nodes to return. - @param into The protocol object into which we add information. - @param seq The sequence number of the ledger the map is a part of. - @param withLeaves True if leaf nodes should be included. - - @note: The withLeaves parameter is configurable even though the - code, so far, only ever sets the parameter to true. - - The rationale is that for transaction trees, it may make - sense to not include the leaves if the fetch pack is being - constructed for someone attempting to get a recent ledger - for which they already have the transactions. - - However, for historical ledgers, which is the only use we - have for fetch packs right now, it makes sense to include - the transactions because the caller is unlikely to have - them. +/** + * Populate a fetch pack with data from the map the recipient wants. + * + * A recipient may or may not have the map that they are asking for. If + * they do, we can optimize the transfer by not including parts of the + * map that they are already have. + * + * @param have The map that the recipient already has (if any). + * @param cnt The maximum number of nodes to return. + * @param into The protocol object into which we add information. + * @param seq The sequence number of the ledger the map is a part of. + * @param withLeaves True if leaf nodes should be included. + * + * @note: The withLeaves parameter is configurable even though the + * code, so far, only ever sets the parameter to true. + * + * The rationale is that for transaction trees, it may make + * sense to not include the leaves if the fetch pack is being + * constructed for someone attempting to get a recent ledger + * for which they already have the transactions. + * + * However, for historical ledgers, which is the only use we + * have for fetch packs right now, it makes sense to include + * the transactions because the caller is unlikely to have + * them. */ static void populateFetchPack( diff --git a/src/xrpld/app/ledger/detail/TimeoutCounter.h b/src/xrpld/app/ledger/detail/TimeoutCounter.h index ab4dd28e47..682abf1537 100644 --- a/src/xrpld/app/ledger/detail/TimeoutCounter.h +++ b/src/xrpld/app/ledger/detail/TimeoutCounter.h @@ -18,39 +18,39 @@ namespace xrpl { /** - This class is an "active" object. It maintains its own timer - and dispatches work to a job queue. Implementations derive - from this class and override the abstract hook functions in - the base. - - This class implements an asynchronous loop: - - 1. The entry point is `setTimer`. - - 2. After `timerInterval_`, `queueJob` is called, which schedules a job to - call `invokeOnTimer` (or loops back to setTimer if there are too many - concurrent jobs). - - 3. The job queue calls `invokeOnTimer` which either breaks the loop if - `isDone` or calls `onTimer`. - - 4. `onTimer` is the only "real" virtual method in this class. It is the - callback for when the timeout expires. Generally, its only responsibility - is to set `failed_ = true`. However, if it wants to implement a policy of - retries, then it has a chance to just increment a count of expired - timeouts. - - 5. Once `onTimer` returns, if the object is still not `isDone`, then - `invokeOnTimer` sets another timeout by looping back to setTimer. - - This loop executes concurrently with another asynchronous sequence, - implemented by the subtype, that is trying to make progress and eventually - set `complete_ = true`. While it is making progress but not complete, it - should set `progress_ = true`, which is passed to onTimer so it can decide - whether to postpone failure and reset the timeout. However, if it can - complete all its work in one synchronous step (while it holds the lock), then - it can ignore `progress_`. -*/ + * This class is an "active" object. It maintains its own timer + * and dispatches work to a job queue. Implementations derive + * from this class and override the abstract hook functions in + * the base. + * + * This class implements an asynchronous loop: + * + * 1. The entry point is `setTimer`. + * + * 2. After `timerInterval_`, `queueJob` is called, which schedules a job to + * call `invokeOnTimer` (or loops back to setTimer if there are too many + * concurrent jobs). + * + * 3. The job queue calls `invokeOnTimer` which either breaks the loop if + * `isDone` or calls `onTimer`. + * + * 4. `onTimer` is the only "real" virtual method in this class. It is the + * callback for when the timeout expires. Generally, its only responsibility + * is to set `failed_ = true`. However, if it wants to implement a policy of + * retries, then it has a chance to just increment a count of expired + * timeouts. + * + * 5. Once `onTimer` returns, if the object is still not `isDone`, then + * `invokeOnTimer` sets another timeout by looping back to setTimer. + * + * This loop executes concurrently with another asynchronous sequence, + * implemented by the subtype, that is trying to make progress and eventually + * set `complete_ = true`. While it is making progress but not complete, it + * should set `progress_ = true`, which is passed to onTimer so it can decide + * whether to postpone failure and reset the timeout. However, if it can + * complete all its work in one synchronous step (while it holds the lock), then + * it can ignore `progress_`. + */ class TimeoutCounter { public: @@ -84,19 +84,27 @@ protected: QueueJobParameter&& jobParameter, beast::Journal journal); - /** Schedule a call to queueJob() after timerInterval_. */ + /** + * Schedule a call to queueJob() after timerInterval_. + */ void setTimer(ScopedLockType&); - /** Queue a job to call invokeOnTimer(). */ + /** + * Queue a job to call invokeOnTimer(). + */ void queueJob(ScopedLockType&); - /** Hook called from invokeOnTimer(). */ + /** + * Hook called from invokeOnTimer(). + */ virtual void onTimer(bool progress, ScopedLockType&) = 0; - /** Return a weak pointer to this. */ + /** + * Return a weak pointer to this. + */ virtual std::weak_ptr pmDowncast() = 0; @@ -112,21 +120,28 @@ protected: beast::Journal journal_; mutable std::recursive_mutex mtx_; - /** The hash of the object (in practice, always a ledger) we are trying to - * fetch. */ + /** + * The hash of the object (in practice, always a ledger) we are trying to + * fetch. + */ uint256 const hash_; int timeouts_{0}; bool complete_{false}; bool failed_{false}; - /** Whether forward progress has been made. */ + /** + * Whether forward progress has been made. + */ bool progress_{false}; - /** The minimum time to wait between calls to execute(). */ + /** + * The minimum time to wait between calls to execute(). + */ std::chrono::milliseconds timerInterval_; QueueJobParameter queueJobParameter_; private: - /** Calls onTimer() if in the right state. + /** + * Calls onTimer() if in the right state. * Only called by queueJob(). */ void diff --git a/src/xrpld/app/main/Application.h b/src/xrpld/app/main/Application.h index 33876b97b9..225275afe4 100644 --- a/src/xrpld/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -129,7 +129,9 @@ public: // --- // - /** Returns a 64-bit instance identifier, generated at startup */ + /** + * Returns a 64-bit instance identifier, generated at startup + */ [[nodiscard]] virtual std::uint64_t instanceID() const = 0; @@ -152,12 +154,16 @@ public: [[nodiscard]] virtual int fdRequired() const = 0; - /** Ensure that a newly-started validator does not sign proposals older - * than the last ledger it persisted. */ + /** + * Ensure that a newly-started validator does not sign proposals older + * than the last ledger it persisted. + */ virtual LedgerIndex getMaxDisallowedLedger() = 0; - /** Returns the number of io_context (I/O worker) threads used by the application. */ + /** + * Returns the number of io_context (I/O worker) threads used by the application. + */ [[nodiscard]] virtual size_t getNumberOfThreads() const = 0; }; diff --git a/src/xrpld/app/main/CollectorManager.h b/src/xrpld/app/main/CollectorManager.h index e695ddc956..fac72b4de1 100644 --- a/src/xrpld/app/main/CollectorManager.h +++ b/src/xrpld/app/main/CollectorManager.h @@ -10,7 +10,9 @@ namespace xrpl { -/** Provides the beast::insight::Collector service. */ +/** + * Provides the beast::insight::Collector service. + */ class CollectorManager { public: diff --git a/src/xrpld/app/main/LoadManager.h b/src/xrpld/app/main/LoadManager.h index 794048567a..5d3f07e996 100644 --- a/src/xrpld/app/main/LoadManager.h +++ b/src/xrpld/app/main/LoadManager.h @@ -12,17 +12,18 @@ namespace xrpl { class Application; -/** Manages load sources. - - This object creates an associated thread to maintain a clock. - - When the server is overloaded by a particular peer it issues a warning - first. This allows friendly peers to reduce their consumption of resources, - or disconnect from the server. - - The warning system is used instead of merely dropping, because hostile - peers can just reconnect anyway. -*/ +/** + * Manages load sources. + * + * This object creates an associated thread to maintain a clock. + * + * When the server is overloaded by a particular peer it issues a warning + * first. This allows friendly peers to reduce their consumption of resources, + * or disconnect from the server. + * + * The warning system is used instead of merely dropping, because hostile + * peers can just reconnect anyway. + */ class LoadManager { LoadManager(Application& app, beast::Journal journal); @@ -33,20 +34,22 @@ public: LoadManager& operator=(LoadManager const&) = delete; - /** Destroy the manager. - - The destructor returns only after the thread has stopped. - */ + /** + * Destroy the manager. + * + * The destructor returns only after the thread has stopped. + */ ~LoadManager(); - /** Turn on stall detection. - - The stall detector begins in a disabled state. After this function - is called, it will report stalls using a separate thread whenever - the reset function is not called at least once per 10 seconds. - - @see resetStallDetector - */ + /** + * Turn on stall detection. + * + * The stall detector begins in a disabled state. After this function + * is called, it will report stalls using a separate thread whenever + * the reset function is not called at least once per 10 seconds. + * + * @see resetStallDetector + */ // VFALCO NOTE it seems that the stall detector has an "armed" state // to prevent it from going off during program startup if // there's a lengthy initialization operation taking place? @@ -54,11 +57,12 @@ public: void activateStallDetector(); - /** Reset the stall detection timer. - - A dedicated thread monitors the stall timer, and if too much - time passes it will produce log warnings. - */ + /** + * Reset the stall detection timer. + * + * A dedicated thread monitors the stall timer, and if too much + * time passes it will produce log warnings. + */ void heartbeat(); diff --git a/src/xrpld/app/main/NodeIdentity.h b/src/xrpld/app/main/NodeIdentity.h index 789d061021..117acffdb1 100644 --- a/src/xrpld/app/main/NodeIdentity.h +++ b/src/xrpld/app/main/NodeIdentity.h @@ -11,10 +11,11 @@ namespace xrpl { -/** The cryptographic credentials identifying this server instance. - - @param app The application object - @param cmdline The command line parameters passed into the application. +/** + * The cryptographic credentials identifying this server instance. + * + * @param app The application object + * @param cmdline The command line parameters passed into the application. */ std::pair getNodeIdentity(Application& app, boost::program_options::variables_map const& cmdline); diff --git a/src/xrpld/app/main/NodeStoreScheduler.h b/src/xrpld/app/main/NodeStoreScheduler.h index 48e606bb45..8bfd1607ae 100644 --- a/src/xrpld/app/main/NodeStoreScheduler.h +++ b/src/xrpld/app/main/NodeStoreScheduler.h @@ -6,7 +6,9 @@ namespace xrpl { -/** A NodeStore::Scheduler which uses the JobQueue. */ +/** + * A NodeStore::Scheduler which uses the JobQueue. + */ class NodeStoreScheduler : public NodeStore::Scheduler { public: diff --git a/src/xrpld/app/misc/DeliverMax.h b/src/xrpld/app/misc/DeliverMax.h index fefa59d46b..73ccc95800 100644 --- a/src/xrpld/app/misc/DeliverMax.h +++ b/src/xrpld/app/misc/DeliverMax.h @@ -9,13 +9,13 @@ class Value; namespace xrpl::RPC { /** - Copy `Amount` field to `DeliverMax` field in transaction output JSON. - This only applies to Payment transaction type, all others are ignored. - - When apiVersion > 1 will also remove `Amount` field, forcing users - to access this value using new `DeliverMax` field only. - @{ + * Copy `Amount` field to `DeliverMax` field in transaction output JSON. + * This only applies to Payment transaction type, all others are ignored. + * + * When apiVersion > 1 will also remove `Amount` field, forcing users + * to access this value using new `DeliverMax` field only. */ +/** @{ */ void insertDeliverMax(json::Value& txJson, TxType txnType, unsigned int apiVersion); diff --git a/src/xrpld/app/misc/FeeVote.h b/src/xrpld/app/misc/FeeVote.h index d6b4b0fc6a..22b2d888f3 100644 --- a/src/xrpld/app/misc/FeeVote.h +++ b/src/xrpld/app/misc/FeeVote.h @@ -12,25 +12,29 @@ namespace xrpl { -/** Manager to process fee votes. */ +/** + * Manager to process fee votes. + */ class FeeVote { public: virtual ~FeeVote() = default; - /** Add local fee preference to validation. - - @param lastClosedLedger - @param baseValidation - */ + /** + * Add local fee preference to validation. + * + * @param lastClosedLedger + * @param baseValidation + */ virtual void doValidation(Fees const& lastFees, Rules const& rules, STValidation& val) = 0; - /** Cast our local vote on the fee. - - @param lastClosedLedger - @param initialPosition - */ + /** + * Cast our local vote on the fee. + * + * @param lastClosedLedger + * @param initialPosition + */ virtual void doVoting( std::shared_ptr const& lastClosedLedger, @@ -39,10 +43,11 @@ public: }; struct FeeSetup; -/** Create an instance of the FeeVote logic. - @param setup The fee schedule to vote for. - @param journal Where to log. -*/ +/** + * Create an instance of the FeeVote logic. + * @param setup The fee schedule to vote for. + * @param journal Where to log. + */ std::unique_ptr makeFeeVote(FeeSetup const& setup, beast::Journal journal); diff --git a/src/xrpld/app/misc/NegativeUNLVote.h b/src/xrpld/app/misc/NegativeUNLVote.h index 2896962a84..a01bf04dcf 100644 --- a/src/xrpld/app/misc/NegativeUNLVote.h +++ b/src/xrpld/app/misc/NegativeUNLVote.h @@ -141,8 +141,8 @@ private: * Pick one candidate from a vector of candidates. * * @param randomPadData the data used for picking a candidate. - * @note Nodes must use the same randomPadData for picking the same - * candidate. The hash of the parent ledger is used. + * @note Nodes must use the same randomPadData for picking the same + * candidate. The hash of the parent ledger is used. * @param candidates the vector of candidates * @return the picked candidate */ diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 4d40247a29..4b0091dff6 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -266,7 +266,9 @@ class NetworkOPsImp final : public NetworkOPs } }; - //! Server fees published on `server` subscription + /** + * Server fees published on `server` subscription + */ struct ServerFeeSummary { ServerFeeSummary() = default; @@ -469,9 +471,10 @@ public: void setStandAlone() override; - /** Called to initially start our timers. - Not called for stand-alone mode. - */ + /** + * Called to initially start our timers. + * Not called for stand-alone mode. + */ void setStateTimer() override; @@ -838,7 +841,8 @@ private: LedgerMaster& ledgerMaster_; - /** Maps each order book to its current set of subscribers. + /** + * Maps each order book to its current set of subscribers. * Outer key: the Book (currency pair + optional domain). * Inner key: InfoSub::seq (unique per connection). * Inner value: weak_ptr so that a dropped connection does not prevent @@ -3815,10 +3819,9 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) return true; } - for (auto& node : meta->getNodes()) - { + return std::ranges::any_of(meta->getNodes(), [&](auto& node) { if (node.getFieldU16(sfLedgerEntryType) != ltACCOUNT_ROOT) - continue; + return false; if (node.isFieldPresent(sfNewFields)) { @@ -3832,9 +3835,8 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) } } } - } - - return false; + return false; + }); }; auto send = [&](json::Value const& jvObj, bool unsubscribe) -> bool { @@ -3876,7 +3878,8 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) .ledgerRange = {.min = minLedger, .max = maxLedger}, .marker = marker, .limit = 0, - .bAdmin = true}; + .bAdmin = true, + .delegate = std::nullopt}; return db.newestAccountTxPage(options); }; diff --git a/src/xrpld/app/misc/SHAMapStore.h b/src/xrpld/app/misc/SHAMapStore.h index 9f12546463..df696c685f 100644 --- a/src/xrpld/app/misc/SHAMapStore.h +++ b/src/xrpld/app/misc/SHAMapStore.h @@ -25,7 +25,9 @@ class SHAMapStore public: virtual ~SHAMapStore() = default; - /** Called by LedgerMaster every time a ledger validates. */ + /** + * Called by LedgerMaster every time a ledger validates. + */ virtual void onLedgerClosed(std::shared_ptr const& ledger) = 0; @@ -44,44 +46,54 @@ public: virtual std::unique_ptr makeNodeStore(int readThreads) = 0; - /** Highest ledger that may be deleted. */ + /** + * Highest ledger that may be deleted. + */ virtual LedgerIndex setCanDelete(LedgerIndex canDelete) = 0; - /** Whether advisory delete is enabled. */ + /** + * Whether advisory delete is enabled. + */ [[nodiscard]] virtual bool advisoryDelete() const = 0; - /** Maximum ledger that has been deleted, or will be deleted if + /** + * Maximum ledger that has been deleted, or will be deleted if * currently in the act of online deletion. */ virtual LedgerIndex getLastRotated() = 0; - /** Highest ledger that may be deleted. */ + /** + * Highest ledger that may be deleted. + */ virtual LedgerIndex getCanDelete() = 0; - /** Returns the number of file descriptors that are needed. */ + /** + * Returns the number of file descriptors that are needed. + */ [[nodiscard]] virtual int fdRequired() const = 0; - /** The minimum ledger to try and maintain in our database. - - This defines the lower bound for attempting to acquire historical - ledgers over the peer to peer network. - - If online_delete is enabled, then each time online_delete executes - and just prior to clearing SQL databases of historical ledgers, - move the value forward to one past the greatest ledger being deleted. - This minimizes fetching of ledgers that are in the process of being - deleted. Without online_delete or before online_delete is - executed, this value is always the minimum value persisted in the - ledger database, if any. - - @return The minimum ledger sequence to keep online based on the - description above. If not set, then an unseated optional. - */ + /** + * The minimum ledger to try and maintain in our database. + * + * This defines the lower bound for attempting to acquire historical + * ledgers over the peer to peer network. + * + * If online_delete is enabled, then each time online_delete executes + * and just prior to clearing SQL databases of historical ledgers, + * move the value forward to one past the greatest ledger being deleted. + * This minimizes fetching of ledgers that are in the process of being + * deleted. Without online_delete or before online_delete is + * executed, this value is always the minimum value persisted in the + * ledger database, if any. + * + * @return The minimum ledger sequence to keep online based on the + * description above. If not set, then an unseated optional. + */ [[nodiscard]] virtual std::optional minimumOnline() const = 0; }; diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 0e0099cdbf..9b5f412fc5 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -16,9 +16,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -254,8 +256,24 @@ bool SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node) { // Copy a single record from node to dbRotating_ - dbRotating_->fetchNodeObject( + auto obj = dbRotating_->fetchNodeObject( node.getHash().asUInt256(), 0, NodeStore::FetchType::Synchronous, true); + if (!obj) + { + XRPL_ASSERT(node.cowid() == 0, "SHAMapStoreImp::copyNode : rescued node must be clean"); + // Reachable from the validated state map in memory, but present in + // neither backend: its only on-disk copy lived in a backend removed by + // an earlier rotation, and it was never rewritten because it is clean + // (cowid == 0, so flushDirty skips it). Persist the in-memory body + // directly into the writable backend so it survives this rotation + // instead of later surfacing as an unresolvable SHAMapMissingNode. + auto const hash = node.getHash().asUInt256(); + Serializer s; + node.serializeWithPrefix(s); + dbRotating_->store(NodeObjectType::AccountNode, std::move(s.modData()), hash, 0); + JLOG(journal_.warn()) << "copyNode: re-stored node missing from both backends, hash=" + << hash << " type=" << static_cast(node.getType()); + } if ((++nodeCount % checkHealthInterval_) == 0u) { if (healthWait() == HealthResult::Stopping) @@ -348,6 +366,23 @@ SHAMapStoreImp::run() JLOG(journal_.debug()) << "copied ledger " << validatedSeq << " nodecount " << nodeCount; + // Close the getKeys()->swap exposure window: from here until + // rotate() completes, an ordinary read served by the archive is + // copied forward into the writable backend, so a node fetched + // from the doomed archive cannot be left RAM-only when the + // archive is deleted. RAII so the early returns below (and any + // exception) also clear the flag. + struct RotationExposureGuard + { + NodeStore::DatabaseRotating& db; + ~RotationExposureGuard() + { + db.setRotationInFlight(false); + } + }; + RotationExposureGuard const rotationExposureGuard{*dbRotating_}; + dbRotating_->setRotationInFlight(true); + JLOG(journal_.debug()) << "freshening caches"; freshenCaches(); if (healthWait() == HealthResult::Stopping) @@ -437,15 +472,15 @@ SHAMapStoreImp::dbPaths() it != boost::filesystem::directory_iterator(); ++it) { - if (state.writableDb.compare(it->path().string()) == 0) + if (state.writableDb == it->path().string()) { writableDbExists = true; } - else if (state.archiveDb.compare(it->path().string()) == 0) + else if (state.archiveDb == it->path().string()) { archiveDbExists = true; } - else if (dbPrefix_.compare(it->path().stem().string()) == 0) + else if (dbPrefix_ == it->path().stem().string()) { pathsToDelete.push_back(it->path()); } diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index 4025236868..a0ca59ecc8 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -101,10 +101,12 @@ private: std::uint32_t deleteBatch_ = 100; std::chrono::milliseconds backOff_{100}; std::chrono::seconds ageThreshold_{60}; - /// If the node is out of sync during an online_delete healthWait() - /// call, sleep the thread for this time, and continue checking until - /// recovery. - /// See also: "recovery_wait_seconds" in xrpld-example.cfg + /** + * If the node is out of sync during an online_delete healthWait() + * call, sleep the thread for this time, and continue checking until + * recovery. + * See also: "recovery_wait_seconds" in xrpld-example.cfg + */ std::chrono::seconds recoveryWaitTime_{5}; // these do not exist upon SHAMapStore creation, but do exist @@ -197,7 +199,8 @@ private: return false; } - /** delete from sqlite table in batches to not lock the db excessively. + /** + * delete from sqlite table in batches to not lock the db excessively. * Pause briefly to extend access time to other users. * Call with mutex object unlocked. */ diff --git a/src/xrpld/app/misc/Transaction.h b/src/xrpld/app/misc/Transaction.h index ab0fa1f4d8..b6b6d1a8d5 100644 --- a/src/xrpld/app/misc/Transaction.h +++ b/src/xrpld/app/misc/Transaction.h @@ -310,38 +310,46 @@ public: { std::variant, ClosedInterval> locator; - // @return true if transaction was found, false otherwise - // - // Call this function first to determine the type of the contained info. - // Calling the wrong getter function will throw an exception. - // See documentation for the getter functions for more details + /** + * @return true if transaction was found, false otherwise + * + * Call this function first to determine the type of the contained info. + * Calling the wrong getter function will throw an exception. + * See documentation for the getter functions for more details + */ [[nodiscard]] bool isFound() const { return std::holds_alternative>(locator); } - // @return key used to find transaction in nodestore - // - // Throws if isFound() returns false + /** + * @return key used to find transaction in nodestore + * + * @throws if isFound() returns false + */ uint256 const& getNodestoreHash() { return std::get>(locator).first; } - // @return sequence of ledger containing the transaction - // - // Throws is isFound() returns false + /** + * @return sequence of ledger containing the transaction + * + * @throws if isFound() returns false + */ uint32_t getLedgerSequence() { return std::get>(locator).second; } - // @return range of ledgers searched - // - // Throws if isFound() returns true + /** + * @return range of ledgers searched + * + * @throws if isFound() returns true + */ ClosedInterval const& getLedgerRangeSearched() { @@ -400,7 +408,9 @@ private: */ bool applying_ = false; - /** different ways for transaction to be accepted */ + /** + * different ways for transaction to be accepted + */ SubmitResult submitResult_; std::optional currentLedgerState_; diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index 135cd592f0..65b4e9778c 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -37,160 +37,203 @@ class Application; class Config; /** - Transaction Queue. Used to manage transactions in conjunction with - fee escalation. - - Once enough transactions are added to the open ledger, the required - fee will jump dramatically. If additional transactions are added, - the fee will grow exponentially from there. - - Transactions that don't have a high enough fee to be applied to - the ledger are added to the queue in order from highest fee level to - lowest. Whenever a new ledger is accepted as validated, transactions - are first applied from the queue to the open ledger in fee level order - until either all transactions are applied or the fee again jumps - too high for the remaining transactions. - - For further information and a high-level overview of how transactions - are processed with the `TxQ`, see FeeEscalation.md -*/ + * Transaction Queue. Used to manage transactions in conjunction with + * fee escalation. + * + * Once enough transactions are added to the open ledger, the required + * fee will jump dramatically. If additional transactions are added, + * the fee will grow exponentially from there. + * + * Transactions that don't have a high enough fee to be applied to + * the ledger are added to the queue in order from highest fee level to + * lowest. Whenever a new ledger is accepted as validated, transactions + * are first applied from the queue to the open ledger in fee level order + * until either all transactions are applied or the fee again jumps + * too high for the remaining transactions. + * + * For further information and a high-level overview of how transactions + * are processed with the `TxQ`, see FeeEscalation.md + */ class TxQ { public: - /// Fee level for single-signed reference transaction. + /** + * Fee level for single-signed reference transaction. + */ static constexpr FeeLevel64 kBaseLevel{256}; /** - Structure used to customize @ref TxQ behavior. - */ + * Structure used to customize @ref TxQ behavior. + */ struct Setup { - /// Default constructor + /** + * Default constructor + */ explicit Setup() = default; - /** Number of ledgers' worth of transactions to allow - in the queue. For example, if the last ledger had - 150 transactions, then up to 3000 transactions can - be queued. - - Can be overridden by @ref queueSizeMin - */ + /** + * Number of ledgers' worth of transactions to allow + * in the queue. For example, if the last ledger had + * 150 transactions, then up to 3000 transactions can + * be queued. + * + * Can be overridden by @ref queueSizeMin + */ std::size_t ledgersInQueue = 20; - /** The smallest limit the queue is allowed. - - Will allow more than `ledgersInQueue` in the queue - if ledgers are small. - */ + /** + * The smallest limit the queue is allowed. + * + * Will allow more than `ledgersInQueue` in the queue + * if ledgers are small. + */ std::size_t queueSizeMin = 2000; - /** Extra percentage required on the fee level of a queued - transaction to replace that transaction with another - with the same SeqProxy. - - If queued transaction for account "Alice" with seq 45 - has a fee level of 512, a replacement transaction for - "Alice" with seq 45 must have a fee level of at least - 512 * (1 + 0.25) = 640 to be considered. - */ + /** + * Extra percentage required on the fee level of a queued + * transaction to replace that transaction with another + * with the same SeqProxy. + * + * If queued transaction for account "Alice" with seq 45 + * has a fee level of 512, a replacement transaction for + * "Alice" with seq 45 must have a fee level of at least + * 512 * (1 + 0.25) = 640 to be considered. + */ std::uint32_t retrySequencePercent = 25; - /// Minimum value of the escalation multiplier, regardless - /// of the prior ledger's median fee level. + /** + * Minimum value of the escalation multiplier, regardless + * of the prior ledger's median fee level. + */ FeeLevel64 minimumEscalationMultiplier = kBaseLevel * 500; - /// Minimum number of transactions to allow into the ledger - /// before escalation, regardless of the prior ledger's size. + /** + * Minimum number of transactions to allow into the ledger + * before escalation, regardless of the prior ledger's size. + */ std::uint32_t minimumTxnInLedger = 32; - /// Like @ref minimumTxnInLedger for standalone mode. - /// Primarily so that tests don't need to worry about queuing. + /** + * Like @ref minimumTxnInLedger for standalone mode. + * Primarily so that tests don't need to worry about queuing. + */ std::uint32_t minimumTxnInLedgerSA = 1000; - /// Number of transactions per ledger that fee escalation "works - /// towards". + /** + * Number of transactions per ledger that fee escalation "works + * towards". + */ std::uint32_t targetTxnInLedger = 256; - /** Optional maximum allowed value of transactions per ledger before - fee escalation kicks in. By default, the maximum is an emergent - property of network, validator, and consensus performance. This - setting can override that behavior to prevent fee escalation from - allowing more than `maximumTxnInLedger` "cheap" transactions into - the open ledger. - - @todo ximinez. This setting seems to go against our goals and - values. Can it be removed? - */ + /** + * Optional maximum allowed value of transactions per ledger before + * fee escalation kicks in. By default, the maximum is an emergent + * property of network, validator, and consensus performance. This + * setting can override that behavior to prevent fee escalation from + * allowing more than `maximumTxnInLedger` "cheap" transactions into + * the open ledger. + * + * @todo ximinez. This setting seems to go against our goals and + * values. Can it be removed? + */ std::optional maximumTxnInLedger; - /** When the ledger has more transactions than "expected", and - performance is humming along nicely, the expected ledger size - is updated to the previous ledger size plus this percentage. - - Calculations are subject to configured limits, and the recent - transactions counts buffer. - - Example: If the "expectation" is for 500 transactions, and a - ledger is validated normally with 501 transactions, then the - expected ledger size will be updated to 601. - */ + /** + * When the ledger has more transactions than "expected", and + * performance is humming along nicely, the expected ledger size + * is updated to the previous ledger size plus this percentage. + * + * Calculations are subject to configured limits, and the recent + * transactions counts buffer. + * + * Example: If the "expectation" is for 500 transactions, and a + * ledger is validated normally with 501 transactions, then the + * expected ledger size will be updated to 601. + */ std::uint32_t normalConsensusIncreasePercent = 20; - /** When consensus takes longer than appropriate, the expected - ledger size is updated to the lesser of the previous ledger - size and the current expected ledger size minus this - percentage. - - Calculations are subject to configured limits. - - Example: If the ledger has 15000 transactions, and it is - validated slowly, then the expected ledger size will be - updated to 7500. If there are only 6 transactions, the - expected ledger size will be updated to 5, assuming the - default minimum. - */ + /** + * When consensus takes longer than appropriate, the expected + * ledger size is updated to the lesser of the previous ledger + * size and the current expected ledger size minus this + * percentage. + * + * Calculations are subject to configured limits. + * + * Example: If the ledger has 15000 transactions, and it is + * validated slowly, then the expected ledger size will be + * updated to 7500. If there are only 6 transactions, the + * expected ledger size will be updated to 5, assuming the + * default minimum. + */ std::uint32_t slowConsensusDecreasePercent = 50; - /// Maximum number of transactions that can be queued by one account. + /** + * Maximum number of transactions that can be queued by one account. + */ std::uint32_t maximumTxnPerAccount = 10; - /** Minimum difference between the current ledger sequence and a - transaction's `LastLedgerSequence` for the transaction to be - queueable. Decreases the chance a transaction will get queued - and broadcast only to expire before it gets a chance to be - processed. - */ + /** + * Minimum difference between the current ledger sequence and a + * transaction's `LastLedgerSequence` for the transaction to be + * queueable. Decreases the chance a transaction will get queued + * and broadcast only to expire before it gets a chance to be + * processed. + */ std::uint32_t minimumLastLedgerBuffer = 2; - /// Use standalone mode behavior. + /** + * Use standalone mode behavior. + */ bool standAlone = false; }; /** - Structure returned by @ref TxQ::getMetrics, expressed in - reference fee level units. - */ + * Structure returned by @ref TxQ::getMetrics, expressed in + * reference fee level units. + */ struct Metrics { - /// Default constructor + /** + * Default constructor + */ explicit Metrics() = default; - /// Number of transactions in the queue + /** + * Number of transactions in the queue + */ std::size_t txCount{}; - /// Max transactions currently allowed in queue + /** + * Max transactions currently allowed in queue + */ std::optional txQMaxSize; - /// Number of transactions currently in the open ledger + /** + * Number of transactions currently in the open ledger + */ std::size_t txInLedger{}; - /// Number of transactions expected per ledger + /** + * Number of transactions expected per ledger + */ std::size_t txPerLedger{}; - /// Reference transaction fee level + /** + * Reference transaction fee level + */ FeeLevel64 referenceFeeLevel{}; - /// Minimum fee level for a transaction to be considered for - /// the open ledger or the queue + /** + * Minimum fee level for a transaction to be considered for + * the open ledger or the queue + */ FeeLevel64 minProcessingFeeLevel{}; - /// Median fee level of the last ledger + /** + * Median fee level of the last ledger + */ FeeLevel64 medFeeLevel{}; - /// Minimum fee level to get into the current open ledger, - /// bypassing the queue + /** + * Minimum fee level to get into the current open ledger, + * bypassing the queue + */ FeeLevel64 openLedgerFeeLevel{}; }; /** - Structure that describes a transaction in the queue - waiting to be applied to the current open ledger. - A collection of these is returned by @ref TxQ::getTxs. - */ + * Structure that describes a transaction in the queue + * waiting to be applied to the current open ledger. + * A collection of these is returned by @ref TxQ::getTxs. + */ struct TxDetails { - /// Full initialization + /** + * Full initialization + */ TxDetails( FeeLevel64 feeLevel, std::optional const& lastValid, @@ -213,60 +256,78 @@ public: { } - /// Fee level of the queued transaction + /** + * Fee level of the queued transaction + */ FeeLevel64 feeLevel; - /// LastValidLedger field of the queued transaction, if any + /** + * LastValidLedger field of the queued transaction, if any + */ std::optional lastValid; - /** Potential @ref TxConsequences of applying the queued transaction - to the open ledger. - */ + /** + * Potential @ref TxConsequences of applying the queued transaction + * to the open ledger. + */ TxConsequences consequences; - /// The account the transaction is queued for + /** + * The account the transaction is queued for + */ AccountID account; - /// SeqProxy of the transaction + /** + * SeqProxy of the transaction + */ SeqProxy seqProxy; - /// The full transaction + /** + * The full transaction + */ std::shared_ptr txn; - /** Number of times the transactor can return a retry / `ter` result - when attempting to apply this transaction to the open ledger - from the queue. If the transactor returns `ter` and no retries are - left, this transaction will be dropped. - */ + /** + * Number of times the transactor can return a retry / `ter` result + * when attempting to apply this transaction to the open ledger + * from the queue. If the transactor returns `ter` and no retries are + * left, this transaction will be dropped. + */ int retriesRemaining; - /** The *intermediate* result returned by @ref preflight before - this transaction was queued, or after it is queued, but before - a failed attempt to `apply` it to the open ledger. This will - usually be `tesSUCCESS`, but there are some edge cases where - it has another value. Those edge cases are interesting enough - that this value is made available here. Specifically, if the - `rules` change between attempts, `preflight` will be run again - in `TxQ::MaybeTx::apply`. - */ + /** + * The *intermediate* result returned by @ref preflight before + * this transaction was queued, or after it is queued, but before + * a failed attempt to `apply` it to the open ledger. This will + * usually be `tesSUCCESS`, but there are some edge cases where + * it has another value. Those edge cases are interesting enough + * that this value is made available here. Specifically, if the + * `rules` change between attempts, `preflight` will be run again + * in `TxQ::MaybeTx::apply`. + */ TER preflightResult; - /** If the transactor attempted to apply the transaction to the open - ledger from the queue and *failed*, then this is the transactor - result from the last attempt. Should never be a `tec`, `tef`, - `tem`, or `tesSUCCESS`, because those results cause the - transaction to be removed from the queue. - */ + /** + * If the transactor attempted to apply the transaction to the open + * ledger from the queue and *failed*, then this is the transactor + * result from the last attempt. Should never be a `tec`, `tef`, + * `tem`, or `tesSUCCESS`, because those results cause the + * transaction to be removed from the queue. + */ std::optional lastResult; }; - /// Constructor + /** + * Constructor + */ TxQ(Setup const& setup, beast::Journal j); - /// Destructor + /** + * Destructor + */ virtual ~TxQ(); /** - Add a new transaction to the open ledger, hold it in the queue, - or reject it. - - @return A pair with the `TER` and a `bool` indicating - whether or not the transaction was applied to - the open ledger. If the transaction is queued, - will return `{ terQUEUED, false }`. - */ + * Add a new transaction to the open ledger, hold it in the queue, + * or reject it. + * + * @return A pair with the `TER` and a `bool` indicating + * whether or not the transaction was applied to + * the open ledger. If the transaction is queued, + * will return `{ terQUEUED, false }`. + */ ApplyResult apply( Application& app, @@ -276,39 +337,42 @@ public: beast::Journal j); /** - Fill the new open ledger with transactions from the queue. - - @note As more transactions are applied to the ledger, the - required fee may increase. The required fee may rise above - the fee level of the queued items before the queue is emptied, - which will end the process, leaving those in the queue for - the next open ledger. - - @return Whether any transactions were added to the `view`. - */ + * Fill the new open ledger with transactions from the queue. + * + * @note As more transactions are applied to the ledger, the + * required fee may increase. The required fee may rise above + * the fee level of the queued items before the queue is emptied, + * which will end the process, leaving those in the queue for + * the next open ledger. + * + * @return Whether any transactions were added to the `view`. + */ bool accept(Application& app, OpenView& view); /** - Update fee metrics and clean up the queue in preparation for - the next ledger. - - @note Fee metrics are updated based on the fee levels of the - txs in the validated ledger and whether consensus is slow. - Maximum queue size is adjusted to be enough to hold - `ledgersInQueue` ledgers or `queueSizeMin` transactions. - Any transactions for which the `LastLedgerSequence` has - passed are removed from the queue, and any account objects - that have no candidates under them are removed. - */ + * Update fee metrics and clean up the queue in preparation for + * the next ledger. + * + * @note Fee metrics are updated based on the fee levels of the + * txs in the validated ledger and whether consensus is slow. + * Maximum queue size is adjusted to be enough to hold + * `ledgersInQueue` ledgers or `queueSizeMin` transactions. + * Any transactions for which the `LastLedgerSequence` has + * passed are removed from the queue, and any account objects + * that have no candidates under them are removed. + */ void processClosedLedger(Application& app, ReadView const& view, bool timeLeap); - /** Return the next sequence that would go in the TxQ for an account. */ + /** + * Return the next sequence that would go in the TxQ for an account. + */ SeqProxy nextQueuableSeq(SLE::const_ref sleAccount) const; - /** Returns fee metrics in reference fee level units. + /** + * Returns fee metrics in reference fee level units. */ Metrics getMetrics(OpenView const& view) const; @@ -327,33 +391,36 @@ public: * @param view current open ledger * @param tx the transaction * @return minimum required fee, first sequence in the ledger - * and first available sequence + * and first available sequence */ FeeAndSeq getTxRequiredFeeAndSeq(OpenView const& view, std::shared_ptr const& tx) const; - /** Returns information about the transactions currently - in the queue for the account. - - @returns Empty `vector` if the account has no transactions - in the queue. - */ + /** + * Returns information about the transactions currently + * in the queue for the account. + * + * @return Empty `vector` if the account has no transactions + * in the queue. + */ std::vector getAccountTxs(AccountID const& account) const; - /** Returns information about all transactions currently - in the queue. - - @returns Empty `vector` if there are no transactions - in the queue. - */ + /** + * Returns information about all transactions currently + * in the queue. + * + * @return Empty `vector` if there are no transactions + * in the queue. + */ std::vector getTxs() const; - /** Summarize current fee metrics for the `fee` RPC command. - - @returns a `Json objectvalue` - */ + /** + * Summarize current fee metrics for the `fee` RPC command. + * + * @return a `Json objectvalue` + */ json::Value doRPC(Application& app) const; @@ -363,35 +430,51 @@ private: nextQueuableSeqImpl(SLE::const_ref sleAccount, std::scoped_lock const&) const; /** - Track and use the fee escalation metrics of the - current open ledger. Does the work of scaling fees - as the open ledger grows. - */ + * Track and use the fee escalation metrics of the + * current open ledger. Does the work of scaling fees + * as the open ledger grows. + */ class FeeMetrics { private: - /// Minimum value of txnsExpected. + /** + * Minimum value of txnsExpected. + */ std::size_t const minimumTxnCount_; - /// Number of transactions per ledger that fee escalation "works - /// towards". + /** + * Number of transactions per ledger that fee escalation "works + * towards". + */ std::size_t const targetTxnCount_; - /// Maximum value of txnsExpected + /** + * Maximum value of txnsExpected + */ std::optional const maximumTxnCount_; - /// Number of transactions expected per ledger. - /// One more than this value will be accepted - /// before escalation kicks in. + /** + * Number of transactions expected per ledger. + * One more than this value will be accepted + * before escalation kicks in. + */ std::size_t txnsExpected_; - /// Recent history of transaction counts that - /// exceed the targetTxnCount_ + /** + * Recent history of transaction counts that + * exceed the targetTxnCount_ + */ boost::circular_buffer recentTxnCounts_; - /// Based on the median fee of the LCL. Used - /// when fee escalation kicks in. + /** + * Based on the median fee of the LCL. Used + * when fee escalation kicks in. + */ FeeLevel64 escalationMultiplier_; - /// Journal + /** + * Journal + */ beast::Journal const j_; public: - /// Constructor + /** + * Constructor + */ FeeMetrics(Setup const& setup, beast::Journal j) : minimumTxnCount_( setup.standAlone ? setup.minimumTxnInLedgerSA : setup.minimumTxnInLedger) @@ -412,20 +495,22 @@ private: } /** - Updates fee metrics based on the transactions in the ReadView - for use in fee escalation calculations. - - @param app Xrpld Application object. - @param view View of the LCL that was just closed or received. - @param timeLeap Indicates that xrpld is under load so fees - should grow faster. - @param setup Customization params. - */ + * Updates fee metrics based on the transactions in the ReadView + * for use in fee escalation calculations. + * + * @param app Xrpld Application object. + * @param view View of the LCL that was just closed or received. + * @param timeLeap Indicates that xrpld is under load so fees + * should grow faster. + * @param setup Customization params. + */ std::size_t update(Application& app, ReadView const& view, bool timeLeap, TxQ::Setup const& setup); - /// Snapshot of the externally relevant FeeMetrics - /// fields at any given time. + /** + * Snapshot of the externally relevant FeeMetrics + * fields at any given time. + */ struct Snapshot { // Number of transactions expected per ledger. @@ -437,54 +522,57 @@ private: FeeLevel64 const escalationMultiplier; }; - /// Get the current @ref Snapshot + /** + * Get the current @ref Snapshot + */ [[nodiscard]] Snapshot getSnapshot() const { return {.txnsExpected = txnsExpected_, .escalationMultiplier = escalationMultiplier_}; } - /** Use the number of transactions in the current open ledger - to compute the fee level a transaction must pay to bypass the - queue. - - @param view Current open ledger. - - @return A fee level value. - */ + /** + * Use the number of transactions in the current open ledger + * to compute the fee level a transaction must pay to bypass the + * queue. + * + * @param view Current open ledger. + * + * @return A fee level value. + */ static FeeLevel64 scaleFeeLevel(Snapshot const& snapshot, OpenView const& view); /** - Computes the total fee level for all transactions in a series. - Assumes that there are already more than @ref txnsExpected_ txns - between the view and `extraCount`. If there aren't, the result - will be sensible (e.g. there won't be any underflows or - overflows), but the level will be higher than actually required. - - @note A "series" is a set of transactions for the same account. - In the context of this function, the series is already in - the queue, and the series starts with the account's current - sequence number. This function is called by - @ref tryClearAccountQueueUpThruTx to figure out if a newly - submitted transaction is paying enough to get all of the queued - transactions plus itself out of the queue and into the open - ledger while accounting for the escalating fee as each one - is processed. The idea is that if a series of transactions - are taking too long to get out of the queue, a user can - "rescue" them without having to resubmit each one with an - individually higher fee. - - @param view Current open / working ledger. (May be a sandbox.) - @param extraCount Number of additional transactions to count as - in the ledger. (If `view` is a sandbox, should be the number of - transactions in the parent ledger.) - @param seriesSize Total number of transactions in the series to be - processed. - - @return A `std::pair` indicating - whether the calculation result overflows. - */ + * Computes the total fee level for all transactions in a series. + * Assumes that there are already more than @ref txnsExpected_ txns + * between the view and `extraCount`. If there aren't, the result + * will be sensible (e.g. there won't be any underflows or + * overflows), but the level will be higher than actually required. + * + * @note A "series" is a set of transactions for the same account. + * In the context of this function, the series is already in + * the queue, and the series starts with the account's current + * sequence number. This function is called by + * @ref tryClearAccountQueueUpThruTx to figure out if a newly + * submitted transaction is paying enough to get all of the queued + * transactions plus itself out of the queue and into the open + * ledger while accounting for the escalating fee as each one + * is processed. The idea is that if a series of transactions + * are taking too long to get out of the queue, a user can + * "rescue" them without having to resubmit each one with an + * individually higher fee. + * + * @param view Current open / working ledger. (May be a sandbox.) + * @param extraCount Number of additional transactions to count as + * in the ledger. (If `view` is a sandbox, should be the number of + * transactions in the parent ledger.) + * @param seriesSize Total number of transactions in the series to be + * processed. + * + * @return A `std::pair` indicating + * whether the calculation result overflows. + */ static std::pair escalatedSeriesFeeLevel( Snapshot const& snapshot, @@ -494,90 +582,112 @@ private: }; /** - Represents a transaction in the queue which may be applied - later to the open ledger. - */ + * Represents a transaction in the queue which may be applied + * later to the open ledger. + */ class MaybeTx { public: - /// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below - /// to put each MaybeTx object into more than one - /// set without copies, pointers, etc. + /** + * Used by the TxQ::FeeHook and TxQ::FeeMultiSet below + * to put each MaybeTx object into more than one + * set without copies, pointers, etc. + */ boost::intrusive::set_member_hook<> byFeeListHook; - /// The complete transaction. + /** + * The complete transaction. + */ std::shared_ptr txn; - /// Computed fee level that the transaction will pay. + /** + * Computed fee level that the transaction will pay. + */ FeeLevel64 const feeLevel; - /// Transaction ID. + /** + * Transaction ID. + */ TxID const txID; - /// Account submitting the transaction. + /** + * Account submitting the transaction. + */ AccountID const account; - /// Expiration ledger for the transaction - /// (`sfLastLedgerSequence` field). + /** + * Expiration ledger for the transaction + * (`sfLastLedgerSequence` field). + */ std::optional const lastValid; - /// Transaction SeqProxy number - /// (`sfSequence` or `sfTicketSequence` field). + /** + * Transaction SeqProxy number + * (`sfSequence` or `sfTicketSequence` field). + */ SeqProxy const seqProxy; /** - A transaction at the front of the queue will be given - several attempts to succeed before being dropped from - the queue. If dropped, one of the account's penalty - flags will be set, and other transactions may have - their `retriesRemaining` forced down as part of the - penalty. - */ + * A transaction at the front of the queue will be given + * several attempts to succeed before being dropped from + * the queue. If dropped, one of the account's penalty + * flags will be set, and other transactions may have + * their `retriesRemaining` forced down as part of the + * penalty. + */ int retriesRemaining{kRetriesAllowed}; - /// Flags provided to `apply`. If the transaction is later - /// attempted with different flags, it will need to be - /// `preflight`ed again. + /** + * Flags provided to `apply`. If the transaction is later + * attempted with different flags, it will need to be + * `preflight`ed again. + */ ApplyFlags const flags; - /** If the transactor attempted to apply the transaction to the open - ledger from the queue and *failed*, then this is the transactor - result from the last attempt. Should never be a `tec`, `tef`, - `tem`, or `tesSUCCESS`, because those results cause the - transaction to be removed from the queue. - */ + /** + * If the transactor attempted to apply the transaction to the open + * ledger from the queue and *failed*, then this is the transactor + * result from the last attempt. Should never be a `tec`, `tef`, + * `tem`, or `tesSUCCESS`, because those results cause the + * transaction to be removed from the queue. + */ std::optional lastResult; - /** Cached result of the `preflight` operation. Because - `preflight` is expensive, minimize the number of times - it needs to be done. - @invariant `pfResult` is never allowed to be empty. The - `std::optional` is leveraged to allow `emplace`d - construction and replacement without a copy - assignment operation. - */ + /** + * Cached result of the `preflight` operation. Because + * `preflight` is expensive, minimize the number of times + * it needs to be done. + * @invariant `pfResult` is never allowed to be empty. The + * `std::optional` is leveraged to allow `emplace`d + * construction and replacement without a copy + * assignment operation. + */ std::optional pfResult; - /** Starting retry count for newly queued transactions. - - In TxQ::accept, the required fee level may be low - enough that this transaction gets a chance to apply - to the ledger, but it may get a retry ter result for - another reason (eg. insufficient balance). When that - happens, the transaction is left in the queue to try - again later, but it shouldn't be allowed to fail - indefinitely. The number of failures allowed is - essentially arbitrary. It should be large enough to - allow temporary failures to clear up, but small enough - that the queue doesn't fill up with stale transactions - which prevent lower fee level transactions from queuing. - */ + /** + * Starting retry count for newly queued transactions. + * + * In TxQ::accept, the required fee level may be low + * enough that this transaction gets a chance to apply + * to the ledger, but it may get a retry ter result for + * another reason (eg. insufficient balance). When that + * happens, the transaction is left in the queue to try + * again later, but it shouldn't be allowed to fail + * indefinitely. The number of failures allowed is + * essentially arbitrary. It should be large enough to + * allow temporary failures to clear up, but small enough + * that the queue doesn't fill up with stale transactions + * which prevent lower fee level transactions from queuing. + */ static constexpr int kRetriesAllowed = 10; - /** The hash of the parent ledger. - - This is used to pseudo-randomize the transaction order when - populating byFee_, by XORing it with the transaction hash (txID). - Using a single static and doing the XOR operation every time was - tested to be as fast or faster than storing the computed "sort key", - and obviously uses less memory. + /** + * The hash of the parent ledger. + * + * This is used to pseudo-randomize the transaction order when + * populating byFee_, by XORing it with the transaction hash (txID). + * Using a single static and doing the XOR operation every time was + * tested to be as fast or faster than storing the computed "sort key", + * and obviously uses less memory. */ static LedgerHash parentHashComp; public: - /// Constructor + /** + * Constructor + */ MaybeTx( std::shared_ptr const&, TxID const& txID, @@ -585,12 +695,16 @@ private: ApplyFlags const flags, PreflightResult const& pfResult); - /// Attempt to apply the queued transaction to the open ledger. + /** + * Attempt to apply the queued transaction to the open ledger. + */ ApplyResult apply(Application& app, OpenView& view, beast::Journal j); - /// Potential @ref TxConsequences of applying this transaction - /// to the open ledger. + /** + * Potential @ref TxConsequences of applying this transaction + * to the open ledger. + */ [[nodiscard]] TxConsequences const& consequences() const { @@ -598,7 +712,9 @@ private: // pfResult is never empty } - /// Return a TxDetails based on contained information. + /** + * Return a TxDetails based on contained information. + */ [[nodiscard]] TxDetails getTxDetails() const { @@ -616,14 +732,19 @@ private: } }; - /// Used for sorting @ref MaybeTx + /** + * Used for sorting @ref MaybeTx + */ class OrderCandidates { public: - /// Default constructor + /** + * Default constructor + */ explicit OrderCandidates() = default; - /** Sort @ref MaybeTx by `feeLevel` descending, then by + /** + * Sort @ref MaybeTx by `feeLevel` descending, then by * pseudo-randomized transaction ID ascending * * The transaction queue is ordered such that transactions @@ -636,7 +757,6 @@ private: * unpredictable. This allows validators to build similar queues * in the same order, and thus have more similar initial * proposals. - * */ bool operator()(MaybeTx const& lhs, MaybeTx const& rhs) const @@ -647,17 +767,22 @@ private: } }; - /** Used to represent an account to the queue, and stores the - transactions queued for that account by SeqProxy. - */ + /** + * Used to represent an account to the queue, and stores the + * transactions queued for that account by SeqProxy. + */ class TxQAccount { public: using TxMap = std::map; - /// The account + /** + * The account + */ AccountID const account; - /// Sequence number will be used as the key. + /** + * Sequence number will be used as the key. + */ TxMap transactions; /* If this account has had any transaction retry more than `retriesAllowed` times so that it was dropped from the @@ -675,38 +800,51 @@ private: bool dropPenalty = false; public: - /// Construct from a transaction + /** + * Construct from a transaction + */ explicit TxQAccount(std::shared_ptr const& txn); - /// Construct from an account + /** + * Construct from an account + */ explicit TxQAccount(AccountID const& account); - /// Return the number of transactions currently queued for this account + /** + * Return the number of transactions currently queued for this account + */ [[nodiscard]] std::size_t getTxnCount() const { return transactions.size(); } - /// Checks if this account has no transactions queued + /** + * Checks if this account has no transactions queued + */ [[nodiscard]] bool empty() const { return getTxnCount() == 0u; } - /// Find the entry in transactions that precedes seqProx, if one does. + /** + * Find the entry in transactions that precedes seqProx, if one does. + */ [[nodiscard]] TxMap::const_iterator getPrevTx(SeqProxy seqProx) const; - /// Add a transaction candidate to this account for queuing + /** + * Add a transaction candidate to this account for queuing + */ MaybeTx& add(MaybeTx&&); - /** Remove the candidate with given SeqProxy value from this - account. - - @return Whether a candidate was removed - */ + /** + * Remove the candidate with given SeqProxy value from this + * account. + * + * @return Whether a candidate was removed + */ bool remove(SeqProxy seqProx); }; @@ -743,56 +881,68 @@ private: using AccountMap = std::map; - /// Setup parameters used to control the behavior of the queue + /** + * Setup parameters used to control the behavior of the queue + */ Setup const setup_; - /// Journal + /** + * Journal + */ beast::Journal const j_; - /** Tracks the current state of the queue. - @note This member must always and only be accessed under - locked mutex_ - */ + /** + * Tracks the current state of the queue. + * @note This member must always and only be accessed under + * locked mutex_ + */ FeeMetrics feeMetrics_; - /** The queue itself: the collection of transactions ordered - by fee level. - @note This member must always and only be accessed under - locked mutex_ - */ + /** + * The queue itself: the collection of transactions ordered + * by fee level. + * @note This member must always and only be accessed under + * locked mutex_ + */ FeeMultiSet byFee_; - /** All of the accounts which currently have any transactions - in the queue. Entries are created and destroyed dynamically - as transactions are added and removed. - @note This member must always and only be accessed under - locked mutex_ - */ + /** + * All of the accounts which currently have any transactions + * in the queue. Entries are created and destroyed dynamically + * as transactions are added and removed. + * @note This member must always and only be accessed under + * locked mutex_ + */ AccountMap byAccount_; - /** Maximum number of transactions allowed in the queue based - on the current metrics. If uninitialized, there is no limit, - but that condition cannot last for long in practice. - @note This member must always and only be accessed under - locked mutex_ - */ + /** + * Maximum number of transactions allowed in the queue based + * on the current metrics. If uninitialized, there is no limit, + * but that condition cannot last for long in practice. + * @note This member must always and only be accessed under + * locked mutex_ + */ std::optional maxSize_; /** - parentHash_ used for logging only - */ + * parentHash_ used for logging only + */ LedgerHash parentHash_{beast::kZero}; - /** Most queue operations are done under the master lock, - but use this mutex for the RPC "fee" command, which isn't. - */ + /** + * Most queue operations are done under the master lock, + * but use this mutex for the RPC "fee" command, which isn't. + */ std::mutex mutable mutex_; private: - /// Is the queue at least `fillPercentage` full? + /** + * Is the queue at least `fillPercentage` full? + */ template bool isFull() const; - /** Checks if the indicated transaction fits the conditions - for being stored in the queue. - */ + /** + * Checks if the indicated transaction fits the conditions + * for being stored in the queue. + */ TER canBeHeld( STTx const&, @@ -803,14 +953,19 @@ private: std::optional const&, std::scoped_lock const& lock); - /// Erase and return the next entry in byFee_ (lower fee level) + /** + * Erase and return the next entry in byFee_ (lower fee level) + */ FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type); - /** Erase and return the next entry for the account (if fee level - is higher), or next entry in byFee_ (lower fee level). - Used to get the next "applicable" MaybeTx for accept(). - */ + /** + * Erase and return the next entry for the account (if fee level + * is higher), or next entry in byFee_ (lower fee level). + * Used to get the next "applicable" MaybeTx for accept(). + */ FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type); - /// Erase a range of items, based on TxQAccount::TxMap iterators + /** + * Erase a range of items, based on TxQAccount::TxMap iterators + */ TxQAccount::TxMap::iterator erase( TxQAccount& txQAccount, @@ -818,10 +973,10 @@ private: TxQAccount::TxMap::const_iterator end); /** - All-or-nothing attempt to try to apply the queued txs for - `accountIter` up to and including `tx`. Transactions following - `tx` are not cleared. - */ + * All-or-nothing attempt to try to apply the queued txs for + * `accountIter` up to and including `tx`. Transactions following + * `tx` are not cleared. + */ ApplyResult tryClearAccountQueueUpThruTx( Application& app, @@ -838,8 +993,8 @@ private: }; /** - Build a @ref TxQ::Setup object from application configuration. -*/ + * Build a @ref TxQ::Setup object from application configuration. + */ TxQ::Setup setupTxQ(Config const&); diff --git a/src/xrpld/app/misc/ValidatorKeys.h b/src/xrpld/app/misc/ValidatorKeys.h index df88ec33a3..100b16a511 100644 --- a/src/xrpld/app/misc/ValidatorKeys.h +++ b/src/xrpld/app/misc/ValidatorKeys.h @@ -13,9 +13,10 @@ namespace xrpl { class Config; -/** Validator keys and manifest as set in configuration file. Values will be - empty if not configured as a validator or not configured with a manifest. -*/ +/** + * Validator keys and manifest as set in configuration file. Values will be + * empty if not configured as a validator or not configured with a manifest. + */ class ValidatorKeys { public: diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index 0db3eee284..3f9039eab8 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -46,31 +46,49 @@ class STValidation; The "better" dispositions have lower values than the "worse" dispositions */ enum class ListDisposition { - /// List is valid + /** + * List is valid + */ Accepted = 0, - /// List is expired, but has the largest non-pending sequence seen so far + /** + * List is expired, but has the largest non-pending sequence seen so far + */ Expired, - /// List will be valid in the future + /** + * List will be valid in the future + */ Pending, - /// Same sequence as current list + /** + * Same sequence as current list + */ SameSequence, - /// Future sequence already seen + /** + * Future sequence already seen + */ KnownSequence, - /// Trusted publisher key, but seq is too old + /** + * Trusted publisher key, but seq is too old + */ Stale, - /// List signed by untrusted publisher key + /** + * List signed by untrusted publisher key + */ Untrusted, - /// List version is not supported + /** + * List version is not supported + */ UnsupportedVersion, - /// Invalid format or signature + /** + * Invalid format or signature + */ Invalid }; @@ -95,7 +113,8 @@ enum class PublisherStatus { std::string to_string(ListDisposition disposition); -/** Changes in trusted nodes after updating validator list +/** + * Changes in trusted nodes after updating validator list */ struct TrustChanges { @@ -105,7 +124,9 @@ struct TrustChanges hash_set removed; }; -/** Used to represent the information stored in the blobs_v2 Json array */ +/** + * Used to represent the information stored in the blobs_v2 Json array + */ struct ValidatorBlobInfo { // base-64 encoded JSON containing the validator list. @@ -118,50 +139,50 @@ struct ValidatorBlobInfo }; /** - Trusted Validators List - ----------------------- - - Xrpld accepts ledger proposals and validations from trusted validator - nodes. A ledger is considered fully-validated once the number of received - trusted validations for a ledger meets or exceeds a quorum value. - - This class manages the set of validation public keys the local xrpld node - trusts. The list of trusted keys is populated using the keys listed in the - configuration file as well as lists signed by trusted publishers. The - trusted publisher public keys are specified in the config. - - New lists are expected to include the following data: - - @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c - "validFrom", @c "validUntil", and @c "validators" field. @c "validFrom" - contains the XRPL timestamp (seconds since January 1st, 2000 (00:00 - UTC)) for when the list becomes valid. @c "validUntil" contains the - XRPL timestamp for when the list expires. @c "validators" contains - an array of objects with a @c "validation_public_key" and optional - @c "manifest" field. @c "validation_public_key" should be the - hex-encoded master public key. @c "manifest" should be the - base64-encoded validator manifest. - - @li @c "manifest": Base64-encoded serialization of a manifest containing the - publisher's master and signing public keys. - - @li @c "signature": Hex-encoded signature of the blob using the publisher's - signing key. - - @li @c "version": 1 - - Individual validator lists are stored separately by publisher. The number of - lists on which a validator's public key appears is also tracked. - - The list of trusted validation public keys is reset at the start of each - consensus round to take into account the latest known lists as well as the - set of validators from whom validations are being received. Listed - validation public keys are shuffled and then sorted by the number of lists - they appear on. (The shuffling makes the order/rank of validators with the - same number of listings non-deterministic.) A quorum value is calculated for - the new trusted validator list. If there is only one list, all listed keys - are trusted. Otherwise, the trusted list size is set to 125% of the quorum. -*/ + * Trusted Validators List + * ----------------------- + * + * Xrpld accepts ledger proposals and validations from trusted validator + * nodes. A ledger is considered fully-validated once the number of received + * trusted validations for a ledger meets or exceeds a quorum value. + * + * This class manages the set of validation public keys the local xrpld node + * trusts. The list of trusted keys is populated using the keys listed in the + * configuration file as well as lists signed by trusted publishers. The + * trusted publisher public keys are specified in the config. + * + * New lists are expected to include the following data: + * + * @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c + * "validFrom", @c "validUntil", and @c "validators" field. @c "validFrom" + * contains the XRPL timestamp (seconds since January 1st, 2000 (00:00 + * UTC)) for when the list becomes valid. @c "validUntil" contains the + * XRPL timestamp for when the list expires. @c "validators" contains + * an array of objects with a @c "validation_public_key" and optional + * @c "manifest" field. @c "validation_public_key" should be the + * hex-encoded master public key. @c "manifest" should be the + * base64-encoded validator manifest. + * + * @li @c "manifest": Base64-encoded serialization of a manifest containing the + * publisher's master and signing public keys. + * + * @li @c "signature": Hex-encoded signature of the blob using the publisher's + * signing key. + * + * @li @c "version": 1 + * + * Individual validator lists are stored separately by publisher. The number of + * lists on which a validator's public key appears is also tracked. + * + * The list of trusted validation public keys is reset at the start of each + * consensus round to take into account the latest known lists as well as the + * set of validators from whom validations are being received. Listed + * validation public keys are shuffled and then sorted by the number of lists + * they appear on. (The shuffling makes the order/rank of validators with the + * same number of listings non-deterministic.) A quorum value is calculated for + * the new trusted validator list. If there is only one list, all listed keys + * are trusted. Otherwise, the trusted list size is set to 125% of the quorum. + */ class ValidatorList { struct PublisherList @@ -276,11 +297,12 @@ public: std::optional minimumQuorum = std::nullopt); ~ValidatorList() = default; - /** Describes the result of processing a Validator List (UNL), - including some of the information from the list which can - be used by the caller to know which list publisher is - involved. - */ + /** + * Describes the result of processing a Validator List (UNL), + * including some of the information from the list which can + * be used by the caller to know which list publisher is + * involved. + */ struct PublisherListStats { explicit PublisherListStats() = default; @@ -314,23 +336,24 @@ public: std::size_t numVLs = 0; }; - /** Load configured trusted keys. - - @param localSigningKey This node's validation public key - - @param configKeys List of trusted keys from config. Each entry - consists of a base58 encoded validation public key, optionally followed - by a comment. - - @param publisherKeys List of trusted publisher public keys. Each - entry contains a base58 encoded account public key. - - @par Thread Safety - - May be called concurrently - - @return `false` if an entry is invalid or unparsable - */ + /** + * Load configured trusted keys. + * + * @param localSigningKey This node's validation public key + * + * @param configKeys List of trusted keys from config. Each entry + * consists of a base58 encoded validation public key, optionally followed + * by a comment. + * + * @param publisherKeys List of trusted publisher public keys. Each + * entry contains a base58 encoded account public key. + * + * @par Thread Safety + * + * May be called concurrently + * + * @return `false` if an entry is invalid or unparsable + */ bool load( std::optional const& localSigningKey, @@ -338,10 +361,11 @@ public: std::vector const& publisherKeys, std::optional listThreshold = {}); - /** Pull the blob/signature/manifest information out of the appropriate Json - body fields depending on the version. - - @return An empty vector indicates malformed Json. + /** + * Pull the blob/signature/manifest information out of the appropriate Json + * body fields depending on the version. + * + * @return An empty vector indicates malformed Json. */ static std::vector parseBlobs(std::uint32_t version, json::Value const& body); @@ -375,35 +399,36 @@ public: std::vector& messages, std::size_t maxSize = kMaximumMessageSize); - /** Apply multiple published lists of public keys, then broadcast it to all - peers that have not seen it or sent it. - - @param manifest base64-encoded publisher key manifest - - @param version Version of published list format - - @param blobs Vector of BlobInfos representing one or more encoded - validator lists and signatures (and optional manifests) - - @param siteUri Uri of the site from which the list was validated - - @param hash Hash of the data parameters - - @param overlay Overlay object which will handle sending the message - - @param hashRouter HashRouter object which will determine which - peers not to send to - - @param networkOPs NetworkOPs object which will be informed if there - is a valid VL - - @return `ListDisposition::Accepted`, plus some of the publisher - information, if list was successfully applied - - @par Thread Safety - - May be called concurrently - */ + /** + * Apply multiple published lists of public keys, then broadcast it to all + * peers that have not seen it or sent it. + * + * @param manifest base64-encoded publisher key manifest + * + * @param version Version of published list format + * + * @param blobs Vector of BlobInfos representing one or more encoded + * validator lists and signatures (and optional manifests) + * + * @param siteUri Uri of the site from which the list was validated + * + * @param hash Hash of the data parameters + * + * @param overlay Overlay object which will handle sending the message + * + * @param hashRouter HashRouter object which will determine which + * peers not to send to + * + * @param networkOPs NetworkOPs object which will be informed if there + * is a valid VL + * + * @return `ListDisposition::Accepted`, plus some of the publisher + * information, if list was successfully applied + * + * @par Thread Safety + * + * May be called concurrently + */ PublisherListStats applyListsAndBroadcast( std::string const& manifest, @@ -415,26 +440,27 @@ public: HashRouter& hashRouter, NetworkOPs& networkOPs); - /** Apply multiple published lists of public keys. - - @param manifest base64-encoded publisher key manifest - - @param version Version of published list format - - @param blobs Vector of BlobInfos representing one or more encoded - validator lists and signatures (and optional manifests) - - @param siteUri Uri of the site from which the list was validated - - @param hash Optional hash of the data parameters - - @return `ListDisposition::Accepted`, plus some of the publisher - information, if list was successfully applied - - @par Thread Safety - - May be called concurrently - */ + /** + * Apply multiple published lists of public keys. + * + * @param manifest base64-encoded publisher key manifest + * + * @param version Version of published list format + * + * @param blobs Vector of BlobInfos representing one or more encoded + * validator lists and signatures (and optional manifests) + * + * @param siteUri Uri of the site from which the list was validated + * + * @param hash Optional hash of the data parameters + * + * @return `ListDisposition::Accepted`, plus some of the publisher + * information, if list was successfully applied + * + * @par Thread Safety + * + * May be called concurrently + */ PublisherListStats applyLists( std::string const& manifest, @@ -443,33 +469,35 @@ public: std::string siteUri, std::optional const& hash = {}); - /* Attempt to read previously stored list files. Expected to only be - called when loading from URL fails. - - @return A list of valid file:// URLs, if any. - - @par Thread Safety - - May be called concurrently - */ + /** + * Attempt to read previously stored list files. Expected to only be + * called when loading from URL fails. + * + * @return A list of valid file:// URLs, if any. + * + * @par Thread Safety + * + * May be called concurrently + */ std::vector loadLists(); - /** Update trusted nodes - - Reset the trusted nodes based on latest manifests, received validations, - and lists. - - @param seenValidators Set of NodeIDs of validators that have signed - recently received validations - - @return TrustedKeyChanges instance with newly trusted or untrusted - node identities. - - @par Thread Safety - - May be called concurrently - */ + /** + * Update trusted nodes + * + * Reset the trusted nodes based on latest manifests, received validations, + * and lists. + * + * @param seenValidators Set of NodeIDs of validators that have signed + * recently received validations + * + * @return TrustedKeyChanges instance with newly trusted or untrusted + * node identities. + * + * @par Thread Safety + * + * May be called concurrently + */ TrustChanges updateTrusted( hash_set const& seenValidators, @@ -478,139 +506,148 @@ public: Overlay& overlay, HashRouter& hashRouter); - /** Get quorum value for current trusted key set - - The quorum is the minimum number of validations needed for a ledger to - be fully validated. It can change when the set of trusted validation - keys is updated (at the start of each consensus round) and primarily - depends on the number of trusted keys. - - @par Thread Safety - - May be called concurrently - - @return quorum value - */ + /** + * Get quorum value for current trusted key set + * + * The quorum is the minimum number of validations needed for a ledger to + * be fully validated. It can change when the set of trusted validation + * keys is updated (at the start of each consensus round) and primarily + * depends on the number of trusted keys. + * + * @par Thread Safety + * + * May be called concurrently + * + * @return quorum value + */ std::size_t quorum() const { return quorum_; } - /** Returns `true` if public key is trusted - - @param identity Validation public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if public key is trusted + * + * @param identity Validation public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool trusted(PublicKey const& identity) const; - /** Returns `true` if public key is included on any lists - - @param identity Validation public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if public key is included on any lists + * + * @param identity Validation public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool listed(PublicKey const& identity) const; - /** Returns master public key if public key is trusted - - @param identity Validation public key - - @return `std::nullopt` if key is not trusted - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns master public key if public key is trusted + * + * @param identity Validation public key + * + * @return `std::nullopt` if key is not trusted + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional getTrustedKey(PublicKey const& identity) const; - /** Returns listed master public if public key is included on any lists - - @param identity Validation public key - - @return `std::nullopt` if key is not listed - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns listed master public if public key is included on any lists + * + * @param identity Validation public key + * + * @return `std::nullopt` if key is not listed + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional getListedKey(PublicKey const& identity) const; - /** Returns `true` if public key is a trusted publisher - - @param identity Publisher public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if public key is a trusted publisher + * + * @param identity Publisher public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool trustedPublisher(PublicKey const& identity) const; - /** This function returns the local validator public key + /** + * This function returns the local validator public key * or a std::nullopt - - @par Thread Safety - - May be called concurrently - */ + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional localPublicKey() const; - /** Invokes the callback once for every listed validation public key. - - @note Undefined behavior results when calling ValidatorList members from - within the callback - - The arguments passed into the lambda are: - - @li The validation public key - - @li A boolean indicating whether this is a trusted key - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every listed validation public key. + * + * @note Undefined behavior results when calling ValidatorList members from + * within the callback + * + * The arguments passed into the lambda are: + * + * @li The validation public key + * + * @li A boolean indicating whether this is a trusted key + * + * @par Thread Safety + * + * May be called concurrently + */ void forEachListed(std::function func) const; - /** Invokes the callback once for every available publisher list's raw - data members - - @note Undefined behavior results when calling ValidatorList members - from within the callback - - The arguments passed into the lambda are: - - @li The raw manifest string - - @li The raw "blob" string containing the values for the validator list - - @li The signature string used to sign the blob - - @li The version number - - @li The `PublicKey` of the blob signer (matches the value from - [validator_list_keys]) - - @li The sequence number of the "blob" - - @li The precomputed hash of the original / raw elements - - @par Thread Safety - - May be called concurrently - */ + /** + * Invokes the callback once for every available publisher list's raw + * data members + * + * @note Undefined behavior results when calling ValidatorList members + * from within the callback + * + * The arguments passed into the lambda are: + * + * @li The raw manifest string + * + * @li The raw "blob" string containing the values for the validator list + * + * @li The signature string used to sign the blob + * + * @li The version number + * + * @li The `PublicKey` of the blob signer (matches the value from + * [validator_list_keys]) + * + * @li The sequence number of the "blob" + * + * @li The precomputed hash of the original / raw elements + * + * @par Thread Safety + * + * May be called concurrently + */ void forEachAvailable( std::function func) const; - /** Returns the current valid list for the given publisher key, - if available, as a Json object. - */ + /** + * Returns the current valid list for the given publisher key, + * if available, as a Json object. + */ std::optional getAvailable(std::string_view pubKey, std::optional forceVersion = {}); - /** Return the number of configured validator list sites. */ + /** + * Return the number of configured validator list sites. + */ std::size_t count() const; - /** Return the time when the validator list will expire - - @note This may be a time in the past if a published list has not - been updated since its validUntil. It will be std::nullopt if any - configured published list has not been fetched. - - @par Thread Safety - May be called concurrently - */ + /** + * Return the time when the validator list will expire + * + * @note This may be a time in the past if a published list has not + * been updated since its validUntil. It will be std::nullopt if any + * configured published list has not been fetched. + * + * @par Thread Safety + * May be called concurrently + */ std::optional expires() const; - /** Return a JSON representation of the state of the validator list - - @par Thread Safety - May be called concurrently - */ + /** + * Return a JSON representation of the state of the validator list + * + * @par Thread Safety + * May be called concurrently + */ json::Value getJson() const; using QuorumKeys = std::pair>; - /** Get the quorum and all of the trusted keys. + /** + * Get the quorum and all of the trusted keys. * * @return quorum and keys. */ @@ -701,68 +744,74 @@ public: negativeUNLFilter(std::vector>&& validations) const; private: - /** Return the number of configured validator list sites. */ + /** + * Return the number of configured validator list sites. + */ std::size_t count(shared_lock const&) const; - /** Returns `true` if public key is trusted - - @param identity Validation public key - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns `true` if public key is trusted + * + * @param identity Validation public key + * + * @par Thread Safety + * + * May be called concurrently + */ bool trusted(shared_lock const&, PublicKey const& identity) const; - /** Returns master public key if public key is trusted - - @param identity Validation public key - - @return `std::nullopt` if key is not trusted - - @par Thread Safety - - May be called concurrently - */ + /** + * Returns master public key if public key is trusted + * + * @param identity Validation public key + * + * @return `std::nullopt` if key is not trusted + * + * @par Thread Safety + * + * May be called concurrently + */ std::optional getTrustedKey(shared_lock const&, PublicKey const& identity) const; - /** Return the time when the validator list will expire - - @note This may be a time in the past if a published list has not - been updated since its expiration. It will be std::nullopt if any - configured published list has not been fetched. - - @par Thread Safety - May be called concurrently - */ + /** + * Return the time when the validator list will expire + * + * @note This may be a time in the past if a published list has not + * been updated since its expiration. It will be std::nullopt if any + * configured published list has not been fetched. + * + * @par Thread Safety + * May be called concurrently + */ std::optional expires(shared_lock const&) const; - /** Apply published list of public keys - - @param manifest base64-encoded publisher key manifest - - @param blob base64-encoded json containing published validator list - - @param signature Signature of the decoded blob - - @param version Version of published list format - - @param siteUri Uri of the site from which the list was validated - - @param hash Optional hash of the data parameters. - Defaults to uninitialized - - @return `ListDisposition::Accepted`, plus some of the publisher - information, if list was successfully applied - - @par Thread Safety - - May be called concurrently - */ + /** + * Apply published list of public keys + * + * @param manifest base64-encoded publisher key manifest + * + * @param blob base64-encoded json containing published validator list + * + * @param signature Signature of the decoded blob + * + * @param version Version of published list format + * + * @param siteUri Uri of the site from which the list was validated + * + * @param hash Optional hash of the data parameters. + * Defaults to uninitialized + * + * @return `ListDisposition::Accepted`, plus some of the publisher + * information, if list was successfully applied + * + * @par Thread Safety + * + * May be called concurrently + */ PublisherListStats applyList( std::string const& globalManifest, @@ -814,23 +863,26 @@ private: HashRouter& hashRouter, beast::Journal j); - /** Get the filename used for caching UNLs + /** + * Get the filename used for caching UNLs */ boost::filesystem::path getCacheFileName(scoped_lock const&, PublicKey const& pubKey) const; - /** Build a Json representation of the collection, suitable for - writing to a cache file, or serving to a /vl/ query - */ + /** + * Build a Json representation of the collection, suitable for + * writing to a cache file, or serving to a /vl/ query + */ static json::Value buildFileData( std::string const& pubKey, PublisherListCollection const& pubCollection, beast::Journal j); - /** Build a Json representation of the collection, suitable for - writing to a cache file, or serving to a /vl/ query - */ + /** + * Build a Json representation of the collection, suitable for + * writing to a cache file, or serving to a /vl/ query + */ static json::Value buildFileData( std::string const& pubKey, @@ -846,19 +898,21 @@ private: hash_append(h, pl.rawManifest, buildBlobInfos(pl), pl.rawVersion); } - /** Write a JSON UNL to a cache file + /** + * Write a JSON UNL to a cache file */ void cacheValidatorFile(scoped_lock const& lock, PublicKey const& pubKey) const; - /** Check response for trusted valid published list - - @return `ListDisposition::Accepted` if list can be applied - - @par Thread Safety - - Calling public member function is expected to lock mutex - */ + /** + * Check response for trusted valid published list + * + * @return `ListDisposition::Accepted` if list can be applied + * + * @par Thread Safety + * + * Calling public member function is expected to lock mutex + */ std::pair> verify( scoped_lock const&, @@ -867,29 +921,31 @@ private: std::string const& blob, std::string const& signature); - /** Stop trusting publisher's list of keys. - - @param publisherKey Publisher public key - - @return `false` if key was not trusted - - @par Thread Safety - - Calling public member function is expected to lock mutex - */ + /** + * Stop trusting publisher's list of keys. + * + * @param publisherKey Publisher public key + * + * @return `false` if key was not trusted + * + * @par Thread Safety + * + * Calling public member function is expected to lock mutex + */ bool removePublisherList(scoped_lock const&, PublicKey const& publisherKey, PublisherStatus reason); - /** Return quorum for trusted validator set - - @param unlSize Number of trusted validator keys - - @param effectiveUnlSize Number of trusted validator keys that are not in - the NegativeUNL - - @param seenSize Number of trusted validators that have signed - recently received validations - */ + /** + * Return quorum for trusted validator set + * + * @param unlSize Number of trusted validator keys + * + * @param effectiveUnlSize Number of trusted validator keys that are not in + * the NegativeUNL + * + * @param seenSize Number of trusted validators that have signed + * recently received validations + */ std::size_t calculateQuorum(std::size_t unlSize, std::size_t effectiveUnlSize, std::size_t seenSize); }; diff --git a/src/xrpld/app/misc/ValidatorSite.h b/src/xrpld/app/misc/ValidatorSite.h index 7302ebbb52..7e7ad098eb 100644 --- a/src/xrpld/app/misc/ValidatorSite.h +++ b/src/xrpld/app/misc/ValidatorSite.h @@ -23,35 +23,35 @@ namespace xrpl { /** - Validator Sites - --------------- - - This class manages the set of configured remote sites used to fetch the - latest published recommended validator lists. - - Lists are fetched at a regular interval. - Fetched lists are expected to be in JSON format and contain the following - fields: - - @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c - "validUntil", and @c "validators" field. @c "validUntil" contains the - XRPL timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when - the list expires. @c "validators" contains an array of objects with a - @c "validation_public_key" and optional @c "manifest" field. - @c "validation_public_key" should be the hex-encoded master public key. - @c "manifest" should be the base64-encoded validator manifest. - - @li @c "manifest": Base64-encoded serialization of a manifest containing the - publisher's master and signing public keys. - - @li @c "signature": Hex-encoded signature of the blob using the publisher's - signing key. - - @li @c "version": 1 - - @li @c "refreshInterval" (optional, integer minutes). - This value is clamped internally to [1,1440] (1 min - 1 day) -*/ + * Validator Sites + * --------------- + * + * This class manages the set of configured remote sites used to fetch the + * latest published recommended validator lists. + * + * Lists are fetched at a regular interval. + * Fetched lists are expected to be in JSON format and contain the following + * fields: + * + * @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c + * "validUntil", and @c "validators" field. @c "validUntil" contains the + * XRPL timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when + * the list expires. @c "validators" contains an array of objects with a + * @c "validation_public_key" and optional @c "manifest" field. + * @c "validation_public_key" should be the hex-encoded master public key. + * @c "manifest" should be the base64-encoded validator manifest. + * + * @li @c "manifest": Base64-encoded serialization of a manifest containing the + * publisher's master and signing public keys. + * + * @li @c "signature": Hex-encoded signature of the blob using the publisher's + * signing key. + * + * @li @c "version": 1 + * + * @li @c "refreshInterval" (optional, integer minutes). + * This value is clamped internally to [1,1440] (1 min - 1 day) + */ class ValidatorSite { friend class Work; @@ -79,17 +79,23 @@ private: explicit Site(std::string uri); - /// the original uri as loaded from config + /** + * the original uri as loaded from config + */ std::shared_ptr loadedResource; - /// the resource to request at - /// intervals. same as loadedResource - /// except in the case of a permanent redir. + /** + * the resource to request at + * intervals. same as loadedResource + * except in the case of a permanent redir. + */ std::shared_ptr startingResource; - /// the active resource being requested. - /// same as startingResource except - /// when we've gotten a temp redirect + /** + * the active resource being requested. + * same as startingResource except + * when we've gotten a temp redirect + */ std::shared_ptr activeResource; unsigned short redirCount{0}; @@ -132,74 +138,89 @@ public: std::chrono::seconds timeout = std::chrono::seconds{20}); ~ValidatorSite(); - /** Load configured site URIs. - - @param siteURIs List of URIs to fetch published validator lists - - @par Thread Safety - - May be called concurrently - - @return `false` if an entry is invalid or unparsable - */ + /** + * Load configured site URIs. + * + * @param siteURIs List of URIs to fetch published validator lists + * + * @par Thread Safety + * + * May be called concurrently + * + * @return `false` if an entry is invalid or unparsable + */ bool load(std::vector const& siteURIs); - /** Start fetching lists from sites - - This does nothing if list fetching has already started - - @par Thread Safety - - May be called concurrently - */ + /** + * Start fetching lists from sites + * + * This does nothing if list fetching has already started + * + * @par Thread Safety + * + * May be called concurrently + */ void start(); - /** Wait for current fetches from sites to complete - - @par Thread Safety - - May be called concurrently - */ + /** + * Wait for current fetches from sites to complete + * + * @par Thread Safety + * + * May be called concurrently + */ void join(); - /** Stop fetching lists from sites - - This blocks until list fetching has stopped - - @par Thread Safety - - May be called concurrently - */ + /** + * Stop fetching lists from sites + * + * This blocks until list fetching has stopped + * + * @par Thread Safety + * + * May be called concurrently + */ void stop(); - /** Return JSON representation of configured validator sites + /** + * Return JSON representation of configured validator sites */ json::Value getJson() const; private: - /// Load configured site URIs. + /** + * Load configured site URIs. + */ bool load(std::vector const& siteURIs, std::scoped_lock const&); - /// Queue next site to be fetched - /// lock over site_mutex_ and state_mutex_ required + /** + * Queue next site to be fetched + * lock over site_mutex_ and state_mutex_ required + */ void setTimer(std::scoped_lock const&, std::scoped_lock const&); - /// request took too long + /** + * request took too long + */ void onRequestTimeout(std::size_t siteIdx, error_code const& ec); - /// Fetch site whose time has come + /** + * Fetch site whose time has come + */ void onTimer(std::size_t siteIdx, error_code const& ec); - /// Store latest list fetched from site + /** + * Store latest list fetched from site + */ void onSiteFetch( boost::system::error_code const& ec, @@ -207,36 +228,46 @@ private: detail::response_type const& res, std::size_t siteIdx); - /// Store latest list fetched from anywhere + /** + * Store latest list fetched from anywhere + */ void onTextFetch(boost::system::error_code const& ec, std::string const& res, std::size_t siteIdx); - /// Initiate request to given resource. - /// lock over sites_mutex_ required + /** + * Initiate request to given resource. + * lock over sites_mutex_ required + */ void makeRequest( std::shared_ptr resource, std::size_t siteIdx, std::scoped_lock const&); - /// Parse json response from validator list site. - /// lock over sites_mutex_ required + /** + * Parse json response from validator list site. + * lock over sites_mutex_ required + */ void parseJsonResponse( std::string const& res, std::size_t siteIdx, std::scoped_lock const&); - /// Interpret a redirect response. - /// lock over sites_mutex_ required + /** + * Interpret a redirect response. + * lock over sites_mutex_ required + */ std::shared_ptr processRedirect( detail::response_type const& res, std::size_t siteIdx, std::scoped_lock const&); - /// If no sites are provided, or a site fails to load, - /// get a list of local cache files from the ValidatorList. + /** + * If no sites are provided, or a site fails to load, + * get a list of local cache files from the ValidatorList. + */ bool missingSite(std::scoped_lock const&); }; diff --git a/src/xrpld/app/misc/detail/AmendmentTable.cpp b/src/xrpld/app/misc/detail/AmendmentTable.cpp index 694268752e..4af556d3e7 100644 --- a/src/xrpld/app/misc/detail/AmendmentTable.cpp +++ b/src/xrpld/app/misc/detail/AmendmentTable.cpp @@ -81,24 +81,25 @@ 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. -*/ +/** + * 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: @@ -107,10 +108,11 @@ private: struct UpvotesAndTimeout { std::vector upVotes; - /** An unseated timeout indicates that either - 1. No validations have ever been received - 2. The validator has not been heard from in long enough that the - timeout passed, and votes expired. + /** + * An unseated timeout indicates that either + * 1. No validations have ever been received + * 2. The validator has not been heard from in long enough that the + * timeout passed, and votes expired. */ std::optional timeout; }; @@ -279,32 +281,42 @@ public: } }; -/** 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. -*/ +/** + * 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. + */ struct AmendmentState { - /** If an amendment is down-voted, a server will not vote to enable it */ + /** + * If an amendment is down-voted, a server will not vote to enable it + */ AmendmentVote vote = AmendmentVote::Down; - /** Indicates that the amendment has been enabled. - This is a one-way switch: once an amendment is enabled - it can never be disabled, but it can be superseded by - a subsequent amendment. - */ + /** + * Indicates that the amendment has been enabled. + * This is a one-way switch: once an amendment is enabled + * it can never be disabled, but it can be superseded by + * a subsequent amendment. + */ bool enabled = false; - /** Indicates an amendment that this server has code support for. */ + /** + * Indicates an amendment that this server has code support for. + */ bool supported = false; - /** The name of this amendment, possibly empty. */ + /** + * The name of this amendment, possibly empty. + */ std::string name; explicit AmendmentState() = default; }; -/** The status of all amendments requested in a given window. */ +/** + * The status of all amendments requested in a given window. + */ class AmendmentSet { private: @@ -376,12 +388,13 @@ public: //------------------------------------------------------------------------------ -/** Track the list of "amendments" - - An "amendment" is an option that can affect transaction processing rules. - Amendments are proposed and then adopted or rejected by the network. An - Amendment is uniquely identified by its AmendmentID, a 256-bit key. -*/ +/** + * Track the list of "amendments" + * + * An "amendment" is an option that can affect transaction processing rules. + * Amendments are proposed and then adopted or rejected by the network. An + * Amendment is uniquely identified by its AmendmentID, a 256-bit key. + */ class AmendmentTableImpl final : public AmendmentTable { private: diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index f6d00974b4..041d2ade1e 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -408,6 +409,9 @@ TxQ::canBeHeld( // Disallow delegated transactions from being queued. if (tx.isFieldPresent(sfDelegate)) return telCAN_NOT_QUEUE; + // Disallow fee-sponsored transactions from being queued. + if (isFeeSponsored(tx)) + return telCAN_NOT_QUEUE; { // To be queued and relayed, the transaction needs to diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index 7b51fbd597..73ec3e0d6f 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -389,7 +389,7 @@ ValidatorSite::parseJsonResponse( json::Value const body = [&res, siteIdx, this]() { json::Reader r; json::Value body; - if (!r.parse(res.data(), body)) + if (!r.parse(res, body)) { JLOG(j_.warn()) << "Unable to parse JSON response from " << sites_[siteIdx].activeResource->uri; diff --git a/src/xrpld/app/misc/setup_HashRouter.h b/src/xrpld/app/misc/setup_HashRouter.h index 665366ba03..86c472da7f 100644 --- a/src/xrpld/app/misc/setup_HashRouter.h +++ b/src/xrpld/app/misc/setup_HashRouter.h @@ -7,7 +7,9 @@ namespace xrpl { // Forward declaration class Config; -/** Create HashRouter setup from configuration */ +/** + * Create HashRouter setup from configuration + */ HashRouter::Setup setupHashRouter(Config const& config); diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index f8dc4a6981..b2f14c71ea 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -990,6 +992,57 @@ getNewestAccountTxsB( return getAccountTxsB(session, app, options, true, j); } +/** + * @brief Determines whether a transaction should be included in account_tx + * results based on a delegation filter. + * @param rawData Serialized transaction blob. + * @param filter The delegate filter specifying the role of the queried account + * (Actor or Authorizer) and an optional counterparty to match against. + * @param contextAccount The account passed to account_tx (the queried account). + * @return True if the transaction passes the filter and should be included, + * false if it should be skipped. + */ +static bool +passesDelegateFilter( + Blob const& rawData, + DelegateFilter const& filter, + AccountID const& contextAccount) +{ + SerialIter sit{makeSlice(rawData)}; + STTx const tx{sit}; + + AccountID const txOwner = tx.getAccountID(sfAccount); + + if (!tx.isFieldPresent(sfDelegate)) + return false; + + AccountID const txSigner = tx.getAccountID(sfDelegate); + + switch (filter.type) + { + case DelegateType::Actor: { + // Keep txns where the queried account (A) is the owner but + // another account (C) was the delegatee that signed. + bool const isDelegated = (txOwner == contextAccount) && (txSigner != contextAccount); + if (!isDelegated) + return false; + return !filter.counterparty || (txSigner == *filter.counterparty); + } + + case DelegateType::Authorizer: { + // Keep txns where the queried account (C) is the signer acting + // on behalf of another account (A, the delegator/owner). + bool const isActingAsDelegate = + (txSigner == contextAccount) && (txOwner != contextAccount); + if (!isActingAsDelegate) + return false; + return !filter.counterparty || (txOwner == *filter.counterparty); + } + } + + return false; // LCOV_EXCL_LINE +} + /** * @brief accountTxPage Searches for the oldest or newest transactions for the * account that matches the given criteria starting from the provided @@ -1020,6 +1073,7 @@ accountTxPage( { int total = 0; + bool const hasDelegateFilter = options.delegate.has_value(); bool lookingForMarker = options.marker.has_value(); std::uint32_t numberOfResults = 0; @@ -1107,6 +1161,11 @@ accountTxPage( { Blob rawData; Blob rawMeta; + // Delegate filtering happens after SQL, so skipped rows need their own + // continuation marker accounting. + std::uint32_t fetchedRows = 0; + std::optional lastEmitted; + std::optional lastScanned; // SOCI requires boost::optional (not std::optional) as parameters. boost::optional ledgerSeq; @@ -1128,18 +1187,31 @@ accountTxPage( while (st.fetch()) { + if (hasDelegateFilter) + { + ++fetchedRows; + lastScanned = { + .ledgerSeq = rangeCheckedCast(ledgerSeq.value_or(0)), + .txnSeq = txnSeq.value_or(0)}; + } + if (lookingForMarker) { if (findLedger == ledgerSeq.value_or(0) && findSeq == txnSeq.value_or(0)) { lookingForMarker = false; + // Delegate markers are continuation cursors for the last + // scanned row, so resume after the marker row. + if (hasDelegateFilter) + continue; } else { continue; } } - else if (numberOfResults == 0) + + if (!hasDelegateFilter && numberOfResults == 0) { newmarker = { .ledgerSeq = rangeCheckedCast(ledgerSeq.value_or(0)), @@ -1165,6 +1237,23 @@ accountTxPage( rawMeta.clear(); } + if (hasDelegateFilter) + { + if (rawData.empty() || + !passesDelegateFilter(rawData, options.delegate.value(), options.account)) + { + rawData.clear(); + rawMeta.clear(); + continue; + } + + if (numberOfResults == 0) + { + newmarker = lastEmitted; + break; + } + } + // Work around a bug that could leave the metadata missing if (rawMeta.empty()) onUnsavedLedger(ledgerSeq.value_or(0)); @@ -1186,7 +1275,18 @@ accountTxPage( --numberOfResults; total++; + if (hasDelegateFilter) + { + lastEmitted = { + .ledgerSeq = rangeCheckedCast(ledgerSeq.value_or(0)), + .txnSeq = txnSeq.value_or(0)}; + } } + + // If this filtered page did not fill the requested number of results, + // still return a marker so the caller can continue scanning later rows. + if (hasDelegateFilter && !newmarker && !lookingForMarker && fetchedRows == queryLimit) + newmarker = lastScanned; } return {newmarker, total}; diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index fa41f25be1..440191939b 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -28,25 +28,26 @@ namespace xrpl { -/** Determines whether the current ledger should close at this time. - - This function should be called when a ledger is open and there is no close - in progress, or when a transaction is received and no close is in progress. - - @param anyTransactions indicates whether any transactions have been received - @param prevProposers proposers in the last closing - @param proposersClosed proposers who have currently closed this ledger - @param proposersValidated proposers who have validated the last closed - ledger - @param prevRoundTime time for the previous ledger to reach consensus - @param timeSincePrevClose time since the previous ledger's (possibly - rounded) close time - @param openTime duration this ledger has been open - @param idleInterval the network's desired idle interval - @param parms Consensus constant parameters - @param j journal for logging - @param clog log object to which to append -*/ +/** + * Determines whether the current ledger should close at this time. + * + * This function should be called when a ledger is open and there is no close + * in progress, or when a transaction is received and no close is in progress. + * + * @param anyTransactions indicates whether any transactions have been received + * @param prevProposers proposers in the last closing + * @param proposersClosed proposers who have currently closed this ledger + * @param proposersValidated proposers who have validated the last closed + * ledger + * @param prevRoundTime time for the previous ledger to reach consensus + * @param timeSincePrevClose time since the previous ledger's (possibly + * rounded) close time + * @param openTime duration this ledger has been open + * @param idleInterval the network's desired idle interval + * @param parms Consensus constant parameters + * @param j journal for logging + * @param clog log object to which to append + */ bool shouldCloseLedger( bool anyTransactions, @@ -61,25 +62,26 @@ shouldCloseLedger( beast::Journal j, std::unique_ptr const& clog = {}); -/** Determine whether the network reached consensus and whether we joined. - - @param prevProposers proposers in the last closing (not including us) - @param currentProposers proposers in this closing so far (not including us) - @param currentAgree proposers who agree with us - @param currentFinished proposers who have validated a ledger after this one - @param previousAgreeTime how long, in milliseconds, it took to agree on the - last ledger - @param currentAgreeTime how long, in milliseconds, we've been trying to - agree - @param stalled the network appears to be stalled, where - neither we nor our peers have changed their vote on any disputes in a - while. This is undesirable, and should be rare, and will cause us to - end consensus without 80% agreement. - @param parms Consensus constant parameters - @param proposing whether we should count ourselves - @param j journal for logging - @param clog log object to which to append -*/ +/** + * Determine whether the network reached consensus and whether we joined. + * + * @param prevProposers proposers in the last closing (not including us) + * @param currentProposers proposers in this closing so far (not including us) + * @param currentAgree proposers who agree with us + * @param currentFinished proposers who have validated a ledger after this one + * @param previousAgreeTime how long, in milliseconds, it took to agree on the + * last ledger + * @param currentAgreeTime how long, in milliseconds, we've been trying to + * agree + * @param stalled the network appears to be stalled, where + * neither we nor our peers have changed their vote on any disputes in a + * while. This is undesirable, and should be rare, and will cause us to + * end consensus without 80% agreement. + * @param parms Consensus constant parameters + * @param proposing whether we should count ourselves + * @param j journal for logging + * @param clog log object to which to append + */ ConsensusState checkConsensus( std::size_t prevProposers, @@ -94,194 +96,195 @@ checkConsensus( beast::Journal j, std::unique_ptr const& clog = {}); -/** Generic implementation of consensus algorithm. - - Achieves consensus on the next ledger. - - Two things need consensus: - - 1. The set of transactions included in the ledger. - 2. The close time for the ledger. - - The basic flow: - - 1. A call to `startRound` places the node in the `Open` phase. In this - phase, the node is waiting for transactions to include in its open - ledger. - 2. Successive calls to `timerEntry` check if the node can close the ledger. - Once the node `Close`s the open ledger, it transitions to the - `Establish` phase. In this phase, the node shares/receives peer - proposals on which transactions should be accepted in the closed ledger. - 3. During a subsequent call to `timerEntry`, the node determines it has - reached consensus with its peers on which transactions to include. It - transitions to the `Accept` phase. In this phase, the node works on - applying the transactions to the prior ledger to generate a new closed - ledger. Once the new ledger is completed, the node shares the validated - ledger with the network, does some book-keeping, then makes a call to - `startRound` to start the cycle again. - - This class uses a generic interface to allow adapting Consensus for specific - applications. The Adaptor template implements a set of helper functions that - plug the consensus algorithm into a specific application. It also identifies - the types that play important roles in Consensus (transactions, ledgers, ...). - The code stubs below outline the interface and type requirements. The traits - types must be copy constructible and assignable. - - @warning The generic implementation is not thread safe and the public methods - are not intended to be run concurrently. When in a concurrent environment, - the application is responsible for ensuring thread-safety. Simply locking - whenever touching the Consensus instance is one option. - - @code - // A single transaction - struct Tx - { - // Unique identifier of transaction - using ID = ...; - - ID id() const; - - }; - - // A set of transactions - struct TxSet - { - // Unique ID of TxSet (not of Tx) - using ID = ...; - // Type of individual transaction comprising the TxSet - using Tx = Tx; - - bool exists(Tx::ID const &) const; - // Return value should have semantics like Tx const * - Tx const * find(Tx::ID const &) const ; - ID const & id() const; - - // Return set of transactions that are not common to this set or other - // boolean indicates which set it was in - std::map compare(TxSet const & other) const; - - // A mutable view of transactions - struct MutableTxSet - { - MutableTxSet(TxSet const &); - bool insert(Tx const &); - bool erase(Tx::ID const &); - }; - - // Construct from a mutable view. - TxSet(MutableTxSet const &); - - // Alternatively, if the TxSet is itself mutable - // just alias MutableTxSet = TxSet - - }; - - // Agreed upon state that consensus transactions will modify - struct Ledger - { - using ID = ...; - using Seq = ...; - - // Unique identifier of ledger - ID const id() const; - Seq seq() const; - auto closeTimeResolution() const; - auto closeAgree() const; - auto closeTime() const; - auto parentCloseTime() const; - json::Value getJson() const; - }; - - // Wraps a peer's ConsensusProposal - struct PeerPosition - { - ConsensusProposal< - std::uint32_t, //NodeID, - typename Ledger::ID, - typename TxSet::ID> const & - proposal() const; - - }; - - - class Adaptor - { - public: - //----------------------------------------------------------------------- - // Define consensus types - using Ledger_t = Ledger; - using NodeID_t = std::uint32_t; - using TxSet_t = TxSet; - using PeerPosition_t = PeerPosition; - - //----------------------------------------------------------------------- - // - // Attempt to acquire a specific ledger. - std::optional acquireLedger(Ledger::ID const & ledgerID); - - // Acquire the transaction set associated with a proposed position. - std::optional acquireTxSet(TxSet::ID const & setID); - - // Whether any transactions are in the open ledger - bool hasOpenTransactions() const; - - // Number of proposers that have validated the given ledger - std::size_t proposersValidated(Ledger::ID const & prevLedger) const; - - // Number of proposers that have validated a ledger descended from the - // given ledger; if prevLedger.id() != prevLedgerID, use prevLedgerID - // for the determination - std::size_t proposersFinished(Ledger const & prevLedger, - Ledger::ID const & prevLedger) const; - - // Return the ID of the last closed (and validated) ledger that the - // application thinks consensus should use as the prior ledger. - Ledger::ID getPrevLedger(Ledger::ID const & prevLedgerID, - Ledger const & prevLedger, - Mode mode); - - // Called whenever consensus operating mode changes - void onModeChange(ConsensusMode before, ConsensusMode after); - - // Called when ledger closes - Result onClose(Ledger const &, Ledger const & prev, Mode mode); - - // Called when ledger is accepted by consensus - void onAccept(Result const & result, - RCLCxLedger const & prevLedger, - NetClock::duration closeResolution, - CloseTimes const & rawCloseTimes, - Mode const & mode); - - // Called when ledger was forcibly accepted by consensus via the simulate - // function. - void onForceAccept(Result const & result, - RCLCxLedger const & prevLedger, - NetClock::duration closeResolution, - CloseTimes const & rawCloseTimes, - Mode const & mode); - - // Propose the position to peers. - void propose(ConsensusProposal<...> const & pos); - - // Share a received peer proposal with other peer's. - void share(PeerPosition_t const & prop); - - // Share a disputed transaction with peers - void share(Txn const & tx); - - // Share given transaction set with peers - void share(TxSet const &s); - - // Consensus timing parameters and constants - ConsensusParms const & - parms() const; - }; - @endcode - - @tparam Adaptor Defines types and provides helper functions needed to adapt - Consensus to the larger application. -*/ +/** + * Generic implementation of consensus algorithm. + * + * Achieves consensus on the next ledger. + * + * Two things need consensus: + * + * 1. The set of transactions included in the ledger. + * 2. The close time for the ledger. + * + * The basic flow: + * + * 1. A call to `startRound` places the node in the `Open` phase. In this + * phase, the node is waiting for transactions to include in its open + * ledger. + * 2. Successive calls to `timerEntry` check if the node can close the ledger. + * Once the node `Close`s the open ledger, it transitions to the + * `Establish` phase. In this phase, the node shares/receives peer + * proposals on which transactions should be accepted in the closed ledger. + * 3. During a subsequent call to `timerEntry`, the node determines it has + * reached consensus with its peers on which transactions to include. It + * transitions to the `Accept` phase. In this phase, the node works on + * applying the transactions to the prior ledger to generate a new closed + * ledger. Once the new ledger is completed, the node shares the validated + * ledger with the network, does some book-keeping, then makes a call to + * `startRound` to start the cycle again. + * + * This class uses a generic interface to allow adapting Consensus for specific + * applications. The Adaptor template implements a set of helper functions that + * plug the consensus algorithm into a specific application. It also identifies + * the types that play important roles in Consensus (transactions, ledgers, ...). + * The code stubs below outline the interface and type requirements. The traits + * types must be copy constructible and assignable. + * + * @warning The generic implementation is not thread safe and the public methods + * are not intended to be run concurrently. When in a concurrent environment, + * the application is responsible for ensuring thread-safety. Simply locking + * whenever touching the Consensus instance is one option. + * + * @code + * // A single transaction + * struct Tx + * { + * // Unique identifier of transaction + * using ID = ...; + * + * ID id() const; + * + * }; + * + * // A set of transactions + * struct TxSet + * { + * // Unique ID of TxSet (not of Tx) + * using ID = ...; + * // Type of individual transaction comprising the TxSet + * using Tx = Tx; + * + * bool exists(Tx::ID const &) const; + * // Return value should have semantics like Tx const * + * Tx const * find(Tx::ID const &) const ; + * ID const & id() const; + * + * // Return set of transactions that are not common to this set or other + * // boolean indicates which set it was in + * std::map compare(TxSet const & other) const; + * + * // A mutable view of transactions + * struct MutableTxSet + * { + * MutableTxSet(TxSet const &); + * bool insert(Tx const &); + * bool erase(Tx::ID const &); + * }; + * + * // Construct from a mutable view. + * TxSet(MutableTxSet const &); + * + * // Alternatively, if the TxSet is itself mutable + * // just alias MutableTxSet = TxSet + * + * }; + * + * // Agreed upon state that consensus transactions will modify + * struct Ledger + * { + * using ID = ...; + * using Seq = ...; + * + * // Unique identifier of ledger + * ID const id() const; + * Seq seq() const; + * auto closeTimeResolution() const; + * auto closeAgree() const; + * auto closeTime() const; + * auto parentCloseTime() const; + * json::Value getJson() const; + * }; + * + * // Wraps a peer's ConsensusProposal + * struct PeerPosition + * { + * ConsensusProposal< + * std::uint32_t, //NodeID, + * typename Ledger::ID, + * typename TxSet::ID> const & + * proposal() const; + * + * }; + * + * + * class Adaptor + * { + * public: + * //----------------------------------------------------------------------- + * // Define consensus types + * using Ledger_t = Ledger; + * using NodeID_t = std::uint32_t; + * using TxSet_t = TxSet; + * using PeerPosition_t = PeerPosition; + * + * //----------------------------------------------------------------------- + * // + * // Attempt to acquire a specific ledger. + * std::optional acquireLedger(Ledger::ID const & ledgerID); + * + * // Acquire the transaction set associated with a proposed position. + * std::optional acquireTxSet(TxSet::ID const & setID); + * + * // Whether any transactions are in the open ledger + * bool hasOpenTransactions() const; + * + * // Number of proposers that have validated the given ledger + * std::size_t proposersValidated(Ledger::ID const & prevLedger) const; + * + * // Number of proposers that have validated a ledger descended from the + * // given ledger; if prevLedger.id() != prevLedgerID, use prevLedgerID + * // for the determination + * std::size_t proposersFinished(Ledger const & prevLedger, + * Ledger::ID const & prevLedger) const; + * + * // Return the ID of the last closed (and validated) ledger that the + * // application thinks consensus should use as the prior ledger. + * Ledger::ID getPrevLedger(Ledger::ID const & prevLedgerID, + * Ledger const & prevLedger, + * Mode mode); + * + * // Called whenever consensus operating mode changes + * void onModeChange(ConsensusMode before, ConsensusMode after); + * + * // Called when ledger closes + * Result onClose(Ledger const &, Ledger const & prev, Mode mode); + * + * // Called when ledger is accepted by consensus + * void onAccept(Result const & result, + * RCLCxLedger const & prevLedger, + * NetClock::duration closeResolution, + * CloseTimes const & rawCloseTimes, + * Mode const & mode); + * + * // Called when ledger was forcibly accepted by consensus via the simulate + * // function. + * void onForceAccept(Result const & result, + * RCLCxLedger const & prevLedger, + * NetClock::duration closeResolution, + * CloseTimes const & rawCloseTimes, + * Mode const & mode); + * + * // Propose the position to peers. + * void propose(ConsensusProposal<...> const & pos); + * + * // Share a received peer proposal with other peer's. + * void share(PeerPosition_t const & prop); + * + * // Share a disputed transaction with peers + * void share(Txn const & tx); + * + * // Share given transaction set with peers + * void share(TxSet const &s); + * + * // Consensus timing parameters and constants + * ConsensusParms const & + * parms() const; + * }; + * @endcode + * + * @tparam Adaptor Defines types and provides helper functions needed to adapt + * Consensus to the larger application. + */ template class Consensus { @@ -319,34 +322,38 @@ class Consensus }; public: - //! Clock type for measuring time within the consensus code + /** + * Clock type for measuring time within the consensus code + */ using clock_type = beast::AbstractClock; Consensus(Consensus&&) noexcept = default; - /** Constructor. - - @param clock The clock used to internally sample consensus progress - @param adaptor The instance of the adaptor class - @param j The journal to log debug output - */ + /** + * Constructor. + * + * @param clock The clock used to internally sample consensus progress + * @param adaptor The instance of the adaptor class + * @param j The journal to log debug output + */ Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal j); - /** Kick-off the next round of consensus. - - Called by the client code to start each round of consensus. - - @param now The network adjusted time - @param prevLedgerID the ID of the last ledger - @param prevLedger The last ledger - @param nowUntrusted ID of nodes that are newly untrusted this round - @param proposing Whether we want to send proposals to peers this - round. - @param clog log object to which to append - - @note @b prevLedgerID is not required to the ID of @b prevLedger since - the ID may be known locally before the contents of the ledger arrive - */ + /** + * Kick-off the next round of consensus. + * + * Called by the client code to start each round of consensus. + * + * @param now The network adjusted time + * @param prevLedgerID the ID of the last ledger + * @param prevLedger The last ledger + * @param nowUntrusted ID of nodes that are newly untrusted this round + * @param proposing Whether we want to send proposals to peers this + * round. + * @param clog log object to which to append + * + * @note @b prevLedgerID is not required to the ID of @b prevLedger since + * the ID may be known locally before the contents of the ledger arrive + */ void startRound( NetClock::time_point const& now, @@ -356,61 +363,66 @@ public: bool proposing, std::unique_ptr const& clog = {}); - /** A peer has proposed a new position, adjust our tracking. - - @param now The network adjusted time - @param newProposal The new proposal from a peer - @return Whether we should do delayed relay of this proposal. - */ + /** + * A peer has proposed a new position, adjust our tracking. + * + * @param now The network adjusted time + * @param newProposal The new proposal from a peer + * @return Whether we should do delayed relay of this proposal. + */ bool peerProposal(NetClock::time_point const& now, PeerPosition_t const& newProposal); - /** Call periodically to drive consensus forward. - - @param now The network adjusted time - @param clog log object to which to append - */ + /** + * Call periodically to drive consensus forward. + * + * @param now The network adjusted time + * @param clog log object to which to append + */ void timerEntry( NetClock::time_point const& now, std::unique_ptr const& clog = {}); - /** Process a transaction set acquired from the network - - @param now The network adjusted time - @param txSet the transaction set - */ + /** + * Process a transaction set acquired from the network + * + * @param now The network adjusted time + * @param txSet the transaction set + */ void gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet); - /** Simulate the consensus process without any network traffic. - - The end result, is that consensus begins and completes as if everyone - had agreed with whatever we propose. - - This function is only called from the rpc "ledger_accept" path with the - server in standalone mode and SHOULD NOT be used during the normal - consensus process. - - Simulate will call onForceAccept since clients are manually driving - consensus to the accept phase. - - @param now The current network adjusted time. - @param consensusDelay Duration to delay between closing and accepting the - ledger. Uses 100ms if unspecified. - */ + /** + * Simulate the consensus process without any network traffic. + * + * The end result, is that consensus begins and completes as if everyone + * had agreed with whatever we propose. + * + * This function is only called from the rpc "ledger_accept" path with the + * server in standalone mode and SHOULD NOT be used during the normal + * consensus process. + * + * Simulate will call onForceAccept since clients are manually driving + * consensus to the accept phase. + * + * @param now The current network adjusted time. + * @param consensusDelay Duration to delay between closing and accepting the + * ledger. Uses 100ms if unspecified. + */ void simulate( NetClock::time_point const& now, std::optional consensusDelay); - /** Get the previous ledger ID. - - The previous ledger is the last ledger seen by the consensus code and - should correspond to the most recent validated ledger seen by this peer. - - @return ID of previous ledger - */ + /** + * Get the previous ledger ID. + * + * The previous ledger is the last ledger seen by the consensus code and + * should correspond to the most recent validated ledger seen by this peer. + * + * @return ID of previous ledger + */ Ledger_t::ID prevLedgerID() const { @@ -423,13 +435,14 @@ public: return phase_; } - /** Get the Json state of the consensus process. - - Called by the consensus_info RPC. - - @param full True if verbose response desired. - @return The Json state. - */ + /** + * Get the Json state of the consensus process. + * + * Called by the consensus_info RPC. + * + * @param full True if verbose response desired. + * @return The Json state. + */ [[nodiscard]] json::Value getJson(bool full) const; @@ -446,46 +459,52 @@ private: void handleWrongLedger(Ledger_t::ID const& lgrId, std::unique_ptr const& clog); - /** Check if our previous ledger matches the network's. - - If the previous ledger differs, we are no longer in sync with - the network and need to bow out/switch modes. - */ + /** + * Check if our previous ledger matches the network's. + * + * If the previous ledger differs, we are no longer in sync with + * the network and need to bow out/switch modes. + */ void checkLedger(std::unique_ptr const& clog); - /** If we radically changed our consensus context for some reason, - we need to replay recent proposals so that they're not lost. - */ + /** + * If we radically changed our consensus context for some reason, + * we need to replay recent proposals so that they're not lost. + */ void playbackProposals(); - /** Handle a replayed or a new peer proposal. + /** + * Handle a replayed or a new peer proposal. */ bool peerProposalInternal(NetClock::time_point const& now, PeerPosition_t const& newProposal); - /** Handle pre-close phase. - - In the pre-close phase, the ledger is open as we wait for new - transactions. After enough time has elapsed, we will close the ledger, - switch to the establish phase and start the consensus process. - */ + /** + * Handle pre-close phase. + * + * In the pre-close phase, the ledger is open as we wait for new + * transactions. After enough time has elapsed, we will close the ledger, + * switch to the establish phase and start the consensus process. + */ void phaseOpen(std::unique_ptr const& clog); - /** Handle establish phase. - - In the establish phase, the ledger has closed and we work with peers - to reach consensus. Update our position only on the timer, and in this - phase. - - If we have consensus, move to the accepted phase. - */ + /** + * Handle establish phase. + * + * In the establish phase, the ledger has closed and we work with peers + * to reach consensus. Update our position only on the timer, and in this + * phase. + * + * If we have consensus, move to the accepted phase. + */ void phaseEstablish(std::unique_ptr const& clog); - /** Evaluate whether pausing increases likelihood of validation. + /** + * Evaluate whether pausing increases likelihood of validation. * * As a validator that has previously synced to the network, if our most * recent locally-validated ledger did not also achieve @@ -1245,7 +1264,8 @@ Consensus::shouldPause(std::unique_ptr const& clog) bool willPause = false; - /** Maximum phase with distinct thresholds to determine how + /** + * Maximum phase with distinct thresholds to determine how * many validators must be on our same ledger sequence number. * The threshold for the 1st (0) phase is >= the minimum number that * can achieve quorum. Threshold for the maximum phase is 100% @@ -1429,18 +1449,19 @@ Consensus::closeLedger(std::unique_ptr const& clog) } } -/** How many of the participants must agree to reach a given threshold? - -Note that the number may not precisely yield the requested percentage. -For example, with with size = 5 and percent = 70, we return 3, but -3 out of 5 works out to 60%. There are no security implications to -this. - -@param participants The number of participants (i.e. validators) -@param percent The percent that we want to reach - -@return the number of participants which must agree -*/ +/** + * How many of the participants must agree to reach a given threshold? + * + * Note that the number may not precisely yield the requested percentage. + * For example, with with size = 5 and percent = 70, we return 3, but + * 3 out of 5 works out to 60%. There are no security implications to + * this. + * + * @param participants The number of participants (i.e. validators) + * @param percent The percent that we want to reach + * + * @return the number of participants which must agree + */ inline int participantsNeeded(int participants, int percent) { diff --git a/src/xrpld/consensus/ConsensusParms.h b/src/xrpld/consensus/ConsensusParms.h index d00a5dc3c7..5fdbaa38bf 100644 --- a/src/xrpld/consensus/ConsensusParms.h +++ b/src/xrpld/consensus/ConsensusParms.h @@ -10,11 +10,12 @@ namespace xrpl { -/** Consensus algorithm parameters - - Parameters which control the consensus algorithm. This are not - meant to be changed arbitrarily. -*/ +/** + * Consensus algorithm parameters + * + * Parameters which control the consensus algorithm. This are not + * meant to be changed arbitrarily. + */ struct ConsensusParms { explicit ConsensusParms() = default; @@ -22,49 +23,63 @@ struct ConsensusParms //------------------------------------------------------------------------- // Validation and proposal durations are relative to NetClock times, so use // second resolution - /** The duration a validation remains current after its ledger's - close time. - - This is a safety to protect against very old validations and the time - it takes to adjust the close time accuracy window. - */ + /** + * The duration a validation remains current after its ledger's + * close time. + * + * This is a safety to protect against very old validations and the time + * it takes to adjust the close time accuracy window. + */ std::chrono::seconds const validationValidWall = std::chrono::minutes{5}; - /** Duration a validation remains current after first observed. - - The duration a validation remains current after the time we - first saw it. This provides faster recovery in very rare cases where the - number of validations produced by the network is lower than normal - */ + /** + * Duration a validation remains current after first observed. + * + * The duration a validation remains current after the time we + * first saw it. This provides faster recovery in very rare cases where the + * number of validations produced by the network is lower than normal + */ std::chrono::seconds const validationValidLocal = std::chrono::minutes{3}; - /** Duration pre-close in which validations are acceptable. - - The number of seconds before a close time that we consider a validation - acceptable. This protects against extreme clock errors - */ + /** + * Duration pre-close in which validations are acceptable. + * + * The number of seconds before a close time that we consider a validation + * acceptable. This protects against extreme clock errors + */ std::chrono::seconds const validationValidEarly = std::chrono::minutes{3}; - //! How long we consider a proposal fresh + /** + * How long we consider a proposal fresh + */ std::chrono::seconds const proposeFRESHNESS = std::chrono::seconds{20}; - //! How often we force generating a new proposal to keep ours fresh + /** + * How often we force generating a new proposal to keep ours fresh + */ std::chrono::seconds const proposeINTERVAL = std::chrono::seconds{12}; //------------------------------------------------------------------------- // Consensus durations are relative to the internal Consensus clock and use // millisecond resolution. - //! The percentage threshold above which we can declare consensus. + /** + * The percentage threshold above which we can declare consensus. + */ std::size_t const minConsensusPct = 80; - //! The duration a ledger may remain idle before closing + /** + * The duration a ledger may remain idle before closing + */ std::chrono::milliseconds const ledgerIdleInterval = std::chrono::seconds{15}; - //! The number of seconds we wait minimum to ensure participation + /** + * The number of seconds we wait minimum to ensure participation + */ std::chrono::milliseconds const ledgerMinConsensus = std::chrono::milliseconds{1950}; - /** The maximum amount of time to spend pausing for laggards. + /** + * The maximum amount of time to spend pausing for laggards. * * This should be sufficiently less than validationFRESHNESS so that * validators don't appear to be offline that are merely waiting for @@ -72,13 +87,19 @@ struct ConsensusParms */ std::chrono::milliseconds const ledgerMaxConsensus = std::chrono::seconds{15}; - //! Minimum number of seconds to wait to ensure others have computed the LCL + /** + * Minimum number of seconds to wait to ensure others have computed the LCL + */ std::chrono::milliseconds const ledgerMinClose = std::chrono::seconds{2}; - //! How often we check state or change positions + /** + * How often we check state or change positions + */ std::chrono::milliseconds const ledgerGRANULARITY = std::chrono::seconds{1}; - //! How long to wait before completely abandoning consensus + /** + * How long to wait before completely abandoning consensus + */ std::size_t const ledgerAbandonConsensusFactor = 10; /** @@ -89,16 +110,17 @@ struct ConsensusParms */ std::chrono::milliseconds const ledgerAbandonConsensus = std::chrono::seconds{120}; - /** The minimum amount of time to consider the previous round - to have taken. - - The minimum amount of time to consider the previous round - to have taken. This ensures that there is an opportunity - for a round at each avalanche threshold even if the - previous consensus was very fast. This should be at least - twice the interval between proposals (0.7s) divided by - the interval between mid and late consensus ([85-50]/100). - */ + /** + * The minimum amount of time to consider the previous round + * to have taken. + * + * The minimum amount of time to consider the previous round + * to have taken. This ensures that there is an opportunity + * for a round at each avalanche threshold even if the + * previous consensus was very fast. This should be at least + * twice the interval between proposals (0.7s) divided by + * the interval between mid and late consensus ([85-50]/100). + */ std::chrono::milliseconds const avMinConsensusTime = std::chrono::seconds{5}; //------------------------------------------------------------------------------ @@ -113,11 +135,13 @@ struct ConsensusParms std::size_t const consensusPct; AvalancheState const next; }; - //! Map the consensus requirement avalanche state to the amount of time that - //! must pass before moving to that state, the agreement percentage required - //! at that state, and the next state. "stuck" loops back on itself because - //! once we're stuck, we're stuck. - //! This structure allows for "looping" of states if needed. + /** + * Map the consensus requirement avalanche state to the amount of time that + * must pass before moving to that state, the agreement percentage required + * at that state, and the next state. "stuck" loops back on itself because + * once we're stuck, we're stuck. + * This structure allows for "looping" of states if needed. + */ std::map const avalancheCutoffs{ // {state, {time, percent, nextState}}, // Initial state: 50% of nodes must vote yes @@ -135,16 +159,22 @@ struct ConsensusParms {.consensusTime = 200, .consensusPct = 95, .next = AvalancheState::Stuck}}, }; - //! Percentage of nodes required to reach agreement on ledger close time + /** + * Percentage of nodes required to reach agreement on ledger close time + */ std::size_t const avCtConsensusPct = 75; - //! Number of rounds before certain actions can happen. + /** + * Number of rounds before certain actions can happen. + */ // (Moving to the next avalanche level, considering that votes are stalled // without consensus.) std::size_t const avMinRounds = 2; - //! Number of rounds before a stuck vote is considered unlikely to change - //! because voting stalled + /** + * Number of rounds before a stuck vote is considered unlikely to change + * because voting stalled + */ std::size_t const avStalledRounds = 4; }; diff --git a/src/xrpld/consensus/ConsensusProposal.h b/src/xrpld/consensus/ConsensusProposal.h index c27d5c2819..4586479286 100644 --- a/src/xrpld/consensus/ConsensusProposal.h +++ b/src/xrpld/consensus/ConsensusProposal.h @@ -13,28 +13,29 @@ #include namespace xrpl { -/** Represents a proposed position taken during a round of consensus. - - During consensus, peers seek agreement on a set of transactions to - apply to the prior ledger to generate the next ledger. Each peer takes a - position on whether to include or exclude potential transactions. - The position on the set of transactions is proposed to its peers as an - instance of the ConsensusProposal class. - - An instance of ConsensusProposal can be either our own proposal or one of - our peer's. - - As consensus proceeds, peers may change their position on the transaction, - or choose to abstain. Each successive proposal includes a strictly - monotonically increasing number (or, if a peer is choosing to abstain, - the special value `kSeqLeave`). - - Refer to @ref Consensus for requirements of the template arguments. - - @tparam NodeId Type used to uniquely identify nodes/peers - @tparam LedgerId Type used to uniquely identify ledgers - @tparam Position Type used to represent the position taken on transactions - under consideration during this round of consensus +/** + * Represents a proposed position taken during a round of consensus. + * + * During consensus, peers seek agreement on a set of transactions to + * apply to the prior ledger to generate the next ledger. Each peer takes a + * position on whether to include or exclude potential transactions. + * The position on the set of transactions is proposed to its peers as an + * instance of the ConsensusProposal class. + * + * An instance of ConsensusProposal can be either our own proposal or one of + * our peer's. + * + * As consensus proceeds, peers may change their position on the transaction, + * or choose to abstain. Each successive proposal includes a strictly + * monotonically increasing number (or, if a peer is choosing to abstain, + * the special value `kSeqLeave`). + * + * Refer to @ref Consensus for requirements of the template arguments. + * + * @tparam NodeId Type used to uniquely identify nodes/peers + * @tparam LedgerId Type used to uniquely identify ledgers + * @tparam Position Type used to represent the position taken on transactions + * under consideration during this round of consensus */ template class ConsensusProposal @@ -48,15 +49,16 @@ public: //< Sequence number when a peer wants to bow out and leave consensus static std::uint32_t const kSeqLeave = 0xffffffff; - /** Constructor - - @param prevLedger The previous ledger this proposal is building on. - @param seq The sequence number of this proposal. - @param position The position taken on transactions in this round. - @param closeTime Position of when this ledger closed. - @param now Time when the proposal was taken. - @param nodeID ID of node/peer taking this position. - */ + /** + * Constructor + * + * @param prevLedger The previous ledger this proposal is building on. + * @param seq The sequence number of this proposal. + * @param position The position taken on transactions in this round. + * @param closeTime Position of when this ledger closed. + * @param now Time when the proposal was taken. + * @param nodeID ID of node/peer taking this position. + */ ConsensusProposal( LedgerId const& prevLedger, std::uint32_t seq, @@ -73,83 +75,100 @@ public: { } - //! Identifying which peer took this position. + /** + * Identifying which peer took this position. + */ NodeId const& nodeID() const { return nodeID_; } - //! Get the proposed position. + /** + * Get the proposed position. + */ Position const& position() const { return position_; } - //! Get the prior accepted ledger this position is based on. + /** + * Get the prior accepted ledger this position is based on. + */ LedgerId const& prevLedger() const { return previousLedger_; } - /** Get the sequence number of this proposal - - Starting with an initial sequence number of `kSeqJoin`, successive - proposals from a peer will increase the sequence number. - - @return the sequence number - */ + /** + * Get the sequence number of this proposal + * + * Starting with an initial sequence number of `kSeqJoin`, successive + * proposals from a peer will increase the sequence number. + * + * @return the sequence number + */ std::uint32_t proposeSeq() const { return proposeSeq_; } - //! The current position on the consensus close time. + /** + * The current position on the consensus close time. + */ NetClock::time_point const& closeTime() const { return closeTime_; } - //! Get when this position was taken. + /** + * Get when this position was taken. + */ NetClock::time_point const& seenTime() const { return time_; } - /** Whether this is the first position taken during the current - consensus round. - */ + /** + * Whether this is the first position taken during the current + * consensus round. + */ bool isInitial() const { return proposeSeq_ == kSeqJoin; } - //! Get whether this node left the consensus process + /** + * Get whether this node left the consensus process + */ bool isBowOut() const { return proposeSeq_ == kSeqLeave; } - //! Get whether this position is stale relative to the provided cutoff + /** + * Get whether this position is stale relative to the provided cutoff + */ bool isStale(NetClock::time_point cutoff) const { return time_ <= cutoff; } - /** Update the position during the consensus process. This will increment - the proposal's sequence number if it has not already bowed out. - - @param newPosition The new position taken. - @param newCloseTime The new close time. - @param now the time The new position was taken + /** + * Update the position during the consensus process. This will increment + * the proposal's sequence number if it has not already bowed out. + * + * @param newPosition The new position taken. + * @param newCloseTime The new close time. + * @param now the time The new position was taken */ void changePosition( @@ -165,11 +184,12 @@ public: ++proposeSeq_; } - /** Leave consensus - - Update position to indicate the node left consensus. - - @param now Time when this node left consensus. + /** + * Leave consensus + * + * Update position to indicate the node left consensus. + * + * @param now Time when this node left consensus. */ void bowOut(NetClock::time_point now) @@ -190,7 +210,9 @@ public: return ss.str(); } - //! Get JSON representation for debugging + /** + * Get JSON representation for debugging + */ json::Value getJson() const { @@ -210,7 +232,9 @@ public: return ret; } - //! The digest for this proposal, used for signing purposes. + /** + * The digest for this proposal, used for signing purposes. + */ uint256 const& signingHash() const { @@ -228,25 +252,37 @@ public: } private: - //! Unique identifier of prior ledger this proposal is based on + /** + * Unique identifier of prior ledger this proposal is based on + */ LedgerId previousLedger_; - //! Unique identifier of the position this proposal is taking + /** + * Unique identifier of the position this proposal is taking + */ Position position_; - //! The ledger close time this position is taking + /** + * The ledger close time this position is taking + */ NetClock::time_point closeTime_; // !The time this position was last updated NetClock::time_point time_; - //! The sequence number of these positions taken by this node + /** + * The sequence number of these positions taken by this node + */ std::uint32_t proposeSeq_; - //! The identifier of the node taking this position + /** + * The identifier of the node taking this position + */ NodeId nodeID_; - //! The signing hash for this proposal + /** + * The signing hash for this proposal + */ mutable std::optional signingHash_; }; diff --git a/src/xrpld/consensus/ConsensusTypes.h b/src/xrpld/consensus/ConsensusTypes.h index 6b33f50662..4553e46f48 100644 --- a/src/xrpld/consensus/ConsensusTypes.h +++ b/src/xrpld/consensus/ConsensusTypes.h @@ -14,41 +14,50 @@ namespace xrpl { -/** Represents how a node currently participates in Consensus. - - A node participates in consensus in varying modes, depending on how - the node was configured by its operator and how well it stays in sync - with the network during consensus. - - @code - proposing observing - \ / - \---> wrongLedger <---/ - ^ - | - | - v - switchedLedger - @endcode - - We enter the round proposing or observing. If we detect we are working - on the wrong prior ledger, we go to wrongLedger and attempt to acquire - the right one. Once we acquire the right one, we go to the switchedLedger - mode. It is possible we fall behind again and find there is a new better - ledger, moving back and forth between wrongLedger and switchLedger as - we attempt to catch up. -*/ +/** + * Represents how a node currently participates in Consensus. + * + * A node participates in consensus in varying modes, depending on how + * the node was configured by its operator and how well it stays in sync + * with the network during consensus. + * + * @code + * proposing observing + * \ / + * \---> wrongLedger <---/ + * ^ + * | + * | + * v + * switchedLedger + * @endcode + * + * We enter the round proposing or observing. If we detect we are working + * on the wrong prior ledger, we go to wrongLedger and attempt to acquire + * the right one. Once we acquire the right one, we go to the switchedLedger + * mode. It is possible we fall behind again and find there is a new better + * ledger, moving back and forth between wrongLedger and switchLedger as + * we attempt to catch up. + */ enum class ConsensusMode { - //! We are normal participant in consensus and propose our position + /** + * We are normal participant in consensus and propose our position + */ Proposing, - //! We are observing peer positions, but not proposing our position + /** + * We are observing peer positions, but not proposing our position + */ Observing, - //! We have the wrong ledger and are attempting to acquire it + /** + * We have the wrong ledger and are attempting to acquire it + */ WrongLedger, - //! We switched ledgers since we started this consensus round but are now - //! running on what we believe is the correct ledger. This mode is as - //! if we entered the round observing, but is used to indicate we did - //! have the wrongLedger at some point. + /** + * We switched ledgers since we started this consensus round but are now + * running on what we believe is the correct ledger. This mode is as + * if we entered the round observing, but is used to indicate we did + * have the wrongLedger at some point. + */ SwitchedLedger }; @@ -70,32 +79,39 @@ to_string(ConsensusMode m) } } -/** Phases of consensus for a single ledger round. - - @code - "close" "accept" - open ------- > establish ---------> accepted - ^ | | - |---------------| | - ^ "startRound" | - |------------------------------------| - @endcode - - The typical transition goes from open to establish to accepted and - then a call to startRound begins the process anew. However, if a wrong prior - ledger is detected and recovered during the establish or accept phase, - consensus will internally go back to open (see Consensus::handleWrongLedger). -*/ +/** + * Phases of consensus for a single ledger round. + * + * @code + * "close" "accept" + * open ------- > establish ---------> accepted + * ^ | | + * |---------------| | + * ^ "startRound" | + * |------------------------------------| + * @endcode + * + * The typical transition goes from open to establish to accepted and + * then a call to startRound begins the process anew. However, if a wrong prior + * ledger is detected and recovered during the establish or accept phase, + * consensus will internally go back to open (see Consensus::handleWrongLedger). + */ enum class ConsensusPhase { - //! We haven't closed our ledger yet, but others might have + /** + * We haven't closed our ledger yet, but others might have + */ Open, - //! Establishing consensus by exchanging proposals with our peers + /** + * Establishing consensus by exchanging proposals with our peers + */ Establish, - //! We have accepted a new last closed ledger and are waiting on a call - //! to startRound to begin the next consensus round. No changes - //! to consensus phase occur while in this phase. + /** + * We have accepted a new last closed ledger and are waiting on a call + * to startRound to begin the next consensus round. No changes + * to consensus phase occur while in this phase. + */ Accepted, }; @@ -115,7 +131,8 @@ to_string(ConsensusPhase p) } } -/** Measures the duration of phases of consensus +/** + * Measures the duration of phases of consensus */ class ConsensusTimer { @@ -151,39 +168,47 @@ public: } }; -/** Stores the set of initial close times - - The initial consensus proposal from each peer has that peer's view of - when the ledger closed. This object stores all those close times for - analysis of clock drift between peers. -*/ +/** + * Stores the set of initial close times + * + * The initial consensus proposal from each peer has that peer's view of + * when the ledger closed. This object stores all those close times for + * analysis of clock drift between peers. + */ struct ConsensusCloseTimes { explicit ConsensusCloseTimes() = default; - //! Close time estimates, keep ordered for predictable traverse + /** + * Close time estimates, keep ordered for predictable traverse + */ std::map peers; - //! Our close time estimate + /** + * Our close time estimate + */ NetClock::time_point self; }; -/** Whether we have or don't have a consensus */ +/** + * Whether we have or don't have a consensus + */ enum class ConsensusState { - No, //!< We do not have consensus - MovedOn, //!< The network has consensus without us - Expired, //!< Consensus time limit has hard-expired - Yes //!< We have consensus along with the network + No, ///< We do not have consensus + MovedOn, ///< The network has consensus without us + Expired, ///< Consensus time limit has hard-expired + Yes ///< We have consensus along with the network }; -/** Encapsulates the result of consensus. - - Stores all relevant data for the outcome of consensus on a single - ledger. - - @tparam Traits Traits class defining the concrete consensus types used - by the application. -*/ +/** + * Encapsulates the result of consensus. + * + * Stores all relevant data for the outcome of consensus on a single + * ledger. + * + * @tparam Traits Traits class defining the concrete consensus types used + * by the application. + */ template struct ConsensusResult { @@ -200,13 +225,19 @@ struct ConsensusResult XRPL_ASSERT(txns.id() == position.position(), "xrpl::ConsensusResult : valid inputs"); } - //! The set of transactions consensus agrees go in the ledger + /** + * The set of transactions consensus agrees go in the ledger + */ TxSet_t txns; - //! Our proposed position on transactions/close time + /** + * Our proposed position on transactions/close time + */ Proposal_t position; - //! Transactions which are under dispute with our peers + /** + * Transactions which are under dispute with our peers + */ hash_map disputes; // Set of TxSet ids we have already compared/created disputes diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index 96df1536f5..12ed00d460 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -17,19 +17,20 @@ namespace xrpl { -/** A transaction discovered to be in dispute during consensus. - - During consensus, a @ref DisputedTx is created when a transaction - is discovered to be disputed. The object persists only as long as - the dispute. - - Undisputed transactions have no corresponding @ref DisputedTx object. - - Refer to @ref Consensus for details on the template type requirements. - - @tparam Tx The type for a transaction - @tparam NodeId The type for a node identifier -*/ +/** + * A transaction discovered to be in dispute during consensus. + * + * During consensus, a @ref DisputedTx is created when a transaction + * is discovered to be disputed. The object persists only as long as + * the dispute. + * + * Undisputed transactions have no corresponding @ref DisputedTx object. + * + * Refer to @ref Consensus for details on the template type requirements. + * + * @tparam Tx The type for a transaction + * @tparam NodeId The type for a node identifier + */ template class DisputedTx @@ -38,35 +39,42 @@ class DisputedTx using Map_t = boost::container::flat_map; public: - /** Constructor - - @param tx The transaction under dispute - @param ourVote Our vote on whether tx should be included - @param numPeers Anticipated number of peer votes - @param j Journal for debugging - */ + /** + * Constructor + * + * @param tx The transaction under dispute + * @param ourVote Our vote on whether tx should be included + * @param numPeers Anticipated number of peer votes + * @param j Journal for debugging + */ DisputedTx(Tx tx, bool ourVote, std::size_t numPeers, beast::Journal j) : ourVote_(ourVote), tx_(std::move(tx)), j_(j) { votes_.reserve(numPeers); } - //! The unique id/hash of the disputed transaction. + /** + * The unique id/hash of the disputed transaction. + */ [[nodiscard]] TxID_t const& id() const { return tx_.id(); } - //! Our vote on whether the transaction should be included. + /** + * Our vote on whether the transaction should be included. + */ [[nodiscard]] bool getOurVote() const { return ourVote_; } - //! Are we and our peers "stalled" where we probably won't change - //! our vote? + /** + * Are we and our peers "stalled" where we probably won't change + * our vote? + */ [[nodiscard]] bool stalled( ConsensusParms const& p, @@ -131,53 +139,62 @@ public: return stalled; } - //! The disputed transaction. + /** + * The disputed transaction. + */ [[nodiscard]] Tx const& tx() const { return tx_; } - //! Change our vote + /** + * Change our vote + */ void setOurVote(bool o) { ourVote_ = o; } - /** Change a peer's vote - - @param peer Identifier of peer. - @param votesYes Whether peer votes to include the disputed transaction. - - @return bool Whether the peer changed its vote. (A new vote counts as a - change.) - */ + /** + * Change a peer's vote + * + * @param peer Identifier of peer. + * @param votesYes Whether peer votes to include the disputed transaction. + * + * @return bool Whether the peer changed its vote. (A new vote counts as a + * change.) + */ [[nodiscard]] bool setVote(NodeId const& peer, bool votesYes); - /** Remove a peer's vote - - @param peer Identifier of peer. - */ + /** + * Remove a peer's vote + * + * @param peer Identifier of peer. + */ void unVote(NodeId const& peer); - /** Update our vote given progression of consensus. - - Updates our vote on this disputed transaction based on our peers' votes - and how far along consensus has proceeded. - - @param percentTime Percentage progress through consensus, e.g. 50% - through or 90%. - @param proposing Whether we are proposing to our peers in this round. - @param p Consensus parameters controlling thresholds for voting - @return Whether our vote changed - */ + /** + * Update our vote given progression of consensus. + * + * Updates our vote on this disputed transaction based on our peers' votes + * and how far along consensus has proceeded. + * + * @param percentTime Percentage progress through consensus, e.g. 50% + * through or 90%. + * @param proposing Whether we are proposing to our peers in this round. + * @param p Consensus parameters controlling thresholds for voting + * @return Whether our vote changed + */ bool updateVote(int percentTime, bool proposing, ConsensusParms const& p); - //! JSON representation of dispute, used for debugging + /** + * JSON representation of dispute, used for debugging + */ [[nodiscard]] json::Value getJson() const; @@ -187,11 +204,17 @@ private: bool ourVote_; //< Our vote (true is yes) Tx tx_; //< Transaction under dispute Map_t votes_; //< Map from NodeID to vote - //! The number of rounds we've gone without changing our vote + /** + * The number of rounds we've gone without changing our vote + */ std::size_t currentVoteCounter_ = 0; - //! Which minimum acceptance percentage phase we are currently in + /** + * Which minimum acceptance percentage phase we are currently in + */ ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::AvalancheState::Init; - //! How long we have been in the current acceptance phase + /** + * How long we have been in the current acceptance phase + */ std::size_t avalancheCounter_ = 0; beast::Journal const j_; }; diff --git a/src/xrpld/consensus/LedgerTrie.h b/src/xrpld/consensus/LedgerTrie.h index a21eea2a8a..8b6d9b5bdb 100644 --- a/src/xrpld/consensus/LedgerTrie.h +++ b/src/xrpld/consensus/LedgerTrie.h @@ -19,7 +19,8 @@ namespace xrpl { -/** The tip of a span of ledger ancestry +/** + * The tip of a span of ledger ancestry */ template class SpanTip @@ -37,14 +38,15 @@ public: // The ID of the tip ledger ID id; - /** Lookup the ID of an ancestor of the tip ledger - - @param s The sequence number of the ancestor - @return The ID of the ancestor with that sequence number - - @note s must be less than or equal to the sequence number of the - tip ledger - */ + /** + * Lookup the ID of an ancestor of the tip ledger + * + * @param s The sequence number of the ancestor + * @return The ID of the ancestor with that sequence number + * + * @note s must be less than or equal to the sequence number of the + * tip ledger + */ [[nodiscard]] ID ancestor(Seq const& s) const { @@ -199,12 +201,13 @@ struct Node std::vector> children; Node* parent = nullptr; - /** Remove the given node from this Node's children - - @param child The address of the child node to remove - @note The child must be a member of the vector. The passed pointer - will be dangling as a result of this call - */ + /** + * Remove the given node from this Node's children + * + * @param child The address of the child node to remove + * @note The child must be a member of the vector. The passed pointer + * will be dangling as a result of this call + */ void erase(Node const* child) { @@ -245,83 +248,84 @@ struct Node }; } // namespace ledger_trie_detail -/** Ancestry trie of ledgers - - A compressed trie tree that maintains validation support of recent ledgers - based on their ancestry. - - The compressed trie structure comes from recognizing that ledger history - can be viewed as a string over the alphabet of ledger ids. That is, - a given ledger with sequence number `seq` defines a length `seq` string, - with i-th entry equal to the id of the ancestor ledger with sequence - number i. "Sequence" strings with a common prefix share those ancestor - ledgers in common. Tracking this ancestry information and relations across - all validated ledgers is done conveniently in a compressed trie. A node in - the trie is an ancestor of all its children. If a parent node has sequence - number `seq`, each child node has a different ledger starting at `seq+1`. - The compression comes from the invariant that any non-root node with 0 tip - support has either no children or multiple children. In other words, a - non-root 0-tip-support node can be combined with its single child. - - Each node has a tipSupport, which is the number of current validations for - that particular ledger. The node's branch support is the sum of the tip - support and the branch support of that node's children: - - @code - node->branchSupport = node->tipSupport; - for (child : node->children) - node->branchSupport += child->branchSupport; - @endcode - - The templated Ledger type represents a ledger which has a unique history. - It should be lightweight and cheap to copy. - - @code - // Identifier types that should be equality-comparable and copyable - struct ID; - struct Seq; - - struct Ledger - { - struct MakeGenesis{}; - - // The genesis ledger represents a ledger that prefixes all other - // ledgers - Ledger(MakeGenesis{}); - - Ledger(Ledger const&); - Ledger& operator=(Ledger const&); - - // Return the sequence number of this ledger - Seq seq() const; - - // Return the ID of this ledger's ancestor with given sequence number - // or ID{0} if unknown - ID - operator[](Seq s); - - }; - - // Return the sequence number of the first possible mismatching ancestor - // between two ledgers - Seq - mismatch(ledgerA, ledgerB); - @endcode - - The unique history invariant of ledgers requires any ledgers that agree - on the id of a given sequence number agree on ALL ancestors before that - ledger: - - @code - Ledger a,b; - // For all Seq s: - if(a[s] == b[s]); - for(Seq p = 0; p < s; ++p) - assert(a[p] == b[p]); - @endcode - - @tparam Ledger A type representing a ledger and its history -*/ +/** + * Ancestry trie of ledgers + * + * A compressed trie tree that maintains validation support of recent ledgers + * based on their ancestry. + * + * The compressed trie structure comes from recognizing that ledger history + * can be viewed as a string over the alphabet of ledger ids. That is, + * a given ledger with sequence number `seq` defines a length `seq` string, + * with i-th entry equal to the id of the ancestor ledger with sequence + * number i. "Sequence" strings with a common prefix share those ancestor + * ledgers in common. Tracking this ancestry information and relations across + * all validated ledgers is done conveniently in a compressed trie. A node in + * the trie is an ancestor of all its children. If a parent node has sequence + * number `seq`, each child node has a different ledger starting at `seq+1`. + * The compression comes from the invariant that any non-root node with 0 tip + * support has either no children or multiple children. In other words, a + * non-root 0-tip-support node can be combined with its single child. + * + * Each node has a tipSupport, which is the number of current validations for + * that particular ledger. The node's branch support is the sum of the tip + * support and the branch support of that node's children: + * + * @code + * node->branchSupport = node->tipSupport; + * for (child : node->children) + * node->branchSupport += child->branchSupport; + * @endcode + * + * The templated Ledger type represents a ledger which has a unique history. + * It should be lightweight and cheap to copy. + * + * @code + * // Identifier types that should be equality-comparable and copyable + * struct ID; + * struct Seq; + * + * struct Ledger + * { + * struct MakeGenesis{}; + * + * // The genesis ledger represents a ledger that prefixes all other + * // ledgers + * Ledger(MakeGenesis{}); + * + * Ledger(Ledger const&); + * Ledger& operator=(Ledger const&); + * + * // Return the sequence number of this ledger + * Seq seq() const; + * + * // Return the ID of this ledger's ancestor with given sequence number + * // or ID{0} if unknown + * ID + * operator[](Seq s); + * + * }; + * + * // Return the sequence number of the first possible mismatching ancestor + * // between two ledgers + * Seq + * mismatch(ledgerA, ledgerB); + * @endcode + * + * The unique history invariant of ledgers requires any ledgers that agree + * on the id of a given sequence number agree on ALL ancestors before that + * ledger: + * + * @code + * Ledger a,b; + * // For all Seq s: + * if(a[s] == b[s]); + * for(Seq p = 0; p < s; ++p) + * assert(a[p] == b[p]); + * @endcode + * + * @tparam Ledger A type representing a ledger and its history + */ template class LedgerTrie { @@ -338,12 +342,13 @@ class LedgerTrie // Count of the tip support for each sequence number std::map seqSupport_; - /** Find the node in the trie that represents the longest common ancestry - with the given ledger. - - @return Pair of the found node and the sequence number of the first - ledger difference. - */ + /** + * Find the node in the trie that represents the longest common ancestry + * with the given ledger. + * + * @return Pair of the found node and the sequence number of the first + * ledger difference. + */ [[nodiscard]] std::pair find(Ledger const& ledger) const { @@ -377,12 +382,13 @@ class LedgerTrie return std::make_pair(curr, pos); } - /** Find the node in the trie with an exact match to the given ledger ID - - @return the found node or nullptr if an exact match was not found. - - @note O(n) since this searches all nodes until a match is found - */ + /** + * Find the node in the trie with an exact match to the given ledger ID + * + * @return the found node or nullptr if an exact match was not found. + * + * @note O(n) since this searches all nodes until a match is found + */ Node* findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const { @@ -420,10 +426,11 @@ public: { } - /** Insert and/or increment the support for the given ledger. - - @param ledger A ledger and its ancestry - @param count The count of support for this ledger + /** + * Insert and/or increment the support for the given ledger. + * + * @param ledger A ledger and its ancestry + * @param count The count of support for this ledger */ void insert(Ledger const& ledger, std::uint32_t count = 1) @@ -504,13 +511,14 @@ public: seqSupport_[ledger.seq()] += count; } - /** Decrease support for a ledger, removing and compressing if possible. - - @param ledger The ledger history to remove - @param count The amount of tip support to remove - - @return Whether a matching node was decremented and possibly removed. - */ + /** + * Decrease support for a ledger, removing and compressing if possible. + * + * @param ledger The ledger history to remove + * @param count The amount of tip support to remove + * + * @return Whether a matching node was decremented and possibly removed. + */ bool remove(Ledger const& ledger, std::uint32_t count = 1) { @@ -564,10 +572,11 @@ public: return true; } - /** Return count of tip support for the specific ledger. - - @param ledger The ledger to lookup - @return The number of entries in the trie for this *exact* ledger + /** + * Return count of tip support for the specific ledger. + * + * @param ledger The ledger to lookup + * @return The number of entries in the trie for this *exact* ledger */ [[nodiscard]] std::uint32_t tipSupport(Ledger const& ledger) const @@ -577,11 +586,12 @@ public: return 0; } - /** Return the count of branch support for the specific ledger - - @param ledger The ledger to lookup - @return The number of entries in the trie for this ledger or a - descendant + /** + * Return the count of branch support for the specific ledger + * + * @param ledger The ledger to lookup + * @return The number of entries in the trie for this ledger or a + * descendant */ [[nodiscard]] std::uint32_t branchSupport(Ledger const& ledger) const @@ -598,65 +608,66 @@ public: return loc ? loc->branchSupport : 0; } - /** Return the preferred ledger ID - - The preferred ledger is used to determine the working ledger - for consensus amongst competing alternatives. - - Recall that each validator is normally validating a chain of ledgers, - e.g. A->B->C->D. However, if due to network connectivity or other - issues, validators generate different chains - - @code - /->C - A->B - \->D->E - @endcode - - we need a way for validators to converge on the chain with the most - support. We call this the preferred ledger. Intuitively, the idea is to - be conservative and only switch to a different branch when you see - enough peer validations to *know* another branch won't have preferred - support. - - The preferred ledger is found by walking this tree of validated ledgers - starting from the common ancestor ledger. - - At each sequence number, we have - - - The prior sequence preferred ledger, e.g. B. - - The (tip) support of ledgers with this sequence number,e.g. the - number of validators whose last validation was for C or D. - - The (branch) total support of all descendants of the current - sequence number ledgers, e.g. the branch support of D is the - tip support of D plus the tip support of E; the branch support of - C is just the tip support of C. - - The number of validators that have yet to validate a ledger - with this sequence number (uncommitted support). Uncommitted - includes all validators whose last sequence number is smaller than - our last issued sequence number, since due to asynchrony, we may - not have heard from those nodes yet. - - The preferred ledger for this sequence number is then the ledger - with relative majority of support, where uncommitted support - can be given to ANY ledger at that sequence number - (including one not yet known). If no such preferred ledger exists, then - the prior sequence preferred ledger is the overall preferred ledger. - - In this example, for D to be preferred, the number of validators - supporting it or a descendant must exceed the number of validators - supporting C _plus_ the current uncommitted support. This is because if - all uncommitted validators end up validating C, that new support must - be less than that for D to be preferred. - - If a preferred ledger does exist, then we continue with the next - sequence using that ledger as the root. - - @param largestIssued The sequence number of the largest validation - issued by this node. - @return Pair with the sequence number and ID of the preferred ledger or - std::nullopt if no preferred ledger exists - */ + /** + * Return the preferred ledger ID + * + * The preferred ledger is used to determine the working ledger + * for consensus amongst competing alternatives. + * + * Recall that each validator is normally validating a chain of ledgers, + * e.g. A->B->C->D. However, if due to network connectivity or other + * issues, validators generate different chains + * + * @code + * /->C + * A->B + * \->D->E + * @endcode + * + * we need a way for validators to converge on the chain with the most + * support. We call this the preferred ledger. Intuitively, the idea is to + * be conservative and only switch to a different branch when you see + * enough peer validations to *know* another branch won't have preferred + * support. + * + * The preferred ledger is found by walking this tree of validated ledgers + * starting from the common ancestor ledger. + * + * At each sequence number, we have + * + * - The prior sequence preferred ledger, e.g. B. + * - The (tip) support of ledgers with this sequence number,e.g. the + * number of validators whose last validation was for C or D. + * - The (branch) total support of all descendants of the current + * sequence number ledgers, e.g. the branch support of D is the + * tip support of D plus the tip support of E; the branch support of + * C is just the tip support of C. + * - The number of validators that have yet to validate a ledger + * with this sequence number (uncommitted support). Uncommitted + * includes all validators whose last sequence number is smaller than + * our last issued sequence number, since due to asynchrony, we may + * not have heard from those nodes yet. + * + * The preferred ledger for this sequence number is then the ledger + * with relative majority of support, where uncommitted support + * can be given to ANY ledger at that sequence number + * (including one not yet known). If no such preferred ledger exists, then + * the prior sequence preferred ledger is the overall preferred ledger. + * + * In this example, for D to be preferred, the number of validators + * supporting it or a descendant must exceed the number of validators + * supporting C _plus_ the current uncommitted support. This is because if + * all uncommitted validators end up validating C, that new support must + * be less than that for D to be preferred. + * + * If a preferred ledger does exist, then we continue with the next + * sequence using that ledger as the root. + * + * @param largestIssued The sequence number of the largest validation + * issued by this node. + * @return Pair with the sequence number and ID of the preferred ledger or + * std::nullopt if no preferred ledger exists + */ [[nodiscard]] std::optional> getPreferred(Seq const largestIssued) const { @@ -758,7 +769,8 @@ public: return curr->span.tip(); } - /** Return whether the trie is tracking any ledgers + /** + * Return whether the trie is tracking any ledgers */ [[nodiscard]] bool empty() const @@ -766,7 +778,8 @@ public: return !root_ || root_->branchSupport == 0; } - /** Dump an ascii representation of the trie to the stream + /** + * Dump an ascii representation of the trie to the stream */ void dump(std::ostream& o) const @@ -774,7 +787,8 @@ public: dumpImpl(o, root_, 0); } - /** Dump JSON representation of trie state + /** + * Dump JSON representation of trie state */ [[nodiscard]] json::Value getJson() const @@ -787,7 +801,8 @@ public: return res; } - /** Check the compressed trie and support invariants. + /** + * Check the compressed trie and support invariants. */ [[nodiscard]] bool checkInvariants() const diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index a204cd0c78..2696804c86 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -26,48 +26,54 @@ namespace xrpl { -/** Timing parameters to control validation staleness and expiration. - - @note These are protocol level parameters that should not be changed without - careful consideration. They are *not* implemented as static constexpr - to allow simulation code to test alternate parameter settings. +/** + * Timing parameters to control validation staleness and expiration. + * + * @note These are protocol level parameters that should not be changed without + * careful consideration. They are *not* implemented as static constexpr + * to allow simulation code to test alternate parameter settings. */ struct ValidationParms { explicit ValidationParms() = default; - /** The number of seconds a validation remains current after its ledger's - close time. - - This is a safety to protect against very old validations and the time - it takes to adjust the close time accuracy window. - */ + /** + * The number of seconds a validation remains current after its ledger's + * close time. + * + * This is a safety to protect against very old validations and the time + * it takes to adjust the close time accuracy window. + */ std::chrono::seconds validationCurrentWall = std::chrono::minutes{5}; - /** Duration a validation remains current after first observed. - - The number of seconds a validation remains current after the time we - first saw it. This provides faster recovery in very rare cases where the - number of validations produced by the network is lower than normal - */ + /** + * Duration a validation remains current after first observed. + * + * The number of seconds a validation remains current after the time we + * first saw it. This provides faster recovery in very rare cases where the + * number of validations produced by the network is lower than normal + */ std::chrono::seconds validationCurrentLocal = std::chrono::minutes{3}; - /** Duration pre-close in which validations are acceptable. - - The number of seconds before a close time that we consider a validation - acceptable. This protects against extreme clock errors - */ + /** + * Duration pre-close in which validations are acceptable. + * + * The number of seconds before a close time that we consider a validation + * acceptable. This protects against extreme clock errors + */ std::chrono::seconds validationCurrentEarly = std::chrono::minutes{3}; - /** Duration a set of validations for a given ledger hash remain valid - - The number of seconds before a set of validations for a given ledger - hash can expire. This keeps validations for recent ledgers available - for a reasonable interval. - */ + /** + * Duration a set of validations for a given ledger hash remain valid + * + * The number of seconds before a set of validations for a given ledger + * hash can expire. This keeps validations for recent ledgers available + * for a reasonable interval. + */ std::chrono::seconds validationSetExpires = std::chrono::minutes{10}; - /** How long we consider a validation fresh. + /** + * How long we consider a validation fresh. * * The number of seconds since a validation has been seen for it to * be considered to accurately represent a live proposer's most recent @@ -78,12 +84,13 @@ struct ValidationParms std::chrono::seconds validationFRESHNESS = std::chrono::seconds{20}; }; -/** Enforce validation increasing sequence requirement. - - Helper class for enforcing that a validation must be larger than all - unexpired validation sequence numbers previously issued by the validator - tracked by the instance of this class. -*/ +/** + * Enforce validation increasing sequence requirement. + * + * Helper class for enforcing that a validation must be larger than all + * unexpired validation sequence numbers previously issued by the validator + * tracked by the instance of this class. + */ template class SeqEnforcer { @@ -92,18 +99,19 @@ class SeqEnforcer time_point when_; public: - /** Try advancing the largest observed validation ledger sequence - - Try setting the largest validation sequence observed, but return false - if it violates the invariant that a validation must be larger than all - unexpired validation sequence numbers. - - @param now The current time - @param s The sequence number we want to validate - @param p Validation parameters - - @return Whether the validation satisfies the invariant - */ + /** + * Try advancing the largest observed validation ledger sequence + * + * Try setting the largest validation sequence observed, but return false + * if it violates the invariant that a validation must be larger than all + * unexpired validation sequence numbers. + * + * @param now The current time + * @param s The sequence number we want to validate + * @param p Validation parameters + * + * @return Whether the validation satisfies the invariant + */ bool operator()(time_point now, Seq s, ValidationParms const& p) { @@ -123,17 +131,18 @@ public: } }; -/** Whether a validation is still current - - Determines whether a validation can still be considered the current - validation from a node based on when it was signed by that node and first - seen by this node. - - @param p ValidationParms with timing parameters - @param now Current time - @param signTime When the validation was signed - @param seenTime When the validation was first seen locally -*/ +/** + * Whether a validation is still current + * + * Determines whether a validation can still be considered the current + * validation from a node based on when it was signed by that node and first + * seen by this node. + * + * @param p ValidationParms with timing parameters + * @param now Current time + * @param signTime When the validation was signed + * @param seenTime When the validation was first seen locally + */ inline bool isCurrent( ValidationParms const& p, @@ -153,17 +162,29 @@ isCurrent( ((seenTime == NetClock::time_point{}) || (seenTime < (now + p.validationCurrentLocal))); } -/** Status of validation we received */ +/** + * Status of validation we received + */ enum class ValStatus { - /// This was a new validation and was added + /** + * This was a new validation and was added + */ Current, - /// Not current or was older than current from this node + /** + * Not current or was older than current from this node + */ Stale, - /// A validation violates the increasing seq requirement + /** + * A validation violates the increasing seq requirement + */ BadSeq, - /// Multiple validations by a validator for the same ledger + /** + * Multiple validations by a validator for the same ledger + */ Multiple, - /// Multiple validations by a validator for different ledgers + /** + * Multiple validations by a validator for different ledgers + */ Conflicting }; @@ -187,92 +208,93 @@ to_string(ValStatus m) } } -/** Maintains current and recent ledger validations. - - Manages storage and queries related to validations received on the network. - Stores the most current validation from nodes and sets of recent - validations grouped by ledger identifier. - - Stored validations are not necessarily from trusted nodes, so clients - and implementations should take care to use `trusted` member functions or - check the validation's trusted status. - - This class uses a generic interface to allow adapting Validations for - specific applications. The Adaptor template implements a set of helper - functions and type definitions. The code stubs below outline the - interface and type requirements. - - - @warning The Adaptor::MutexType is used to manage concurrent access to - private members of Validations but does not manage any data in the - Adaptor instance itself. - - @code - - // Conforms to the Ledger type requirements of LedgerTrie - struct Ledger; - - struct Validation - { - using NodeID = ...; - using NodeKey = ...; - - // Ledger ID associated with this validation - Ledger::ID ledgerID() const; - - // Sequence number of validation's ledger (0 means no sequence number) - Ledger::Seq seq() const - - // When the validation was signed - NetClock::time_point signTime() const; - - // When the validation was first observed by this node - NetClock::time_point seenTime() const; - - // Signing key of node that published the validation - NodeKey key() const; - - // Whether the publishing node was trusted at the time the validation - // arrived - bool trusted() const; - - // Set the validation as trusted - void setTrusted(); - - // Set the validation as untrusted - void setUntrusted(); - - // Whether this is a full or partial validation - bool full() const; - - // Identifier for this node that remains fixed even when rotating - // signing keys - NodeID nodeID() const; - - implementation_specific_t - unwrap() -> return the implementation-specific type being wrapped - - // ... implementation specific - }; - - class Adaptor - { - using Mutex = std::mutex; - using Validation = Validation; - using Ledger = Ledger; - - // Return the current network time (used to determine staleness) - NetClock::time_point now() const; - - // Attempt to acquire a specific ledger. - std::optional acquire(Ledger::ID const & ledgerID); - - // ... implementation specific - }; - @endcode - - @tparam Adaptor Provides type definitions and callbacks -*/ +/** + * Maintains current and recent ledger validations. + * + * Manages storage and queries related to validations received on the network. + * Stores the most current validation from nodes and sets of recent + * validations grouped by ledger identifier. + * + * Stored validations are not necessarily from trusted nodes, so clients + * and implementations should take care to use `trusted` member functions or + * check the validation's trusted status. + * + * This class uses a generic interface to allow adapting Validations for + * specific applications. The Adaptor template implements a set of helper + * functions and type definitions. The code stubs below outline the + * interface and type requirements. + * + * + * @warning The Adaptor::MutexType is used to manage concurrent access to + * private members of Validations but does not manage any data in the + * Adaptor instance itself. + * + * @code + * + * // Conforms to the Ledger type requirements of LedgerTrie + * struct Ledger; + * + * struct Validation + * { + * using NodeID = ...; + * using NodeKey = ...; + * + * // Ledger ID associated with this validation + * Ledger::ID ledgerID() const; + * + * // Sequence number of validation's ledger (0 means no sequence number) + * Ledger::Seq seq() const + * + * // When the validation was signed + * NetClock::time_point signTime() const; + * + * // When the validation was first observed by this node + * NetClock::time_point seenTime() const; + * + * // Signing key of node that published the validation + * NodeKey key() const; + * + * // Whether the publishing node was trusted at the time the validation + * // arrived + * bool trusted() const; + * + * // Set the validation as trusted + * void setTrusted(); + * + * // Set the validation as untrusted + * void setUntrusted(); + * + * // Whether this is a full or partial validation + * bool full() const; + * + * // Identifier for this node that remains fixed even when rotating + * // signing keys + * NodeID nodeID() const; + * + * implementation_specific_t + * unwrap() -> return the implementation-specific type being wrapped + * + * // ... implementation specific + * }; + * + * class Adaptor + * { + * using Mutex = std::mutex; + * using Validation = Validation; + * using Ledger = Ledger; + * + * // Return the current network time (used to determine staleness) + * NetClock::time_point now() const; + * + * // Attempt to acquire a specific ledger. + * std::optional acquire(Ledger::ID const & ledgerID); + * + * // ... implementation specific + * }; + * @endcode + * + * @tparam Adaptor Provides type definitions and callbacks + */ template class Validations { @@ -299,7 +321,9 @@ class Validations // Sequence of the largest validation received from each node hash_map> seqEnforcers_; - //! Validations from listed nodes, indexed by ledger id (partial and full) + /** + * Validations from listed nodes, indexed by ledger id (partial and full) + */ beast::aged_unordered_map< ID, hash_map, @@ -397,19 +421,20 @@ private: trie_.insert(ledger); } - /** Process a new validation - - Process a new trusted validation from a validator. This will be - reflected only after the validated ledger is successfully acquired by - the local node. In the interim, the prior validated ledger from this - node remains. - - @param lock Existing lock of mutex_ - @param nodeID The node identifier of the validating node - @param val The trusted validation issued by the node - @param prior If not none, the last current validated ledger Seq,ID of - key - */ + /** + * Process a new validation + * + * Process a new trusted validation from a validator. This will be + * reflected only after the validated ledger is successfully acquired by + * the local node. In the interim, the prior validated ledger from this + * node remains. + * + * @param lock Existing lock of mutex_ + * @param nodeID The node identifier of the validating node + * @param val The trusted validation issued by the node + * @param prior If not none, the last current validated ledger Seq,ID of + * key + */ void updateTrie( std::scoped_lock const& lock, @@ -452,18 +477,18 @@ private: } } - /** Use the trie for a calculation - - Accessing the trie through this helper ensures acquiring validations - are checked and any stale validations are flushed from the trie. - - @param lock Existing lock of mutex_ - @param f Invocable with signature (LedgerTrie &) - - @warning The invocable `f` is expected to be a simple transformation of - its arguments and will be called with mutex_ under lock. - - */ + /** + * Use the trie for a calculation + * + * Accessing the trie through this helper ensures acquiring validations + * are checked and any stale validations are flushed from the trie. + * + * @param lock Existing lock of mutex_ + * @param f Invocable with signature (LedgerTrie &) + * + * @warning The invocable `f` is expected to be a simple transformation of + * its arguments and will be called with mutex_ under lock. + */ template auto withTrie(std::scoped_lock const& lock, F&& f) @@ -474,21 +499,22 @@ private: return f(trie_); } - /** Iterate current validations. - - Iterate current validations, flushing any which are stale. - - @param lock Existing lock of mutex_ - @param pre Invocable with signature (std::size_t) called prior to - looping. - @param f Invocable with signature (NodeID const &, Validations const &) - for each current validation. - - @note The invocable `pre` is called _prior_ to checking for staleness - and reflects an upper-bound on the number of calls to `f. - @warning The invocable `f` is expected to be a simple transformation of - its arguments and will be called with mutex_ under lock. - */ + /** + * Iterate current validations. + * + * Iterate current validations, flushing any which are stale. + * + * @param lock Existing lock of mutex_ + * @param pre Invocable with signature (std::size_t) called prior to + * looping. + * @param f Invocable with signature (NodeID const &, Validations const &) + * for each current validation. + * + * @note The invocable `pre` is called _prior_ to checking for staleness + * and reflects an upper-bound on the number of calls to `f. + * @warning The invocable `f` is expected to be a simple transformation of + * its arguments and will be called with mutex_ under lock. + */ template void @@ -515,18 +541,19 @@ private: } } - /** Iterate the set of validations associated with a given ledger id - - @param lock Existing lock on mutex_ - @param ledgerID The identifier of the ledger - @param pre Invocable with signature(std::size_t) - @param f Invocable with signature (NodeID const &, Validation const &) - - @note The invocable `pre` is called prior to iterating validations. The - argument is the number of times `f` will be called. - @warning The invocable f is expected to be a simple transformation of - its arguments and will be called with mutex_ under lock. - */ + /** + * Iterate the set of validations associated with a given ledger id + * + * @param lock Existing lock on mutex_ + * @param ledgerID The identifier of the ledger + * @param pre Invocable with signature(std::size_t) + * @param f Invocable with signature (NodeID const &, Validation const &) + * + * @note The invocable `pre` is called prior to iterating validations. The + * argument is the number of times `f` will be called. + * @warning The invocable f is expected to be a simple transformation of + * its arguments and will be called with mutex_ under lock. + */ template void byLedger(std::scoped_lock const&, ID const& ledgerID, Pre&& pre, F&& f) @@ -543,12 +570,13 @@ private: } public: - /** Constructor - - @param p ValidationParms to control staleness/expiration of validations - @param c Clock to use for expiring validations stored by ledger - @param ts Parameters for constructing Adaptor instance - */ + /** + * Constructor + * + * @param p ValidationParms to control staleness/expiration of validations + * @param c Clock to use for expiring validations stored by ledger + * @param ts Parameters for constructing Adaptor instance + */ template Validations( ValidationParms const& p, @@ -558,7 +586,8 @@ public: { } - /** Return the adaptor instance + /** + * Return the adaptor instance */ Adaptor const& adaptor() const @@ -566,7 +595,8 @@ public: return adaptor_; } - /** Return the validation timing parameters + /** + * Return the validation timing parameters */ ValidationParms const& parms() const @@ -574,13 +604,14 @@ public: return parms_; } - /** Return whether the local node can issue a validation for the given - sequence number - - @param s The sequence number of the ledger the node wants to validate - @return Whether the validation satisfies the invariant, updating the - largest sequence number seen accordingly - */ + /** + * Return whether the local node can issue a validation for the given + * sequence number + * + * @param s The sequence number of the ledger the node wants to validate + * @return Whether the validation satisfies the invariant, updating the + * largest sequence number seen accordingly + */ bool canValidateSeq(Seq const s) { @@ -588,14 +619,15 @@ public: return localSeqEnforcer_(byLedger_.clock().now(), s, parms_); } - /** Add a new validation - - Attempt to add a new validation. - - @param nodeID The identity of the node issuing this validation - @param val The validation to store - @return The outcome - */ + /** + * Add a new validation + * + * Attempt to add a new validation. + * + * @param nodeID The identity of the node issuing this validation + * @param val The validation to store + * @return The outcome + */ ValStatus add(NodeID const& nodeID, Validation const& val) { @@ -696,11 +728,12 @@ public: toKeep_ = {low, high}; } - /** Expire old validation sets - - Remove validation sets that were accessed more than - validationSET_EXPIRES ago and were not asked to keep. - */ + /** + * Expire old validation sets + * + * Remove validation sets that were accessed more than + * validationSET_EXPIRES ago and were not asked to keep. + */ void expire(beast::Journal const& j) { @@ -751,15 +784,16 @@ public: << "ms"; } - /** Update trust status of validations - - Updates the trusted status of known validations to account for nodes - that have been added or removed from the UNL. This also updates the trie - to ensure only currently trusted nodes' validations are used. - - @param added Identifiers of nodes that are now trusted - @param removed Identifiers of nodes that are no longer trusted - */ + /** + * Update trust status of validations + * + * Updates the trusted status of known validations to account for nodes + * that have been added or removed from the UNL. This also updates the trie + * to ensure only currently trusted nodes' validations are used. + * + * @param added Identifiers of nodes that are now trusted + * @param removed Identifiers of nodes that are no longer trusted + */ void trustChanged(hash_set const& added, hash_set const& removed) { @@ -803,18 +837,19 @@ public: return trie_.getJson(); } - /** Return the sequence number and ID of the preferred working ledger - - A ledger is preferred if it has more support amongst trusted validators - and is *not* an ancestor of the current working ledger; otherwise it - remains the current working ledger. - - @param curr The local node's current working ledger - - @return The sequence and id of the preferred working ledger, - or std::nullopt if no trusted validations are available to - determine the preferred ledger. - */ + /** + * Return the sequence number and ID of the preferred working ledger + * + * A ledger is preferred if it has more support amongst trusted validators + * and is *not* an ancestor of the current working ledger; otherwise it + * remains the current working ledger. + * + * @param curr The local node's current working ledger + * + * @return The sequence and id of the preferred working ledger, + * or std::nullopt if no trusted validations are available to + * determine the preferred ledger. + */ std::optional> getPreferred(Ledger const& curr) { @@ -859,15 +894,16 @@ public: return std::make_pair(curr.seq(), curr.id()); } - /** Get the ID of the preferred working ledger that exceeds a minimum valid - ledger sequence number - - @param curr Current working ledger - @param minValidSeq Minimum allowed sequence number - - @return ID Of the preferred ledger, or curr if the preferred ledger - is not valid - */ + /** + * Get the ID of the preferred working ledger that exceeds a minimum valid + * ledger sequence number + * + * @param curr Current working ledger + * @param minValidSeq Minimum allowed sequence number + * + * @return ID Of the preferred ledger, or curr if the preferred ledger + * is not valid + */ ID getPreferred(Ledger const& curr, Seq minValidSeq) { @@ -877,22 +913,23 @@ public: return curr.id(); } - /** Determine the preferred last closed ledger for the next consensus round. - - Called before starting the next round of ledger consensus to determine - the preferred working ledger. Uses the dominant peerCount ledger if no - trusted validations are available. - - @param lcl Last closed ledger by this node - @param minSeq Minimum allowed sequence number of the trusted preferred - ledger - @param peerCounts Map from ledger ids to count of peers with that as the - last closed ledger - @return The preferred last closed ledger ID - - @note The minSeq does not apply to the peerCounts, since this function - does not know their sequence number - */ + /** + * Determine the preferred last closed ledger for the next consensus round. + * + * Called before starting the next round of ledger consensus to determine + * the preferred working ledger. Uses the dominant peerCount ledger if no + * trusted validations are available. + * + * @param lcl Last closed ledger by this node + * @param minSeq Minimum allowed sequence number of the trusted preferred + * ledger + * @param peerCounts Map from ledger ids to count of peers with that as the + * last closed ledger + * @return The preferred last closed ledger ID + * + * @note The minSeq does not apply to the peerCounts, since this function + * does not know their sequence number + */ ID getPreferredLCL(Ledger const& lcl, Seq minSeq, hash_map const& peerCounts) { @@ -915,17 +952,18 @@ public: return lcl.id(); } - /** Count the number of current trusted validators working on a ledger - after the specified one. - - @param ledger The working ledger - @param ledgerID The preferred ledger - @return The number of current trusted validators working on a descendant - of the preferred ledger - - @note If ledger.id() != ledgerID, only counts immediate child ledgers of - ledgerID - */ + /** + * Count the number of current trusted validators working on a ledger + * after the specified one. + * + * @param ledger The working ledger + * @param ledgerID The preferred ledger + * @return The number of current trusted validators working on a descendant + * of the preferred ledger + * + * @note If ledger.id() != ledgerID, only counts immediate child ledgers of + * ledgerID + */ std::size_t getNodesAfter(Ledger const& ledger, ID const& ledgerID) { @@ -946,10 +984,11 @@ public: }); } - /** Get the currently trusted full validations - - @return Vector of validations from currently trusted validators - */ + /** + * Get the currently trusted full validations + * + * @return Vector of validations from currently trusted validators + */ std::vector currentTrusted() { @@ -965,10 +1004,11 @@ public: return ret; } - /** Get the set of node ids associated with current validations - - @return The set of node ids for active, listed validators - */ + /** + * Get the set of node ids associated with current validations + * + * @return The set of node ids for active, listed validators + */ auto getCurrentNodeIDs() -> hash_set { @@ -982,11 +1022,12 @@ public: return ret; } - /** Count the number of trusted full validations for the given ledger - - @param ledgerID The identifier of ledger of interest - @return The number of trusted validations - */ + /** + * Count the number of trusted full validations for the given ledger + * + * @param ledgerID The identifier of ledger of interest + * @return The number of trusted validations + */ std::size_t numTrustedForLedger(ID const& ledgerID) { @@ -1003,12 +1044,13 @@ public: return count; } - /** Get trusted full validations for a specific ledger - - @param ledgerID The identifier of ledger of interest - @param seq The sequence number of ledger of interest - @return Trusted validations associated with ledger - */ + /** + * Get trusted full validations for a specific ledger + * + * @param ledgerID The identifier of ledger of interest + * @param seq The sequence number of ledger of interest + * @return Trusted validations associated with ledger + */ std::vector getTrustedForLedger(ID const& ledgerID, Seq const& seq) { @@ -1026,12 +1068,13 @@ public: return res; } - /** Returns fees reported by trusted full validators in the given ledger - - @param ledgerID The identifier of ledger of interest - @param baseFee The fee to report if not present in the validation - @return Vector of fees - */ + /** + * Returns fees reported by trusted full validators in the given ledger + * + * @param ledgerID The identifier of ledger of interest + * @param baseFee The fee to report if not present in the validation + * @return Vector of fees + */ std::vector fees(ID const& ledgerID, std::uint32_t baseFee) { @@ -1058,7 +1101,8 @@ public: return res; } - /** Flush all current validations + /** + * Flush all current validations */ void flush() @@ -1067,7 +1111,8 @@ public: current_.clear(); } - /** Return quantity of lagging proposers, and remove online proposers + /** + * Return quantity of lagging proposers, and remove online proposers * for purposes of evaluating whether to pause. * * Laggards are the trusted proposers whose sequence number is lower diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 285ea7b9ac..852e46218a 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -44,26 +44,35 @@ enum class SizedItem : std::size_t { AccountIdCacheSize, }; -/** Fee schedule for startup / standalone, and to vote for. -During voting ledgers, the FeeVote logic will try to move towards -these values when injecting fee-setting transactions. -A default-constructed Setup contains recommended values. -*/ +/** + * Fee schedule for startup / standalone, and to vote for. + * During voting ledgers, the FeeVote logic will try to move towards + * these values when injecting fee-setting transactions. + * A default-constructed Setup contains recommended values. + */ struct FeeSetup { - /** The cost of a reference transaction in drops. */ + /** + * The cost of a reference transaction in drops. + */ XRPAmount referenceFee{10}; - /** The account reserve requirement in drops. */ + /** + * The account reserve requirement in drops. + */ XRPAmount accountReserve{10 * kDropsPerXrp}; - /** The per-owned item reserve requirement in drops. */ + /** + * The per-owned item reserve requirement in drops. + */ XRPAmount ownerReserve{2 * kDropsPerXrp}; /* (Remember to update the example cfg files when changing any of these * values.) */ - /** Convert to a Fees object for use with Ledger construction. */ + /** + * Convert to a Fees object for use with Ledger construction. + */ [[nodiscard]] Fees toFees() const { @@ -85,7 +94,9 @@ public: static char const* const kDatabaseDirName; static char const* const kValidatorsFileName; - /** Returns the full path and filename of the debug log file. */ + /** + * Returns the full path and filename of the debug log file. + */ [[nodiscard]] boost::filesystem::path getDebugLogFile() const; @@ -104,25 +115,27 @@ private: bool quiet_ = false; // Minimize logging verbosity. bool silent_ = false; // No output to console after startup. - /** Operate in stand-alone mode. - - In stand alone mode: - - - Peer connections are not attempted or accepted - - The ledger is not advanced automatically. - - If no ledger is loaded, the default ledger with the root - account is created. - */ + /** + * Operate in stand-alone mode. + * + * In stand alone mode: + * + * - Peer connections are not attempted or accepted + * - The ledger is not advanced automatically. + * - If no ledger is loaded, the default ledger with the root + * account is created. + */ bool runStandalone_ = false; bool useTxTables_ = true; - /** Determines if the server will sign a tx, given an account's secret seed. - - In the past, this was allowed, but this functionality can have security - implications. The new default is to not allow this functionality, but - a config option is included to enable this. - */ + /** + * Determines if the server will sign a tx, given an account's secret seed. + * + * In the past, this was allowed, but this functionality can have security + * implications. The new default is to not allow this functionality, but + * a config option is included to enable this. + */ bool signingEnabled_ = false; // The amount of RAM, in bytes, that we detected on this system. @@ -236,12 +249,16 @@ public: // Enable base squelching of duplicate validation/proposal messages bool vpReduceRelayBaseSquelchEnable = false; - ///////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// + /** + * ////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// + */ // Temporary squelching config for the peers selected as a source of // // validator messages. The config must be removed once squelching is // // made the default routing algorithm // std::size_t vpReduceRelaySquelchMaxSelectedPeers = 5; - ///////////////// END OF TEMPORARY CODE BLOCK ///////////////////// + /** + * ////////////// END OF TEMPORARY CODE BLOCK ///////////////////// + */ // Transaction reduce-relay feature bool txReduceRelayEnable = false; @@ -299,9 +316,9 @@ public: setupControl(bool bQuiet, bool bSilent, bool bStandalone); /** - * Load the config from the contents of the string. + * Load the config from the contents of the string. * - * @param fileContents String representing the config contents. + * @param fileContents String representing the config contents. */ void loadFromString(std::string const& fileContents); @@ -334,23 +351,24 @@ public: return signingEnabled_; } - /** Retrieve the default value for the item at the specified node size - - @param item The item for which the default value is needed - @param node Optional value, used to adjust the result to match the - size of a node (0: tiny, ..., 4: huge). If unseated, - uses the configured size (NODE_SIZE). - - @throw This method can throw std::out_of_range if you ask for values - that it does not recognize or request a non-default node-size. - - @return The value for the requested item. - - @note The defaults are selected so as to be reasonable, but the node - size is an imprecise metric that combines multiple aspects of - the underlying system; this means that we can't provide optimal - defaults in the code for every case. - */ + /** + * Retrieve the default value for the item at the specified node size + * + * @param item The item for which the default value is needed + * @param node Optional value, used to adjust the result to match the + * size of a node (0: tiny, ..., 4: huge). If unseated, + * uses the configured size (NODE_SIZE). + * + * @throws This method can throw std::out_of_range if you ask for values + * that it does not recognize or request a non-default node-size. + * + * @return The value for the requested item. + * + * @note The defaults are selected so as to be reasonable, but the node + * size is an imprecise metric that combines multiple aspects of + * the underlying system; this means that we can't provide optimal + * defaults in the code for every case. + */ [[nodiscard]] int getValueFor(SizedItem item, std::optional node = std::nullopt) const; diff --git a/src/xrpld/core/NetworkIDServiceImpl.h b/src/xrpld/core/NetworkIDServiceImpl.h index 2236a854ff..7977566ef8 100644 --- a/src/xrpld/core/NetworkIDServiceImpl.h +++ b/src/xrpld/core/NetworkIDServiceImpl.h @@ -9,12 +9,13 @@ namespace xrpl { // Forward declaration class Config; -/** Implementation of NetworkIDService that reads from Config. - - This class provides a NetworkIDService interface that wraps - the network ID from the application Config. It caches the - network ID at construction time. -*/ +/** + * Implementation of NetworkIDService that reads from Config. + * + * This class provides a NetworkIDService interface that wraps + * the network ID from the application Config. It caches the + * network ID at construction time. + */ class NetworkIDServiceImpl final : public NetworkIDService { public: diff --git a/src/xrpld/core/TimeKeeper.h b/src/xrpld/core/TimeKeeper.h index 9e067759ec..8ee9d17a06 100644 --- a/src/xrpld/core/TimeKeeper.h +++ b/src/xrpld/core/TimeKeeper.h @@ -8,7 +8,9 @@ namespace xrpl { -/** Manages various times used by the server. */ +/** + * Manages various times used by the server. + */ class TimeKeeper : public beast::AbstractClock { private: @@ -25,34 +27,36 @@ private: public: ~TimeKeeper() override = default; - /** Returns the current time, using the server's clock. - - 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. - - 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. - - @note The network time is adjusted for the "XRPL 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. - */ + /** + * Returns the current time, using the server's clock. + * + * 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. + * + * 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. + * + * @note The network time is adjusted for the "XRPL 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. + */ [[nodiscard]] time_point now() const override { return adjust(std::chrono::system_clock::now()); } - /** Returns the predicted close time, in network time. - - 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. - */ + /** + * Returns the predicted close time, in network time. + * + * 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. + */ [[nodiscard]] time_point closeTime() const { @@ -66,7 +70,9 @@ public: return closeOffset_.load(); } - /** Adjust the close time, based on the network's view of time. */ + /** + * Adjust the close time, based on the network's view of time. + */ std::chrono::seconds adjustCloseTime(std::chrono::seconds by) { diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 0706163ab1..3b7b57328b 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -793,7 +793,9 @@ Config::loadFromString(std::string const& fileContents) { auto sec = section(Sections::kReduceRelay); - ///////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// + /** + * ////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// + */ // vp_enable config option is deprecated by vp_base_squelch_enable // // This option is kept for backwards compatibility. When squelching // // is the default algorithm, it must be replaced with: // @@ -821,9 +823,13 @@ Config::loadFromString(std::string const& fileContents) { vpReduceRelayBaseSquelchEnable = false; } - ///////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// + /** + * ////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// + */ - ///////////////////// !!TEMPORARY CODE BLOCK!! /////////////////////// + /** + * ////////////////// !!TEMPORARY CODE BLOCK!! /////////////////////// + */ // Temporary squelching config for the peers selected as a source of // // validator messages. The config must be removed once squelching is // // made the default routing algorithm. // @@ -835,7 +841,9 @@ Config::loadFromString(std::string const& fileContents) " vp_base_squelch_max_selected_peers must be " "greater than or equal to 3"); } - ///////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// + /** + * ////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// + */ txReduceRelayEnable = sec.valueOr(Keys::kTxEnable, false); txReduceRelayMetrics = sec.valueOr(Keys::kTxMetrics, false); diff --git a/src/xrpld/overlay/Cluster.h b/src/xrpld/overlay/Cluster.h index b3864e0fc6..703e1601aa 100644 --- a/src/xrpld/overlay/Cluster.h +++ b/src/xrpld/overlay/Cluster.h @@ -53,23 +53,27 @@ private: public: Cluster(beast::Journal j); - /** Determines whether a node belongs in the cluster - @return std::nullopt if the node isn't a member, - otherwise, the comment associated with the - node (which may be an empty string). - */ + /** + * Determines whether a node belongs in the cluster + * @return std::nullopt if the node isn't a member, + * otherwise, the comment associated with the + * node (which may be an empty string). + */ std::optional member(PublicKey const& node) const; - /** The number of nodes in the cluster list. */ + /** + * The number of nodes in the cluster list. + */ std::size_t size() const; - /** Store information about the state of a cluster node. - @param identity The node's public identity - @param name The node's name (may be empty) - @return true if we updated our information - */ + /** + * Store information about the state of a cluster node. + * @param identity The node's public identity + * @param name The node's name (may be empty) + * @return true if we updated our information + */ bool update( PublicKey const& identity, @@ -77,23 +81,25 @@ public: std::uint32_t loadFee = 0, NetClock::time_point reportTime = NetClock::time_point{}); - /** Invokes the callback once for every cluster node. - @note You are not allowed to call `update` from - within the callback. - */ + /** + * Invokes the callback once for every cluster node. + * @note You are not allowed to call `update` from + * within the callback. + */ void forEach(std::function func) const; - /** Load the list of cluster nodes. - - The section contains entries consisting of a base58 - encoded node public key, optionally followed by - a comment. - - @return false if an entry could not be parsed or - contained an invalid node public key, - true otherwise. - */ + /** + * Load the list of cluster nodes. + * + * The section contains entries consisting of a base58 + * encoded node public key, optionally followed by + * a comment. + * + * @return false if an entry could not be parsed or + * contained an invalid node public key, + * true otherwise. + */ bool load(Section const& nodes); }; diff --git a/src/xrpld/overlay/Compression.h b/src/xrpld/overlay/Compression.h index 4b7493e7e6..8d4a1d56c8 100644 --- a/src/xrpld/overlay/Compression.h +++ b/src/xrpld/overlay/Compression.h @@ -18,7 +18,8 @@ enum class Algorithm : std::uint8_t { None = 0x00, LZ4 = 0x90 }; enum class Compressed : std::uint8_t { On, Off }; -/** Decompress input stream. +/** + * Decompress input stream. * @tparam InputStream ZeroCopyInputStream * @param in Input source stream * @param inSize Size of compressed data @@ -57,7 +58,8 @@ decompress( return 0; } -/** Compress input data. +/** + * Compress input data. * @tparam BufferFactory Callable object or lambda. * Takes the requested buffer size and returns allocated buffer pointer. * @param in Data to compress diff --git a/src/xrpld/overlay/Message.h b/src/xrpld/overlay/Message.h index 63aa360f92..2e187a2a4d 100644 --- a/src/xrpld/overlay/Message.h +++ b/src/xrpld/overlay/Message.h @@ -39,7 +39,8 @@ class Message : public std::enable_shared_from_this using Algorithm = compression::Algorithm; public: - /** Constructor + /** + * Constructor * @param message Protocol message to serialize * @param type Protocol message type * @param validator Public Key of the source validator for Validation or @@ -50,7 +51,9 @@ public: protocol::MessageType type, std::optional const& validator = {}); - /** Retrieve the size of the packed but uncompressed message data. */ + /** + * Retrieve the size of the packed but uncompressed message data. + */ std::size_t getBufferSize(); @@ -60,7 +63,8 @@ public: static std::size_t totalSize(::google::protobuf::Message const& message); - /** Retrieve the packed message data. If compressed message is requested but + /** + * Retrieve the packed message data. If compressed message is requested but * the message is not compressible then the uncompressed buffer is returned. * @param compressed Request compressed (Compress::On) or * uncompressed (Compress::Off) payload buffer @@ -69,14 +73,18 @@ public: std::vector const& getBuffer(Compressed tryCompressed); - /** Get the traffic category */ + /** + * Get the traffic category + */ std::size_t getCategory() const { return category_; } - /** Get the validator's key */ + /** + * Get the validator's key + */ std::optional const& getValidatorKey() const { @@ -90,7 +98,8 @@ private: std::once_flag onceFlag_; std::optional validatorKey_; - /** Set the payload header + /** + * Set the payload header * @param in Pointer to the payload * @param payloadBytes Size of the payload excluding the header size * @param type Protocol message type @@ -106,14 +115,16 @@ private: Algorithm compression, std::uint32_t uncompressedBytes); - /** Try to compress the payload. + /** + * Try to compress the payload. * Can be called concurrently by multiple peers but is compressed once. * If the message is not compressible then the serialized buffer_ is used. */ void compress(); - /** Get the message type from the payload header. + /** + * 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 diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index cc5be791a7..6cc229f5a0 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -31,7 +31,9 @@ class context; namespace xrpl { -/** Manages the set of connected peers. */ +/** + * Manages the set of connected peers. + */ class Overlay : public beast::PropertyStream::Source { protected: @@ -75,66 +77,84 @@ public: { } - /** Conditionally accept an incoming HTTP request. */ + /** + * Conditionally accept an incoming HTTP request. + */ virtual Handoff onHandoff( std::unique_ptr&& bundle, http_request_type&& request, boost::asio::ip::tcp::endpoint remoteAddress) = 0; - /** Establish a peer connection to the specified endpoint. - The call returns immediately, the connection attempt is - performed asynchronously. - */ + /** + * Establish a peer connection to the specified endpoint. + * The call returns immediately, the connection attempt is + * performed asynchronously. + */ virtual void connect(beast::IP::Endpoint const& address) = 0; - /** Returns the maximum number of peers we are configured to allow. */ + /** + * Returns the maximum number of peers we are configured to allow. + */ virtual int limit() = 0; - /** Returns the number of active peers. - Active peers are only those peers that have completed the - handshake and are using the peer protocol. - */ + /** + * Returns the number of active peers. + * Active peers are only those peers that have completed the + * handshake and are using the peer protocol. + */ [[nodiscard]] virtual std::size_t size() const = 0; - /** Return diagnostics on the status of all peers. - @deprecated This is superseded by PropertyStream - */ + /** + * Return diagnostics on the status of all peers. + * @deprecated This is superseded by PropertyStream + */ virtual json::Value json() = 0; - /** Returns a sequence representing the current list of peers. - The snapshot is made at the time of the call. - */ + /** + * Returns a sequence representing the current list of peers. + * The snapshot is made at the time of the call. + */ [[nodiscard]] virtual PeerSequence getActivePeers() const = 0; - /** Calls the checkTracking function on each peer - @param index the value to pass to the peer's checkTracking function - */ + /** + * Calls the checkTracking function on each peer + * @param index the value to pass to the peer's checkTracking function + */ virtual void checkTracking(std::uint32_t index) = 0; - /** Returns the peer with the matching short id, or null. */ + /** + * Returns the peer with the matching short id, or null. + */ [[nodiscard]] virtual std::shared_ptr findPeerByShortID(Peer::id_t const& id) const = 0; - /** Returns the peer with the matching public key, or null. */ + /** + * Returns the peer with the matching public key, or null. + */ virtual std::shared_ptr findPeerByPublicKey(PublicKey const& pubKey) = 0; - /** Broadcast a proposal. */ + /** + * Broadcast a proposal. + */ virtual void broadcast(protocol::TMProposeSet const& m) = 0; - /** Broadcast a validation. */ + /** + * Broadcast a validation. + */ virtual void broadcast(protocol::TMValidation const& m) = 0; - /** Relay a proposal. + /** + * Relay a proposal. * @param m the serialized proposal * @param uid the id used to identify this proposal * @param validator The pubkey of the validator that issued this proposal @@ -143,7 +163,8 @@ public: virtual std::set relay(protocol::TMProposeSet const& m, uint256 const& uid, PublicKey const& validator) = 0; - /** Relay a validation. + /** + * Relay a validation. * @param m the serialized validation * @param uid the id used to identify this validation * @param validator The pubkey of the validator that issued this validation @@ -152,7 +173,8 @@ public: virtual std::set relay(protocol::TMValidation const& m, uint256 const& uid, PublicKey const& validator) = 0; - /** Relay a transaction. If the tx reduce-relay feature is enabled then + /** + * Relay a transaction. If the tx reduce-relay feature is enabled then * randomly select peers to relay to and queue transaction's hash * for the rest of the peers. * @param hash transaction's hash @@ -165,7 +187,8 @@ public: std::optional> m, std::set const& toSkip) = 0; - /** Visit every active peer. + /** + * Visit every active peer. * * The visitor must be invocable as: * Function(std::shared_ptr const& peer); @@ -180,13 +203,16 @@ public: f(p); } - /** Increment and retrieve counter for transaction job queue overflows. */ + /** + * Increment and retrieve counter for transaction job queue overflows. + */ virtual void incJqTransOverflow() = 0; [[nodiscard]] virtual std::uint64_t getJqTransOverflow() const = 0; - /** Increment and retrieve counters for total peer disconnects, and + /** + * Increment and retrieve counters for total peer disconnects, and * disconnects we initiate for excessive resource consumption. */ virtual void @@ -198,19 +224,21 @@ public: [[nodiscard]] virtual std::uint64_t getPeerDisconnectCharges() const = 0; - /** Returns the ID of the network this server is configured for, if any. - - The ID is just a numerical identifier, with the IDs 0, 1 and 2 used to - identify the mainnet, the testnet and the devnet respectively. - - @return The numerical identifier configured by the administrator of the - server. An unseated optional, otherwise. - */ + /** + * Returns the ID of the network this server is configured for, if any. + * + * The ID is just a numerical identifier, with the IDs 0, 1 and 2 used to + * identify the mainnet, the testnet and the devnet respectively. + * + * @return The numerical identifier configured by the administrator of the + * server. An unseated optional, otherwise. + */ [[nodiscard]] virtual std::optional networkID() const = 0; - /** Returns tx reduce-relay metrics - @return json value of tx reduce-relay metrics + /** + * Returns tx reduce-relay metrics + * @return json value of tx reduce-relay metrics */ [[nodiscard]] virtual json::Value txMetrics() const = 0; diff --git a/src/xrpld/overlay/Peer.h b/src/xrpld/overlay/Peer.h index 29778b42a6..23a45dc512 100644 --- a/src/xrpld/overlay/Peer.h +++ b/src/xrpld/overlay/Peer.h @@ -25,17 +25,20 @@ enum class ProtocolFeature { LedgerReplay, }; -/** Represents a peer connection in the overlay. */ +/** + * Represents a peer connection in the overlay. + */ class Peer { public: using ptr = std::shared_ptr; - /** Uniquely identifies a peer. - This can be stored in tables to find the peer later. Callers - can discover if the peer is no longer connected and make - adjustments as needed. - */ + /** + * Uniquely identifies a peer. + * This can be stored in tables to find the peer later. Callers + * can discover if the peer is no longer connected and make + * adjustments as needed. + */ using id_t = std::uint32_t; virtual ~Peer() = default; @@ -50,19 +53,27 @@ public: [[nodiscard]] virtual beast::IP::Endpoint getRemoteAddress() const = 0; - /** Send aggregated transactions' hashes. */ + /** + * Send aggregated transactions' hashes. + */ virtual void sendTxQueue() = 0; - /** Aggregate transaction's hash. */ + /** + * Aggregate transaction's hash. + */ virtual void addTxQueue(uint256 const&) = 0; - /** Remove hash from the transactions' hashes queue. */ + /** + * Remove hash from the transactions' hashes queue. + */ virtual void removeTxQueue(uint256 const&) = 0; - /** Adjust this peer's load balance based on the type of load imposed. */ + /** + * Adjust this peer's load balance based on the type of load imposed. + */ virtual void charge(Resource::Charge const& fee, std::string const& context) = 0; @@ -73,7 +84,9 @@ public: [[nodiscard]] virtual id_t id() const = 0; - /** Returns `true` if this connection is a member of the cluster. */ + /** + * Returns `true` if this connection is a member of the cluster. + */ [[nodiscard]] virtual bool cluster() const = 0; diff --git a/src/xrpld/overlay/PeerSet.h b/src/xrpld/overlay/PeerSet.h index 4670ec9783..ffba7932de 100644 --- a/src/xrpld/overlay/PeerSet.h +++ b/src/xrpld/overlay/PeerSet.h @@ -15,16 +15,17 @@ namespace xrpl { -/** Supports data retrieval by managing a set of peers. - - When desired data (such as a ledger or a transaction set) - is missing locally it can be obtained by querying connected - peers. This class manages common aspects of the retrieval. - Callers maintain the set by adding and removing peers depending - on whether the peers have useful information. - - The data is represented by its hash. -*/ +/** + * Supports data retrieval by managing a set of peers. + * + * When desired data (such as a ledger or a transaction set) + * is missing locally it can be obtained by querying connected + * peers. This class manages common aspects of the retrieval. + * Callers maintain the set by adding and removing peers depending + * on whether the peers have useful information. + * + * The data is represented by its hash. + */ class PeerSet { public: @@ -42,7 +43,9 @@ public: std::function const&)> hasItem, std::function const&)> onPeerAdded) = 0; - /** send a message */ + /** + * send a message + */ template void sendRequest(MessageType const& message, std::shared_ptr const& peer) @@ -56,7 +59,9 @@ public: protocol::MessageType type, std::shared_ptr const& peer) = 0; - /** get the set of ids of previously added peers */ + /** + * get the set of ids of previously added peers + */ [[nodiscard]] virtual std::set const& getPeerIds() const = 0; }; diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index a29020e03a..4b5026414b 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -38,13 +38,17 @@ namespace xrpl::reduce_relay { template class Slots; -/** Peer's State */ +/** + * Peer's State + */ enum class PeerState : uint8_t { Counting, // counting messages Selected, // selected to relay, counting if Slot in Counting Squelched, // squelched, doesn't relay }; -/** Slot's State */ +/** + * Slot's State + */ enum class SlotState : uint8_t { Counting, // counting messages Selected, // peers selected, stop counting @@ -57,22 +61,26 @@ epoch(TP const& t) return std::chrono::duration_cast(t.time_since_epoch()); } -/** Abstract class. Declares squelch and unsquelch handlers. +/** + * Abstract class. Declares squelch and unsquelch handlers. * OverlayImpl inherits from this class. Motivation is * for easier unit tests to facilitate on the fly - * changing callbacks. */ + * changing callbacks. + */ class SquelchHandler { public: virtual ~SquelchHandler() = default; - /** Squelch handler + /** + * Squelch handler * @param validator Public key of the source validator * @param id Peer's id to squelch * @param duration Squelch duration in seconds */ virtual void squelch(PublicKey const& validator, Peer::id_t id, std::uint32_t duration) const = 0; - /** Unsquelch handler + /** + * Unsquelch handler * @param validator Public key of the source validator * @param id Peer's id to unsquelch */ @@ -101,9 +109,10 @@ private: // a callback to report ignored squelches using ignored_squelch_callback = std::function; - /** Constructor - * @param journal Journal for logging + /** + * Constructor * @param handler Squelch/Unsquelch implementation + * @param journal Journal for logging * @param maxSelectedPeers the maximum number of peers to be selected as * validator message source */ @@ -115,7 +124,8 @@ private: { } - /** Update peer info. If the message is from a new + /** + * Update peer info. If the message is from a new * peer or from a previously expired squelched peer then switch * the peer's and slot's state to Counting. If time of last * selection round is > 2 * kMaxUnsquelchExpireDefault then switch the @@ -141,7 +151,8 @@ private: protocol::MessageType type, ignored_squelch_callback callback); - /** Handle peer deletion when a peer disconnects. + /** + * Handle peer deletion when a peer disconnects. * If the peer is in Selected state then * call unsquelch handler for every peer in squelched state and reset * every peer's state to Counting. Switch Slot's state to Counting. @@ -154,39 +165,51 @@ private: void deletePeer(PublicKey const& validator, id_t id, bool erase); - /** Get the time of the last peer selection round */ + /** + * Get the time of the last peer selection round + */ [[nodiscard]] time_point const& getLastSelected() const { return lastSelected_; } - /** Return number of peers in state */ + /** + * Return number of peers in state + */ [[nodiscard]] std::uint16_t inState(PeerState state) const; - /** Return number of peers not in state */ + /** + * Return number of peers not in state + */ [[nodiscard]] std::uint16_t notInState(PeerState state) const; - /** Return Slot's state */ + /** + * Return Slot's state + */ [[nodiscard]] SlotState getState() const { return state_; } - /** Return selected peers */ + /** + * Return selected peers + */ [[nodiscard]] std::set getSelected() const; - /** Get peers info. Return map of peer's state, count, squelch + /** + * Get peers info. Return map of peer's state, count, squelch * expiration milsec, and last message time milsec. */ [[nodiscard]] std::unordered_map> getPeers() const; - /** Check if peers stopped relaying messages. If a peer is + /** + * Check if peers stopped relaying messages. If a peer is * selected peer then call unsquelch handler for all * currently squelched peers and switch the slot to * Counting state. @@ -195,7 +218,8 @@ private: void deleteIdlePeer(PublicKey const& validator); - /** Get random squelch duration between kMinUnsquelchExpire and + /** + * Get random squelch duration between kMinUnsquelchExpire and * min(max(kMaxUnsquelchExpireDefault, kSquelchPerPeer * npeers), * kMaxUnsquelchExpirePeers) * @param npeers number of peers that can be squelched in the Slot @@ -204,15 +228,21 @@ private: getSquelchDuration(std::size_t npeers); private: - /** Reset counts of peers in Selected or Counting state */ + /** + * Reset counts of peers in Selected or Counting state + */ void resetCounts(); - /** Initialize slot to Counting state */ + /** + * Initialize slot to Counting state + */ void initCounting(); - /** Data maintained for each peer */ + /** + * Data maintained for each peer + */ struct PeerInfo { PeerState state; // peer's state @@ -533,7 +563,8 @@ Slot::getPeers() const return r; } -/** Slots is a container for validator's Slot and handles Slot update +/** + * Slots is a container for validator's Slot and handles Slot update * when a message is received from a validator. It also handles Slot aging * and checks for peers which are disconnected or stopped relaying the messages. */ @@ -564,14 +595,18 @@ public: } ~Slots() = default; - /** Check if base squelching feature is enabled and ready */ + /** + * Check if base squelching feature is enabled and ready + */ bool baseSquelchReady() { return baseSquelchEnabled_ && reduceRelayReady(); } - /** Check if reduce_relay::kWaitOnBootup time passed since startup */ + /** + * Check if reduce_relay::kWaitOnBootup time passed since startup + */ bool reduceRelayReady() { @@ -584,7 +619,8 @@ public: return reduceRelayReady_; } - /** Calls Slot::update of Slot associated with the validator, with a noop + /** + * Calls Slot::update of Slot associated with the validator, with a noop * callback. * @param key Message's hash * @param validator Validator's public key @@ -601,7 +637,8 @@ public: updateSlotAndSquelch(key, validator, id, type, []() {}); } - /** Calls Slot::update of Slot associated with the validator. + /** + * Calls Slot::update of Slot associated with the validator. * @param key Message's hash * @param validator Validator's public key * @param id Peer's id which received the message @@ -616,13 +653,16 @@ public: protocol::MessageType type, Slot::ignored_squelch_callback callback); - /** Check if peers stopped relaying messages + /** + * Check if peers stopped relaying messages * and if slots stopped receiving messages from the validator. */ void deleteIdlePeers(); - /** Return number of peers in state */ + /** + * Return number of peers in state + */ [[nodiscard]] std::optional inState(PublicKey const& validator, PeerState state) const { @@ -632,7 +672,9 @@ public: return {}; } - /** Return number of peers not in state */ + /** + * Return number of peers not in state + */ [[nodiscard]] std::optional notInState(PublicKey const& validator, PeerState state) const { @@ -642,7 +684,9 @@ public: return {}; } - /** Return true if Slot is in state */ + /** + * Return true if Slot is in state + */ [[nodiscard]] bool inState(PublicKey const& validator, SlotState state) const { @@ -652,7 +696,9 @@ public: return false; } - /** Get selected peers */ + /** + * Get selected peers + */ std::set getSelected(PublicKey const& validator) { @@ -662,7 +708,8 @@ public: return {}; } - /** Get peers info. Return map of peer's state, count, and squelch + /** + * Get peers info. Return map of peer's state, count, and squelch * expiration milliseconds. */ std::unordered_map> @@ -674,7 +721,9 @@ public: return {}; } - /** Get Slot's state */ + /** + * Get Slot's state + */ std::optional getState(PublicKey const& validator) { @@ -684,7 +733,8 @@ public: return {}; } - /** Called when a peer is deleted. If the peer was selected to be the + /** + * Called when a peer is deleted. If the peer was selected to be the * source of messages from the validator then squelched peers have to be * unsquelched. * @param id Peer's id @@ -694,9 +744,11 @@ public: deletePeer(id_t id, bool erase); private: - /** Add message/peer if have not seen this message + /** + * Add message/peer if have not seen this message * from the peer. A message is aged after IDLED seconds. - * Return true if added */ + * Return true if added + */ bool addPeerMessage(uint256 const& key, id_t id); diff --git a/src/xrpld/overlay/Squelch.h b/src/xrpld/overlay/Squelch.h index 0485c91c81..6899bf39f0 100644 --- a/src/xrpld/overlay/Squelch.h +++ b/src/xrpld/overlay/Squelch.h @@ -11,7 +11,9 @@ namespace xrpl::reduce_relay { -/** Maintains squelching of relaying messages from validators */ +/** + * Maintains squelching of relaying messages from validators + */ template class Squelch { @@ -23,7 +25,8 @@ public: } virtual ~Squelch() = default; - /** Squelch validation/proposal relaying for the validator + /** + * Squelch validation/proposal relaying for the validator * @param validator The validator's public key * @param squelchDuration Squelch duration in seconds * @return false if invalid squelch duration @@ -31,13 +34,15 @@ public: bool addSquelch(PublicKey const& validator, std::chrono::seconds const& squelchDuration); - /** Remove the squelch + /** + * Remove the squelch * @param validator The validator's public key */ void removeSquelch(PublicKey const& validator); - /** Remove expired squelch + /** + * Remove expired squelch * @param validator Validator's public key * @return true if removed or doesn't exist, false if still active */ @@ -45,8 +50,10 @@ public: expireSquelch(PublicKey const& validator); private: - /** Maintains the list of squelched relaying to downstream peers. - * Expiration time is included in the TMSquelch message. */ + /** + * Maintains the list of squelched relaying to downstream peers. + * Expiration time is included in the TMSquelch message. + */ hash_map squelched_; beast::Journal const journal_; }; diff --git a/src/xrpld/overlay/detail/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h index bed3f672be..d7836e3c84 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -19,7 +19,9 @@ namespace xrpl { -/** Manages an outbound connection attempt. */ +/** + * Manages an outbound connection attempt. + */ class ConnectAttempt : public OverlayImpl::Child, public std::enable_shared_from_this { diff --git a/src/xrpld/overlay/detail/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp index 39fd93f1d4..a860d2d604 100644 --- a/src/xrpld/overlay/detail/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -118,20 +118,21 @@ makeFeaturesResponseHeader( return str.str(); } -/** Hashes the latest finished message from an SSL stream. - - @param ssl the session to get the message from. - @param get a pointer to the function to call to retrieve the finished - message. This can be either: - - `SSL_get_finished` or - - `SSL_get_peer_finished`. - @return `true` if successful, `false` otherwise. - - @note This construct is non-standard. There are potential "standard" - alternatives that should be considered. For a discussion, on - this topic, see https://github.com/openssl/openssl/issues/5509 and - https://github.com/XRPLF/rippled/issues/2413. -*/ +/** + * Hashes the latest finished message from an SSL stream. + * + * @param ssl the session to get the message from. + * @param get a pointer to the function to call to retrieve the finished + * message. This can be either: + * - `SSL_get_finished` or + * - `SSL_get_peer_finished`. + * @return `true` if successful, `false` otherwise. + * + * @note This construct is non-standard. There are potential "standard" + * alternatives that should be considered. For a discussion, on + * this topic, see https://github.com/openssl/openssl/issues/5509 and + * https://github.com/XRPLF/rippled/issues/2413. + */ static std::optional> hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t)) { diff --git a/src/xrpld/overlay/detail/Handshake.h b/src/xrpld/overlay/detail/Handshake.h index 6dcc06bbf1..9a4e5ba507 100644 --- a/src/xrpld/overlay/detail/Handshake.h +++ b/src/xrpld/overlay/detail/Handshake.h @@ -26,19 +26,21 @@ using request_type = boost::beast::http::request using http_request_type = boost::beast::http::request; using http_response_type = boost::beast::http::response; -/** Computes a shared value based on the SSL connection state. - - When there is no man in the middle, both sides will compute the same - value. In the presence of an attacker, the computed values will be - different. - - @param ssl the SSL/TLS connection state. - @return A 256-bit value on success; an unseated optional otherwise. -*/ +/** + * Computes a shared value based on the SSL connection state. + * + * When there is no man in the middle, both sides will compute the same + * value. In the presence of an attacker, the computed values will be + * different. + * + * @param ssl the SSL/TLS connection state. + * @return A 256-bit value on success; an unseated optional otherwise. + */ std::optional makeSharedValue(stream_type& ssl, beast::Journal journal); -/** Insert fields headers necessary for upgrading the link to the peer protocol. +/** + * Insert fields headers necessary for upgrading the link to the peer protocol. */ void buildHandshake( @@ -49,17 +51,18 @@ buildHandshake( beast::IP::Address remoteIp, Application& app); -/** Validate header fields necessary for upgrading the link to the peer - protocol. - - This performs critical security checks that ensure that prevent - MITM attacks on our peer-to-peer links and that the remote peer - has the private keys that correspond to the public identity it - claims. - - @return The public key of the remote peer. - @throw A class derived from std::exception. -*/ +/** + * Validate header fields necessary for upgrading the link to the peer + * protocol. + * + * This performs critical security checks that ensure that prevent + * MITM attacks on our peer-to-peer links and that the remote peer + * has the private keys that correspond to the public identity it + * claims. + * + * @return The public key of the remote peer. + * @throws A class derived from std::exception. + */ PublicKey verifyHandshake( boost::beast::http::fields const& headers, @@ -69,16 +72,17 @@ verifyHandshake( beast::IP::Address remote, Application& app); -/** Make outbound http request - - @param crawlPublic if true then server's IP/Port are included in crawl - @param comprEnabled if true then compression feature is enabled - @param ledgerReplayEnabled if true then ledger-replay feature is enabled - @param txReduceRelayEnabled if true then transaction reduce-relay feature is - enabled - @param vpReduceRelayEnabled if true then validation/proposal reduce-relay - feature is enabled - @return http request with empty body +/** + * Make outbound http request + * + * @param crawlPublic if true then server's IP/Port are included in crawl + * @param comprEnabled if true then compression feature is enabled + * @param ledgerReplayEnabled if true then ledger-replay feature is enabled + * @param txReduceRelayEnabled if true then transaction reduce-relay feature is + * enabled + * @param vpReduceRelayEnabled if true then validation/proposal reduce-relay + * feature is enabled + * @return http request with empty body */ request_type makeRequest( @@ -88,17 +92,18 @@ makeRequest( bool txReduceRelayEnabled, bool vpReduceRelayEnabled); -/** Make http response - - @param crawlPublic if true then server's IP/Port are included in crawl - @param req incoming http request - @param publicIp server's public IP - @param remoteIp peer's IP - @param sharedValue shared value based on the SSL connection state - @param networkID specifies what network we intend to connect to - @param version supported protocol version - @param app Application's reference to access some common properties - @return http response +/** + * Make http response + * + * @param crawlPublic if true then server's IP/Port are included in crawl + * @param req incoming http request + * @param publicIp server's public IP + * @param remoteIp peer's IP + * @param sharedValue shared value based on the SSL connection state + * @param networkID specifies what network we intend to connect to + * @param version supported protocol version + * @param app Application's reference to access some common properties + * @return http response */ http_response_type makeResponse( @@ -127,22 +132,24 @@ static constexpr char kFeatureLedgerReplay[] = "ledgerreplay"; static constexpr char kDelimFeature[] = ";"; static constexpr char kDelimValue[] = ","; -/** Get feature's header value - @param headers request/response header - @param feature name - @return seated optional with feature's value if the feature - is found in the header, unseated optional otherwise +/** + * Get feature's header value + * @param headers request/response header + * @param feature name + * @return seated optional with feature's value if the feature + * is found in the header, unseated optional otherwise */ std::optional getFeatureValue(boost::beast::http::fields const& headers, std::string const& feature); -/** Check if a feature's value is equal to the specified value - @param headers request/response header - @param feature to check - @param value of the feature to check, must be a single value; i.e. not - value1,value2... - @return true if the feature's value matches the specified value, false if - doesn't match or the feature is not found in the header +/** + * Check if a feature's value is equal to the specified value + * @param headers request/response header + * @param feature to check + * @param value of the feature to check, must be a single value; i.e. not + * value1,value2... + * @return true if the feature's value matches the specified value, false if + * doesn't match or the feature is not found in the header */ bool isFeatureValue( @@ -150,23 +157,25 @@ isFeatureValue( std::string const& feature, std::string const& value); -/** Check if a feature is enabled - @param headers request/response header - @param feature to check - @return true if enabled +/** + * Check if a feature is enabled + * @param headers request/response header + * @param feature to check + * @return true if enabled */ bool featureEnabled(boost::beast::http::fields const& headers, std::string const& feature); -/** Check if a feature should be enabled for a peer. The feature - is enabled if its configured value is true and the http header - has the specified feature value. - @tparam headers request (inbound) or response (outbound) header - @param request http headers - @param feature to check - @param config feature's configuration value - @param value feature's value to check in the headers - @return true if the feature is enabled +/** + * Check if a feature should be enabled for a peer. The feature + * is enabled if its configured value is true and the http header + * has the specified feature value. + * @tparam Headers request (inbound) or response (outbound) header + * @param request http headers + * @param feature to check + * @param value feature's value to check in the headers + * @param config feature's configuration value + * @return true if the feature is enabled */ template bool @@ -179,7 +188,9 @@ peerFeatureEnabled( return config && isFeatureValue(request, feature, value); } -/** Wrapper for enable(1)/disable type(0) of feature */ +/** + * Wrapper for enable(1)/disable type(0) of feature + */ template bool peerFeatureEnabled(Headers const& request, std::string const& feature, bool config) @@ -187,14 +198,15 @@ peerFeatureEnabled(Headers const& request, std::string const& feature, bool conf return config && peerFeatureEnabled(request, feature, "1", config); } -/** Make request header X-Protocol-Ctl value with supported features - @param comprEnabled if true then compression feature is enabled - @param ledgerReplayEnabled if true then ledger-replay feature is enabled - @param txReduceRelayEnabled if true then transaction reduce-relay feature is - enabled - @param vpReduceRelayEnabled if true then validation/proposal reduce-relay - base squelch feature is enabled - @return X-Protocol-Ctl header value +/** + * Make request header X-Protocol-Ctl value with supported features + * @param comprEnabled if true then compression feature is enabled + * @param ledgerReplayEnabled if true then ledger-replay feature is enabled + * @param txReduceRelayEnabled if true then transaction reduce-relay feature is + * enabled + * @param vpReduceRelayEnabled if true then validation/proposal reduce-relay + * base squelch feature is enabled + * @return X-Protocol-Ctl header value */ std::string makeFeaturesRequestHeader( @@ -203,18 +215,19 @@ makeFeaturesRequestHeader( bool txReduceRelayEnabled, bool vpReduceRelayEnabled); -/** Make response header X-Protocol-Ctl value with supported features. - If the request has a feature that we support enabled - and the feature's configuration is enabled then enable this feature in - the response header. - @param header request's header - @param comprEnabled if true then compression feature is enabled - @param ledgerReplayEnabled if true then ledger-replay feature is enabled - @param txReduceRelayEnabled if true then transaction reduce-relay feature is - enabled - @param vpReduceRelayEnabled if true then validation/proposal reduce-relay - base squelch feature is enabled - @return X-Protocol-Ctl header value +/** + * Make response header X-Protocol-Ctl value with supported features. + * If the request has a feature that we support enabled + * and the feature's configuration is enabled then enable this feature in + * the response header. + * @param header request's header + * @param comprEnabled if true then compression feature is enabled + * @param ledgerReplayEnabled if true then ledger-replay feature is enabled + * @param txReduceRelayEnabled if true then transaction reduce-relay feature is + * enabled + * @param vpReduceRelayEnabled if true then validation/proposal reduce-relay + * base squelch feature is enabled + * @return X-Protocol-Ctl header value */ std::string makeFeaturesResponseHeader( diff --git a/src/xrpld/overlay/detail/Message.cpp b/src/xrpld/overlay/detail/Message.cpp index 120b34c78c..c6e0511515 100644 --- a/src/xrpld/overlay/detail/Message.cpp +++ b/src/xrpld/overlay/detail/Message.cpp @@ -127,41 +127,42 @@ Message::compress() } } -/** Set payload header - - The header is a variable-sized structure that contains information about - the type of the message and the length and encoding of the payload. - - The first bit determines whether a message is compressed or uncompressed; - for compressed messages, the next three bits identify the compression - algorithm. - - All multi-byte values are represented in big endian. - - For uncompressed messages (6 bytes), numbering bits from left to right: - - - The first 6 bits are set to 0. - - The next 26 bits represent the payload size. - - The remaining 16 bits represent the message type. - - For compressed messages (10 bytes), numbering bits from left to right: - - - The first 32 bits, together, represent the compression algorithm - and payload size: - - The first bit is set to 1 to indicate the message is compressed. - - The next 3 bits indicate the compression algorithm. - - The next 2 bits are reserved at this time and set to 0. - - The remaining 26 bits represent the payload size. - - The next 16 bits represent the message type. - - The remaining 32 bits are the uncompressed message size. - - The maximum size of a message at this time is 64 MB. Messages larger than - this will be dropped and the recipient may, at its option, sever the link. - - @note While nominally a part of the wire protocol, the framing is subject - to change; future versions of the code may negotiate the use of - substantially different framing. -*/ +/** + * Set payload header + * + * The header is a variable-sized structure that contains information about + * the type of the message and the length and encoding of the payload. + * + * The first bit determines whether a message is compressed or uncompressed; + * for compressed messages, the next three bits identify the compression + * algorithm. + * + * All multi-byte values are represented in big endian. + * + * For uncompressed messages (6 bytes), numbering bits from left to right: + * + * - The first 6 bits are set to 0. + * - The next 26 bits represent the payload size. + * - The remaining 16 bits represent the message type. + * + * For compressed messages (10 bytes), numbering bits from left to right: + * + * - The first 32 bits, together, represent the compression algorithm + * and payload size: + * - The first bit is set to 1 to indicate the message is compressed. + * - The next 3 bits indicate the compression algorithm. + * - The next 2 bits are reserved at this time and set to 0. + * - The remaining 26 bits represent the payload size. + * - The next 16 bits represent the message type. + * - The remaining 32 bits are the uncompressed message size. + * + * The maximum size of a message at this time is 64 MB. Messages larger than + * this will be dropped and the recipient may, at its option, sever the link. + * + * @note While nominally a part of the wire protocol, the framing is subject + * to change; future versions of the code may negotiate the use of + * substantially different framing. + */ void Message::setHeader( std::uint8_t* in, diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index b7af3b6ace..6a6a6edace 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -624,11 +624,12 @@ OverlayImpl::onWrite(beast::PropertyStream::Map& stream) } //------------------------------------------------------------------------------ -/** A peer has connected successfully - This is called after the peer handshake has been completed and during - peer activation. At this point, the peer address and the public key - are known. -*/ +/** + * A peer has connected successfully + * This is called after the peer handshake has been completed and during + * peer activation. At this point, the peer address and the public key + * are known. + */ void OverlayImpl::activate(std::shared_ptr const& peer) { @@ -725,10 +726,11 @@ OverlayImpl::reportOutboundTraffic(TrafficCount::Category cat, int size) { traffic_.addCount(cat, false, size); } -/** The number of active peers on the network - Active peers are only those peers that have completed the handshake - and are running the XRPL protocol. -*/ +/** + * The number of active peers on the network + * Active peers are only those peers that have completed the handshake + * and are running the XRPL protocol. + */ std::size_t OverlayImpl::size() const { diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 83d5a81a89..092ac86a6d 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -194,14 +194,15 @@ public: PeerSequence getActivePeers() const override; - /** Get active peers excluding peers in toSkip. - @param toSkip peers to skip - @param active a number of active peers - @param disabled a number of peers with tx reduce-relay - feature disabled - @param enabledInSkip a number of peers with tx reduce-relay - feature enabled and in toSkip - @return active peers less peers in toSkip + /** + * Get active peers excluding peers in toSkip. + * @param toSkip peers to skip + * @param active a number of active peers + * @param disabled a number of peers with tx reduce-relay + * feature disabled + * @param enabledInSkip a number of peers with tx reduce-relay + * feature enabled and in toSkip + * @return active peers less peers in toSkip */ PeerSequence getActivePeers( @@ -251,11 +252,12 @@ public: void remove(std::shared_ptr const& slot); - /** Called when a peer has connected successfully - This is called after the peer handshake has been completed and during - peer activation. At this point, the peer address and the public key - are known. - */ + /** + * Called when a peer has connected successfully + * This is called after the peer handshake has been completed and during + * peer activation. At this point, the peer address and the public key + * are known. + */ void activate(std::shared_ptr const& peer); @@ -382,7 +384,8 @@ public: return setup_.networkID; } - /** Updates message count for validator/peer. Sends TMSquelch if the number + /** + * Updates message count for validator/peer. Sends TMSquelch if the number * of messages for N peers reaches threshold T. A message is counted * if a peer receives the message for the first time and if * the message has been relayed. @@ -398,7 +401,8 @@ public: std::set&& peers, protocol::MessageType type); - /** Overload to reduce allocation in case of single peer + /** + * Overload to reduce allocation in case of single peer */ void updateSlotAndSquelch( @@ -407,7 +411,8 @@ public: Peer::id_t peer, protocol::MessageType type); - /** Called when the peer is deleted. If the peer was selected to be the + /** + * Called when the peer is deleted. If the peer was selected to be the * source of messages from the validator then squelched peers have to be * unsquelched. * @param id Peer's id @@ -421,7 +426,9 @@ public: return txMetrics_.json(); } - /** Add tx reduce-relay metrics. */ + /** + * Add tx reduce-relay metrics. + */ template void addTxMetrics(Args... args) @@ -453,64 +460,72 @@ private: address_type remoteAddress, std::string const& msg); - /** Handles crawl requests. Crawl returns information about the - node and its peers so crawlers can map the network. - - @return true if the request was handled. - */ + /** + * Handles crawl requests. Crawl returns information about the + * node and its peers so crawlers can map the network. + * + * @return true if the request was handled. + */ bool processCrawl(http_request_type const& req, Handoff& handoff); - /** Handles validator list requests. - Using a /vl/ URL, will retrieve the - latest validator list (or UNL) that this node has for that - public key, if the node trusts that public key. - - @return true if the request was handled. - */ + /** + * Handles validator list requests. + * Using a /vl/ URL, will retrieve the + * latest validator list (or UNL) that this node has for that + * public key, if the node trusts that public key. + * + * @return true if the request was handled. + */ bool processValidatorList(http_request_type const& req, Handoff& handoff); - /** Handles health requests. Health returns information about the - health of the node. - - @return true if the request was handled. - */ + /** + * Handles health requests. Health returns information about the + * health of the node. + * + * @return true if the request was handled. + */ bool processHealth(http_request_type const& req, Handoff& handoff); - /** Handles non-peer protocol requests. - - @return true if the request was handled. - */ + /** + * Handles non-peer protocol requests. + * + * @return true if the request was handled. + */ bool processRequest(http_request_type const& req, Handoff& handoff); - /** Returns information about peers on the overlay network. - Reported through the /crawl API - Controlled through the config section [crawl] overlay=[0|1] - */ + /** + * Returns information about peers on the overlay network. + * Reported through the /crawl API + * Controlled through the config section [crawl] overlay=[0|1] + */ json::Value getOverlayInfo() const; - /** Returns information about the local server. - Reported through the /crawl API - Controlled through the config section [crawl] server=[0|1] - */ + /** + * Returns information about the local server. + * Reported through the /crawl API + * Controlled through the config section [crawl] server=[0|1] + */ json::Value getServerInfo(); - /** Returns information about the local server's performance counters. - Reported through the /crawl API - Controlled through the config section [crawl] counts=[0|1] - */ + /** + * Returns information about the local server's performance counters. + * Reported through the /crawl API + * Controlled through the config section [crawl] counts=[0|1] + */ json::Value getServerCounts(); - /** Returns information about the local server's UNL. - Reported through the /crawl API - Controlled through the config section [crawl] unl=[0|1] - */ + /** + * Returns information about the local server's UNL. + * Reported through the /crawl API + * Controlled through the config section [crawl] unl=[0|1] + */ json::Value getUnlInfo(); @@ -537,12 +552,16 @@ private: void sendEndpoints(); - /** Send once a second transactions' hashes aggregated by peers. */ + /** + * Send once a second transactions' hashes aggregated by peers. + */ void sendTxQueue() const; - /** Check if peers stopped relaying messages - * and if slots stopped receiving messages from the validator */ + /** + * Check if peers stopped relaying messages + * and if slots stopped receiving messages from the validator + */ void deleteIdlePeers(); diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 4e42d46f46..8838970b5f 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -105,10 +105,14 @@ using namespace std::chrono_literals; namespace xrpl { namespace { -/** The threshold above which we treat a peer connection as high latency */ +/** + * The threshold above which we treat a peer connection as high latency + */ constexpr std::chrono::milliseconds kPeerHighLatency{300}; -/** How often we PING the peer to check for latency and sendq probe */ +/** + * How often we PING the peer to check for latency and sendq probe + */ constexpr std::chrono::seconds kPeerTimerInterval{60}; } // namespace diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index e9ef948d48..ea6eccd656 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -64,7 +64,9 @@ class SHAMap; class PeerImp : public Peer, public std::enable_shared_from_this, public OverlayImpl::Child { public: - /** Whether the peer's view of the ledger converges or diverges from ours */ + /** + * Whether the peer's view of the ledger converges or diverges from ours + */ enum class Tracking { Diverged, Unknown, Converged }; private: @@ -249,7 +251,9 @@ public: PeerImp& operator=(PeerImp const&) = delete; - /** Create an active incoming peer from an established ssl connection. */ + /** + * Create an active incoming peer from an established ssl connection. + */ PeerImp( Application& app, id_t id, @@ -261,7 +265,9 @@ public: std::unique_ptr&& streamPtr, OverlayImpl& overlay); - /** Create outgoing, handshaked peer. */ + /** + * Create outgoing, handshaked peer. + */ // VFALCO legacyPublicKey should be implied by the Slot template PeerImp( @@ -305,23 +311,29 @@ public: void send(std::shared_ptr const& m) override; - /** Send aggregated transactions' hashes */ + /** + * Send aggregated transactions' hashes + */ void sendTxQueue() override; - /** Add transaction's hash to the transactions' hashes queue - @param hash transaction's hash + /** + * Add transaction's hash to the transactions' hashes queue + * @param hash transaction's hash */ void addTxQueue(uint256 const& hash) override; - /** Remove transaction's hash from the transactions' hashes queue - @param hash transaction's hash + /** + * Remove transaction's hash from the transactions' hashes queue + * @param hash transaction's hash */ void removeTxQueue(uint256 const& hash) override; - /** Send a set of PeerFinder endpoints as a protocol message. */ + /** + * Send a set of PeerFinder endpoints as a protocol message. + */ template void sendEndpoints(FwdIt first, FwdIt last) @@ -347,16 +359,19 @@ public: return id_; } - /** Returns `true` if this connection will publicly share its IP address. */ + /** + * Returns `true` if this connection will publicly share its IP address. + */ bool crawl() const; bool cluster() const override; - /** Check if the peer is tracking - @param validationSeq The ledger sequence of a recently-validated ledger - */ + /** + * Check if the peer is tracking + * @param validationSeq The ledger sequence of a recently-validated ledger + */ void checkTracking(std::uint32_t validationSeq); @@ -369,7 +384,9 @@ public: return publicKey_; } - /** Return the version of xrpld that the peer is running, if reported. */ + /** + * Return the version of xrpld that the peer is running, if reported. + */ std::string getVersion() const; @@ -504,17 +521,18 @@ private: void onWriteMessage(error_code ec, std::size_t bytesTransferred); - /** Called from onMessage(TMTransaction(s)). - @param m Transaction protocol message - @param eraseTxQueue is true when called from onMessage(TMTransaction) - and is false when called from onMessage(TMTransactions). If true then - the transaction hash is erased from txQueue_. Don't need to erase from - the queue when called from onMessage(TMTransactions) because this - message is a response to the missing transactions request and the queue - would not have any of these transactions. - @param batch is false when called from onMessage(TMTransaction) - and is true when called from onMessage(TMTransactions). If true, then the - transaction is part of a batch, and should not be charged an extra fee. + /** + * Called from onMessage(TMTransaction(s)). + * @param m Transaction protocol message + * @param eraseTxQueue is true when called from onMessage(TMTransaction) + * and is false when called from onMessage(TMTransactions). If true then + * the transaction hash is erased from txQueue_. Don't need to erase from + * the queue when called from onMessage(TMTransactions) because this + * message is a response to the missing transactions request and the queue + * would not have any of these transactions. + * @param batch is false when called from onMessage(TMTransaction) + * and is true when called from onMessage(TMTransactions). If true, then the + * transaction is part of a batch, and should not be charged an extra fee. */ void handleTransaction( @@ -522,10 +540,11 @@ private: bool eraseTxQueue, bool batch); - /** Handle protocol message with hashes of transactions that have not - been relayed by an upstream node down to its peers - request - transactions, which have not been relayed to this peer. - @param m protocol message with transactions' hashes + /** + * Handle protocol message with hashes of transactions that have not + * been relayed by an upstream node down to its peers - request + * transactions, which have not been relayed to this peer. + * @param m protocol message with transactions' hashes */ void handleHaveTransactions(std::shared_ptr const& m); @@ -623,9 +642,10 @@ private: std::uint32_t version, std::vector const& blobs); - /** Process peer's request to send missing transactions. The request is - sent in response to TMHaveTransactions. - @param packet protocol message containing missing transactions' hashes. + /** + * Process peer's request to send missing transactions. The request is + * sent in response to TMHaveTransactions. + * @param packet protocol message containing missing transactions' hashes. */ void doTransactions(std::shared_ptr const& packet); @@ -669,52 +689,55 @@ protected: // Production callers reach these members only via // `onMessage(TMGetObjectByHash)` → JobQueue → `processGetObjectByHash`. - /** Process a generic-query TMGetObjectByHash message. - - Dispatched from `onMessage(TMGetObjectByHash)` to the JobQueue - (`JtLedgerReq`) so synchronous NodeStore lookups do not block the - peer's I/O strand. Caps iteration at `Tuning::kHardMaxReplyNodes` - regardless of hit/miss outcome and applies differential pricing - via `computeGetObjectByHashFee()` after the fetch loop completes. - - @param m The protocol message containing requested object hashes. + /** + * Process a generic-query TMGetObjectByHash message. + * + * Dispatched from `onMessage(TMGetObjectByHash)` to the JobQueue + * (`JtLedgerReq`) so synchronous NodeStore lookups do not block the + * peer's I/O strand. Caps iteration at `Tuning::kHardMaxReplyNodes` + * regardless of hit/miss outcome and applies differential pricing + * via `computeGetObjectByHashFee()` after the fetch loop completes. + * + * @param m The protocol message containing requested object hashes. */ void processGetObjectByHash(std::shared_ptr const& m); - /** Compute the per-message resource charge for a TMGetObjectByHash - request based on how much work was actually performed. - - The charge has three components on top of the base - `Resource::kFeeModerateBurdenPeer`: - - per-hit lookup cost (cheap; usually served from cache) - - per-miss lookup cost (expensive node store seeks) - - request-size band surcharge (escalates abusive batch sizes) - - The first `Tuning::kFreeObjectsPerRequest` objects are free so - that legitimate `InboundLedger::getNeededHashes()` traffic - (at most 8 objects) is unaffected. - - @param requested Number of objects requested by the message. This - value is used for request-size pricing and may - exceed `Tuning::kHardMaxReplyNodes` when this - helper is called directly, even though processing - caps the iterations to `Tuning::kHardMaxReplyNodes`. - @param found Number of objects successfully returned in the - reply. - @return A `Resource::Charge` whose cost reflects the work performed. + /** + * Compute the per-message resource charge for a TMGetObjectByHash + * request based on how much work was actually performed. + * + * The charge has three components on top of the base + * `Resource::kFeeModerateBurdenPeer`: + * - per-hit lookup cost (cheap; usually served from cache) + * - per-miss lookup cost (expensive node store seeks) + * - request-size band surcharge (escalates abusive batch sizes) + * + * The first `Tuning::kFreeObjectsPerRequest` objects are free so + * that legitimate `InboundLedger::getNeededHashes()` traffic + * (at most 8 objects) is unaffected. + * + * @param requested Number of objects requested by the message. This + * value is used for request-size pricing and may + * exceed `Tuning::kHardMaxReplyNodes` when this + * helper is called directly, even though processing + * caps the iterations to `Tuning::kHardMaxReplyNodes`. + * @param found Number of objects successfully returned in the + * reply. + * @return A `Resource::Charge` whose cost reflects the work performed. */ static Resource::Charge computeGetObjectByHashFee(int const requested, int const found); - /** Read-only accessor for the accumulated peer-message charge. - - Exposed at `protected` scope so test subclasses can verify the - oversized-request rejection path (Layer 1) without invoking the - full JobQueue handler. Production callers should never read this back — - the value is consumed by `charge()`/`disconnect()` internally. - - @return The current `Resource::Charge` accumulated on `fee_`. + /** + * Read-only accessor for the accumulated peer-message charge. + * + * Exposed at `protected` scope so test subclasses can verify the + * oversized-request rejection path (Layer 1) without invoking the + * full JobQueue handler. Production callers should never read this back — + * the value is consumed by `charge()`/`disconnect()` internally. + * + * @return The current `Resource::Charge` accumulated on `fee_`. */ Resource::Charge currentFeeCharge() const diff --git a/src/xrpld/overlay/detail/PeerSet.cpp b/src/xrpld/overlay/detail/PeerSet.cpp index 463b68bb6c..61bede37fe 100644 --- a/src/xrpld/overlay/detail/PeerSet.cpp +++ b/src/xrpld/overlay/detail/PeerSet.cpp @@ -33,7 +33,9 @@ public: std::function const&)> hasItem, std::function const&)> onPeerAdded) override; - /** Send a message to one or all peers. */ + /** + * Send a message to one or all peers. + */ void sendRequest( ::google::protobuf::Message const& message, @@ -49,7 +51,9 @@ private: Application& app_; beast::Journal journal_; - /** The identifiers of the peers we are tracking. */ + /** + * The identifiers of the peers we are tracking. + */ std::set peers_; }; diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index 156fcfc104..ef1bc8cb2b 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -42,7 +42,9 @@ protocolMessageType(protocol::TMProofPathRequest const&) return protocol::mtPROOF_PATH_REQ; } -/** Returns the name of a protocol message given its type. */ +/** + * Returns the name of a protocol message given its type. + */ template std::string protocolMessageName(int type) @@ -101,25 +103,35 @@ namespace detail { struct MessageHeader { - /** The size of the message on the wire. - - @note This is the sum of sizes of the header and the payload. - */ + /** + * The size of the message on the wire. + * + * @note This is the sum of sizes of the header and the payload. + */ std::uint32_t totalWireSize = 0; - /** The size of the header associated with this message. */ + /** + * The size of the header associated with this message. + */ std::uint32_t headerSize = 0; - /** The size of the payload on the wire. */ + /** + * The size of the payload on the wire. + */ std::uint32_t payloadWireSize = 0; - /** Uncompressed message size if the message is compressed. */ + /** + * Uncompressed message size if the message is compressed. + */ std::uint32_t uncompressedSize = 0; - /** The type of the message. */ + /** + * The type of the message. + */ std::uint16_t messageType = 0; - /** Indicates which compression algorithm the payload is compressed with. + /** + * Indicates which compression algorithm the payload is compressed with. * Currently only lz4 is supported. If None then the message is not * compressed. */ @@ -140,14 +152,15 @@ buffersEnd(BufferSequence const& bufs) return boost::asio::buffers_iterator::end(bufs); } -/** Parse a message header - * @return a seated optional if the message header was successfully - * parsed. An unseated optional otherwise, in which case - * @param ec contains more information: - * - set to `errc::success` if not enough bytes were present - * - set to `errc::no_message` if a valid header was not present - * @bufs - sequence of input buffers, can't be empty - * @size input data size +/** + * Parse a message header. + * + * @param ec On failure, set to `errc::success` if not enough bytes were + * present, or `errc::no_message` if a valid header was not present. + * @param bufs Sequence of input buffers; can't be empty. + * @param size Input data size. + * @return A seated optional if the message header was successfully parsed, or + * an unseated optional otherwise (see @p ec). */ template std::optional @@ -294,18 +307,19 @@ invoke(MessageHeader const& header, Buffers const& buffers, Handler& handler) } // namespace detail -/** Calls the handler for up to one protocol message in the passed buffers. - - If there is insufficient data to produce a complete protocol - message, zero is returned for the number of bytes consumed. - - @param buffers The buffer that contains the data we've received - @param handler The handler that will be used to process the message - @param hint If possible, a hint as to the amount of data to read next. The - returned value MAY be zero, which means "no hint" - - @return The number of bytes consumed, or the error code if any. -*/ +/** + * Calls the handler for up to one protocol message in the passed buffers. + * + * If there is insufficient data to produce a complete protocol + * message, zero is returned for the number of bytes consumed. + * + * @param buffers The buffer that contains the data we've received + * @param handler The handler that will be used to process the message + * @param hint If possible, a hint as to the amount of data to read next. The + * returned value MAY be zero, which means "no hint" + * + * @return The number of bytes consumed, or the error code if any. + */ template std::pair invokeProtocolMessage(Buffers const& buffers, Handler& handler, std::size_t& hint) diff --git a/src/xrpld/overlay/detail/ProtocolVersion.cpp b/src/xrpld/overlay/detail/ProtocolVersion.cpp index 62660b0e01..347e59accb 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.cpp +++ b/src/xrpld/overlay/detail/ProtocolVersion.cpp @@ -19,11 +19,12 @@ namespace xrpl { -/** The list of protocol versions we speak and we prefer to use. - - @note The list must be sorted in strictly ascending order (and so - it may not contain any duplicates!) -*/ +/** + * The list of protocol versions we speak and we prefer to use. + * + * @note The list must be sorted in strictly ascending order (and so + * it may not contain any duplicates!) + */ constexpr ProtocolVersion const kSupportedProtocolList[]{ {2, 1}, diff --git a/src/xrpld/overlay/detail/ProtocolVersion.h b/src/xrpld/overlay/detail/ProtocolVersion.h index d6d9da7ad3..b56871318a 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.h +++ b/src/xrpld/overlay/detail/ProtocolVersion.h @@ -10,11 +10,12 @@ namespace xrpl { -/** Represents a particular version of the peer-to-peer protocol. - - The protocol is represented as two pairs of 16-bit integers; a major - and a minor. - * */ +/** + * Represents a particular version of the peer-to-peer protocol. + * + * The protocol is represented as two pairs of 16-bit integers; a major + * and a minor. + */ using ProtocolVersion = std::pair; constexpr ProtocolVersion @@ -23,37 +24,48 @@ makeProtocol(std::uint16_t major, std::uint16_t minor) return {major, minor}; } -/** Print a protocol version a human-readable string. */ +/** + * Print a protocol version a human-readable string. + */ std::string to_string(ProtocolVersion const& p); -/** Parse a set of protocol versions. - - Given a comma-separated string, extract and return all those that look - like valid protocol versions (i.e. XRPL/2.0 and later). Strings that are - not parsable as valid protocol strings are excluded from the result set. - - @return A list of all apparently valid protocol versions. - - @note The returned list of protocol versions is guaranteed to contain - no duplicates and will be sorted in ascending protocol order. -*/ +/** + * Parse a set of protocol versions. + * + * Given a comma-separated string, extract and return all those that look + * like valid protocol versions (i.e. XRPL/2.0 and later). Strings that are + * not parsable as valid protocol strings are excluded from the result set. + * + * @return A list of all apparently valid protocol versions. + * + * @note The returned list of protocol versions is guaranteed to contain + * no duplicates and will be sorted in ascending protocol order. + */ std::vector parseProtocolVersions(boost::beast::string_view const& s); -/** Given a list of supported protocol versions, choose the one we prefer. */ +/** + * Given a list of supported protocol versions, choose the one we prefer. + */ std::optional negotiateProtocolVersion(std::vector const& versions); -/** Given a list of supported protocol versions, choose the one we prefer. */ +/** + * Given a list of supported protocol versions, choose the one we prefer. + */ std::optional negotiateProtocolVersion(boost::beast::string_view const& versions); -/** The list of all the protocol versions we support. */ +/** + * The list of all the protocol versions we support. + */ std::string const& supportedProtocolVersions(); -/** Determine whether we support a specific protocol version. */ +/** + * Determine whether we support a specific protocol version. + */ bool isProtocolSupported(ProtocolVersion const& v); diff --git a/src/xrpld/overlay/detail/TrafficCount.h b/src/xrpld/overlay/detail/TrafficCount.h index b96ee022d6..8dc8ddb08e 100644 --- a/src/xrpld/overlay/detail/TrafficCount.h +++ b/src/xrpld/overlay/detail/TrafficCount.h @@ -15,23 +15,23 @@ namespace xrpl { /** - TrafficCount is used to count ingress and egress wire bytes and number of - messages. The general intended usage is as follows: - 1. Determine the message category by callin TrafficCount::categorize - 2. Increment the counters for incoming or outgoing traffic by calling - TrafficCount::addCount - 3. Optionally, TrafficCount::addCount can be called at any time to - increment additional traffic categories, not captured by - TrafficCount::categorize. - - There are two special categories: - 1. category::total - this category is used to report the total traffic - amount. It should be incremented once just after receiving a new message, and - once just before sending a message to a peer. Messages whose category is not - in TrafficCount::categorize are not included in the total. - 2. category::unknown - this category is used to report traffic for - messages of unknown type. -*/ + * TrafficCount is used to count ingress and egress wire bytes and number of + * messages. The general intended usage is as follows: + * 1. Determine the message category by callin TrafficCount::categorize + * 2. Increment the counters for incoming or outgoing traffic by calling + * TrafficCount::addCount + * 3. Optionally, TrafficCount::addCount can be called at any time to + * increment additional traffic categories, not captured by + * TrafficCount::categorize. + * + * There are two special categories: + * 1. category::total - this category is used to report the total traffic + * amount. It should be incremented once just after receiving a new message, and + * once just before sending a message to a peer. Messages whose category is not + * in TrafficCount::categorize are not included in the total. + * 2. category::unknown - this category is used to report traffic for + * messages of unknown type. + */ class TrafficCount { public: @@ -186,7 +186,8 @@ public: TrafficCount() = default; - /** Given a protocol message, determine which traffic category it belongs to + /** + * Given a protocol message, determine which traffic category it belongs to */ static Category categorize( @@ -194,7 +195,9 @@ public: protocol::MessageType type, bool inbound); - /** Account for traffic associated with the given category */ + /** + * Account for traffic associated with the given category + */ void addCount(Category cat, bool inbound, int bytes) { @@ -219,9 +222,10 @@ public: } } - /** An up-to-date copy of all the counters - - @return an object which satisfies the requirements of Container + /** + * An up-to-date copy of all the counters + * + * @return an object which satisfies the requirements of Container */ [[nodiscard]] auto const& getCounts() const diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index 8357fcd130..5488fab07b 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -7,126 +7,161 @@ namespace xrpl::Tuning { -/** How many ledgers off a server can be and we will - still consider it converged */ +/** + * How many ledgers off a server can be and we will + * still consider it converged + */ static constexpr std::uint32_t kConvergedLedgerLimit = 24; -/** How many ledgers off a server has to be before we - consider it diverged */ +/** + * How many ledgers off a server has to be before we + * consider it diverged + */ static constexpr std::uint32_t kDivergedLedgerLimit = 128; -/** The soft cap on the number of ledger entries in a single reply. */ +/** + * The soft cap on the number of ledger entries in a single reply. + */ static constexpr auto kSoftMaxReplyNodes = 8192; -/** The hard cap on the number of ledger entries in a single reply. */ +/** + * The hard cap on the number of ledger entries in a single reply. + */ static constexpr auto kHardMaxReplyNodes = 12288; -/** How many timer intervals a sendq has to stay large before we disconnect */ +/** + * How many timer intervals a sendq has to stay large before we disconnect + */ static constexpr auto kSendqIntervals = 4; -/** How many messages on a send queue before we refuse queries */ +/** + * How many messages on a send queue before we refuse queries + */ static constexpr auto kDropSendQueue = 192; -/** How many messages we consider reasonable sustained on a send queue */ +/** + * How many messages we consider reasonable sustained on a send queue + */ static constexpr auto kTargetSendQueue = 128; -/** How often to log send queue size */ +/** + * How often to log send queue size + */ static constexpr auto kSendQueueLogFreq = 64; -/** How often we check for idle peers (seconds) */ +/** + * How often we check for idle peers (seconds) + */ static constexpr auto kCheckIdlePeers = 4; -/** The maximum number of levels to search */ +/** + * The maximum number of levels to search + */ static constexpr auto kMaxQueryDepth = 3; -/** Size of buffer used to read from the socket. */ +/** + * Size of buffer used to read from the socket. + */ constexpr std::size_t kReadBufferBytes = 16384; -/** TMGetObjectByHash differential pricing. +/** + * TMGetObjectByHash differential pricing. + * + * Honest peers ask for at most 8 hashes per call (the header, or up to + * 4 state + 4 tx hashes from `InboundLedger::getNeededHashes()`). The + * free tier covers them at zero cost. Beyond that, each lookup is billed: + * 'misses' cost much more than 'hits' because a miss does a node store seek + * while a hit is usually served from cache. On top of that, a size-band + * surcharge kicks in for larger requests so an attacker who crams a + * single message with thousands of hashes blows past + * `Resource::kDropThreshold` and gets disconnected. + * + * The numbers below are picked to keep three things true given + * `kDropThreshold = 25000`: + * + * - Honest traffic (<= 8 objects per request) is free. + * - A single all-miss request at `kHardMaxReplyNodes` (12288) costs + * more than the drop threshold, so an attacker gets dropped in one + * message. + * - A peer spamming 1024-object hit-only requests gets dropped in + * ~19 messages — fast enough to be useful, slow enough that an + * honest peer momentarily sending oversized requests has time to + * back off. + */ - Honest peers ask for at most 8 hashes per call (the header, or up to - 4 state + 4 tx hashes from `InboundLedger::getNeededHashes()`). The - free tier covers them at zero cost. Beyond that, each lookup is billed: - 'misses' cost much more than 'hits' because a miss does a node store seek - while a hit is usually served from cache. On top of that, a size-band - surcharge kicks in for larger requests so an attacker who crams a - single message with thousands of hashes blows past - `Resource::kDropThreshold` and gets disconnected. - - The numbers below are picked to keep three things true given - `kDropThreshold = 25000`: - - - Honest traffic (<= 8 objects per request) is free. - - A single all-miss request at `kHardMaxReplyNodes` (12288) costs - more than the drop threshold, so an attacker gets dropped in one - message. - - A peer spamming 1024-object hit-only requests gets dropped in - ~19 messages — fast enough to be useful, slow enough that an - honest peer momentarily sending oversized requests has time to - back off. */ - -/** How many objects a request can ask for before per-lookup billing - begins? - Twice the honest peak (8) so a peer that occasionally retries a hash - never trips pricing. Same value as `SHAMapInnerNode::kBranchFactor`; - that's a coincidence, not a requirement. */ +/** + * How many objects a request can ask for before per-lookup billing + * begins? + * Twice the honest peak (8) so a peer that occasionally retries a hash + * never trips pricing. Same value as `SHAMapInnerNode::kBranchFactor`; + * that's a coincidence, not a requirement. + */ static constexpr auto kFreeObjectsPerRequest = 16; -/** Cost of one cache-hit lookup. The unit; everything else is a - multiple of this. */ +/** + * Cost of one cache-hit lookup. The unit; everything else is a + * multiple of this. + */ static constexpr auto kCostPerLookupHit = 1; -/** Cost of one node-store miss, in units of `kCostPerLookupHit`. - - A miss does a node store disk seek; a hit usually comes from cache. - The 8x ratio is an order-of-magnitude guess at the latency gap on - SSD-backed nodes, not a measured number. The math only requires this - to be at least 2 — any smaller and a full-miss request at the hard - cap wouldn't trip the drop threshold. 8 leaves headroom: if - `kDropThreshold` goes up or `kHardMaxReplyNodes` comes down, the - drop-on-attack property still holds without a code change. */ +/** + * Cost of one node-store miss, in units of `kCostPerLookupHit`. + * + * A miss does a node store disk seek; a hit usually comes from cache. + * The 8x ratio is an order-of-magnitude guess at the latency gap on + * SSD-backed nodes, not a measured number. The math only requires this + * to be at least 2 — any smaller and a full-miss request at the hard + * cap wouldn't trip the drop threshold. 8 leaves headroom: if + * `kDropThreshold` goes up or `kHardMaxReplyNodes` comes down, the + * drop-on-attack property still holds without a code change. + */ static constexpr auto kCostPerLookupMiss = 8; -/** Size-band surcharges. Whichever band a request's size falls into, - its surcharge is added once on top of the per-lookup cost. - - The job of the surcharge is to make crossing a band edge feel like - a step, not a slope. With these values, the cost roughly doubles or triples at each cliff: - - n=64: costs 48 => n=65 costs 149 (~3x jump) - n=1024: costs 1108 => n=1025 costs 2009 (~2x jump) - - The 10x step between medium and large mirrors the ~16x step - between the band edges (64 -> 1024) so the cliff feels comparable - at both scales. +/** + * Size-band surcharges. Whichever band a request's size falls into, + * its surcharge is added once on top of the per-lookup cost. + * + * The job of the surcharge is to make crossing a band edge feel like + * a step, not a slope. With these values, the cost roughly doubles or triples at each cliff: + * + * n=64: costs 48 => n=65 costs 149 (~3x jump) + * n=1024: costs 1108 => n=1025 costs 2009 (~2x jump) + * + * The 10x step between medium and large mirrors the ~16x step + * between the band edges (64 -> 1024) so the cliff feels comparable + * at both scales. */ static constexpr auto kCostBandSmall = 0; static constexpr auto kCostBandMedium = 100; static constexpr auto kCostBandLarge = 1000; -/** How many hashes per type an honest peer asks for at a time. - - Matches the `4` passed to `neededStateHashes(4)` and - `neededTxHashes(4)` in `InboundLedger::getNeededHashes()`. Kept here - instead of imported from the ledger module so overlay stays - self-contained; if that `4` ever changes, update this in lockstep or - the band thresholds below will start charging honest peers. */ +/** + * How many hashes per type an honest peer asks for at a time. + * + * Matches the `4` passed to `neededStateHashes(4)` and + * `neededTxHashes(4)` in `InboundLedger::getNeededHashes()`. Kept here + * instead of imported from the ledger module so overlay stays + * self-contained; if that `4` ever changes, update this in lockstep or + * the band thresholds below will start charging honest peers. + */ static constexpr auto kLegitHashesPerType = 4; -/** Cutoffs that decide which size band a request falls into. - - A SHAMap inner node has 16 children; an honest peer asks for 4 - hashes per type. So: - - kBandSmallMax = 4 * 16 = 64 // one inner node's worth - kBandMediumMax = 4 * 16^2 = 1024 // a depth-2 subtree's worth - - A request up to 64 objects is small (no surcharge); up to 1024 is - medium; anything larger is large. The bounds are inclusive: a - request of exactly 64 is small, 65 is medium. Anything past 1024 is - well beyond what the honest sync path produces, so it's billed at - the large rate to drive attack-shaped traffic over the drop - threshold quickly. */ +/** + * Cutoffs that decide which size band a request falls into. + * + * A SHAMap inner node has 16 children; an honest peer asks for 4 + * hashes per type. So: + * + * kBandSmallMax = 4 * 16 = 64 // one inner node's worth + * kBandMediumMax = 4 * 16^2 = 1024 // a depth-2 subtree's worth + * + * A request up to 64 objects is small (no surcharge); up to 1024 is + * medium; anything larger is large. The bounds are inclusive: a + * request of exactly 64 is small, 65 is medium. Anything past 1024 is + * well beyond what the honest sync path produces, so it's billed at + * the large rate to drive attack-shaped traffic over the drop + * threshold quickly. + */ static constexpr auto kBandSmallMax = kLegitHashesPerType * SHAMapInnerNode::kBranchFactor; static constexpr auto kBandMediumMax = kBandSmallMax * SHAMapInnerNode::kBranchFactor; diff --git a/src/xrpld/overlay/detail/TxMetrics.h b/src/xrpld/overlay/detail/TxMetrics.h index 44cd0272ee..a9afa1d6b2 100644 --- a/src/xrpld/overlay/detail/TxMetrics.h +++ b/src/xrpld/overlay/detail/TxMetrics.h @@ -12,17 +12,19 @@ namespace xrpl::metrics { -/** Run single metrics rolling average. Can be either average of a value - per second or average of a value's sample per second. For instance, - for transaction it makes sense to have transaction bytes and count - per second, but for a number of selected peers to relay per transaction - it makes sense to have sample's average. +/** + * Run single metrics rolling average. Can be either average of a value + * per second or average of a value's sample per second. For instance, + * for transaction it makes sense to have transaction bytes and count + * per second, but for a number of selected peers to relay per transaction + * it makes sense to have sample's average. */ struct SingleMetrics { - /** Class constructor - @param ptu if true then calculate metrics per second, otherwise - sample's average + /** + * Class constructor + * @param ptu if true then calculate metrics per second, otherwise + * sample's average */ SingleMetrics(bool ptu = true) : perTimeUnit(ptu) { @@ -34,15 +36,18 @@ struct SingleMetrics std::uint32_t n{0}; bool perTimeUnit{true}; boost::circular_buffer rollingAvgAggregate{30, 0ull}; - /** Add metrics value + /** + * Add metrics value * @param val metrics value, either bytes or count */ void addMetrics(std::uint32_t val); }; -/** Run two metrics. For instance message size and count for - protocol messages. */ +/** + * Run two metrics. For instance message size and count for + * protocol messages. + */ struct MultipleMetrics { MultipleMetrics(bool ptu1 = true, bool ptu2 = true) : m1(ptu1), m2(ptu2) @@ -51,12 +56,14 @@ struct MultipleMetrics SingleMetrics m1; SingleMetrics m2; - /** Add metrics to m2. m1 in this case aggregates the frequency. - @param val2 m2 metrics value + /** + * Add metrics to m2. m1 in this case aggregates the frequency. + * @param val2 m2 metrics value */ void addMetrics(std::uint32_t val2); - /** Add metrics to m1 and m2. + /** + * Add metrics to m1 and m2. * @param val1 m1 metrics value * @param val2 m2 metrics value */ @@ -64,7 +71,9 @@ struct MultipleMetrics addMetrics(std::uint32_t val1, std::uint32_t val2); }; -/** Run transaction reduce-relay feature related metrics */ +/** + * Run transaction reduce-relay feature related metrics + */ struct TxMetrics { mutable std::mutex mutex; @@ -86,26 +95,30 @@ struct TxMetrics SingleMetrics notEnabled{false}; // TMTransactions number of transactions count per second SingleMetrics missingTx; - /** Add protocol message metrics - @param type protocol message type - @param val message size in bytes + /** + * Add protocol message metrics + * @param type protocol message type + * @param val message size in bytes */ void addMetrics(protocol::MessageType type, std::uint32_t val); - /** Add peers selected for relaying and suppressed peers metrics. - @param selected number of selected peers to relay - @param suppressed number of suppressed peers - @param notEnabled number of peers with tx reduce-relay featured disabled + /** + * Add peers selected for relaying and suppressed peers metrics. + * @param selected number of selected peers to relay + * @param suppressed number of suppressed peers + * @param notEnabled number of peers with tx reduce-relay featured disabled */ void addMetrics(std::uint32_t selected, std::uint32_t suppressed, std::uint32_t notEnabled); - /** Add number of missing transactions that a node requested - @param missing number of missing transactions + /** + * Add number of missing transactions that a node requested + * @param missing number of missing transactions */ void addMetrics(std::uint32_t missing); - /** Get json representation of the metrics - @return json object + /** + * Get json representation of the metrics + * @return json object */ json::Value json() const; diff --git a/src/xrpld/overlay/detail/ZeroCopyStream.h b/src/xrpld/overlay/detail/ZeroCopyStream.h index deccb627b9..9bbe69910b 100644 --- a/src/xrpld/overlay/detail/ZeroCopyStream.h +++ b/src/xrpld/overlay/detail/ZeroCopyStream.h @@ -11,11 +11,12 @@ namespace xrpl { -/** Implements ZeroCopyInputStream around a buffer sequence. - @tparam Buffers A type meeting the requirements of ConstBufferSequence. - @see - https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.io.zero_copy_stream -*/ +/** + * Implements ZeroCopyInputStream around a buffer sequence. + * @tparam Buffers A type meeting the requirements of ConstBufferSequence. + * @see + * https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.io.zero_copy_stream + */ template class ZeroCopyInputStream : public ::google::protobuf::io::ZeroCopyInputStream { @@ -105,10 +106,11 @@ ZeroCopyInputStream::Skip(int count) //------------------------------------------------------------------------------ -/** Implements ZeroCopyOutputStream around a Streambuf. - Streambuf matches the public interface defined by boost::asio::streambuf. - @tparam Streambuf A type meeting the requirements of Streambuf. -*/ +/** + * Implements ZeroCopyOutputStream around a Streambuf. + * Streambuf matches the public interface defined by boost::asio::streambuf. + * @tparam Streambuf A type meeting the requirements of Streambuf. + */ template class ZeroCopyOutputStream : public ::google::protobuf::io::ZeroCopyOutputStream { diff --git a/src/xrpld/overlay/make_Overlay.h b/src/xrpld/overlay/make_Overlay.h index f0dfa429c0..a62d4b49de 100644 --- a/src/xrpld/overlay/make_Overlay.h +++ b/src/xrpld/overlay/make_Overlay.h @@ -19,7 +19,9 @@ namespace xrpl { Overlay::Setup setupOverlay(BasicConfig const& config, beast::Journal j); -/** Creates the implementation of Overlay. */ +/** + * Creates the implementation of Overlay. + */ std::unique_ptr makeOverlay( Application& app, diff --git a/src/xrpld/overlay/predicates.h b/src/xrpld/overlay/predicates.h index 2527b8c728..56f4c80291 100644 --- a/src/xrpld/overlay/predicates.h +++ b/src/xrpld/overlay/predicates.h @@ -8,7 +8,9 @@ namespace xrpl { -/** Sends a message to all peers */ +/** + * Sends a message to all peers + */ struct SendAlways { using return_type = void; @@ -28,7 +30,9 @@ struct SendAlways //------------------------------------------------------------------------------ -/** Sends a message to match peers */ +/** + * Sends a message to match peers + */ template struct SendIfPred { @@ -49,7 +53,9 @@ struct SendIfPred } }; -/** Helper function to aid in type deduction */ +/** + * Helper function to aid in type deduction + */ template SendIfPred sendIf(std::shared_ptr const& m, Predicate const& f) @@ -59,7 +65,9 @@ sendIf(std::shared_ptr const& m, Predicate const& f) //------------------------------------------------------------------------------ -/** Sends a message to non-matching peers */ +/** + * Sends a message to non-matching peers + */ template struct SendIfNotPred { @@ -80,7 +88,9 @@ struct SendIfNotPred } }; -/** Helper function to aid in type deduction */ +/** + * Helper function to aid in type deduction + */ template SendIfNotPred sendIfNot(std::shared_ptr const& m, Predicate const& f) @@ -90,7 +100,9 @@ sendIfNot(std::shared_ptr const& m, Predicate const& f) //------------------------------------------------------------------------------ -/** Select the specific peer */ +/** + * Select the specific peer + */ struct MatchPeer { Peer const* matchPeer; @@ -108,7 +120,9 @@ struct MatchPeer //------------------------------------------------------------------------------ -/** Select all peers (except optional excluded) that are in our cluster */ +/** + * Select all peers (except optional excluded) that are in our cluster + */ struct PeerInCluster { MatchPeer skipPeer; @@ -132,7 +146,9 @@ struct PeerInCluster //------------------------------------------------------------------------------ -/** Select all peers that are in the specified set */ +/** + * Select all peers that are in the specified set + */ struct PeerInSet { std::set const& peerSet; diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index d482ae7241..0530343641 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -24,71 +24,101 @@ namespace xrpl::PeerFinder { using clock_type = beast::AbstractClock; -/** Represents a set of addresses. */ +/** + * Represents a set of addresses. + */ using IPAddresses = std::vector; //------------------------------------------------------------------------------ -/** PeerFinder configuration settings. */ +/** + * PeerFinder configuration settings. + */ struct Config { - /** The largest number of public peer slots to allow. - This includes both inbound and outbound, but does not include - fixed peers. - */ + /** + * The largest number of public peer slots to allow. + * This includes both inbound and outbound, but does not include + * fixed peers. + */ std::size_t maxPeers{Tuning::kDefaultMaxPeers}; - /** The number of automatic outbound connections to maintain. - Outbound connections are only maintained if autoConnect - is `true`. - */ + /** + * The number of automatic outbound connections to maintain. + * Outbound connections are only maintained if autoConnect + * is `true`. + */ std::size_t outPeers; - /** The number of automatic inbound connections to maintain. - Inbound connections are only maintained if wantIncoming - is `true`. - */ + /** + * The number of automatic inbound connections to maintain. + * Inbound connections are only maintained if wantIncoming + * is `true`. + */ std::size_t inPeers{0}; - /** `true` if we want our IP address kept private. */ + /** + * `true` if we want our IP address kept private. + */ bool peerPrivate = true; - /** `true` if we want to accept incoming connections. */ + /** + * `true` if we want to accept incoming connections. + */ bool wantIncoming{true}; - /** `true` if we want to establish connections automatically */ + /** + * `true` if we want to establish connections automatically + */ bool autoConnect{true}; - /** The listening port number. */ + /** + * The listening port number. + */ std::uint16_t listeningPort{0}; - /** The set of features we advertise. */ + /** + * The set of features we advertise. + */ std::string features; - /** Limit how many incoming connections we allow per IP */ + /** + * Limit how many incoming connections we allow per IP + */ int ipLimit{0}; - /** `true` if we want to verify endpoints in TMEndpoints messages */ + /** + * `true` if we want to verify endpoints in TMEndpoints messages + */ bool verifyEndpoints = true; //-------------------------------------------------------------------------- - /** Create a configuration with default values. */ + /** + * Create a configuration with default values. + */ Config(); - /** Returns a suitable value for outPeers according to the rules. */ + /** + * Returns a suitable value for outPeers according to the rules. + */ [[nodiscard]] std::size_t calcOutPeers() const; - /** Adjusts the values so they follow the business rules. */ + /** + * Adjusts the values so they follow the business rules. + */ void applyTuning(); - /** Write the configuration into a property stream */ + /** + * Write the configuration into a property stream + */ void onWrite(beast::PropertyStream::Map& map) const; - /** Make PeerFinder::Config from configuration parameters + /** + * Make PeerFinder::Config from configuration parameters * @param config server's configuration * @param port server's listening port * @param validationPublicKey true if validation public key is not empty @@ -111,7 +141,9 @@ struct Config //------------------------------------------------------------------------------ -/** Describes a connectable peer address along with some metadata. */ +/** + * Describes a connectable peer address along with some metadata. + */ struct Endpoint { Endpoint() = default; @@ -128,12 +160,16 @@ operator<(Endpoint const& lhs, Endpoint const& rhs) return lhs.address < rhs.address; } -/** A set of Endpoint used for connecting. */ +/** + * A set of Endpoint used for connecting. + */ using Endpoints = std::vector; //------------------------------------------------------------------------------ -/** Possible results from activating a slot. */ +/** + * Possible results from activating a slot. + */ enum class Result { InboundDisabled, DuplicatePeer, IpLimitExceeded, Full, Success }; /** @@ -170,57 +206,70 @@ to_string(Result result) noexcept return "unknown"; } -/** Maintains a set of IP addresses used for getting into the network. */ +/** + * Maintains a set of IP addresses used for getting into the network. + */ class Manager : public beast::PropertyStream::Source { protected: Manager() noexcept; public: - /** Destroy the object. - Any pending source fetch operations are aborted. - There may be some listener calls made before the - destructor returns. - */ + /** + * Destroy the object. + * Any pending source fetch operations are aborted. + * There may be some listener calls made before the + * destructor returns. + */ ~Manager() override = default; - /** Set the configuration for the manager. - The new settings will be applied asynchronously. - Thread safety: - Can be called from any threads at any time. - */ + /** + * Set the configuration for the manager. + * The new settings will be applied asynchronously. + * Thread safety: + * Can be called from any threads at any time. + */ virtual void setConfig(Config const& config) = 0; - /** Transition to the started state, synchronously. */ + /** + * Transition to the started state, synchronously. + */ virtual void start() = 0; - /** Transition to the stopped state, synchronously. */ + /** + * Transition to the stopped state, synchronously. + */ virtual void stop() = 0; - /** Returns the configuration for the manager. */ + /** + * Returns the configuration for the manager. + */ virtual Config config() = 0; - /** Add a peer that should always be connected. - This is useful for maintaining a private cluster of peers. - The string is the name as specified in the configuration - file, along with the set of corresponding IP addresses. - */ + /** + * Add a peer that should always be connected. + * This is useful for maintaining a private cluster of peers. + * The string is the name as specified in the configuration + * file, along with the set of corresponding IP addresses. + */ virtual void addFixedPeer(std::string_view name, std::vector const& addresses) = 0; - /** Add a set of strings as fallback IP::Endpoint sources. - @param name A label used for diagnostics. - */ + /** + * Add a set of strings as fallback IP::Endpoint sources. + * @param name A label used for diagnostics. + */ virtual void addFallbackStrings(std::string const& name, std::vector const& strings) = 0; - /** Add a URL as a fallback location to obtain IP::Endpoint sources. - @param name A label used for diagnostics. - */ + /** + * Add a URL as a fallback location to obtain IP::Endpoint sources. + * @param name A label used for diagnostics. + */ /* VFALCO NOTE Unimplemented virtual void addFallbackURL (std::string const& name, std::string const& url) = 0; @@ -228,38 +277,47 @@ public: //-------------------------------------------------------------------------- - /** Create a new inbound slot with the specified remote endpoint. - If nullptr is returned, then the slot could not be assigned. - Usually this is because of a detected self-connection. - */ + /** + * Create a new inbound slot with the specified remote endpoint. + * If nullptr is returned, then the slot could not be assigned. + * Usually this is because of a detected self-connection. + */ virtual std::pair, Result> newInboundSlot( beast::IP::Endpoint const& localEndpoint, beast::IP::Endpoint const& remoteEndpoint) = 0; - /** Create a new outbound slot with the specified remote endpoint. - If nullptr is returned, then the slot could not be assigned. - Usually this is because of a duplicate connection. - */ + /** + * Create a new outbound slot with the specified remote endpoint. + * If nullptr is returned, then the slot could not be assigned. + * Usually this is because of a duplicate connection. + */ virtual std::pair, Result> newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) = 0; - /** Called when mtENDPOINTS is received. */ + /** + * Called when mtENDPOINTS is received. + */ virtual void onEndpoints(std::shared_ptr const& slot, Endpoints const& endpoints) = 0; - /** Called when the slot is closed. - This always happens when the socket is closed, unless the socket - was canceled. - */ + /** + * Called when the slot is closed. + * This always happens when the socket is closed, unless the socket + * was canceled. + */ virtual void onClosed(std::shared_ptr const& slot) = 0; - /** Called when an outbound connection is deemed to have failed */ + /** + * Called when an outbound connection is deemed to have failed + */ virtual void onFailure(std::shared_ptr const& slot) = 0; - /** Called when we received redirect IPs from a busy peer. */ + /** + * Called when we received redirect IPs from a busy peer. + */ virtual void onRedirects( boost::asio::ip::tcp::endpoint const& remoteAddress, @@ -267,34 +325,42 @@ public: //-------------------------------------------------------------------------- - /** Called when an outbound connection attempt succeeds. - The local endpoint must be valid. If the caller receives an error - when retrieving the local endpoint from the socket, it should - proceed as if the connection attempt failed by calling on_closed - instead of on_connected. - @return `true` if the connection should be kept - */ + /** + * Called when an outbound connection attempt succeeds. + * The local endpoint must be valid. If the caller receives an error + * when retrieving the local endpoint from the socket, it should + * proceed as if the connection attempt failed by calling on_closed + * instead of on_connected. + * @return `true` if the connection should be kept + */ virtual bool onConnected(std::shared_ptr const& slot, beast::IP::Endpoint const& localEndpoint) = 0; - /** Request an active slot type. */ + /** + * Request an active slot type. + */ virtual Result activate(std::shared_ptr const& slot, PublicKey const& key, bool reserved) = 0; - /** Returns a set of endpoints suitable for redirection. */ + /** + * Returns a set of endpoints suitable for redirection. + */ virtual std::vector redirect(std::shared_ptr const& slot) = 0; - /** Return a set of addresses we should connect to. */ + /** + * Return a set of addresses we should connect to. + */ virtual std::vector autoconnect() = 0; virtual std::vector, std::vector>> buildEndpointsForPeers() = 0; - /** Perform periodic activity. - This should be called once per second. - */ + /** + * Perform periodic activity. + * This should be called once per second. + */ virtual void oncePerSecond() = 0; }; diff --git a/src/xrpld/peerfinder/Slot.h b/src/xrpld/peerfinder/Slot.h index f43b7d1009..9db39ac94c 100644 --- a/src/xrpld/peerfinder/Slot.h +++ b/src/xrpld/peerfinder/Slot.h @@ -9,7 +9,9 @@ namespace xrpl::PeerFinder { -/** Properties and state associated with a peer to peer overlay connection. */ +/** + * Properties and state associated with a peer to peer overlay connection. + */ class Slot { public: @@ -19,42 +21,53 @@ public: virtual ~Slot() = 0; - /** Returns `true` if this is an inbound connection. */ + /** + * Returns `true` if this is an inbound connection. + */ [[nodiscard]] virtual bool inbound() const = 0; - /** Returns `true` if this is a fixed connection. - A connection is fixed if its remote endpoint is in the list of - remote endpoints for fixed connections. - */ + /** + * Returns `true` if this is a fixed connection. + * A connection is fixed if its remote endpoint is in the list of + * remote endpoints for fixed connections. + */ [[nodiscard]] virtual bool fixed() const = 0; - /** Returns `true` if this is a reserved connection. - It might be a cluster peer, or a peer with a reservation. - This is only known after then handshake completes. + /** + * Returns `true` if this is a reserved connection. + * It might be a cluster peer, or a peer with a reservation. + * This is only known after then handshake completes. */ [[nodiscard]] virtual bool reserved() const = 0; - /** Returns the state of the connection. */ + /** + * Returns the state of the connection. + */ [[nodiscard]] virtual State state() const = 0; - /** The remote endpoint of socket. */ + /** + * The remote endpoint of socket. + */ [[nodiscard]] virtual beast::IP::Endpoint const& remoteEndpoint() const = 0; - /** The local endpoint of the socket, when known. */ + /** + * The local endpoint of the socket, when known. + */ [[nodiscard]] virtual std::optional const& localEndpoint() const = 0; [[nodiscard]] virtual std::optional listeningPort() const = 0; - /** The peer's public key, when known. - The public key is established when the handshake is complete. - */ + /** + * The peer's public key, when known. + * The public key is established when the handshake is complete. + */ [[nodiscard]] virtual std::optional const& publicKey() const = 0; }; diff --git a/src/xrpld/peerfinder/detail/Bootcache.h b/src/xrpld/peerfinder/detail/Bootcache.h index cee03fa322..c84fed42c7 100644 --- a/src/xrpld/peerfinder/detail/Bootcache.h +++ b/src/xrpld/peerfinder/detail/Bootcache.h @@ -16,21 +16,22 @@ namespace xrpl::PeerFinder { -/** Stores IP addresses useful for gaining initial connections. - - This is one of the caches that is consulted when additional outgoing - connections are needed. Along with the address, each entry has this - additional metadata: - - Valence - A signed integer which represents the number of successful - consecutive connection attempts when positive, and the number of - failed consecutive connection attempts when negative. - - When choosing addresses from the boot cache for the purpose of - establishing outgoing connections, addresses are ranked in decreasing - order of high uptime, with valence as the tie breaker. -*/ +/** + * Stores IP addresses useful for gaining initial connections. + * + * This is one of the caches that is consulted when additional outgoing + * connections are needed. Along with the address, each entry has this + * additional metadata: + * + * Valence + * A signed integer which represents the number of successful + * consecutive connection attempts when positive, and the number of + * failed consecutive connection attempts when negative. + * + * When choosing addresses from the boot cache for the purpose of + * establishing outgoing connections, addresses are ranked in decreasing + * order of high uptime, with valence as the tie breaker. + */ class Bootcache { private: @@ -107,15 +108,21 @@ public: ~Bootcache(); - /** Returns `true` if the cache is empty. */ + /** + * Returns `true` if the cache is empty. + */ [[nodiscard]] bool empty() const; - /** Returns the number of entries in the cache. */ + /** + * Returns the number of entries in the cache. + */ [[nodiscard]] map_type::size_type size() const; - /** IP::Endpoint iterators that traverse in decreasing valence. */ + /** + * IP::Endpoint iterators that traverse in decreasing valence. + */ /** @{ */ [[nodiscard]] const_iterator begin() const; @@ -129,31 +136,45 @@ public: clear(); /** @} */ - /** Load the persisted data from the Store into the container. */ + /** + * Load the persisted data from the Store into the container. + */ void load(); - /** Add a newly-learned address to the cache. */ + /** + * Add a newly-learned address to the cache. + */ bool insert(beast::IP::Endpoint const& endpoint); - /** Add a staticallyconfigured address to the cache. */ + /** + * Add a staticallyconfigured address to the cache. + */ bool insertStatic(beast::IP::Endpoint const& endpoint); - /** Called when an outbound connection handshake completes. */ + /** + * Called when an outbound connection handshake completes. + */ void onSuccess(beast::IP::Endpoint const& endpoint); - /** Called when an outbound connection attempt fails to handshake. */ + /** + * Called when an outbound connection attempt fails to handshake. + */ void onFailure(beast::IP::Endpoint const& endpoint); - /** Stores the cache in the persistent database on a timer. */ + /** + * Stores the cache in the persistent database on a timer. + */ void periodicActivity(); - /** Write the cache state to the property stream. */ + /** + * Write the cache state to the property stream. + */ void onWrite(beast::PropertyStream::Map& map); diff --git a/src/xrpld/peerfinder/detail/Checker.h b/src/xrpld/peerfinder/detail/Checker.h index 208bad390c..28ec83adb1 100644 --- a/src/xrpld/peerfinder/detail/Checker.h +++ b/src/xrpld/peerfinder/detail/Checker.h @@ -13,7 +13,9 @@ namespace xrpl::PeerFinder { -/** Tests remote listening sockets to make sure they are connectable. */ +/** + * Tests remote listening sockets to make sure they are connectable. + */ template class Checker { @@ -70,31 +72,36 @@ private: public: explicit Checker(boost::asio::io_context& ioContext); - /** Destroy the service. - Any pending I/O operations will be canceled. This call blocks until - all pending operations complete (either with success or with - operation_aborted) and the associated thread and io_context have - no more work remaining. - */ + /** + * Destroy the service. + * Any pending I/O operations will be canceled. This call blocks until + * all pending operations complete (either with success or with + * operation_aborted) and the associated thread and io_context have + * no more work remaining. + */ ~Checker(); - /** Stop the service. - Pending I/O operations will be canceled. - This issues cancel orders for all pending I/O operations and then - returns immediately. Handlers will receive operation_aborted errors, - or if they were already queued they will complete normally. - */ + /** + * Stop the service. + * Pending I/O operations will be canceled. + * This issues cancel orders for all pending I/O operations and then + * returns immediately. Handlers will receive operation_aborted errors, + * or if they were already queued they will complete normally. + */ void stop(); - /** Block until all pending I/O completes. */ + /** + * Block until all pending I/O completes. + */ void wait(); - /** Performs an async connection test on the specified endpoint. - The port must be non-zero. Note that the execution guarantees - offered by asio handlers are NOT enforced. - */ + /** + * Performs an async connection test on the specified endpoint. + * The port must be non-zero. Note that the execution guarantees + * offered by asio handlers are NOT enforced. + */ template void asyncConnect(beast::IP::Endpoint const& endpoint, Handler&& handler); diff --git a/src/xrpld/peerfinder/detail/Counts.h b/src/xrpld/peerfinder/detail/Counts.h index 0d8bf1c56e..c90598c1a1 100644 --- a/src/xrpld/peerfinder/detail/Counts.h +++ b/src/xrpld/peerfinder/detail/Counts.h @@ -13,28 +13,38 @@ namespace xrpl::PeerFinder { -/** Direction of a slot count adjustment. */ +/** + * Direction of a slot count adjustment. + */ enum class CountAdjustment : int { Decrement = -1, Increment = 1 }; -/** Manages the count of available connections for the various slots. */ +/** + * Manages the count of available connections for the various slots. + */ class Counts { public: - /** Adds the slot state and properties to the slot counts. */ + /** + * Adds the slot state and properties to the slot counts. + */ void add(Slot const& s) { adjust(s, CountAdjustment::Increment); } - /** Removes the slot state and properties from the slot counts. */ + /** + * Removes the slot state and properties from the slot counts. + */ void remove(Slot const& s) { adjust(s, CountAdjustment::Decrement); } - /** Returns `true` if the slot can become active. */ + /** + * Returns `true` if the slot can become active. + */ [[nodiscard]] bool canActivate(Slot const& s) const { @@ -52,7 +62,9 @@ public: return outActive_ < outMax_; } - /** Returns the number of attempts needed to bring us to the max. */ + /** + * Returns the number of attempts needed to bring us to the max. + */ [[nodiscard]] std::size_t attemptsNeeded() const { @@ -61,37 +73,46 @@ public: return Tuning::kMaxConnectAttempts - attempts_; } - /** Returns the number of outbound connection attempts. */ + /** + * Returns the number of outbound connection attempts. + */ [[nodiscard]] std::size_t attempts() const { return attempts_; } - /** Returns the total number of outbound slots. */ + /** + * Returns the total number of outbound slots. + */ [[nodiscard]] int outMax() const { return outMax_; } - /** Returns the number of outbound peers assigned an open slot. - Fixed peers do not count towards outbound slots used. - */ + /** + * Returns the number of outbound peers assigned an open slot. + * Fixed peers do not count towards outbound slots used. + */ [[nodiscard]] int outActive() const { return outActive_; } - /** Returns the number of fixed connections. */ + /** + * Returns the number of fixed connections. + */ [[nodiscard]] std::size_t fixed() const { return fixed_; } - /** Returns the number of active fixed connections. */ + /** + * Returns the number of active fixed connections. + */ [[nodiscard]] std::size_t fixedActive() const { @@ -100,7 +121,9 @@ public: //-------------------------------------------------------------------------- - /** Called when the config is set or changed. */ + /** + * Called when the config is set or changed. + */ void onConfig(Config const& config) { @@ -109,51 +132,64 @@ public: inMax_ = config.inPeers; } - /** Returns the number of accepted connections that haven't handshaked. */ + /** + * Returns the number of accepted connections that haven't handshaked. + */ [[nodiscard]] int acceptCount() const { return acceptCount_; } - /** Returns the number of connection attempts currently active. */ + /** + * Returns the number of connection attempts currently active. + */ [[nodiscard]] int connectCount() const { return attempts_; } - /** Returns the number of connections that are gracefully closing. */ + /** + * Returns the number of connections that are gracefully closing. + */ [[nodiscard]] int closingCount() const { return closingCount_; } - /** Returns the total number of inbound slots. */ + /** + * Returns the total number of inbound slots. + */ [[nodiscard]] int inMax() const { return inMax_; } - /** Returns the number of inbound peers assigned an open slot. */ + /** + * Returns the number of inbound peers assigned an open slot. + */ [[nodiscard]] int inboundActive() const { return inActive_; } - /** Returns the total number of active peers excluding fixed peers. */ + /** + * Returns the total number of active peers excluding fixed peers. + */ [[nodiscard]] int totalActive() const { return inActive_ + outActive_; } - /** Returns the number of unused inbound slots. - Fixed peers do not deduct from inbound slots or count towards totals. - */ + /** + * Returns the number of unused inbound slots. + * Fixed peers do not deduct from inbound slots or count towards totals. + */ [[nodiscard]] int inboundSlotsFree() const { @@ -162,9 +198,10 @@ public: return 0; } - /** Returns the number of unused outbound slots. - Fixed peers do not deduct from outbound slots or count towards totals. - */ + /** + * Returns the number of unused outbound slots. + * Fixed peers do not deduct from outbound slots or count towards totals. + */ [[nodiscard]] int outboundSlotsFree() const { @@ -175,7 +212,8 @@ public: //-------------------------------------------------------------------------- - /** Returns true if the slot logic considers us "connected" to the network. + /** + * Returns true if the slot logic considers us "connected" to the network. */ [[nodiscard]] bool isConnectedToNetwork() const @@ -189,7 +227,9 @@ public: return outMax_ <= 0; } - /** Output statistics. */ + /** + * Output statistics. + */ void onWrite(beast::PropertyStream::Map& map) const { @@ -203,7 +243,9 @@ public: map["total"] = active_; } - /** Records the state for diagnostics. */ + /** + * Records the state for diagnostics. + */ [[nodiscard]] std::string stateString() const { @@ -215,7 +257,9 @@ public: //-------------------------------------------------------------------------- private: - /** Increments or decrements a counter based on the adjustment direction. */ + /** + * Increments or decrements a counter based on the adjustment direction. + */ template static void adjustCounter(T& counter, CountAdjustment dir) @@ -295,31 +339,49 @@ private: } private: - /** Outbound connection attempts. */ + /** + * Outbound connection attempts. + */ int attempts_{0}; - /** Active connections, including fixed and reserved. */ + /** + * Active connections, including fixed and reserved. + */ std::size_t active_{0}; - /** Total number of inbound slots. */ + /** + * Total number of inbound slots. + */ std::size_t inMax_{0}; - /** Number of inbound slots assigned to active peers. */ + /** + * Number of inbound slots assigned to active peers. + */ std::size_t inActive_{0}; - /** Maximum desired outbound slots. */ + /** + * Maximum desired outbound slots. + */ std::size_t outMax_{0}; - /** Active outbound slots. */ + /** + * Active outbound slots. + */ std::size_t outActive_{0}; - /** Fixed connections. */ + /** + * Fixed connections. + */ std::size_t fixed_{0}; - /** Active fixed connections. */ + /** + * Active fixed connections. + */ std::size_t fixedActive_{0}; - /** Reserved connections. */ + /** + * Reserved connections. + */ std::size_t reserved_{0}; // Number of inbound connections that are diff --git a/src/xrpld/peerfinder/detail/Fixed.h b/src/xrpld/peerfinder/detail/Fixed.h index 3319994251..24d54775ef 100644 --- a/src/xrpld/peerfinder/detail/Fixed.h +++ b/src/xrpld/peerfinder/detail/Fixed.h @@ -9,7 +9,9 @@ namespace xrpl::PeerFinder { -/** Metadata for a Fixed slot. */ +/** + * Metadata for a Fixed slot. + */ class Fixed { public: @@ -19,14 +21,18 @@ public: Fixed(Fixed const&) = default; - /** Returns the time after which we should allow a connection attempt. */ + /** + * Returns the time after which we should allow a connection attempt. + */ [[nodiscard]] clock_type::time_point const& when() const { return when_; } - /** Updates metadata to reflect a failed connection. */ + /** + * Updates metadata to reflect a failed connection. + */ void failure(clock_type::time_point const& now) { @@ -34,7 +40,9 @@ public: when_ = now + std::chrono::minutes(Tuning::kConnectionBackoff[failures_]); } - /** Updates metadata to reflect a successful connection. */ + /** + * Updates metadata to reflect a successful connection. + */ void success(clock_type::time_point const& now) { diff --git a/src/xrpld/peerfinder/detail/Handouts.h b/src/xrpld/peerfinder/detail/Handouts.h index faadb51fd2..757f1a8e1b 100644 --- a/src/xrpld/peerfinder/detail/Handouts.h +++ b/src/xrpld/peerfinder/detail/Handouts.h @@ -17,10 +17,11 @@ namespace xrpl::PeerFinder { namespace detail { -/** Try to insert one object in the target. - When an item is handed out it is moved to the end of the container. - @return The number of objects inserted -*/ +/** + * Try to insert one object in the target. + * When an item is handed out it is moved to the end of the container. + * @return The number of objects inserted + */ // VFALCO TODO specialization that handles std::list for SequenceContainer // using splice for optimization over erase/push_back // @@ -43,10 +44,11 @@ handoutOne(Target& t, HopContainer& h) } // namespace detail -/** Distributes objects to targets according to business rules. - A best effort is made to evenly distribute items in the sequence - container list into the target sequence list. -*/ +/** + * Distributes objects to targets according to business rules. + * A best effort is made to evenly distribute items in the sequence + * container list into the target sequence list. + */ template void handout(TargetFwdIter first, TargetFwdIter last, SeqFwdIter seqFirst, SeqFwdIter seqLast) @@ -77,9 +79,10 @@ handout(TargetFwdIter first, TargetFwdIter last, SeqFwdIter seqFirst, SeqFwdIter //------------------------------------------------------------------------------ -/** Receives handouts for redirecting a connection. - An incoming connection request is redirected when we are full on slots. -*/ +/** + * Receives handouts for redirecting a connection. + * An incoming connection request is redirected when we are full on slots. + */ class RedirectHandouts { public: @@ -163,7 +166,9 @@ RedirectHandouts::tryInsert(Endpoint const& ep) //------------------------------------------------------------------------------ -/** Receives endpoints for a slot during periodic handouts. */ +/** + * Receives endpoints for a slot during periodic handouts. + */ class SlotHandouts { public: @@ -247,7 +252,9 @@ SlotHandouts::tryInsert(Endpoint const& ep) //------------------------------------------------------------------------------ -/** Receives handouts for making automatic connections. */ +/** + * Receives handouts for making automatic connections. + */ class ConnectHandouts { public: diff --git a/src/xrpld/peerfinder/detail/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h index 1dcc8f6daf..2015098847 100644 --- a/src/xrpld/peerfinder/detail/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -55,10 +55,11 @@ protected: boost::intrusive::make_list>::type; public: - /** A list of Endpoint at the same hops - This is a lightweight wrapper around a reference to the underlying - container. - */ + /** + * A list of Endpoint at the same hops + * This is a lightweight wrapper around a reference to the underlying + * container. + */ template class Hop { @@ -169,18 +170,19 @@ protected: //------------------------------------------------------------------------------ -/** The Livecache holds the short-lived relayed Endpoint messages. - - Since peers only advertise themselves when they have open slots, - we want these messages to expire rather quickly after the peer becomes - full. - - Addresses added to the cache are not connection-tested to see if - they are connectable (with one small exception regarding neighbors). - Therefore, these addresses are not suitable for persisting across - launches or for bootstrapping, because they do not have verifiable - and locally observed uptime and connectability information. -*/ +/** + * The Livecache holds the short-lived relayed Endpoint messages. + * + * Since peers only advertise themselves when they have open slots, + * we want these messages to expire rather quickly after the peer becomes + * full. + * + * Addresses added to the cache are not connection-tested to see if + * they are connectable (with one small exception regarding neighbors). + * Therefore, these addresses are not suitable for persisting across + * launches or for bootstrapping, because they do not have verifiable + * and locally observed uptime and connectability information. + */ template > class Livecache : protected detail::LivecacheBase { @@ -198,7 +200,9 @@ private: public: using allocator_type = Allocator; - /** Create the cache. */ + /** + * Create the cache. + */ Livecache(clock_type& clock, beast::Journal journal, Allocator alloc = Allocator()); // @@ -318,7 +322,9 @@ public: return const_reverse_iterator(lists_.crend(), Transform()); } - /** Shuffle each hop list. */ + /** + * Shuffle each hop list. + */ void shuffle(); @@ -343,29 +349,39 @@ public: Histogram hist_{}; } hops; - /** Returns `true` if the cache is empty. */ + /** + * Returns `true` if the cache is empty. + */ [[nodiscard]] bool empty() const { return cache_.empty(); } - /** Returns the number of entries in the cache. */ + /** + * Returns the number of entries in the cache. + */ cache_type::size_type size() const { return cache_.size(); } - /** Erase entries whose time has expired. */ + /** + * Erase entries whose time has expired. + */ void expire(); - /** Creates or updates an existing Element based on a new message. */ + /** + * Creates or updates an existing Element based on a new message. + */ void insert(Endpoint const& ep); - /** Output statistics. */ + /** + * Output statistics. + */ void onWrite(beast::PropertyStream::Map& map); }; diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index 8ea348f560..a7dbbf850d 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -41,10 +41,11 @@ namespace xrpl::PeerFinder { -/** The Logic for maintaining the list of Slot addresses. - We keep this in a separate class so it can be instantiated - for unit tests. -*/ +/** + * The Logic for maintaining the list of Slot addresses. + * We keep this in a separate class so it can be instantiated + * for unit tests. + */ template class Logic { @@ -127,12 +128,13 @@ public: bootcache.load(); } - /** Stop the logic. - This will cancel the current fetch and set the stopping flag - to `true` to prevent further fetches. - Thread safety: - Safe to call from any thread. - */ + /** + * Stop the logic. + * This will cancel the current fetch and set the stopping flag + * to `true` to prevent further fetches. + * Thread safety: + * Safe to call from any thread. + */ void stop() { @@ -448,10 +450,11 @@ public: return Result::Success; } - /** Return a list of addresses suitable for redirection. - This is a legacy function, redirects should be returned in - the HTTP handshake and not via TMEndpoints. - */ + /** + * Return a list of addresses suitable for redirection. + * This is a legacy function, redirects should be returned in + * the HTTP handshake and not via TMEndpoints. + */ std::vector redirect(SlotImp::ptr const& slot) { @@ -462,9 +465,10 @@ public: return std::move(h.list()); } - /** Create new outbound connection attempts as needed. - This implements PeerFinder's "Outbound Connection Strategy" - */ + /** + * Create new outbound connection attempts as needed. + * This implements PeerFinder's "Outbound Connection Strategy" + */ // VFALCO TODO This should add the returned addresses to the // squelch list in one go once the list is built, // rather than having each module add to the squelch list. @@ -961,12 +965,8 @@ public: bool fixed(beast::IP::Endpoint const& endpoint) const { - for (auto const& entry : fixed_) - { - if (entry.first == endpoint) - return true; - } - return false; + return std::ranges::any_of( + fixed_, [&endpoint](auto const& entry) { return entry.first == endpoint; }); } // Returns `true` if the address matches a fixed slot address @@ -975,12 +975,8 @@ public: bool fixed(beast::IP::Address const& address) const { - for (auto const& entry : fixed_) - { - if (entry.first.address() == address) - return true; - } - return false; + return std::ranges::any_of( + fixed_, [&address](auto const& entry) { return entry.first.address() == address; }); } //-------------------------------------------------------------------------- @@ -989,7 +985,9 @@ public: // //-------------------------------------------------------------------------- - /** Adds eligible Fixed addresses for outbound attempts. */ + /** + * Adds eligible Fixed addresses for outbound attempts. + */ template void getFixed(std::size_t needed, Container& c, ConnectHandouts::Squelches& squelches) diff --git a/src/xrpld/peerfinder/detail/SlotImp.h b/src/xrpld/peerfinder/detail/SlotImp.h index cf1915268f..898941b157 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.h +++ b/src/xrpld/peerfinder/detail/SlotImp.h @@ -135,14 +135,17 @@ public: public: explicit RecentT(clock_type& clock); - /** Called for each valid endpoint received for a slot. - We also insert messages that we send to the slot to prevent - sending a slot the same address too frequently. - */ + /** + * Called for each valid endpoint received for a slot. + * We also insert messages that we send to the slot to prevent + * sending a slot the same address too frequently. + */ void insert(beast::IP::Endpoint const& ep, std::uint32_t hops); - /** Returns `true` if we should not send endpoint to the slot. */ + /** + * Returns `true` if we should not send endpoint to the slot. + */ bool filter(beast::IP::Endpoint const& ep, std::uint32_t hops); diff --git a/src/xrpld/peerfinder/detail/Source.h b/src/xrpld/peerfinder/detail/Source.h index cf8920e056..b205dc8dfb 100644 --- a/src/xrpld/peerfinder/detail/Source.h +++ b/src/xrpld/peerfinder/detail/Source.h @@ -10,18 +10,21 @@ namespace xrpl::PeerFinder { -/** A static or dynamic source of peer addresses. - These are used as fallbacks when we are bootstrapping and don't have - a local cache, or when none of our addresses are functioning. Typically - sources will represent things like static text in the config file, a - separate local file with addresses, or a remote HTTPS URL that can - be updated automatically. Another solution is to use a custom DNS server - that hands out peer IP addresses when name lookups are performed. -*/ +/** + * A static or dynamic source of peer addresses. + * These are used as fallbacks when we are bootstrapping and don't have + * a local cache, or when none of our addresses are functioning. Typically + * sources will represent things like static text in the config file, a + * separate local file with addresses, or a remote HTTPS URL that can + * be updated automatically. Another solution is to use a custom DNS server + * that hands out peer IP addresses when name lookups are performed. + */ class Source { public: - /** The results of a fetch. */ + /** + * The results of a fetch. + */ struct Results { explicit Results() = default; diff --git a/src/xrpld/peerfinder/detail/SourceStrings.h b/src/xrpld/peerfinder/detail/SourceStrings.h index 618970fa03..b79cf0df03 100644 --- a/src/xrpld/peerfinder/detail/SourceStrings.h +++ b/src/xrpld/peerfinder/detail/SourceStrings.h @@ -8,7 +8,9 @@ namespace xrpl::PeerFinder { -/** Provides addresses from a static set of strings. */ +/** + * Provides addresses from a static set of strings. + */ class SourceStrings : public Source { public: diff --git a/src/xrpld/peerfinder/detail/Store.h b/src/xrpld/peerfinder/detail/Store.h index 570dba0523..9393ef6c2b 100644 --- a/src/xrpld/peerfinder/detail/Store.h +++ b/src/xrpld/peerfinder/detail/Store.h @@ -8,7 +8,9 @@ namespace xrpl::PeerFinder { -/** Abstract persistence for PeerFinder data. */ +/** + * Abstract persistence for PeerFinder data. + */ class Store { public: diff --git a/src/xrpld/peerfinder/detail/StoreSqdb.h b/src/xrpld/peerfinder/detail/StoreSqdb.h index f868e89ff7..b17d2fdc5b 100644 --- a/src/xrpld/peerfinder/detail/StoreSqdb.h +++ b/src/xrpld/peerfinder/detail/StoreSqdb.h @@ -16,7 +16,9 @@ namespace xrpl::PeerFinder { -/** Database persistence for PeerFinder using SQLite */ +/** + * Database persistence for PeerFinder using SQLite + */ class StoreSqdb : public Store { private: diff --git a/src/xrpld/peerfinder/detail/Tuning.h b/src/xrpld/peerfinder/detail/Tuning.h index 1bf9df382e..ea4637dd9d 100644 --- a/src/xrpld/peerfinder/detail/Tuning.h +++ b/src/xrpld/peerfinder/detail/Tuning.h @@ -5,7 +5,9 @@ #include #include -/** Heuristically tuned constants. */ +/** + * Heuristically tuned constants. + */ /** @{ */ namespace xrpl::PeerFinder::Tuning { @@ -15,32 +17,41 @@ namespace xrpl::PeerFinder::Tuning { // //--------------------------------------------------------- -/** Time to wait between making batches of connection attempts */ +/** + * Time to wait between making batches of connection attempts + */ static constexpr auto kSecondsPerConnect = 10; -/** Maximum number of simultaneous connection attempts. */ +/** + * Maximum number of simultaneous connection attempts. + */ static constexpr auto kMaxConnectAttempts = 20; -/** The percentage of total peer slots that are outbound. - The number of outbound peers will be the larger of the - minOutCount and outPercent * Config::maxPeers specially - rounded. -*/ +/** + * The percentage of total peer slots that are outbound. + * The number of outbound peers will be the larger of the + * minOutCount and outPercent * Config::maxPeers specially + * rounded. + */ static constexpr auto kOutPercent = 15; -/** A hard minimum on the number of outgoing connections. - This is enforced outside the Logic, so that the unit test - can use any settings it wants. -*/ +/** + * A hard minimum on the number of outgoing connections. + * This is enforced outside the Logic, so that the unit test + * can use any settings it wants. + */ static constexpr auto kMinOutCount = 10; -/** The default value of Config::maxPeers. */ +/** + * The default value of Config::maxPeers. + */ static constexpr auto kDefaultMaxPeers = 21; -/** Max redirects we will accept from one connection. - Redirects are limited for security purposes, to prevent - the address caches from getting flooded. -*/ +/** + * Max redirects we will accept from one connection. + * Redirects are limited for security purposes, to prevent + * the address caches from getting flooded. + */ static constexpr auto kMaxRedirects = 30; //------------------------------------------------------------------------------ diff --git a/src/xrpld/peerfinder/detail/iosformat.h b/src/xrpld/peerfinder/detail/iosformat.h index a0b9ff537a..46c69ef602 100644 --- a/src/xrpld/peerfinder/detail/iosformat.h +++ b/src/xrpld/peerfinder/detail/iosformat.h @@ -12,7 +12,9 @@ namespace beast { // A collection of handy stream manipulators and // functions to produce nice looking log output. -/** Left justifies a field at the specified width. */ +/** + * Left justifies a field at the specified width. + */ struct Leftw { explicit Leftw(int width) : width(width) @@ -29,7 +31,9 @@ struct Leftw } }; -/** Produce a section heading and fill the rest of the line with dashes. */ +/** + * Produce a section heading and fill the rest of the line with dashes. + */ template std::basic_string heading(std::basic_string title, int width = 80, CharT fill = CharT('-')) @@ -40,7 +44,9 @@ heading(std::basic_string title, int width = 80, CharT return title; } -/** Produce a dashed line separator, with a specified or default size. */ +/** + * Produce a dashed line separator, with a specified or default size. + */ struct Divider { using CharT = char; @@ -58,7 +64,9 @@ struct Divider } }; -/** Creates a padded field with an optional fill character. */ +/** + * Creates a padded field with an optional fill character. + */ struct Fpad { explicit Fpad(int width, int pad = 0, char fill = ' ') : width(width + pad), fill(fill) @@ -90,7 +98,9 @@ to_string(T const& t) } // namespace detail -/** Justifies a field at the specified width. */ +/** + * Justifies a field at the specified width. + */ /** @{ */ template < class CharT, diff --git a/src/xrpld/peerfinder/make_Manager.h b/src/xrpld/peerfinder/make_Manager.h index 1f3f226397..1c13d7a4ca 100644 --- a/src/xrpld/peerfinder/make_Manager.h +++ b/src/xrpld/peerfinder/make_Manager.h @@ -12,7 +12,9 @@ namespace xrpl::PeerFinder { -/** Create a new Manager. */ +/** + * Create a new Manager. + */ std::unique_ptr makeManager( boost::asio::io_context& ioContext, diff --git a/src/xrpld/perflog/detail/PerfLogImp.h b/src/xrpld/perflog/detail/PerfLogImp.h index 88bf473554..14477512ff 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.h +++ b/src/xrpld/perflog/detail/PerfLogImp.h @@ -24,7 +24,9 @@ namespace xrpl::perf { -/** A box coupling data with a mutex for locking access to it. */ +/** + * A box coupling data with a mutex for locking access to it. + */ template struct Locked { diff --git a/src/xrpld/rpc/Context.h b/src/xrpld/rpc/Context.h index fe6bb81ce3..81ba068d8f 100644 --- a/src/xrpld/rpc/Context.h +++ b/src/xrpld/rpc/Context.h @@ -20,7 +20,9 @@ class LedgerMaster; namespace RPC { -/** The context of information needed to call an RPC. */ +/** + * The context of information needed to call an RPC. + */ struct Context { beast::Journal const j; diff --git a/src/xrpld/rpc/DeliveredAmount.h b/src/xrpld/rpc/DeliveredAmount.h index bba045d494..dc635c6861 100644 --- a/src/xrpld/rpc/DeliveredAmount.h +++ b/src/xrpld/rpc/DeliveredAmount.h @@ -24,14 +24,13 @@ struct JsonContext; struct Context; /** - Add a `delivered_amount` field to the `meta` input/output parameter. - The field is only added to successful payment and check cash transactions. - If a delivered amount field is available in the TxMeta parameter, that value - is used. Otherwise, the transaction's `Amount` field is used. If neither is - available, then the delivered amount is set to "unavailable". - - @{ + * Add a `delivered_amount` field to the `meta` input/output parameter. + * The field is only added to successful payment and check cash transactions. + * If a delivered amount field is available in the TxMeta parameter, that value + * is used. Otherwise, the transaction's `Amount` field is used. If neither is + * available, then the delivered amount is set to "unavailable". */ +/** @{ */ void insertDeliveredAmount( json::Value& meta, diff --git a/src/xrpld/rpc/MPTokenIssuanceID.h b/src/xrpld/rpc/MPTokenIssuanceID.h index cb2bfd1bdc..f56826bfb8 100644 --- a/src/xrpld/rpc/MPTokenIssuanceID.h +++ b/src/xrpld/rpc/MPTokenIssuanceID.h @@ -11,13 +11,12 @@ namespace xrpl::RPC { /** - Add a `mpt_issuance_id` field to the `meta` input/output parameter. - The field is only added to successful MPTokenIssuanceCreate transactions. - The mpt_issuance_id is parsed from the sequence and the issuer in the - MPTokenIssuance object. - - @{ + * Add a `mpt_issuance_id` field to the `meta` input/output parameter. + * The field is only added to successful MPTokenIssuanceCreate transactions. + * The mpt_issuance_id is parsed from the sequence and the issuer in the + * MPTokenIssuance object. */ +/** @{ */ bool canHaveMPTokenIssuanceID( std::shared_ptr const& serializedTx, diff --git a/src/xrpld/rpc/RPCCall.h b/src/xrpld/rpc/RPCCall.h index a06eca4413..a72b35e344 100644 --- a/src/xrpld/rpc/RPCCall.h +++ b/src/xrpld/rpc/RPCCall.h @@ -23,7 +23,9 @@ namespace xrpl { // // Improvements to be more strict and to provide better diagnostics are welcome. -/** Processes XRPL RPC calls. */ +/** + * Processes XRPL RPC calls. + */ namespace RPCCall { int @@ -54,7 +56,8 @@ rpcCmdToJson( unsigned int apiVersion, beast::Journal j); -/** Internal invocation of RPC client. +/** + * Internal invocation of RPC client. * Used by both xrpld command line as well as xrpld unit tests */ std::pair diff --git a/src/xrpld/rpc/RPCHandler.h b/src/xrpld/rpc/RPCHandler.h index d1cd54145d..fcd0f54265 100644 --- a/src/xrpld/rpc/RPCHandler.h +++ b/src/xrpld/rpc/RPCHandler.h @@ -12,7 +12,9 @@ namespace xrpl::RPC { struct JsonContext; -/** Execute an RPC command and store the results in a json::Value. */ +/** + * Execute an RPC command and store the results in a json::Value. + */ Status doCommand(RPC::JsonContext&, json::Value&); diff --git a/src/xrpld/rpc/RPCSub.h b/src/xrpld/rpc/RPCSub.h index 95206e5cf6..bf31536bc5 100644 --- a/src/xrpld/rpc/RPCSub.h +++ b/src/xrpld/rpc/RPCSub.h @@ -11,7 +11,9 @@ namespace xrpl { -/** Subscription object for JSON RPC. */ +/** + * Subscription object for JSON RPC. + */ class RPCSub : public InfoSub { public: diff --git a/src/xrpld/rpc/Role.h b/src/xrpld/rpc/Role.h index 660fb92c7c..2c2ae6b781 100644 --- a/src/xrpld/rpc/Role.h +++ b/src/xrpld/rpc/Role.h @@ -17,7 +17,8 @@ namespace xrpl { -/** Indicates the level of administrative permission to grant. +/** + * Indicates the level of administrative permission to grant. * IDENTIFIED role has unlimited resources but cannot perform some * RPC commands. * ADMIN role has unlimited resources and is able to perform all RPC @@ -25,14 +26,15 @@ namespace xrpl { */ enum class Role { GUEST, USER, IDENTIFIED, ADMIN, PROXY, FORBID }; -/** Return the allowed privilege role. - params must meet the requirements of the JSON-RPC - specification. It must be of type Object, containing the key params - which is an array with at least one object. Inside this object - are the optional keys 'admin_user' and 'admin_password' used to - validate the credentials. If user is non-blank, it's username - passed in the HTTP header by a secureGateway proxy. -*/ +/** + * Return the allowed privilege role. + * params must meet the requirements of the JSON-RPC + * specification. It must be of type Object, containing the key params + * which is an array with at least one object. Inside this object + * are the optional keys 'admin_user' and 'admin_password' used to + * validate the credentials. If user is non-blank, it's username + * passed in the HTTP header by a secureGateway proxy. + */ Role requestRole( Role const& required, diff --git a/src/xrpld/rpc/Status.h b/src/xrpld/rpc/Status.h index ff77f2ace7..dda1e89d31 100644 --- a/src/xrpld/rpc/Status.h +++ b/src/xrpld/rpc/Status.h @@ -13,14 +13,15 @@ namespace xrpl::RPC { -/** Status represents the results of an operation that might fail. - - It wraps the legacy codes TER and error_code_i, providing both a uniform - interface and a way to attach additional information to existing status - returns. - - A Status can also be used to fill a json::Value with a JSON-RPC 2.0 - error response: see http://www.jsonrpc.org/specification#error_object +/** + * Status represents the results of an operation that might fail. + * + * It wraps the legacy codes TER and error_code_i, providing both a uniform + * interface and a way to attach additional information to existing status + * returns. + * + * A Status can also be used to fill a json::Value with a JSON-RPC 2.0 + * error response: see http://www.jsonrpc.org/specification#error_object */ struct Status : public std::exception { @@ -61,21 +62,27 @@ public: [[nodiscard]] std::string codeString() const; - /** Returns true if the Status is *not* OK. */ + /** + * Returns true if the Status is *not* OK. + */ operator bool() const { return code_ != kOK; } - /** Returns true if the Status is OK. */ + /** + * Returns true if the Status is OK. + */ bool operator!() const { return !bool(*this); } - /** Returns the Status as a TER. - This may only be called if type() == Type::TER. */ + /** + * Returns the Status as a TER. + * This may only be called if type() == Type::TER. + */ [[nodiscard]] TER toTER() const { @@ -83,8 +90,10 @@ public: return TER::fromInt(code_); } - /** Returns the Status as an error_code_i. - This may only be called if type() == Type::ErrorCodeI. */ + /** + * Returns the Status as an error_code_i. + * This may only be called if type() == Type::ErrorCodeI. + */ [[nodiscard]] ErrorCodeI toErrorCode() const { @@ -92,7 +101,8 @@ public: return ErrorCodeI(code_); } - /** Apply the Status to a JsonObject + /** + * Apply the Status to a JsonObject */ void inject(json::Value& object) const @@ -116,7 +126,9 @@ public: return messages_; } - /** Return the first message, if any. */ + /** + * Return the first message, if any. + */ [[nodiscard]] std::string message() const; @@ -129,9 +141,11 @@ public: [[nodiscard]] std::string toString() const; - /** Fill a json::Value with an RPC 2.0 response. - If the Status is OK, fillJson has no effect. - Not currently used. */ + /** + * Fill a json::Value with an RPC 2.0 response. + * If the Status is OK, fillJson has no effect. + * Not currently used. + */ void fillJson(json::Value&); diff --git a/src/xrpld/rpc/detail/AssetCache.h b/src/xrpld/rpc/detail/AssetCache.h index 4b89487526..71a7c262d4 100644 --- a/src/xrpld/rpc/detail/AssetCache.h +++ b/src/xrpld/rpc/detail/AssetCache.h @@ -30,18 +30,19 @@ public: return ledger_; } - /** Find the trust lines associated with an account. - - @param accountID The account - @param direction Whether the account is an "outgoing" link on the path. - "Outgoing" is defined as the source account, or an account found via a - trustline that has rippling enabled on the @accountID's side. If an - account is "outgoing", all trust lines will be returned. If an account is - not "outgoing", then any trust lines that don't have rippling enabled are - not usable, so only return trust lines that have rippling enabled on - @accountID's side. - @return Returns a vector of the usable trust lines. - */ + /** + * Find the trust lines associated with an account. + * + * @param accountID The account + * @param direction Whether the account is an "outgoing" link on the path. + * "Outgoing" is defined as the source account, or an account found via a + * trustline that has rippling enabled on the @accountID's side. If an + * account is "outgoing", all trust lines will be returned. If an account is + * not "outgoing", then any trust lines that don't have rippling enabled are + * not usable, so only return trust lines that have rippling enabled on + * @accountID's side. + * @return Returns a vector of the usable trust lines. + */ std::shared_ptr> getRippleLines(AccountID const& accountID, LineDirection direction); diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 23eb8fdeec..4f5ce34c1f 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -21,7 +21,9 @@ namespace xrpl::RPC { namespace { -/** Adjust an old-style handler to be call-by-reference. */ +/** + * Adjust an old-style handler to be call-by-reference. + */ template Handler::Method byRef(Function const& f) diff --git a/src/xrpld/rpc/detail/Handler.h b/src/xrpld/rpc/detail/Handler.h index 5e583aa5bb..37259c8648 100644 --- a/src/xrpld/rpc/detail/Handler.h +++ b/src/xrpld/rpc/detail/Handler.h @@ -47,7 +47,9 @@ struct Handler Handler const* getHandler(unsigned int version, bool betaEnabled, std::string const&); -/** Return a json::ValueType::Object with a single entry. */ +/** + * Return a json::ValueType::Object with a single entry. + */ template json::Value makeObjectValue(Value const& value, json::StaticString const& field = jss::message) @@ -57,7 +59,9 @@ makeObjectValue(Value const& value, json::StaticString const& field = jss::messa return result; } -/** Return names of all methods. */ +/** + * Return names of all methods. + */ std::set getHandlerNames(); diff --git a/src/xrpld/rpc/detail/PathRequest.h b/src/xrpld/rpc/detail/PathRequest.h index 64bc6ef181..d40d9c82d6 100644 --- a/src/xrpld/rpc/detail/PathRequest.h +++ b/src/xrpld/rpc/detail/PathRequest.h @@ -114,9 +114,10 @@ private: int const, std::function const&); - /** Finds and sets a PathSet in the JSON argument. - Returns false if the source currencies are invalid. - */ + /** + * Finds and sets a PathSet in the JSON argument. + * Returns false if the source currencies are invalid. + */ bool findPaths( std::shared_ptr const&, diff --git a/src/xrpld/rpc/detail/PathRequestManager.cpp b/src/xrpld/rpc/detail/PathRequestManager.cpp index 7013353cb1..4953634181 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.cpp +++ b/src/xrpld/rpc/detail/PathRequestManager.cpp @@ -26,9 +26,10 @@ namespace xrpl { -/** Get the current AssetCache, updating it if necessary. - Get the correct ledger to use. -*/ +/** + * Get the current AssetCache, updating it if necessary. + * Get the correct ledger to use. + */ std::shared_ptr PathRequestManager::getAssetCache(std::shared_ptr const& ledger, bool authoritative) { diff --git a/src/xrpld/rpc/detail/PathRequestManager.h b/src/xrpld/rpc/detail/PathRequestManager.h index 94d126ed23..f6eb80d291 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.h +++ b/src/xrpld/rpc/detail/PathRequestManager.h @@ -23,7 +23,9 @@ namespace xrpl { class PathRequestManager { public: - /** A collection of all PathRequest instances. */ + /** + * A collection of all PathRequest instances. + */ PathRequestManager( Application& app, beast::Journal journal, @@ -34,9 +36,10 @@ public: full_ = collector->makeEvent("pathfind_full"); } - /** Update all of the contained PathRequest instances. - - @param ledger Ledger we are pathfinding in. + /** + * Update all of the contained PathRequest instances. + * + * @param ledger Ledger we are pathfinding in. */ void updateAll(std::shared_ptr const& ledger); diff --git a/src/xrpld/rpc/detail/Pathfinder.h b/src/xrpld/rpc/detail/Pathfinder.h index 5ef6c31b25..aeacd218d2 100644 --- a/src/xrpld/rpc/detail/Pathfinder.h +++ b/src/xrpld/rpc/detail/Pathfinder.h @@ -26,16 +26,19 @@ namespace xrpl { -/** Calculates payment paths. - - The @ref RippleCalc determines the quality of the found paths. - - @see RippleCalc -*/ +/** + * Calculates payment paths. + * + * The @ref RippleCalc determines the quality of the found paths. + * + * @see RippleCalc + */ class Pathfinder : public CountedObject { public: - /** Construct a pathfinder without an issuer.*/ + /** + * Construct a pathfinder without an issuer. + */ Pathfinder( std::shared_ptr const& cache, AccountID const& srcAccount, @@ -57,7 +60,9 @@ public: bool findPaths(int searchLevel, std::function const& continueCallback = {}); - /** Compute the rankings of the paths. */ + /** + * Compute the rankings of the paths. + */ void computePathRanks(int maxPaths, std::function const& continueCallback = {}); @@ -189,8 +194,10 @@ private: PathAsset srcPathAsset_; std::optional srcIssuer_; STAmount srcAmount_; - /** The amount remaining from srcAccount_ after the default liquidity has - been removed. */ + /** + * The amount remaining from srcAccount_ after the default liquidity has + * been removed. + */ STAmount remainingAmount_; bool convertAll_; std::optional domain_; diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index af5677008d..b5d5c680cd 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -569,12 +569,10 @@ private: { if (jv.size() == 0) return false; - for (auto const& j : jv) - { - if (!isValidJson2(j)) - return false; - } - return true; + // json::Value is not a std::ranges range, so the iterator form is used. + // NOLINTNEXTLINE(modernize-use-ranges) + return std::all_of( + jv.begin(), jv.end(), [this](auto const& j) { return isValidJson2(j); }); } if (jv.isObject()) { diff --git a/src/xrpld/rpc/detail/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp index e839d9d78d..6f46aed62d 100644 --- a/src/xrpld/rpc/detail/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -29,83 +29,82 @@ namespace xrpl::RPC { namespace { /** - This code is called from both the HTTP RPC handler and Websockets. - - The form of the Json returned is somewhat different between the two services. - - HTML: - Success: - { - "result" : { - "ledger" : { - "accepted" : false, - "transaction_hash" : "..." - }, - "ledger_index" : 10300865, - "validated" : false, - "status" : "success" # Status is inside the result. - } - } - - Failure: - { - "result" : { - // api_version == 1 - "error" : "noNetwork", - "error_code" : 17, - "error_message" : "Not synced to the network.", - - // api_version == 2 - "error" : "notSynced", - "error_code" : 18, - "error_message" : "Not synced to the network.", - - "request" : { - "command" : "ledger", - "ledger_index" : 10300865 - }, - "status" : "error" - } - } - - Websocket: - Success: - { - "result" : { - "ledger" : { - "accepted" : false, - "transaction_hash" : "..." - }, - "ledger_index" : 10300865, - "validated" : false - } - "type": "response", - "status": "success", # Status is OUTside the result! - "id": "client's ID", # Optional - "warning": 3.14 # Optional - } - - Failure: - { - // api_version == 1 - "error" : "noNetwork", - "error_code" : 17, - "error_message" : "Not synced to the network.", - - // api_version == 2 - "error" : "notSynced", - "error_code" : 18, - "error_message" : "Not synced to the network.", - - "request" : { - "command" : "ledger", - "ledger_index" : 10300865 - }, - "type": "response", - "status" : "error", - "id": "client's ID" # Optional - } - + * This code is called from both the HTTP RPC handler and Websockets. + * + * The form of the Json returned is somewhat different between the two services. + * + * HTML: + * Success: + * { + * "result" : { + * "ledger" : { + * "accepted" : false, + * "transaction_hash" : "..." + * }, + * "ledger_index" : 10300865, + * "validated" : false, + * "status" : "success" # Status is inside the result. + * } + * } + * + * Failure: + * { + * "result" : { + * // api_version == 1 + * "error" : "noNetwork", + * "error_code" : 17, + * "error_message" : "Not synced to the network.", + * + * // api_version == 2 + * "error" : "notSynced", + * "error_code" : 18, + * "error_message" : "Not synced to the network.", + * + * "request" : { + * "command" : "ledger", + * "ledger_index" : 10300865 + * }, + * "status" : "error" + * } + * } + * + * Websocket: + * Success: + * { + * "result" : { + * "ledger" : { + * "accepted" : false, + * "transaction_hash" : "..." + * }, + * "ledger_index" : 10300865, + * "validated" : false + * } + * "type": "response", + * "status": "success", # Status is OUTside the result! + * "id": "client's ID", # Optional + * "warning": 3.14 # Optional + * } + * + * Failure: + * { + * // api_version == 1 + * "error" : "noNetwork", + * "error_code" : 17, + * "error_message" : "Not synced to the network.", + * + * // api_version == 2 + * "error" : "notSynced", + * "error_code" : 18, + * "error_message" : "Not synced to the network.", + * + * "request" : { + * "command" : "ledger", + * "ledger_index" : 10300865 + * }, + * "type": "response", + * "status" : "error", + * "id": "client's ID" # Optional + * } */ ErrorCodeI diff --git a/src/xrpld/rpc/detail/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h index 4a4dca42e5..881b758487 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -162,7 +162,8 @@ keypairForSignature( json::Value& error, unsigned int apiVersion = kApiVersionIfUnspecified); -/** Parse subscribe/unsubscribe parameters +/** + * Parse subscribe/unsubscribe parameters */ ErrorCodeI parseSubUnsubJson( diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 8c3a5ea245..e1c5180b5c 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -993,7 +993,9 @@ checkFee( //------------------------------------------------------------------------------ -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSign( json::Value jvRequest, @@ -1027,7 +1029,9 @@ transactionSign( return transactionFormatResultImpl(txn.second, apiVersion); } -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSubmit( json::Value jvRequest, @@ -1150,7 +1154,9 @@ sortAndValidateSigners(STArray& signers, AccountID const& signingForID) } // namespace detail -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSignFor( json::Value jvRequest, @@ -1256,7 +1262,7 @@ transactionSignFor( // The array must be sorted and validated. // For delegated transactions, the delegate account is // the one forbidden from appearing in its own Signers array. - auto err = sortAndValidateSigners(signers, sttx->getFeePayer()); + auto err = sortAndValidateSigners(signers, sttx->getInitiator()); if (RPC::containsError(err)) return err; } @@ -1271,7 +1277,9 @@ transactionSignFor( return transactionFormatResultImpl(txn.second, apiVersion); } -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSubmitMultiSigned( json::Value jvRequest, @@ -1423,9 +1431,9 @@ transactionSubmitMultiSigned( } // The array must be sorted and validated. - // For delegated transactions, getFeePayer() returns sfDelegate, + // For delegated transactions, getInitiator() returns sfDelegate, // that account is the one forbidden from appearing in its own Signers array. - auto err = sortAndValidateSigners(signers, stTx->getFeePayer()); + auto err = sortAndValidateSigners(signers, stTx->getInitiator()); if (RPC::containsError(err)) return err; diff --git a/src/xrpld/rpc/detail/TransactionSign.h b/src/xrpld/rpc/detail/TransactionSign.h index cb3fb176dc..dcb417dd16 100644 --- a/src/xrpld/rpc/detail/TransactionSign.h +++ b/src/xrpld/rpc/detail/TransactionSign.h @@ -33,33 +33,34 @@ getCurrentNetworkFee( int mult = Tuning::kDefaultAutoFillFeeMultiplier, int div = Tuning::kDefaultAutoFillFeeDivisor); -/** Fill in the fee on behalf of the client. - This is called when the client does not explicitly specify the fee. - The client may also put a ceiling on the amount of the fee. This ceiling - is expressed as a multiplier based on the current ledger's fee schedule. - - JSON fields - - "Fee" The fee paid by the transaction. Omitted when the client - wants the fee filled in. - - "fee_mult_max" A multiplier applied to the current ledger's transaction - fee that caps the maximum fee the server should auto fill. - If this optional field is not specified, then a default - multiplier is used. - "fee_div_max" A divider applied to the current ledger's transaction - fee that caps the maximum fee the server should auto fill. - If this optional field is not specified, then a default - divider (1) is used. "fee_mult_max" and "fee_div_max" - are both used such that the maximum fee will be - `base * fee_mult_max / fee_div_max` as an integer. - - @param tx The JSON corresponding to the transaction to fill in. - @param ledger A ledger for retrieving the current fee schedule. - @param roll Identifies if this is called by an administrative endpoint. - - @return A JSON object containing the error results, if any -*/ +/** + * Fill in the fee on behalf of the client. + * This is called when the client does not explicitly specify the fee. + * The client may also put a ceiling on the amount of the fee. This ceiling + * is expressed as a multiplier based on the current ledger's fee schedule. + * + * JSON fields + * + * "Fee" The fee paid by the transaction. Omitted when the client + * wants the fee filled in. + * + * "fee_mult_max" A multiplier applied to the current ledger's transaction + * fee that caps the maximum fee the server should auto fill. + * If this optional field is not specified, then a default + * multiplier is used. + * "fee_div_max" A divider applied to the current ledger's transaction + * fee that caps the maximum fee the server should auto fill. + * If this optional field is not specified, then a default + * divider (1) is used. "fee_mult_max" and "fee_div_max" + * are both used such that the maximum fee will be + * `base * fee_mult_max / fee_div_max` as an integer. + * + * @param tx The JSON corresponding to the transaction to fill in. + * @param ledger A ledger for retrieving the current fee schedule. + * @param roll Identifies if this is called by an administrative endpoint. + * + * @return A JSON object containing the error results, if any + */ json::Value checkFee( json::Value& request, @@ -89,7 +90,9 @@ getProcessTxnFn(NetworkOPs& netOPs) }; } -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSign( json::Value params, // Passed by value so it can be modified locally. @@ -99,7 +102,9 @@ transactionSign( std::chrono::seconds validatedLedgerAge, Application& app); -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSubmit( json::Value params, // Passed by value so it can be modified locally. @@ -110,7 +115,9 @@ transactionSubmit( Application& app, ProcessTransactionFn const& processTransaction); -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSignFor( json::Value params, // Passed by value so it can be modified locally. @@ -120,7 +127,9 @@ transactionSignFor( std::chrono::seconds validatedLedgerAge, Application& app); -/** Returns a json::ValueType::Object. */ +/** + * Returns a json::ValueType::Object. + */ json::Value transactionSubmitMultiSigned( json::Value params, // Passed by value so it can be modified locally. diff --git a/src/xrpld/rpc/detail/TrustLine.h b/src/xrpld/rpc/detail/TrustLine.h index a80d81e4ae..c3f76784ba 100644 --- a/src/xrpld/rpc/detail/TrustLine.h +++ b/src/xrpld/rpc/detail/TrustLine.h @@ -16,25 +16,27 @@ namespace xrpl { -/** Describes how an account was found in a path, and how to find the next set -of paths. "Outgoing" is defined as the source account, or an account found via a -trustline that has rippling enabled on the account's side. -"Incoming" is defined as an account found via a trustline that has rippling -disabled on the account's side. Any trust lines for an incoming account that -have rippling disabled are unusable in paths. -*/ +/** + * Describes how an account was found in a path, and how to find the next set + * of paths. "Outgoing" is defined as the source account, or an account found via a + * trustline that has rippling enabled on the account's side. + * "Incoming" is defined as an account found via a trustline that has rippling + * disabled on the account's side. Any trust lines for an incoming account that + * have rippling disabled are unusable in paths. + */ enum class LineDirection : bool { Incoming = false, Outgoing = true }; -/** Wraps a trust line SLE for convenience. - The complication of trust lines is that there is a - "low" account and a "high" account. This wraps the - SLE and expresses its data from the perspective of - a chosen account on the line. - - This wrapper is primarily used in the path finder and there can easily be - tens of millions of instances of this class. When modifying this class think - carefully about the memory implications. -*/ +/** + * Wraps a trust line SLE for convenience. + * The complication of trust lines is that there is a + * "low" account and a "high" account. This wraps the + * SLE and expresses its data from the perspective of + * a chosen account on the line. + * + * This wrapper is primarily used in the path finder and there can easily be + * tens of millions of instances of this class. When modifying this class think + * carefully about the memory implications. + */ class TrustLineBase { public: @@ -51,7 +53,9 @@ protected: TrustLineBase(TrustLineBase&&) = default; public: - /** Returns the state map key for the ledger entry. */ + /** + * Returns the state map key for the ledger entry. + */ [[nodiscard]] uint256 const& key() const { @@ -109,28 +113,36 @@ public: return getNoRipplePeer() ? LineDirection::Incoming : LineDirection::Outgoing; } - /** Have we set the freeze flag on our peer */ + /** + * Have we set the freeze flag on our peer + */ [[nodiscard]] bool getFreeze() const { return (flags_ & (viewLowest_ ? lsfLowFreeze : lsfHighFreeze)) != 0u; } - /** Have we set the deep freeze flag on our peer */ + /** + * Have we set the deep freeze flag on our peer + */ [[nodiscard]] bool getDeepFreeze() const { return (flags_ & (viewLowest_ ? lsfLowDeepFreeze : lsfHighDeepFreeze)) != 0u; } - /** Has the peer set the freeze flag on us */ + /** + * Has the peer set the freeze flag on us + */ [[nodiscard]] bool getFreezePeer() const { return (flags_ & (!viewLowest_ ? lsfLowFreeze : lsfHighFreeze)) != 0u; } - /** Has the peer set the deep freeze flag on us */ + /** + * Has the peer set the deep freeze flag on us + */ [[nodiscard]] bool getDeepFreezePeer() const { diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index 5a9d546472..b904822698 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -2,41 +2,63 @@ #include -/** Tuned constants. */ +/** + * Tuned constants. + */ /** @{ */ namespace xrpl::RPC::Tuning { -/** Represents RPC limit parameter values that have a min, default and max. */ +/** + * Represents RPC limit parameter values that have a min, default and max. + */ struct LimitRange { unsigned int rmin, rDefault, rmax; }; -/** Limits for the account_lines command. */ +/** + * Limits for the account_lines command. + */ static constexpr LimitRange kAccountLines = {.rmin = 10, .rDefault = 200, .rmax = 400}; -/** Limits for the account_channels command. */ +/** + * Limits for the account_channels command. + */ static constexpr LimitRange kAccountChannels = {.rmin = 10, .rDefault = 200, .rmax = 400}; -/** Limits for the account_objects command. */ +/** + * Limits for the account_objects command. + */ static constexpr LimitRange kAccountObjects = {.rmin = 10, .rDefault = 200, .rmax = 400}; -/** Limits for the account_offers command. */ +/** + * Limits for the account_offers command. + */ static constexpr LimitRange kAccountOffers = {.rmin = 10, .rDefault = 200, .rmax = 400}; -/** Limits for the account_tx command. */ +/** + * Limits for the account_tx command. + */ static constexpr LimitRange kAccountTx = {.rmin = 10, .rDefault = 200, .rmax = 400}; -/** Limits for the book_offers command. */ +/** + * Limits for the book_offers command. + */ static constexpr LimitRange kBookOffers = {.rmin = 1, .rDefault = 60, .rmax = 100}; -/** Limits for the no_ripple_check command. */ +/** + * Limits for the no_ripple_check command. + */ static constexpr LimitRange kNoRippleCheck = {.rmin = 10, .rDefault = 300, .rmax = 400}; -/** Limits for the account_nftokens command, in pages. */ +/** + * Limits for the account_nftokens command, in pages. + */ static constexpr LimitRange kAccountNfTokens = {.rmin = 20, .rDefault = 100, .rmax = 400}; -/** Limits for the nft_buy_offers & nft_sell_offers commands. */ +/** + * Limits for the nft_buy_offers & nft_sell_offers commands. + */ static constexpr LimitRange kNftOffers = {.rmin = 50, .rDefault = 250, .rmax = 500}; static constexpr int kDefaultAutoFillFeeMultiplier = 10; @@ -47,23 +69,33 @@ static constexpr int kMaxJobQueueClients = 500; constexpr auto kMaxValidatedLedgerAge = std::chrono::minutes{2}; static constexpr int kMaxRequestSize = 1000000; -/** Maximum number of pages in one response from a binary LedgerData request. */ +/** + * Maximum number of pages in one response from a binary LedgerData request. + */ static constexpr int kBinaryPageLength = 2048; -/** Maximum number of pages in one response from a Json LedgerData request. */ +/** + * Maximum number of pages in one response from a Json LedgerData request. + */ static constexpr int kJsonPageLength = 256; -/** Maximum number of pages in a LedgerData response. */ +/** + * Maximum number of pages in a LedgerData response. + */ constexpr int pageLength(bool isBinary) { return isBinary ? kBinaryPageLength : kJsonPageLength; } -/** Maximum number of source currencies allowed in a path find request. */ +/** + * Maximum number of source currencies allowed in a path find request. + */ static constexpr int kMaxSrcCur = 18; -/** Maximum number of auto source currencies in a path find request. */ +/** + * Maximum number of auto source currencies in a path find request. + */ static constexpr int kMaxAutoSrcCur = 88; } // namespace xrpl::RPC::Tuning diff --git a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp index 5ce10f6121..ea9bec0f45 100644 --- a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp +++ b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp @@ -23,16 +23,17 @@ namespace xrpl { -/** General RPC command that can retrieve objects in the account root. - { - account: - ledger_hash: // optional - ledger_index: // optional - type: // optional, defaults to all account objects types - limit: // optional - marker: // optional, resume previous query - } -*/ +/** + * General RPC command that can retrieve objects in the account root. + * { + * account: + * ledger_hash: // optional + * ledger_index: // optional + * type: // optional, defaults to all account objects types + * limit: // optional + * marker: // optional, resume previous query + * } + */ json::Value doAccountNFTs(RPC::JsonContext& context) { diff --git a/src/xrpld/rpc/handlers/account/AccountObjects.cpp b/src/xrpld/rpc/handlers/account/AccountObjects.cpp index 8375feb4e7..4a34ff02fc 100644 --- a/src/xrpld/rpc/handlers/account/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/account/AccountObjects.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -26,15 +27,17 @@ namespace xrpl { -/** Gathers all objects for an account in a ledger. - @param ledger Ledger to search account objects. - @param account AccountID to find objects for. - @param typeFilter Gathers objects of these types. empty gathers all types. - @param dirIndex Begin gathering account objects from this directory. - @param entryIndex Begin gathering objects from this directory node. - @param limit Maximum number of objects to find. - @param jvResult A JSON result that holds the request objects. -*/ +/** + * Gathers all objects for an account in a ledger. + * @param ledger Ledger to search account objects. + * @param account AccountID to find objects for. + * @param typeFilter Gathers objects of these types. empty gathers all types. + * @param dirIndex Begin gathering account objects from this directory. + * @param entryIndex Begin gathering objects from this directory node. + * @param limit Maximum number of objects to find. + * @param sponsoredFilter If set, only return objects whose sponsored state matches the value. + * @param jvResult A JSON result that holds the request objects. + */ bool getAccountObjects( ReadView const& ledger, @@ -43,6 +46,7 @@ getAccountObjects( uint256 dirIndex, uint256 entryIndex, std::uint32_t const limit, + std::optional const sponsoredFilter, json::Value& jvResult) { // check if dirIndex is valid @@ -55,6 +59,12 @@ getAccountObjects( return it != typeFilter.end(); }; + auto sponsoredMatchesFilter = [&sponsoredFilter](std::optional const& sponsor) { + if (!sponsoredFilter.has_value()) + return true; + return sponsor.has_value() == *sponsoredFilter; + }; + // if dirIndex != 0, then all NFTs have already been returned. only // iterate NFT pages if the filter says so AND dirIndex == 0 bool iterateNFTPages = @@ -93,7 +103,10 @@ getAccountObjects( while (currentPage) { - jvObjects.append(currentPage->getJson(JsonOptions::Values::None)); + std::optional const nftSponsor = currentPage->at(~sfSponsor); + bool const canAppendNFT = sponsoredMatchesFilter(nftSponsor); + if (canAppendNFT) + jvObjects.append(currentPage->getJson()); auto const npm = (*currentPage)[~sfNextPageMin]; if (npm) { @@ -179,11 +192,32 @@ getAccountObjects( { auto const sleNode = ledger.read(keylet::child(*entryIter)); - if (!typeFilter.has_value() || - typeMatchesFilter(typeFilter.value(), sleNode->getType())) + bool canAppend = true; + + if (typeFilter.has_value() && + !typeMatchesFilter(typeFilter.value(), sleNode->getType())) + canAppend = false; + + // An object counts as sponsored no matter which party's directory + // it was found through; the sponsorship need not belong to + // `account`'s side. + std::optional sponsor; + if (sleNode->getType() == ltRIPPLE_STATE) { - jvObjects.append(sleNode->getJson(JsonOptions::Values::None)); + sponsor = getLedgerEntryReserveSponsorID(sleNode, sfHighSponsor); + if (!sponsor) + sponsor = getLedgerEntryReserveSponsorID(sleNode, sfLowSponsor); } + else if (isLedgerEntrySupportedBySponsorship(*sleNode)) + { + sponsor = getLedgerEntryReserveSponsorID(sleNode); + } + + if (!sponsoredMatchesFilter(sponsor)) + canAppend = false; + + if (canAppend) + jvObjects.append(sleNode->getJson(JsonOptions::Values::None)); if (++itemsAdded == limitLeft) { @@ -271,6 +305,7 @@ doAccountObjects(RPC::JsonContext& context) {.name = jss::mptoken, .type = ltMPTOKEN}, {.name = jss::permissioned_domain, .type = ltPERMISSIONED_DOMAIN}, {.name = jss::vault, .type = ltVAULT}, + {.name = jss::sponsorship, .type = ltSPONSORSHIP}, }; typeFilter.emplace(); @@ -329,7 +364,18 @@ doAccountObjects(RPC::JsonContext& context) return RPC::invalidFieldError(jss::marker); } - if (!getAccountObjects(*ledger, accountID, typeFilter, dirIndex, entryIndex, limit, result)) + std::optional sponsoredFilter; + if (params.isMember(jss::sponsored)) + { + auto const& sponsoredJv = params[jss::sponsored]; + if (!sponsoredJv.isBool()) + return RPC::expectedFieldError(jss::sponsored, "boolean"); + + sponsoredFilter = sponsoredJv.asBool(); + } + + if (!getAccountObjects( + *ledger, accountID, typeFilter, dirIndex, entryIndex, limit, sponsoredFilter, result)) return RPC::invalidFieldError(jss::marker); result[jss::account] = toBase58(accountID); diff --git a/src/xrpld/rpc/handlers/account/AccountTx.cpp b/src/xrpld/rpc/handlers/account/AccountTx.cpp index 6c6d2bb6fd..c43f560861 100644 --- a/src/xrpld/rpc/handlers/account/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/account/AccountTx.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,48 @@ namespace xrpl { +static std::expected +parseDelegateFilter(json::Value const& delegateNode) +{ + if (!delegateNode.isObject()) + return std::unexpected(RPC::invalidFieldError(jss::delegate)); + + if (!delegateNode.isMember(jss::delegate_filter) || + !delegateNode[jss::delegate_filter].isString()) + return std::unexpected(RPC::invalidFieldError(jss::delegate_filter)); + + auto const& delegateFilterStr = delegateNode[jss::delegate_filter].asString(); + + auto typeResult = [&] -> std::expected { + if (delegateFilterStr == "actor") + return DelegateType::Actor; + + if (delegateFilterStr == "authorizer") + return DelegateType::Authorizer; + + return std::unexpected(RPC::invalidFieldError(jss::delegate_filter)); + }(); + + if (!typeResult) + return std::unexpected(typeResult.error()); + + DelegateType const type = *typeResult; + + std::optional counterparty; + if (delegateNode.isMember(jss::counter_party)) + { + if (!delegateNode[jss::counter_party].isString()) + return std::unexpected(RPC::invalidFieldError(jss::counter_party)); + + counterparty = parseBase58(delegateNode[jss::counter_party].asString()); + + if (!counterparty) + return std::unexpected(rpcError(RpcActMalformed)); + } + + return DelegateFilter{.type = type, .counterparty = counterparty}; +} + using TxnsData = RelationalDatabase::AccountTxs; using TxnsDataBinary = RelationalDatabase::MetaTxsList; using TxnDataBinary = RelationalDatabase::txnMetaLedgerType; @@ -231,7 +274,8 @@ doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) .ledgerRange = result.ledgerRange, .marker = result.marker, .limit = args.limit, - .bAdmin = isUnlimited(context.role)}; + .bAdmin = isUnlimited(context.role), + .delegate = args.delegate}; auto& db = context.app.getRelationalDatabase(); @@ -370,6 +414,9 @@ populateJsonResponse( response[jss::marker] = json::ValueType::Object; response[jss::marker][jss::ledger] = result.marker->ledgerSeq; response[jss::marker][jss::seq] = result.marker->txnSeq; + + if (args.delegate) + response[jss::marker][jss::delegate] = true; } } @@ -386,7 +433,17 @@ populateJsonResponse( // limit: integer, // optional // marker: object {ledger: ledger_index, seq: txn_sequence} // optional, // resume previous query +// delegate: object { // optional +// delegate_filter: string, // required; "actor" or "authorizer" +// counter_party: account // optional +// } // } +// +// Pagination note for delegate-filtered queries: the `delegate` object (both +// `delegate_filter` and `counter_party`) must be supplied unchanged on every +// paginated request until the query completes. A marker returned by a +// delegate-filtered query is only valid for a follow-up request that repeats +// the same `delegate` object json::Value doAccountTx(RPC::JsonContext& context) { @@ -454,6 +511,38 @@ doAccountTx(RPC::JsonContext& context) .ledgerSeq = token[jss::ledger].asUInt(), .txnSeq = token[jss::seq].asUInt()}; } + if (params.isMember(jss::delegate)) + { + if (auto const filter = parseDelegateFilter(params[jss::delegate]); filter.has_value()) + { + args.delegate = *filter; + } + else + { + return filter.error(); + } + } + + // A marker produced by a delegate-filtered query uses a different + // pagination cursor than a normal query, so it is only valid when the same + // `delegate` object is supplied again. Reject any mismatch so pagination + // cannot silently skip or duplicate results. + if (args.marker) + { + bool const markerFromDelegate = params[jss::marker].isMember(jss::delegate) && + params[jss::marker][jss::delegate].isBool() && + params[jss::marker][jss::delegate].asBool(); + if (markerFromDelegate != args.delegate.has_value()) + { + RPC::Status const status{ + RpcInvalidParams, + "Do not mix delegate and non-delegate pagination markers in account_tx; " + "repeat the same `delegate` object when using a delegate marker."}; + status.inject(response); + return response; + } + } + auto res = doAccountTxHelp(context, args); JLOG(context.j.debug()) << __func__ << " populating response"; return populateJsonResponse(res, args, context); diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index 2dd7ae34d4..784be779bb 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -719,6 +719,28 @@ parseSignerList( return parseObjectID(params, fieldName, "hex string"); } +static std::expected +parseSponsorship( + json::Value const& params, + json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) +{ + if (!params.isObject()) + return parseObjectID(params, fieldName); + + auto const sponsorID = + LedgerEntryHelpers::requiredAccountID(params, jss::sponsor, "malformedSponsor"); + if (!sponsorID) + return std::unexpected(sponsorID.error()); + + auto const sponseeID = + LedgerEntryHelpers::requiredAccountID(params, jss::sponsee, "malformedSponsee"); + if (!sponseeID) + return std::unexpected(sponseeID.error()); + + return keylet::sponsorship(*sponsorID, *sponseeID).key; +} + static std::expected parseTicket( json::Value const& params, diff --git a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp index d2f596ed8e..632456a3fa 100644 --- a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp @@ -42,7 +42,8 @@ using namespace boost::bimaps; // sorted descending by lastUpdateTime, ascending by AssetPrice using Prices = bimap>, multiset_of>; -/** Calls callback "f" on the ledger-object sle and up to three previous +/** + * Calls callback "f" on the ledger-object sle and up to three previous * metadata objects. Stops early if the callback returns true. */ static void diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index 1e9e0ddc14..cce1b3e07f 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -108,7 +108,7 @@ ServerDefinitions::translate(std::string const& inp) if (token.size() > 1) { boost::algorithm::to_lower(token); - token.data()[0] -= ('a' - 'A'); + token[0] -= ('a' - 'A'); out += token; } else diff --git a/src/xrpld/rpc/handlers/transaction/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp index 2a6c2280e3..0f163c7356 100644 --- a/src/xrpld/rpc/handlers/transaction/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -77,7 +77,7 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) } static std::optional -autofillSignature(json::Value& sigObject) +autofillSignature(json::Value& sigObject, std::string const& fieldPrefix = "tx") { if (!sigObject.isMember(jss::SigningPubKey)) { @@ -88,14 +88,17 @@ autofillSignature(json::Value& sigObject) if (sigObject.isMember(jss::Signers)) { if (!sigObject[jss::Signers].isArray()) - return RPC::invalidFieldError("tx.Signers"); + return RPC::invalidFieldError(fieldPrefix + ".Signers"); // check multisigned signers for (unsigned index = 0; index < sigObject[jss::Signers].size(); index++) { auto& signer = sigObject[jss::Signers][index]; if (!signer.isObject() || !signer.isMember(jss::Signer) || !signer[jss::Signer].isObject()) - return RPC::invalidFieldError("tx.Signers[" + std::to_string(index) + "]"); + { + return RPC::invalidFieldError( + fieldPrefix + ".Signers[" + std::to_string(index) + "]"); + } if (!signer[jss::Signer].isMember(jss::SigningPubKey)) { @@ -132,26 +135,19 @@ autofillSignature(json::Value& sigObject) static std::optional autofillTx(json::Value& txJson, RPC::JsonContext& context) { - if (!txJson.isMember(jss::Fee)) - { - // autofill Fee - // Must happen after all the other autofills happen - // Error handling/messaging works better that way - auto feeOrError = RPC::getCurrentNetworkFee( - context.role, - context.app.config(), - context.app.getFeeTrack(), - context.app.getTxQ(), - context.app, - txJson); - if (feeOrError.isMember(jss::error)) - return feeOrError; - txJson[jss::Fee] = feeOrError; - } - if (auto error = autofillSignature(txJson)) return error; + if (txJson.isMember(sfSponsorSignature.jsonName)) + { + auto& sponsorSignature = txJson[sfSponsorSignature.jsonName]; + if (!sponsorSignature.isObject()) + return RPC::objectFieldError(sfSponsorSignature.jsonName); + + if (auto const error = autofillSignature(sponsorSignature, "tx.SponsorSignature")) + return error; + } + if (!txJson.isMember(jss::Sequence)) { auto const seq = getAutofillSequence(txJson, context); @@ -167,6 +163,22 @@ autofillTx(json::Value& txJson, RPC::JsonContext& context) txJson[jss::NetworkID] = to_string(networkId); } + if (!txJson.isMember(jss::Fee)) + { + // Autofill Fee after normalizing nested signer fields so the fee + // estimator sees the full transaction shape. + auto feeOrError = RPC::getCurrentNetworkFee( + context.role, + context.app.config(), + context.app.getFeeTrack(), + context.app.getTxQ(), + context.app, + txJson); + if (feeOrError.isMember(jss::error)) + return feeOrError; + txJson[jss::Fee] = feeOrError; + } + return std::nullopt; } diff --git a/src/xrpld/rpc/json_body.h b/src/xrpld/rpc/json_body.h index 49c2b0e6e0..0f56b852f7 100644 --- a/src/xrpld/rpc/json_body.h +++ b/src/xrpld/rpc/json_body.h @@ -13,7 +13,9 @@ namespace xrpl { -/// Body that holds JSON +/** + * Body that holds JSON + */ struct JsonBody { explicit JsonBody() = default;