Compare commits

...

4 Commits

Author SHA1 Message Date
Pratik Mankawde
7def28c8ef switch to ucontext and coroutine2
Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

updated conan profiles

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

removed no-pie

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

minor change

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

fixes for boost flags

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

minor fix

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

added boost flags to cxx_flags

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

added boost context to conanfile

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

added dependency to coroutine2

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

now building coroutine2

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

using without_coroutine2=false as well

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>

fix build

Signed-off-by: Pratik Mankawde <pmankawde@ripple.com>
2025-11-21 12:11:13 +00:00
Ayaz Salikhov
adbeb94c2b ci: Only upload artifacts in XRPLF repo owner (#6060)
This change prevents uploading too many artifacts in non-public repositories.
2025-11-20 18:09:03 +00:00
Mayukha Vadari
a3d4be4eaf fix: Set correct index for limit in book_offers CLI (#6043)
This change fixes an indexing typo in the `book_offers` CLI processing, and does not affect the HTTPS/WS RPC processing.
2025-11-20 06:37:28 -05:00
Olek
6ff495fd9b Fix: Perform array size check (#6030)
The `ledger_entry` and `deposit_preauth` requests require an array of credentials. However, the array size is not checked before is gets processing. This fix adds checks and return errors in case array size is too big.
2025-11-19 16:58:18 +00:00
13 changed files with 250 additions and 55 deletions

View File

@@ -156,15 +156,107 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Add the configuration to the list, with the most unique fields first,
# so that they are easier to identify in the GitHub Actions UI, as long
# names get truncated.
configurations.append({
'config_name': config_name,
'cmake_args': cmake_args,
'cmake_target': cmake_target,
'build_only': build_only,
'build_type': build_type,
'os': os,
'architecture': architecture,
})
# Add Address and Thread (both coupled with UB) sanitizers when the distro is bookworm.
if os['distro_version'] == 'bookworm' and f'{os["compiler_name"]}-{os["compiler_version"]}' in {'gcc-15', 'clang-20'}:
extra_warning_flags = ''
linker_relocation_flags = ''
linker_flags = ''
cxx_flags += ' -DBOOST_USE_TSAN -DBOOST_USE_UBSAN -DBOOST_USE_UCONTEXT'
# Use large code model to avoid relocation errors with large binaries
# Only for x86-64 (amd64) - ARM64 doesn't support -mcmodel=large
if architecture['platform'] == 'linux/amd64' and os['compiler_name'] == 'gcc':
# Add -mcmodel=large and -fPIC to both compiler AND linker flags
# This is needed because sanitizers create very large binaries
# -fPIC enables position independent code to avoid relocation range issues
# large model removes the 2GB limitation that medium model has
cxx_flags += ' -mcmodel=large -fno-PIC'
linker_relocation_flags+=' -mcmodel=large -fno-PIC'
# Create default sanitizer flags
sanitizers_flags = 'undefined,float-divide-by-zero'
if os['compiler_name'] == 'gcc':
sanitizers_flags = f'{sanitizers_flags},signed-integer-overflow'
# Suppress false positive warnings in GCC with stringop-overflow
extra_warning_flags += ' -Wno-stringop-overflow'
# Disable mold, gold and lld linkers.
# Use default linker (bfd/ld) which is more lenient with mixed code models
cmake_args += ' -Duse_mold=OFF -Duse_gold=OFF -Duse_lld=OFF'
# Add linker flags for Sanitizers
linker_flags += f' -DCMAKE_EXE_LINKER_FLAGS="{linker_relocation_flags} -fsanitize=address,{sanitizers_flags}"'
linker_flags += f' -DCMAKE_SHARED_LINKER_FLAGS="{linker_relocation_flags} -fsanitize=address,{sanitizers_flags}"'
elif os['compiler_name'] == 'clang':
sanitizers_flags = f'{sanitizers_flags},signed-integer-overflow,unsigned-integer-overflow'
linker_flags += f' -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,{sanitizers_flags}"'
linker_flags += f' -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address,{sanitizers_flags}"'
# Sanitizers recommend minimum of -O1 for reasonable performance
if "-O0" in cxx_flags:
cxx_flags = cxx_flags.replace("-O0", "-O1")
else:
cxx_flags += " -O1"
# First create config for asan
cmake_args_flags = f'{cmake_args} -DCMAKE_CXX_FLAGS="-fsanitize=address,{sanitizers_flags} -fno-omit-frame-pointer {cxx_flags} {extra_warning_flags}" {linker_flags}'
# Add config with asan
configurations.append({
'config_name': config_name + "_asan",
'cmake_args': cmake_args_flags,
'cmake_target': cmake_target,
'build_only': build_only,
'build_type': build_type,
'os': os,
'architecture': architecture,
'sanitizers': 'Address'
})
linker_flags = ''
# Update configs for tsan
# gcc doesn't supports atomic_thread_fence with tsan. Suppress warnings.
# Also tsan doesn't work well with mcmode=large and bfd linker
if os['compiler_name'] == 'gcc':
extra_warning_flags += ' -Wno-tsan'
cxx_flags = cxx_flags.replace('-mcmodel=large', '-mcmodel=medium')
linker_relocation_flags = linker_relocation_flags.replace('-mcmodel=large', '-mcmodel=medium')
# Add linker flags for Sanitizers
linker_flags += f' -DCMAKE_EXE_LINKER_FLAGS="{linker_relocation_flags} -fsanitize=thread,{sanitizers_flags}"'
linker_flags += f' -DCMAKE_SHARED_LINKER_FLAGS="{linker_relocation_flags} -fsanitize=thread,{sanitizers_flags}"'
elif os['compiler_name'] == 'clang':
cxx_flags += ' -fsanitize-blacklist=$GITHUB_WORKSPACE/external/sanitizer-blacklist.txt'
linker_flags += f' -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread,{sanitizers_flags}"'
linker_flags += f' -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread,{sanitizers_flags}"'
# Note: We use $GITHUB_WORKSPACE environment variable which will be expanded by the shell
# before CMake processes it. This ensures the compiler receives an absolute path.
# CMAKE_SOURCE_DIR won't work here because it's inside CMAKE_CXX_FLAGS string.
cmake_args_flags = f'{cmake_args} -DCMAKE_CXX_FLAGS="-fsanitize=thread,{sanitizers_flags} -fno-omit-frame-pointer {cxx_flags} {extra_warning_flags}" {linker_flags}'
configurations.append({
'config_name': config_name+ "_tsan",
'cmake_args': cmake_args_flags,
'cmake_target': cmake_target,
'build_only': build_only,
'build_type': build_type,
'os': os,
'architecture': architecture,
'sanitizers': 'Thread'
})
else:
if cxx_flags:
cmake_args_flags = f'{cmake_args} -DCMAKE_CXX_FLAGS={cxx_flags}'
else:
cmake_args_flags = f'{cmake_args}'
configurations.append({
'config_name': config_name,
'cmake_args': cmake_args_flags,
'cmake_target': cmake_target,
'build_only': build_only,
'build_type': build_type,
'os': os,
'architecture': architecture
})
return configurations

View File

@@ -130,7 +130,7 @@ jobs:
--target "${CMAKE_TARGET}"
- name: Upload rippled artifact (Linux)
if: ${{ runner.os == 'Linux' }}
if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
env:
BUILD_DIR: ${{ inputs.build_dir }}

View File

@@ -17,7 +17,7 @@ find_dependency (Boost
chrono
container
context
coroutine
coroutine2
date_time
filesystem
program_options

View File

@@ -2,7 +2,7 @@ find_package(Boost 1.82 REQUIRED
COMPONENTS
chrono
container
coroutine
context
date_time
filesystem
json
@@ -20,7 +20,7 @@ target_link_libraries(xrpl_boost
Boost::headers
Boost::chrono
Boost::container
Boost::coroutine
Boost::context
Boost::date_time
Boost::filesystem
Boost::json

1
conan/profiles/ci Normal file
View File

@@ -0,0 +1 @@
include(sanitizers)

69
conan/profiles/sanitizers Normal file
View File

@@ -0,0 +1,69 @@
include(default)
{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}
{% set default_sanitizer_flags = "undefined,float-divide-by-zero,signed-integer-overflow" %}
{% set sanitizers = os.getenv("SANITIZERS") %}
[settings]
[conf]
{% if sanitizers == "Address" or sanitizers == "Thread" %}
user.package:sanitizers={{ sanitizers }}
tools.info.package_id:confs+=["user.package:sanitizers"]
{% endif %}
{% if compiler == "gcc" %}
{% set asan_sanitizer_flags = "-fsanitize=address,"~default_sanitizer_flags~" -mcmodel=large -fno-PIC" %}
{% set tsan_sanitizer_flags = "-fsanitize=thread,"~default_sanitizer_flags~" -mcmodel=medium -fno-PIC" %}
{% if sanitizers == "Address" %}
tools.build:cxxflags+=['{{asan_sanitizer_flags}} -fno-omit-frame-pointer -O1 -Wno-stringop-overflow -DBOOST_USE_ASAN -DBOOST_USE_UBSAN -DBOOST_USE_UCONTEXT']
tools.build:sharedlinkflags+=['{{asan_sanitizer_flags}}']
tools.build:exelinkflags+=['{{asan_sanitizer_flags}}']
tools.cmake.cmaketoolchain:extra_variables={"use_mold": "OFF", "use_gold": "OFF", "use_lld": "OFF"}
tools.build:defines+=["BOOST_USE_ASAN", "BOOST_USE_UBSAN", "BOOST_USE_UCONTEXT"]
{% elif sanitizers == "Thread" %}
tools.build:cxxflags+=['{{tsan_sanitizer_flags}} -fno-omit-frame-pointer -O1 -Wno-stringop-overflow -Wno-tsan -DBOOST_USE_TSAN -DBOOST_USE_UBSAN -DBOOST_USE_UCONTEXT']
tools.build:sharedlinkflags+=['{{tsan_sanitizer_flags}}']
tools.build:exelinkflags+=['{{tsan_sanitizer_flags}}']
tools.build:defines+=["BOOST_USE_TSAN", "BOOST_USE_UBSAN", "BOOST_USE_UCONTEXT"]
{% endif %}
{% elif compiler == "apple-clang" or compiler == "clang" %}
{% set asan_sanitizer_flags = "-fsanitize=address,"~default_sanitizer_flags~",unsigned-integer-overflow" %}
{% set tsan_sanitizer_flags = "-fsanitize=thread,"~default_sanitizer_flags~",unsigned-integer-overflow" %}
{% if sanitizers == "Address" %}
tools.build:cxxflags+=['{{asan_sanitizer_flags}} -fno-omit-frame-pointer -O1 -DBOOST_USE_ASAN -DBOOST_USE_UBSAN -DBOOST_USE_UCONTEXT']
tools.build:sharedlinkflags+=['{{asan_sanitizer_flags}}']
tools.build:exelinkflags+=['{{asan_sanitizer_flags}}']
tools.build:defines+=["BOOST_USE_ASAN", "BOOST_USE_UBSAN", "BOOST_USE_UCONTEXT"]
{% elif sanitizers == "Thread" %}
tools.build:cxxflags+=['{{tsan_sanitizer_flags}} -fno-omit-frame-pointer -O1 -DBOOST_USE_TSAN -DBOOST_USE_UBSAN -DBOOST_USE_UCONTEXT']
tools.build:sharedlinkflags+=['{{tsan_sanitizer_flags}}']
tools.build:exelinkflags+=['{{tsan_sanitizer_flags}}']
tools.build:defines+=["BOOST_USE_TSAN", "BOOST_USE_UBSAN", "BOOST_USE_UCONTEXT"]
{% endif %}
{% endif %}
[options]
{% if compiler == "gcc" or compiler == "apple-clang" or compiler == "clang" %}
{% if sanitizers == "Address" or sanitizers == "Thread" %}
boost/*:without_context=False
boost/*:without_stacktrace=True
boost/*:without_coroutine2=False
{% if sanitizers == "Address" %}
boost/*:extra_b2_flags="context-impl=ucontext address-sanitizer=norecover undefined-sanitizer=norecover --with-coroutine2"
{% elif sanitizers == "Thread" %}
boost/*:extra_b2_flags="context-impl=ucontext thread-sanitizer=norecover undefined-sanitizer=norecover --with-coroutine2"
{% endif %}
{% endif %}
{% endif %}

View File

@@ -102,6 +102,7 @@ class Xrpl(ConanFile):
self.options['boost'].visibility = 'global'
if self.settings.compiler in ['clang', 'gcc']:
self.options['boost'].without_cobalt = True
self.options['boost'].without_coroutine2 = False
def requirements(self):
# Conan 2 requires transitive headers to be specified
@@ -172,7 +173,8 @@ class Xrpl(ConanFile):
'boost::headers',
'boost::chrono',
'boost::container',
'boost::coroutine',
'boost::context',
'boost::coroutine2',
'boost::date_time',
'boost::filesystem',
'boost::json',

View File

@@ -1103,7 +1103,7 @@ class LedgerEntry_test : public beast::unit_test::suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
"Invalid field 'authorized_credentials', not array.");
"Invalid field 'authorized_credentials', array empty.");
}
{
@@ -1144,7 +1144,7 @@ class LedgerEntry_test : public beast::unit_test::suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
"Invalid field 'authorized_credentials', not array.");
"Invalid field 'authorized_credentials', array too long.");
}
}

View File

@@ -1584,8 +1584,6 @@ static RPCCallTestData const rpcCallTestArray[] = {
"EUR/rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"junk", // Note: indexing bug in parseBookOffers() requires junk
// param.
"200",
},
RPCCallTestData::no_exception,
@@ -1597,7 +1595,6 @@ static RPCCallTestData const rpcCallTestArray[] = {
"issuer" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"ledger_hash" : "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"limit" : 200,
"proof" : true,
"taker_gets" : {
"currency" : "EUR",
"issuer" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA"
@@ -1617,8 +1614,8 @@ static RPCCallTestData const rpcCallTestArray[] = {
"EUR/rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"junk", // Note: indexing bug in parseBookOffers() requires junk param.
"200",
"0",
"MyMarker"},
RPCCallTestData::no_exception,
R"({
@@ -1630,7 +1627,6 @@ static RPCCallTestData const rpcCallTestArray[] = {
"ledger_hash" : "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"limit" : 200,
"marker" : "MyMarker",
"proof" : true,
"taker_gets" : {
"currency" : "EUR",
"issuer" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA"
@@ -1665,8 +1661,8 @@ static RPCCallTestData const rpcCallTestArray[] = {
"EUR/rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"junk", // Note: indexing bug in parseBookOffers() requires junk param.
"200",
"0",
"MyMarker",
"extra"},
RPCCallTestData::no_exception,
@@ -1770,12 +1766,19 @@ static RPCCallTestData const rpcCallTestArray[] = {
"EUR/rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789",
"junk", // Note: indexing bug in parseBookOffers() requires junk
// param.
"not_a_number",
},
RPCCallTestData::bad_cast,
R"()"},
RPCCallTestData::no_exception,
R"({
"method" : "book_offers",
"params" : [
{
"error" : "invalidParams",
"error_code" : 31,
"error_message" : "Invalid field 'limit'."
}
]
})"},
// can_delete
// ------------------------------------------------------------------

View File

@@ -16,18 +16,16 @@ JobQueue::Coro::Coro(
, type_(type)
, name_(name)
, running_(false)
, coro_(
[this, fn = std::forward<F>(f)](
boost::coroutines::asymmetric_coroutine<void>::push_type&
do_yield) {
yield_ = &do_yield;
yield();
fn(shared_from_this());
, coro_([this, fn = std::forward<F>(f)](
boost::coroutines2::asymmetric_coroutine<void>::push_type&
do_yield) {
yield_ = &do_yield;
yield();
fn(shared_from_this());
#ifndef NDEBUG
finished_ = true;
finished_ = true;
#endif
},
boost::coroutines::attributes(megabytes(1)))
})
{
}

View File

@@ -9,7 +9,7 @@
#include <xrpl/basics/LocalValue.h>
#include <xrpl/json/json_value.h>
#include <boost/coroutine/all.hpp>
#include <boost/coroutine2/all.hpp>
#include <set>
@@ -50,8 +50,8 @@ public:
std::mutex mutex_;
std::mutex mutex_run_;
std::condition_variable cv_;
boost::coroutines::asymmetric_coroutine<void>::pull_type coro_;
boost::coroutines::asymmetric_coroutine<void>::push_type* yield_;
boost::coroutines2::asymmetric_coroutine<void>::pull_type coro_;
boost::coroutines2::asymmetric_coroutine<void>::push_type* yield_;
#ifndef NDEBUG
bool finished_ = false;
#endif
@@ -334,7 +334,7 @@ private:
other requests while the RPC command completes its work asynchronously.
postCoro() creates a Coro object. When the Coro ctor is called, and its
coro_ member is initialized (a boost::coroutines::pull_type), execution
coro_ member is initialized (a boost::coroutines2::pull_type), execution
automatically passes to the coroutine, which we don't want at this point,
since we are still in the handler thread context. It's important to note
here that construction of a boost pull_type automatically passes execution to

View File

@@ -332,15 +332,31 @@ private:
if (jvParams.size() >= 5)
{
int iLimit = jvParams[5u].asInt();
try
{
int iLimit = jvParams[4u].asInt();
if (iLimit > 0)
jvRequest[jss::limit] = iLimit;
if (iLimit > 0)
jvRequest[jss::limit] = iLimit;
}
catch (std::exception const&)
{
return RPC::invalid_field_error(jss::limit);
}
}
if (jvParams.size() >= 6 && jvParams[5u].asInt())
if (jvParams.size() >= 6)
{
jvRequest[jss::proof] = true;
try
{
int bProof = jvParams[5u].asInt();
if (bProof)
jvRequest[jss::proof] = true;
}
catch (std::exception const&)
{
return RPC::invalid_field_error(jss::proof);
}
}
if (jvParams.size() == 7)

View File

@@ -16,8 +16,6 @@
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/jss.h>
#include <functional>
namespace ripple {
static Expected<uint256, Json::Value>
@@ -178,18 +176,41 @@ static Expected<STArray, Json::Value>
parseAuthorizeCredentials(Json::Value const& jv)
{
if (!jv.isArray())
{
return LedgerEntryHelpers::invalidFieldError(
"malformedAuthorizedCredentials",
jss::authorized_credentials,
"array");
STArray arr(sfAuthorizeCredentials, jv.size());
}
std::uint32_t const n = jv.size();
if (n > maxCredentialsArraySize)
{
return Unexpected(LedgerEntryHelpers::malformedError(
"malformedAuthorizedCredentials",
"Invalid field '" + std::string(jss::authorized_credentials) +
"', array too long."));
}
if (n == 0)
{
return Unexpected(LedgerEntryHelpers::malformedError(
"malformedAuthorizedCredentials",
"Invalid field '" + std::string(jss::authorized_credentials) +
"', array empty."));
}
STArray arr(sfAuthorizeCredentials, n);
for (auto const& jo : jv)
{
if (!jo.isObject())
{
return LedgerEntryHelpers::invalidFieldError(
"malformedAuthorizedCredentials",
jss::authorized_credentials,
"array");
}
if (auto const value = LedgerEntryHelpers::hasRequired(
jo,
{jss::issuer, jss::credential_type},
@@ -260,13 +281,6 @@ parseDepositPreauth(Json::Value const& dp, Json::StaticString const fieldName)
auto const arr = parseAuthorizeCredentials(ac);
if (!arr.has_value())
return Unexpected(arr.error());
if (arr->empty() || (arr->size() > maxCredentialsArraySize))
{
return LedgerEntryHelpers::invalidFieldError(
"malformedAuthorizedCredentials",
jss::authorized_credentials,
"array");
}
auto const& sorted = credentials::makeSorted(arr.value());
if (sorted.empty())