mirror of
https://github.com/XRPLF/clio.git
synced 2026-01-12 10:45:23 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c35649eb6e | ||
|
|
f2f5a6ab19 | ||
|
|
1469d4b198 | ||
|
|
06ea05891d | ||
|
|
c7c270cc03 | ||
|
|
c1f2f5b100 | ||
|
|
bea0b51c8b | ||
|
|
69b8e5bd06 | ||
|
|
33dc4ad95a | ||
|
|
13cbb405c7 | ||
|
|
8a37a2e083 | ||
|
|
f8b6c98219 | ||
|
|
92883bf012 | ||
|
|
88881e95dd | ||
|
|
94e70e4026 | ||
|
|
b534570cdd | ||
|
|
56fbfc63c2 | ||
|
|
80978657c0 | ||
|
|
067449c3f8 | ||
|
|
946976546a | ||
|
|
73e90b0a3f | ||
|
|
7681c58a3a | ||
|
|
391e7b07ab | ||
|
|
4eadaa85fa | ||
|
|
1b1a46c429 | ||
|
|
89707d9668 | ||
|
|
ae260d1229 | ||
|
|
058c05cfb6 | ||
|
|
b2a7d185cb | ||
|
|
9ea61ba6b9 | ||
|
|
19157dec74 | ||
|
|
42a6f516dc | ||
|
|
2cd8226a11 | ||
|
|
4da4b49eda | ||
|
|
e3170203de | ||
|
|
8b280e7742 | ||
|
|
7ed30bc40d | ||
|
|
ac608004bc | ||
|
|
6ab92ca0a6 | ||
|
|
77387d8f9f |
@@ -52,7 +52,7 @@ runs:
|
||||
cache-image: false
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
- uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
id: meta
|
||||
with:
|
||||
images: ${{ inputs.images }}
|
||||
|
||||
2
.github/scripts/conan/generate_matrix.py
vendored
2
.github/scripts/conan/generate_matrix.py
vendored
@@ -4,7 +4,7 @@ import json
|
||||
|
||||
LINUX_OS = ["heavy", "heavy-arm64"]
|
||||
LINUX_CONTAINERS = [
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
]
|
||||
LINUX_COMPILERS = ["gcc", "clang"]
|
||||
|
||||
|
||||
4
.github/scripts/conan/init.sh
vendored
4
.github/scripts/conan/init.sh
vendored
@@ -40,9 +40,9 @@ mkdir -p "$PROFILES_DIR"
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
create_profile_with_sanitizers "apple-clang" "$APPLE_CLANG_PROFILE"
|
||||
echo "include(apple-clang)" > "$PROFILES_DIR/default"
|
||||
echo "include(apple-clang)" >"$PROFILES_DIR/default"
|
||||
else
|
||||
create_profile_with_sanitizers "clang" "$CLANG_PROFILE"
|
||||
create_profile_with_sanitizers "gcc" "$GCC_PROFILE"
|
||||
echo "include(gcc)" > "$PROFILES_DIR/default"
|
||||
echo "include(gcc)" >"$PROFILES_DIR/default"
|
||||
fi
|
||||
|
||||
18
.github/scripts/execute-tests-under-sanitizer.sh
vendored
18
.github/scripts/execute-tests-under-sanitizer.sh
vendored
@@ -22,8 +22,8 @@ fi
|
||||
TEST_BINARY=$1
|
||||
|
||||
if [[ ! -f "$TEST_BINARY" ]]; then
|
||||
echo "Test binary not found: $TEST_BINARY"
|
||||
exit 1
|
||||
echo "Test binary not found: $TEST_BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TESTS=$($TEST_BINARY --gtest_list_tests | awk '/^ / {print suite $1} !/^ / {suite=$1}')
|
||||
@@ -35,12 +35,12 @@ export TSAN_OPTIONS="die_after_fork=0"
|
||||
export MallocNanoZone='0' # for MacOSX
|
||||
|
||||
for TEST in $TESTS; do
|
||||
OUTPUT_FILE="$OUTPUT_DIR/${TEST//\//_}.log"
|
||||
$TEST_BINARY --gtest_filter="$TEST" > "$OUTPUT_FILE" 2>&1
|
||||
OUTPUT_FILE="$OUTPUT_DIR/${TEST//\//_}.log"
|
||||
$TEST_BINARY --gtest_filter="$TEST" >"$OUTPUT_FILE" 2>&1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "'$TEST' failed a sanitizer check."
|
||||
else
|
||||
rm "$OUTPUT_FILE"
|
||||
fi
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "'$TEST' failed a sanitizer check."
|
||||
else
|
||||
rm "$OUTPUT_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
2
.github/scripts/prepare-release-artifacts.sh
vendored
2
.github/scripts/prepare-release-artifacts.sh
vendored
@@ -20,5 +20,5 @@ for artifact_name in $(ls); do
|
||||
rm "${artifact_name}/${BINARY_NAME}"
|
||||
rm -r "${artifact_name}"
|
||||
|
||||
sha256sum "./${artifact_name}.zip" > "./${artifact_name}.zip.sha256sum"
|
||||
sha256sum "./${artifact_name}.zip" >"./${artifact_name}.zip.sha256sum"
|
||||
done
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Download Clio binary from artifact
|
||||
if: ${{ inputs.artifact_name != null }}
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
build_type: [Release, Debug]
|
||||
container:
|
||||
[
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }',
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }',
|
||||
]
|
||||
static: [true]
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
download_ccache: true
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: true
|
||||
@@ -115,10 +115,10 @@ jobs:
|
||||
needs: build-and-test
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
|
||||
8
.github/workflows/check-libxrpl.yml
vendored
8
.github/workflows/check-libxrpl.yml
vendored
@@ -21,10 +21,10 @@ jobs:
|
||||
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
needs: build
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Create an issue
|
||||
uses: ./.github/actions/create-issue
|
||||
|
||||
2
.github/workflows/check-pr-title.yml
vendored
2
.github/workflows/check-pr-title.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: ytanikin/pr-conventional-commits@b72758283dcbee706975950e96bc4bf323a8d8c0 # 1.4.2
|
||||
- uses: ytanikin/pr-conventional-commits@fda730cb152c05a849d6d84325e50c6182d9d1e9 # 1.5.1
|
||||
with:
|
||||
task_types: '["build","feat","fix","docs","test","ci","style","refactor","perf","chore"]'
|
||||
add_label: false
|
||||
|
||||
39
.github/workflows/clang-tidy.yml
vendored
39
.github/workflows/clang-tidy.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
if: github.event_name != 'push' || contains(github.event.head_commit.message, 'clang-tidy auto fixes')
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -62,27 +62,30 @@ jobs:
|
||||
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
|
||||
id: nproc
|
||||
|
||||
- name: Run clang-tidy
|
||||
- name: Run clang-tidy (several times)
|
||||
continue-on-error: true
|
||||
id: run_clang_tidy
|
||||
id: clang_tidy
|
||||
run: |
|
||||
run-clang-tidy-${{ env.LLVM_TOOLS_VERSION }} -p build -j "${{ steps.nproc.outputs.nproc }}" -fix -quiet 1>output.txt
|
||||
# We run clang-tidy several times, because some fixes may enable new fixes in subsequent runs.
|
||||
CLANG_TIDY_COMMAND="run-clang-tidy-${{ env.LLVM_TOOLS_VERSION }} -p build -j ${{ steps.nproc.outputs.nproc }} -fix -quiet"
|
||||
${CLANG_TIDY_COMMAND} ||
|
||||
${CLANG_TIDY_COMMAND} ||
|
||||
${CLANG_TIDY_COMMAND}
|
||||
|
||||
- name: Check for changes
|
||||
id: files_changed
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: Fix local includes and clang-format style
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
if: ${{ steps.files_changed.outcome != 'success' }}
|
||||
run: |
|
||||
pre-commit run --all-files fix-local-includes || true
|
||||
pre-commit run --all-files clang-format || true
|
||||
|
||||
- name: Print issues found
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
run: |
|
||||
sed -i '/error\||/!d' ./output.txt
|
||||
cat output.txt
|
||||
rm output.txt
|
||||
|
||||
- name: Create an issue
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
if: ${{ (steps.clang_tidy.outcome != 'success' || steps.files_changed.outcome != 'success') && github.event_name != 'pull_request' }}
|
||||
id: create_issue
|
||||
uses: ./.github/actions/create-issue
|
||||
env:
|
||||
@@ -95,7 +98,7 @@ jobs:
|
||||
List of the issues found: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/
|
||||
|
||||
- uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.ACTIONS_GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.ACTIONS_GPG_PASSPHRASE }}
|
||||
@@ -103,8 +106,8 @@ jobs:
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: Create PR with fixes
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
if: ${{ steps.files_changed.outcome != 'success' && github.event_name != 'pull_request' }}
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -119,5 +122,5 @@ jobs:
|
||||
reviewers: "godexsoft,kuznetsss,PeterChen13579,mathbunnyru"
|
||||
|
||||
- name: Fail the job
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
if: ${{ steps.clang_tidy.outcome != 'success' || steps.files_changed.outcome != 'success' }}
|
||||
run: exit 1
|
||||
|
||||
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -18,11 +18,11 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
|
||||
10
.github/workflows/nightly.yml
vendored
10
.github/workflows/nightly.yml
vendored
@@ -43,17 +43,17 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc.ubsan
|
||||
build_type: Release
|
||||
static: false
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
include:
|
||||
- os: heavy
|
||||
conan_profile: clang
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
static: true
|
||||
- os: macos15
|
||||
conan_profile: apple-clang
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Create an issue
|
||||
uses: ./.github/actions/create-issue
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@34790936fae4c6c751f62ec8c06696f9c1a5753a
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:c117f470f2ef954520ab5d1c8a5ed2b9e68d6f8a" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
|
||||
16
.github/workflows/reusable-build.yml
vendored
16
.github/workflows/reusable-build.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/.github/actions/cleanup-workspace@ea9970b7c211b18f4c8bcdb28c29f5711752029f
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# We need to fetch tags to have correct version in the release
|
||||
@@ -116,9 +116,8 @@ jobs:
|
||||
code_coverage: ${{ inputs.code_coverage }}
|
||||
|
||||
- name: Restore ccache cache
|
||||
if: ${{ inputs.download_ccache }}
|
||||
if: ${{ inputs.download_ccache && github.ref != 'refs/heads/develop' }}
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
id: restore_cache
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ steps.cache_key.outputs.key }}
|
||||
@@ -160,17 +159,14 @@ jobs:
|
||||
name: build_time_report_${{ runner.os }}_${{ inputs.build_type }}_${{ inputs.conan_profile }}
|
||||
path: build_time_report.txt
|
||||
|
||||
- name: Show ccache's statistics
|
||||
- name: Show ccache's statistics and zero it
|
||||
if: ${{ inputs.download_ccache }}
|
||||
id: ccache_stats
|
||||
run: |
|
||||
ccache -s > /tmp/ccache.stats
|
||||
miss_rate=$(cat /tmp/ccache.stats | grep 'Misses' | head -n1 | sed 's/.*(\(.*\)%).*/\1/')
|
||||
echo "miss_rate=${miss_rate}" >> $GITHUB_OUTPUT
|
||||
cat /tmp/ccache.stats
|
||||
ccache --show-stats
|
||||
ccache --zero-stats
|
||||
|
||||
- name: Save ccache cache
|
||||
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' && (steps.restore_cache.outputs.cache-hit != 'true' || steps.ccache_stats.outputs.miss_rate == '100.0') }}
|
||||
if: ${{ inputs.upload_ccache && github.ref == 'refs/heads/develop' }}
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
|
||||
4
.github/workflows/reusable-release.yml
vendored
4
.github/workflows/reusable-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
release:
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/reusable-test.yml
vendored
2
.github/workflows/reusable-test.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/.github/actions/cleanup-workspace@ea9970b7c211b18f4c8bcdb28c29f5711752029f
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
5
.github/workflows/sanitizers.yml
vendored
5
.github/workflows/sanitizers.yml
vendored
@@ -44,14 +44,13 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f" }'
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
build_type: ${{ matrix.build_type }}
|
||||
static: false
|
||||
# Currently, both gcc.tsan and clang.tsan unit tests hang
|
||||
run_unit_tests: ${{ matrix.sanitizer_ext != '.tsan' }}
|
||||
run_unit_tests: true
|
||||
run_integration_tests: false
|
||||
upload_clio_server: false
|
||||
targets: clio_tests clio_integration_tests
|
||||
|
||||
18
.github/workflows/update-docker-ci.yml
vendored
18
.github/workflows/update-docker-ci.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
needs: [repo, gcc-amd64, gcc-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
needs: repo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
needs: [repo, gcc-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
needs: [repo, tools-amd64, tools-arm64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
@@ -316,7 +316,7 @@ jobs:
|
||||
needs: [repo, tools-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: ./.github/actions/build-docker-image
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -338,7 +338,7 @@ jobs:
|
||||
needs: [repo, gcc-merge, clang, tools-merge]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: ./.github/actions/build-docker-image
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/upload-conan-deps.yml
vendored
4
.github/workflows/upload-conan-deps.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Calculate conan matrix
|
||||
id: set-matrix
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@8abb0722cbff83a9a2dc7d06c473f7a4964b7382
|
||||
|
||||
@@ -29,12 +29,12 @@ repos:
|
||||
|
||||
# Autoformat: YAML, JSON, Markdown, etc.
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2
|
||||
rev: 3c603eae8faac85303ae675fd33325cff699a797 # frozen: v3.7.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: 192ad822316c3a22fb3d3cc8aa6eafa0b8488360 # frozen: v0.45.0
|
||||
rev: c8fd5003603dd6f12447314ecd935ba87c09aff5 # frozen: v0.46.0
|
||||
hooks:
|
||||
- id: markdownlint-fix
|
||||
exclude: LICENSE.md
|
||||
@@ -58,6 +58,17 @@ repos:
|
||||
--ignore-words=pre-commit-hooks/codespell_ignore.txt,
|
||||
]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 2892f1f81088477370d4fbc56545c05d33d2493f # frozen: 25.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 2a30809d16bc7a60d9b97353c797f42b510d3368 # frozen: v3.12.0-2
|
||||
hooks:
|
||||
- id: shfmt
|
||||
args: ["-i", "4", "--write"]
|
||||
|
||||
# Running some C++ hooks before clang-format
|
||||
# to ensure that the style is consistent.
|
||||
- repo: local
|
||||
@@ -83,7 +94,7 @@ repos:
|
||||
language: script
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: 719856d56a62953b8d2839fb9e851f25c3cfeef8 # frozen: v21.1.2
|
||||
rev: 4c26f99731e7c22a047c35224150ee9e43d7c03e # frozen: v21.1.6
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
|
||||
@@ -75,11 +75,6 @@ if (san)
|
||||
endif ()
|
||||
target_compile_options(clio_options INTERFACE ${SAN_OPTIMIZATION_FLAG} ${SAN_FLAG} -fno-omit-frame-pointer)
|
||||
|
||||
if (san STREQUAL "address")
|
||||
# ASAN needs these definitions as well as correct b2 flags in conan profile for sanitizers
|
||||
target_compile_definitions(clio_options INTERFACE BOOST_USE_ASAN=1 BOOST_USE_UCONTEXT=1)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(clio_options INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ Existing maintainers can resign, or be subject to a vote for removal at the behe
|
||||
- [kuznetsss](https://github.com/kuznetsss) (Ripple)
|
||||
- [legleux](https://github.com/legleux) (Ripple)
|
||||
- [PeterChen13579](https://github.com/PeterChen13579) (Ripple)
|
||||
- [mathbunnyru](https://github.com/mathbunnyru) (Ripple)
|
||||
|
||||
### Honorable ex-Maintainers
|
||||
|
||||
|
||||
17
cmake/install/clio.service.in
Normal file
17
cmake/install/clio.service.in
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Clio XRPL API server
|
||||
Documentation=https://github.com/XRPLF/clio.git
|
||||
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=@CLIO_INSTALL_DIR@/bin/clio_server @CLIO_INSTALL_DIR@/etc/config.json
|
||||
Restart=on-failure
|
||||
User=clio
|
||||
Group=clio
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -11,3 +11,6 @@ file(READ docs/examples/config/example-config.json config)
|
||||
string(REGEX REPLACE "./clio_log" "/var/log/clio/" config "${config}")
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/install-config.json "${config}")
|
||||
install(FILES ${CMAKE_BINARY_DIR}/install-config.json DESTINATION etc RENAME config.json)
|
||||
|
||||
configure_file("${CMAKE_SOURCE_DIR}/cmake/install/clio.service.in" "${CMAKE_BINARY_DIR}/clio.service")
|
||||
install(FILES "${CMAKE_BINARY_DIR}/clio.service" DESTINATION /lib/systemd/system)
|
||||
|
||||
@@ -10,37 +10,36 @@ CLIO_BIN="$CLIO_PREFIX/bin/${CLIO_EXECUTABLE}"
|
||||
CLIO_CONFIG="$CLIO_PREFIX/etc/config.json"
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if ! id -u "$USER_NAME" >/dev/null 2>&1; then
|
||||
# Users who should not have a home directory should have their home directory set to /nonexistent
|
||||
# https://www.debian.org/doc/debian-policy/ch-opersys.html#non-existent-home-directories
|
||||
useradd \
|
||||
--system \
|
||||
--home-dir /nonexistent \
|
||||
--no-create-home \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "system user for ${CLIO_EXECUTABLE}" \
|
||||
--user-group \
|
||||
${USER_NAME}
|
||||
fi
|
||||
configure)
|
||||
if ! id -u "$USER_NAME" >/dev/null 2>&1; then
|
||||
# Users who should not have a home directory should have their home directory set to /nonexistent
|
||||
# https://www.debian.org/doc/debian-policy/ch-opersys.html#non-existent-home-directories
|
||||
useradd \
|
||||
--system \
|
||||
--home-dir /nonexistent \
|
||||
--no-create-home \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "system user for ${CLIO_EXECUTABLE}" \
|
||||
--user-group \
|
||||
${USER_NAME}
|
||||
fi
|
||||
|
||||
install -d -o "$USER_NAME" -g "$GROUP_NAME" /var/log/clio
|
||||
install -d -o "$USER_NAME" -g "$GROUP_NAME" /var/log/clio
|
||||
|
||||
if [ -f "$CLIO_CONFIG" ]; then
|
||||
chown "$USER_NAME:$GROUP_NAME" "$CLIO_CONFIG"
|
||||
fi
|
||||
if [ -f "$CLIO_CONFIG" ]; then
|
||||
chown "$USER_NAME:$GROUP_NAME" "$CLIO_CONFIG"
|
||||
fi
|
||||
|
||||
chown -R "$USER_NAME:$GROUP_NAME" "$CLIO_PREFIX"
|
||||
chown -R "$USER_NAME:$GROUP_NAME" "$CLIO_PREFIX"
|
||||
|
||||
ln -sf "$CLIO_BIN" "/usr/bin/${CLIO_EXECUTABLE}"
|
||||
ln -sf "$CLIO_BIN" "/usr/bin/${CLIO_EXECUTABLE}"
|
||||
|
||||
;;
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
;;
|
||||
abort-upgrade | abort-remove | abort-deconfigure) ;;
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
36
conan.lock
36
conan.lock
@@ -3,13 +3,13 @@
|
||||
"requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1756234289.683",
|
||||
"xrpl/3.0.0-rc1#f5c8ecd42bdf511ad36f57bc702dacd2%1762975621.294",
|
||||
"xrpl/3.0.0#534d3f65a336109eee929b88962bae4e%1765375071.547",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1756234266.869",
|
||||
"spdlog/1.15.3#3ca0e9e6b83af4d0151e26541d140c86%1754401846.61",
|
||||
"spdlog/1.16.0#942c2c39562ae25ba575d9c8e2bdf3b6%1763984117.108",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1756234262.318",
|
||||
"re2/20230301#dfd6e2bf050eb90ddd8729cfb4c844a4%1756234257.976",
|
||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1764175362.029",
|
||||
"rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1754325007.656",
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1756234251.614",
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||
"openssl/1.1.1w#a8f0792d7c5121b954578a7149d23e03%1756223730.729",
|
||||
"nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1763150366.909",
|
||||
"minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1754325004.374",
|
||||
@@ -17,41 +17,45 @@
|
||||
"libuv/1.46.0#dc28c1f653fa197f00db5b577a6f6011%1754325003.592",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1756223727.64",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1756230911.03",
|
||||
"libarchive/3.8.1#5cf685686322e906cb42706ab7e099a8%1756234256.696",
|
||||
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1764175360.142",
|
||||
"http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1754325001.385",
|
||||
"gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1754325000.842",
|
||||
"grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1756234248.958",
|
||||
"fmt/11.2.0#579bb2cdf4a7607621beea4eb4651e0f%1754324999.086",
|
||||
"fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1763984116.926",
|
||||
"doctest/2.4.11#a4211dfc329a16ba9f280f9574025659%1756234220.819",
|
||||
"date/3.0.4#f74bbba5a08fa388256688743136cb6f%1756234217.493",
|
||||
"cassandra-cpp-driver/2.17.0#e50919efac8418c26be6671fd702540a%1754324997.363",
|
||||
"c-ares/1.34.5#b78b91e7cfb1f11ce777a285bbf169c6%1756234217.915",
|
||||
"bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1756234261.716",
|
||||
"boost/1.83.0#5d975011d65b51abb2d2f6eb8386b368%1754325043.336",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1763584497.32",
|
||||
"cassandra-cpp-driver/2.17.0#bd3934138689482102c265d01288a316%1764175359.611",
|
||||
"c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1764175359.429",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1764175359.429",
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1%1764175359.61",
|
||||
"benchmark/1.9.4#ce4403f7a24d3e1f907cd9da4b678be4%1754578869.672",
|
||||
"abseil/20230802.1#f0f91485b111dc9837a68972cb19ca7b%1756234220.907"
|
||||
"abseil/20230802.1#90ba607d4ee8fb5fb157c3db540671fc%1764175359.429"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497",
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1756234251.614",
|
||||
"cmake/3.31.8#dde3bde00bb843687e55aea5afa0e220%1756234232.89",
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1764175361.456",
|
||||
"cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1764175359.44",
|
||||
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1764175359.429",
|
||||
"b2/5.3.3#107c15377719889654eb9a162a673975%1756234226.28"
|
||||
],
|
||||
"python_requires": [],
|
||||
"overrides": {
|
||||
"boost/1.83.0": [
|
||||
null,
|
||||
"boost/1.83.0#5d975011d65b51abb2d2f6eb8386b368"
|
||||
"boost/1.83.0#91d8b1572534d2c334d6790e3c34d0c1"
|
||||
],
|
||||
"protobuf/3.21.12": [
|
||||
null,
|
||||
"protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1"
|
||||
"protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88"
|
||||
],
|
||||
"lz4/1.9.4": [
|
||||
"lz4/1.10.0"
|
||||
],
|
||||
"sqlite3/3.44.2": [
|
||||
"sqlite3/3.49.1"
|
||||
],
|
||||
"fmt/12.0.0": [
|
||||
"fmt/12.1.0"
|
||||
]
|
||||
},
|
||||
"config_requires": []
|
||||
|
||||
74
conanfile.py
74
conanfile.py
@@ -3,62 +3,60 @@ from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
|
||||
|
||||
class ClioConan(ConanFile):
|
||||
name = 'clio'
|
||||
license = 'ISC'
|
||||
author = 'Alex Kremer <akremer@ripple.com>, John Freeman <jfreeman@ripple.com>, Ayaz Salikhov <asalikhov@ripple.com>'
|
||||
url = 'https://github.com/xrplf/clio'
|
||||
description = 'Clio RPC server'
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
name = "clio"
|
||||
license = "ISC"
|
||||
author = "Alex Kremer <akremer@ripple.com>, John Freeman <jfreeman@ripple.com>, Ayaz Salikhov <asalikhov@ripple.com>"
|
||||
url = "https://github.com/xrplf/clio"
|
||||
description = "Clio RPC server"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {}
|
||||
|
||||
requires = [
|
||||
'boost/1.83.0',
|
||||
'cassandra-cpp-driver/2.17.0',
|
||||
'fmt/11.2.0',
|
||||
'protobuf/3.21.12',
|
||||
'grpc/1.50.1',
|
||||
'openssl/1.1.1w',
|
||||
'xrpl/3.0.0-rc1',
|
||||
'zlib/1.3.1',
|
||||
'libbacktrace/cci.20210118',
|
||||
'spdlog/1.15.3',
|
||||
"boost/1.83.0",
|
||||
"cassandra-cpp-driver/2.17.0",
|
||||
"protobuf/3.21.12",
|
||||
"grpc/1.50.1",
|
||||
"openssl/1.1.1w",
|
||||
"xrpl/3.0.0",
|
||||
"zlib/1.3.1",
|
||||
"libbacktrace/cci.20210118",
|
||||
"spdlog/1.16.0",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
'xrpl/*:tests': False,
|
||||
'xrpl/*:rocksdb': False,
|
||||
'cassandra-cpp-driver/*:shared': False,
|
||||
'date/*:header_only': True,
|
||||
'grpc/*:shared': False,
|
||||
'grpc/*:secure': True,
|
||||
'libpq/*:shared': False,
|
||||
'lz4/*:shared': False,
|
||||
'openssl/*:shared': False,
|
||||
'protobuf/*:shared': False,
|
||||
'protobuf/*:with_zlib': True,
|
||||
'snappy/*:shared': False,
|
||||
'gtest/*:no_main': True,
|
||||
"xrpl/*:tests": False,
|
||||
"xrpl/*:rocksdb": False,
|
||||
"cassandra-cpp-driver/*:shared": False,
|
||||
"date/*:header_only": True,
|
||||
"grpc/*:shared": False,
|
||||
"grpc/*:secure": True,
|
||||
"libpq/*:shared": False,
|
||||
"lz4/*:shared": False,
|
||||
"openssl/*:shared": False,
|
||||
"protobuf/*:shared": False,
|
||||
"protobuf/*:with_zlib": True,
|
||||
"snappy/*:shared": False,
|
||||
"gtest/*:no_main": True,
|
||||
}
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt', 'cmake/*', 'src/*'
|
||||
)
|
||||
exports_sources = ("CMakeLists.txt", "cmake/*", "src/*")
|
||||
|
||||
def requirements(self):
|
||||
self.requires('gtest/1.14.0')
|
||||
self.requires('benchmark/1.9.4')
|
||||
self.requires("gtest/1.14.0")
|
||||
self.requires("benchmark/1.9.4")
|
||||
self.requires("fmt/12.1.0", force=True)
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost'].visibility = 'global'
|
||||
if self.settings.compiler == "apple-clang":
|
||||
self.options["boost"].visibility = "global"
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
# Fix this setting to follow the default introduced in Conan 1.48
|
||||
# to align with our build instructions.
|
||||
self.folders.generators = 'build/generators'
|
||||
self.folders.generators = "build/generators"
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
generators = "CMakeDeps"
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
|
||||
@@ -36,7 +36,6 @@ RUN apt-get update \
|
||||
libmpfr-dev \
|
||||
libncurses-dev \
|
||||
make \
|
||||
ninja-build \
|
||||
wget \
|
||||
zip \
|
||||
&& apt-get clean \
|
||||
@@ -107,6 +106,7 @@ COPY --from=clio-tools \
|
||||
/usr/local/bin/git-cliff \
|
||||
/usr/local/bin/gh \
|
||||
/usr/local/bin/gdb \
|
||||
/usr/local/bin/ninja \
|
||||
/usr/local/bin/
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
@@ -15,6 +15,7 @@ The image is based on Ubuntu 20.04 and contains:
|
||||
- gh 2.82.1
|
||||
- git-cliff 2.10.1
|
||||
- mold 2.40.4
|
||||
- Ninja 1.13.2
|
||||
- Python 3.8
|
||||
- and some other useful tools
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
{% set sanitizer_opt_map = {"asan": "address", "tsan": "thread", "ubsan": "undefined"} %}
|
||||
{% set sanitizer = sanitizer_opt_map[sani] %}
|
||||
|
||||
{% set sanitizer_b2_flags_map = {"address": "define=BOOST_USE_ASAN=1 context-impl=ucontext address-sanitizer=on", "thread": "thread-sanitizer=on", "undefined": "undefined-sanitizer=on"} %}
|
||||
{% set sanitizer_b2_flags_map = {
|
||||
"address": "context-impl=ucontext address-sanitizer=norecover",
|
||||
"thread": "context-impl=ucontext thread-sanitizer=norecover",
|
||||
"undefined": "undefined-sanitizer=norecover"
|
||||
} %}
|
||||
{% set sanitizer_b2_flags_str = sanitizer_b2_flags_map[sanitizer] %}
|
||||
|
||||
{% set sanitizer_build_flags_str = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %}
|
||||
@@ -24,4 +28,10 @@ tools.build:cxxflags+={{ sanitizer_build_flags }}
|
||||
tools.build:exelinkflags+={{ sanitizer_link_flags }}
|
||||
tools.build:sharedlinkflags+={{ sanitizer_link_flags }}
|
||||
|
||||
tools.info.package_id:confs+=["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"]
|
||||
{% if sanitizer == "address" %}
|
||||
tools.build:defines+=["BOOST_USE_ASAN", "BOOST_USE_UCONTEXT"]
|
||||
{% elif sanitizer == "thread" %}
|
||||
tools.build:defines+=["BOOST_USE_TSAN", "BOOST_USE_UCONTEXT"]
|
||||
{% endif %}
|
||||
|
||||
tools.info.package_id:confs+=["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clio_develop:
|
||||
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
image: ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
volumes:
|
||||
- clio_develop_conan_data:/root/.conan2/p
|
||||
- clio_develop_ccache:/root/.ccache
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
|
||||
pushd $script_dir > /dev/null
|
||||
pushd $script_dir >/dev/null
|
||||
|
||||
function start_container {
|
||||
if [ -z "$(docker ps -q -f name=clio_develop)" ]; then
|
||||
@@ -41,21 +41,26 @@ EOF
|
||||
}
|
||||
|
||||
case $1 in
|
||||
-h|--help)
|
||||
print_help ;;
|
||||
-h | --help)
|
||||
print_help
|
||||
;;
|
||||
|
||||
-t|--terminal)
|
||||
open_terminal ;;
|
||||
-t | --terminal)
|
||||
open_terminal
|
||||
;;
|
||||
|
||||
-s|--stop)
|
||||
stop_container ;;
|
||||
-s | --stop)
|
||||
stop_container
|
||||
;;
|
||||
|
||||
-*)
|
||||
echo "Unknown option: $1"
|
||||
print_help ;;
|
||||
-*)
|
||||
echo "Unknown option: $1"
|
||||
print_help
|
||||
;;
|
||||
|
||||
*)
|
||||
run "$@" ;;
|
||||
*)
|
||||
run "$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
popd > /dev/null
|
||||
popd >/dev/null
|
||||
|
||||
@@ -12,7 +12,6 @@ ARG BUILD_VERSION=0
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends --no-install-suggests \
|
||||
ninja-build \
|
||||
python3 \
|
||||
python3-pip \
|
||||
software-properties-common \
|
||||
@@ -24,6 +23,15 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
ARG NINJA_VERSION=1.13.2
|
||||
|
||||
RUN wget --progress=dot:giga "https://github.com/ninja-build/ninja/archive/refs/tags/v${NINJA_VERSION}.tar.gz" \
|
||||
&& tar xf "v${NINJA_VERSION}.tar.gz" \
|
||||
&& cd "ninja-${NINJA_VERSION}" \
|
||||
&& ./configure.py --bootstrap \
|
||||
&& mv ninja /usr/local/bin/ninja \
|
||||
&& rm -rf /tmp/* /var/tmp/*
|
||||
|
||||
ARG MOLD_VERSION=2.40.4
|
||||
RUN wget --progress=dot:giga "https://github.com/rui314/mold/archive/refs/tags/v${MOLD_VERSION}.tar.gz" \
|
||||
&& tar xf "v${MOLD_VERSION}.tar.gz" \
|
||||
|
||||
@@ -191,7 +191,7 @@ Open the `index.html` file in your browser to see the documentation pages.
|
||||
It is also possible to build Clio using [Docker](https://www.docker.com/) if you don't want to install all the dependencies on your machine.
|
||||
|
||||
```sh
|
||||
docker run -it ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
|
||||
docker run -it ghcr.io/xrplf/clio-ci:067449c3f8ae6755ea84752ea2962b589fe56c8f
|
||||
git clone https://github.com/XRPLF/clio
|
||||
cd clio
|
||||
```
|
||||
|
||||
@@ -391,7 +391,7 @@ This document provides a list of all available Clio configuration properties in
|
||||
- **Type**: double
|
||||
- **Default value**: `10`
|
||||
- **Constraints**: The value must be a positive double number.
|
||||
- **Description**: The number of milliseconds the server waits to shutdown gracefully. If Clio does not shutdown gracefully after the specified value, it will be killed instead.
|
||||
- **Description**: The number of seconds the server waits to shutdown gracefully. If Clio does not shutdown gracefully after the specified value, it will be killed instead.
|
||||
|
||||
### cache.num_diffs
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ if [[ "1.14.0" > "$version" ]]; then
|
||||
|
||||
ERROR
|
||||
-----------------------------------------------------------------------------
|
||||
A minimum of version 1.14 of `which doxygen` is required.
|
||||
A minimum of version 1.14 of $(which doxygen) is required.
|
||||
Your version is $version. Please upgrade it.
|
||||
|
||||
Your changes may fail CI checks.
|
||||
@@ -55,26 +55,26 @@ EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p ${DOCDIR} > /dev/null 2>&1
|
||||
pushd ${DOCDIR} > /dev/null 2>&1
|
||||
mkdir -p ${DOCDIR} >/dev/null 2>&1
|
||||
pushd ${DOCDIR} >/dev/null 2>&1
|
||||
|
||||
cat ${ROOT}/docs/Doxyfile | \
|
||||
sed \
|
||||
-e "s/\${LINT}/YES/" \
|
||||
-e "s/\${WARN_AS_ERROR}/NO/" \
|
||||
-e "s!\${SOURCE}!${ROOT}!" \
|
||||
-e "s/\${USE_DOT}/NO/" \
|
||||
-e "s/\${EXCLUDES}/impl/" \
|
||||
| ${DOXYGEN} - 2> ${TMPFILE} 1> /dev/null
|
||||
cat ${ROOT}/docs/Doxyfile |
|
||||
sed \
|
||||
-e "s/\${LINT}/YES/" \
|
||||
-e "s/\${WARN_AS_ERROR}/NO/" \
|
||||
-e "s!\${SOURCE}!${ROOT}!" \
|
||||
-e "s/\${USE_DOT}/NO/" \
|
||||
-e "s/\${EXCLUDES}/impl/" |
|
||||
${DOXYGEN} - 2>${TMPFILE} 1>/dev/null
|
||||
|
||||
# We don't want to check for default values and typedefs as well as for member variables
|
||||
OUT=$(cat ${TMPFILE} \
|
||||
| grep -v "=default" \
|
||||
| grep -v "\(variable\)" \
|
||||
| grep -v "\(typedef\)")
|
||||
OUT=$(cat ${TMPFILE} |
|
||||
grep -v "=default" |
|
||||
grep -v "\(variable\)" |
|
||||
grep -v "\(typedef\)")
|
||||
|
||||
rm -rf ${TMPFILE} > /dev/null 2>&1
|
||||
popd > /dev/null 2>&1
|
||||
rm -rf ${TMPFILE} >/dev/null 2>&1
|
||||
popd >/dev/null 2>&1
|
||||
|
||||
if [[ ! -z "$OUT" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
@@ -23,10 +23,10 @@ fix_includes() {
|
||||
file_path_fixed="${file_path}.tmp.fixed"
|
||||
|
||||
# Make all includes to be <...> style
|
||||
sed -E 's|#include "(.*)"|#include <\1>|g' "$file_path" > "$file_path_all_global"
|
||||
sed -E 's|#include "(.*)"|#include <\1>|g' "$file_path" >"$file_path_all_global"
|
||||
|
||||
# Make local includes to be "..." style
|
||||
sed -E "s|#include <(($main_src_dirs)/.*)>|#include \"\1\"|g" "$file_path_all_global" > "$file_path_fixed"
|
||||
sed -E "s|#include <(($main_src_dirs)/.*)>|#include \"\1\"|g" "$file_path_all_global" >"$file_path_fixed"
|
||||
rm "$file_path_all_global"
|
||||
|
||||
# Check if the temporary file is different from the original file
|
||||
|
||||
@@ -4,7 +4,6 @@ import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PATTERN = r'R"JSON\((.*?)\)JSON"'
|
||||
|
||||
|
||||
@@ -40,6 +39,7 @@ def fix_colon_spacing(cpp_content: str) -> str:
|
||||
raw_json = match.group(1)
|
||||
raw_json = re.sub(r'":\n\s*(\[|\{)', r'": \1', raw_json)
|
||||
return f'R"JSON({raw_json})JSON"'
|
||||
|
||||
return re.sub(PATTERN, replace_json, cpp_content, flags=re.DOTALL)
|
||||
|
||||
|
||||
@@ -49,12 +49,12 @@ def fix_indentation(cpp_content: str) -> str:
|
||||
|
||||
lines = cpp_content.splitlines()
|
||||
|
||||
ends_with_newline = cpp_content.endswith('\n')
|
||||
ends_with_newline = cpp_content.endswith("\n")
|
||||
|
||||
def find_indentation(line: str) -> int:
|
||||
return len(line) - len(line.lstrip())
|
||||
|
||||
for (line_num, (line, next_line)) in enumerate(zip(lines[:-1], lines[1:])):
|
||||
for line_num, (line, next_line) in enumerate(zip(lines[:-1], lines[1:])):
|
||||
if "JSON(" in line and ")JSON" not in line:
|
||||
indent = find_indentation(line)
|
||||
next_indent = find_indentation(next_line)
|
||||
@@ -69,7 +69,11 @@ def fix_indentation(cpp_content: str) -> str:
|
||||
if ")JSON" in lines[i]:
|
||||
lines[i] = " " * indent + lines[i].lstrip()
|
||||
break
|
||||
lines[i] = lines[i][by_how_much:] if by_how_much > 0 else " " * (-by_how_much) + lines[i]
|
||||
lines[i] = (
|
||||
lines[i][by_how_much:]
|
||||
if by_how_much > 0
|
||||
else " " * (-by_how_much) + lines[i]
|
||||
)
|
||||
|
||||
result = "\n".join(lines)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
set -e -o pipefail
|
||||
|
||||
if ! command -v gofmt &> /dev/null ; then
|
||||
if ! command -v gofmt &>/dev/null; then
|
||||
echo "gofmt not installed or available in the PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# git for-each-ref refs/tags # see which tags are annotated and which are lightweight. Annotated tags are "tag" objects.
|
||||
# # Set these so your commits and tags are always signed
|
||||
@@ -7,7 +6,7 @@
|
||||
# git config tag.gpgsign true
|
||||
|
||||
verify_commit_signed() {
|
||||
if git verify-commit HEAD &> /dev/null; then
|
||||
if git verify-commit HEAD &>/dev/null; then
|
||||
:
|
||||
# echo "HEAD commit seems signed..."
|
||||
else
|
||||
@@ -17,7 +16,7 @@ verify_commit_signed() {
|
||||
}
|
||||
|
||||
verify_tag() {
|
||||
if git describe --exact-match --tags HEAD &> /dev/null; then
|
||||
if git describe --exact-match --tags HEAD &>/dev/null; then
|
||||
: # You might be ok to push
|
||||
# echo "Tag is annotated."
|
||||
return 0
|
||||
@@ -28,7 +27,7 @@ verify_tag() {
|
||||
}
|
||||
|
||||
verify_tag_signed() {
|
||||
if git verify-tag "$version" &> /dev/null ; then
|
||||
if git verify-tag "$version" &>/dev/null; then
|
||||
: # ok, I guess we'll let you push
|
||||
# echo "Tag appears signed"
|
||||
return 0
|
||||
@@ -40,11 +39,11 @@ verify_tag_signed() {
|
||||
}
|
||||
|
||||
# Check some things if we're pushing a branch called "release/"
|
||||
if echo "$PRE_COMMIT_REMOTE_BRANCH" | grep ^refs\/heads\/release\/ &> /dev/null ; then
|
||||
if echo "$PRE_COMMIT_REMOTE_BRANCH" | grep ^refs\/heads\/release\/ &>/dev/null; then
|
||||
version=$(git tag --points-at HEAD)
|
||||
echo "Looks like you're trying to push a $version release..."
|
||||
echo "Making sure you've signed and tagged it."
|
||||
if verify_commit_signed && verify_tag && verify_tag_signed ; then
|
||||
if verify_commit_signed && verify_tag && verify_tag_signed; then
|
||||
: # Ok, I guess you can push
|
||||
else
|
||||
exit 1
|
||||
|
||||
@@ -91,6 +91,7 @@ ClioApplication::ClioApplication(util::config::ClioConfigDefinition const& confi
|
||||
{
|
||||
LOG(util::LogService::info()) << "Clio version: " << util::build::getClioFullVersionString();
|
||||
signalsHandler_.subscribeToStop([this]() { appStopper_.stop(); });
|
||||
appStopper_.setOnComplete([this]() { signalsHandler_.notifyGracefulShutdownComplete(); });
|
||||
}
|
||||
|
||||
int
|
||||
@@ -182,7 +183,7 @@ ClioApplication::run(bool const useNgWebServer)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
httpServer->onGet("/metrics", MetricsHandler{adminVerifier});
|
||||
httpServer->onGet("/metrics", MetricsHandler{adminVerifier, workQueue});
|
||||
httpServer->onGet("/health", HealthCheckHandler{});
|
||||
httpServer->onGet("/cache_state", CacheStateHandler{cache});
|
||||
auto requestHandler = RequestHandler{adminVerifier, handler};
|
||||
|
||||
@@ -38,7 +38,18 @@ Stopper::~Stopper()
|
||||
void
|
||||
Stopper::setOnStop(std::function<void(boost::asio::yield_context)> cb)
|
||||
{
|
||||
util::spawn(ctx_, std::move(cb));
|
||||
util::spawn(ctx_, [this, cb = std::move(cb)](auto yield) {
|
||||
cb(yield);
|
||||
|
||||
if (onCompleteCallback_)
|
||||
onCompleteCallback_();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
Stopper::setOnComplete(std::function<void()> cb)
|
||||
{
|
||||
onCompleteCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace app {
|
||||
class Stopper {
|
||||
boost::asio::io_context ctx_;
|
||||
std::thread worker_;
|
||||
std::function<void()> onCompleteCallback_;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -58,6 +59,14 @@ public:
|
||||
void
|
||||
setOnStop(std::function<void(boost::asio::yield_context)> cb);
|
||||
|
||||
/**
|
||||
* @brief Set the callback to be called when graceful shutdown completes.
|
||||
*
|
||||
* @param cb The callback to be called when shutdown completes.
|
||||
*/
|
||||
void
|
||||
setOnComplete(std::function<void()> cb);
|
||||
|
||||
/**
|
||||
* @brief Stop the application and run the shutdown tasks.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
|
||||
#include "app/WebHandlers.hpp"
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/WorkQueue.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/CoroutineGroup.hpp"
|
||||
#include "util/prometheus/Http.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
@@ -31,6 +34,7 @@
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -76,8 +80,8 @@ DisconnectHook::operator()(web::ng::Connection const& connection)
|
||||
dosguard_.get().decrement(connection.ip());
|
||||
}
|
||||
|
||||
MetricsHandler::MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier)
|
||||
: adminVerifier_{std::move(adminVerifier)}
|
||||
MetricsHandler::MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, rpc::WorkQueue& workQueue)
|
||||
: adminVerifier_{std::move(adminVerifier)}, workQueue_{std::ref(workQueue)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -86,19 +90,45 @@ MetricsHandler::operator()(
|
||||
web::ng::Request const& request,
|
||||
web::ng::ConnectionMetadata& connectionMetadata,
|
||||
web::SubscriptionContextPtr,
|
||||
boost::asio::yield_context
|
||||
boost::asio::yield_context yield
|
||||
)
|
||||
{
|
||||
auto const maybeHttpRequest = request.asHttpRequest();
|
||||
ASSERT(maybeHttpRequest.has_value(), "Got not a http request in Get");
|
||||
auto const& httpRequest = maybeHttpRequest->get();
|
||||
std::optional<web::ng::Response> response;
|
||||
util::CoroutineGroup coroutineGroup{yield, 1};
|
||||
auto const onTaskComplete = coroutineGroup.registerForeign(yield);
|
||||
ASSERT(onTaskComplete.has_value(), "Coroutine group can't be full");
|
||||
|
||||
// FIXME(#1702): Using veb server thread to handle prometheus request. Better to post on work queue.
|
||||
auto maybeResponse = util::prometheus::handlePrometheusRequest(
|
||||
httpRequest, adminVerifier_->isAdmin(httpRequest, connectionMetadata.ip())
|
||||
bool const postSuccessful = workQueue_.get().postCoro(
|
||||
[this, &request, &response, &onTaskComplete = onTaskComplete.value(), &connectionMetadata](
|
||||
boost::asio::yield_context
|
||||
) mutable {
|
||||
auto const maybeHttpRequest = request.asHttpRequest();
|
||||
ASSERT(maybeHttpRequest.has_value(), "Got not a http request in Get");
|
||||
auto const& httpRequest = maybeHttpRequest->get();
|
||||
|
||||
auto maybeResponse = util::prometheus::handlePrometheusRequest(
|
||||
httpRequest, adminVerifier_->isAdmin(httpRequest, connectionMetadata.ip())
|
||||
);
|
||||
ASSERT(maybeResponse.has_value(), "Got unexpected request for Prometheus");
|
||||
response = web::ng::Response{std::move(maybeResponse).value(), request};
|
||||
// notify the coroutine group that the foreign task is done
|
||||
onTaskComplete();
|
||||
},
|
||||
/* isWhiteListed= */ true,
|
||||
rpc::WorkQueue::Priority::High
|
||||
);
|
||||
ASSERT(maybeResponse.has_value(), "Got unexpected request for Prometheus");
|
||||
return web::ng::Response{std::move(maybeResponse).value(), request};
|
||||
|
||||
if (!postSuccessful) {
|
||||
return web::ng::Response{
|
||||
boost::beast::http::status::too_many_requests, rpc::makeError(rpc::RippledError::rpcTOO_BUSY), request
|
||||
};
|
||||
}
|
||||
|
||||
// Put the coroutine to sleep until the foreign task is done
|
||||
coroutineGroup.asyncWait(yield);
|
||||
ASSERT(response.has_value(), "Woke up coroutine without setting response");
|
||||
|
||||
return std::move(response).value();
|
||||
}
|
||||
|
||||
web::ng::Response
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/LedgerCacheInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/WorkQueue.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
#include "web/AdminVerificationStrategy.hpp"
|
||||
#include "web/SubscriptionContextInterface.hpp"
|
||||
@@ -119,20 +120,23 @@ public:
|
||||
*/
|
||||
class MetricsHandler {
|
||||
std::shared_ptr<web::AdminVerificationStrategy> adminVerifier_;
|
||||
std::reference_wrapper<rpc::WorkQueue> workQueue_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new MetricsHandler object
|
||||
*
|
||||
* @param adminVerifier The AdminVerificationStrategy to use for verifying the connection for admin access.
|
||||
* @param workQueue The WorkQueue to use for handling the request.
|
||||
*/
|
||||
MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier);
|
||||
MetricsHandler(std::shared_ptr<web::AdminVerificationStrategy> adminVerifier, rpc::WorkQueue& workQueue);
|
||||
|
||||
/**
|
||||
* @brief The call of the function object.
|
||||
*
|
||||
* @param request The request to handle.
|
||||
* @param connectionMetadata The connection metadata.
|
||||
* @param yield The yield context.
|
||||
* @return The response to the request.
|
||||
*/
|
||||
web::ng::Response
|
||||
@@ -140,7 +144,7 @@ public:
|
||||
web::ng::Request const& request,
|
||||
web::ng::ConnectionMetadata& connectionMetadata,
|
||||
web::SubscriptionContextPtr,
|
||||
boost::asio::yield_context
|
||||
boost::asio::yield_context yield
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ struct Amendments {
|
||||
REGISTER(fixDirectoryLimit);
|
||||
REGISTER(fixIncludeKeyletFields);
|
||||
REGISTER(fixTokenEscrowV1);
|
||||
REGISTER(LendingProtocol);
|
||||
|
||||
// Obsolete but supported by libxrpl
|
||||
REGISTER(CryptoConditionsSuite);
|
||||
|
||||
@@ -53,7 +53,7 @@ LedgerCacheSaver::save()
|
||||
success.has_value()) {
|
||||
LOG(util::LogService::info()) << "Successfully saved ledger cache in " << durationMs << " ms";
|
||||
} else {
|
||||
LOG(util::LogService::error()) << "Error saving LedgerCache to file";
|
||||
LOG(util::LogService::error()) << "Error saving LedgerCache to file: " << success.error();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ TaskManager::run(std::size_t numExtractors)
|
||||
util::async::AnyOperation<void>
|
||||
TaskManager::spawnExtractor(TaskQueue& queue)
|
||||
{
|
||||
// TODO: these values may be extracted to config later and/or need to be fine-tuned on a realistic system
|
||||
static constexpr auto kDELAY_BETWEEN_ATTEMPTS = std::chrono::milliseconds{100u};
|
||||
// TODO https://github.com/XRPLF/clio/issues/2838: the approach should be changed to a reactive one instead
|
||||
static constexpr auto kDELAY_BETWEEN_ATTEMPTS = std::chrono::milliseconds{10u};
|
||||
static constexpr auto kDELAY_BETWEEN_ENQUEUE_ATTEMPTS = std::chrono::milliseconds{1u};
|
||||
|
||||
return ctx_.execute([this, &queue](auto stopRequested) {
|
||||
|
||||
@@ -316,8 +316,11 @@ tag_invoke(boost::json::value_to_tag<AMMInfoHandler::Input>, boost::json::value
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(asset)))
|
||||
input.issue1 = parseIssue(jsonObject.at(JS(asset)).as_object());
|
||||
|
||||
@@ -154,8 +154,11 @@ tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(destination_account)))
|
||||
input.destinationAccount = boost::json::value_to<std::string>(jv.at(JS(destination_account)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -128,8 +128,11 @@ tag_invoke(boost::json::value_to_tag<AccountCurrenciesHandler::Input>, boost::js
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -204,8 +204,11 @@ tag_invoke(boost::json::value_to_tag<AccountInfoHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(signer_lists)))
|
||||
input.signerLists = boost::json::value_to<JsonBool>(jsonObject.at(JS(signer_lists)));
|
||||
|
||||
@@ -215,8 +215,11 @@ tag_invoke(boost::json::value_to_tag<AccountLinesHandler::Input>, boost::json::v
|
||||
if (jsonObject.contains(JS(ignore_default)))
|
||||
input.ignoreDefault = jv.at(JS(ignore_default)).as_bool();
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ AccountMPTokenIssuancesHandler::addMPTokenIssuance(
|
||||
{
|
||||
MPTokenIssuanceResponse issuance;
|
||||
|
||||
issuance.MPTokenIssuanceID = ripple::strHex(sle.key());
|
||||
issuance.issuer = ripple::to_string(account);
|
||||
issuance.sequence = sle.getFieldU32(ripple::sfSequence);
|
||||
auto const flags = sle.getFieldU32(ripple::sfFlags);
|
||||
@@ -73,6 +74,24 @@ AccountMPTokenIssuancesHandler::addMPTokenIssuance(
|
||||
setFlag(issuance.mptCanTransfer, ripple::lsfMPTCanTransfer);
|
||||
setFlag(issuance.mptCanClawback, ripple::lsfMPTCanClawback);
|
||||
|
||||
if (sle.isFieldPresent(ripple::sfMutableFlags)) {
|
||||
auto const mutableFlags = sle.getFieldU32(ripple::sfMutableFlags);
|
||||
|
||||
auto const setMutableFlag = [&](std::optional<bool>& field, std::uint32_t mask) {
|
||||
if ((mutableFlags & mask) != 0u)
|
||||
field = true;
|
||||
};
|
||||
|
||||
setMutableFlag(issuance.mptCanMutateCanLock, ripple::lsmfMPTCanMutateCanLock);
|
||||
setMutableFlag(issuance.mptCanMutateRequireAuth, ripple::lsmfMPTCanMutateRequireAuth);
|
||||
setMutableFlag(issuance.mptCanMutateCanEscrow, ripple::lsmfMPTCanMutateCanEscrow);
|
||||
setMutableFlag(issuance.mptCanMutateCanTrade, ripple::lsmfMPTCanMutateCanTrade);
|
||||
setMutableFlag(issuance.mptCanMutateCanTransfer, ripple::lsmfMPTCanMutateCanTransfer);
|
||||
setMutableFlag(issuance.mptCanMutateCanClawback, ripple::lsmfMPTCanMutateCanClawback);
|
||||
setMutableFlag(issuance.mptCanMutateMetadata, ripple::lsmfMPTCanMutateMetadata);
|
||||
setMutableFlag(issuance.mptCanMutateTransferFee, ripple::lsmfMPTCanMutateTransferFee);
|
||||
}
|
||||
|
||||
if (sle.isFieldPresent(ripple::sfTransferFee))
|
||||
issuance.transferFee = sle.getFieldU16(ripple::sfTransferFee);
|
||||
|
||||
@@ -164,8 +183,11 @@ tag_invoke(boost::json::value_to_tag<AccountMPTokenIssuancesHandler::Input>, boo
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
@@ -198,6 +220,7 @@ tag_invoke(
|
||||
)
|
||||
{
|
||||
auto obj = boost::json::object{
|
||||
{JS(mpt_issuance_id), issuance.MPTokenIssuanceID},
|
||||
{JS(issuer), issuance.issuer},
|
||||
{JS(sequence), issuance.sequence},
|
||||
};
|
||||
@@ -224,6 +247,15 @@ tag_invoke(
|
||||
setIfPresent("mpt_can_transfer", issuance.mptCanTransfer);
|
||||
setIfPresent("mpt_can_clawback", issuance.mptCanClawback);
|
||||
|
||||
setIfPresent("mpt_can_mutate_can_lock", issuance.mptCanMutateCanLock);
|
||||
setIfPresent("mpt_can_mutate_require_auth", issuance.mptCanMutateRequireAuth);
|
||||
setIfPresent("mpt_can_mutate_can_escrow", issuance.mptCanMutateCanEscrow);
|
||||
setIfPresent("mpt_can_mutate_can_trade", issuance.mptCanMutateCanTrade);
|
||||
setIfPresent("mpt_can_mutate_can_transfer", issuance.mptCanMutateCanTransfer);
|
||||
setIfPresent("mpt_can_mutate_can_clawback", issuance.mptCanMutateCanClawback);
|
||||
setIfPresent("mpt_can_mutate_metadata", issuance.mptCanMutateMetadata);
|
||||
setIfPresent("mpt_can_mutate_transfer_fee", issuance.mptCanMutateTransferFee);
|
||||
|
||||
jv = std::move(obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
* @brief A struct to hold data for one MPTokenIssuance response.
|
||||
*/
|
||||
struct MPTokenIssuanceResponse {
|
||||
std::string MPTokenIssuanceID;
|
||||
std::string issuer;
|
||||
uint32_t sequence{};
|
||||
|
||||
@@ -80,6 +81,15 @@ public:
|
||||
std::optional<bool> mptCanTrade;
|
||||
std::optional<bool> mptCanTransfer;
|
||||
std::optional<bool> mptCanClawback;
|
||||
|
||||
std::optional<bool> mptCanMutateCanLock;
|
||||
std::optional<bool> mptCanMutateRequireAuth;
|
||||
std::optional<bool> mptCanMutateCanEscrow;
|
||||
std::optional<bool> mptCanMutateCanTrade;
|
||||
std::optional<bool> mptCanMutateCanTransfer;
|
||||
std::optional<bool> mptCanMutateCanClawback;
|
||||
std::optional<bool> mptCanMutateMetadata;
|
||||
std::optional<bool> mptCanMutateTransferFee;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ AccountMPTokensHandler::addMPToken(std::vector<MPTokenResponse>& mpts, ripple::S
|
||||
MPTokenResponse token{};
|
||||
auto const flags = sle.getFieldU32(ripple::sfFlags);
|
||||
|
||||
token.MPTokenID = ripple::strHex(sle.key());
|
||||
token.account = ripple::to_string(sle.getAccountID(ripple::sfAccount));
|
||||
token.MPTokenIssuanceID = ripple::strHex(sle.getFieldH192(ripple::sfMPTokenIssuanceID));
|
||||
token.MPTAmount = sle.getFieldU64(ripple::sfMPTAmount);
|
||||
@@ -139,8 +140,11 @@ tag_invoke(boost::json::value_to_tag<AccountMPTokensHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
@@ -167,6 +171,7 @@ void
|
||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountMPTokensHandler::MPTokenResponse const& mptoken)
|
||||
{
|
||||
auto obj = boost::json::object{
|
||||
{"mpt_id", mptoken.MPTokenID},
|
||||
{JS(account), mptoken.account},
|
||||
{JS(mpt_issuance_id), mptoken.MPTokenIssuanceID},
|
||||
{JS(mpt_amount), mptoken.MPTAmount},
|
||||
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
* @brief A struct to hold data for one MPToken response.
|
||||
*/
|
||||
struct MPTokenResponse {
|
||||
std::string MPTokenID;
|
||||
std::string account;
|
||||
std::string MPTokenIssuanceID;
|
||||
uint64_t MPTAmount{};
|
||||
|
||||
@@ -157,8 +157,11 @@ tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(limit)))
|
||||
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
||||
|
||||
@@ -153,8 +153,11 @@ tag_invoke(boost::json::value_to_tag<AccountObjectsHandler::Input>, boost::json:
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(type))) {
|
||||
input.type =
|
||||
|
||||
@@ -169,8 +169,11 @@ tag_invoke(boost::json::value_to_tag<AccountOffersHandler::Input>, boost::json::
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(limit)))
|
||||
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
||||
|
||||
@@ -258,8 +258,10 @@ tag_invoke(boost::json::value_to_tag<AccountTxHandler::Input>, boost::json::valu
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (not input.ledgerIndex.has_value()) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value()) {
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
} else {
|
||||
// could not get the latest validated ledger seq here, using this flag to indicate that
|
||||
input.usingValidatedLedger = true;
|
||||
}
|
||||
|
||||
@@ -90,8 +90,11 @@ tag_invoke(boost::json::value_to_tag<BookChangesHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -122,8 +122,11 @@ tag_invoke(boost::json::value_to_tag<BookOffersHandler::Input>, boost::json::val
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(taker)))
|
||||
input.taker = accountFromStringStrict(boost::json::value_to<std::string>(jv.at(JS(taker))));
|
||||
|
||||
@@ -145,8 +145,11 @@ tag_invoke(boost::json::value_to_tag<DepositAuthorizedHandler::Input>, boost::js
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(credentials)))
|
||||
input.credentials = boost::json::value_to<boost::json::array>(jv.at(JS(credentials)));
|
||||
|
||||
@@ -168,8 +168,11 @@ tag_invoke(boost::json::value_to_tag<FeatureHandler::Input>, boost::json::value
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -249,8 +249,11 @@ tag_invoke(boost::json::value_to_tag<GatewayBalancesHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(hotwallet))) {
|
||||
if (jsonObject.at(JS(hotwallet)).is_string()) {
|
||||
|
||||
@@ -263,8 +263,11 @@ tag_invoke(boost::json::value_to_tag<GetAggregatePriceHandler::Input>, boost::js
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
for (auto const& oracle : jsonObject.at(JS(oracles)).as_array()) {
|
||||
input.oracles.push_back(
|
||||
|
||||
@@ -208,8 +208,11 @@ tag_invoke(boost::json::value_to_tag<LedgerHandler::Input>, boost::json::value c
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(transactions)))
|
||||
input.transactions = jv.at(JS(transactions)).as_bool();
|
||||
|
||||
@@ -210,8 +210,11 @@ tag_invoke(boost::json::value_to_tag<LedgerDataHandler::Input>, boost::json::val
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(type)))
|
||||
input.type = util::LedgerTypes::getLedgerEntryTypeFromStr(boost::json::value_to<std::string>(jv.at(JS(type))));
|
||||
|
||||
@@ -305,8 +305,11 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(binary)))
|
||||
input.binary = jv.at(JS(binary)).as_bool();
|
||||
|
||||
@@ -124,8 +124,11 @@ tag_invoke(boost::json::value_to_tag<MPTHoldersHandler::Input>, boost::json::val
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str();
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(limit)))
|
||||
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
||||
|
||||
@@ -215,8 +215,11 @@ tag_invoke(boost::json::value_to_tag<NFTHistoryHandler::Input>, boost::json::val
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(binary)))
|
||||
input.binary = jsonObject.at(JS(binary)).as_bool();
|
||||
|
||||
@@ -115,8 +115,11 @@ tag_invoke(boost::json::value_to_tag<NFTInfoHandler::Input>, boost::json::value
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -194,8 +194,11 @@ tag_invoke(boost::json::value_to_tag<NFTOffersHandlerBase::Input>, boost::json::
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(marker)))
|
||||
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
|
||||
|
||||
@@ -136,8 +136,11 @@ tag_invoke(boost::json::value_to_tag<NFTsByIssuerHandler::Input>, boost::json::v
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(limit)))
|
||||
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
|
||||
|
||||
@@ -196,8 +196,11 @@ tag_invoke(boost::json::value_to_tag<NoRippleCheckHandler::Input>, boost::json::
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jsonObject.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -109,8 +109,11 @@ tag_invoke(boost::json::value_to_tag<TransactionEntryHandler::Input>, boost::jso
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -177,8 +177,11 @@ tag_invoke(boost::json::value_to_tag<VaultInfoHandler::Input>, boost::json::valu
|
||||
if (jsonObject.contains(JS(vault_id)))
|
||||
input.vaultID = jsonObject.at(JS(vault_id)).as_string();
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index)))
|
||||
input.ledgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index)));
|
||||
if (expectedLedgerIndex.has_value())
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@
|
||||
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <concepts>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -96,12 +99,11 @@ removeSecret(boost::json::object const& object)
|
||||
*
|
||||
* @tparam Type The type to cast to
|
||||
* @param value The JSON value to cast
|
||||
* @return Value casted to the requested type
|
||||
* @throws logic_error if the underlying number is neither int64 nor uint64
|
||||
* @return Value casted to the requested type or an error message
|
||||
*/
|
||||
template <std::integral Type>
|
||||
Type
|
||||
integralValueAs(boost::json::value const& value)
|
||||
std::expected<Type, std::string>
|
||||
tryIntegralValueAs(boost::json::value const& value)
|
||||
{
|
||||
if (value.is_uint64())
|
||||
return static_cast<Type>(value.as_uint64());
|
||||
@@ -109,29 +111,49 @@ integralValueAs(boost::json::value const& value)
|
||||
if (value.is_int64())
|
||||
return static_cast<Type>(value.as_int64());
|
||||
|
||||
throw std::logic_error("Value neither uint64 nor int64");
|
||||
return std::unexpected("Value neither uint64 nor int64");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Detects the type of number stored in value and casts it back to the requested Type.
|
||||
* @note This conversion can possibly cause wrapping around or UB. Use with caution.
|
||||
*
|
||||
* @tparam Type The type to cast to
|
||||
* @param value The JSON value to cast
|
||||
* @return Value casted to the requested type
|
||||
* @throws logic_error if the underlying number is neither int64 nor uint64
|
||||
*/
|
||||
template <std::integral Type>
|
||||
Type
|
||||
integralValueAs(boost::json::value const& value)
|
||||
{
|
||||
auto expectedResult = tryIntegralValueAs<Type>(value);
|
||||
if (expectedResult.has_value())
|
||||
return *expectedResult;
|
||||
|
||||
throw std::logic_error(std::move(expectedResult).error());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extracts ledger index from a JSON value which can be either a number or a string.
|
||||
*
|
||||
* @param value The JSON value to extract ledger index from
|
||||
* @return An optional containing the ledger index if it is a number; std::nullopt otherwise
|
||||
* @throws logic_error comes from integralValueAs if the underlying number is neither int64 nor uint64
|
||||
* @throws std::invalid_argument or std::out_of_range if the string cannot be converted to a number
|
||||
* @return The extracted ledger index or an error message
|
||||
*/
|
||||
[[nodiscard]] inline std::optional<uint32_t>
|
||||
[[nodiscard]] inline std::expected<uint32_t, std::string>
|
||||
getLedgerIndex(boost::json::value const& value)
|
||||
{
|
||||
std::optional<uint32_t> ledgerIndex;
|
||||
|
||||
if (not value.is_string()) {
|
||||
ledgerIndex = util::integralValueAs<uint32_t>(value);
|
||||
} else if (value.as_string() != "validated") {
|
||||
ledgerIndex = std::stoi(value.as_string().c_str());
|
||||
return tryIntegralValueAs<uint32_t>(value);
|
||||
}
|
||||
|
||||
return ledgerIndex;
|
||||
if (value.as_string() != "validated") {
|
||||
uint32_t ledgerIndex{};
|
||||
if (beast::lexicalCastChecked(ledgerIndex, value.as_string().c_str())) {
|
||||
return ledgerIndex;
|
||||
}
|
||||
return std::unexpected("Invalid ledger index string");
|
||||
}
|
||||
return std::unexpected("'validated' ledger index is requested");
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
426
src/util/ObservableValue.hpp
Normal file
426
src/util/ObservableValue.hpp
Normal file
@@ -0,0 +1,426 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/signals2/connection.hpp>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <boost/signals2/variadic_signal.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
concept SomeAtomic = std::same_as<std::remove_cvref_t<T>, std::atomic<std::remove_cvref_t<typename T::value_type>>>;
|
||||
|
||||
/**
|
||||
* @brief Concept defining types that can be observed for changes.
|
||||
*
|
||||
* A type is Observable if it satisfies all requirements for being stored
|
||||
* and monitored in an ObservableValue container:
|
||||
*
|
||||
* - Must be equality comparable to detect changes
|
||||
* - Must be copy constructible for capturing old values in guards
|
||||
* - Must be move constructible for efficient value updates
|
||||
*
|
||||
* @note Copy assignment is intentionally not required since we use move semantics
|
||||
* for value updates and only need copy construction for change detection.
|
||||
*/
|
||||
template <typename T>
|
||||
concept Observable = std::equality_comparable<T> && std::copy_constructible<T> && std::move_constructible<T>;
|
||||
|
||||
namespace impl {
|
||||
|
||||
/**
|
||||
* @brief Base class containing common ObservableValue functionality.
|
||||
*
|
||||
* This class contains all the observer management and notification logic
|
||||
* that is shared between regular and atomic ObservableValue specializations.
|
||||
*
|
||||
* @tparam T The value type (for atomic specializations, this is the underlying type, not std::atomic<T>)
|
||||
*/
|
||||
template <Observable T>
|
||||
class ObservableValueBase {
|
||||
protected:
|
||||
boost::signals2::signal<void(T const&)> onUpdate_;
|
||||
|
||||
public:
|
||||
virtual ~ObservableValueBase() = default;
|
||||
|
||||
/**
|
||||
* @brief Registers an observer callback for value changes.
|
||||
* @param fn Callback function/lambda that accepts T const&
|
||||
* @return Connection object for managing the subscription
|
||||
*/
|
||||
boost::signals2::connection
|
||||
observe(std::invocable<T const&> auto&& fn)
|
||||
{
|
||||
return onUpdate_.connect(std::forward<decltype(fn)>(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are any active observers.
|
||||
* @return true if there are observers, false otherwise
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
hasObservers() const
|
||||
{
|
||||
return not onUpdate_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forces notification of all observers with the current value.
|
||||
*
|
||||
* This method will notify all observers with the current value regardless
|
||||
* of whether the value has changed since the last notification.
|
||||
*/
|
||||
virtual void
|
||||
forceNotify() = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Notifies all observers with the given value.
|
||||
* @param value The value to send to observers
|
||||
*/
|
||||
void
|
||||
notifyObservers(T const& value)
|
||||
{
|
||||
onUpdate_(value);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
// Forward declaration
|
||||
template <typename T>
|
||||
class ObservableValue;
|
||||
|
||||
/**
|
||||
* @brief An observable value container that notifies observers when the value changes.
|
||||
*
|
||||
* ObservableValue wraps a value of type T and provides a mechanism to observe changes to that value.
|
||||
* When the value is modified (and actually changes), all registered observers are notified.
|
||||
*
|
||||
* @tparam T The type of value to observe. Must satisfy the Observable concept.
|
||||
*
|
||||
* @par Thread Safety
|
||||
* - Observer subscription/unsubscription (observe() and connection.disconnect()) are thread-safe
|
||||
* - Value modification operations (set(), operator=) are NOT thread-safe and require external synchronization
|
||||
* - Observer callbacks are invoked synchronously on the same thread that triggered the value change
|
||||
* - If observers need to perform work on different threads, they must handle dispatch themselves
|
||||
* (e.g., using an async execution context or message queue)
|
||||
*
|
||||
* @par Exception Handling
|
||||
* - If an observer callback throws an exception, the exception will propagate to the caller
|
||||
* - The value will still be updated even if observers throw exceptions
|
||||
* - No guarantee is made about whether other observers will be called if one throws
|
||||
* - It is the caller's responsibility to handle exceptions from observer callbacks
|
||||
*/
|
||||
template <Observable T>
|
||||
requires(not SomeAtomic<T>)
|
||||
class ObservableValue<T> : public impl::ObservableValueBase<T> {
|
||||
T value_;
|
||||
|
||||
/**
|
||||
* @brief RAII guard for deferred notification of value changes.
|
||||
*
|
||||
* ObservableGuard captures the current value when created and compares it
|
||||
* with the final value when destroyed. If the values differ, observers
|
||||
* are notified. This allows for multiple modifications to the value with
|
||||
* only a single notification at the end.
|
||||
*
|
||||
* @note This class is returned by operator->() and should not be used directly.
|
||||
*/
|
||||
struct ObservableGuard {
|
||||
T const oldValue; ///< Value captured at construction time
|
||||
ObservableValue<T>& ref; ///< Reference to the observable value
|
||||
|
||||
/**
|
||||
* @brief Constructs guard and captures current value.
|
||||
* @param observable The ObservableValue to guard
|
||||
*/
|
||||
ObservableGuard(ObservableValue<T>& observable) : oldValue(observable), ref(observable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor that triggers notification if value changed.
|
||||
*
|
||||
* Compares the captured value with the current value. If they differ,
|
||||
* notifies all observers with the current value.
|
||||
*/
|
||||
~ObservableGuard()
|
||||
{
|
||||
if (oldValue != ref.value_)
|
||||
ref.notifyObservers(ref.value_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides mutable access to the underlying value.
|
||||
* @return Mutable reference to the wrapped value
|
||||
*/
|
||||
[[nodiscard]]
|
||||
operator T&()
|
||||
{
|
||||
return ref.value_;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs ObservableValue with initial value.
|
||||
* @param value Initial value (must be convertible to T)
|
||||
*/
|
||||
ObservableValue(std::convertible_to<T> auto&& value) : value_{std::forward<decltype(value)>(value)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs ObservableValue with default initial value.
|
||||
*/
|
||||
ObservableValue()
|
||||
requires std::default_initializable<T>
|
||||
: value_{}
|
||||
{
|
||||
}
|
||||
|
||||
ObservableValue(ObservableValue const&) = delete;
|
||||
ObservableValue(ObservableValue&&) = default;
|
||||
ObservableValue&
|
||||
operator=(ObservableValue const&) = delete;
|
||||
ObservableValue&
|
||||
operator=(ObservableValue&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Assignment operator that updates value and notifies observers.
|
||||
*
|
||||
* Updates the stored value and notifies observers if the new value
|
||||
* differs from the current value (using operator!=).
|
||||
*
|
||||
* @param val New value (must be convertible to T)
|
||||
* @return Reference to this object for chaining
|
||||
*
|
||||
* @throws Any exception thrown by observer callbacks will propagate
|
||||
*/
|
||||
ObservableValue&
|
||||
operator=(std::convertible_to<T> auto&& val)
|
||||
{
|
||||
set(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides deferred notification access to the value.
|
||||
*
|
||||
* Returns an ObservableGuard that allows modification of the value
|
||||
* with notification deferred until the guard is destroyed.
|
||||
*
|
||||
* @return ObservableGuard for deferred notification
|
||||
*/
|
||||
[[nodiscard]] ObservableGuard
|
||||
operator->()
|
||||
{
|
||||
return {*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Implicit conversion to const reference of the value.
|
||||
* @return Const reference to the stored value
|
||||
*/
|
||||
[[nodiscard]]
|
||||
operator T const&() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Explicitly gets the current value.
|
||||
* @return Const reference to the stored value
|
||||
*/
|
||||
[[nodiscard]] T const&
|
||||
get() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a new value and notifies observers if changed.
|
||||
*
|
||||
* Updates the stored value and notifies all observers if the new value
|
||||
* differs from the current value (using operator!=). If the values are
|
||||
* equal, no notification occurs.
|
||||
*
|
||||
* @param val New value (must be convertible to T)
|
||||
*
|
||||
* @throws Any exception thrown by observer callbacks will propagate
|
||||
*
|
||||
* @par Thread Safety
|
||||
* - This method is NOT thread-safe and requires external synchronization for concurrent access
|
||||
* - Observer callbacks are invoked synchronously on the calling thread
|
||||
*/
|
||||
void
|
||||
set(std::convertible_to<T> auto&& val)
|
||||
{
|
||||
if (value_ != val) {
|
||||
value_ = std::forward<decltype(val)>(val);
|
||||
this->notifyObservers(value_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forces notification of all observers with the current value.
|
||||
*
|
||||
* This method will notify all observers with the current value regardless
|
||||
* of whether the value has changed since the last notification.
|
||||
*/
|
||||
void
|
||||
forceNotify() override
|
||||
{
|
||||
this->notifyObservers(value_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Partial specialization of ObservableValue for atomic types.
|
||||
*
|
||||
* This specialization provides thread-safe observation of atomic values while
|
||||
* maintaining atomic semantics. It avoids the issues of copying atomic values
|
||||
* and handles race conditions properly.
|
||||
*
|
||||
* @tparam T The underlying type stored in the atomic
|
||||
*
|
||||
* @par Thread Safety
|
||||
* - All operations are thread-safe
|
||||
* - Observer notifications are atomic with respect to value changes
|
||||
* - Multiple threads can safely modify and observe the atomic value
|
||||
*
|
||||
* @par Performance Considerations
|
||||
* - Uses atomic compare-and-swap operations for updates
|
||||
* - Minimizes atomic reads during guard operations
|
||||
* - Observer notifications happen outside of atomic operations when possible
|
||||
*/
|
||||
template <Observable T>
|
||||
class ObservableValue<std::atomic<T>> : public impl::ObservableValueBase<T> {
|
||||
std::atomic<T> value_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs ObservableValue with initial atomic value.
|
||||
* @param value Initial value (will be stored in the atomic)
|
||||
*/
|
||||
ObservableValue(std::convertible_to<T> auto&& value) : value_{std::forward<decltype(value)>(value)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs ObservableValue with default initial value.
|
||||
*/
|
||||
ObservableValue()
|
||||
requires std::default_initializable<T>
|
||||
: value_{}
|
||||
{
|
||||
}
|
||||
|
||||
ObservableValue(ObservableValue const&) = delete;
|
||||
ObservableValue(ObservableValue&&) = default;
|
||||
ObservableValue&
|
||||
operator=(ObservableValue const&) = delete;
|
||||
ObservableValue&
|
||||
operator=(ObservableValue&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Assignment operator that updates atomic value and notifies observers.
|
||||
*
|
||||
* Uses atomic compare-and-swap to update the value and notifies observers
|
||||
* only if the value actually changed.
|
||||
*
|
||||
* @param val New value
|
||||
* @return Reference to this object for chaining
|
||||
*/
|
||||
ObservableValue&
|
||||
operator=(std::convertible_to<T> auto&& val)
|
||||
{
|
||||
set(std::forward<decltype(val)>(val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the current atomic value.
|
||||
* @return Current value stored in the atomic
|
||||
*/
|
||||
[[nodiscard]] T
|
||||
get() const
|
||||
{
|
||||
return value_.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Implicit conversion to the current atomic value.
|
||||
* @return Current value stored in the atomic
|
||||
*/
|
||||
[[nodiscard]]
|
||||
operator T() const
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a new atomic value and notifies observers if changed.
|
||||
*
|
||||
* Uses atomic compare-and-swap to update the value. Notifies all observers
|
||||
* if the value actually changed.
|
||||
*
|
||||
* @param val New value
|
||||
*/
|
||||
void
|
||||
set(std::convertible_to<T> auto&& val)
|
||||
{
|
||||
T newValue = std::forward<decltype(val)>(val);
|
||||
T oldValue = value_.load();
|
||||
|
||||
// Use compare-and-swap to atomically update
|
||||
while (!value_.compare_exchange_weak(oldValue, newValue)) {
|
||||
// compare_exchange_weak updates oldValue with current value on failure
|
||||
// Continue until we succeed
|
||||
}
|
||||
|
||||
// Notify observers if we actually changed the value
|
||||
// Note: oldValue now contains the actual previous value that was replaced
|
||||
if (oldValue != newValue) {
|
||||
this->notifyObservers(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forces notification of all observers with the current value.
|
||||
*
|
||||
* This method will notify all observers with the current atomic value
|
||||
* regardless of whether the value has changed since the last notification.
|
||||
*/
|
||||
void
|
||||
forceNotify() override
|
||||
{
|
||||
this->notifyObservers(value_.load());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "util/Repeat.hpp"
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
namespace util {
|
||||
|
||||
void
|
||||
@@ -27,8 +29,11 @@ Repeat::stop()
|
||||
if (control_->stopping)
|
||||
return;
|
||||
|
||||
control_->stopping = true;
|
||||
control_->timer.cancel();
|
||||
boost::asio::post(control_->strand, [control = control_] {
|
||||
control->stopping = true;
|
||||
control->timer.cancel();
|
||||
});
|
||||
|
||||
control_->semaphore.acquire();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
|
||||
#include "util/Assert.hpp"
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -41,10 +43,11 @@ namespace util {
|
||||
class Repeat {
|
||||
struct Control {
|
||||
boost::asio::steady_timer timer;
|
||||
boost::asio::strand<boost::asio::any_io_executor> strand;
|
||||
std::atomic_bool stopping{true};
|
||||
std::binary_semaphore semaphore{0};
|
||||
|
||||
Control(auto& ctx) : timer(ctx)
|
||||
Control(auto& ctx) : timer(ctx), strand(boost::asio::make_strand(ctx))
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -98,15 +101,24 @@ private:
|
||||
static void
|
||||
startImpl(std::shared_ptr<Control> control, std::chrono::steady_clock::duration interval, Action&& action)
|
||||
{
|
||||
control->timer.expires_after(interval);
|
||||
control->timer.async_wait([control, interval, action = std::forward<Action>(action)](auto const& ec) mutable {
|
||||
if (ec or control->stopping) {
|
||||
boost::asio::post(control->strand, [control, interval, action = std::forward<Action>(action)]() mutable {
|
||||
if (control->stopping) {
|
||||
control->semaphore.release();
|
||||
return;
|
||||
}
|
||||
action();
|
||||
|
||||
startImpl(std::move(control), interval, std::forward<Action>(action));
|
||||
control->timer.expires_after(interval);
|
||||
control->timer.async_wait(
|
||||
[control, interval, action = std::forward<Action>(action)](auto const& ec) mutable {
|
||||
if (ec or control->stopping) {
|
||||
control->semaphore.release();
|
||||
return;
|
||||
}
|
||||
action();
|
||||
|
||||
startImpl(std::move(control), interval, std::forward<Action>(action));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,10 +23,13 @@
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <csignal>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace util {
|
||||
@@ -50,17 +53,11 @@ public:
|
||||
}
|
||||
|
||||
static void
|
||||
handleSignal(int signal)
|
||||
handleSignal(int /* signal */)
|
||||
{
|
||||
ASSERT(installedHandler != nullptr, "SignalsHandler is not initialized");
|
||||
installedHandler->stopHandler_(signal);
|
||||
}
|
||||
|
||||
static void
|
||||
handleSecondSignal(int signal)
|
||||
{
|
||||
ASSERT(installedHandler != nullptr, "SignalsHandler is not initialized");
|
||||
installedHandler->secondSignalHandler_(signal);
|
||||
installedHandler->signalReceived_ = true;
|
||||
installedHandler->cv_.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,56 +66,109 @@ SignalsHandler* SignalsHandlerStatic::installedHandler = nullptr;
|
||||
} // namespace impl
|
||||
|
||||
SignalsHandler::SignalsHandler(config::ClioConfigDefinition const& config, std::function<void()> forceExitHandler)
|
||||
: gracefulPeriod_(0)
|
||||
, context_(1)
|
||||
, stopHandler_([this, forceExitHandler](int) mutable {
|
||||
LOG(LogService::info()) << "Got stop signal. Stopping Clio. Graceful period is "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(gracefulPeriod_).count()
|
||||
<< " milliseconds.";
|
||||
setHandler(impl::SignalsHandlerStatic::handleSecondSignal);
|
||||
timer_.emplace(context_.scheduleAfter(
|
||||
gracefulPeriod_, [forceExitHandler = std::move(forceExitHandler)](auto&& stopToken, bool canceled) {
|
||||
// TODO: Update this after https://github.com/XRPLF/clio/issues/1380
|
||||
if (not stopToken.isStopRequested() and not canceled) {
|
||||
LOG(LogService::warn()) << "Force exit at the end of graceful period.";
|
||||
forceExitHandler();
|
||||
}
|
||||
}
|
||||
));
|
||||
stopSignal_();
|
||||
})
|
||||
, secondSignalHandler_([this, forceExitHandler = std::move(forceExitHandler)](int) {
|
||||
LOG(LogService::warn()) << "Force exit on second signal.";
|
||||
forceExitHandler();
|
||||
cancelTimer();
|
||||
setHandler();
|
||||
})
|
||||
: gracefulPeriod_(util::config::ClioConfigDefinition::toMilliseconds(config.get<float>("graceful_period")))
|
||||
, forceExitHandler_(std::move(forceExitHandler))
|
||||
{
|
||||
impl::SignalsHandlerStatic::registerHandler(*this);
|
||||
|
||||
gracefulPeriod_ = util::config::ClioConfigDefinition::toMilliseconds(config.get<float>("graceful_period"));
|
||||
workerThread_ = std::thread([this]() { runStateMachine(); });
|
||||
setHandler(impl::SignalsHandlerStatic::handleSignal);
|
||||
}
|
||||
|
||||
SignalsHandler::~SignalsHandler()
|
||||
{
|
||||
cancelTimer();
|
||||
setHandler();
|
||||
|
||||
state_ = State::NormalExit;
|
||||
cv_.notify_one();
|
||||
|
||||
if (workerThread_.joinable())
|
||||
workerThread_.join();
|
||||
|
||||
impl::SignalsHandlerStatic::resetHandler(); // This is needed mostly for tests to reset static state
|
||||
}
|
||||
|
||||
void
|
||||
SignalsHandler::cancelTimer()
|
||||
SignalsHandler::notifyGracefulShutdownComplete()
|
||||
{
|
||||
if (timer_.has_value())
|
||||
timer_->abort();
|
||||
if (state_ == State::GracefulShutdown) {
|
||||
LOG(LogService::info()) << "Graceful shutdown completed successfully.";
|
||||
state_ = State::NormalExit;
|
||||
cv_.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SignalsHandler::setHandler(void (*handler)(int))
|
||||
{
|
||||
for (int const signal : kHANDLED_SIGNALS) {
|
||||
for (int const signal : kHANDLED_SIGNALS)
|
||||
std::signal(signal, handler == nullptr ? SIG_DFL : handler);
|
||||
}
|
||||
|
||||
void
|
||||
SignalsHandler::runStateMachine()
|
||||
{
|
||||
while (state_ != State::NormalExit) {
|
||||
auto currentState = state_.load();
|
||||
|
||||
switch (currentState) {
|
||||
case State::WaitingForSignal: {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cv_.wait(lock, [this]() { return signalReceived_ or state_ == State::NormalExit; });
|
||||
}
|
||||
|
||||
if (state_ == State::NormalExit)
|
||||
return;
|
||||
|
||||
LOG(
|
||||
LogService::info()
|
||||
) << "Got stop signal. Stopping Clio. Graceful period is "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(gracefulPeriod_).count() << " milliseconds.";
|
||||
|
||||
state_ = State::GracefulShutdown;
|
||||
signalReceived_ = false;
|
||||
|
||||
stopSignal_();
|
||||
break;
|
||||
}
|
||||
|
||||
case State::GracefulShutdown: {
|
||||
bool waitResult = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
// Wait for either:
|
||||
// 1. Graceful period to elapse (timeout)
|
||||
// 2. Another signal (signalReceived_)
|
||||
// 3. Graceful shutdown completion (state changes to NormalExit)
|
||||
waitResult = cv_.wait_for(lock, gracefulPeriod_, [this]() {
|
||||
return signalReceived_ or state_ == State::NormalExit;
|
||||
});
|
||||
}
|
||||
|
||||
if (state_ == State::NormalExit)
|
||||
break;
|
||||
|
||||
if (signalReceived_) {
|
||||
LOG(LogService::warn()) << "Force exit on second signal.";
|
||||
state_ = State::ForceExit;
|
||||
signalReceived_ = false;
|
||||
} else if (not waitResult) {
|
||||
LOG(LogService::warn()) << "Force exit at the end of graceful period.";
|
||||
state_ = State::ForceExit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case State::ForceExit: {
|
||||
forceExitHandler_();
|
||||
state_ = State::NormalExit;
|
||||
break;
|
||||
}
|
||||
|
||||
case State::NormalExit:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,22 +19,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/async/context/BasicExecutionContext.hpp"
|
||||
#include "util/config/ConfigDefinition.hpp"
|
||||
#include "util/log/Logger.hpp"
|
||||
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <boost/signals2/variadic_signal.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <condition_variable>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace util {
|
||||
namespace impl {
|
||||
@@ -48,13 +46,22 @@ class SignalsHandlerStatic;
|
||||
* @note There could be only one instance of this class.
|
||||
*/
|
||||
class SignalsHandler {
|
||||
/**
|
||||
* @brief States of the signal handler state machine.
|
||||
*/
|
||||
enum class State { WaitingForSignal, GracefulShutdown, ForceExit, NormalExit };
|
||||
|
||||
std::chrono::steady_clock::duration gracefulPeriod_;
|
||||
async::PoolExecutionContext context_;
|
||||
std::optional<async::PoolExecutionContext::ScheduledOperation<void>> timer_;
|
||||
std::function<void()> forceExitHandler_;
|
||||
|
||||
boost::signals2::signal<void()> stopSignal_;
|
||||
std::function<void(int)> stopHandler_;
|
||||
std::function<void(int)> secondSignalHandler_;
|
||||
|
||||
std::atomic<bool> signalReceived_{false};
|
||||
std::atomic<State> state_{State::WaitingForSignal};
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
std::thread workerThread_;
|
||||
|
||||
friend class impl::SignalsHandlerStatic;
|
||||
|
||||
@@ -101,15 +108,16 @@ public:
|
||||
stopSignal_.connect(static_cast<int>(priority), std::forward<SomeCallback>(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify the signal handler that graceful shutdown has completed.
|
||||
* This allows the handler to transition to NormalExit state.
|
||||
*/
|
||||
void
|
||||
notifyGracefulShutdownComplete();
|
||||
|
||||
static constexpr auto kHANDLED_SIGNALS = {SIGINT, SIGTERM};
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Cancel scheduled force exit if any.
|
||||
*/
|
||||
void
|
||||
cancelTimer();
|
||||
|
||||
/**
|
||||
* @brief Set signal handler for handled signals.
|
||||
*
|
||||
@@ -118,6 +126,12 @@ private:
|
||||
static void
|
||||
setHandler(void (*handler)(int) = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Run the state machine loop in a worker thread.
|
||||
*/
|
||||
void
|
||||
runStateMachine();
|
||||
|
||||
static constexpr auto kDEFAULT_FORCE_EXIT_HANDLER = []() { std::exit(EXIT_FAILURE); };
|
||||
};
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ This document provides a list of all available Clio configuration properties in
|
||||
.value = "The number of worker threads or processes that are responsible for managing and processing "
|
||||
"subscription-based tasks from `rippled`."},
|
||||
KV{.key = "graceful_period",
|
||||
.value = "The number of milliseconds the server waits to shutdown gracefully. If Clio does not shutdown "
|
||||
.value = "The number of seconds the server waits to shutdown gracefully. If Clio does not shutdown "
|
||||
"gracefully after the specified value, it will be killed instead."},
|
||||
KV{.key = "cache.num_diffs",
|
||||
.value = "The number of cursors generated is the number of changed (without counting deleted) objects in "
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/tls1.h>
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
using SslTcpStreamData = SslStreamData<boost::beast::ssl_stream<boost::beast::tcp_stream>>;
|
||||
using SslTcpStreamData = SslStreamData<boost::asio::ssl::stream<boost::beast::tcp_stream>>;
|
||||
using SslWsStreamData =
|
||||
SslStreamData<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::tcp_stream>>>;
|
||||
SslStreamData<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::beast::tcp_stream>>>;
|
||||
|
||||
} // namespace util::requests::impl
|
||||
|
||||
@@ -124,6 +124,6 @@ public:
|
||||
|
||||
using PlainWsConnection = WsConnectionImpl<boost::beast::websocket::stream<boost::beast::tcp_stream>>;
|
||||
using SslWsConnection =
|
||||
WsConnectionImpl<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::tcp_stream>>>;
|
||||
WsConnectionImpl<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::beast::tcp_stream>>>;
|
||||
|
||||
} // namespace util::requests::impl
|
||||
|
||||
@@ -61,7 +61,7 @@ using tcp = boost::asio::ip::tcp;
|
||||
template <SomeServerHandler HandlerType>
|
||||
class SslHttpSession : public impl::HttpBase<SslHttpSession, HandlerType>,
|
||||
public std::enable_shared_from_this<SslHttpSession<HandlerType>> {
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> stream_;
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream> stream_;
|
||||
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
|
||||
std::uint32_t maxWsSendingQueueSize_;
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
~SslHttpSession() override = default;
|
||||
|
||||
/** @return The SSL stream. */
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream>&
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream>&
|
||||
stream()
|
||||
{
|
||||
return stream_;
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace web {
|
||||
*/
|
||||
template <SomeServerHandler HandlerType>
|
||||
class SslWsSession : public impl::WsBase<SslWsSession, HandlerType> {
|
||||
using StreamType = boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::tcp_stream>>;
|
||||
using StreamType = boost::beast::websocket::stream<boost::asio::ssl::stream<boost::beast::tcp_stream>>;
|
||||
StreamType ws_;
|
||||
|
||||
public:
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
* @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
|
||||
*/
|
||||
explicit SslWsSession(
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream,
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream>&& stream,
|
||||
std::string ip,
|
||||
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
|
||||
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
|
||||
@@ -107,7 +107,7 @@ template <SomeServerHandler HandlerType>
|
||||
class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader<HandlerType>> {
|
||||
using std::enable_shared_from_this<SslWsUpgrader<HandlerType>>::shared_from_this;
|
||||
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> https_;
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream> https_;
|
||||
boost::optional<http::request_parser<http::string_body>> parser_;
|
||||
boost::beast::flat_buffer buffer_;
|
||||
std::string ip_;
|
||||
@@ -133,7 +133,7 @@ public:
|
||||
* @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
|
||||
*/
|
||||
SslWsUpgrader(
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream> stream,
|
||||
std::string ip,
|
||||
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
|
||||
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/error.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/ssl/stream_base.hpp>
|
||||
#include <boost/asio/ssl/verify_context.hpp>
|
||||
#include <boost/asio/ssl/verify_mode.hpp>
|
||||
@@ -40,7 +41,6 @@
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/beast/http/write.hpp> // IWYU pragma: keep
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/tls1.h>
|
||||
@@ -148,7 +148,7 @@ HttpsSyncClient::syncPost(std::string const& host, std::string const& port, std:
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
|
||||
tcp::resolver resolver(ioc);
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> stream(ioc, ctx);
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream> stream(ioc, ctx);
|
||||
|
||||
// We can't fix this so have to ignore
|
||||
#pragma GCC diagnostic push
|
||||
|
||||
@@ -1452,7 +1452,8 @@ createMptIssuanceObject(
|
||||
std::optional<std::uint8_t> assetScale,
|
||||
std::optional<std::uint64_t> maxAmount,
|
||||
std::optional<std::uint64_t> lockedAmount,
|
||||
std::optional<std::string_view> domainId
|
||||
std::optional<std::string_view> domainId,
|
||||
std::optional<std::uint32_t> mutableFlags
|
||||
)
|
||||
{
|
||||
ripple::STObject mptIssuance(ripple::sfLedgerEntry);
|
||||
@@ -1479,6 +1480,8 @@ createMptIssuanceObject(
|
||||
}
|
||||
if (domainId.has_value())
|
||||
mptIssuance.setFieldH256(ripple::sfDomainID, ripple::uint256{*domainId});
|
||||
if (mutableFlags.has_value())
|
||||
mptIssuance.setFieldU32(ripple::sfMutableFlags, *mutableFlags);
|
||||
|
||||
return mptIssuance;
|
||||
}
|
||||
@@ -1789,7 +1792,7 @@ createVault(
|
||||
vault[ripple::sfShareMPTID] = shareMPTID;
|
||||
vault.setFieldNumber(ripple::sfAssetsTotal, ripple::STNumber{ripple::sfAssetsTotal, 300});
|
||||
vault.setFieldNumber(ripple::sfAssetsAvailable, ripple::STNumber{ripple::sfAssetsAvailable, 300});
|
||||
vault.setFieldNumber(ripple::sfLossUnrealized, ripple::STNumber{ripple::sfLossUnrealized, 0});
|
||||
vault.setFieldNumber(ripple::sfLossUnrealized, ripple::STNumber{ripple::sfLossUnrealized, 1});
|
||||
vault.setFieldU8(ripple::sfWithdrawalPolicy, 200);
|
||||
|
||||
vault.setFieldU32(ripple::sfFlags, 0);
|
||||
|
||||
@@ -461,7 +461,8 @@ createMptIssuanceObject(
|
||||
std::optional<std::uint8_t> assetScale = std::nullopt,
|
||||
std::optional<std::uint64_t> maxAmount = std::nullopt,
|
||||
std::optional<std::uint64_t> lockedAmount = std::nullopt,
|
||||
std::optional<std::string_view> domainId = std::nullopt
|
||||
std::optional<std::string_view> domainId = std::nullopt,
|
||||
std::optional<std::uint32_t> mutableFlags = std::nullopt
|
||||
);
|
||||
|
||||
[[nodiscard]] ripple::STObject
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
|
||||
class WebServerSslSyncClient {
|
||||
boost::asio::io_context ioc_;
|
||||
std::optional<boost::beast::websocket::stream<boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> ws_;
|
||||
std::optional<boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>> ws_;
|
||||
|
||||
public:
|
||||
void
|
||||
|
||||
@@ -169,6 +169,8 @@ target_sources(
|
||||
util/BytesConverterTests.cpp
|
||||
util/CoroutineTest.cpp
|
||||
util/MoveTrackerTests.cpp
|
||||
util/ObservableValueTest.cpp
|
||||
util/ObservableValueAtomicTest.cpp
|
||||
util/RandomTests.cpp
|
||||
util/RepeatTests.cpp
|
||||
util/ResponseExpirationCacheTests.cpp
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "util/JsonUtils.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
@@ -26,7 +27,8 @@
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
TEST(JsonUtils, RemoveSecrets)
|
||||
{
|
||||
@@ -90,28 +92,123 @@ TEST(JsonUtils, integralValueAs)
|
||||
EXPECT_THROW(util::integralValueAs<int>(stringJson), std::logic_error);
|
||||
}
|
||||
|
||||
TEST(JsonUtils, getLedgerIndex)
|
||||
TEST(JsonUtils, tryIntegralValueAs)
|
||||
{
|
||||
auto const emptyJson = boost::json::value();
|
||||
EXPECT_THROW(std::ignore = util::getLedgerIndex(emptyJson), std::logic_error);
|
||||
auto const expectedResultUint64 = static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) + 1u;
|
||||
auto const uint64Json = boost::json::value(expectedResultUint64);
|
||||
|
||||
auto const boolJson = boost::json::value(true);
|
||||
EXPECT_THROW(std::ignore = util::getLedgerIndex(emptyJson), std::logic_error);
|
||||
auto const expectedResultInt64 = static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1u;
|
||||
auto const int64Json = boost::json::value(expectedResultInt64);
|
||||
|
||||
auto const numberJson = boost::json::value(12345);
|
||||
auto ledgerIndex = util::getLedgerIndex(numberJson);
|
||||
EXPECT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(ledgerIndex.value(), 12345u);
|
||||
auto checkHasValue = [&](boost::json::value const& jv, auto const& expectedValue) {
|
||||
using T = std::remove_cvref_t<decltype(expectedValue)>;
|
||||
auto const res = util::tryIntegralValueAs<T>(jv);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_EQ(res.value(), expectedValue);
|
||||
};
|
||||
|
||||
auto const validStringJson = boost::json::value("12345");
|
||||
ledgerIndex = util::getLedgerIndex(validStringJson);
|
||||
EXPECT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(ledgerIndex.value(), 12345u);
|
||||
auto checkError = [&](boost::json::value const& jv) {
|
||||
auto res = util::tryIntegralValueAs<int>(jv);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
EXPECT_EQ(res.error(), "Value neither uint64 nor int64");
|
||||
};
|
||||
|
||||
auto const invalidStringJson = boost::json::value("invalid123");
|
||||
EXPECT_THROW(std::ignore = util::getLedgerIndex(invalidStringJson), std::invalid_argument);
|
||||
// checks for uint64Json
|
||||
checkHasValue(uint64Json, std::numeric_limits<int32_t>::min());
|
||||
checkHasValue(uint64Json, static_cast<uint32_t>(expectedResultUint64));
|
||||
checkHasValue(uint64Json, static_cast<int64_t>(expectedResultUint64));
|
||||
checkHasValue(uint64Json, expectedResultUint64);
|
||||
|
||||
auto const validatedJson = boost::json::value("validated");
|
||||
ledgerIndex = util::getLedgerIndex(validatedJson);
|
||||
EXPECT_FALSE(ledgerIndex.has_value());
|
||||
// checks for int64Json
|
||||
checkHasValue(int64Json, std::numeric_limits<int32_t>::min());
|
||||
checkHasValue(int64Json, static_cast<uint32_t>(expectedResultInt64));
|
||||
checkHasValue(int64Json, expectedResultInt64);
|
||||
checkHasValue(int64Json, static_cast<uint64_t>(expectedResultInt64));
|
||||
|
||||
// non-integral inputs
|
||||
checkError(boost::json::value());
|
||||
checkError(boost::json::value(false));
|
||||
checkError(boost::json::value(3.14));
|
||||
checkError(boost::json::value("not a number"));
|
||||
}
|
||||
|
||||
struct GetLedgerIndexParameterTestBundle {
|
||||
std::string testName;
|
||||
boost::json::value jv;
|
||||
std::expected<uint32_t, std::string> expectedResult;
|
||||
};
|
||||
|
||||
// parameterized test cases for parameters check
|
||||
struct GetLedgerIndexParameterTest : ::testing::TestWithParam<GetLedgerIndexParameterTestBundle> {};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
JsonUtils,
|
||||
GetLedgerIndexParameterTest,
|
||||
testing::Values(
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "EmptyValue",
|
||||
.jv = boost::json::value(),
|
||||
.expectedResult = std::unexpected{"Value neither uint64 nor int64"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "BoolValue",
|
||||
.jv = boost::json::value(false),
|
||||
.expectedResult = std::unexpected{"Value neither uint64 nor int64"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "NumberValue",
|
||||
.jv = boost::json::value(123),
|
||||
.expectedResult = 123u
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringNumberValue",
|
||||
.jv = boost::json::value("123"),
|
||||
.expectedResult = 123u
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringNumberWithPlusSignValue",
|
||||
.jv = boost::json::value("+123"),
|
||||
.expectedResult = 123u
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringEmptyValue",
|
||||
.jv = boost::json::value(""),
|
||||
.expectedResult = std::unexpected{"Invalid ledger index string"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringWithLeadingCharsValue",
|
||||
.jv = boost::json::value("123invalid"),
|
||||
.expectedResult = std::unexpected{"Invalid ledger index string"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringWithTrailingCharsValue",
|
||||
.jv = boost::json::value("invalid123"),
|
||||
.expectedResult = std::unexpected{"Invalid ledger index string"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "StringWithLeadingAndTrailingCharsValue",
|
||||
.jv = boost::json::value("123invalid123"),
|
||||
.expectedResult = std::unexpected{"Invalid ledger index string"}
|
||||
},
|
||||
GetLedgerIndexParameterTestBundle{
|
||||
.testName = "ValidatedStringValue",
|
||||
.jv = boost::json::value("validated"),
|
||||
.expectedResult = std::unexpected{"'validated' ledger index is requested"}
|
||||
}
|
||||
),
|
||||
tests::util::kNAME_GENERATOR
|
||||
);
|
||||
|
||||
TEST_P(GetLedgerIndexParameterTest, getLedgerIndexParams)
|
||||
{
|
||||
auto const& testBundle = GetParam();
|
||||
auto const ledgerIndex = util::getLedgerIndex(testBundle.jv);
|
||||
|
||||
if (testBundle.expectedResult.has_value()) {
|
||||
EXPECT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(ledgerIndex.value(), testBundle.expectedResult.value());
|
||||
} else {
|
||||
EXPECT_FALSE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(ledgerIndex.error(), testBundle.expectedResult.error());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ struct StopperTest : virtual public ::testing::Test {
|
||||
protected:
|
||||
// Order here is important, stopper_ should die before mockCallback_, otherwise UB
|
||||
testing::StrictMock<testing::MockFunction<void(boost::asio::yield_context)>> mockCallback_;
|
||||
testing::StrictMock<testing::MockFunction<void()>> mockCompleteCallback_;
|
||||
Stopper stopper_;
|
||||
};
|
||||
|
||||
@@ -60,6 +61,22 @@ TEST_F(StopperTest, stopCalledMultipleTimes)
|
||||
stopper_.stop();
|
||||
}
|
||||
|
||||
TEST_F(StopperTest, stopCallsCompletionCallback)
|
||||
{
|
||||
stopper_.setOnStop(mockCallback_.AsStdFunction());
|
||||
stopper_.setOnComplete(mockCompleteCallback_.AsStdFunction());
|
||||
EXPECT_CALL(mockCallback_, Call);
|
||||
EXPECT_CALL(mockCompleteCallback_, Call);
|
||||
stopper_.stop();
|
||||
}
|
||||
|
||||
TEST_F(StopperTest, stopWithoutCompletionCallback)
|
||||
{
|
||||
stopper_.setOnStop(mockCallback_.AsStdFunction());
|
||||
EXPECT_CALL(mockCallback_, Call);
|
||||
stopper_.stop();
|
||||
}
|
||||
|
||||
struct StopperMakeCallbackTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
|
||||
struct ServerMock : web::ServerTag {
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "app/WebHandlers.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/WorkQueue.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/MockLedgerCache.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
@@ -122,7 +123,9 @@ struct MetricsHandlerTests : util::prometheus::WithPrometheus, SyncAsioContextTe
|
||||
std::make_shared<testing::StrictMock<AdminVerificationStrategyMock>>()
|
||||
};
|
||||
|
||||
MetricsHandler metricsHandler{adminVerifier};
|
||||
rpc::WorkQueue workQueue{1};
|
||||
|
||||
MetricsHandler metricsHandler{adminVerifier, workQueue};
|
||||
web::ng::Request request{http::request<http::string_body>{http::verb::get, "/metrics", 11}};
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user