Compare commits

..

10 Commits

Author SHA1 Message Date
github-actions[bot]
9ea61ba6b9 style: clang-tidy auto fixes (#2801)
Co-authored-by: mathbunnyru <12270691+mathbunnyru@users.noreply.github.com>
2025-11-21 11:27:40 +00:00
github-actions[bot]
19157dec74 style: clang-tidy auto fixes (#2799)
Co-authored-by: mathbunnyru <12270691+mathbunnyru@users.noreply.github.com>
2025-11-21 11:02:07 +00:00
github-actions[bot]
42a6f516dc style: clang-tidy auto fixes (#2797) 2025-11-21 10:17:56 +00:00
emrearıyürek
2cd8226a11 refactor: Make getLedgerIndex return std::expected instead of throwing (#2788)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Sergey Kuznetsov <kuzzz99@gmail.com>
2025-11-20 17:46:15 +00:00
Sergey Kuznetsov
e3170203de fix: Print cache saving error (#2794) 2025-11-20 14:48:42 +00:00
Ayaz Salikhov
8b280e7742 ci: Always upload cache on develop (#2793) 2025-11-19 20:42:18 +00:00
Ayaz Salikhov
7ed30bc40d ci: Don't download ccache on develop branch (#2792) 2025-11-19 19:32:12 +00:00
Sergey Kuznetsov
ac608004bc docs: Fix graceful_period description (#2791) 2025-11-19 19:17:44 +00:00
Alex Kremer
6ab92ca0a6 chore: Enable TSAN in CI (#2785) 2025-11-19 18:06:33 +00:00
Alex Kremer
77387d8f9f chore: Add defines for asan/tsan to conan profile (#2784)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2025-11-19 17:28:10 +00:00
47 changed files with 332 additions and 130 deletions

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
]
LINUX_COMPILERS = ["gcc", "clang"]

View File

@@ -49,7 +49,7 @@ jobs:
build_type: [Release, Debug]
container:
[
'{ "image": "ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8" }',
'{ "image": "ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }',
]
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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
conan_profile: gcc
build_type: Release
download_ccache: true
@@ -115,7 +115,7 @@ jobs:
needs: build-and-test
runs-on: heavy
container:
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

View File

@@ -21,7 +21,7 @@ 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:77387d8f9f13aea8f23831d221ac3e7683bb69b7
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.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:77387d8f9f13aea8f23831d221ac3e7683bb69b7
steps:
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7
permissions:
contents: write

View File

@@ -18,7 +18,7 @@ jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
steps:
- name: Checkout

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
- 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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
- 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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
static: true
- os: macos15
conan_profile: apple-clang

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
uses: ./.github/workflows/reusable-build-test.yml
with:

View File

@@ -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 }}

View File

@@ -46,7 +46,7 @@ jobs:
release:
runs-on: heavy
container:
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7" }'
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

View 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 ()

View File

@@ -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"]

View File

@@ -1,6 +1,6 @@
services:
clio_develop:
image: ghcr.io/xrplf/clio-ci:62369411404eb32b0140603a785ff05e1dc36ce8
image: ghcr.io/xrplf/clio-ci:77387d8f9f13aea8f23831d221ac3e7683bb69b7
volumes:
- clio_develop_conan_data:/root/.conan2/p
- clio_develop_ccache:/root/.ccache

View File

@@ -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:77387d8f9f13aea8f23831d221ac3e7683bb69b7
git clone https://github.com/XRPLF/clio
cd clio
```

View File

@@ -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

View File

@@ -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();
}
});
}

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)));

View File

@@ -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;
}

View File

@@ -164,8 +164,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;
}

View File

@@ -139,8 +139,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;
}

View File

@@ -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)));

View File

@@ -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 =

View File

@@ -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)));

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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))));

View File

@@ -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)));

View File

@@ -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;
}

View File

@@ -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()) {

View File

@@ -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(

View File

@@ -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();

View File

@@ -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))));

View File

@@ -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();

View File

@@ -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)));

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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)));

View File

@@ -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)));

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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 "

View File

@@ -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());
}
}