mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-25 00:01:04 +00:00
Compare commits
30 Commits
bthomee/sh
...
pratik/std
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec82ee704 | ||
|
|
c6224c48ed | ||
|
|
5fb1429e3a | ||
|
|
b6526522fe | ||
|
|
61041155d2 | ||
|
|
5ea72b45f1 | ||
|
|
7385004952 | ||
|
|
a6fea1227f | ||
|
|
968f9e1bf3 | ||
|
|
70813b336a | ||
|
|
ab8350b558 | ||
|
|
072e11ecd1 | ||
|
|
f669b70451 | ||
|
|
62839b1531 | ||
|
|
9bddc54722 | ||
|
|
25b9b4d27d | ||
|
|
22ed85c3ad | ||
|
|
8c7a27d721 | ||
|
|
9266c23ef6 | ||
|
|
5e9ce16de6 | ||
|
|
8d4ea00453 | ||
|
|
bdcf094171 | ||
|
|
a42e8174d9 | ||
|
|
91004209e9 | ||
|
|
4c18dd867c | ||
|
|
fb6ece56f2 | ||
|
|
42cced50fb | ||
|
|
0e815aa1ac | ||
|
|
21149a81e3 | ||
|
|
b78202a99a |
@@ -256,6 +256,8 @@ words:
|
||||
- replayer
|
||||
- repodata
|
||||
- repomd
|
||||
- repost
|
||||
- reposts
|
||||
- rerandomize
|
||||
- rerandomization
|
||||
- rerandomized
|
||||
@@ -303,6 +305,7 @@ words:
|
||||
- sponsees
|
||||
- SRPMS
|
||||
- sslws
|
||||
- stackful
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
@@ -366,7 +369,6 @@ words:
|
||||
- venv
|
||||
- vfalco
|
||||
- vinnie
|
||||
- vkeylet
|
||||
- wasmi
|
||||
- wextra
|
||||
- wptr
|
||||
|
||||
39
.github/actions/cargo-cache/action.yml
vendored
39
.github/actions/cargo-cache/action.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Use cargo artifacts cache
|
||||
description: >
|
||||
Cache the cargo build artifacts with rust-cache. Never caches ~/.cargo/bin:
|
||||
when saving the cache, rust-cache deletes all binaries that were already
|
||||
present there, which on persistent self-hosted runners wipes the tools
|
||||
installed by prepare-runner. Harmless on ephemeral runners, but kept
|
||||
consistent everywhere.
|
||||
|
||||
inputs:
|
||||
workspaces:
|
||||
description: "Workspaces to cache, as 'workspace -> target' lines."
|
||||
required: false
|
||||
default: crates
|
||||
key:
|
||||
description: "Additional part of the cache key."
|
||||
required: false
|
||||
default: ""
|
||||
cache-directories:
|
||||
description: "Additional non-workspace directories to cache."
|
||||
required: false
|
||||
default: ""
|
||||
save-if:
|
||||
description: >
|
||||
Condition for saving the cache after the job. Defaults to save only from develop branch
|
||||
required: false
|
||||
default: ${{ github.ref == 'refs/heads/develop' }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-bin: "false"
|
||||
cache-directories: ${{ inputs.cache-directories }}
|
||||
key: ${{ inputs.key }}
|
||||
save-if: ${{ inputs.save-if }}
|
||||
workspaces: ${{ inputs.workspaces }}
|
||||
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@@ -4,7 +4,6 @@ updates:
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/cargo-cache/
|
||||
- .github/actions/release-info/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
|
||||
2
.github/workflows/build-nix-images.yml
vendored
2
.github/workflows/build-nix-images.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
|
||||
with:
|
||||
image_name: xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
|
||||
2
.github/workflows/build-packaging-images.yml
vendored
2
.github/workflows/build-packaging-images.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
# AlmaLinux rather than UBI9, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: almalinux:9
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
|
||||
2
.github/workflows/build-pre-commit-image.yml
vendored
2
.github/workflows/build-pre-commit-image.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
|
||||
with:
|
||||
image_name: xrpld/pre-commit
|
||||
dockerfile: bin/pre-commit/Dockerfile
|
||||
|
||||
2
.github/workflows/check-tools.yml
vendored
2
.github/workflows/check-tools.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@f1952595d212e86169935135efc66294b4574131
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@3ba08d6ddf114092891d48491fc2e26c3ba15552
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-f56b79f" }'
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -163,10 +163,11 @@ jobs:
|
||||
compiler: ${{ inputs.compiler }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
key: ${{ inputs.config_name }}
|
||||
save-if: ${{ github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release') }}
|
||||
# two workspaces here because build artifacts are located in 2 places:
|
||||
# - crates/target when cargo is called directly
|
||||
# - build/cargo when cargo is called by cmake
|
||||
|
||||
7
.github/workflows/reusable-clang-tidy.yml
vendored
7
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
determine-files:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@70145243b905dc3e040a61d39c00e178cfb96f71
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@d041ac9f1fa9f07a4ba335eb4c1c82233fb3fef6
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -60,9 +60,10 @@ jobs:
|
||||
compiler: ${{ env.COMPILER }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
save-if: ${{ github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release') }}
|
||||
workspaces: crates -> ../${{ env.BUILD_DIR }}/cargo
|
||||
|
||||
- name: Setup Conan
|
||||
|
||||
12
.github/workflows/reusable-rust.yml
vendored
12
.github/workflows/reusable-rust.yml
vendored
@@ -33,7 +33,9 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
@@ -46,7 +48,9 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Generate coverage report
|
||||
run: cargo llvm-cov nextest --workspace --all-features --locked --no-tests=warn --lcov --output-path lcov.info
|
||||
@@ -72,7 +76,9 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -54,9 +54,6 @@ This section contains changes targeting a future version.
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
|
||||
|
||||
@@ -1094,8 +1094,8 @@
|
||||
# Default is 100.
|
||||
#
|
||||
# back_off_milliseconds
|
||||
# Number of milliseconds to wait between online_delete
|
||||
# SQL deletion batches to allow other functions
|
||||
# Number of milliseconds to wait between
|
||||
# online_delete batches to allow other functions
|
||||
# to catch up.
|
||||
# Default is 100.
|
||||
#
|
||||
@@ -1109,22 +1109,10 @@
|
||||
# The online delete process checks periodically
|
||||
# that xrpld is still in sync with the network,
|
||||
# and that the validated ledger is less than
|
||||
# 'age_threshold_seconds' old, and that all
|
||||
# recent ledgers are available. If not, then continue
|
||||
# 'age_threshold_seconds' old. If not, then continue
|
||||
# sleeping for this number of seconds and
|
||||
# checking until healthy.
|
||||
# Default is 2.
|
||||
#
|
||||
# max_waiting_ledgers
|
||||
# The maximum number of ledgers that may be validated
|
||||
# while online deletion is waiting for the node to get
|
||||
# fully synced with the rest of the network. If more than
|
||||
# this number of ledgers are validated while waiting, then
|
||||
# online deletion gives up on the current ledger and tries
|
||||
# again later. Note this only affects situations that cause
|
||||
# rotation to wait, such as going out of sync, or missing
|
||||
# ledgers. Forward progress is not penalized. Minimum is 64.
|
||||
# Default is the online_delete value.
|
||||
# Default is 5.
|
||||
#
|
||||
# Notes:
|
||||
# The 'node_db' entry configures the primary, persistent storage.
|
||||
|
||||
@@ -120,10 +120,7 @@ if(MSVC)
|
||||
_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:Debug>>:_CRTDBG_MAP_ALLOC>
|
||||
)
|
||||
target_link_libraries(
|
||||
common
|
||||
INTERFACE -errorreport:none -machine:X64 -ignore:4099
|
||||
)
|
||||
target_link_libraries(common INTERFACE -errorreport:none -machine:X64)
|
||||
else()
|
||||
target_compile_options(
|
||||
common
|
||||
|
||||
@@ -8,7 +8,6 @@ Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
|
||||
|
||||
import io
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
import pyparsing as pp
|
||||
|
||||
@@ -54,89 +53,28 @@ def create_transaction_parser():
|
||||
return macro_parser
|
||||
|
||||
|
||||
# Defaults for xrpl::TxSettings members, mirroring
|
||||
# include/xrpl/protocol/TxSettings.h. A transaction's settings blob only names
|
||||
# the members that differ from these.
|
||||
SETTING_DEFAULTS = {
|
||||
"delegable": "Delegation::NotDelegable",
|
||||
"amendment": "uint256{}",
|
||||
"privileges": "Privilege::NoPriv",
|
||||
}
|
||||
|
||||
|
||||
def parse_settings(settings_str):
|
||||
"""Parse a TxSettings blob into a dict, filling in defaults.
|
||||
|
||||
Args:
|
||||
settings_str: A string like '({.delegable = Delegation::NotDelegable,
|
||||
.privileges = Privilege::CreateAcct})', or '({})'.
|
||||
|
||||
Returns:
|
||||
A dict with a value for every key in SETTING_DEFAULTS.
|
||||
"""
|
||||
body = settings_str.strip()
|
||||
if not (body.startswith("(") and body.endswith(")")):
|
||||
raise ValueError(
|
||||
f"Malformed settings blob, expected '({{...}})': {settings_str!r}"
|
||||
)
|
||||
body = body[1:-1].strip()
|
||||
if not (body.startswith("{") and body.endswith("}")):
|
||||
raise ValueError(
|
||||
f"Malformed settings blob, expected '({{...}})': {settings_str!r}"
|
||||
)
|
||||
body = body[1:-1]
|
||||
|
||||
# Strip comments, which may be interleaved with the designated initializers.
|
||||
body = re.sub(r"//[^\n]*", "", body)
|
||||
|
||||
settings = dict(SETTING_DEFAULTS)
|
||||
seen = set()
|
||||
# Each entry runs from '.key =' up to the next '.key =' or the end.
|
||||
for key, value in re.findall(
|
||||
r"\.(\w+)\s*=\s*(.*?)(?=,\s*\.\w+\s*=|,?\s*$)", body, re.S
|
||||
):
|
||||
if key not in SETTING_DEFAULTS:
|
||||
raise ValueError(f"Unknown TxSettings member '.{key}' in {settings_str!r}")
|
||||
settings[key] = " ".join(value.split()).rstrip(",")
|
||||
seen.add(key)
|
||||
|
||||
# Catch a typo'd or unparsed initializer rather than silently defaulting it.
|
||||
# Every '.member' in the blob must have been consumed above.
|
||||
if len(re.findall(r"\.\w+", body)) != len(seen):
|
||||
raise ValueError(f"Could not parse every setting in {settings_str!r}")
|
||||
|
||||
# A blob with content but no designated initializer is positional, which
|
||||
# would otherwise be read as "all defaults" and silently generate the
|
||||
# wrong output.
|
||||
if body.strip() and not seen:
|
||||
raise ValueError(
|
||||
"TxSettings requires designated initializers (.member = value), "
|
||||
f"got {settings_str!r}"
|
||||
)
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def parse_transaction_args(args_list):
|
||||
"""Parse the arguments of a TRANSACTION macro call.
|
||||
|
||||
Args:
|
||||
args_list: A list of parsed arguments from pyparsing, e.g.,
|
||||
['ttPAYMENT', '0', 'Payment',
|
||||
'({.privileges = Privilege::CreateAcct})', '({...})']
|
||||
['ttPAYMENT', '0', 'Payment', 'Delegation::delegable',
|
||||
'uint256{}', 'createAcct', '({...})']
|
||||
|
||||
Returns:
|
||||
A dict with parsed transaction information.
|
||||
"""
|
||||
if len(args_list) < 5:
|
||||
if len(args_list) < 7:
|
||||
raise ValueError(
|
||||
f"Expected at least 5 parts in TRANSACTION, got {len(args_list)}: {args_list}"
|
||||
f"Expected at least 7 parts in TRANSACTION, got {len(args_list)}: {args_list}"
|
||||
)
|
||||
|
||||
tag = args_list[0]
|
||||
value = args_list[1]
|
||||
name = args_list[2]
|
||||
settings = parse_settings(args_list[3])
|
||||
delegable = args_list[3]
|
||||
amendments = args_list[4]
|
||||
privileges = args_list[5]
|
||||
fields_str = args_list[-1]
|
||||
|
||||
# Parse fields: ({field1, field2, ...})
|
||||
@@ -146,9 +84,9 @@ def parse_transaction_args(args_list):
|
||||
"tag": tag,
|
||||
"value": value,
|
||||
"name": name,
|
||||
"delegable": settings["delegable"],
|
||||
"amendments": settings["amendment"],
|
||||
"privileges": settings["privileges"],
|
||||
"delegable": delegable,
|
||||
"amendments": amendments,
|
||||
"privileges": privileges,
|
||||
"fields": fields,
|
||||
}
|
||||
|
||||
|
||||
20
crates/Cargo.lock
generated
20
crates/Cargo.lock
generated
@@ -57,9 +57,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.199"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824894a4a85dca76d4c95c2b9098c036f5a29f627b30c12780774f6654e60974"
|
||||
checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx-build",
|
||||
@@ -72,9 +72,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.199"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1ae0b651ea5b0000b19513aef5a03f194d7e3486f2d9258b658da8677fe9036"
|
||||
checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
@@ -87,9 +87,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.199"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb05f91d3fb8435d9bab6ac5ce6ac1868be774325fb7fb2a91be39393b21388e"
|
||||
checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
@@ -101,15 +101,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.199"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf293202e0e3e98495785745389e8d0755b217e66f19194a5c695c25e03282ef"
|
||||
checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.199"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca001d746947c7249ed9d332a10f7a59daedbafeb0ec68c5c18a7db7a93f6ccc"
|
||||
checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
|
||||
@@ -65,7 +65,7 @@ wherever it appears in the repository configuration.
|
||||
4. Add the repository, using the channel you picked in [Release channels](#release-channels):
|
||||
|
||||
```bash
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xrplf.asc] https://packages.xrplf.org/repository/deb-stable any main" | \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xrplf.asc] https://packages.xrplf.org/repository/deb-stable focal main" | \
|
||||
sudo tee /etc/apt/sources.list.d/xrplf.list
|
||||
```
|
||||
|
||||
@@ -92,19 +92,19 @@ wherever it appears in the repository configuration.
|
||||
2. Add the repository, using the channel you picked in [Release channels](#release-channels):
|
||||
|
||||
```bash
|
||||
cat << 'REPOFILE' | sudo tee /etc/yum.repos.d/xrplf.repo
|
||||
cat << REPOFILE | sudo tee /etc/yum.repos.d/xrplf.repo
|
||||
[xrplf-stable]
|
||||
name=XRP Ledger Packages
|
||||
enabled=1
|
||||
baseurl=https://packages.xrplf.org/repository/rpm-stable/$basearch/
|
||||
baseurl=https://packages.xrplf.org/repository/rpm-stable/
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
repo_gpgcheck=0
|
||||
gpgkey=https://packages.xrplf.org/xrplf.asc
|
||||
REPOFILE
|
||||
```
|
||||
|
||||
`gpgcheck=1` verifies each package against the key above.
|
||||
`repo_gpgcheck=1` verifies the repository metadata, which the server signs with the same key.
|
||||
`repo_gpgcheck` is off because the repository metadata is generated by the server and is not signed.
|
||||
|
||||
3. Install the `xrpld` package:
|
||||
|
||||
|
||||
@@ -529,30 +529,11 @@ staticPointerCast(TT const& v)
|
||||
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
// A bare `TT&&` here would be a forwarding reference, since TT is deduced directly from this
|
||||
// parameter, and would then also bind to lvalues in preference to the `const&` overload above
|
||||
// (binding to a plain reference beats binding to a const one), silently moving out of a caller's
|
||||
// live variable on what looks like a copy call. Naming the wrapped type keeps TT nested inside
|
||||
// SharedIntrusive<TT>, so this only binds to an actual rvalue of that type.
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
staticPointerCast(SharedIntrusive<TT>&& v)
|
||||
{
|
||||
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, std::move(v));
|
||||
}
|
||||
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
dynamicPointerCast(TT const& v)
|
||||
{
|
||||
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, v);
|
||||
}
|
||||
|
||||
template <class T, class TT>
|
||||
SharedPtr<T>
|
||||
dynamicPointerCast(SharedIntrusive<TT>&& v)
|
||||
{
|
||||
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, std::move(v));
|
||||
}
|
||||
} // namespace intr_ptr
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -125,7 +125,6 @@ struct Keys
|
||||
static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger";
|
||||
static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account";
|
||||
static constexpr auto kMemoryLevel = "memory_level";
|
||||
static constexpr auto kMaxWaitingLedgers = "max_waiting_ledgers";
|
||||
static constexpr auto kMinLedgersToComputeSizeLimit = "min_ledgers_to_compute_size_limit";
|
||||
static constexpr auto kMinimumEscalationMultiplier = "minimum_escalation_multiplier";
|
||||
static constexpr auto kMinimumLastLedgerBuffer = "minimum_last_ledger_buffer";
|
||||
|
||||
713
include/xrpl/core/CoroTask.h
Normal file
713
include/xrpl/core/CoroTask.h
Normal file
@@ -0,0 +1,713 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <coroutine>
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename T = void>
|
||||
class CoroTask;
|
||||
|
||||
/**
|
||||
* CoroTask<void> -- coroutine return type for void-returning coroutines.
|
||||
*
|
||||
* Class / Dependency Diagram
|
||||
* ==========================
|
||||
*
|
||||
* CoroTask<void>
|
||||
* +-----------------------------------------------+
|
||||
* | - handle_ : Handle (coroutine_handle<promise>) |
|
||||
* +-----------------------------------------------+
|
||||
* | + handle(), done() |
|
||||
* | + await_ready/suspend/resume (Awaiter iface) |
|
||||
* +-----------------------------------------------+
|
||||
* | owns
|
||||
* v
|
||||
* promise_type
|
||||
* +-----------------------------------------------+
|
||||
* | - exception_ : std::exception_ptr |
|
||||
* | - continuation_ : std::coroutine_handle<> |
|
||||
* +-----------------------------------------------+
|
||||
* | + get_return_object() -> CoroTask |
|
||||
* | + initial_suspend() -> suspend_always (lazy) |
|
||||
* | + final_suspend() -> FinalAwaiter |
|
||||
* | + return_void() |
|
||||
* | + unhandled_exception() |
|
||||
* +-----------------------------------------------+
|
||||
* | returns at final_suspend
|
||||
* v
|
||||
* FinalAwaiter
|
||||
* +-----------------------------------------------+
|
||||
* | await_suspend(h): |
|
||||
* | if continuation_ set -> symmetric transfer |
|
||||
* | else -> noop_coroutine |
|
||||
* +-----------------------------------------------+
|
||||
*
|
||||
* Design Notes
|
||||
* ------------
|
||||
* - Lazy start: initial_suspend returns suspend_always, so the coroutine
|
||||
* body does not execute until the handle is explicitly resumed.
|
||||
* - Symmetric transfer: await_suspend returns a coroutine_handle instead
|
||||
* of void/bool, allowing the scheduler to jump directly to the next
|
||||
* coroutine without growing the call stack.
|
||||
* - Continuation chaining: when one CoroTask is co_await-ed inside
|
||||
* another, the caller's handle is stored as continuation_ so
|
||||
* FinalAwaiter can resume it when this task finishes.
|
||||
* - Move-only: the handle is exclusively owned; copy is deleted.
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Basic void coroutine (the most common case in xrpld):
|
||||
*
|
||||
* CoroTask<void> doWork(std::shared_ptr<CoroTaskRunner> runner) {
|
||||
* // do something
|
||||
* co_await runner->suspend(); // yield control
|
||||
* // resumed later via runner->post() or runner->resume()
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 2. co_await-ing one CoroTask<void> from another (chaining):
|
||||
*
|
||||
* CoroTask<void> inner() {
|
||||
* // ...
|
||||
* co_return;
|
||||
* }
|
||||
* CoroTask<void> outer() {
|
||||
* co_await inner(); // continuation_ links outer -> inner
|
||||
* co_return; // FinalAwaiter resumes outer
|
||||
* }
|
||||
*
|
||||
* 3. Exceptions propagate through co_await:
|
||||
*
|
||||
* CoroTask<void> failing() {
|
||||
* throw std::runtime_error("oops");
|
||||
* co_return;
|
||||
* }
|
||||
* CoroTask<void> caller() {
|
||||
* try { co_await failing(); }
|
||||
* catch (std::runtime_error const&) { // caught here }
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Dangling references in coroutine parameters.
|
||||
* Coroutine parameters are copied into the frame, but references
|
||||
* are NOT -- they are stored as-is. If the referent goes out of scope
|
||||
* before the coroutine finishes, you get use-after-free.
|
||||
*
|
||||
* // BROKEN -- local dies before coroutine runs:
|
||||
* CoroTask<void> bad(int& ref) { co_return; }
|
||||
* void launch() {
|
||||
* int local = 42;
|
||||
* auto task = bad(local); // frame stores &local
|
||||
* } // local destroyed; frame holds dangling ref
|
||||
*
|
||||
* // FIX -- pass by value, or ensure lifetime via shared_ptr.
|
||||
*
|
||||
* BUG-RISK: GCC 14 corrupts reference captures in coroutine lambdas.
|
||||
* When a lambda that returns CoroTask captures by reference ([&]),
|
||||
* GCC 14 may generate a corrupted coroutine frame. Always capture
|
||||
* by explicit pointer-to-value instead:
|
||||
*
|
||||
* // BROKEN on GCC 14:
|
||||
* jq.postCoroTask(t, n, [&](auto) -> CoroTask<void> { ... });
|
||||
*
|
||||
* // FIX -- capture pointers explicitly:
|
||||
* jq.postCoroTask(t, n, [ptr = &val](auto) -> CoroTask<void> { ... });
|
||||
*
|
||||
* BUG-RISK: Resuming a destroyed or completed CoroTask.
|
||||
* Calling handle().resume() after the coroutine has already run to
|
||||
* completion (done() == true) is undefined behavior. The CoroTaskRunner
|
||||
* guards against this with an XRPL_ASSERT, but standalone usage of
|
||||
* CoroTask must check done() before resuming.
|
||||
*
|
||||
* BUG-RISK: Moving a CoroTask that is being awaited.
|
||||
* If task A is co_await-ed by task B (so A.continuation_ == B), moving
|
||||
* or destroying A will invalidate the continuation link. Never move
|
||||
* or reassign a CoroTask while it is mid-execution or being awaited.
|
||||
*
|
||||
* LIMITATION: CoroTask is fire-and-forget for the top-level owner.
|
||||
* There is no built-in notification when the coroutine finishes.
|
||||
* The caller must use external synchronization (e.g. CoroTaskRunner::join
|
||||
* or a gate/condition_variable) to know when it is done.
|
||||
*
|
||||
* LIMITATION: No cancellation token.
|
||||
* There is no way to cancel a suspended CoroTask from outside. The
|
||||
* coroutine body must cooperatively check a flag (e.g. jq_.isStopping())
|
||||
* after each co_await and co_return early if needed.
|
||||
*
|
||||
* LIMITATION: Stackless -- cannot suspend from nested non-coroutine calls.
|
||||
* If a coroutine calls a regular function that wants to "yield", it
|
||||
* cannot. Only the immediate coroutine body can use co_await.
|
||||
* This is acceptable for xrpld because all yield() sites are shallow.
|
||||
*/
|
||||
template <>
|
||||
class CoroTask<void>
|
||||
{
|
||||
public:
|
||||
// The C++ coroutine protocol mandates these names (promise_type,
|
||||
// initial_suspend, await_ready, ...) and instance-callable awaiter
|
||||
// methods, which conflict with the project naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct promise_type;
|
||||
using Handle = std::coroutine_handle<promise_type>;
|
||||
|
||||
/**
|
||||
* Coroutine promise. Compiler uses this to manage coroutine state.
|
||||
* Stores the exception (if any) and the continuation handle for
|
||||
* symmetric transfer back to the awaiting coroutine.
|
||||
*/
|
||||
struct promise_type
|
||||
{
|
||||
// Captured exception from the coroutine body, rethrown in
|
||||
// await_resume() when this task is co_await-ed by a caller.
|
||||
std::exception_ptr exception_;
|
||||
|
||||
// Handle to the coroutine that is co_await-ing this task.
|
||||
// Set by await_suspend(). FinalAwaiter uses it for symmetric
|
||||
// transfer back to the caller. Null if this is a top-level task.
|
||||
std::coroutine_handle<> continuation_;
|
||||
|
||||
/**
|
||||
* Create the CoroTask return object.
|
||||
* Called by the compiler at coroutine creation.
|
||||
*/
|
||||
CoroTask
|
||||
get_return_object()
|
||||
{
|
||||
return CoroTask{Handle::from_promise(*this)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy start. The coroutine body does not execute until the
|
||||
* handle is explicitly resumed (e.g. by CoroTaskRunner::resume).
|
||||
*/
|
||||
std::suspend_always
|
||||
initial_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaiter returned by final_suspend(). Uses symmetric transfer:
|
||||
* if a continuation exists, transfers control directly to it
|
||||
* (tail-call, no stack growth). Otherwise returns noop_coroutine
|
||||
* so the coroutine frame stays alive for the owner to destroy.
|
||||
*/
|
||||
struct FinalAwaiter
|
||||
{
|
||||
/**
|
||||
* Always false. We need await_suspend to run for
|
||||
* symmetric transfer.
|
||||
*/
|
||||
bool
|
||||
await_ready() noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symmetric transfer: returns the continuation handle so
|
||||
* the compiler emits a tail-call instead of a nested resume.
|
||||
* If no continuation is set, returns noop_coroutine to
|
||||
* suspend at final_suspend without destroying the frame.
|
||||
*
|
||||
* @param h Handle to this completing coroutine
|
||||
*
|
||||
* @return Continuation handle, or noop_coroutine
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(Handle h) noexcept
|
||||
{
|
||||
if (auto cont = h.promise().continuation_)
|
||||
return cont;
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns FinalAwaiter for symmetric transfer at coroutine end.
|
||||
*/
|
||||
FinalAwaiter
|
||||
final_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler for `co_return;` (void coroutine).
|
||||
*/
|
||||
void
|
||||
return_void()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler when an exception escapes the coroutine
|
||||
* body. Captures it for later rethrowing in await_resume().
|
||||
*/
|
||||
void
|
||||
unhandled_exception()
|
||||
{
|
||||
exception_ = std::current_exception();
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
/**
|
||||
* Default constructor. Creates an empty (null handle) task.
|
||||
*/
|
||||
CoroTask() = default;
|
||||
|
||||
/**
|
||||
* Takes ownership of a compiler-generated coroutine handle.
|
||||
*
|
||||
* @param h Coroutine handle to own
|
||||
*/
|
||||
explicit CoroTask(Handle h) : handle_(h)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the coroutine frame if this task owns one.
|
||||
*/
|
||||
~CoroTask()
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor. Transfers handle ownership, leaves other empty.
|
||||
*/
|
||||
CoroTask(CoroTask&& other) noexcept : handle_(std::exchange(other.handle_, {}))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Move assignment. Destroys current frame (if any), takes other's.
|
||||
*/
|
||||
CoroTask&
|
||||
operator=(CoroTask&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
handle_ = std::exchange(other.handle_, {});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoroTask(CoroTask const&) = delete;
|
||||
CoroTask&
|
||||
operator=(CoroTask const&) = delete;
|
||||
|
||||
/**
|
||||
* @return The underlying coroutine_handle
|
||||
*/
|
||||
[[nodiscard]] Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
done() const
|
||||
{
|
||||
return handle_ && handle_.done();
|
||||
}
|
||||
|
||||
// -- Awaiter interface: allows `co_await someCoroTask;` --
|
||||
|
||||
/**
|
||||
* Always false. This task is lazy, so co_await always suspends
|
||||
* the caller to set up the continuation link.
|
||||
*/
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the caller's handle as our continuation, then returns
|
||||
* our handle for symmetric transfer (caller suspends, we resume).
|
||||
*
|
||||
* @param caller Handle of the coroutine doing co_await on us
|
||||
*
|
||||
* @return Our handle for symmetric transfer
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> caller) noexcept
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<void>::await_suspend : handle is valid");
|
||||
handle_.promise().continuation_ = caller;
|
||||
return handle_; // Symmetric transfer
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the awaiting coroutine's context after this task
|
||||
* completes. Rethrows any exception captured by
|
||||
* unhandled_exception().
|
||||
*/
|
||||
void
|
||||
await_resume()
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<void>::await_resume : handle is valid");
|
||||
if (auto& ep = handle_.promise().exception_)
|
||||
std::rethrow_exception(ep);
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
private:
|
||||
// Exclusively-owned coroutine handle. Null after move or default
|
||||
// construction. Destroyed in the destructor.
|
||||
Handle handle_;
|
||||
};
|
||||
|
||||
/**
|
||||
* CoroTask<T> -- coroutine return type for value-returning coroutines.
|
||||
*
|
||||
* Class / Dependency Diagram
|
||||
* ==========================
|
||||
*
|
||||
* CoroTask<T>
|
||||
* +-----------------------------------------------+
|
||||
* | - handle_ : Handle (coroutine_handle<promise>) |
|
||||
* +-----------------------------------------------+
|
||||
* | + handle(), done() |
|
||||
* | + await_ready/suspend/resume (Awaiter iface) |
|
||||
* +-----------------------------------------------+
|
||||
* | owns
|
||||
* v
|
||||
* promise_type
|
||||
* +-----------------------------------------------+
|
||||
* | - result_ : variant<monostate, T, |
|
||||
* | exception_ptr> |
|
||||
* | - continuation_ : std::coroutine_handle<> |
|
||||
* +-----------------------------------------------+
|
||||
* | + get_return_object() -> CoroTask |
|
||||
* | + initial_suspend() -> suspend_always (lazy) |
|
||||
* | + final_suspend() -> FinalAwaiter |
|
||||
* | + return_value(T) -> stores in result_[1] |
|
||||
* | + unhandled_exception -> stores in result_[2] |
|
||||
* +-----------------------------------------------+
|
||||
* | returns at final_suspend
|
||||
* v
|
||||
* FinalAwaiter (same symmetric-transfer pattern as CoroTask<void>)
|
||||
*
|
||||
* Value Extraction
|
||||
* ----------------
|
||||
* await_resume() inspects the variant:
|
||||
* - index 2 (exception_ptr) -> rethrow
|
||||
* - index 1 (T) -> return value via move
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Simple value return:
|
||||
*
|
||||
* CoroTask<int> computeAnswer() { co_return 42; }
|
||||
*
|
||||
* CoroTask<void> caller() {
|
||||
* int v = co_await computeAnswer(); // v == 42
|
||||
* }
|
||||
*
|
||||
* 2. Chaining value-returning coroutines:
|
||||
*
|
||||
* CoroTask<int> add(int a, int b) { co_return a + b; }
|
||||
* CoroTask<int> doubleSum(int a, int b) {
|
||||
* int s = co_await add(a, b);
|
||||
* co_return s * 2;
|
||||
* }
|
||||
*
|
||||
* 3. Exception propagation from inner to outer:
|
||||
*
|
||||
* CoroTask<int> failing() {
|
||||
* throw std::runtime_error("bad");
|
||||
* co_return 0; // never reached
|
||||
* }
|
||||
* CoroTask<void> caller() {
|
||||
* try {
|
||||
* int v = co_await failing(); // throws here
|
||||
* } catch (std::runtime_error const& e) {
|
||||
* // e.what() == "bad"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls (in addition to CoroTask<void> caveats above)
|
||||
* ================================================================
|
||||
*
|
||||
* BUG-RISK: await_resume() moves the value out of the variant.
|
||||
* Calling co_await on the same CoroTask<T> instance twice is undefined
|
||||
* behavior -- the second call will see a moved-from T. CoroTask is
|
||||
* single-shot: one co_return, one co_await.
|
||||
*
|
||||
* BUG-RISK: T must be move-constructible.
|
||||
* return_value(T) takes by value and moves into the variant.
|
||||
* Types that are not movable cannot be used as T.
|
||||
*
|
||||
* LIMITATION: No co_yield support.
|
||||
* CoroTask<T> only supports a single co_return. It does not implement
|
||||
* yield_value(), so using co_yield inside a CoroTask<T> coroutine is a
|
||||
* compile error. For streaming values, a different return type
|
||||
* (e.g. Generator<T>) would be needed.
|
||||
*
|
||||
* LIMITATION: Result is only accessible via co_await.
|
||||
* There is no .get() or .result() method. The value can only be
|
||||
* extracted by co_await-ing the CoroTask<T> from inside another
|
||||
* coroutine. For extracting results in non-coroutine code, pass a
|
||||
* pointer to the caller and write through it (as the tests do).
|
||||
*/
|
||||
template <typename T>
|
||||
class CoroTask
|
||||
{
|
||||
static_assert(
|
||||
std::is_move_constructible_v<T>,
|
||||
"CoroTask<T> requires T to be move-constructible");
|
||||
|
||||
public:
|
||||
// The C++ coroutine protocol mandates these names (promise_type,
|
||||
// initial_suspend, await_ready, ...) and instance-callable awaiter
|
||||
// methods, which conflict with the project naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct promise_type;
|
||||
using Handle = std::coroutine_handle<promise_type>;
|
||||
|
||||
/**
|
||||
* Coroutine promise for value-returning coroutines.
|
||||
* Stores the result as a variant: monostate (not yet set),
|
||||
* T (co_return value), or exception_ptr (unhandled exception).
|
||||
*/
|
||||
struct promise_type
|
||||
{
|
||||
// Tri-state result:
|
||||
// index 0 (monostate) -- coroutine has not yet completed
|
||||
// index 1 (T) -- co_return value stored here
|
||||
// index 2 (exception) -- unhandled exception captured here
|
||||
std::variant<std::monostate, T, std::exception_ptr> result_;
|
||||
|
||||
// Handle to the coroutine co_await-ing this task. Used by
|
||||
// FinalAwaiter for symmetric transfer. Null for top-level tasks.
|
||||
std::coroutine_handle<> continuation_;
|
||||
|
||||
/**
|
||||
* Create the CoroTask return object.
|
||||
* Called by the compiler at coroutine creation.
|
||||
*/
|
||||
CoroTask
|
||||
get_return_object()
|
||||
{
|
||||
return CoroTask{Handle::from_promise(*this)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy start. Coroutine body does not run until explicitly resumed.
|
||||
*/
|
||||
std::suspend_always
|
||||
initial_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Symmetric-transfer awaiter at coroutine completion.
|
||||
* Same pattern as CoroTask<void>::FinalAwaiter.
|
||||
*/
|
||||
struct FinalAwaiter
|
||||
{
|
||||
bool
|
||||
await_ready() noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns continuation for symmetric transfer, or
|
||||
* noop_coroutine if this is a top-level task.
|
||||
*
|
||||
* @param h Handle to this completing coroutine
|
||||
*
|
||||
* @return Continuation handle, or noop_coroutine
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(Handle h) noexcept
|
||||
{
|
||||
if (auto cont = h.promise().continuation_)
|
||||
return cont;
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
FinalAwaiter
|
||||
final_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler for `co_return value;`.
|
||||
* Moves the value into result_ at index 1.
|
||||
*
|
||||
* @param value The value to store
|
||||
*/
|
||||
void
|
||||
return_value(T value)
|
||||
{
|
||||
result_.template emplace<1>(std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures unhandled exceptions at index 2 of result_.
|
||||
* Rethrown later in await_resume().
|
||||
*/
|
||||
void
|
||||
unhandled_exception()
|
||||
{
|
||||
result_.template emplace<2>(std::current_exception());
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
/**
|
||||
* Default constructor. Creates an empty (null handle) task.
|
||||
*/
|
||||
CoroTask() = default;
|
||||
|
||||
/**
|
||||
* Takes ownership of a compiler-generated coroutine handle.
|
||||
*
|
||||
* @param h Coroutine handle to own
|
||||
*/
|
||||
explicit CoroTask(Handle h) : handle_(h)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the coroutine frame if this task owns one.
|
||||
*/
|
||||
~CoroTask()
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor. Transfers handle ownership, leaves other empty.
|
||||
*/
|
||||
CoroTask(CoroTask&& other) noexcept : handle_(std::exchange(other.handle_, {}))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Move assignment. Destroys current frame (if any), takes other's.
|
||||
*/
|
||||
CoroTask&
|
||||
operator=(CoroTask&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
handle_ = std::exchange(other.handle_, {});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoroTask(CoroTask const&) = delete;
|
||||
CoroTask&
|
||||
operator=(CoroTask const&) = delete;
|
||||
|
||||
/**
|
||||
* @return The underlying coroutine_handle
|
||||
*/
|
||||
[[nodiscard]] Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
done() const
|
||||
{
|
||||
return handle_ && handle_.done();
|
||||
}
|
||||
|
||||
// -- Awaiter interface: allows `T val = co_await someCoroTask;` --
|
||||
|
||||
/**
|
||||
* Always false. co_await always suspends to set up continuation.
|
||||
*/
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores caller as continuation, returns our handle for
|
||||
* symmetric transfer.
|
||||
*
|
||||
* @param caller Handle of the coroutine doing co_await on us
|
||||
*
|
||||
* @return Our handle for symmetric transfer
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> caller) noexcept
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<T>::await_suspend : handle is valid");
|
||||
handle_.promise().continuation_ = caller;
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the result: rethrows if exception, otherwise moves
|
||||
* the T value out of the variant. Single-shot: calling twice
|
||||
* on the same task is undefined (moved-from T).
|
||||
*
|
||||
* @return The co_return-ed value
|
||||
*/
|
||||
T
|
||||
await_resume()
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<T>::await_resume : handle is valid");
|
||||
auto& result = handle_.promise().result_;
|
||||
if (auto* ep = std::get_if<2>(&result))
|
||||
std::rethrow_exception(*ep);
|
||||
return std::get<1>(std::move(result));
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
private:
|
||||
// Exclusively-owned coroutine handle. Null after move or default
|
||||
// construction. Destroyed in the destructor.
|
||||
Handle handle_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
448
include/xrpl/core/CoroTaskRunner.ipp
Normal file
448
include/xrpl/core/CoroTaskRunner.ipp
Normal file
@@ -0,0 +1,448 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file CoroTaskRunner.ipp
|
||||
*
|
||||
* CoroTaskRunner inline implementation.
|
||||
*
|
||||
* This file contains the business logic for managing C++20 coroutines
|
||||
* on the JobQueue. It is included at the bottom of JobQueue.h.
|
||||
*
|
||||
* Data Flow: suspend / post / resume cycle
|
||||
* =========================================
|
||||
*
|
||||
* coroutine body CoroTaskRunner JobQueue
|
||||
* -------------- -------------- --------
|
||||
* |
|
||||
* co_await runner->suspend()
|
||||
* |
|
||||
* +--- await_suspend ------> onSuspend()
|
||||
* | ++nSuspend_ ------------> nSuspend_
|
||||
* | [coroutine is now suspended]
|
||||
* |
|
||||
* . (externally or by yieldAndPost())
|
||||
* .
|
||||
* +--- (caller calls) -----> post()
|
||||
* | ++runCount_
|
||||
* | addJob(resume) ----------> job enqueued
|
||||
* | |
|
||||
* | [worker picks up]
|
||||
* | |
|
||||
* +--- <----- resume() <-----------------------------------+
|
||||
* | --nSuspend_ ------> nSuspend_
|
||||
* | swap in LocalValues (lvs_)
|
||||
* | task_.handle().resume()
|
||||
* | |
|
||||
* | [coroutine body continues here]
|
||||
* | |
|
||||
* | swap out LocalValues
|
||||
* | --runCount_
|
||||
* | cv_.notify_all()
|
||||
* v
|
||||
*
|
||||
* Thread Safety
|
||||
* =============
|
||||
* - mutex_ : guards task_.handle().resume() so that post()-before-suspend
|
||||
* races cannot resume the coroutine while it is still running.
|
||||
* (See the race condition discussion in JobQueue.h)
|
||||
* - mutexRun_ : guards runCount_ counter; used by join() to wait until
|
||||
* all in-flight resume operations complete.
|
||||
* - jq_.mutex_: guards nSuspend_ increments/decrements.
|
||||
*
|
||||
* Common Mistakes When Modifying This File
|
||||
* =========================================
|
||||
*
|
||||
* 1. Changing lock ordering.
|
||||
* resume() acquires locks sequentially (never held simultaneously):
|
||||
* jq_.mutex_ (released immediately), then mutex_ (held across resume),
|
||||
* then mutexRun_ (released after decrement). post() acquires only
|
||||
* mutexRun_. Any new code path must follow the same order.
|
||||
*
|
||||
* 2. Removing the shared_from_this() capture in post().
|
||||
* The lambda passed to addJob captures [this, sp = shared_from_this()].
|
||||
* If you remove sp, 'this' can be destroyed before the job runs,
|
||||
* causing use-after-free. The sp capture is load-bearing.
|
||||
*
|
||||
* 3. Forgetting to decrement nSuspend_ on a new code path.
|
||||
* Every ++nSuspend_ must have a matching --nSuspend_. If you add a new
|
||||
* suspension path (e.g. a new awaiter) and forget to decrement on resume
|
||||
* or on failure, JobQueue::stop() will hang.
|
||||
*
|
||||
* 4. Calling task_.handle().resume() without holding mutex_.
|
||||
* This allows a race where the coroutine runs on two threads
|
||||
* simultaneously. Always hold mutex_ around resume().
|
||||
*
|
||||
* 5. Swapping LocalValues outside of the mutex_ critical section.
|
||||
* The swap-in and swap-out of LocalValues must bracket the resume()
|
||||
* call. If you move the swap-out before the lock_guard(mutex_) is
|
||||
* released, you break LocalValue isolation for any code that runs
|
||||
* after the coroutine suspends but before the lock is dropped.
|
||||
*/
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Construct a CoroTaskRunner. Sets runCount_ to 0; does not
|
||||
* create the coroutine. Call init() afterwards.
|
||||
*
|
||||
* @param jq The JobQueue this coroutine will run on
|
||||
* @param type Job type for scheduling priority
|
||||
* @param name Human-readable name for logging
|
||||
*/
|
||||
inline JobQueue::CoroTaskRunner::CoroTaskRunner(
|
||||
CreateT,
|
||||
JobQueue& jq,
|
||||
JobType type,
|
||||
std::string name)
|
||||
: jq_(jq), type_(type), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize with a coroutine-returning callable.
|
||||
* Stores the callable on the heap (FuncStore) so it outlives the
|
||||
* coroutine frame. Coroutine frames store a reference to the
|
||||
* callable's implicit object parameter (the lambda). If the callable
|
||||
* is a temporary, that reference dangles after the caller returns.
|
||||
* Keeping the callable alive here ensures the coroutine's captures
|
||||
* remain valid.
|
||||
*
|
||||
* @param f Callable: CoroTask<void>(shared_ptr<CoroTaskRunner>)
|
||||
*/
|
||||
template <class F>
|
||||
void
|
||||
JobQueue::CoroTaskRunner::init(F&& f)
|
||||
{
|
||||
using Fn = std::decay_t<F>;
|
||||
auto store = std::make_unique<FuncStore<Fn>>(std::forward<F>(f));
|
||||
task_ = store->func(shared_from_this());
|
||||
storedFunc_ = std::move(store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor. Waits for any in-flight resume() to complete, then
|
||||
* asserts (debug) that the coroutine has finished or
|
||||
* expectEarlyExit() was called.
|
||||
*
|
||||
* The join() call is necessary because with async dispatch the
|
||||
* coroutine runs on a worker thread. The gate signal (which wakes
|
||||
* the test thread) can arrive before resume() has set finished_.
|
||||
* join() synchronizes via mutexRun_, establishing a happens-before
|
||||
* edge: finished_ = true -> unlock(mutexRun_) in resume() ->
|
||||
* lock(mutexRun_) in join() -> read finished_.
|
||||
*/
|
||||
inline JobQueue::CoroTaskRunner::~CoroTaskRunner()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
join();
|
||||
XRPL_ASSERT(finished_, "xrpl::JobQueue::CoroTaskRunner::~CoroTaskRunner : is finished");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the JobQueue's suspended-coroutine count (nSuspend_).
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onSuspend()
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
++jq_.nSuspend_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement nSuspend_ without resuming.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onUndoSuspend()
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SuspendAwaiter whose await_suspend() increments nSuspend_
|
||||
* before the coroutine actually suspends. The caller must later call
|
||||
* post() or resume() to continue execution.
|
||||
*
|
||||
* @return Awaiter for use with `co_await runner->suspend()`
|
||||
*/
|
||||
inline auto
|
||||
JobQueue::CoroTaskRunner::suspend()
|
||||
{
|
||||
/**
|
||||
* Custom awaiter for suspend(). Always suspends (await_ready
|
||||
* returns false) and increments nSuspend_ in await_suspend().
|
||||
*/
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct SuspendAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_; // The runner that owns this coroutine.
|
||||
|
||||
/**
|
||||
* Always returns false so the coroutine suspends.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the coroutine suspends. Increments nSuspend_
|
||||
* so the JobQueue knows a coroutine is waiting.
|
||||
*/
|
||||
void
|
||||
await_suspend(std::coroutine_handle<>) const
|
||||
{
|
||||
runner_.onSuspend();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
return SuspendAwaiter{*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend and immediately repost on the JobQueue. Equivalent to
|
||||
* `co_await JobQueueAwaiter{runner}` but uses an inline struct
|
||||
* to work around a GCC-12 codegen bug (see declaration in JobQueue.h).
|
||||
*
|
||||
* If the JobQueue is stopping (post fails), the suspend count is
|
||||
* undone and the coroutine continues immediately via symmetric
|
||||
* transfer back to its own handle.
|
||||
*
|
||||
* @return An inline YieldPostAwaiter
|
||||
*/
|
||||
inline auto
|
||||
JobQueue::CoroTaskRunner::yieldAndPost()
|
||||
{
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct YieldPostAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_;
|
||||
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a coroutine_handle<> (symmetric transfer) rather than
|
||||
* void + h.resume(). Two reasons:
|
||||
*
|
||||
* 1. h.resume() runs the coroutine nested inside this frame. A
|
||||
* coroutine that yields in a loop against a stopping JobQueue
|
||||
* fails post() every iteration, so the stack grows without
|
||||
* bound. Symmetric transfer is a tail call and does not nest.
|
||||
*
|
||||
* 2. After h.resume() returns, the coroutine may have completed
|
||||
* and destroyed its frame -- the frame this awaiter lives in.
|
||||
* Returning from await_suspend would then touch freed memory.
|
||||
*
|
||||
* A bool return would also avoid nesting, but GCC-12 miscompiles
|
||||
* bool-returning await_suspend (see JobQueueAwaiter.h).
|
||||
*
|
||||
* @return noop_coroutine() to stay suspended (job posted);
|
||||
* the caller's handle to continue now (JQ stopping)
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
runner_.onSuspend();
|
||||
if (!runner_.post())
|
||||
{
|
||||
runner_.onUndoSuspend();
|
||||
return h;
|
||||
}
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
return YieldPostAwaiter{*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule coroutine resumption as a job on the JobQueue.
|
||||
* A shared_ptr capture (sp) prevents this CoroTaskRunner from being
|
||||
* destroyed while the job is queued but not yet executed.
|
||||
*
|
||||
* @return false if the JobQueue rejected the job (shutting down)
|
||||
*/
|
||||
inline bool
|
||||
JobQueue::CoroTaskRunner::post()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
++runCount_;
|
||||
}
|
||||
|
||||
// sp prevents 'this' from being destroyed while the job is pending
|
||||
if (jq_.addJob(type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// The coroutine will not run. Undo the runCount_ increment.
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the coroutine on the current thread.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Decrement nSuspend_ (under jq_.mutex_)
|
||||
* 2. Swap in this coroutine's LocalValues for thread-local isolation
|
||||
* 3. Resume the coroutine handle (under mutex_)
|
||||
* 4. Swap out LocalValues, restoring the thread's previous state
|
||||
* 5. Decrement runCount_ and notify join() waiters
|
||||
*
|
||||
* @pre post() must have been called before resume(). Direct calls
|
||||
* without a prior post() will corrupt runCount_ and break join().
|
||||
* Note: runCount_ is NOT incremented here — post() already did that.
|
||||
* This ensures join() stays blocked for the entire post->resume lifetime.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::resume()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
std::scoped_lock const lock(mutex_);
|
||||
XRPL_ASSERT(
|
||||
task_.handle() && !task_.done(),
|
||||
"xrpl::JobQueue::CoroTaskRunner::resume : task handle is valid and not done");
|
||||
if (task_.handle() && !task_.done())
|
||||
{
|
||||
task_.handle().resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
// A resume() with no coroutine to run (e.g. a duplicate external
|
||||
// post() after completion). Resuming a null or finished handle is
|
||||
// undefined behavior, so skip it -- this matches the old
|
||||
// Coro::resume() `if (coro_)` guard. The bookkeeping below still
|
||||
// runs to balance the ++runCount_ done by the post() that
|
||||
// scheduled this call.
|
||||
JLOG(jq_.journal_.warn())
|
||||
<< "CoroTaskRunner::resume called for coroutine '" << name_
|
||||
<< "' with no runnable coroutine (duplicate post or already completed)";
|
||||
}
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
if (task_.done())
|
||||
{
|
||||
finished_ = true;
|
||||
// An exception that escapes a top-level coroutine body is captured
|
||||
// by promise_type::unhandled_exception() but has no awaiter to
|
||||
// rethrow it, so it would vanish with the frame. Surface it in the
|
||||
// log. (The old Boost path propagated it out of resume() instead.)
|
||||
if (auto const& ep = task_.handle().promise().exception_)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(ep);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(jq_.journal_.error())
|
||||
<< "Unhandled exception in coroutine '" << name_ << "': " << e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(jq_.journal_.error())
|
||||
<< "Unhandled non-standard exception in coroutine '" << name_ << "'";
|
||||
}
|
||||
}
|
||||
// Break the shared_ptr cycle: frame -> shared_ptr<runner> -> this.
|
||||
// Use std::move (not task_ = {}) so task_.handle_ is null BEFORE the
|
||||
// frame is destroyed. operator= would destroy the frame while handle_
|
||||
// still holds the old value -- a re-entrancy hazard on GCC-12 if
|
||||
// frame destruction triggers runner cleanup.
|
||||
[[maybe_unused]] auto completed = std::move(task_);
|
||||
}
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has not yet run to completion
|
||||
*/
|
||||
inline bool
|
||||
JobQueue::CoroTaskRunner::runnable() const
|
||||
{
|
||||
// After normal completion, task_ is reset to break the shared_ptr cycle
|
||||
// (handle_ becomes null). A null handle means the coroutine is done.
|
||||
return task_.handle() && !task_.done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle early termination when the coroutine never ran (e.g. JobQueue
|
||||
* is stopping). Decrements nSuspend_ and destroys the coroutine frame
|
||||
* to break the shared_ptr cycle: frame -> lambda -> runner -> frame.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::expectEarlyExit()
|
||||
{
|
||||
if (!finished_)
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
finished_ = true;
|
||||
}
|
||||
// Break the shared_ptr cycle: frame -> shared_ptr<runner> -> this.
|
||||
// The coroutine is at initial_suspend and never ran user code, so
|
||||
// destroying it is safe. Use std::move (not task_ = {}) so
|
||||
// task_.handle_ is null before the frame is destroyed.
|
||||
{
|
||||
[[maybe_unused]] auto completed = std::move(task_);
|
||||
}
|
||||
storedFunc_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until all pending/active resume operations complete.
|
||||
* Uses cv_ + mutexRun_ to wait until runCount_ reaches 0 or
|
||||
* finished_ becomes true. The finished_ check handles the case
|
||||
* where resume() is called directly (without post()), which
|
||||
* decrements runCount_ below zero. In that scenario runCount_
|
||||
* never returns to 0, but finished_ becoming true guarantees
|
||||
* the coroutine is done and no more resumes will occur.
|
||||
*
|
||||
* Note: when join() returns via the finished_ disjunct, the final
|
||||
* resume() call may still be executing its post-completion
|
||||
* bookkeeping (the --runCount_ / notify after finished_ is set).
|
||||
* That is safe -- the coroutine body has fully completed and the
|
||||
* runner is kept alive by the resume job's shared_ptr -- but
|
||||
* callers must not assume resume() itself has returned.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutexRun_);
|
||||
cv_.wait(lk, [this]() { return runCount_ == 0 || finished_; });
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
#include <xrpl/core/ClosureCounter.h>
|
||||
#include <xrpl/core/CoroTask.h>
|
||||
#include <xrpl/core/JobTypeData.h>
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -152,6 +153,420 @@ public:
|
||||
join();
|
||||
};
|
||||
|
||||
/**
|
||||
* C++20 coroutine lifecycle manager. Replaces Coro for new code.
|
||||
*
|
||||
* Class / Inheritance / Dependency Diagram
|
||||
* =========================================
|
||||
*
|
||||
* std::enable_shared_from_this<CoroTaskRunner>
|
||||
* ^
|
||||
* | (public inheritance)
|
||||
* |
|
||||
* CoroTaskRunner
|
||||
* +---------------------------------------------------+
|
||||
* | - lvs_ : detail::LocalValues |
|
||||
* | - jq_ : JobQueue& |
|
||||
* | - type_ : JobType |
|
||||
* | - name_ : std::string |
|
||||
* | - runCount_ : int (in-flight resumes) |
|
||||
* | - mutex_ : std::mutex (coroutine guard) |
|
||||
* | - mutexRun_ : std::mutex (join guard) |
|
||||
* | - cv_ : condition_variable |
|
||||
* | - task_ : CoroTask<void> |
|
||||
* | - storedFunc_ : unique_ptr<FuncBase> (type-erased)|
|
||||
* +---------------------------------------------------+
|
||||
* | + init(F&&) : set up coroutine callable |
|
||||
* | + onSuspend() : ++jq_.nSuspend_ |
|
||||
* | + onUndoSuspend() : --jq_.nSuspend_ |
|
||||
* | + suspend() : returns SuspendAwaiter |
|
||||
* | + post() : schedule resume on JobQueue |
|
||||
* | + resume() : resume coroutine on caller |
|
||||
* | + runnable() : !task_.done() |
|
||||
* | + expectEarlyExit() : teardown for failed post |
|
||||
* | + join() : block until not running |
|
||||
* +---------------------------------------------------+
|
||||
* | |
|
||||
* | owns | references
|
||||
* v v
|
||||
* CoroTask<void> JobQueue
|
||||
* (coroutine frame) (thread pool + nSuspend_)
|
||||
*
|
||||
* FuncBase / FuncStore<F> (type-erased heap storage
|
||||
* for the coroutine lambda)
|
||||
*
|
||||
* Coroutine Lifecycle (Control Flow)
|
||||
* ===================================
|
||||
*
|
||||
* Caller thread JobQueue worker thread
|
||||
* ------------- ----------------------
|
||||
* postCoroTask(f)
|
||||
* |
|
||||
* +-- reserve a jobCounter_ slot (reject if JQ shutting down)
|
||||
* +-- ++nSuspend_ (lazy start counts as suspended)
|
||||
* +-- make_shared<CoroTaskRunner>
|
||||
* +-- init(f)
|
||||
* | +-- store lambda on heap (FuncStore)
|
||||
* | +-- task_ = f(shared_from_this())
|
||||
* | [coroutine created, suspended at initial_suspend]
|
||||
* +-- post()
|
||||
* | +-- ++runCount_
|
||||
* | +-- addJob(type_, [resume]{})
|
||||
* | resume()
|
||||
* | |
|
||||
* | +-- --nSuspend_
|
||||
* | +-- swap in LocalValues
|
||||
* | +-- task_.handle().resume()
|
||||
* | | [coroutine body runs]
|
||||
* | | ...
|
||||
* | | co_await suspend()
|
||||
* | | +-- ++nSuspend_
|
||||
* | | [coroutine suspends]
|
||||
* | +-- swap out LocalValues
|
||||
* | +-- --runCount_
|
||||
* | +-- cv_.notify_all()
|
||||
* |
|
||||
* post() <-- called externally or by yieldAndPost()
|
||||
* +-- ++runCount_
|
||||
* +-- addJob(type_, [resume]{})
|
||||
* resume()
|
||||
* |
|
||||
* +-- [coroutine body continues]
|
||||
* +-- co_return
|
||||
* +-- --runCount_
|
||||
* +-- cv_.notify_all()
|
||||
* join()
|
||||
* +-- cv_.wait([]{runCount_ == 0})
|
||||
* +-- [done]
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Fire-and-forget coroutine (most common pattern):
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "MyWork",
|
||||
* [](auto runner) -> CoroTask<void> {
|
||||
* doSomeWork();
|
||||
* co_await runner->suspend(); // yield to other jobs
|
||||
* doMoreWork();
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* 2. Manually controlling suspend / resume (external trigger):
|
||||
*
|
||||
* auto runner = jq.postCoroTask(JtClient, "ExtTrigger",
|
||||
* [&result](auto runner) -> CoroTask<void> {
|
||||
* startAsyncOperation(callback);
|
||||
* co_await runner->suspend();
|
||||
* // callback called runner->post() to get here
|
||||
* result = collectResult();
|
||||
* co_return;
|
||||
* });
|
||||
* // ... later, from the callback:
|
||||
* runner->post(); // reschedule the coroutine on the JobQueue
|
||||
*
|
||||
* 3. Using yieldAndPost() for automatic suspend + repost:
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "AutoRepost",
|
||||
* [](auto runner) -> CoroTask<void> {
|
||||
* step1();
|
||||
* co_await runner->yieldAndPost(); // yield + auto-repost
|
||||
* step2();
|
||||
* co_await runner->yieldAndPost();
|
||||
* step3();
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* 4. Checking shutdown after co_await (cooperative cancellation):
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "Cancellable",
|
||||
* [&jq](auto runner) -> CoroTask<void> {
|
||||
* while (moreWork()) {
|
||||
* co_await runner->yieldAndPost();
|
||||
* if (jq.isStopping())
|
||||
* co_return; // bail out cleanly
|
||||
* processNextItem();
|
||||
* }
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Calling suspend() without a matching post()/resume().
|
||||
* After co_await runner->suspend(), the coroutine is parked and
|
||||
* nSuspend_ is incremented. If nothing ever calls post() or
|
||||
* resume(), the coroutine is leaked and JobQueue::stop() will
|
||||
* hang forever waiting for nSuspend_ to reach zero.
|
||||
*
|
||||
* BUG-RISK: Calling post() on an already-running coroutine.
|
||||
* post() schedules a resume() job. If the coroutine has not
|
||||
* actually suspended yet (no co_await executed), the resume job
|
||||
* will try to call handle().resume() while the coroutine is still
|
||||
* running on another thread. This is UB. The mutex_ prevents
|
||||
* data corruption but the logic is wrong — always co_await
|
||||
* suspend() before calling post(). (The test incorrect_order()
|
||||
* shows this works only because mutex_ serializes the calls.)
|
||||
*
|
||||
* BUG-RISK: Dropping the shared_ptr<CoroTaskRunner> before join().
|
||||
* The CoroTaskRunner destructor asserts that finished_ is true
|
||||
* (the coroutine completed). If you let the last shared_ptr die
|
||||
* while the coroutine is still running or suspended, you get an
|
||||
* assertion failure in debug and UB in release. Always call
|
||||
* join() or expectEarlyExit() first.
|
||||
*
|
||||
* BUG-RISK: Lambda captures outliving the coroutine frame.
|
||||
* The lambda passed to postCoroTask is heap-allocated (FuncStore)
|
||||
* to prevent dangling. But objects captured by pointer still need
|
||||
* their own lifetime management. If you capture a raw pointer to
|
||||
* a stack variable, and the stack frame exits before the coroutine
|
||||
* finishes, the pointer dangles. Use shared_ptr or ensure the
|
||||
* pointed-to object outlives the coroutine.
|
||||
*
|
||||
* BUG-RISK: Forgetting co_return in a void coroutine.
|
||||
* If the coroutine body falls off the end without co_return,
|
||||
* the compiler may silently treat it as co_return (per standard),
|
||||
* but some compilers warn. Always write explicit co_return.
|
||||
*
|
||||
* LIMITATION: CoroTaskRunner only supports CoroTask<void>.
|
||||
* The task_ member is CoroTask<void>. To return values from
|
||||
* the top-level coroutine, write through a captured pointer
|
||||
* (as the tests demonstrate), or co_await inner CoroTask<T>
|
||||
* coroutines that return values.
|
||||
*
|
||||
* LIMITATION: One coroutine per CoroTaskRunner.
|
||||
* init() must be called exactly once. You cannot reuse a
|
||||
* CoroTaskRunner to run a second coroutine. Create a new one
|
||||
* via postCoroTask() instead.
|
||||
*
|
||||
* LIMITATION: No timeout on join().
|
||||
* join() blocks indefinitely. If the coroutine is suspended
|
||||
* and never posted, join() will deadlock. Use timed waits
|
||||
* on the gate pattern (condition_variable + wait_for) in tests.
|
||||
*/
|
||||
class CoroTaskRunner : public std::enable_shared_from_this<CoroTaskRunner>
|
||||
{
|
||||
private:
|
||||
// Per-coroutine thread-local storage. Swapped in before resume()
|
||||
// and swapped out after, so each coroutine sees its own LocalValue
|
||||
// state regardless of which worker thread executes it.
|
||||
detail::LocalValues lvs_;
|
||||
|
||||
// Back-reference to the owning JobQueue. Used to post jobs,
|
||||
// increment/decrement nSuspend_, and acquire jq_.mutex_.
|
||||
JobQueue& jq_;
|
||||
|
||||
// Job type passed to addJob() when posting this coroutine.
|
||||
JobType type_;
|
||||
|
||||
// Human-readable name for this coroutine job (for logging).
|
||||
std::string name_;
|
||||
|
||||
// Number of in-flight resume operations (pending + active).
|
||||
// Incremented by post(), decremented when resume() finishes.
|
||||
// Guarded by mutexRun_. join() blocks until this reaches 0.
|
||||
//
|
||||
// A counter (not a bool) is needed because post() can be called
|
||||
// from within the coroutine body (e.g. via yieldAndPost()),
|
||||
// enqueuing a second resume while the first is still running.
|
||||
// A bool would be clobbered: R2.post() sets true, then R1's
|
||||
// cleanup sets false — losing the fact that R2 is still pending.
|
||||
int runCount_ = 0;
|
||||
|
||||
// Serializes all coroutine resume() calls, preventing concurrent
|
||||
// execution of the coroutine body on multiple threads. Handles the
|
||||
// race where post() enqueues a resume before the coroutine has
|
||||
// actually suspended (post-before-suspend pattern).
|
||||
std::mutex mutex_;
|
||||
|
||||
// Guards runCount_. Used with cv_ for join() to wait
|
||||
// until all pending/active resume operations complete.
|
||||
std::mutex mutexRun_;
|
||||
|
||||
// Notified when runCount_ reaches zero, allowing
|
||||
// join() waiters to wake up.
|
||||
std::condition_variable cv_;
|
||||
|
||||
// The coroutine handle wrapper. Owns the coroutine frame.
|
||||
// Set by init(). Reset to empty in resume() upon coroutine
|
||||
// completion (to break the shared_ptr cycle) or in
|
||||
// expectEarlyExit() on early termination.
|
||||
CoroTask<void> task_;
|
||||
|
||||
/**
|
||||
* Type-erased base for heap-stored callables.
|
||||
* Prevents the coroutine lambda from being destroyed before
|
||||
* the coroutine frame is done with it.
|
||||
*
|
||||
* @see FuncStore
|
||||
*/
|
||||
struct FuncBase
|
||||
{
|
||||
virtual ~FuncBase() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Concrete type-erased storage for a callable of type F.
|
||||
* The coroutine frame stores a reference to the lambda's implicit
|
||||
* object parameter. If the lambda is a temporary, that reference
|
||||
* dangles after the call returns. FuncStore keeps it alive on
|
||||
* the heap for the lifetime of the CoroTaskRunner.
|
||||
*/
|
||||
template <class F>
|
||||
struct FuncStore : FuncBase
|
||||
{
|
||||
F func; // The stored callable (coroutine lambda).
|
||||
explicit FuncStore(F&& f) : func(std::move(f))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Heap-allocated callable storage. Set by init(), ensures the
|
||||
// lambda outlives the coroutine frame that references it.
|
||||
std::unique_ptr<FuncBase> storedFunc_;
|
||||
|
||||
// True once the coroutine has completed or expectEarlyExit() was
|
||||
// called. Asserted in the destructor (debug) to catch leaked
|
||||
// runners. Available in all builds to guard expectEarlyExit()
|
||||
// against double-decrementing nSuspend_.
|
||||
bool finished_ = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Tag type for private construction. Prevents external code
|
||||
* from constructing CoroTaskRunner directly. Use postCoroTask().
|
||||
*/
|
||||
struct CreateT
|
||||
{
|
||||
explicit CreateT() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a CoroTaskRunner. Private by convention (CreateT tag).
|
||||
*
|
||||
* @param jq The JobQueue this coroutine will run on
|
||||
* @param type Job type for scheduling priority
|
||||
* @param name Human-readable name for logging
|
||||
*/
|
||||
CoroTaskRunner(CreateT, JobQueue&, JobType, std::string);
|
||||
|
||||
CoroTaskRunner(CoroTaskRunner const&) = delete;
|
||||
CoroTaskRunner&
|
||||
operator=(CoroTaskRunner const&) = delete;
|
||||
|
||||
/**
|
||||
* Destructor. Asserts (debug) that the coroutine has finished
|
||||
* or expectEarlyExit() was called.
|
||||
*/
|
||||
~CoroTaskRunner();
|
||||
|
||||
/**
|
||||
* Initialize with a coroutine-returning callable.
|
||||
* Must be called exactly once, after the object is managed by
|
||||
* shared_ptr (because init uses shared_from_this internally).
|
||||
* This is handled automatically by postCoroTask().
|
||||
*
|
||||
* @param f Callable: CoroTask<void>(shared_ptr<CoroTaskRunner>)
|
||||
*/
|
||||
template <class F>
|
||||
void
|
||||
init(F&& f);
|
||||
|
||||
/**
|
||||
* Increment the JobQueue's suspended-coroutine count (nSuspend_).
|
||||
* Called when the coroutine is about to suspend. Every call
|
||||
* must be balanced by a corresponding decrement (via resume()
|
||||
* or onUndoSuspend()), or JobQueue::stop() will hang.
|
||||
*/
|
||||
void
|
||||
onSuspend();
|
||||
|
||||
/**
|
||||
* Decrement nSuspend_ without resuming.
|
||||
* Used to undo onSuspend() when a scheduled post() fails
|
||||
* (e.g. JobQueue is stopping).
|
||||
*/
|
||||
void
|
||||
onUndoSuspend();
|
||||
|
||||
/**
|
||||
* Suspend the coroutine.
|
||||
* The awaiter's await_suspend() increments nSuspend_ before the
|
||||
* coroutine actually suspends. The caller must later call post()
|
||||
* or resume() to continue execution.
|
||||
*
|
||||
* @return An awaiter for use with `co_await runner->suspend()`
|
||||
*/
|
||||
auto
|
||||
suspend();
|
||||
|
||||
/**
|
||||
* Suspend the coroutine and immediately repost it on the
|
||||
* JobQueue. Combines suspend() + post() atomically inside
|
||||
* await_suspend, so there is no window where an external
|
||||
* event could race between the two.
|
||||
*
|
||||
* Equivalent to JobQueueAwaiter but defined as an inline
|
||||
* awaiter returned from a member function. This avoids a
|
||||
* GCC-12 coroutine codegen bug where an external awaiter
|
||||
* struct (JobQueueAwaiter) used at multiple co_await points
|
||||
* corrupts the coroutine state machine's resume index,
|
||||
* causing the coroutine to hang on the third resumption.
|
||||
*
|
||||
* @return An awaiter for use with `co_await runner->yieldAndPost()`
|
||||
*/
|
||||
auto
|
||||
yieldAndPost();
|
||||
|
||||
/**
|
||||
* Schedule coroutine resumption as a job on the JobQueue.
|
||||
* Captures shared_from_this() to prevent this runner from being
|
||||
* destroyed while the job is queued.
|
||||
*
|
||||
* @return true if the job was accepted; false if the JobQueue
|
||||
* is stopping (caller must handle cleanup)
|
||||
*/
|
||||
bool
|
||||
post();
|
||||
|
||||
/**
|
||||
* Resume the coroutine on the current thread.
|
||||
* Decrements nSuspend_, swaps in LocalValues, resumes the
|
||||
* coroutine handle, swaps out LocalValues, and notifies join()
|
||||
* waiters. Lock ordering (sequential, non-overlapping):
|
||||
* jq_.mutex_ -> mutex_ -> mutexRun_.
|
||||
*
|
||||
* @pre post() must have been called before resume(). Direct
|
||||
* calls without a prior post() will corrupt runCount_
|
||||
* and break join().
|
||||
*/
|
||||
void
|
||||
resume();
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has not yet run to completion
|
||||
*/
|
||||
bool
|
||||
runnable() const;
|
||||
|
||||
/**
|
||||
* Handle early termination when the coroutine never ran.
|
||||
* Decrements nSuspend_ and destroys the coroutine frame to
|
||||
* break the shared_ptr cycle (frame -> lambda -> runner -> frame).
|
||||
* Called by postCoroTask() when post() fails.
|
||||
*/
|
||||
void
|
||||
expectEarlyExit();
|
||||
|
||||
/**
|
||||
* Block until all pending/active resume operations complete.
|
||||
* Uses cv_ + mutexRun_ to wait until runCount_ reaches 0.
|
||||
* Warning: deadlocks if the coroutine is suspended and never posted.
|
||||
*/
|
||||
void
|
||||
join();
|
||||
};
|
||||
|
||||
using JobFunction = std::function<void()>;
|
||||
|
||||
JobQueue(
|
||||
@@ -197,6 +612,20 @@ public:
|
||||
std::shared_ptr<Coro>
|
||||
postCoro(JobType t, std::string const& name, F&& f);
|
||||
|
||||
/**
|
||||
* Creates a C++20 coroutine and adds a job to the queue to run it.
|
||||
*
|
||||
* @param t The type of job.
|
||||
* @param name Name of the job.
|
||||
* @param f Callable with signature
|
||||
* CoroTask<void>(std::shared_ptr<CoroTaskRunner>).
|
||||
*
|
||||
* @return shared_ptr to posted CoroTaskRunner. nullptr if not successful.
|
||||
*/
|
||||
template <class F>
|
||||
std::shared_ptr<CoroTaskRunner>
|
||||
postCoroTask(JobType t, std::string const& name, F&& f);
|
||||
|
||||
/**
|
||||
* Jobs waiting at this priority.
|
||||
*/
|
||||
@@ -419,7 +848,8 @@ private:
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
#include <xrpl/core/Coro.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/core/Coro.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/core/CoroTaskRunner.ipp> // IWYU pragma: keep
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -442,4 +872,82 @@ JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
||||
return coro;
|
||||
}
|
||||
|
||||
// postCoroTask — entry point for launching a C++20 coroutine on the JobQueue.
|
||||
//
|
||||
// Control Flow
|
||||
// ============
|
||||
//
|
||||
// postCoroTask(t, name, f)
|
||||
// |
|
||||
// +-- 1. Reserve a jobCounter_ slot — reject if JQ shutting down
|
||||
// |
|
||||
// +-- 2. ++nSuspend_ (mirrors Boost Coro ctor's implicit yield)
|
||||
// | The coroutine is "suspended" from the JobQueue's perspective
|
||||
// | even though it hasn't run yet — this keeps the JQ shutdown
|
||||
// | logic correct (it waits for nSuspend_ to reach 0).
|
||||
// |
|
||||
// +-- 3. Create CoroTaskRunner (shared_ptr, ref-counted)
|
||||
// |
|
||||
// +-- 4. runner->init(f)
|
||||
// | +-- Heap-allocate the lambda (FuncStore) to prevent
|
||||
// | | dangling captures in the coroutine frame
|
||||
// | +-- task_ = f(shared_from_this())
|
||||
// | [coroutine created but NOT started — lazy initial_suspend]
|
||||
// |
|
||||
// +-- 5. runner->post()
|
||||
// | +-- addJob(type_, [resume]{}) → resume on worker thread
|
||||
// | +-- failure (JQ stopping):
|
||||
// | +-- runner->expectEarlyExit()
|
||||
// | | --nSuspend_, destroy coroutine frame
|
||||
// | +-- return nullptr
|
||||
// |
|
||||
// +-- 6. Release the jobCounter_ slot (on return)
|
||||
//
|
||||
// Why reserve a jobCounter_ slot?
|
||||
// ===============================
|
||||
// JobQueue::stop() joins jobCounter_ before it acquires mutex_ and asserts
|
||||
// nSuspend_ == 0. Without a reservation, stop() could run to completion in
|
||||
// the window between the ++nSuspend_ in step 2 and the balancing post() or
|
||||
// expectEarlyExit() in step 5, tripping that assert. Holding a slot blocks
|
||||
// stop()'s join() for the whole function, closing the window. wrap() also
|
||||
// returns nullopt once the counter is joined, so it doubles as the shutdown
|
||||
// check — a plain stopping_ read cannot do this, because the read and the
|
||||
// ++nSuspend_ are not a single atomic step.
|
||||
//
|
||||
// Why async post() instead of synchronous resume()?
|
||||
// ==================================================
|
||||
// The initial dispatch MUST use async post() so the coroutine body runs on
|
||||
// a JobQueue worker thread, not the caller's thread. resume() swaps the
|
||||
// caller's thread-local LocalValues with the coroutine's private copy.
|
||||
// If the coroutine mutates LocalValues (e.g. thread_specific_storage test),
|
||||
// those mutations bleed back into the caller's thread-local state after the
|
||||
// swap-out, corrupting subsequent tests that share the same thread pool.
|
||||
// Async post() avoids this by running the coroutine on a worker thread whose
|
||||
// LocalValues are managed by the thread pool, not by the caller.
|
||||
//
|
||||
template <class F>
|
||||
std::shared_ptr<JobQueue::CoroTaskRunner>
|
||||
JobQueue::postCoroTask(JobType t, std::string const& name, F&& f)
|
||||
{
|
||||
// Held until this function returns. Null once jobCounter_ is joined,
|
||||
// which is how a shutting-down JobQueue rejects new coroutines.
|
||||
auto const shutdownGuard = jobCounter_.wrap([]() {});
|
||||
if (!shutdownGuard)
|
||||
return nullptr;
|
||||
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
++nSuspend_;
|
||||
}
|
||||
|
||||
auto runner = std::make_shared<CoroTaskRunner>(CoroTaskRunner::CreateT{}, *this, t, name);
|
||||
runner->init(std::forward<F>(f));
|
||||
if (!runner->post())
|
||||
{
|
||||
runner->expectEarlyExit();
|
||||
runner.reset();
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
212
include/xrpl/core/JobQueueAwaiter.h
Normal file
212
include/xrpl/core/JobQueueAwaiter.h
Normal file
@@ -0,0 +1,212 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
|
||||
#include <coroutine>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Awaiter that suspends and immediately reschedules on the JobQueue.
|
||||
* Equivalent to calling yield() followed by post() in the old Coro API.
|
||||
*
|
||||
* Usage:
|
||||
* co_await JobQueueAwaiter{runner};
|
||||
*
|
||||
* What it waits for: The coroutine is re-queued as a job and resumes
|
||||
* when a worker thread picks it up.
|
||||
*
|
||||
* Which thread resumes: A JobQueue worker thread.
|
||||
*
|
||||
* What await_resume() returns: void.
|
||||
*
|
||||
* Dependency Diagram
|
||||
* ==================
|
||||
*
|
||||
* JobQueueAwaiter
|
||||
* +----------------------------------------------+
|
||||
* | + runner : shared_ptr<CoroTaskRunner> |
|
||||
* +----------------------------------------------+
|
||||
* | + await_ready() -> false (always suspend) |
|
||||
* | + await_suspend() -> bool (suspend or cancel) |
|
||||
* | + await_resume() -> void |
|
||||
* +----------------------------------------------+
|
||||
* | |
|
||||
* | uses | uses
|
||||
* v v
|
||||
* CoroTaskRunner JobQueue
|
||||
* .onSuspend() (via runner->post() -> addJob)
|
||||
* .onUndoSuspend()
|
||||
* .post()
|
||||
*
|
||||
* Control Flow (await_suspend)
|
||||
* ============================
|
||||
*
|
||||
* co_await JobQueueAwaiter{runner}
|
||||
* |
|
||||
* +-- await_ready() -> false
|
||||
* +-- await_suspend(handle)
|
||||
* |
|
||||
* +-- runner->onSuspend() // ++nSuspend_
|
||||
* +-- runner->post() // addJob to JobQueue
|
||||
* | |
|
||||
* | +-- success? return noop_coroutine()
|
||||
* | | // coroutine stays suspended;
|
||||
* | | // worker thread will call resume()
|
||||
* | +-- failure? (JQ stopping)
|
||||
* | +-- runner->onUndoSuspend() // --nSuspend_
|
||||
* | +-- return handle // symmetric transfer back
|
||||
* | // coroutine continues immediately
|
||||
* | // so it can clean up and co_return
|
||||
*
|
||||
* DEPRECATED — prefer `co_await runner->yieldAndPost()`
|
||||
* =====================================================
|
||||
*
|
||||
* GCC-12 has a coroutine codegen bug where using this external awaiter
|
||||
* struct at multiple co_await points in the same coroutine corrupts the
|
||||
* state machine's resume index. After the second co_await, the third
|
||||
* resumption enters handle().resume() but never reaches await_resume()
|
||||
* or any subsequent user code — the coroutine hangs indefinitely.
|
||||
*
|
||||
* The fix is `co_await runner->yieldAndPost()`, which defines the
|
||||
* awaiter as an inline struct inside a CoroTaskRunner member function.
|
||||
* GCC-12 handles inline awaiters correctly at multiple co_await points.
|
||||
*
|
||||
* This struct is retained for single-use scenarios and documentation
|
||||
* purposes. For any code that may use co_await in a loop or at
|
||||
* multiple points, always use `runner->yieldAndPost()`.
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Yield and auto-repost (preferred — works on all compilers):
|
||||
*
|
||||
* CoroTask<void> handler(auto runner) {
|
||||
* doPartA();
|
||||
* co_await runner->yieldAndPost(); // yield + repost
|
||||
* doPartB(); // runs on a worker thread
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 2. Multiple yield points in a loop:
|
||||
*
|
||||
* CoroTask<void> batchProcessor(auto runner) {
|
||||
* for (auto& item : items) {
|
||||
* process(item);
|
||||
* co_await runner->yieldAndPost(); // let other jobs run
|
||||
* }
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 3. Graceful shutdown — checking after resume:
|
||||
*
|
||||
* CoroTask<void> longTask(auto runner, JobQueue& jq) {
|
||||
* while (hasWork()) {
|
||||
* co_await runner->yieldAndPost();
|
||||
* // If JQ is stopping, await_suspend resumes the coroutine
|
||||
* // immediately without re-queuing. Always check
|
||||
* // isStopping() to decide whether to proceed:
|
||||
* if (jq.isStopping())
|
||||
* co_return;
|
||||
* doNextChunk();
|
||||
* }
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Using a stale or null runner.
|
||||
* The runner shared_ptr must be valid and point to the CoroTaskRunner
|
||||
* that owns the coroutine currently executing. Passing a runner from
|
||||
* a different coroutine, or a default-constructed shared_ptr, is UB.
|
||||
*
|
||||
* BUG-RISK: Assuming resume happens on the same thread.
|
||||
* After co_await, the coroutine resumes on whatever worker thread
|
||||
* picks up the job. Do not rely on thread-local state unless it is
|
||||
* managed through LocalValue (which CoroTaskRunner automatically
|
||||
* swaps in/out).
|
||||
*
|
||||
* BUG-RISK: Ignoring the shutdown path.
|
||||
* When the JobQueue is stopping, post() fails and await_suspend()
|
||||
* resumes the coroutine immediately (symmetric transfer back to h).
|
||||
* The coroutine body continues on the same thread. If your code
|
||||
* after co_await assumes it was re-queued and is running on a worker
|
||||
* thread, that assumption breaks during shutdown. Always handle the
|
||||
* "JQ is stopping" case, either by checking jq.isStopping() or by
|
||||
* letting the coroutine fall through to co_return naturally.
|
||||
*
|
||||
* DIFFERENCE from runner->suspend() + runner->post():
|
||||
* Both JobQueueAwaiter and yieldAndPost() combine suspend + post
|
||||
* in one atomic operation. With the manual suspend()/post() pattern,
|
||||
* there is a window between the two calls where an external event
|
||||
* could race. The atomic awaiters remove that window — onSuspend()
|
||||
* and post() happen within the same await_suspend() call while the
|
||||
* coroutine is guaranteed to be suspended. Use yieldAndPost() unless
|
||||
* you need an external party to decide *when* to call post().
|
||||
*/
|
||||
struct JobQueueAwaiter
|
||||
{
|
||||
// The CoroTaskRunner that owns the currently executing coroutine.
|
||||
std::shared_ptr<JobQueue::CoroTaskRunner> runner;
|
||||
|
||||
/**
|
||||
* Always returns false so the coroutine suspends.
|
||||
*/
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment nSuspend (equivalent to yield()) and schedule resume
|
||||
* on the JobQueue (equivalent to post()). If the JobQueue is
|
||||
* stopping, undoes the suspend count and transfers back to the
|
||||
* coroutine so it can clean up and co_return.
|
||||
*
|
||||
* Returns a coroutine_handle<> (symmetric transfer) instead of
|
||||
* bool to work around a GCC-12 codegen bug where bool-returning
|
||||
* await_suspend leaves the coroutine in an invalid state —
|
||||
* neither properly suspended nor resumed — causing a hang.
|
||||
*
|
||||
* WARNING: GCC-12 has an additional codegen bug where using this
|
||||
* external awaiter struct at multiple co_await points in the same
|
||||
* coroutine corrupts the state machine's resume index, causing the
|
||||
* coroutine to hang on the third resumption. Prefer
|
||||
* `co_await runner->yieldAndPost()` which uses an inline awaiter
|
||||
* that GCC-12 handles correctly.
|
||||
*
|
||||
* @return noop_coroutine() to stay suspended (job posted);
|
||||
* the caller's handle to resume immediately (JQ stopping)
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
XRPL_ASSERT(runner, "xrpl::JobQueueAwaiter::await_suspend : runner is valid");
|
||||
runner->onSuspend();
|
||||
if (!runner->post())
|
||||
{
|
||||
// JobQueue is stopping. Undo the suspend count and
|
||||
// transfer back to the coroutine so it can clean up
|
||||
// and co_return.
|
||||
runner->onUndoSuspend();
|
||||
return h;
|
||||
}
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -199,10 +198,7 @@ dirLink(
|
||||
* if withdrawing to self.
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it (account-based or credential-based).
|
||||
* - Expects any credentials passed in to already exist in the ledger, and
|
||||
* returns an internal error otherwise. Validate them beforehand with
|
||||
* credentials::valid().
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
@@ -213,8 +209,7 @@ canWithdraw(
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag,
|
||||
std::optional<std::vector<uint256>> const& credentialIDs = std::nullopt);
|
||||
bool hasDestinationTag);
|
||||
|
||||
/**
|
||||
* Checks that can withdraw funds from an object to itself or a destination.
|
||||
@@ -227,10 +222,7 @@ canWithdraw(
|
||||
* if withdrawing to self.
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it (account-based or credential-based).
|
||||
* - Expects any credentials passed in to already exist in the ledger, and
|
||||
* returns an internal error otherwise. Validate them beforehand with
|
||||
* credentials::valid().
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
@@ -240,25 +232,20 @@ canWithdraw(
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag,
|
||||
std::optional<std::vector<uint256>> const& credentialIDs = std::nullopt);
|
||||
bool hasDestinationTag);
|
||||
|
||||
/**
|
||||
* 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). Credentials, if any, are taken from the
|
||||
* transaction's sfCredentialIDs field.
|
||||
* destination account (sfDestination).
|
||||
*
|
||||
* - Checks that the receiver account exists.
|
||||
* - If the receiver requires a destination tag, check that one exists, even
|
||||
* if withdrawing to self.
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it (account-based or credential-based).
|
||||
* - Expects any credentials in sfCredentialIDs to already exist in the
|
||||
* ledger, and returns an internal error otherwise. Validate them
|
||||
* beforehand with credentials::valid().
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
@@ -34,32 +33,6 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
|
||||
[[nodiscard]] TER
|
||||
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
|
||||
|
||||
/**
|
||||
* @brief Remove credentials pinned to a pseudo-account's owner directory.
|
||||
*
|
||||
* Cleans up credentials that were linked to a pseudo-account (Vault, LoanBroker,
|
||||
* AMM), which such an account can neither accept nor delete. Only credentials
|
||||
* are removed; every other object is left in place. The walk visits at most
|
||||
* @p maxNodesToDelete directory entries and charges the ones it leaves alone
|
||||
* against that budget too, so a directory holding other objects yields fewer
|
||||
* than @p maxNodesToDelete deletions. On reaching the bound the result is
|
||||
* `tecINCOMPLETE` and the caller must propagate it so a later transaction
|
||||
* resumes.
|
||||
*
|
||||
* @param view Mutable ledger view.
|
||||
* @param pseudoAcct The pseudo-account whose directory is cleaned.
|
||||
* @param maxNodesToDelete Upper bound on directory entries processed in one call.
|
||||
* @param j Journal for diagnostics.
|
||||
* @return tesSUCCESS once no credentials remain, tecINCOMPLETE if the bound was
|
||||
* reached, or a deletion error.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deletePseudoAccountCredentials(
|
||||
ApplyView& view,
|
||||
AccountID const& pseudoAcct,
|
||||
std::uint16_t maxNodesToDelete,
|
||||
beast::Journal j);
|
||||
|
||||
// Amendment and parameters checks for sfCredentialIDs field
|
||||
NotTEC
|
||||
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
@@ -239,40 +238,4 @@ getVaultPhase(
|
||||
std::optional<std::uint32_t> subscriptionDate,
|
||||
std::optional<std::uint32_t> redemptionDate);
|
||||
|
||||
/**
|
||||
* Controls whether checkVaultDomain reports an expired credential as an
|
||||
* error. A caller that deletes expired credentials later, in doApply, passes
|
||||
* Yes and treats the subject as authorized; a caller with no such cleanup
|
||||
* step must keep the error.
|
||||
*/
|
||||
enum class SuppressExpired : bool { No = false, Yes = true };
|
||||
|
||||
/**
|
||||
* Checks that subject belongs to the permissioned domain governing a vault's
|
||||
* shares.
|
||||
*
|
||||
* The domain is read from the share issuance rather than from the vault. Vault
|
||||
* shares are issued by the vault's pseudo-account, which cannot grant an
|
||||
* authorization explicitly, so domain membership is the only route to being
|
||||
* authorized: a vault with no domain set has no authorized participants at
|
||||
* all, and every subject fails with tecNO_AUTH.
|
||||
*
|
||||
* Which accounts to check, and whether to check at all, is left to the caller.
|
||||
* This says nothing about vault privacy or about the roles of the accounts.
|
||||
*
|
||||
* @param view The ledger view.
|
||||
* @param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
* @param subject The account whose domain membership is checked.
|
||||
* @param suppressExpired Whether an expired credential counts as authorized.
|
||||
*
|
||||
* @return tesSUCCESS if the subject is a domain member, otherwise the reason
|
||||
* it is not.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkVaultDomain(
|
||||
ReadView const& view,
|
||||
SLE::const_ref issuance,
|
||||
AccountID const& subject,
|
||||
SuppressExpired suppressExpired);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -91,17 +91,6 @@ getFee(std::uint16_t tfee)
|
||||
return Number{tfee} / kAuctionSlotFeeScaleFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum auction slot price: LPTokens * TradingFee / kAuctionSlotMinFeeFraction
|
||||
* @param lptAMMBalance AMM LP token balance
|
||||
* @param tradingFee trading fee in {0, 1000}
|
||||
*/
|
||||
inline Number
|
||||
ammAuctionMinSlotPrice(Number const& lptAMMBalance, std::uint16_t tradingFee)
|
||||
{
|
||||
return lptAMMBalance * getFee(tradingFee) / kAuctionSlotMinFeeFraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fee multiplier (1 - tfee)
|
||||
* @tfee trading fee in basis points
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/TxSettings.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -39,6 +38,11 @@ enum GranularPermissionType : std::uint32_t {
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
// Injected bare enumerators (xrpl::delegable / xrpl::notDelegable) are required by preprocessor
|
||||
// tricks in tests and macro-generated code; enum class would break that.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum Delegation { Delegable, NotDelegable };
|
||||
|
||||
class Permission
|
||||
{
|
||||
private:
|
||||
@@ -61,7 +65,7 @@ private:
|
||||
struct TxDelegationEntry
|
||||
{
|
||||
uint256 amendment;
|
||||
Delegation delegable{Delegation::NotDelegable};
|
||||
Delegation delegable{NotDelegable};
|
||||
};
|
||||
|
||||
std::unordered_set<TxType> granularTxTypes_;
|
||||
|
||||
@@ -396,16 +396,6 @@ using TxID = uint256;
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512;
|
||||
|
||||
/**
|
||||
* The maximum number of owner-directory entries to walk when clearing
|
||||
* credentials pinned to a pseudo-account, in a single transaction.
|
||||
*
|
||||
* The walk stops after this many entries whether or not each one turns out to
|
||||
* be a credential, so a directory that also holds other objects yields fewer
|
||||
* deletions per transaction.
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletablePseudoAccountCredentials = 512;
|
||||
|
||||
/**
|
||||
* The maximum length of a URI inside an Oracle
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace xrpl {
|
||||
|
||||
class Rules;
|
||||
namespace test {
|
||||
class InvariantsMisc_test;
|
||||
class Invariants_test;
|
||||
} // namespace test
|
||||
|
||||
class STLedgerEntry final : public STObject, public CountedObject<STLedgerEntry>
|
||||
@@ -83,8 +83,8 @@ private:
|
||||
void
|
||||
setSLEType();
|
||||
|
||||
friend test::InvariantsMisc_test; // this test wants access to the
|
||||
// private type_
|
||||
friend test::Invariants_test; // this test wants access to the private
|
||||
// type_
|
||||
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
enum class Delegation { Delegable, NotDelegable };
|
||||
|
||||
/**
|
||||
* Operations a transaction is permitted to perform, as a bitfield.
|
||||
*
|
||||
* These are declared per-transaction in transactions.macro (via
|
||||
* TxSettings::privileges) and enforced in InvariantCheck.cpp.
|
||||
*/
|
||||
enum class Privilege : std::uint16_t {
|
||||
NoPriv = 0x0000, // The transaction can not do any of the enumerated operations
|
||||
CreateAcct = 0x0001, // The transaction can create a new ACCOUNT_ROOT object.
|
||||
CreatePseudoAcct = 0x0002, // The transaction can create a pseudo account,
|
||||
// which implies createAcct
|
||||
MustDeleteAcct = 0x0004, // The transaction must delete an ACCOUNT_ROOT object
|
||||
MayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT
|
||||
// object, but does not have to
|
||||
OverrideFreeze = 0x0010, // The transaction can override some freeze rules
|
||||
ChangeNftCounts = 0x0020, // The transaction can mint or burn an NFT
|
||||
CreateMptIssuance = 0x0040, // The transaction can create a new MPT issuance
|
||||
DestroyMptIssuance = 0x0080, // The transaction can destroy an MPT issuance
|
||||
MustAuthorizeMpt = 0x0100, // The transaction MUST create or delete an MPT
|
||||
// object (except by issuer)
|
||||
MayAuthorizeMpt = 0x0200, // The transaction MAY create or delete an MPT
|
||||
// object (except by issuer)
|
||||
MayDeleteMpt = 0x0400, // The transaction MAY delete an MPT object. May not create.
|
||||
MustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault
|
||||
MayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault
|
||||
MayCreateMpt = 0x2000, // The transaction MAY create an MPT object, except for issuer.
|
||||
};
|
||||
|
||||
// The inner static_cast is not redundant: the underlying type is narrower than
|
||||
// `int`, so the operands integer-promote and the result has to be narrowed back.
|
||||
// safeCast rejects that narrowing, but every input bit is a Privilege bit by
|
||||
// construction, so the result is always representable.
|
||||
constexpr Privilege
|
||||
operator|(Privilege lhs, Privilege rhs)
|
||||
{
|
||||
using Underlying = std::underlying_type_t<Privilege>;
|
||||
return static_cast<Privilege>(
|
||||
static_cast<Underlying>(safeCast<Underlying>(lhs) | safeCast<Underlying>(rhs)));
|
||||
}
|
||||
|
||||
constexpr Privilege
|
||||
operator&(Privilege lhs, Privilege rhs)
|
||||
{
|
||||
using Underlying = std::underlying_type_t<Privilege>;
|
||||
return static_cast<Privilege>(
|
||||
static_cast<Underlying>(safeCast<Underlying>(lhs) & safeCast<Underlying>(rhs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-transaction metadata declared in transactions.macro.
|
||||
*
|
||||
* Every member has a default, so a transaction only needs to name the settings
|
||||
* that differ from the common case. See the documentation at the top of
|
||||
* transactions.macro for the authoring syntax.
|
||||
*
|
||||
* This is deliberately not a constexpr-friendly type: amendment identifiers are
|
||||
* runtime-initialized `extern uint256 const` globals (see Feature.h), so a
|
||||
* TxSettings can only be built at runtime.
|
||||
*/
|
||||
struct TxSettings
|
||||
{
|
||||
/**
|
||||
* Whether an account may delegate this transaction to another account.
|
||||
*/
|
||||
Delegation delegable{Delegation::NotDelegable};
|
||||
|
||||
/**
|
||||
* The amendment gating this transaction, or uint256{} if always available.
|
||||
*/
|
||||
// The `{}` looks redundant, because BaseUInt's default constructor already
|
||||
// zeroes the value. It is not: without a default member initializer here,
|
||||
// every partial designated initializer in transactions.macro trips the
|
||||
// missing-designated-field-initializers warning, which the build treats as
|
||||
// an error.
|
||||
// NOLINTNEXTLINE(readability-redundant-member-init)
|
||||
uint256 amendment{};
|
||||
|
||||
/**
|
||||
* Operations this transaction is permitted to perform.
|
||||
*/
|
||||
Privilege privileges{Privilege::NoPriv};
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ class AMMBidBuilder;
|
||||
* Type: ttAMM_BID (39)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMBidBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMClawbackBuilder;
|
||||
* Type: ttAMM_CLAWBACK (31)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMMClawback
|
||||
* Privileges: Privilege::MayDeleteAcct | Privilege::OverrideFreeze | Privilege::MayAuthorizeMpt
|
||||
* Privileges: MayDeleteAcct | OverrideFreeze | MayAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMClawbackBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMCreateBuilder;
|
||||
* Type: ttAMM_CREATE (35)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::CreatePseudoAcct | Privilege::MayCreateMpt
|
||||
* Privileges: CreatePseudoAcct | MayCreateMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMDeleteBuilder;
|
||||
* Type: ttAMM_DELETE (40)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::MustDeleteAcct | Privilege::MayDeleteMpt
|
||||
* Privileges: MustDeleteAcct | MayDeleteMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMDepositBuilder;
|
||||
* Type: ttAMM_DEPOSIT (36)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMDepositBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMVoteBuilder;
|
||||
* Type: ttAMM_VOTE (38)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMVoteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AMMWithdrawBuilder;
|
||||
* Type: ttAMM_WITHDRAW (37)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureAMM
|
||||
* Privileges: Privilege::MayDeleteAcct | Privilege::MayAuthorizeMpt
|
||||
* Privileges: MayDeleteAcct | MayAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AMMWithdrawBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AccountDeleteBuilder;
|
||||
* Type: ttACCOUNT_DELETE (21)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::MustDeleteAcct
|
||||
* Privileges: MustDeleteAcct
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AccountDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class AccountSetBuilder;
|
||||
* Type: ttACCOUNT_SET (3)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use AccountSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class BatchBuilder;
|
||||
* Type: ttBATCH (71)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureBatchV1_1
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use BatchBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CheckCancelBuilder;
|
||||
* Type: ttCHECK_CANCEL (18)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CheckCancelBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CheckCashBuilder;
|
||||
* Type: ttCHECK_CASH (17)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::MayCreateMpt
|
||||
* Privileges: MayCreateMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CheckCashBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CheckCreateBuilder;
|
||||
* Type: ttCHECK_CREATE (16)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CheckCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ClawbackBuilder;
|
||||
* Type: ttCLAWBACK (30)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ClawbackBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConfidentialMPTClawbackBuilder;
|
||||
* Type: ttCONFIDENTIAL_MPT_CLAWBACK (89)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTClawbackBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConfidentialMPTConvertBuilder;
|
||||
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTConvertBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConfidentialMPTConvertBackBuilder;
|
||||
* Type: ttCONFIDENTIAL_MPT_CONVERT_BACK (87)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTConvertBackBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConfidentialMPTMergeInboxBuilder;
|
||||
* Type: ttCONFIDENTIAL_MPT_MERGE_INBOX (86)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTMergeInboxBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConfidentialMPTSendBuilder;
|
||||
* Type: ttCONFIDENTIAL_MPT_SEND (88)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTSendBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CredentialAcceptBuilder;
|
||||
* Type: ttCREDENTIAL_ACCEPT (59)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureCredentials
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CredentialAcceptBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CredentialCreateBuilder;
|
||||
* Type: ttCREDENTIAL_CREATE (58)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureCredentials
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CredentialCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CredentialDeleteBuilder;
|
||||
* Type: ttCREDENTIAL_DELETE (60)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureCredentials
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CredentialDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class DIDDeleteBuilder;
|
||||
* Type: ttDID_DELETE (50)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureDID
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use DIDDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class DIDSetBuilder;
|
||||
* Type: ttDID_SET (49)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureDID
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use DIDSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class DelegateSetBuilder;
|
||||
* Type: ttDELEGATE_SET (64)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featurePermissionDelegationV1_1
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use DelegateSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class DepositPreauthBuilder;
|
||||
* Type: ttDEPOSIT_PREAUTH (19)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use DepositPreauthBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class EnableAmendmentBuilder;
|
||||
* Type: ttAMENDMENT (100)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use EnableAmendmentBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class EscrowCancelBuilder;
|
||||
* Type: ttESCROW_CANCEL (4)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use EscrowCancelBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class EscrowCreateBuilder;
|
||||
* Type: ttESCROW_CREATE (1)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use EscrowCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class EscrowFinishBuilder;
|
||||
* Type: ttESCROW_FINISH (2)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use EscrowFinishBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LedgerStateFixBuilder;
|
||||
* Type: ttLEDGER_STATE_FIX (53)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: fixNFTokenPageLinks
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LedgerStateFixBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanBrokerCoverClawbackBuilder;
|
||||
* Type: ttLOAN_BROKER_COVER_CLAWBACK (78)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanBrokerCoverClawbackBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanBrokerCoverDepositBuilder;
|
||||
* Type: ttLOAN_BROKER_COVER_DEPOSIT (76)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanBrokerCoverDepositBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanBrokerCoverWithdrawBuilder;
|
||||
* Type: ttLOAN_BROKER_COVER_WITHDRAW (77)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::MayAuthorizeMpt
|
||||
* Privileges: MayAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanBrokerCoverWithdrawBuilder to construct new transactions.
|
||||
@@ -121,32 +121,6 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCredentialIDs (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VECTOR256::type::value_type>
|
||||
getCredentialIDs() const
|
||||
{
|
||||
if (hasCredentialIDs())
|
||||
{
|
||||
return this->tx_->at(sfCredentialIDs);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCredentialIDs is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCredentialIDs() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCredentialIDs);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -240,17 +214,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCredentialIDs (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
LoanBrokerCoverWithdrawBuilder&
|
||||
setCredentialIDs(std::decay_t<typename SF_VECTOR256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCredentialIDs] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the LoanBrokerCoverWithdraw wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanBrokerDeleteBuilder;
|
||||
* Type: ttLOAN_BROKER_DELETE (75)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::MustDeleteAcct | Privilege::MayAuthorizeMpt
|
||||
* Privileges: MustDeleteAcct | MayAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanBrokerDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanBrokerSetBuilder;
|
||||
* Type: ttLOAN_BROKER_SET (74)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::CreatePseudoAcct | Privilege::MayAuthorizeMpt
|
||||
* Privileges: CreatePseudoAcct | MayAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanBrokerSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanDeleteBuilder;
|
||||
* Type: ttLOAN_DELETE (81)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanManageBuilder;
|
||||
* Type: ttLOAN_MANAGE (82)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::MayModifyVault
|
||||
* Privileges: MayModifyVault
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanManageBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanPayBuilder;
|
||||
* Type: ttLOAN_PAY (84)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::MayAuthorizeMpt | Privilege::MustModifyVault
|
||||
* Privileges: MayAuthorizeMpt | MustModifyVault
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanPayBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class LoanSetBuilder;
|
||||
* Type: ttLOAN_SET (80)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureLendingProtocol
|
||||
* Privileges: Privilege::MayAuthorizeMpt | Privilege::MustModifyVault
|
||||
* Privileges: MayAuthorizeMpt | MustModifyVault
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use LoanSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class MPTokenAuthorizeBuilder;
|
||||
* Type: ttMPTOKEN_AUTHORIZE (57)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureMPTokensV1
|
||||
* Privileges: Privilege::MustAuthorizeMpt
|
||||
* Privileges: MustAuthorizeMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use MPTokenAuthorizeBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class MPTokenIssuanceCreateBuilder;
|
||||
* Type: ttMPTOKEN_ISSUANCE_CREATE (54)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureMPTokensV1
|
||||
* Privileges: Privilege::CreateMptIssuance
|
||||
* Privileges: CreateMptIssuance
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use MPTokenIssuanceCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class MPTokenIssuanceDestroyBuilder;
|
||||
* Type: ttMPTOKEN_ISSUANCE_DESTROY (55)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureMPTokensV1
|
||||
* Privileges: Privilege::DestroyMptIssuance
|
||||
* Privileges: DestroyMptIssuance
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use MPTokenIssuanceDestroyBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class MPTokenIssuanceSetBuilder;
|
||||
* Type: ttMPTOKEN_ISSUANCE_SET (56)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureMPTokensV1
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use MPTokenIssuanceSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenAcceptOfferBuilder;
|
||||
* Type: ttNFTOKEN_ACCEPT_OFFER (29)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenAcceptOfferBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenBurnBuilder;
|
||||
* Type: ttNFTOKEN_BURN (26)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::ChangeNftCounts
|
||||
* Privileges: ChangeNftCounts
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenBurnBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenCancelOfferBuilder;
|
||||
* Type: ttNFTOKEN_CANCEL_OFFER (28)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenCancelOfferBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenCreateOfferBuilder;
|
||||
* Type: ttNFTOKEN_CREATE_OFFER (27)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenCreateOfferBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenMintBuilder;
|
||||
* Type: ttNFTOKEN_MINT (25)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::ChangeNftCounts
|
||||
* Privileges: ChangeNftCounts
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenMintBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class NFTokenModifyBuilder;
|
||||
* Type: ttNFTOKEN_MODIFY (61)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureDynamicNFT
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use NFTokenModifyBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class OfferCancelBuilder;
|
||||
* Type: ttOFFER_CANCEL (8)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use OfferCancelBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class OfferCreateBuilder;
|
||||
* Type: ttOFFER_CREATE (7)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::MayCreateMpt
|
||||
* Privileges: MayCreateMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use OfferCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class OracleDeleteBuilder;
|
||||
* Type: ttORACLE_DELETE (52)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featurePriceOracle
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use OracleDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class OracleSetBuilder;
|
||||
* Type: ttORACLE_SET (51)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featurePriceOracle
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use OracleSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentBuilder;
|
||||
* Type: ttPAYMENT (0)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::CreateAcct | Privilege::MayCreateMpt
|
||||
* Privileges: CreateAcct | MayCreateMpt
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PaymentBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentChannelClaimBuilder;
|
||||
* Type: ttPAYCHAN_CLAIM (15)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PaymentChannelClaimBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentChannelCreateBuilder;
|
||||
* Type: ttPAYCHAN_CREATE (13)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PaymentChannelCreateBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentChannelFundBuilder;
|
||||
* Type: ttPAYCHAN_FUND (14)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PaymentChannelFundBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PermissionedDomainDeleteBuilder;
|
||||
* Type: ttPERMISSIONED_DOMAIN_DELETE (63)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featurePermissionedDomains
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PermissionedDomainDeleteBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class PermissionedDomainSetBuilder;
|
||||
* Type: ttPERMISSIONED_DOMAIN_SET (62)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featurePermissionedDomains
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use PermissionedDomainSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class SetFeeBuilder;
|
||||
* Type: ttFEE (101)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SetFeeBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class SetRegularKeyBuilder;
|
||||
* Type: ttREGULAR_KEY_SET (5)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SetRegularKeyBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class SignerListSetBuilder;
|
||||
* Type: ttSIGNER_LIST_SET (12)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SignerListSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class SponsorshipSetBuilder;
|
||||
* Type: ttSPONSORSHIP_SET (91)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureSponsor
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SponsorshipSetBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class SponsorshipTransferBuilder;
|
||||
* Type: ttSPONSORSHIP_TRANSFER (90)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureSponsor
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SponsorshipTransferBuilder to construct new transactions.
|
||||
|
||||
@@ -21,7 +21,7 @@ class TicketCreateBuilder;
|
||||
* Type: ttTICKET_CREATE (10)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: uint256{}
|
||||
* Privileges: Privilege::NoPriv
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use TicketCreateBuilder to construct new transactions.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user