mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-02 10:02:27 +00:00
Compare commits
25 Commits
pratik/std
...
a1q123456/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bbb14696e | ||
|
|
df39ac33d2 | ||
|
|
96fdc711c0 | ||
|
|
a1344b91c3 | ||
|
|
5c8dfe5456 | ||
|
|
ab8c168e3b | ||
|
|
3a477e4d01 | ||
|
|
96bfc32fe2 | ||
|
|
de671863e2 | ||
|
|
e0cabb9f8c | ||
|
|
3d9c545f59 | ||
|
|
9b944ee8c2 | ||
|
|
509677abfd | ||
|
|
addc1e8e25 | ||
|
|
faf69da4b0 | ||
|
|
76e3b4fb0f | ||
|
|
e8bdbf975a | ||
|
|
2c765f6eb0 | ||
|
|
a9269fa846 | ||
|
|
15fd9feae5 | ||
|
|
b9d07730f3 | ||
|
|
85b65c8e9a | ||
|
|
8f182e825a | ||
|
|
cd78569d94 | ||
|
|
2c7af360c2 |
106
.clang-tidy
106
.clang-tidy
@@ -1,4 +1,6 @@
|
||||
---
|
||||
# This entire group of checks was applied to all cpp files but not all header files.
|
||||
# ---
|
||||
Checks: "-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
@@ -8,26 +10,26 @@ Checks: "-*,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-compare-pointer-to-member-virtual-function,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-crtp-constructor-accessibility,
|
||||
# bugprone-crtp-constructor-accessibility, # has issues
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-empty-catch,
|
||||
# bugprone-empty-catch, # has issues
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-inc-dec-in-conditions,
|
||||
bugprone-incorrect-enable-if,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
bugprone-integer-division,
|
||||
# bugprone-forward-declaration-namespace, # has issues
|
||||
# bugprone-inaccurate-erase,
|
||||
# bugprone-inc-dec-in-conditions,
|
||||
# bugprone-incorrect-enable-if,
|
||||
# bugprone-incorrect-roundings,
|
||||
# bugprone-infinite-loop,
|
||||
# bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
# bugprone-macro-parentheses, # has issues
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multi-level-implicit-pointer-conversion,
|
||||
# bugprone-multi-level-implicit-pointer-conversion, # has issues
|
||||
bugprone-multiple-new-in-one-expression,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
@@ -37,13 +39,13 @@ Checks: "-*,
|
||||
bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-return-const-ref-from-parameter,
|
||||
# bugprone-reserved-identifier, # has issues
|
||||
# bugprone-return-const-ref-from-parameter, # has issues
|
||||
bugprone-shared-ptr-array-mismatch,
|
||||
bugprone-signal-handler,
|
||||
bugprone-signed-char-misuse,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
# bugprone-sizeof-expression, # has issues
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-standalone-empty,
|
||||
bugprone-string-constructor,
|
||||
@@ -60,7 +62,7 @@ Checks: "-*,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-stringview-data-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-switch-missing-default-case,
|
||||
# bugprone-switch-missing-default-case, # has issues
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
@@ -71,61 +73,66 @@ Checks: "-*,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-use-after-move,
|
||||
# bugprone-use-after-move, # has issues
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-init-variables,
|
||||
cppcoreguidelines-misleading-capture-default-by-value,
|
||||
# cppcoreguidelines-init-variables, # has issues
|
||||
# cppcoreguidelines-misleading-capture-default-by-value, # has issues
|
||||
cppcoreguidelines-no-suspend-with-lock,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
# cppcoreguidelines-pro-type-member-init, # has issues
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||
cppcoreguidelines-use-default-member-init,
|
||||
cppcoreguidelines-virtual-class-destructor,
|
||||
# cppcoreguidelines-rvalue-reference-param-not-moved, # has issues
|
||||
# cppcoreguidelines-use-default-member-init, # has issues
|
||||
# cppcoreguidelines-virtual-class-destructor, # has issues
|
||||
hicpp-ignored-remove-result,
|
||||
misc-definitions-in-headers,
|
||||
# misc-definitions-in-headers, # has issues
|
||||
misc-header-include-cycle,
|
||||
misc-misplaced-const,
|
||||
misc-static-assert,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
# misc-throw-by-value-catch-by-reference, # has issues
|
||||
misc-unused-alias-decls,
|
||||
misc-unused-using-decls,
|
||||
modernize-deprecated-headers,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-inefficient-vector-operation,
|
||||
performance-move-const-arg,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-trivially-destructible,
|
||||
readability-avoid-nested-conditional-operator,
|
||||
readability-avoid-return-with-void-value,
|
||||
readability-braces-around-statements,
|
||||
readability-const-return-type,
|
||||
readability-container-contains,
|
||||
readability-container-size-empty,
|
||||
readability-convert-member-functions-to-static,
|
||||
# readability-avoid-nested-conditional-operator, # has issues
|
||||
# readability-avoid-return-with-void-value, # has issues
|
||||
# readability-braces-around-statements, # has issues
|
||||
# readability-const-return-type, # has issues
|
||||
# readability-container-contains, # has issues
|
||||
# readability-container-size-empty, # has issues
|
||||
# readability-convert-member-functions-to-static, # has issues
|
||||
readability-duplicate-include,
|
||||
readability-else-after-return,
|
||||
readability-enum-initial-value,
|
||||
readability-implicit-bool-conversion,
|
||||
readability-make-member-function-const,
|
||||
readability-math-missing-parentheses,
|
||||
# readability-else-after-return, # has issues
|
||||
# readability-enum-initial-value, # has issues
|
||||
# readability-implicit-bool-conversion, # has issues
|
||||
# readability-make-member-function-const, # has issues
|
||||
# readability-math-missing-parentheses, # has issues
|
||||
readability-misleading-indentation,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-casting,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-inline-specifier,
|
||||
readability-redundant-member-init,
|
||||
# readability-redundant-casting, # has issues
|
||||
# readability-redundant-declaration, # has issues
|
||||
# readability-redundant-inline-specifier, # has issues
|
||||
# readability-redundant-member-init, # has issues
|
||||
readability-redundant-string-init,
|
||||
readability-reference-to-constructed-temporary,
|
||||
readability-simplify-boolean-expr,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-suspicious-call-argument,
|
||||
# readability-simplify-boolean-expr, # has issues
|
||||
# readability-static-definition-in-anonymous-namespace, # has issues
|
||||
# readability-suspicious-call-argument, # has issues
|
||||
readability-use-std-min-max
|
||||
"
|
||||
# ---
|
||||
# checks that have some issues that need to be resolved:
|
||||
# other checks that have issues that need to be resolved:
|
||||
#
|
||||
# llvm-namespace-comment,
|
||||
# misc-const-correctness,
|
||||
@@ -134,7 +141,7 @@ Checks: "-*,
|
||||
#
|
||||
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
|
||||
# readability-identifier-naming,
|
||||
# readability-identifier-naming, # https://github.com/XRPLF/rippled/pull/6571
|
||||
#
|
||||
# modernize-concat-nested-namespaces,
|
||||
# modernize-pass-by-value,
|
||||
@@ -148,12 +155,6 @@ Checks: "-*,
|
||||
# modernize-use-starts-ends-with,
|
||||
# modernize-use-std-numbers,
|
||||
# modernize-use-using,
|
||||
#
|
||||
# performance-faster-string-find,
|
||||
# performance-for-range-copy,
|
||||
# performance-inefficient-vector-operation,
|
||||
# performance-move-const-arg,
|
||||
# performance-no-automatic-move,
|
||||
# ---
|
||||
#
|
||||
CheckOptions:
|
||||
@@ -195,5 +196,6 @@ CheckOptions:
|
||||
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
|
||||
# misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp'
|
||||
#
|
||||
# HeaderFilterRegex: '^.*/(src|tests)/.*\.(h|hpp)$'
|
||||
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp)$'
|
||||
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
@@ -21,6 +21,7 @@ libxrpl.protocol > xrpl.json
|
||||
libxrpl.protocol > xrpl.protocol
|
||||
libxrpl.protocol_autogen > xrpl.protocol_autogen
|
||||
libxrpl.rdb > xrpl.basics
|
||||
libxrpl.rdb > xrpl.core
|
||||
libxrpl.rdb > xrpl.rdb
|
||||
libxrpl.resource > xrpl.basics
|
||||
libxrpl.resource > xrpl.json
|
||||
@@ -90,6 +91,7 @@ test.core > xrpl.server
|
||||
test.csf > xrpl.basics
|
||||
test.csf > xrpld.consensus
|
||||
test.csf > xrpl.json
|
||||
test.csf > xrpl.ledger
|
||||
test.csf > xrpl.protocol
|
||||
test.json > test.jtx
|
||||
test.json > xrpl.json
|
||||
@@ -108,7 +110,6 @@ test.jtx > xrpl.tx
|
||||
test.ledger > test.jtx
|
||||
test.ledger > test.toplevel
|
||||
test.ledger > xrpl.basics
|
||||
test.ledger > xrpld.app
|
||||
test.ledger > xrpld.core
|
||||
test.ledger > xrpl.ledger
|
||||
test.ledger > xrpl.protocol
|
||||
@@ -125,6 +126,7 @@ test.overlay > xrpl.basics
|
||||
test.overlay > xrpld.app
|
||||
test.overlay > xrpld.overlay
|
||||
test.overlay > xrpld.peerfinder
|
||||
test.overlay > xrpl.ledger
|
||||
test.overlay > xrpl.nodestore
|
||||
test.overlay > xrpl.protocol
|
||||
test.overlay > xrpl.shamap
|
||||
@@ -183,7 +185,6 @@ xrpl.conditions > xrpl.basics
|
||||
xrpl.conditions > xrpl.protocol
|
||||
xrpl.core > xrpl.basics
|
||||
xrpl.core > xrpl.json
|
||||
xrpl.core > xrpl.ledger
|
||||
xrpl.core > xrpl.protocol
|
||||
xrpl.json > xrpl.basics
|
||||
xrpl.ledger > xrpl.basics
|
||||
@@ -234,6 +235,7 @@ xrpld.app > xrpl.shamap
|
||||
xrpld.app > xrpl.tx
|
||||
xrpld.consensus > xrpl.basics
|
||||
xrpld.consensus > xrpl.json
|
||||
xrpld.consensus > xrpl.ledger
|
||||
xrpld.consensus > xrpl.protocol
|
||||
xrpld.core > xrpl.basics
|
||||
xrpld.core > xrpl.core
|
||||
|
||||
23
.github/scripts/strategy-matrix/generate.py
vendored
23
.github/scripts/strategy-matrix/generate.py
vendored
@@ -235,16 +235,11 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
# so that they are easier to identify in the GitHub Actions UI, as long
|
||||
# names get truncated.
|
||||
# Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros.
|
||||
# Note: GCC ASAN's detect_stack_use_after_return produces false positives with
|
||||
# Boost.Context fibers (boost::asio::spawn). Mitigated in reusable-build-test-config.yml
|
||||
# by setting detect_stack_use_after_return=0 for GCC.
|
||||
# See: https://github.com/google/sanitizers/issues/856
|
||||
if os[
|
||||
"distro_version"
|
||||
] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [
|
||||
"gcc-13",
|
||||
"clang-17",
|
||||
]:
|
||||
# GCC-Asan rippled-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
|
||||
if (
|
||||
os["distro_version"] == "bookworm"
|
||||
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
|
||||
):
|
||||
# Add ASAN + UBSAN configuration.
|
||||
configurations.append(
|
||||
{
|
||||
@@ -258,19 +253,19 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
"sanitizers": "address,undefinedbehavior",
|
||||
}
|
||||
)
|
||||
# TSAN is activated on gcc-13 and clang-17.
|
||||
activate_tsan = True
|
||||
# TSAN is deactivated due to seg faults with latest compilers.
|
||||
activate_tsan = False
|
||||
if activate_tsan:
|
||||
configurations.append(
|
||||
{
|
||||
"config_name": config_name + "-tsan",
|
||||
"config_name": config_name + "-tsan-ubsan",
|
||||
"cmake_args": cmake_args,
|
||||
"cmake_target": cmake_target,
|
||||
"build_only": build_only,
|
||||
"build_type": build_type,
|
||||
"os": os,
|
||||
"architecture": architecture,
|
||||
"sanitizers": "thread",
|
||||
"sanitizers": "thread,undefinedbehavior",
|
||||
}
|
||||
)
|
||||
else:
|
||||
|
||||
25
.github/workflows/conflicting-pr.yml
vendored
Normal file
25
.github/workflows/conflicting-pr.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Label PRs with merge conflicts
|
||||
|
||||
on:
|
||||
# So that PRs touching the same files as the push are updated.
|
||||
push:
|
||||
# So that the `dirtyLabel` is removed if conflicts are resolved.
|
||||
# We recommend `pull_request_target` so that github secrets are available.
|
||||
# In `pull_request` we wouldn't be able to change labels of fork PRs.
|
||||
pull_request_target:
|
||||
types: [synchronize]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if PRs are dirty
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
with:
|
||||
dirtyLabel: "PR: has conflicts"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
commentOnDirty: "This PR has conflicts, please resolve them in order for the PR to be reviewed."
|
||||
commentOnClean: "All conflicts have been resolved. Assigned reviewers can now start or resume their review."
|
||||
7
.github/workflows/publish-docs.yml
vendored
7
.github/workflows/publish-docs.yml
vendored
@@ -6,7 +6,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release*"
|
||||
paths:
|
||||
- ".github/workflows/publish-docs.yml"
|
||||
- "*.md"
|
||||
@@ -82,13 +81,13 @@ jobs:
|
||||
cmake --build . --target docs --parallel ${BUILD_NPROC}
|
||||
|
||||
- name: Create documentation artifact
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }}
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
|
||||
with:
|
||||
path: ${{ env.BUILD_DIR }}/docs/html
|
||||
|
||||
deploy:
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -100,4 +99,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
57
.github/workflows/reusable-build-test-config.yml
vendored
57
.github/workflows/reusable-build-test-config.yml
vendored
@@ -153,19 +153,6 @@ jobs:
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
env:
|
||||
MESSAGE: |
|
||||
@@ -189,6 +176,19 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
run: |
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && runner.os == 'Linux' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: xrpld-${{ inputs.config_name }}
|
||||
@@ -231,11 +231,8 @@ jobs:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
|
||||
# GCC ASAN's detect_stack_use_after_return produces false positives with
|
||||
# Boost.Context fiber stack switching (used by boost::asio::spawn).
|
||||
# See: https://github.com/google/sanitizers/issues/856
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0:detect_stack_use_after_return=0"
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
|
||||
@@ -245,11 +242,10 @@ jobs:
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
# Windows locks some of the build files while running tests, and parallel jobs can collide.
|
||||
# Sanitizer builds use single-threaded execution to reduce memory pressure.
|
||||
# Windows locks some of the build files while running tests, and parallel jobs can collide
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
PARALLELISM: ${{ env.SANITIZERS_ENABLED == 'true' && '1' || (runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc) }}
|
||||
PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
ctest \
|
||||
--output-on-failure \
|
||||
@@ -260,21 +256,12 @@ jobs:
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
env:
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
COVERAGE_ON: ${{ env.COVERAGE_ENABLED }}
|
||||
SANITIZERS_ON: ${{ env.SANITIZERS_ENABLED }}
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
if [ "${COVERAGE_ON}" = "true" ]; then
|
||||
PARALLELISM=$(( NPROC > 2 ? NPROC - 2 : 1 ))
|
||||
else
|
||||
PARALLELISM="${NPROC}"
|
||||
fi
|
||||
if [ "${SANITIZERS_ON}" = "true" ]; then
|
||||
PARALLELISM=1
|
||||
fi
|
||||
./xrpld --unittest --unittest-jobs "${PARALLELISM}" 2>&1 | tee unittest.log
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$(( BUILD_NPROC - 2 ))
|
||||
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
@@ -311,7 +298,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt
|
||||
|
||||
- name: Upload clang-tidy output
|
||||
if: steps.run_clang_tidy.outcome != 'success'
|
||||
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: clang-tidy-results
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -71,6 +71,8 @@ DerivedData
|
||||
/.zed/
|
||||
|
||||
# AI tools.
|
||||
/.agent
|
||||
/.agents
|
||||
/.augment
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,142 +0,0 @@
|
||||
# Boost.Coroutine to C++20 Migration — Task List
|
||||
|
||||
> Parent document: [BoostToStdCoroutineSwitchPlan.md](BoostToStdCoroutineSwitchPlan.md)
|
||||
|
||||
---
|
||||
|
||||
## Milestone 1: New Coroutine Primitives
|
||||
|
||||
- [ ] **1.1** Design `CoroTask<T>` class with `promise_type`
|
||||
- Define `promise_type` with `initial_suspend`, `final_suspend`, `unhandled_exception`, `return_value`/`return_void`
|
||||
- Implement `FinalAwaiter` for continuation support
|
||||
- Implement move-only RAII handle wrapper
|
||||
- Support both `CoroTask<T>` and `CoroTask<void>`
|
||||
|
||||
- [ ] **1.2** Design and implement `JobQueueAwaiter`
|
||||
- `await_suspend()` calls `jq_.addJob(type, name, [h]{ h.resume(); })`
|
||||
- Handle `addJob()` failure (shutdown) — resume with error flag or throw
|
||||
- Integrate `nSuspend_` counter increment/decrement
|
||||
|
||||
- [ ] **1.3** Implement `LocalValues` swap in new coroutine resume path
|
||||
- Before `handle.resume()`: save thread-local, install coroutine-local
|
||||
- After `handle.resume()` returns: restore thread-local
|
||||
- Ensure this works when coroutine migrates between threads
|
||||
|
||||
- [ ] **1.4** Add `postCoroTask()` template to `JobQueue`
|
||||
- Accept callable returning `CoroTask<void>`
|
||||
- Schedule initial execution on JobQueue (mirror `postCoro()` behavior)
|
||||
- Return a handle/shared_ptr for join/cancel
|
||||
|
||||
- [ ] **1.5** Write unit tests (`src/test/core/CoroTask_test.cpp`)
|
||||
- Test `CoroTask<void>` runs to completion
|
||||
- Test `CoroTask<int>` returns value
|
||||
- Test exception propagation across co_await
|
||||
- Test coroutine destruction before completion
|
||||
- Test `JobQueueAwaiter` schedules on correct thread
|
||||
- Test `LocalValue` isolation across 4+ coroutines
|
||||
- Test shutdown rejection (addJob returns false)
|
||||
- Test `correct_order` equivalent (yield → join → post → complete)
|
||||
- Test `incorrect_order` equivalent (post → yield → complete)
|
||||
- Test multiple sequential co_await points
|
||||
|
||||
- [ ] **1.6** Verify build on GCC 12+, Clang 16+
|
||||
- [ ] **1.7** Run ASAN + TSAN on new tests
|
||||
- [ ] **1.8** Run full `--unittest` suite (no regressions)
|
||||
- [ ] **1.9** Self-review and create PR #1
|
||||
|
||||
---
|
||||
|
||||
## Milestone 2: Entry Point Migration
|
||||
|
||||
- [ ] **2.1** Migrate `ServerHandler::onRequest()` (`ServerHandler.cpp:287`)
|
||||
- Replace `m_jobQueue.postCoro(jtCLIENT_RPC, ...)` with `postCoroTask()`
|
||||
- Update lambda to return `CoroTask<void>` (add `co_return`)
|
||||
- Update `processSession` to accept new coroutine type
|
||||
|
||||
- [ ] **2.2** Migrate `ServerHandler::onWSMessage()` (`ServerHandler.cpp:325`)
|
||||
- Replace `m_jobQueue.postCoro(jtCLIENT_WEBSOCKET, ...)` with `postCoroTask()`
|
||||
- Update lambda signature
|
||||
|
||||
- [ ] **2.3** Migrate `GRPCServer::CallData::process()` (`GRPCServer.cpp:102`)
|
||||
- Replace `app_.getJobQueue().postCoro(JobType::jtRPC, ...)` with `postCoroTask()`
|
||||
- Update `process(shared_ptr<Coro> coro)` overload signature
|
||||
|
||||
- [ ] **2.4** Update `RPC::Context` (`Context.h:27`)
|
||||
- Replace `std::shared_ptr<JobQueue::Coro> coro{}` with new coroutine wrapper type
|
||||
- Ensure all code that accesses `context.coro` compiles
|
||||
|
||||
- [ ] **2.5** Update `ServerHandler.h` signatures
|
||||
- `processSession()` and `processRequest()` parameter types
|
||||
|
||||
- [ ] **2.6** Update `GRPCServer.h` signatures
|
||||
- `process()` method parameter types
|
||||
|
||||
- [ ] **2.7** Run full `--unittest` suite
|
||||
- [ ] **2.8** Manual smoke test: HTTP + WS + gRPC RPC requests
|
||||
- [ ] **2.9** Run ASAN + TSAN
|
||||
- [ ] **2.10** Self-review and create PR #2
|
||||
|
||||
---
|
||||
|
||||
## Milestone 3: Handler Migration
|
||||
|
||||
- [ ] **3.1** Migrate `doRipplePathFind()` (`RipplePathFind.cpp`)
|
||||
- Replace `context.coro->yield()` with `co_await PathFindAwaiter{...}`
|
||||
- Replace continuation lambda's `coro->post()` / `coro->resume()` with awaiter scheduling
|
||||
- Handle shutdown case (post failure) in awaiter
|
||||
|
||||
- [ ] **3.2** Create `PathFindAwaiter` (or use generic `JobQueueAwaiter`)
|
||||
- Encapsulate the continuation + yield pattern from `RipplePathFind.cpp` lines 108-132
|
||||
|
||||
- [ ] **3.3** Update `Path_test.cpp`
|
||||
- Replace `postCoro` usage with `postCoroTask`
|
||||
- Ensure `context.coro` usage matches new type
|
||||
|
||||
- [ ] **3.4** Update `AMMTest.cpp`
|
||||
- Replace `postCoro` usage with `postCoroTask`
|
||||
|
||||
- [ ] **3.5** Rewrite `Coroutine_test.cpp` for new API
|
||||
- `correct_order`: postCoroTask → co_await → join → resume → complete
|
||||
- `incorrect_order`: post before yield equivalent
|
||||
- `thread_specific_storage`: 4 coroutines with LocalValue isolation
|
||||
|
||||
- [ ] **3.6** Update `JobQueue_test.cpp` `testPostCoro`
|
||||
- Migrate to `postCoroTask` API
|
||||
|
||||
- [ ] **3.7** Verify `ripple_path_find` works end-to-end with new coroutines
|
||||
- [ ] **3.8** Test shutdown-during-pathfind scenario
|
||||
- [ ] **3.9** Run full `--unittest` suite
|
||||
- [ ] **3.10** Run ASAN + TSAN
|
||||
- [ ] **3.11** Self-review and create PR #3
|
||||
|
||||
---
|
||||
|
||||
## Milestone 4: Cleanup & Validation
|
||||
|
||||
- [ ] **4.1** Delete `include/xrpl/core/Coro.ipp`
|
||||
- [ ] **4.2** Remove from `JobQueue.h`:
|
||||
- `#include <boost/coroutine2/all.hpp>`
|
||||
- `struct Coro_create_t`
|
||||
- `class Coro` (entire class)
|
||||
- `postCoro()` template
|
||||
- Comment block (lines 322-377) describing old race condition
|
||||
- [ ] **4.3** Update `cmake/deps/Boost.cmake`:
|
||||
- Remove `coroutine` from `find_package(Boost REQUIRED COMPONENTS ...)`
|
||||
- Remove `Boost::coroutine` from `target_link_libraries`
|
||||
- [ ] **4.4** Update `cmake/XrplInterface.cmake`:
|
||||
- Remove `BOOST_COROUTINES2_NO_DEPRECATION_WARNING`
|
||||
- [ ] **4.5** Run memory benchmark
|
||||
- Create N=1000 coroutines, compare RSS: before vs after
|
||||
- Document results
|
||||
- [ ] **4.6** Run context switch benchmark
|
||||
- 100K yield/resume cycles, compare latency: before vs after
|
||||
- Document results
|
||||
- [ ] **4.7** Run RPC throughput benchmark
|
||||
- Concurrent `ripple_path_find` requests, compare throughput
|
||||
- Document results
|
||||
- [ ] **4.8** Run full `--unittest` suite
|
||||
- [ ] **4.9** Run ASAN, TSAN, UBSan
|
||||
- Confirm `__asan_handle_no_return` warnings are gone
|
||||
- [ ] **4.10** Verify build on all supported compilers
|
||||
- [ ] **4.11** Self-review and create PR #4
|
||||
- [ ] **4.12** Document final benchmark results in PR description
|
||||
@@ -108,11 +108,10 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
# Level 05
|
||||
## Set up code generation for protocol_autogen module
|
||||
## Set up code generation for protocol_autogen module.
|
||||
## Generation runs at configure time (when the stamp is stale),
|
||||
## so generated files are always present before add_module GLOBs them.
|
||||
include(XrplProtocolAutogen)
|
||||
# Must call setup_protocol_autogen before add_module so that:
|
||||
# 1. Stale generated files are cleared before GLOB runs
|
||||
# 2. Output file list is known for custom commands
|
||||
setup_protocol_autogen()
|
||||
|
||||
add_module(xrpl protocol_autogen)
|
||||
@@ -121,11 +120,6 @@ target_link_libraries(
|
||||
PUBLIC xrpl.libxrpl.protocol
|
||||
)
|
||||
|
||||
# Ensure code generation runs before compiling protocol_autogen
|
||||
if(TARGET protocol_autogen_generate)
|
||||
add_dependencies(xrpl.libxrpl.protocol_autogen protocol_autogen_generate)
|
||||
endif()
|
||||
|
||||
# Level 06
|
||||
add_module(xrpl core)
|
||||
target_link_libraries(
|
||||
|
||||
@@ -23,6 +23,7 @@ target_compile_definitions(
|
||||
BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
>
|
||||
$<$<NOT:$<BOOL:${boost_show_deprecated}>>:
|
||||
BOOST_COROUTINES2_NO_DEPRECATION_WARNING
|
||||
BOOST_BEAST_ALLOW_DEPRECATED
|
||||
BOOST_FILESYSTEM_DEPRECATED
|
||||
>
|
||||
|
||||
@@ -15,7 +15,6 @@ set(CODEGEN_VENV_DIR
|
||||
)
|
||||
|
||||
# Function to set up code generation for protocol_autogen module
|
||||
# This runs at configure time to generate C++ wrapper classes from macro files
|
||||
function(setup_protocol_autogen)
|
||||
# Directory paths
|
||||
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
|
||||
@@ -25,7 +24,7 @@ function(setup_protocol_autogen)
|
||||
set(AUTOGEN_TEST_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen"
|
||||
)
|
||||
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts")
|
||||
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts/codegen")
|
||||
|
||||
# Input macro files
|
||||
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
|
||||
@@ -43,6 +42,7 @@ function(setup_protocol_autogen)
|
||||
set(LEDGER_TEST_TEMPLATE
|
||||
"${SCRIPTS_DIR}/templates/LedgerEntryTests.cpp.mako"
|
||||
)
|
||||
set(UPDATE_STAMP_SCRIPT "${SCRIPTS_DIR}/update_codegen_stamp.py")
|
||||
|
||||
# Check if code generation is disabled
|
||||
if(XRPL_NO_CODEGEN)
|
||||
@@ -60,7 +60,33 @@ function(setup_protocol_autogen)
|
||||
file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/ledger_entries")
|
||||
file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/transactions")
|
||||
|
||||
# Find Python3 - check if already found by Conan or find it ourselves
|
||||
# === Stamp file check ===
|
||||
# All input files whose content affects code generation output.
|
||||
set(STAMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/scripts/codegen/.codegen_stamp")
|
||||
set(ALL_INPUT_FILES
|
||||
"${TRANSACTIONS_MACRO}"
|
||||
"${LEDGER_ENTRIES_MACRO}"
|
||||
"${SFIELDS_MACRO}"
|
||||
"${GENERATE_TX_SCRIPT}"
|
||||
"${GENERATE_LEDGER_SCRIPT}"
|
||||
"${REQUIREMENTS_FILE}"
|
||||
"${MACRO_PARSER_COMMON}"
|
||||
"${TX_TEMPLATE}"
|
||||
"${TX_TEST_TEMPLATE}"
|
||||
"${LEDGER_TEMPLATE}"
|
||||
"${LEDGER_TEST_TEMPLATE}"
|
||||
)
|
||||
|
||||
# Tell CMake to reconfigure automatically when any input file changes.
|
||||
# The reconfigure itself is cheap — it runs the stamp check below
|
||||
# which only invokes stdlib Python (no venv needed).
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${ALL_INPUT_FILES}
|
||||
)
|
||||
|
||||
# Find Python3 (needed for stamp check; no venv required).
|
||||
if(NOT Python3_EXECUTABLE)
|
||||
find_package(Python3 COMPONENTS Interpreter QUIET)
|
||||
endif()
|
||||
@@ -79,19 +105,45 @@ function(setup_protocol_autogen)
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(STATUS "Using Python3 for code generation: ${Python3_EXECUTABLE}")
|
||||
# Check whether the stamp is up-to-date (stdlib-only, no venv).
|
||||
execute_process(
|
||||
COMMAND
|
||||
${Python3_EXECUTABLE} "${UPDATE_STAMP_SCRIPT}" --check
|
||||
"${STAMP_FILE}" ${ALL_INPUT_FILES}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE STAMP_CHECK_RESULT
|
||||
)
|
||||
|
||||
# Set up Python virtual environment for code generation
|
||||
# ------------------------------------------------------------------
|
||||
# Fast path: stamp matches — generated files are up to date.
|
||||
# ------------------------------------------------------------------
|
||||
if(STAMP_CHECK_RESULT EQUAL 0)
|
||||
message(
|
||||
STATUS
|
||||
"Protocol autogen: inputs unchanged (stamp matches), skipping generation"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Slow path: stamp mismatch — run generation at configure time.
|
||||
# ------------------------------------------------------------------
|
||||
message(
|
||||
STATUS
|
||||
"Protocol autogen: inputs changed, running code generation..."
|
||||
)
|
||||
|
||||
# Set up Python virtual environment for code generation.
|
||||
if(CODEGEN_VENV_DIR)
|
||||
# User-provided venv - skip automatic setup
|
||||
# User-provided venv - skip automatic setup.
|
||||
set(VENV_DIR "${CODEGEN_VENV_DIR}")
|
||||
message(STATUS "Using user-provided Python venv: ${VENV_DIR}")
|
||||
else()
|
||||
# Use default venv in build directory
|
||||
# Use default venv in build directory.
|
||||
set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv")
|
||||
endif()
|
||||
|
||||
# Determine the Python executable path in the venv
|
||||
# Determine the Python/pip executables inside the venv.
|
||||
if(WIN32)
|
||||
set(VENV_PYTHON "${VENV_DIR}/Scripts/python.exe")
|
||||
set(VENV_PIP "${VENV_DIR}/Scripts/pip.exe")
|
||||
@@ -100,9 +152,9 @@ function(setup_protocol_autogen)
|
||||
set(VENV_PIP "${VENV_DIR}/bin/pip")
|
||||
endif()
|
||||
|
||||
# Only auto-setup venv if not user-provided
|
||||
# Create or update the virtual environment if needed.
|
||||
if(NOT CODEGEN_VENV_DIR)
|
||||
# Check if venv needs to be created or updated
|
||||
# Check if venv needs to be created or updated.
|
||||
set(VENV_NEEDS_UPDATE FALSE)
|
||||
if(NOT EXISTS "${VENV_PYTHON}")
|
||||
set(VENV_NEEDS_UPDATE TRUE)
|
||||
@@ -122,8 +174,9 @@ function(setup_protocol_autogen)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create/update virtual environment if needed
|
||||
# Create/update virtual environment if needed.
|
||||
if(VENV_NEEDS_UPDATE)
|
||||
# Create the venv.
|
||||
message(
|
||||
STATUS
|
||||
"Setting up Python virtual environment at ${VENV_DIR}"
|
||||
@@ -140,6 +193,29 @@ function(setup_protocol_autogen)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Warn if pip is configured with a non-default index (may need VPN).
|
||||
execute_process(
|
||||
COMMAND ${VENV_PIP} config get global.index-url
|
||||
OUTPUT_VARIABLE PIP_INDEX_URL
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
# Default PyPI URL
|
||||
set(DEFAULT_PIP_INDEX "https://pypi.org/simple")
|
||||
|
||||
# Show warning if using non-default index
|
||||
if(PIP_INDEX_URL AND NOT PIP_INDEX_URL STREQUAL "")
|
||||
if(NOT PIP_INDEX_URL STREQUAL DEFAULT_PIP_INDEX)
|
||||
message(
|
||||
WARNING
|
||||
"Private pip index URL detected: ${PIP_INDEX_URL}\n"
|
||||
"You may need to connect to VPN to access this URL."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Install dependencies.
|
||||
message(STATUS "Installing Python dependencies...")
|
||||
execute_process(
|
||||
COMMAND ${VENV_PIP} install --upgrade pip
|
||||
@@ -163,125 +239,56 @@ function(setup_protocol_autogen)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Mark requirements as installed
|
||||
# Mark requirements as installed.
|
||||
file(TOUCH "${VENV_DIR}/.requirements_installed")
|
||||
message(STATUS "Python virtual environment ready")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# At configure time - get list of output files for transactions
|
||||
# Generate transaction classes.
|
||||
execute_process(
|
||||
COMMAND
|
||||
${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}"
|
||||
--header-dir "${AUTOGEN_HEADER_DIR}/transactions" --test-dir
|
||||
"${AUTOGEN_TEST_DIR}/transactions" --list-outputs
|
||||
OUTPUT_VARIABLE TX_OUTPUT_FILES
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE TX_LIST_RESULT
|
||||
ERROR_VARIABLE TX_LIST_ERROR
|
||||
)
|
||||
if(NOT TX_LIST_RESULT EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Failed to list transaction output files:\n${TX_LIST_ERROR}"
|
||||
)
|
||||
endif()
|
||||
# Convert newline-separated list to CMake list
|
||||
string(REPLACE "\\" "/" TX_OUTPUT_FILES "${TX_OUTPUT_FILES}")
|
||||
string(REPLACE "\n" ";" TX_OUTPUT_FILES "${TX_OUTPUT_FILES}")
|
||||
|
||||
# At configure time - get list of output files for ledger entries
|
||||
execute_process(
|
||||
COMMAND
|
||||
${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}"
|
||||
--header-dir "${AUTOGEN_HEADER_DIR}/ledger_entries" --test-dir
|
||||
"${AUTOGEN_TEST_DIR}/ledger_entries" --list-outputs
|
||||
OUTPUT_VARIABLE LEDGER_OUTPUT_FILES
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE LEDGER_LIST_RESULT
|
||||
ERROR_VARIABLE LEDGER_LIST_ERROR
|
||||
)
|
||||
if(NOT LEDGER_LIST_RESULT EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Failed to list ledger entry output files:\n${LEDGER_LIST_ERROR}"
|
||||
)
|
||||
endif()
|
||||
# Convert newline-separated list to CMake list
|
||||
string(REPLACE "\\" "/" LEDGER_OUTPUT_FILES "${LEDGER_OUTPUT_FILES}")
|
||||
string(REPLACE "\n" ";" LEDGER_OUTPUT_FILES "${LEDGER_OUTPUT_FILES}")
|
||||
|
||||
# Custom command to generate transaction classes at build time
|
||||
add_custom_command(
|
||||
OUTPUT ${TX_OUTPUT_FILES}
|
||||
COMMAND
|
||||
${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}"
|
||||
--header-dir "${AUTOGEN_HEADER_DIR}/transactions" --test-dir
|
||||
"${AUTOGEN_TEST_DIR}/transactions" --sfields-macro
|
||||
"${SFIELDS_MACRO}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS
|
||||
"${TRANSACTIONS_MACRO}"
|
||||
"${SFIELDS_MACRO}"
|
||||
"${GENERATE_TX_SCRIPT}"
|
||||
"${MACRO_PARSER_COMMON}"
|
||||
"${TX_TEMPLATE}"
|
||||
"${TX_TEST_TEMPLATE}"
|
||||
"${REQUIREMENTS_FILE}"
|
||||
COMMENT "Generating transaction classes from transactions.macro..."
|
||||
VERBATIM
|
||||
RESULT_VARIABLE TX_RESULT
|
||||
ERROR_VARIABLE TX_ERROR
|
||||
)
|
||||
if(NOT TX_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Transaction code generation failed:\n${TX_ERROR}")
|
||||
endif()
|
||||
|
||||
# Custom command to generate ledger entry classes at build time
|
||||
add_custom_command(
|
||||
OUTPUT ${LEDGER_OUTPUT_FILES}
|
||||
# Generate ledger entry classes.
|
||||
execute_process(
|
||||
COMMAND
|
||||
${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}"
|
||||
--header-dir "${AUTOGEN_HEADER_DIR}/ledger_entries" --test-dir
|
||||
"${AUTOGEN_TEST_DIR}/ledger_entries" --sfields-macro
|
||||
"${SFIELDS_MACRO}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS
|
||||
"${LEDGER_ENTRIES_MACRO}"
|
||||
"${SFIELDS_MACRO}"
|
||||
"${GENERATE_LEDGER_SCRIPT}"
|
||||
"${MACRO_PARSER_COMMON}"
|
||||
"${LEDGER_TEMPLATE}"
|
||||
"${LEDGER_TEST_TEMPLATE}"
|
||||
"${REQUIREMENTS_FILE}"
|
||||
COMMENT "Generating ledger entry classes from ledger_entries.macro..."
|
||||
VERBATIM
|
||||
RESULT_VARIABLE LEDGER_RESULT
|
||||
ERROR_VARIABLE LEDGER_ERROR
|
||||
)
|
||||
if(NOT LEDGER_RESULT EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Ledger entry code generation failed:\n${LEDGER_ERROR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create a custom target that depends on all generated files
|
||||
add_custom_target(
|
||||
protocol_autogen_generate
|
||||
DEPENDS ${TX_OUTPUT_FILES} ${LEDGER_OUTPUT_FILES}
|
||||
COMMENT "Protocol autogen code generation"
|
||||
# Update the stamp file so subsequent configures skip generation.
|
||||
execute_process(
|
||||
COMMAND
|
||||
${Python3_EXECUTABLE} "${UPDATE_STAMP_SCRIPT}" --update
|
||||
"${STAMP_FILE}" ${ALL_INPUT_FILES}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE STAMP_RESULT
|
||||
)
|
||||
if(NOT STAMP_RESULT EQUAL 0)
|
||||
message(WARNING "Failed to update codegen stamp file")
|
||||
endif()
|
||||
|
||||
# Extract test files from output lists (files ending in Tests.cpp)
|
||||
set(PROTOCOL_AUTOGEN_TEST_SOURCES "")
|
||||
foreach(FILE ${TX_OUTPUT_FILES} ${LEDGER_OUTPUT_FILES})
|
||||
if(FILE MATCHES "Tests\\.cpp$")
|
||||
list(APPEND PROTOCOL_AUTOGEN_TEST_SOURCES "${FILE}")
|
||||
endif()
|
||||
endforeach()
|
||||
# Export test sources to parent scope for use in test CMakeLists.txt
|
||||
set(PROTOCOL_AUTOGEN_TEST_SOURCES
|
||||
"${PROTOCOL_AUTOGEN_TEST_SOURCES}"
|
||||
CACHE INTERNAL
|
||||
"Generated protocol_autogen test sources"
|
||||
)
|
||||
|
||||
# Register dependencies so CMake reconfigures when macro files change
|
||||
# (to update the list of output files)
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY
|
||||
CMAKE_CONFIGURE_DEPENDS
|
||||
"${TRANSACTIONS_MACRO}"
|
||||
"${LEDGER_ENTRIES_MACRO}"
|
||||
)
|
||||
message(STATUS "Protocol autogen: code generation complete")
|
||||
endfunction()
|
||||
|
||||
@@ -39,21 +39,25 @@ if(Boost_COMPILER)
|
||||
target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking)
|
||||
endif()
|
||||
|
||||
# Boost.Context's ucontext backend has sanitizer fiber-switching annotations
|
||||
# that are compiled in when BOOST_USE_ASAN/BOOST_USE_TSAN is defined.
|
||||
# This tells the sanitizer about fiber stack switches used by boost::asio::spawn,
|
||||
# preventing false positive errors.
|
||||
# BOOST_USE_UCONTEXT ensures the ucontext backend is selected (fcontext does
|
||||
# not support sanitizer annotations).
|
||||
# GCC 14+ has a false positive -Wuninitialized warning in Boost.Coroutine2's
|
||||
# state.hpp when compiled with -O3. This is due to GCC's intentional behavior
|
||||
# change (Bug #98871, #119388) where warnings from inlined system header code
|
||||
# are no longer suppressed by -isystem. The warning occurs in operator|= in
|
||||
# boost/coroutine2/detail/state.hpp when inlined from push_control_block::destroy().
|
||||
# See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119388
|
||||
if(is_gcc AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
|
||||
target_compile_options(xrpl_boost INTERFACE -Wno-uninitialized)
|
||||
endif()
|
||||
|
||||
# Boost.Context's ucontext backend has ASAN fiber-switching annotations
|
||||
# (start/finish_switch_fiber) that are compiled in when BOOST_USE_ASAN is defined.
|
||||
# This tells ASAN about coroutine stack switches, preventing false positive
|
||||
# stack-use-after-scope errors. BOOST_USE_UCONTEXT ensures the ucontext backend
|
||||
# is selected (fcontext does not support ASAN annotations).
|
||||
# These defines must match what Boost was compiled with (see conan/profiles/sanitizers).
|
||||
if(enable_asan)
|
||||
target_compile_definitions(
|
||||
xrpl_boost
|
||||
INTERFACE BOOST_USE_ASAN BOOST_USE_UCONTEXT
|
||||
)
|
||||
elseif(enable_tsan)
|
||||
target_compile_definitions(
|
||||
xrpl_boost
|
||||
INTERFACE BOOST_USE_TSAN BOOST_USE_UCONTEXT
|
||||
)
|
||||
endif()
|
||||
|
||||
31
conan.lock
31
conan.lock
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337",
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||
"sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1763899256.358",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1772560729.95",
|
||||
"protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762",
|
||||
"openssl/3.5.5#e6399de266349245a4542fc5f6c71552%1774367199.56",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27",
|
||||
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
|
||||
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
@@ -18,24 +18,23 @@
|
||||
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
|
||||
"jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
|
||||
"grpc/1.72.0#aaade9421980b2d926dbfb613d56c38a%1774376249.106",
|
||||
"grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1772623605.068",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1766500685.317",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
|
||||
"boost/1.90.0#d5e8defe7355494953be18524a7f135b%1769454080.269",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1751971032.423",
|
||||
"protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762",
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87",
|
||||
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1773780142.26",
|
||||
"cmake/3.31.11#f325c933f618a1fcebc1e1c0babfd1ba%1769622857.944",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1766594659.866",
|
||||
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
|
||||
@@ -47,13 +46,13 @@
|
||||
"boost/1.90.0"
|
||||
],
|
||||
"protobuf/[>=5.27.0 <7]": [
|
||||
"protobuf/6.32.1"
|
||||
"protobuf/6.33.5"
|
||||
],
|
||||
"lz4/1.9.4": [
|
||||
"lz4/1.10.0"
|
||||
],
|
||||
"sqlite3/[>=3.44 <4]": [
|
||||
"sqlite3/3.49.1"
|
||||
"sqlite3/3.51.0"
|
||||
],
|
||||
"boost/1.83.0": [
|
||||
"boost/1.90.0"
|
||||
|
||||
@@ -82,12 +82,5 @@ tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags"
|
||||
boost/*:without_context=False
|
||||
# Boost stacktrace fails to build with some sanitizers
|
||||
boost/*:without_stacktrace=True
|
||||
{% elif "thread" in sanitizers %}
|
||||
# Build Boost.Context with ucontext backend (not fcontext) so that
|
||||
# TSAN can track fiber/context switches made by boost::asio::spawn.
|
||||
# fcontext uses raw assembly that TSAN cannot instrument.
|
||||
boost/*:extra_b2_flags=context-impl=ucontext thread-sanitizer=on define=BOOST_USE_TSAN=1
|
||||
boost/*:without_context=False
|
||||
boost/*:without_stacktrace=True
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
23
conanfile.py
23
conanfile.py
@@ -4,7 +4,6 @@ import re
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
|
||||
from conan import ConanFile
|
||||
from conan import __version__ as conan_version
|
||||
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
@@ -30,10 +29,10 @@ class Xrpl(ConanFile):
|
||||
|
||||
requires = [
|
||||
"ed25519/2015.03",
|
||||
"grpc/1.72.0",
|
||||
"grpc/1.78.1",
|
||||
"libarchive/3.8.1",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.5.5",
|
||||
"openssl/3.6.1",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"zlib/1.3.1",
|
||||
@@ -44,7 +43,7 @@ class Xrpl(ConanFile):
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
"protobuf/6.32.1",
|
||||
"protobuf/6.33.5",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
@@ -60,7 +59,7 @@ class Xrpl(ConanFile):
|
||||
"xrpld": False,
|
||||
"boost/*:without_context": False,
|
||||
"boost/*:without_coroutine": True,
|
||||
"boost/*:without_coroutine2": True,
|
||||
"boost/*:without_coroutine2": False,
|
||||
"date/*:header_only": True,
|
||||
"ed25519/*:shared": False,
|
||||
"grpc/*:shared": False,
|
||||
@@ -137,20 +136,16 @@ class Xrpl(ConanFile):
|
||||
self.default_options["fPIC"] = False
|
||||
|
||||
def requirements(self):
|
||||
# Conan 2 requires transitive headers to be specified
|
||||
transitive_headers_opt = (
|
||||
{"transitive_headers": True} if conan_version.split(".")[0] == "2" else {}
|
||||
)
|
||||
self.requires("boost/1.90.0", force=True, **transitive_headers_opt)
|
||||
self.requires("date/3.0.4", **transitive_headers_opt)
|
||||
self.requires("boost/1.90.0", force=True, transitive_headers=True)
|
||||
self.requires("date/3.0.4", transitive_headers=True)
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
self.requires("protobuf/6.32.1", force=True)
|
||||
self.requires("sqlite3/3.49.1", force=True)
|
||||
self.requires("protobuf/6.33.5", force=True)
|
||||
self.requires("sqlite3/3.51.0", force=True)
|
||||
if self.options.jemalloc:
|
||||
self.requires("jemalloc/5.3.0")
|
||||
if self.options.rocksdb:
|
||||
self.requires("rocksdb/10.5.1")
|
||||
self.requires("xxhash/0.8.3", **transitive_headers_opt)
|
||||
self.requires("xxhash/0.8.3", transitive_headers=True)
|
||||
|
||||
exports_sources = (
|
||||
"CMakeLists.txt",
|
||||
|
||||
@@ -70,7 +70,6 @@ words:
|
||||
- coeffs
|
||||
- coldwallet
|
||||
- compr
|
||||
- cppcoro
|
||||
- conanfile
|
||||
- conanrun
|
||||
- confs
|
||||
@@ -98,7 +97,6 @@ words:
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- endmacro
|
||||
- eventfd
|
||||
- exceptioned
|
||||
- Falco
|
||||
- fcontext
|
||||
@@ -107,7 +105,6 @@ words:
|
||||
- fmtdur
|
||||
- fsanitize
|
||||
- funclets
|
||||
- gantt
|
||||
- gcov
|
||||
- gcovr
|
||||
- ghead
|
||||
@@ -151,7 +148,6 @@ words:
|
||||
- ltype
|
||||
- mcmodel
|
||||
- MEMORYSTATUSEX
|
||||
- Mankawde
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
@@ -200,7 +196,6 @@ words:
|
||||
- permissioned
|
||||
- pointee
|
||||
- populator
|
||||
- pratik
|
||||
- preauth
|
||||
- preauthorization
|
||||
- preauthorize
|
||||
@@ -209,7 +204,6 @@ words:
|
||||
- protobuf
|
||||
- protos
|
||||
- ptrs
|
||||
- Pratik
|
||||
- pushd
|
||||
- pyenv
|
||||
- pyparsing
|
||||
@@ -217,8 +211,6 @@ words:
|
||||
- queuable
|
||||
- Raphson
|
||||
- replayer
|
||||
- repost
|
||||
- reposts
|
||||
- rerere
|
||||
- retriable
|
||||
- RIPD
|
||||
@@ -250,7 +242,6 @@ words:
|
||||
- soci
|
||||
- socidb
|
||||
- sslws
|
||||
- stackful
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
@@ -269,7 +260,6 @@ words:
|
||||
- takerpays
|
||||
- ters
|
||||
- TMEndpointv2
|
||||
- TOCTOU
|
||||
- trixie
|
||||
- tx
|
||||
- txid
|
||||
|
||||
132
include/xrpl/core/Coro.ipp
Normal file
132
include/xrpl/core/Coro.ipp
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class F>
|
||||
JobQueue::Coro::Coro(Coro_create_t, JobQueue& jq, JobType type, std::string const& name, F&& f)
|
||||
: jq_(jq)
|
||||
, type_(type)
|
||||
, name_(name)
|
||||
, running_(false)
|
||||
, coro_(
|
||||
// Stack size of 1MB wasn't sufficient for deep calls. ASAN tests flagged the issue. Hence
|
||||
// increasing the size to 1.5MB.
|
||||
boost::context::protected_fixedsize_stack(1536 * 1024),
|
||||
[this, fn = std::forward<F>(f)](
|
||||
boost::coroutines2::asymmetric_coroutine<void>::push_type& do_yield) {
|
||||
yield_ = &do_yield;
|
||||
yield();
|
||||
fn(shared_from_this());
|
||||
#ifndef NDEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
inline JobQueue::Coro::~Coro()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
XRPL_ASSERT(finished_, "xrpl::JobQueue::Coro::~Coro : is finished");
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::yield() const
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(jq_.m_mutex);
|
||||
++jq_.nSuspend_;
|
||||
}
|
||||
(*yield_)();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JobQueue::Coro::post()
|
||||
{
|
||||
{
|
||||
std::lock_guard lk(mutex_run_);
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
// sp keeps 'this' alive
|
||||
if (jq_.addJob(type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// The coroutine will not run. Clean up running_.
|
||||
std::lock_guard lk(mutex_run_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::resume()
|
||||
{
|
||||
{
|
||||
std::lock_guard lk(mutex_run_);
|
||||
running_ = true;
|
||||
}
|
||||
{
|
||||
std::lock_guard lk(jq_.m_mutex);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
std::lock_guard lock(mutex_);
|
||||
// A late resume() can arrive after the coroutine has already completed.
|
||||
// This is an expected (if rare) outcome of the race condition documented
|
||||
// in JobQueue.h:354-377 where post() schedules a resume job before the
|
||||
// coroutine yields — the mutex serializes access, but by the time this
|
||||
// resume() acquires the lock the coroutine may have already run to
|
||||
// completion. Calling operator() on a completed boost::coroutine2 is
|
||||
// undefined behavior, so we must check and skip invoking the coroutine
|
||||
// body if it has already completed.
|
||||
if (coro_)
|
||||
{
|
||||
coro_();
|
||||
}
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
std::lock_guard lk(mutex_run_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JobQueue::Coro::runnable() const
|
||||
{
|
||||
return static_cast<bool>(coro_);
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::expectEarlyExit()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
if (!finished_)
|
||||
#endif
|
||||
{
|
||||
// expectEarlyExit() must only ever be called from outside the
|
||||
// Coro's stack. It you're inside the stack you can simply return
|
||||
// and be done.
|
||||
//
|
||||
// That said, since we're outside the Coro's stack, we need to
|
||||
// decrement the nSuspend that the Coro's call to yield caused.
|
||||
std::lock_guard lock(jq_.m_mutex);
|
||||
--jq_.nSuspend_;
|
||||
#ifndef NDEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_run_);
|
||||
cv_.wait(lk, [this]() { return running_ == false; });
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,699 +0,0 @@
|
||||
#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 rippled):
|
||||
*
|
||||
* 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 rippled because all yield() sites are shallow.
|
||||
*/
|
||||
template <>
|
||||
class CoroTask<void>
|
||||
{
|
||||
public:
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
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:
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
private:
|
||||
// Exclusively-owned coroutine handle. Null after move or default
|
||||
// construction. Destroyed in the destructor.
|
||||
Handle handle_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,378 +0,0 @@
|
||||
#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)
|
||||
* - mutex_run_ : guards runCount_ counter; used by join() to wait until
|
||||
* all in-flight resume operations complete.
|
||||
* - jq_.m_mutex: guards nSuspend_ increments/decrements.
|
||||
*
|
||||
* Common Mistakes When Modifying This File
|
||||
* =========================================
|
||||
*
|
||||
* 1. Changing lock ordering.
|
||||
* resume() acquires locks sequentially (never held simultaneously):
|
||||
* jq_.m_mutex (released immediately), then mutex_ (held across resume),
|
||||
* then mutex_run_ (released after decrement). post() acquires only
|
||||
* mutex_run_. 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(
|
||||
create_t,
|
||||
JobQueue& jq,
|
||||
JobType type,
|
||||
std::string const& name)
|
||||
: jq_(jq), type_(type), name_(name), runCount_(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mutex_run_, establishing a happens-before
|
||||
* edge: finished_ = true -> unlock(mutex_run_) in resume() ->
|
||||
* lock(mutex_run_) in join() -> read finished_.
|
||||
*/
|
||||
inline JobQueue::CoroTaskRunner::~CoroTaskRunner()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
join();
|
||||
XRPL_ASSERT(
|
||||
finished_.load(std::memory_order_acquire),
|
||||
"xrpl::JobQueue::CoroTaskRunner::~CoroTaskRunner : is finished");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the JobQueue's suspended-coroutine count (nSuspend_).
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onSuspend()
|
||||
{
|
||||
std::lock_guard lock(jq_.m_mutex);
|
||||
++jq_.nSuspend_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement nSuspend_ without resuming.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onUndoSuspend()
|
||||
{
|
||||
std::lock_guard lock(jq_.m_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().
|
||||
*/
|
||||
struct SuspendAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_; // The runner that owns this coroutine.
|
||||
|
||||
/**
|
||||
* Always returns false so the coroutine suspends.
|
||||
*/
|
||||
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
|
||||
{
|
||||
}
|
||||
};
|
||||
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 is resumed immediately via h.resume().
|
||||
*
|
||||
* @return An inline YieldPostAwaiter
|
||||
*/
|
||||
inline auto
|
||||
JobQueue::CoroTaskRunner::yieldAndPost()
|
||||
{
|
||||
struct YieldPostAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_;
|
||||
|
||||
bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
runner_.onSuspend();
|
||||
if (!runner_.post())
|
||||
{
|
||||
runner_.onUndoSuspend();
|
||||
h.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
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::lock_guard lk(mutex_run_);
|
||||
++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::lock_guard lk(mutex_run_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the coroutine on the current thread.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Decrement nSuspend_ (under jq_.m_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::lock_guard lock(jq_.m_mutex);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
std::lock_guard lock(mutex_);
|
||||
XRPL_ASSERT(
|
||||
task_.handle() && !task_.done(),
|
||||
"xrpl::JobQueue::CoroTaskRunner::resume : task handle is valid and not done");
|
||||
task_.handle().resume();
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
if (task_.done())
|
||||
{
|
||||
finished_.store(true, std::memory_order_release);
|
||||
// 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::lock_guard lk(mutex_run_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has not yet run to completion.
|
||||
*
|
||||
* Uses the atomic finished_ flag instead of reading task_ directly,
|
||||
* because task_ is modified in resume() under mutex_ and reading it
|
||||
* here without a lock would be a data race visible to TSAN.
|
||||
*/
|
||||
inline bool
|
||||
JobQueue::CoroTaskRunner::runnable() const
|
||||
{
|
||||
return !finished_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_.load(std::memory_order_acquire))
|
||||
{
|
||||
std::lock_guard lock(jq_.m_mutex);
|
||||
--jq_.nSuspend_;
|
||||
finished_.store(true, std::memory_order_release);
|
||||
}
|
||||
// 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_ + mutex_run_ 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.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_run_);
|
||||
cv_.wait(lk, [this]() { return runCount_ == 0 || finished_.load(std::memory_order_acquire); });
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
#include <xrpl/core/ClosureCounter.h>
|
||||
#include <xrpl/core/CoroTask.h>
|
||||
#include <xrpl/core/JobTypeData.h>
|
||||
#include <xrpl/core/JobTypes.h>
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <coroutine>
|
||||
#include <boost/context/protected_fixedsize_stack.hpp>
|
||||
#include <boost/coroutine2/all.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -18,6 +19,10 @@ class PerfLog;
|
||||
}
|
||||
|
||||
class Logs;
|
||||
struct Coro_create_t
|
||||
{
|
||||
explicit Coro_create_t() = default;
|
||||
};
|
||||
|
||||
/** A pool of threads to perform work.
|
||||
|
||||
@@ -32,418 +37,85 @@ class Logs;
|
||||
class JobQueue : private Workers::Callback
|
||||
{
|
||||
public:
|
||||
/** C++20 coroutine lifecycle manager.
|
||||
*
|
||||
* 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) |
|
||||
* | - mutex_run_ : 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)
|
||||
* |
|
||||
* +-- check stopping_ (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>
|
||||
/** Coroutines must run to completion. */
|
||||
class Coro : public std::enable_shared_from_this<Coro>
|
||||
{
|
||||
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_.m_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 mutex_run_. 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_;
|
||||
|
||||
// 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).
|
||||
bool running_;
|
||||
std::mutex mutex_;
|
||||
|
||||
// Guards runCount_. Used with cv_ for join() to wait
|
||||
// until all pending/active resume operations complete.
|
||||
std::mutex mutex_run_;
|
||||
|
||||
// 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_.
|
||||
// Atomic to allow lock-free reads from runnable(), join(), and
|
||||
// the destructor without requiring the same mutex that guards
|
||||
// the write in resume().
|
||||
std::atomic<bool> finished_{false};
|
||||
boost::coroutines2::coroutine<void>::pull_type coro_;
|
||||
boost::coroutines2::coroutine<void>::push_type* yield_;
|
||||
#ifndef NDEBUG
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Tag type for private construction. Prevents external code
|
||||
* from constructing CoroTaskRunner directly. Use postCoroTask().
|
||||
*/
|
||||
struct create_t
|
||||
{
|
||||
explicit create_t() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a CoroTaskRunner. Private by convention (create_t 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(create_t, JobQueue&, JobType, std::string const&);
|
||||
|
||||
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>)
|
||||
*/
|
||||
// Private: Used in the implementation
|
||||
template <class F>
|
||||
Coro(Coro_create_t, JobQueue&, JobType, std::string const&, F&&);
|
||||
|
||||
// Not copy-constructible or assignable
|
||||
Coro(Coro const&) = delete;
|
||||
Coro&
|
||||
operator=(Coro const&) = delete;
|
||||
|
||||
~Coro();
|
||||
|
||||
/** Suspend coroutine execution.
|
||||
Effects:
|
||||
The coroutine's stack is saved.
|
||||
The associated Job thread is released.
|
||||
Note:
|
||||
The associated Job function returns.
|
||||
Undefined behavior if called consecutively without a corresponding
|
||||
post.
|
||||
*/
|
||||
void
|
||||
init(F&& f);
|
||||
yield() const;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
/** Schedule coroutine execution.
|
||||
Effects:
|
||||
Returns immediately.
|
||||
A new job is scheduled to resume the execution of the coroutine.
|
||||
When the job runs, the coroutine's stack is restored and execution
|
||||
continues at the beginning of coroutine function or the
|
||||
statement after the previous call to yield. Undefined behavior if
|
||||
called after the coroutine has completed with a return (as opposed to
|
||||
a yield()). Undefined behavior if post() or resume() called
|
||||
consecutively without a corresponding yield.
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
@return true if the Coro's job is added to the JobQueue.
|
||||
*/
|
||||
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_.m_mutex -> mutex_ -> mutex_run_.
|
||||
*
|
||||
* @pre post() must have been called before resume(). Direct
|
||||
* calls without a prior post() will corrupt runCount_
|
||||
* and break join().
|
||||
*/
|
||||
/** Resume coroutine execution.
|
||||
Effects:
|
||||
The coroutine continues execution from where it last left off
|
||||
using this same thread.
|
||||
If the coroutine has already completed, returns immediately
|
||||
(handles the documented post-before-yield race condition).
|
||||
Undefined behavior if resume() or post() called consecutively
|
||||
without a corresponding yield.
|
||||
*/
|
||||
void
|
||||
resume();
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has not yet run to completion
|
||||
*/
|
||||
/** Returns true if the Coro is still runnable (has not returned). */
|
||||
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.
|
||||
*/
|
||||
/** Once called, the Coro allows early exit without an assert. */
|
||||
void
|
||||
expectEarlyExit();
|
||||
|
||||
/**
|
||||
* Block until all pending/active resume operations complete.
|
||||
* Uses cv_ + mutex_run_ to wait until runCount_ reaches 0.
|
||||
* Warning: deadlocks if the coroutine is suspended and never posted.
|
||||
*/
|
||||
/** Waits until coroutine returns from the user function. */
|
||||
void
|
||||
join();
|
||||
};
|
||||
@@ -481,18 +153,18 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Creates a C++20 coroutine and adds a job to the queue to run it.
|
||||
/** Creates a coroutine and adds a job to the queue which will run it.
|
||||
|
||||
@param t The type of job.
|
||||
@param name Name of the job.
|
||||
@param f Callable with signature
|
||||
CoroTask<void>(std::shared_ptr<CoroTaskRunner>).
|
||||
@param f Has a signature of void(std::shared_ptr<Coro>). Called when the
|
||||
job executes.
|
||||
|
||||
@return shared_ptr to posted CoroTaskRunner. nullptr if not successful.
|
||||
@return shared_ptr to posted Coro. nullptr if post was not successful.
|
||||
*/
|
||||
template <class F>
|
||||
std::shared_ptr<CoroTaskRunner>
|
||||
postCoroTask(JobType t, std::string const& name, F&& f);
|
||||
std::shared_ptr<Coro>
|
||||
postCoro(JobType t, std::string const& name, F&& f);
|
||||
|
||||
/** Jobs waiting at this priority.
|
||||
*/
|
||||
@@ -546,6 +218,8 @@ public:
|
||||
isStopped() const;
|
||||
|
||||
private:
|
||||
friend class Coro;
|
||||
|
||||
using JobDataMap = std::map<JobType, JobTypeData>;
|
||||
|
||||
beast::Journal m_journal;
|
||||
@@ -646,75 +320,88 @@ private:
|
||||
getJobLimit(JobType type);
|
||||
};
|
||||
|
||||
/*
|
||||
An RPC command is received and is handled via ServerHandler(HTTP) or
|
||||
Handler(websocket), depending on the connection type. The handler then calls
|
||||
the JobQueue::postCoro() method to create a coroutine and run it at a later
|
||||
point. This frees up the handler thread and allows it to continue handling
|
||||
other requests while the RPC command completes its work asynchronously.
|
||||
|
||||
postCoro() creates a Coro object. When the Coro ctor is called, and its
|
||||
coro_ member is initialized (a boost::coroutines::pull_type), execution
|
||||
automatically passes to the coroutine, which we don't want at this point,
|
||||
since we are still in the handler thread context. It's important to note
|
||||
here that construction of a boost pull_type automatically passes execution to
|
||||
the coroutine. A pull_type object automatically generates a push_type that is
|
||||
passed as a parameter (do_yield) in the signature of the function the
|
||||
pull_type was created with. This function is immediately called during coro_
|
||||
construction and within it, Coro::yield_ is assigned the push_type
|
||||
parameter (do_yield) address and called (yield()) so we can return execution
|
||||
back to the caller's stack.
|
||||
|
||||
postCoro() then calls Coro::post(), which schedules a job on the job
|
||||
queue to continue execution of the coroutine in a JobQueue worker thread at
|
||||
some later time. When the job runs, we lock on the Coro::mutex_ and call
|
||||
coro_ which continues where we had left off. Since we the last thing we did
|
||||
in coro_ was call yield(), the next thing we continue with is calling the
|
||||
function param f, that was passed into Coro ctor. It is within this
|
||||
function body that the caller specifies what he would like to do while
|
||||
running in the coroutine and allow them to suspend and resume execution.
|
||||
A task that relies on other events to complete, such as path finding, calls
|
||||
Coro::yield() to suspend its execution while waiting on those events to
|
||||
complete and continue when signaled via the Coro::post() method.
|
||||
|
||||
There is a potential race condition that exists here where post() can get
|
||||
called before yield() after f is called. Technically the problem only occurs
|
||||
if the job that post() scheduled is executed before yield() is called.
|
||||
If the post() job were to be executed before yield(), undefined behavior
|
||||
would occur. The lock ensures that coro_ is not called again until we exit
|
||||
the coroutine. At which point a scheduled resume() job waiting on the lock
|
||||
would gain entry. resume() checks if the coroutine has already completed
|
||||
(coro_ converts to false) and, if so, skips invoking operator() since
|
||||
calling operator() on a completed boost::coroutine2 pull_type is undefined
|
||||
behavior.
|
||||
|
||||
The race condition occurs as follows:
|
||||
|
||||
1- The coroutine is running.
|
||||
2- The coroutine is about to suspend, but before it can do so, it must
|
||||
arrange for some event to wake it up.
|
||||
3- The coroutine arranges for some event to wake it up.
|
||||
4- Before the coroutine can suspend, that event occurs and the
|
||||
resumption of the coroutine is scheduled on the job queue. 5- Again, before
|
||||
the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
|
||||
Again, before the coroutine can suspend, the resumption code runs the
|
||||
coroutine.
|
||||
The coroutine is now running in two threads.
|
||||
|
||||
The lock prevents this from happening as step 6 will block until the
|
||||
lock is released which only happens after the coroutine completes.
|
||||
*/
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
#include <xrpl/core/CoroTaskRunner.ipp>
|
||||
#include <xrpl/core/Coro.ipp>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// postCoroTask — entry point for launching a C++20 coroutine on the JobQueue.
|
||||
//
|
||||
// Control Flow
|
||||
// ============
|
||||
//
|
||||
// postCoroTask(t, name, f)
|
||||
// |
|
||||
// +-- 1. Check stopping_ — reject if JQ shutting down
|
||||
// |
|
||||
// +-- 2. ++nSuspend_ (the coroutine uses lazy-start, so it is
|
||||
// | "suspended" from the JQ's perspective before its first resume.
|
||||
// | 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
|
||||
//
|
||||
// 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)
|
||||
std::shared_ptr<JobQueue::Coro>
|
||||
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
||||
{
|
||||
// Reject if the JQ is shutting down and atomically increment
|
||||
// nSuspend_ under the same lock. Without the lock, a TOCTOU race
|
||||
// exists: stopping_ could become true between the check and the
|
||||
// increment, leaving an orphan nSuspend_ that causes stop() to hang.
|
||||
/* First param is a detail type to make construction private.
|
||||
Last param is the function the coroutine runs. Signature of
|
||||
void(std::shared_ptr<Coro>).
|
||||
*/
|
||||
auto coro = std::make_shared<Coro>(Coro_create_t{}, *this, t, name, std::forward<F>(f));
|
||||
if (!coro->post())
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (stopping_)
|
||||
return nullptr;
|
||||
++nSuspend_;
|
||||
// The Coro was not successfully posted. Disable it so it's destructor
|
||||
// can run with no negative side effects. Then destroy it.
|
||||
coro->expectEarlyExit();
|
||||
coro.reset();
|
||||
}
|
||||
|
||||
auto runner = std::make_shared<CoroTaskRunner>(CoroTaskRunner::create_t{}, *this, t, name);
|
||||
runner->init(std::forward<F>(f));
|
||||
if (!runner->post())
|
||||
{
|
||||
runner->expectEarlyExit();
|
||||
runner.reset();
|
||||
}
|
||||
return runner;
|
||||
return coro;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#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.
|
||||
*/
|
||||
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
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/ledger/CachedSLEs.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
@@ -23,6 +22,20 @@ class PerfLog;
|
||||
// This is temporary until we migrate all code to use ServiceRegistry.
|
||||
class Application;
|
||||
|
||||
template <
|
||||
class Key,
|
||||
class T,
|
||||
bool IsKeyCache,
|
||||
class SharedWeakUnionPointer,
|
||||
class SharedPointerType,
|
||||
class Hash,
|
||||
class KeyEqual,
|
||||
class Mutex>
|
||||
class TaggedCache;
|
||||
class STLedgerEntry;
|
||||
using SLE = STLedgerEntry;
|
||||
using CachedSLEs = TaggedCache<uint256, SLE const>;
|
||||
|
||||
// Forward declarations
|
||||
class AcceptedLedger;
|
||||
class AmendmentTable;
|
||||
@@ -45,7 +58,7 @@ class NetworkIDService;
|
||||
class OpenLedger;
|
||||
class OrderBookDB;
|
||||
class Overlay;
|
||||
class PathRequests;
|
||||
class PathRequestManager;
|
||||
class PeerReservationTable;
|
||||
class PendingSaves;
|
||||
class RelationalDatabase;
|
||||
@@ -89,7 +102,7 @@ public:
|
||||
getNodeFamily() = 0;
|
||||
|
||||
virtual TimeKeeper&
|
||||
timeKeeper() = 0;
|
||||
getTimeKeeper() = 0;
|
||||
|
||||
virtual JobQueue&
|
||||
getJobQueue() = 0;
|
||||
@@ -98,7 +111,7 @@ public:
|
||||
getTempNodeCache() = 0;
|
||||
|
||||
virtual CachedSLEs&
|
||||
cachedSLEs() = 0;
|
||||
getCachedSLEs() = 0;
|
||||
|
||||
virtual NetworkIDService&
|
||||
getNetworkIDService() = 0;
|
||||
@@ -120,26 +133,26 @@ public:
|
||||
getValidations() = 0;
|
||||
|
||||
virtual ValidatorList&
|
||||
validators() = 0;
|
||||
getValidators() = 0;
|
||||
|
||||
virtual ValidatorSite&
|
||||
validatorSites() = 0;
|
||||
getValidatorSites() = 0;
|
||||
|
||||
virtual ManifestCache&
|
||||
validatorManifests() = 0;
|
||||
getValidatorManifests() = 0;
|
||||
|
||||
virtual ManifestCache&
|
||||
publisherManifests() = 0;
|
||||
getPublisherManifests() = 0;
|
||||
|
||||
// Network services
|
||||
virtual Overlay&
|
||||
overlay() = 0;
|
||||
getOverlay() = 0;
|
||||
|
||||
virtual Cluster&
|
||||
cluster() = 0;
|
||||
getCluster() = 0;
|
||||
|
||||
virtual PeerReservationTable&
|
||||
peerReservations() = 0;
|
||||
getPeerReservations() = 0;
|
||||
|
||||
virtual Resource::Manager&
|
||||
getResourceManager() = 0;
|
||||
@@ -174,13 +187,13 @@ public:
|
||||
getLedgerReplayer() = 0;
|
||||
|
||||
virtual PendingSaves&
|
||||
pendingSaves() = 0;
|
||||
getPendingSaves() = 0;
|
||||
|
||||
virtual OpenLedger&
|
||||
openLedger() = 0;
|
||||
getOpenLedger() = 0;
|
||||
|
||||
virtual OpenLedger const&
|
||||
openLedger() const = 0;
|
||||
getOpenLedger() const = 0;
|
||||
|
||||
// Transaction and operation services
|
||||
virtual NetworkOPs&
|
||||
@@ -195,8 +208,8 @@ public:
|
||||
virtual TxQ&
|
||||
getTxQ() = 0;
|
||||
|
||||
virtual PathRequests&
|
||||
getPathRequests() = 0;
|
||||
virtual PathRequestManager&
|
||||
getPathRequestManager() = 0;
|
||||
|
||||
// Server services
|
||||
virtual ServerHandler&
|
||||
@@ -210,16 +223,16 @@ public:
|
||||
isStopping() const = 0;
|
||||
|
||||
virtual beast::Journal
|
||||
journal(std::string const& name) = 0;
|
||||
getJournal(std::string const& name) = 0;
|
||||
|
||||
virtual boost::asio::io_context&
|
||||
getIOContext() = 0;
|
||||
|
||||
virtual Logs&
|
||||
logs() = 0;
|
||||
getLogs() = 0;
|
||||
|
||||
virtual std::optional<uint256> const&
|
||||
trapTxID() const = 0;
|
||||
getTrapTxID() const = 0;
|
||||
|
||||
/** Retrieve the "wallet database" */
|
||||
virtual DatabaseCon&
|
||||
@@ -228,7 +241,7 @@ public:
|
||||
// Temporary: Get the underlying Application for functions that haven't
|
||||
// been migrated yet. This should be removed once all code is migrated.
|
||||
virtual Application&
|
||||
app() = 0;
|
||||
getApp() = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
enum class StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK };
|
||||
enum class StartUpType { Fresh, Normal, Load, LoadFile, Replay, Network };
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, StartUpType const& type)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/core/TimeKeeper.h>
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/CachedView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
@@ -15,7 +14,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class Application;
|
||||
class ServiceRegistry;
|
||||
class Job;
|
||||
class TransactionMaster;
|
||||
|
||||
@@ -83,21 +82,26 @@ public:
|
||||
*/
|
||||
Ledger(
|
||||
create_genesis_t,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
std::vector<uint256> const& amendments,
|
||||
Family& family);
|
||||
|
||||
Ledger(LedgerHeader const& info, Config const& config, Family& family);
|
||||
Ledger(LedgerHeader const& info, Rules const& rules, Family& family);
|
||||
|
||||
/** Used for ledgers loaded from JSON files
|
||||
|
||||
@param acquire If true, acquires the ledger if not found locally
|
||||
|
||||
@note The fees parameter provides default values, but setup() may
|
||||
override them from the ledger state if fee-related SLEs exist.
|
||||
*/
|
||||
Ledger(
|
||||
LedgerHeader const& info,
|
||||
bool& loaded,
|
||||
bool acquire,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
Family& family,
|
||||
beast::Journal j);
|
||||
|
||||
@@ -113,7 +117,8 @@ public:
|
||||
Ledger(
|
||||
std::uint32_t ledgerSeq,
|
||||
NetClock::time_point closeTime,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
Family& family);
|
||||
|
||||
~Ledger() = default;
|
||||
@@ -322,7 +327,7 @@ public:
|
||||
walkLedger(beast::Journal j, bool parallel = false) const;
|
||||
|
||||
bool
|
||||
assertSensible(beast::Journal ledgerJ) const;
|
||||
isSensible() const;
|
||||
|
||||
void
|
||||
invariants() const;
|
||||
@@ -379,8 +384,26 @@ private:
|
||||
bool
|
||||
setup();
|
||||
|
||||
void
|
||||
defaultFees(Config const& config);
|
||||
/** @brief Deserialize a SHAMapItem containing a single STTx.
|
||||
*
|
||||
* @param item The SHAMapItem to deserialize.
|
||||
* @return A shared pointer to the deserialized transaction.
|
||||
* @throw May throw on deserialization error.
|
||||
*/
|
||||
static std::shared_ptr<STTx const>
|
||||
deserializeTx(SHAMapItem const& item);
|
||||
|
||||
/** @brief Deserialize a SHAMapItem containing STTx + STObject metadata.
|
||||
*
|
||||
* The SHAMapItem must contain two variable length serialization objects.
|
||||
*
|
||||
* @param item The SHAMapItem to deserialize.
|
||||
* @return A pair containing shared pointers to the deserialized transaction
|
||||
* and metadata.
|
||||
* @throw May throw on deserialization error.
|
||||
*/
|
||||
static std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
|
||||
deserializeTxPlusMeta(SHAMapItem const& item);
|
||||
|
||||
bool mImmutable;
|
||||
|
||||
@@ -402,54 +425,4 @@ private:
|
||||
/** A ledger wrapped in a CachedView. */
|
||||
using CachedLedger = CachedView<Ledger>;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// API
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
extern bool
|
||||
pendSaveValidated(
|
||||
Application& app,
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
bool isSynchronous,
|
||||
bool isCurrent);
|
||||
|
||||
std::shared_ptr<Ledger>
|
||||
loadLedgerHelper(LedgerHeader const& sinfo, Application& app, bool acquire);
|
||||
|
||||
std::shared_ptr<Ledger>
|
||||
loadByIndex(std::uint32_t ledgerIndex, Application& app, bool acquire = true);
|
||||
|
||||
std::shared_ptr<Ledger>
|
||||
loadByHash(uint256 const& ledgerHash, Application& app, bool acquire = true);
|
||||
|
||||
// Fetch the ledger with the highest sequence contained in the database
|
||||
extern std::tuple<std::shared_ptr<Ledger>, std::uint32_t, uint256>
|
||||
getLatestLedger(Application& app);
|
||||
|
||||
/** Deserialize a SHAMapItem containing a single STTx
|
||||
|
||||
Throw:
|
||||
|
||||
May throw on deserializaton error
|
||||
*/
|
||||
std::shared_ptr<STTx const>
|
||||
deserializeTx(SHAMapItem const& item);
|
||||
|
||||
/** Deserialize a SHAMapItem containing STTx + STObject metadata
|
||||
|
||||
The SHAMap must contain two variable length
|
||||
serialization objects.
|
||||
|
||||
Throw:
|
||||
|
||||
May throw on deserializaton error
|
||||
*/
|
||||
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
|
||||
deserializeTxPlusMeta(SHAMapItem const& item);
|
||||
|
||||
uint256
|
||||
calculateLedgerHash(LedgerHeader const& info);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -76,16 +76,33 @@ public:
|
||||
@return true if a book from this issue to XRP exists
|
||||
*/
|
||||
virtual bool
|
||||
isBookToXRP(Issue const& issue, std::optional<Domain> domain = std::nullopt) = 0;
|
||||
isBookToXRP(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
|
||||
|
||||
/**
|
||||
* Process a transaction for order book tracking.
|
||||
* @param ledger The ledger the transaction was applied to
|
||||
* @param alTx The transaction to process
|
||||
* @param jvObj The JSON object of the transaction
|
||||
*/
|
||||
virtual void
|
||||
processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
AcceptedLedgerTx const& alTx,
|
||||
MultiApiJson const& jvObj) = 0;
|
||||
|
||||
/**
|
||||
* Get the book listeners for a book.
|
||||
* @param book The book to get the listeners for
|
||||
* @return The book listeners for the book
|
||||
*/
|
||||
virtual BookListeners::pointer
|
||||
getBookListeners(Book const&) = 0;
|
||||
|
||||
/**
|
||||
* Create a new book listeners for a book.
|
||||
* @param book The book to create the listeners for
|
||||
* @return The new book listeners for the book
|
||||
*/
|
||||
virtual BookListeners::pointer
|
||||
makeBookListeners(Book const&) = 0;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Deprecated constant for backwards compatibility with pre-XRPFees amendment.
|
||||
// This was the reference fee units used in the old fee calculation.
|
||||
inline constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10;
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -11,15 +15,25 @@ namespace xrpl {
|
||||
*/
|
||||
struct Fees
|
||||
{
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
/** @brief Cost of a reference transaction in drops. */
|
||||
XRPAmount base{0};
|
||||
|
||||
/** @brief Minimum XRP an account must hold to exist on the ledger. */
|
||||
XRPAmount reserve{0};
|
||||
|
||||
/** @brief Additional XRP reserve required per owned ledger object. */
|
||||
XRPAmount increment{0};
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
Fees&
|
||||
operator=(Fees const&) = default;
|
||||
|
||||
Fees(XRPAmount base_, XRPAmount reserve_, XRPAmount increment_)
|
||||
: base(base_), reserve(reserve_), increment(increment_)
|
||||
{
|
||||
}
|
||||
|
||||
/** Returns the account reserve given the owner count, in drops.
|
||||
|
||||
The reserve is calculated as the reserve base plus
|
||||
|
||||
@@ -72,4 +72,8 @@ deserializeHeader(Slice data, bool hasHash = false);
|
||||
LedgerHeader
|
||||
deserializePrefixedHeader(Slice data, bool hasHash = false);
|
||||
|
||||
/** Calculate the hash of a ledger header. */
|
||||
uint256
|
||||
calculateLedgerHash(LedgerHeader const& info);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -69,9 +69,6 @@ public:
|
||||
std::vector<uint256>::iterator
|
||||
insert(std::vector<uint256>::const_iterator pos, uint256 const& value);
|
||||
|
||||
std::vector<uint256>::iterator
|
||||
insert(std::vector<uint256>::const_iterator pos, uint256&& value);
|
||||
|
||||
void
|
||||
push_back(uint256 const& v);
|
||||
|
||||
@@ -184,12 +181,6 @@ STVector256::insert(std::vector<uint256>::const_iterator pos, uint256 const& val
|
||||
return mValue.insert(pos, value);
|
||||
}
|
||||
|
||||
inline std::vector<uint256>::iterator
|
||||
STVector256::insert(std::vector<uint256>::const_iterator pos, uint256&& value)
|
||||
{
|
||||
return mValue.insert(pos, std::move(value));
|
||||
}
|
||||
|
||||
inline void
|
||||
STVector256::push_back(uint256 const& v)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
enum class TxSearched { all, some, unknown };
|
||||
enum class TxSearched { All, Some, Unknown };
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -6,15 +6,15 @@ This directory contains auto-generated C++ wrapper classes for XRP Ledger protoc
|
||||
|
||||
The files in this directory are automatically generated at **CMake configure time** from macro definition files:
|
||||
|
||||
- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `scripts/generate_tx_classes.py`
|
||||
- **Ledger entry classes** (in `ledger_entries/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `scripts/generate_ledger_classes.py`
|
||||
- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `scripts/codegen/generate_tx_classes.py`
|
||||
- **Ledger entry classes** (in `ledger_entries/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `scripts/codegen/generate_ledger_classes.py`
|
||||
|
||||
## Generation Process
|
||||
|
||||
The generation happens automatically when you **configure** the project (not during build). When you run CMake, the system:
|
||||
|
||||
1. Creates a Python virtual environment in the build directory (`codegen_venv`)
|
||||
2. Installs Python dependencies from `scripts/requirements.txt` into the venv (only if needed)
|
||||
2. Installs Python dependencies from `scripts/codegen/requirements.txt` into the venv (only if needed)
|
||||
3. Runs the Python generation scripts using the venv Python interpreter
|
||||
4. Parses the macro files to extract type definitions
|
||||
5. Generates type-safe C++ wrapper classes using Mako templates
|
||||
@@ -26,7 +26,7 @@ The code is regenerated when:
|
||||
|
||||
- You run CMake configure for the first time
|
||||
- The Python virtual environment doesn't exist
|
||||
- `scripts/requirements.txt` has been modified
|
||||
- `scripts/codegen/requirements.txt` has been modified
|
||||
|
||||
To force regeneration, delete the build directory and reconfigure.
|
||||
|
||||
@@ -55,9 +55,9 @@ The generated `.h` files **are checked into version control**. This means:
|
||||
To modify the generated classes:
|
||||
|
||||
- Edit the macro files in `include/xrpl/protocol/detail/`
|
||||
- Edit the Mako templates in `scripts/templates/`
|
||||
- Edit the generation scripts in `scripts/`
|
||||
- Update Python dependencies in `scripts/requirements.txt`
|
||||
- Edit the Mako templates in `scripts/codegen/templates/`
|
||||
- Edit the generation scripts in `scripts/codegen/`
|
||||
- Update Python dependencies in `scripts/codegen/requirements.txt`
|
||||
- Run CMake configure to regenerate
|
||||
|
||||
## Adding Common Fields
|
||||
@@ -73,7 +73,7 @@ Base classes:
|
||||
|
||||
Templates (update to pass required common fields to base class constructors):
|
||||
|
||||
- `scripts/templates/Transaction.h.mako`
|
||||
- `scripts/templates/LedgerEntry.h.mako`
|
||||
- `scripts/codegen/templates/Transaction.h.mako`
|
||||
- `scripts/codegen/templates/LedgerEntry.h.mako`
|
||||
|
||||
These files are **not auto-generated** and must be updated by hand.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/core/PerfLog.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/core/StartUpType.h>
|
||||
#include <xrpl/rdb/DBInit.h>
|
||||
#include <xrpl/rdb/SociDB.h>
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
{
|
||||
explicit Setup() = default;
|
||||
|
||||
StartUpType startUp = StartUpType::NORMAL;
|
||||
StartUpType startUp = StartUpType::Normal;
|
||||
bool standAlone = false;
|
||||
boost::filesystem::path dataDir;
|
||||
// Indicates whether or not to return the `globalPragma`
|
||||
@@ -94,7 +95,7 @@ public:
|
||||
struct CheckpointerSetup
|
||||
{
|
||||
JobQueue* jobQueue;
|
||||
Logs* logs;
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
};
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
@@ -106,9 +107,8 @@ public:
|
||||
beast::Journal journal)
|
||||
// Use temporary files or regular DB files?
|
||||
: DatabaseCon(
|
||||
setup.standAlone && setup.startUp != StartUpType::LOAD &&
|
||||
setup.startUp != StartUpType::LOAD_FILE &&
|
||||
setup.startUp != StartUpType::REPLAY
|
||||
setup.standAlone && setup.startUp != StartUpType::Load &&
|
||||
setup.startUp != StartUpType::LoadFile && setup.startUp != StartUpType::Replay
|
||||
? ""
|
||||
: (setup.dataDir / dbName),
|
||||
setup.commonPragma(),
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
beast::Journal journal)
|
||||
: DatabaseCon(setup, dbName, pragma, initSQL, journal)
|
||||
{
|
||||
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
|
||||
setupCheckpointing(checkpointerSetup.jobQueue, checkpointerSetup.registry.get());
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
@@ -154,7 +154,7 @@ public:
|
||||
beast::Journal journal)
|
||||
: DatabaseCon(dataDir, dbName, pragma, initSQL, journal)
|
||||
{
|
||||
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
|
||||
setupCheckpointing(checkpointerSetup.jobQueue, checkpointerSetup.registry.get());
|
||||
}
|
||||
|
||||
~DatabaseCon();
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
|
||||
private:
|
||||
void
|
||||
setupCheckpointing(JobQueue*, Logs&);
|
||||
setupCheckpointing(JobQueue*, ServiceRegistry&);
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
DatabaseCon(
|
||||
|
||||
@@ -49,8 +49,9 @@ public:
|
||||
struct AccountTxOptions
|
||||
{
|
||||
AccountID const& account;
|
||||
std::uint32_t minLedger;
|
||||
std::uint32_t maxLedger;
|
||||
/// Ledger sequence range to search. A value of 0 for min or max
|
||||
/// means unbounded in that direction (no constraint applied).
|
||||
LedgerRange ledgerRange;
|
||||
std::uint32_t offset;
|
||||
std::uint32_t limit;
|
||||
bool bUnlimited;
|
||||
@@ -59,8 +60,7 @@ public:
|
||||
struct AccountTxPageOptions
|
||||
{
|
||||
AccountID const& account;
|
||||
std::uint32_t minLedger;
|
||||
std::uint32_t maxLedger;
|
||||
LedgerRange ledgerRange;
|
||||
std::optional<AccountTxMarker> marker;
|
||||
std::uint32_t limit;
|
||||
bool bAdmin;
|
||||
@@ -247,7 +247,7 @@ public:
|
||||
* @return Struct CountMinMax which contains the minimum sequence,
|
||||
* maximum sequence and number of ledgers.
|
||||
*/
|
||||
virtual struct CountMinMax
|
||||
virtual CountMinMax
|
||||
getLedgerCountMinMax() = 0;
|
||||
|
||||
/**
|
||||
@@ -405,10 +405,10 @@ public:
|
||||
* @param id Hash of the transaction.
|
||||
* @param range Range of ledgers to check, if present.
|
||||
* @param ec Default error code value.
|
||||
* @return Transaction and its metadata if found, otherwise TxSearched::all
|
||||
* @return Transaction and its metadata if found, otherwise TxSearched::All
|
||||
* if a range is provided and all ledgers from the range are present
|
||||
* in the database, TxSearched::some if a range is provided and not
|
||||
* all ledgers are present, TxSearched::unknown if the range is not
|
||||
* in the database, TxSearched::Some if a range is provided and not
|
||||
* all ledgers are present, TxSearched::Unknown if the range is not
|
||||
* provided or a deserializing error occurred. In the last case the
|
||||
* error code is returned via the ec parameter, in other cases the
|
||||
* default error code is not changed.
|
||||
@@ -455,9 +455,10 @@ public:
|
||||
closeTransactionDB() = 0;
|
||||
};
|
||||
|
||||
template <class T, class C>
|
||||
template <typename T, typename C>
|
||||
T
|
||||
rangeCheckedCast(C c)
|
||||
requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<C> && std::convertible_to<C, T>)
|
||||
{
|
||||
if ((c > std::numeric_limits<T>::max()) || (!std::numeric_limits<T>::is_signed && c < 0) ||
|
||||
(std::numeric_limits<T>::is_signed && std::numeric_limits<C>::is_signed &&
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
#endif
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
|
||||
#define SOCI_USE_BOOST
|
||||
#include <soci/soci.h>
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
and so must outlive them both.
|
||||
*/
|
||||
std::shared_ptr<Checkpointer>
|
||||
makeCheckpointer(std::uintptr_t id, std::weak_ptr<soci::session>, JobQueue&, Logs&);
|
||||
makeCheckpointer(std::uintptr_t id, std::weak_ptr<soci::session>, JobQueue&, ServiceRegistry&);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
XRPL_ASSERT((flags & tapBATCH) == 0, "Batch apply flag should not be set");
|
||||
}
|
||||
|
||||
ServiceRegistry& registry;
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
STTx const& tx;
|
||||
TER const preclaimResult;
|
||||
XRPAmount const baseFee;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace xrpl {
|
||||
struct PreflightContext
|
||||
{
|
||||
public:
|
||||
ServiceRegistry& registry;
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
STTx const& tx;
|
||||
Rules const rules;
|
||||
ApplyFlags flags;
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
struct PreclaimContext
|
||||
{
|
||||
public:
|
||||
ServiceRegistry& registry;
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
ReadView const& view;
|
||||
TER preflightResult;
|
||||
ApplyFlags flags;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanInvariant.h>
|
||||
#include <xrpl/tx/invariants/MPTInvariant.h>
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
|
||||
55
include/xrpl/tx/invariants/LoanBrokerInvariant.h
Normal file
55
include/xrpl/tx/invariants/LoanBrokerInvariant.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loan brokers are internally consistent
|
||||
*
|
||||
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
|
||||
* node (the root), which will only hold entries for `RippleState` or
|
||||
* `MPToken` objects.
|
||||
*
|
||||
*/
|
||||
class ValidLoanBroker
|
||||
{
|
||||
// Not all of these elements will necessarily be populated. Remaining items
|
||||
// will be looked up as needed.
|
||||
struct BrokerInfo
|
||||
{
|
||||
SLE::const_pointer brokerBefore = nullptr;
|
||||
// After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
SLE::const_pointer brokerAfter = nullptr;
|
||||
};
|
||||
// Collect all the LoanBrokers found directly or indirectly through
|
||||
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
|
||||
// LoanBroker object if brokerBefore and brokerAfter are nullptr
|
||||
std::map<uint256, BrokerInfo> brokers_;
|
||||
// Collect all the modified trust lines. Their high and low accounts will be
|
||||
// loaded to look for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> lines_;
|
||||
// Collect all the modified MPTokens. Their accounts will be loaded to look
|
||||
// for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> mpts_;
|
||||
|
||||
static bool
|
||||
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,57 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loan brokers are internally consistent
|
||||
*
|
||||
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
|
||||
* node (the root), which will only hold entries for `RippleState` or
|
||||
* `MPToken` objects.
|
||||
*
|
||||
*/
|
||||
class ValidLoanBroker
|
||||
{
|
||||
// Not all of these elements will necessarily be populated. Remaining items
|
||||
// will be looked up as needed.
|
||||
struct BrokerInfo
|
||||
{
|
||||
SLE::const_pointer brokerBefore = nullptr;
|
||||
// After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
SLE::const_pointer brokerAfter = nullptr;
|
||||
};
|
||||
// Collect all the LoanBrokers found directly or indirectly through
|
||||
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
|
||||
// LoanBroker object if brokerBefore and brokerAfter are nullptr
|
||||
std::map<uint256, BrokerInfo> brokers_;
|
||||
// Collect all the modified trust lines. Their high and low accounts will be
|
||||
// loaded to look for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> lines_;
|
||||
// Collect all the modified MPTokens. Their accounts will be loaded to look
|
||||
// for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> mpts_;
|
||||
|
||||
static bool
|
||||
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loans are internally consistent
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
STPathSet const& spsPaths,
|
||||
|
||||
std::optional<uint256> const& domainID,
|
||||
Logs& l,
|
||||
ServiceRegistry& registry,
|
||||
Input const* const pInputs = nullptr);
|
||||
|
||||
// The view we are currently working on
|
||||
|
||||
@@ -203,7 +203,7 @@ getAMMOfferStartWithTakerGets(
|
||||
|
||||
// Try to reduce the offer size to improve the quality.
|
||||
// The quality might still not match the targetQuality for a tiny offer.
|
||||
if (auto const amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
|
||||
if (auto amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
|
||||
return getAmounts(detail::reduceOffer(amounts.out));
|
||||
else
|
||||
return amounts;
|
||||
@@ -270,7 +270,7 @@ getAMMOfferStartWithTakerPays(
|
||||
|
||||
// Try to reduce the offer size to improve the quality.
|
||||
// The quality might still not match the targetQuality for a tiny offer.
|
||||
if (auto const amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
|
||||
if (auto amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
|
||||
return getAmounts(detail::reduceOffer(amounts.in));
|
||||
else
|
||||
return amounts;
|
||||
@@ -335,8 +335,7 @@ changeSpotPriceQuality(
|
||||
}
|
||||
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
|
||||
// should not fail
|
||||
if (auto const amounts =
|
||||
TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
|
||||
if (auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
|
||||
Quality{amounts} < quality &&
|
||||
!withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
|
||||
{
|
||||
@@ -362,7 +361,7 @@ changeSpotPriceQuality(
|
||||
|
||||
// Generate the offer starting with XRP side. Return seated offer amounts
|
||||
// if the offer can be generated, otherwise nullopt.
|
||||
auto const amounts = [&]() {
|
||||
auto amounts = [&]() {
|
||||
if (isXRP(getIssue(pool.out)))
|
||||
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
|
||||
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
|
||||
|
||||
@@ -1,79 +1,102 @@
|
||||
# TSAN suppression file for rippled.
|
||||
# Only suppress issues in third-party libraries and TSAN's own instrumentation.
|
||||
# Races in rippled's own code should be fixed, not suppressed.
|
||||
# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions.
|
||||
|
||||
# Boost ASIO / Boost Context false positives
|
||||
# These are internal to Boost's reactor, fiber context switching, and memory management.
|
||||
# Suppress race in Boost ASIO scheduler detected by GCC-15
|
||||
# This is a false positive in Boost's internal pipe() synchronization
|
||||
race:boost/asio/
|
||||
race:boost/context/
|
||||
race:boost/asio/executor.hpp
|
||||
race:boost::asio
|
||||
race:boost::context::fiber::resume
|
||||
race:boost::asio::detail::spawned_fiber_thread
|
||||
race:boost::asio::detail::spawned_fiber_thread::suspend_with
|
||||
race:boost::asio::detail::spawned_fiber_thread::destroy
|
||||
race:__cxxabiv1::manage_exception_state
|
||||
race:__tsan_memcpy
|
||||
|
||||
# TSAN's own syscall interceptors and libc internals
|
||||
# These are false positives from TSAN's fd tracking in glibc.
|
||||
# TODO: Tighten these suppressions once TSAN runs cleanly — they may mask
|
||||
# real fd-lifetime bugs in rippled's networking code.
|
||||
# Suppress tsan related issues in rippled code.
|
||||
race:src/libxrpl/basics/make_SSLContext.cpp
|
||||
race:src/libxrpl/basics/Number.cpp
|
||||
race:src/libxrpl/json/json_value.cpp
|
||||
race:src/libxrpl/json/to_string.cpp
|
||||
race:src/libxrpl/ledger/OpenView.cpp
|
||||
race:src/libxrpl/net/HTTPClient.cpp
|
||||
race:src/libxrpl/nodestore/backend/NuDBFactory.cpp
|
||||
race:src/libxrpl/protocol/InnerObjectFormats.cpp
|
||||
race:src/libxrpl/protocol/STParsedJSON.cpp
|
||||
race:src/libxrpl/resource/ResourceManager.cpp
|
||||
race:src/test/app/Flow_test.cpp
|
||||
race:src/test/app/LedgerReplay_test.cpp
|
||||
race:src/test/app/NFToken_test.cpp
|
||||
race:src/test/app/Offer_test.cpp
|
||||
race:src/test/app/ValidatorSite_test.cpp
|
||||
race:src/test/consensus/NegativeUNL_test.cpp
|
||||
race:src/test/jtx/impl/Env.cpp
|
||||
race:src/test/jtx/impl/JSONRPCClient.cpp
|
||||
race:src/test/jtx/impl/pay.cpp
|
||||
race:src/test/jtx/impl/token.cpp
|
||||
race:src/test/rpc/Book_test.cpp
|
||||
race:src/xrpld/app/ledger/detail/InboundTransactions.cpp
|
||||
race:src/xrpld/app/main/Application.cpp
|
||||
race:src/xrpld/app/main/BasicApp.cpp
|
||||
race:src/xrpld/app/main/GRPCServer.cpp
|
||||
race:src/xrpld/app/misc/detail/AmendmentTable.cpp
|
||||
race:src/xrpld/app/misc/FeeVoteImpl.cpp
|
||||
race:src/xrpld/app/rdb/detail/Wallet.cpp
|
||||
race:src/xrpld/overlay/detail/OverlayImpl.cpp
|
||||
race:src/xrpld/peerfinder/detail/PeerfinderManager.cpp
|
||||
race:src/xrpld/peerfinder/detail/SourceStrings.cpp
|
||||
race:src/xrpld/rpc/detail/ServerHandler.cpp
|
||||
race:xrpl/server/detail/Door.h
|
||||
race:xrpl/server/detail/Spawn.h
|
||||
race:xrpl/server/detail/ServerImpl.h
|
||||
race:xrpl/nodestore/detail/DatabaseNodeImp.h
|
||||
race:src/libxrpl/beast/utility/beast_Journal.cpp
|
||||
race:src/test/beast/LexicalCast_test.cpp
|
||||
race:ripple::ServerHandler
|
||||
|
||||
# More suppressions in external library code.
|
||||
race:crtstuff.c
|
||||
race:pipe
|
||||
race:epoll_ctl
|
||||
race:epoll_create
|
||||
race:closedir
|
||||
race:__close_nocancel
|
||||
race:__GI___close
|
||||
race:__socket
|
||||
race:__GI___accept4
|
||||
race:eventfd
|
||||
|
||||
# C++ standard library internals
|
||||
race:ostreambuf_iterator
|
||||
race:basic_ostream
|
||||
called_from_lib:libclang_rt
|
||||
# Deadlock / lock-order-inversion suppressions
|
||||
# Note: GCC's TSAN may not fully support all deadlock suppression patterns
|
||||
deadlock:src/libxrpl/beast/utility/beast_Journal.cpp
|
||||
deadlock:src/libxrpl/beast/utility/beast_PropertyStream.cpp
|
||||
deadlock:src/test/beast/beast_PropertyStream_test.cpp
|
||||
deadlock:src/xrpld/core/detail/Workers.cpp
|
||||
deadlock:src/xrpld/app/misc/detail/Manifest.cpp
|
||||
deadlock:src/xrpld/app/misc/detail/ValidatorList.cpp
|
||||
deadlock:src/xrpld/app/misc/detail/ValidatorSite.cpp
|
||||
|
||||
# Deadlock false positives in Boost and threading primitives
|
||||
signal:src/libxrpl/beast/utility/beast_Journal.cpp
|
||||
signal:src/xrpld/core/detail/Workers.cpp
|
||||
signal:src/xrpld/core/JobQueue.cpp
|
||||
signal:ripple::Workers::Worker
|
||||
|
||||
# Aggressive suppressing of deadlock tsan errors
|
||||
deadlock:pthread_create
|
||||
deadlock:pthread_rwlock_rdlock
|
||||
deadlock:boost::asio
|
||||
|
||||
# Pre-existing rippled issues (suppress until fixed, tracked separately)
|
||||
# TODO(RIPD-XXXX): beast::Journal::Sink::threshold is not thread-safe.
|
||||
# The severity field is read/written from multiple threads without synchronization.
|
||||
race:beast::Journal::Sink::threshold
|
||||
|
||||
# TODO(RIPD-XXXX): LedgerReplayTask/LedgerDeltaAcquire lock-order-inversion.
|
||||
# LedgerDeltaAcquire::notify() calls LedgerReplayTask::deltaReady() while
|
||||
# holding its own mutex, but deltaReady() acquires LedgerReplayTask's mutex,
|
||||
# creating an A->B / B->A lock ordering cycle.
|
||||
deadlock:xrpl::LedgerDeltaAcquire
|
||||
deadlock:xrpl::LedgerReplayTask
|
||||
|
||||
# TODO(RIPD-XXXX): SHAMap copy constructor / setLedgerSeq data race.
|
||||
race:xrpl::SHAMap::SHAMap
|
||||
race:xrpl::SHAMap::setLedgerSeq
|
||||
|
||||
# TODO(RIPD-XXXX): RCLConsensus / NetworkOPsImp lock-order-inversion.
|
||||
# doLedgerAccept() acquires NetworkOPsImp mutex then RCLConsensus::startRound()
|
||||
# acquires consensus mutex (M0→M1), while RCLConsensus::Adaptor::doAccept()
|
||||
# acquires them in reverse order via std::lock (M1→M0). The std::lock path is
|
||||
# actually safe (deadlock-free), but TSAN flags the inconsistent ordering.
|
||||
deadlock:xrpl::RCLConsensus
|
||||
deadlock:xrpl::doLedgerAccept
|
||||
|
||||
# TODO(RIPD-XXXX): beast::PropertyStream::Source lock-order-inversion.
|
||||
# find_one_deep() acquires parent's recursive_mutex then recursively acquires
|
||||
# children's mutexes, while find_one() can acquire them in reverse order.
|
||||
# This is a pre-existing issue in beast's PropertyStream tree traversal.
|
||||
deadlock:beast::PropertyStream::Source
|
||||
|
||||
# Signal/crash suppressions for GCC TSAN instrumentation issues
|
||||
# Suppress SEGV crashes in TSAN itself during stringbuf operations
|
||||
# This appears to be a GCC-15 TSAN instrumentation issue with basic_stringbuf::str()
|
||||
# Commonly triggered in beast::Journal::ScopedStream destructor
|
||||
signal:std::__cxx11::basic_stringbuf
|
||||
signal:basic_stringbuf
|
||||
signal:basic_ostringstream
|
||||
|
||||
called_from_lib:libclang_rt
|
||||
race:ostreambuf_iterator
|
||||
race:basic_ostream
|
||||
|
||||
# Suppress SEGV in Boost ASIO memory allocation with GCC-15 TSAN
|
||||
signal:boost::asio::aligned_new
|
||||
signal:boost::asio::detail::memory
|
||||
|
||||
# Suppress SEGV in execute_native_thread_routine
|
||||
signal:execute_native_thread_routine
|
||||
|
||||
# Suppress data race in Boost Context fiber management
|
||||
# This is a false positive in Boost's exception state management during fiber context switching
|
||||
race:__cxxabiv1::manage_exception_state
|
||||
race:boost::context::fiber::resume
|
||||
race:boost::asio::detail::spawned_fiber_thread
|
||||
race:boost::asio::detail::spawned_fiber_thread::suspend_with
|
||||
race:boost::asio::detail::spawned_fiber_thread::destroy
|
||||
|
||||
# Suppress data race in __tsan_memcpy called from Boost fiber operations
|
||||
race:__tsan_memcpy
|
||||
|
||||
4
scripts/codegen/.codegen_stamp
Normal file
4
scripts/codegen/.codegen_stamp
Normal file
@@ -0,0 +1,4 @@
|
||||
# Auto-generated by protocol autogen - do not edit manually.
|
||||
# This file tracks input hashes to avoid unnecessary code regeneration.
|
||||
# It should be checked into version control alongside the generated files.
|
||||
COMBINED_HASH=24a9168ac6a450f09fa4e2ab288d06624a368041e91fbc7741101d3565d1e601
|
||||
@@ -138,28 +138,11 @@ def main():
|
||||
"--sfields-macro",
|
||||
help="Path to sfields.macro (default: auto-detect from macro_path)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list-outputs",
|
||||
action="store_true",
|
||||
help="List output files without generating (one per line)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse the macro file to get ledger entry names
|
||||
entries = parse_macro_file(args.macro_path)
|
||||
|
||||
# If --list-outputs, just print the output file paths and exit
|
||||
if args.list_outputs:
|
||||
header_dir = Path(args.header_dir)
|
||||
for entry in entries:
|
||||
print(header_dir / f"{entry['name']}.h")
|
||||
if args.test_dir:
|
||||
test_dir = Path(args.test_dir)
|
||||
for entry in entries:
|
||||
print(test_dir / f"{entry['name']}Tests.cpp")
|
||||
return
|
||||
|
||||
# Auto-detect sfields.macro path if not provided
|
||||
if args.sfields_macro:
|
||||
sfields_path = Path(args.sfields_macro)
|
||||
@@ -147,28 +147,11 @@ def main():
|
||||
"--sfields-macro",
|
||||
help="Path to sfields.macro (default: auto-detect from macro_path)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list-outputs",
|
||||
action="store_true",
|
||||
help="List output files without generating (one per line)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse the macro file to get transaction names
|
||||
transactions = parse_macro_file(args.macro_path)
|
||||
|
||||
# If --list-outputs, just print the output file paths and exit
|
||||
if args.list_outputs:
|
||||
header_dir = Path(args.header_dir)
|
||||
for tx in transactions:
|
||||
print(header_dir / f"{tx['name']}.h")
|
||||
if args.test_dir:
|
||||
test_dir = Path(args.test_dir)
|
||||
for tx in transactions:
|
||||
print(test_dir / f"{tx['name']}Tests.cpp")
|
||||
return
|
||||
|
||||
# Auto-detect sfields.macro path if not provided
|
||||
if args.sfields_macro:
|
||||
sfields_path = Path(args.sfields_macro)
|
||||
83
scripts/codegen/update_codegen_stamp.py
Normal file
83
scripts/codegen/update_codegen_stamp.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check or update the codegen stamp file.
|
||||
|
||||
Uses only the Python standard library (hashlib, pathlib, sys) so it can
|
||||
run without a virtual environment.
|
||||
|
||||
Modes:
|
||||
--check Exit 0 if stamp is up-to-date, exit 1 if stale/missing.
|
||||
--update Recompute the hash and write it to the stamp file.
|
||||
|
||||
Usage:
|
||||
python update_codegen_stamp.py --check <stamp_file> <input_files...>
|
||||
python update_codegen_stamp.py --update <stamp_file> <input_files...>
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def compute_combined_hash(input_files: list[str]) -> str:
|
||||
"""Compute a combined SHA-256 hash of all input files.
|
||||
|
||||
Algorithm: compute each file's SHA-256 hex digest, concatenate them
|
||||
all, then SHA-256 the concatenation.
|
||||
"""
|
||||
parts = []
|
||||
for filepath in input_files:
|
||||
if not Path(filepath).exists():
|
||||
print(f"Error: input file not found: {filepath}", file=sys.stderr)
|
||||
raise FileNotFoundError(f"Input file not found: {filepath}")
|
||||
file_hash = hashlib.sha256(Path(filepath).read_bytes()).hexdigest()
|
||||
parts.append(file_hash)
|
||||
|
||||
combined = "".join(parts)
|
||||
return hashlib.sha256(combined.encode()).hexdigest()
|
||||
|
||||
|
||||
def read_stamp_hash(stamp_file: str) -> str:
|
||||
"""Read the COMBINED_HASH from an existing stamp file, or '' if missing."""
|
||||
path = Path(stamp_file)
|
||||
if not path.exists():
|
||||
return ""
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
if line.startswith("COMBINED_HASH="):
|
||||
return line.split("=", 1)[1]
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4 or sys.argv[1] not in ("--check", "--update"):
|
||||
print(
|
||||
f"Usage: {sys.argv[0]} --check|--update <stamp_file> <input_files...>",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
mode = sys.argv[1]
|
||||
stamp_file = sys.argv[2]
|
||||
input_files = sys.argv[3:]
|
||||
|
||||
current_hash = compute_combined_hash(input_files)
|
||||
|
||||
if mode == "--check":
|
||||
stamp_hash = read_stamp_hash(stamp_file)
|
||||
if current_hash == stamp_hash:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
# --update
|
||||
with open(stamp_file, "w", encoding="utf-8") as fp:
|
||||
fp.write(
|
||||
"# Auto-generated by protocol autogen - do not edit manually.\n"
|
||||
"# This file tracks input hashes to avoid unnecessary code regeneration.\n"
|
||||
"# It should be checked into version control alongside the generated files.\n"
|
||||
)
|
||||
fp.write(f"COMBINED_HASH={current_hash}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -45,7 +45,7 @@ getFileContents(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string const result{
|
||||
std::string result{
|
||||
std::istreambuf_iterator<char>{fileStream}, std::istreambuf_iterator<char>{}};
|
||||
|
||||
if (fileStream.bad())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpld/app/misc/CanonicalTXSet.h>
|
||||
#include <xrpl/ledger/CanonicalTXSet.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
#include <xrpld/app/ledger/InboundLedgers.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/ledger/LedgerToJson.h>
|
||||
#include <xrpld/app/ledger/PendingSaves.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/consensus/LedgerTiming.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/HashRouter.h>
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
#include <xrpl/nodestore/detail/DatabaseNodeImp.h>
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/ledger/LedgerTiming.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -21,7 +11,6 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/rdb/RelationalDatabase.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -30,23 +19,6 @@ namespace xrpl {
|
||||
|
||||
create_genesis_t const create_genesis{};
|
||||
|
||||
uint256
|
||||
calculateLedgerHash(LedgerHeader const& info)
|
||||
{
|
||||
// VFALCO This has to match addRaw in View.h.
|
||||
return sha512Half(
|
||||
HashPrefix::ledgerMaster,
|
||||
std::uint32_t(info.seq),
|
||||
std::uint64_t(info.drops.drops()),
|
||||
info.parentHash,
|
||||
info.txHash,
|
||||
info.accountHash,
|
||||
std::uint32_t(info.parentCloseTime.time_since_epoch().count()),
|
||||
std::uint32_t(info.closeTime.time_since_epoch().count()),
|
||||
std::uint8_t(info.closeTimeResolution.count()),
|
||||
std::uint8_t(info.closeFlags));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Ledger::sles_iter_impl : public sles_type::iter_base
|
||||
@@ -108,8 +80,7 @@ public:
|
||||
|
||||
txs_iter_impl(txs_iter_impl const&) = default;
|
||||
|
||||
txs_iter_impl(bool metadata, SHAMap::const_iterator iter)
|
||||
: metadata_(metadata), iter_(std::move(iter))
|
||||
txs_iter_impl(bool metadata, SHAMap::const_iterator iter) : metadata_(metadata), iter_(iter)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -138,8 +109,8 @@ public:
|
||||
{
|
||||
auto const& item = *iter_;
|
||||
if (metadata_)
|
||||
return deserializeTxPlusMeta(item);
|
||||
return {deserializeTx(item), nullptr};
|
||||
return Ledger::deserializeTxPlusMeta(item);
|
||||
return {Ledger::deserializeTx(item), nullptr};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -147,13 +118,15 @@ public:
|
||||
|
||||
Ledger::Ledger(
|
||||
create_genesis_t,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
std::vector<uint256> const& amendments,
|
||||
Family& family)
|
||||
: mImmutable(false)
|
||||
, txMap_(SHAMapType::TRANSACTION, family)
|
||||
, stateMap_(SHAMapType::STATE, family)
|
||||
, rules_{config.features}
|
||||
, fees_(fees)
|
||||
, rules_(rules)
|
||||
, j_(beast::Journal(beast::Journal::getNullSink()))
|
||||
{
|
||||
header_.seq = 1;
|
||||
@@ -182,19 +155,19 @@ Ledger::Ledger(
|
||||
// Whether featureXRPFees is supported will depend on startup options.
|
||||
if (std::find(amendments.begin(), amendments.end(), featureXRPFees) != amendments.end())
|
||||
{
|
||||
sle->at(sfBaseFeeDrops) = config.FEES.reference_fee;
|
||||
sle->at(sfReserveBaseDrops) = config.FEES.account_reserve;
|
||||
sle->at(sfReserveIncrementDrops) = config.FEES.owner_reserve;
|
||||
sle->at(sfBaseFeeDrops) = fees.base;
|
||||
sle->at(sfReserveBaseDrops) = fees.reserve;
|
||||
sle->at(sfReserveIncrementDrops) = fees.increment;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const f = config.FEES.reference_fee.dropsAs<std::uint64_t>())
|
||||
if (auto const f = fees.base.dropsAs<std::uint64_t>())
|
||||
sle->at(sfBaseFee) = *f;
|
||||
if (auto const f = config.FEES.account_reserve.dropsAs<std::uint32_t>())
|
||||
if (auto const f = fees.reserve.dropsAs<std::uint32_t>())
|
||||
sle->at(sfReserveBase) = *f;
|
||||
if (auto const f = config.FEES.owner_reserve.dropsAs<std::uint32_t>())
|
||||
if (auto const f = fees.increment.dropsAs<std::uint32_t>())
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
|
||||
sle->at(sfReferenceFeeUnits) = FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
@@ -207,13 +180,15 @@ Ledger::Ledger(
|
||||
LedgerHeader const& info,
|
||||
bool& loaded,
|
||||
bool acquire,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
Family& family,
|
||||
beast::Journal j)
|
||||
: mImmutable(true)
|
||||
, txMap_(SHAMapType::TRANSACTION, info.txHash, family)
|
||||
, stateMap_(SHAMapType::STATE, info.accountHash, family)
|
||||
, rules_(config.features)
|
||||
, fees_(fees)
|
||||
, rules_(rules)
|
||||
, header_(info)
|
||||
, j_(j)
|
||||
{
|
||||
@@ -235,7 +210,6 @@ Ledger::Ledger(
|
||||
txMap_.setImmutable();
|
||||
stateMap_.setImmutable();
|
||||
|
||||
defaultFees(config);
|
||||
if (!setup())
|
||||
loaded = false;
|
||||
|
||||
@@ -275,11 +249,11 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime)
|
||||
}
|
||||
}
|
||||
|
||||
Ledger::Ledger(LedgerHeader const& info, Config const& config, Family& family)
|
||||
Ledger::Ledger(LedgerHeader const& info, Rules const& rules, Family& family)
|
||||
: mImmutable(true)
|
||||
, txMap_(SHAMapType::TRANSACTION, info.txHash, family)
|
||||
, stateMap_(SHAMapType::STATE, info.accountHash, family)
|
||||
, rules_{config.features}
|
||||
, rules_(rules)
|
||||
, header_(info)
|
||||
, j_(beast::Journal(beast::Journal::getNullSink()))
|
||||
{
|
||||
@@ -289,18 +263,19 @@ Ledger::Ledger(LedgerHeader const& info, Config const& config, Family& family)
|
||||
Ledger::Ledger(
|
||||
std::uint32_t ledgerSeq,
|
||||
NetClock::time_point closeTime,
|
||||
Config const& config,
|
||||
Rules const& rules,
|
||||
Fees const& fees,
|
||||
Family& family)
|
||||
: mImmutable(false)
|
||||
, txMap_(SHAMapType::TRANSACTION, family)
|
||||
, stateMap_(SHAMapType::STATE, family)
|
||||
, rules_{config.features}
|
||||
, fees_(fees)
|
||||
, rules_(rules)
|
||||
, j_(beast::Journal(beast::Journal::getNullSink()))
|
||||
{
|
||||
header_.seq = ledgerSeq;
|
||||
header_.closeTime = closeTime;
|
||||
header_.closeTimeResolution = ledgerDefaultTimeResolution;
|
||||
defaultFees(config);
|
||||
setup();
|
||||
}
|
||||
|
||||
@@ -350,14 +325,14 @@ Ledger::addSLE(SLE const& sle)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::shared_ptr<STTx const>
|
||||
deserializeTx(SHAMapItem const& item)
|
||||
Ledger::deserializeTx(SHAMapItem const& item)
|
||||
{
|
||||
SerialIter sit(item.slice());
|
||||
return std::make_shared<STTx const>(sit);
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
|
||||
deserializeTxPlusMeta(SHAMapItem const& item)
|
||||
Ledger::deserializeTxPlusMeta(SHAMapItem const& item)
|
||||
{
|
||||
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>> result;
|
||||
SerialIter sit(item.slice());
|
||||
@@ -636,20 +611,6 @@ Ledger::setup()
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
Ledger::defaultFees(Config const& config)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
|
||||
"xrpl::Ledger::defaultFees : zero fees");
|
||||
if (fees_.base == 0)
|
||||
fees_.base = config.FEES.reference_fee;
|
||||
if (fees_.reserve == 0)
|
||||
fees_.reserve = config.FEES.account_reserve;
|
||||
if (fees_.increment == 0)
|
||||
fees_.increment = config.FEES.owner_reserve;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
Ledger::peek(Keylet const& k) const
|
||||
{
|
||||
@@ -732,7 +693,7 @@ Ledger::updateNegativeUNL()
|
||||
if (sle->isFieldPresent(sfDisabledValidators))
|
||||
{
|
||||
auto const& oldNUnl = sle->getFieldArray(sfDisabledValidators);
|
||||
for (auto v : oldNUnl)
|
||||
for (auto const& v : oldNUnl)
|
||||
{
|
||||
if (hasToReEnable && v.isFieldPresent(sfPublicKey) &&
|
||||
v.getFieldVL(sfPublicKey) == sle->getFieldVL(sfValidatorToReEnable))
|
||||
@@ -816,27 +777,17 @@ Ledger::walkLedger(beast::Journal j, bool parallel) const
|
||||
}
|
||||
|
||||
bool
|
||||
Ledger::assertSensible(beast::Journal ledgerJ) const
|
||||
Ledger::isSensible() const
|
||||
{
|
||||
if (header_.hash.isNonZero() && header_.accountHash.isNonZero() &&
|
||||
(header_.accountHash == stateMap_.getHash().as_uint256()) &&
|
||||
(header_.txHash == txMap_.getHash().as_uint256()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
Json::Value j = getJson({*this, {}});
|
||||
|
||||
j[jss::accountTreeHash] = to_string(header_.accountHash);
|
||||
j[jss::transTreeHash] = to_string(header_.txHash);
|
||||
|
||||
JLOG(ledgerJ.fatal()) << "ledger is not sensible" << j;
|
||||
|
||||
UNREACHABLE("xrpl::Ledger::assertSensible : ledger is not sensible");
|
||||
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
if (header_.hash.isZero())
|
||||
return false;
|
||||
if (header_.accountHash.isZero())
|
||||
return false;
|
||||
if (header_.accountHash != stateMap_.getHash().as_uint256())
|
||||
return false;
|
||||
if (header_.txHash != txMap_.getHash().as_uint256())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// update the skip list with the information from our previous ledger
|
||||
@@ -925,76 +876,6 @@ Ledger::isVotingLedger() const
|
||||
return ::xrpl::isVotingLedger(header_.seq + 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
saveValidatedLedger(Application& app, std::shared_ptr<Ledger const> const& ledger, bool current)
|
||||
{
|
||||
auto j = app.journal("Ledger");
|
||||
auto seq = ledger->header().seq;
|
||||
if (!app.pendingSaves().startWork(seq))
|
||||
{
|
||||
// The save was completed synchronously
|
||||
JLOG(j.debug()) << "Save aborted";
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& db = app.getRelationalDatabase();
|
||||
|
||||
auto const res = db.saveValidatedLedger(ledger, current);
|
||||
|
||||
// Clients can now trust the database for
|
||||
// information about this ledger sequence.
|
||||
app.pendingSaves().finishWork(seq);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Save, or arrange to save, a fully-validated ledger
|
||||
Returns false on error
|
||||
*/
|
||||
bool
|
||||
pendSaveValidated(
|
||||
Application& app,
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
bool isSynchronous,
|
||||
bool isCurrent)
|
||||
{
|
||||
if (!app.getHashRouter().setFlags(ledger->header().hash, HashRouterFlags::SAVED))
|
||||
{
|
||||
// We have tried to save this ledger recently
|
||||
auto stream = app.journal("Ledger").debug();
|
||||
JLOG(stream) << "Double pend save for " << ledger->header().seq;
|
||||
|
||||
if (!isSynchronous || !app.pendingSaves().pending(ledger->header().seq))
|
||||
{
|
||||
// Either we don't need it to be finished
|
||||
// or it is finished
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
XRPL_ASSERT(ledger->isImmutable(), "xrpl::pendSaveValidated : immutable ledger");
|
||||
|
||||
if (!app.pendingSaves().shouldWork(ledger->header().seq, isSynchronous))
|
||||
{
|
||||
auto stream = app.journal("Ledger").debug();
|
||||
JLOG(stream) << "Pend save with seq in pending saves " << ledger->header().seq;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// See if we can use the JobQueue.
|
||||
if (!isSynchronous &&
|
||||
app.getJobQueue().addJob(
|
||||
isCurrent ? jtPUBLEDGER : jtPUBOLDLEDGER,
|
||||
std::to_string(ledger->seq()),
|
||||
[&app, ledger, isCurrent]() { saveValidatedLedger(app, ledger, isCurrent); }))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// The JobQueue won't do the Job. Do the save synchronously.
|
||||
return saveValidatedLedger(app, ledger, isCurrent);
|
||||
}
|
||||
|
||||
void
|
||||
Ledger::unshare() const
|
||||
{
|
||||
@@ -1008,84 +889,5 @@ Ledger::invariants() const
|
||||
stateMap_.invariants();
|
||||
txMap_.invariants();
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Make ledger using info loaded from database.
|
||||
*
|
||||
* @param LedgerHeader: Ledger information.
|
||||
* @param app: Link to the Application.
|
||||
* @param acquire: Acquire the ledger if not found locally.
|
||||
* @return Shared pointer to the ledger.
|
||||
*/
|
||||
std::shared_ptr<Ledger>
|
||||
loadLedgerHelper(LedgerHeader const& info, Application& app, bool acquire)
|
||||
{
|
||||
bool loaded = false;
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
info, loaded, acquire, app.config(), app.getNodeFamily(), app.journal("Ledger"));
|
||||
|
||||
if (!loaded)
|
||||
ledger.reset();
|
||||
|
||||
return ledger;
|
||||
}
|
||||
|
||||
static void
|
||||
finishLoadByIndexOrHash(
|
||||
std::shared_ptr<Ledger> const& ledger,
|
||||
Config const& config,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!ledger)
|
||||
return;
|
||||
|
||||
XRPL_ASSERT(
|
||||
ledger->header().seq < XRP_LEDGER_EARLIEST_FEES || ledger->read(keylet::fees()),
|
||||
"xrpl::finishLoadByIndexOrHash : valid ledger fees");
|
||||
ledger->setImmutable();
|
||||
|
||||
JLOG(j.trace()) << "Loaded ledger: " << to_string(ledger->header().hash);
|
||||
|
||||
ledger->setFull();
|
||||
}
|
||||
|
||||
std::tuple<std::shared_ptr<Ledger>, std::uint32_t, uint256>
|
||||
getLatestLedger(Application& app)
|
||||
{
|
||||
std::optional<LedgerHeader> const info = app.getRelationalDatabase().getNewestLedgerInfo();
|
||||
if (!info)
|
||||
return {std::shared_ptr<Ledger>(), {}, {}};
|
||||
return {loadLedgerHelper(*info, app, true), info->seq, info->hash};
|
||||
}
|
||||
|
||||
std::shared_ptr<Ledger>
|
||||
loadByIndex(std::uint32_t ledgerIndex, Application& app, bool acquire)
|
||||
{
|
||||
if (std::optional<LedgerHeader> info =
|
||||
app.getRelationalDatabase().getLedgerInfoByIndex(ledgerIndex))
|
||||
{
|
||||
std::shared_ptr<Ledger> ledger = loadLedgerHelper(*info, app, acquire);
|
||||
finishLoadByIndexOrHash(ledger, app.config(), app.journal("Ledger"));
|
||||
return ledger;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Ledger>
|
||||
loadByHash(uint256 const& ledgerHash, Application& app, bool acquire)
|
||||
{
|
||||
if (std::optional<LedgerHeader> info =
|
||||
app.getRelationalDatabase().getLedgerInfoByHash(ledgerHash))
|
||||
{
|
||||
std::shared_ptr<Ledger> ledger = loadLedgerHelper(*info, app, acquire);
|
||||
finishLoadByIndexOrHash(ledger, app.config(), app.journal("Ledger"));
|
||||
XRPL_ASSERT(
|
||||
!ledger || ledger->header().hash == ledgerHash,
|
||||
"xrpl::loadByHash : ledger hash match if loaded");
|
||||
return ledger;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -161,7 +161,7 @@ getLineIfUsable(
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
auto sle = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (!sle)
|
||||
{
|
||||
|
||||
@@ -113,7 +113,7 @@ encodeSoftwareVersion(std::string_view versionStr)
|
||||
{
|
||||
std::uint8_t x = 0;
|
||||
|
||||
for (auto id : v.preReleaseIdentifiers)
|
||||
for (auto const& id : v.preReleaseIdentifiers)
|
||||
{
|
||||
auto parsePreRelease = [](std::string_view identifier,
|
||||
std::string_view prefix,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -51,4 +53,21 @@ deserializePrefixedHeader(Slice data, bool hasHash)
|
||||
return deserializeHeader(data + 4, hasHash);
|
||||
}
|
||||
|
||||
uint256
|
||||
calculateLedgerHash(LedgerHeader const& info)
|
||||
{
|
||||
// VFALCO This has to match addRaw in View.h.
|
||||
return sha512Half(
|
||||
HashPrefix::ledgerMaster,
|
||||
std::uint32_t(info.seq),
|
||||
std::uint64_t(info.drops.drops()),
|
||||
info.parentHash,
|
||||
info.txHash,
|
||||
info.accountHash,
|
||||
std::uint32_t(info.parentCloseTime.time_since_epoch().count()),
|
||||
std::uint32_t(info.closeTime.time_since_epoch().count()),
|
||||
std::uint8_t(info.closeTimeResolution.count()),
|
||||
std::uint8_t(info.closeFlags));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -153,7 +153,7 @@ STPath::getJson(JsonOptions) const
|
||||
{
|
||||
Json::Value ret(Json::arrayValue);
|
||||
|
||||
for (auto it : mPath)
|
||||
for (auto const& it : mPath)
|
||||
{
|
||||
Json::Value elem(Json::objectValue);
|
||||
auto const iType = it.getNodeType();
|
||||
@@ -179,7 +179,7 @@ Json::Value
|
||||
STPathSet::getJson(JsonOptions options) const
|
||||
{
|
||||
Json::Value ret(Json::arrayValue);
|
||||
for (auto it : value)
|
||||
for (auto const& it : value)
|
||||
ret.append(it.getJson(options));
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/rdb/DatabaseCon.h>
|
||||
#include <xrpl/rdb/SociDB.h>
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
auto it = checkpointers_.find(id);
|
||||
if (it != checkpointers_.end())
|
||||
return it->second;
|
||||
return {};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -40,11 +40,14 @@ public:
|
||||
}
|
||||
|
||||
std::shared_ptr<Checkpointer>
|
||||
create(std::shared_ptr<soci::session> const& session, JobQueue& jobQueue, Logs& logs)
|
||||
create(
|
||||
std::shared_ptr<soci::session> const& session,
|
||||
JobQueue& jobQueue,
|
||||
ServiceRegistry& registry)
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
auto const id = nextId_++;
|
||||
auto const r = makeCheckpointer(id, session, jobQueue, logs);
|
||||
auto const r = makeCheckpointer(id, session, jobQueue, registry);
|
||||
checkpointers_[id] = r;
|
||||
return r;
|
||||
}
|
||||
@@ -82,11 +85,11 @@ DatabaseCon::~DatabaseCon()
|
||||
std::unique_ptr<std::vector<std::string> const> DatabaseCon::Setup::globalPragma;
|
||||
|
||||
void
|
||||
DatabaseCon::setupCheckpointing(JobQueue* q, Logs& l)
|
||||
DatabaseCon::setupCheckpointing(JobQueue* q, ServiceRegistry& registry)
|
||||
{
|
||||
if (q == nullptr)
|
||||
Throw<std::logic_error>("No JobQueue");
|
||||
checkpointer_ = checkpointers.create(session_, *q, l);
|
||||
checkpointer_ = checkpointers.create(session_, *q, registry);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -187,8 +187,11 @@ public:
|
||||
std::uintptr_t id,
|
||||
std::weak_ptr<soci::session> session,
|
||||
JobQueue& q,
|
||||
Logs& logs)
|
||||
: id_(id), session_(std::move(session)), jobQueue_(q), j_(logs.journal("WALCheckpointer"))
|
||||
ServiceRegistry& registry)
|
||||
: id_(id)
|
||||
, session_(std::move(session))
|
||||
, jobQueue_(q)
|
||||
, j_(registry.getJournal("WALCheckpointer"))
|
||||
{
|
||||
if (auto [conn, keepAlive] = getConnection(); conn)
|
||||
{
|
||||
@@ -307,9 +310,9 @@ makeCheckpointer(
|
||||
std::uintptr_t id,
|
||||
std::weak_ptr<soci::session> session,
|
||||
JobQueue& queue,
|
||||
Logs& logs)
|
||||
ServiceRegistry& registry)
|
||||
{
|
||||
return std::make_shared<WALCheckpointer>(id, std::move(session), queue, logs);
|
||||
return std::make_shared<WALCheckpointer>(id, std::move(session), queue, registry);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1021,10 +1021,7 @@ SHAMap::walkSubTree(bool doWrite, NodeObjectType t)
|
||||
// save our place and work on this node
|
||||
|
||||
stack.emplace(std::move(node), branch);
|
||||
// The semantics of this changes when we move to c++-20
|
||||
// Right now no move will occur; With c++-20 child will
|
||||
// be moved from.
|
||||
node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(std::move(child));
|
||||
node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(child);
|
||||
pos = 0;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace xrpl {
|
||||
Expected<std::vector<SignerEntries::SignerEntry>, NotTEC>
|
||||
SignerEntries::deserialize(STObject const& obj, beast::Journal journal, std::string_view annotation)
|
||||
{
|
||||
std::pair<std::vector<SignerEntry>, NotTEC> s;
|
||||
|
||||
if (!obj.isFieldPresent(sfSignerEntries))
|
||||
{
|
||||
JLOG(journal.trace()) << "Malformed " << annotation << ": Need signer entry array.";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
@@ -35,7 +34,7 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
|
||||
if (!isPseudoTx(ctx.tx) || ctx.tx.isFieldPresent(sfNetworkID))
|
||||
{
|
||||
uint32_t nodeNID = ctx.registry.getNetworkIDService().getNetworkID();
|
||||
uint32_t nodeNID = ctx.registry.get().getNetworkIDService().getNetworkID();
|
||||
std::optional<uint32_t> txNID = ctx.tx[~sfNetworkID];
|
||||
|
||||
if (nodeNID <= 1024)
|
||||
@@ -212,7 +211,7 @@ Transactor::preflight2(PreflightContext const& ctx)
|
||||
// Do not add any checks after this point that are relevant for
|
||||
// batch inner transactions. They will be skipped.
|
||||
|
||||
auto const sigValid = checkValidity(ctx.registry.getHashRouter(), ctx.tx, ctx.rules);
|
||||
auto const sigValid = checkValidity(ctx.registry.get().getHashRouter(), ctx.tx, ctx.rules);
|
||||
if (sigValid.first == Validity::SigBad)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
|
||||
@@ -302,7 +301,7 @@ Transactor::calculateOwnerReserveFee(ReadView const& view, STTx const& tx)
|
||||
// need to rethink charging an owner reserve as a transaction fee.
|
||||
// TODO: This function is static, and I don't want to add more parameters.
|
||||
// When it is finally refactored to be in a context that has access to the
|
||||
// Application, include "app().overlay().networkID() > 2 ||" in the
|
||||
// Application, include "app().getOverlay().networkID() > 2 ||" in the
|
||||
// condition.
|
||||
XRPL_ASSERT(
|
||||
view.fees().increment > view.fees().base * 100,
|
||||
@@ -677,8 +676,7 @@ Transactor::checkSign(
|
||||
}
|
||||
|
||||
// Look up the account.
|
||||
auto const idSigner =
|
||||
pkSigner.empty() ? idAccount : calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const sleAccount = view.read(keylet::account(idAccount));
|
||||
if (!sleAccount)
|
||||
return terNO_ACCOUNT;
|
||||
@@ -1097,7 +1095,8 @@ Transactor::operator()()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (auto const& trap = ctx_.registry.trapTxID(); trap && *trap == ctx_.tx.getTransactionID())
|
||||
if (auto const& trap = ctx_.registry.get().getTrapTxID();
|
||||
trap && *trap == ctx_.tx.getTransactionID())
|
||||
{
|
||||
trapTransaction(*trap);
|
||||
}
|
||||
@@ -1199,16 +1198,27 @@ Transactor::operator()()
|
||||
|
||||
// If necessary, remove any offers found unfunded during processing
|
||||
if ((result == tecOVERSIZE) || (result == tecKILLED))
|
||||
removeUnfundedOffers(view(), removedOffers, ctx_.registry.journal("View"));
|
||||
{
|
||||
removeUnfundedOffers(view(), removedOffers, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (result == tecEXPIRED)
|
||||
removeExpiredNFTokenOffers(view(), expiredNFTokenOffers, ctx_.registry.journal("View"));
|
||||
{
|
||||
removeExpiredNFTokenOffers(
|
||||
view(), expiredNFTokenOffers, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (result == tecINCOMPLETE)
|
||||
removeDeletedTrustLines(view(), removedTrustLines, ctx_.registry.journal("View"));
|
||||
{
|
||||
removeDeletedTrustLines(
|
||||
view(), removedTrustLines, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (result == tecEXPIRED)
|
||||
removeExpiredCredentials(view(), expiredCredentials, ctx_.registry.journal("View"));
|
||||
{
|
||||
removeExpiredCredentials(
|
||||
view(), expiredCredentials, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
applied = isTecClaim(result);
|
||||
}
|
||||
|
||||
194
src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp
Normal file
194
src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidLoanBroker::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after)
|
||||
{
|
||||
if (after->getType() == ltLOAN_BROKER)
|
||||
{
|
||||
auto& broker = brokers_[after->key()];
|
||||
broker.brokerBefore = before;
|
||||
broker.brokerAfter = after;
|
||||
}
|
||||
else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = after->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
else if (after->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
lines_.emplace_back(after);
|
||||
}
|
||||
else if (after->getType() == ltMPTOKEN)
|
||||
{
|
||||
mpts_.emplace_back(after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::goodZeroDirectory(
|
||||
ReadView const& view,
|
||||
SLE::const_ref dir,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
auto const next = dir->at(~sfIndexNext);
|
||||
auto const prev = dir->at(~sfIndexPrevious);
|
||||
if ((prev && (*prev != 0u)) || (next && (*next != 0u)))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple directory pages";
|
||||
return false;
|
||||
}
|
||||
auto indexes = dir->getFieldV256(sfIndexes);
|
||||
if (indexes.size() > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple indexes in the Directory root";
|
||||
return false;
|
||||
}
|
||||
if (indexes.size() == 1)
|
||||
{
|
||||
auto const index = indexes.value().front();
|
||||
auto const sle = view.read(keylet::unchecked(index));
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt";
|
||||
return false;
|
||||
}
|
||||
if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has an unexpected entry in the directory";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Loan Brokers will not exist on ledger if the Lending Protocol amendment
|
||||
// is not enabled, so there's no need to check it.
|
||||
|
||||
for (auto const& line : lines_)
|
||||
{
|
||||
for (auto const& field : {&sfLowLimit, &sfHighLimit})
|
||||
{
|
||||
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
|
||||
// This Invariant doesn't know about the rules for Trust Lines, so
|
||||
// if the account is missing, don't treat it as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& mpt : mpts_)
|
||||
{
|
||||
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
|
||||
// This Invariant doesn't know about the rules for MPTokens, so
|
||||
// if the account is missing, don't treat is as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [brokerID, broker] : brokers_)
|
||||
{
|
||||
auto const& after =
|
||||
broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanbroker(brokerID));
|
||||
|
||||
if (!after)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& before = broker.brokerBefore;
|
||||
|
||||
// https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
|
||||
// If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
|
||||
// one node (the root), which will only hold entries for `RippleState`
|
||||
// or `MPToken` objects.
|
||||
if (after->at(sfOwnerCount) == 0)
|
||||
{
|
||||
auto const dir = view.read(keylet::ownerDir(after->at(sfAccount)));
|
||||
if (dir)
|
||||
{
|
||||
if (!goodZeroDirectory(view, dir, j))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
|
||||
"decreased";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfDebtTotal) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfCoverAvailable) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative";
|
||||
return false;
|
||||
}
|
||||
auto const vault = view.read(keylet::vault(after->at(sfVaultID)));
|
||||
if (!vault)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid";
|
||||
return false;
|
||||
}
|
||||
auto const& vaultAsset = vault->at(sfAsset);
|
||||
if (after->at(sfCoverAvailable) < accountHolds(
|
||||
view,
|
||||
after->at(sfAccount),
|
||||
vaultAsset,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
|
||||
"is less than pseudo-account asset balance";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -2,197 +2,11 @@
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidLoanBroker::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after)
|
||||
{
|
||||
if (after->getType() == ltLOAN_BROKER)
|
||||
{
|
||||
auto& broker = brokers_[after->key()];
|
||||
broker.brokerBefore = before;
|
||||
broker.brokerAfter = after;
|
||||
}
|
||||
else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = after->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
else if (after->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
lines_.emplace_back(after);
|
||||
}
|
||||
else if (after->getType() == ltMPTOKEN)
|
||||
{
|
||||
mpts_.emplace_back(after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::goodZeroDirectory(
|
||||
ReadView const& view,
|
||||
SLE::const_ref dir,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
auto const next = dir->at(~sfIndexNext);
|
||||
auto const prev = dir->at(~sfIndexPrevious);
|
||||
if ((prev && (*prev != 0u)) || (next && (*next != 0u)))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple directory pages";
|
||||
return false;
|
||||
}
|
||||
auto indexes = dir->getFieldV256(sfIndexes);
|
||||
if (indexes.size() > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple indexes in the Directory root";
|
||||
return false;
|
||||
}
|
||||
if (indexes.size() == 1)
|
||||
{
|
||||
auto const index = indexes.value().front();
|
||||
auto const sle = view.read(keylet::unchecked(index));
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt";
|
||||
return false;
|
||||
}
|
||||
if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has an unexpected entry in the directory";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Loan Brokers will not exist on ledger if the Lending Protocol amendment
|
||||
// is not enabled, so there's no need to check it.
|
||||
|
||||
for (auto const& line : lines_)
|
||||
{
|
||||
for (auto const& field : {&sfLowLimit, &sfHighLimit})
|
||||
{
|
||||
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
|
||||
// This Invariant doesn't know about the rules for Trust Lines, so
|
||||
// if the account is missing, don't treat it as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& mpt : mpts_)
|
||||
{
|
||||
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
|
||||
// This Invariant doesn't know about the rules for MPTokens, so
|
||||
// if the account is missing, don't treat is as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [brokerID, broker] : brokers_)
|
||||
{
|
||||
auto const& after =
|
||||
broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanbroker(brokerID));
|
||||
|
||||
if (!after)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& before = broker.brokerBefore;
|
||||
|
||||
// https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
|
||||
// If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
|
||||
// one node (the root), which will only hold entries for `RippleState`
|
||||
// or `MPToken` objects.
|
||||
if (after->at(sfOwnerCount) == 0)
|
||||
{
|
||||
auto const dir = view.read(keylet::ownerDir(after->at(sfAccount)));
|
||||
if (dir)
|
||||
{
|
||||
if (!goodZeroDirectory(view, dir, j))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
|
||||
"decreased";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfDebtTotal) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfCoverAvailable) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative";
|
||||
return false;
|
||||
}
|
||||
auto const vault = view.read(keylet::vault(after->at(sfVaultID)));
|
||||
if (!vault)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid";
|
||||
return false;
|
||||
}
|
||||
auto const& vaultAsset = vault->at(sfAsset);
|
||||
if (after->at(sfCoverAvailable) < accountHolds(
|
||||
view,
|
||||
after->at(sfAccount),
|
||||
vaultAsset,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
|
||||
"is less than pseudo-account asset balance";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
ValidLoan::visitEntry(
|
||||
bool isDelete,
|
||||
@@ -236,8 +50,7 @@ ValidLoan::finalize(
|
||||
after->at(sfPrincipalOutstanding) == beast::zero &&
|
||||
after->at(sfManagementFeeOutstanding) == beast::zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
|
||||
"remaining has not been paid off";
|
||||
JLOG(j.fatal()) << "Invariant failed: Fully paid off Loan still has payments remaining";
|
||||
return false;
|
||||
}
|
||||
if (before && (before->isFlag(lsfLoanOverpayment) != after->isFlag(lsfLoanOverpayment)))
|
||||
|
||||
@@ -38,7 +38,7 @@ ValidPermissionedDomain::visitEntry(
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleStatus.emplace_back(std::move(ss));
|
||||
sleStatus.emplace_back(ss);
|
||||
};
|
||||
|
||||
if (after)
|
||||
|
||||
@@ -205,7 +205,7 @@ ValidVault::finalize(
|
||||
for (auto const& e : beforeMPTs_)
|
||||
{
|
||||
if (e.share.getMptID() == beforeVault.shareMPTID)
|
||||
return std::move(e);
|
||||
return e;
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
@@ -374,7 +374,7 @@ ValidVault::finalize(
|
||||
for (auto const& e : beforeMPTs_)
|
||||
{
|
||||
if (e.share.getMptID() == beforeVault.shareMPTID)
|
||||
return std::move(e);
|
||||
return e;
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpld/app/paths/AMMLiquidity.h>
|
||||
#include <xrpld/app/paths/AMMOffer.h>
|
||||
#include <xrpl/tx/paths/AMMLiquidity.h>
|
||||
#include <xrpl/tx/paths/AMMOffer.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <xrpld/app/paths/AMMLiquidity.h>
|
||||
#include <xrpld/app/paths/AMMOffer.h>
|
||||
|
||||
#include <xrpl/protocol/QualityFunction.h>
|
||||
#include <xrpl/tx/paths/AMMLiquidity.h>
|
||||
#include <xrpl/tx/paths/AMMOffer.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#include <xrpld/app/paths/AMMLiquidity.h>
|
||||
#include <xrpld/app/paths/AMMOffer.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
@@ -11,6 +8,8 @@
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/paths/AMMLiquidity.h>
|
||||
#include <xrpl/tx/paths/AMMOffer.h>
|
||||
#include <xrpl/tx/paths/OfferStream.h>
|
||||
#include <xrpl/tx/paths/detail/FlatSets.h>
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
@@ -1,5 +1,3 @@
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -7,6 +5,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/tx/paths/detail/StepChecks.h>
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/tx/paths/Flow.h>
|
||||
@@ -35,12 +34,12 @@ RippleCalc::rippleCalculate(
|
||||
STPathSet const& spsPaths,
|
||||
|
||||
std::optional<uint256> const& domainID,
|
||||
Logs& l,
|
||||
ServiceRegistry& registry,
|
||||
Input const* const pInputs)
|
||||
{
|
||||
Output flowOut;
|
||||
PaymentSandbox flowSB(&view);
|
||||
auto j = l.journal("Flow");
|
||||
auto j = registry.getJournal("Flow");
|
||||
|
||||
{
|
||||
bool const defaultPaths = (pInputs == nullptr) ? true : pInputs->defaultPathsAllowed;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -9,6 +7,7 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/paths/detail/AmountSpec.h>
|
||||
#include <xrpl/tx/paths/detail/StepChecks.h>
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -197,7 +196,7 @@ removeSignersFromLedger(
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(accountKeylet), removeFromOwnerCount, registry.journal("View"));
|
||||
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
|
||||
|
||||
view.erase(signers);
|
||||
|
||||
@@ -315,7 +314,7 @@ SignerListSet::replaceSignerList()
|
||||
view().insert(signerList);
|
||||
writeSignersToSLE(signerList, flags);
|
||||
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
// Add the signer list to the account's directory.
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(account_));
|
||||
|
||||
@@ -418,7 +418,7 @@ transferHelper(
|
||||
auto const reserve = psb.fees().accountReserve(ownerCount);
|
||||
|
||||
auto const availableBalance = [&]() -> STAmount {
|
||||
STAmount const curBal = (*sleSrc)[sfBalance];
|
||||
STAmount curBal = (*sleSrc)[sfBalance];
|
||||
// Checking that account == src and postFeeBalance == curBal is
|
||||
// not strictly necessary, but helps protect against future
|
||||
// changes
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -58,7 +57,7 @@ CheckCancel::doApply()
|
||||
|
||||
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
|
||||
AccountID const dstId{sleCheck->getAccountID(sfDestination)};
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
|
||||
// If the check is not written to self (and it shouldn't be), remove the
|
||||
// check from the destination account root.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -233,7 +232,7 @@ CheckCash::doApply()
|
||||
//
|
||||
// If it is not a check to self (as should be the case), then there's
|
||||
// work to do...
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
|
||||
|
||||
if (srcId != account_)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -169,7 +168,7 @@ CheckCreate::doApply()
|
||||
|
||||
view().insert(sleCheck);
|
||||
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
// If it's not a self-send (and it shouldn't be), add Check to the
|
||||
// destination's owner directory.
|
||||
if (dstAccountId != account_)
|
||||
|
||||
@@ -281,7 +281,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J
|
||||
Book const book{issueIn, issueOut, std::nullopt};
|
||||
auto const dir = keylet::quality(keylet::book(book), uRate);
|
||||
if (auto const bookExisted = static_cast<bool>(sb.read(dir)); !bookExisted)
|
||||
ctx_.registry.getOrderBookDB().addOrderBook(book);
|
||||
ctx_.registry.get().getOrderBookDB().addOrderBook(book);
|
||||
};
|
||||
addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount));
|
||||
addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
@@ -54,7 +53,7 @@ OfferCancel::doApply()
|
||||
if (auto sleOffer = view().peek(keylet::offer(account_, offerSequence)))
|
||||
{
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete(view(), sleOffer, ctx_.registry.journal("View"));
|
||||
return offerDelete(view(), sleOffer, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << "Offer #" << offerSequence << " can't be found.";
|
||||
|
||||
@@ -144,7 +144,7 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
|
||||
|
||||
auto viewJ = ctx.registry.journal("View");
|
||||
auto viewJ = ctx.registry.get().getJournal("View");
|
||||
|
||||
if (isGlobalFrozen(ctx.view, uPaysIssuerID) || isGlobalFrozen(ctx.view, uGetsIssuerID))
|
||||
{
|
||||
@@ -502,7 +502,7 @@ OfferCreate::applyHybrid(
|
||||
bookArr.push_back(std::move(bookInfo));
|
||||
|
||||
if (!bookExists)
|
||||
ctx_.registry.getOrderBookDB().addOrderBook(book);
|
||||
ctx_.registry.get().getOrderBookDB().addOrderBook(book);
|
||||
|
||||
sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
|
||||
return tesSUCCESS;
|
||||
@@ -536,7 +536,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// end up on the books.
|
||||
auto uRate = getRate(saTakerGets, saTakerPays);
|
||||
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
@@ -846,7 +846,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
sb.insert(sleOffer);
|
||||
|
||||
if (!bookExisted)
|
||||
ctx_.registry.getOrderBookDB().addOrderBook(book);
|
||||
ctx_.registry.get().getOrderBookDB().addOrderBook(book);
|
||||
|
||||
JLOG(j_.debug()) << "final result: success";
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
|
||||
|
||||
if (cb && fb)
|
||||
{
|
||||
auto& router = ctx.registry.getHashRouter();
|
||||
auto& router = ctx.registry.get().getHashRouter();
|
||||
|
||||
auto const id = ctx.tx.getTransactionID();
|
||||
auto const flags = router.getFlags(id);
|
||||
@@ -237,7 +237,7 @@ EscrowFinish::doApply()
|
||||
// Check cryptocondition fulfillment
|
||||
{
|
||||
auto const id = ctx_.tx.getTransactionID();
|
||||
auto flags = ctx_.registry.getHashRouter().getFlags(id);
|
||||
auto flags = ctx_.registry.get().getHashRouter().getFlags(id);
|
||||
|
||||
auto const cb = ctx_.tx[~sfCondition];
|
||||
|
||||
@@ -261,7 +261,7 @@ EscrowFinish::doApply()
|
||||
flags = SF_CF_INVALID;
|
||||
}
|
||||
|
||||
ctx_.registry.getHashRouter().setFlags(id, flags);
|
||||
ctx_.registry.get().getHashRouter().setFlags(id, flags);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ NFTokenAcceptOffer::transferNFToken(
|
||||
if (!tokenAndPage)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ret = nft::removeToken(view(), seller, nftokenID, std::move(tokenAndPage->page));
|
||||
if (auto const ret = nft::removeToken(view(), seller, nftokenID, tokenAndPage->page);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID)
|
||||
if (!page)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
return removeToken(view, owner, nftokenID, std::move(page));
|
||||
return removeToken(view, owner, nftokenID, page);
|
||||
}
|
||||
|
||||
/** Remove the token from the owner's token directory. */
|
||||
|
||||
@@ -236,7 +236,7 @@ OracleSet::doApply()
|
||||
}
|
||||
STArray updatedSeries;
|
||||
for (auto const& iter : pairs)
|
||||
updatedSeries.push_back(std::move(iter.second));
|
||||
updatedSeries.push_back(iter.second);
|
||||
sle->setFieldArray(sfPriceDataSeries, updatedSeries);
|
||||
if (ctx_.tx.isFieldPresent(sfURI))
|
||||
sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
|
||||
@@ -284,7 +284,7 @@ OracleSet::doApply()
|
||||
pairs.emplace(key, std::move(priceData));
|
||||
}
|
||||
for (auto const& iter : pairs)
|
||||
series.push_back(std::move(iter.second));
|
||||
series.push_back(iter.second);
|
||||
}
|
||||
|
||||
sle->setFieldArray(sfPriceDataSeries, series);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
@@ -435,7 +434,7 @@ Payment::doApply()
|
||||
account_,
|
||||
ctx_.tx.getFieldPathSet(sfPaths),
|
||||
ctx_.tx[~sfDomainID],
|
||||
ctx_.registry.logs(),
|
||||
ctx_.registry,
|
||||
&rcInput);
|
||||
// VFALCO NOTE We might not need to apply, depending
|
||||
// on the TER. But always applying *should*
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
@@ -109,7 +108,7 @@ PaymentChannelClaim::doApply()
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(curExpiration && closeTime >= *curExpiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
@@ -170,7 +169,7 @@ PaymentChannelClaim::doApply()
|
||||
{
|
||||
// Channel will close immediately if dry or the receiver closes
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
|
||||
auto const settleExpiration =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
|
||||
@@ -38,7 +38,7 @@ PaymentChannelFund::doApply()
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
|
||||
@@ -200,7 +200,7 @@ Change::applyAmendment()
|
||||
entry[sfAmendment] = amendment;
|
||||
entry[sfCloseTime] = view().parentCloseTime().time_since_epoch().count();
|
||||
|
||||
if (!ctx_.registry.getAmendmentTable().isSupported(amendment))
|
||||
if (!ctx_.registry.get().getAmendmentTable().isSupported(amendment))
|
||||
{
|
||||
JLOG(j_.warn()) << "Unsupported amendment " << amendment << " received a majority.";
|
||||
}
|
||||
@@ -211,13 +211,13 @@ Change::applyAmendment()
|
||||
amendments.push_back(amendment);
|
||||
amendmentObject->setFieldV256(sfAmendments, amendments);
|
||||
|
||||
ctx_.registry.getAmendmentTable().enable(amendment);
|
||||
ctx_.registry.get().getAmendmentTable().enable(amendment);
|
||||
|
||||
if (!ctx_.registry.getAmendmentTable().isSupported(amendment))
|
||||
if (!ctx_.registry.get().getAmendmentTable().isSupported(amendment))
|
||||
{
|
||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||
<< " activated: server blocked.";
|
||||
ctx_.registry.getOPs().setAmendmentBlocked();
|
||||
ctx_.registry.get().getOPs().setAmendmentBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user