Compare commits

..

19 Commits

Author SHA1 Message Date
Ed Hennis
72c700c3e0 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-28 16:23:38 -04:00
Jingchen
46b997b774 feat: Create new transaction testing framework TxTest (#6537)
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 14:16:10 +00:00
Vito Tumas
147da57348 feat: Add cleanup amendment for 3.2.0 (#7037) 2026-04-28 10:22:32 +00:00
Pratik Mankawde
3547112540 fix: Fix ubsan flagged issues (#6151)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-27 20:34:16 +00:00
Ed Hennis
4589fcbcfc Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-25 14:45:54 -04:00
Ed Hennis
86d840f53d Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-23 15:56:11 -04:00
Ed Hennis
741b61cdf3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 23:40:28 -04:00
Ed Hennis
6bb0989c9f Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 14:49:12 -04:00
Ed Hennis
9120506613 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 13:10:44 -04:00
Ed Hennis
3b3de96bd4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-21 18:48:39 -04:00
Ed Hennis
c9ab6ab25f Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop:
  chore: Remove empty Taker.h (6984)
  chore: Enable clang-tidy modernize checks (6975)
  ci: Upload clang-tidy git diff (6983)
  fix: Add rounding to Vault invariants (6217) (6955)
2026-04-21 18:46:58 -04:00
Ed Hennis
fb0605cfd3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 17:49:47 -04:00
Ed Hennis
156553bb5e Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 15:45:04 -04:00
Ed Hennis
781b56849b Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 11:39:06 -04:00
Ed Hennis
278c02bebb Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-17 18:11:31 -04:00
Ed Hennis
1d6fedf9a2 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-16 13:44:37 -04:00
Ed Hennis
2e8de499aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 19:06:29 -04:00
Ed Hennis
0bce3639a6 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 14:28:55 -04:00
Ed Hennis
8f329e3bc6 Use Validator List (VL) cache files in more scenarios
- If any [validator_list_keys] are not available after all
  [validator_list_sites] have had a chance to be queried, then fall
  back to loading cache files. Currently, cache files are only used if
  no sites are defined, or the request to one of them has an error. It
  does not include cases where not enough sites are defined, or if a
  site returns an invalid VL (or something else entirely).
- Resolves #5320
2026-04-13 19:46:06 -04:00
61 changed files with 2608 additions and 460 deletions

View File

@@ -188,10 +188,16 @@ test.toplevel > xrpl.json
test.unit_test > xrpl.basics
test.unit_test > xrpl.protocol
tests.libxrpl > xrpl.basics
tests.libxrpl > xrpl.core
tests.libxrpl > xrpl.json
tests.libxrpl > xrpl.ledger
tests.libxrpl > xrpl.net
tests.libxrpl > xrpl.nodestore
tests.libxrpl > xrpl.protocol
tests.libxrpl > xrpl.protocol_autogen
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx
xrpl.conditions > xrpl.basics
xrpl.conditions > xrpl.protocol
xrpl.core > xrpl.basics

View File

@@ -51,20 +51,21 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Only generate a subset of configurations in PRs.
if not all:
# Debian:
# - Bookworm using GCC 13: Release on linux/amd64, set the reference
# fee to 500.
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
# coverage (which will be done below).
# - Bookworm using GCC 13: Debug on linux/amd64, set the reference
# fee to 500 and enable code coverage (which will be done below).
# - Bookworm using GCC 15: Debug on linux/amd64, enable Address and
# UB sanitizers (which will be done below).
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
# - Bookworm using Clang 17: Release on linux/amd64, set the
# reference fee to 1000.
# - Bookworm using Clang 20: Debug on linux/amd64.
# - Bookworm using Clang 20: Debug on linux/amd64, enable Address
# and UB sanitizers (which will be done below).
if os["distro_name"] == "debian":
skip = True
if os["distro_version"] == "bookworm":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Release"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
@@ -193,11 +194,11 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
):
continue
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
# linux/amd64
# Enable code coverage for Debian Bookworm using GCC 13 in Debug on
# linux/amd64.
if (
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
@@ -234,23 +235,39 @@ 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.
# Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros.
# Add Address and UB sanitizers as separate configurations for specific
# bookworm distros. Thread sanitizer is currently disabled (see below).
# GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
if (
os["distro_version"] == "bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
):
# Add ASAN + UBSAN configuration.
if os[
"distro_version"
] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [
"gcc-15",
"clang-20",
]:
# Add ASAN configuration.
configurations.append(
{
"config_name": config_name + "-asan-ubsan",
"config_name": config_name + "-asan",
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "address,undefinedbehavior",
"sanitizers": "address",
}
)
# Add UBSAN configuration.
configurations.append(
{
"config_name": config_name + "-ubsan",
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "undefinedbehavior",
}
)
# TSAN is deactivated due to seg faults with latest compilers.

View File

@@ -1,6 +1,6 @@
#pragma once
#include <filesystem>
#include <boost/filesystem.hpp>
namespace xrpl {
@@ -12,6 +12,6 @@ namespace xrpl {
@throws runtime_error
*/
void
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst);
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);
} // namespace xrpl

View File

@@ -67,8 +67,10 @@ private:
}
else
{
while ((elapsed--) != 0u)
for (; elapsed > 0; --elapsed)
{
m_value -= (m_value + Window - 1) / Window;
}
}
}

View File

@@ -1,22 +1,22 @@
#pragma once
#include <filesystem>
#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>
#include <optional>
#include <string>
#include <system_error>
namespace xrpl {
std::string
getFileContents(
std::error_code& ec,
std::filesystem::path const& sourcePath,
boost::system::error_code& ec,
boost::filesystem::path const& sourcePath,
std::optional<std::size_t> maxSize = std::nullopt);
void
writeFileContents(
std::error_code& ec,
std::filesystem::path const& destPath,
boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::string const& contents);
} // namespace xrpl

View File

@@ -4,8 +4,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp>
#include <filesystem>
#include <fstream>
#include <map>
#include <memory>
@@ -88,7 +88,7 @@ private:
@return `true` if the file was opened.
*/
bool
open(std::filesystem::path const& path);
open(boost::filesystem::path const& path);
/** Close and re-open the system file associated with the log
This assists in interoperating with external log management tools.
@@ -130,7 +130,7 @@ private:
private:
std::unique_ptr<std::ofstream> m_stream;
std::filesystem::path m_path;
boost::filesystem::path m_path;
};
std::mutex mutable mutex_;
@@ -149,7 +149,7 @@ public:
virtual ~Logs() = default;
bool
open(std::filesystem::path const& pathToLogFile);
open(boost::filesystem::path const& pathToLogFile);
beast::Journal::Sink&
get(std::string const& name);

View File

@@ -43,8 +43,10 @@ public:
: work_(boost::asio::make_work_guard(ios_))
{
threads_.reserve(concurrency);
while ((concurrency--) != 0u)
for (std::size_t i = 0; i < concurrency; ++i)
{
threads_.emplace_back([&] { ios_.run(); });
}
}
~enable_yield_to()

View File

@@ -6,10 +6,10 @@
#include <xrpl/beast/unit_test/runner.h>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/throw_exception.hpp>
#include <filesystem>
#include <ostream>
#include <sstream>
#include <string>
@@ -25,7 +25,7 @@ make_reason(String const& reason, char const* file, int line)
std::string s(reason);
if (!s.empty())
s.append(": ");
namespace fs = std::filesystem;
namespace fs = boost::filesystem;
s.append(fs::path{file}.filename().string());
s.append("(");
s.append(boost::lexical_cast<std::string>(line));

View File

@@ -1,11 +1,8 @@
#pragma once
#include <filesystem>
#include <iomanip>
#include <random>
#include <sstream>
#include <boost/filesystem.hpp>
#include <string>
#include <system_error>
namespace beast {
@@ -16,7 +13,7 @@ namespace beast {
*/
class temp_dir
{
std::filesystem::path path_;
boost::filesystem::path path_;
public:
#if !GENERATING_DOCS
@@ -28,30 +25,20 @@ public:
/// Construct a temporary directory.
temp_dir()
{
auto const dir = std::filesystem::temp_directory_path();
std::random_device rd;
constexpr std::size_t maxAttempts = 100;
for (std::size_t attempt = 0; attempt < maxAttempts; ++attempt)
auto const dir = boost::filesystem::temp_directory_path();
do
{
std::error_code ec;
std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(8) << rd() << std::setw(8) << rd();
path_ = dir / oss.str();
if (!std::filesystem::exists(path_, ec) && !ec)
break;
path_.clear();
}
if (path_.empty())
throw std::runtime_error("Unable to generate a unique temporary directory path");
std::filesystem::create_directory(path_);
path_ = dir / boost::filesystem::unique_path();
} while (boost::filesystem::exists(path_));
boost::filesystem::create_directory(path_);
}
/// Destroy a temporary directory.
~temp_dir()
{
// use non-throwing calls in the destructor
std::error_code ec;
std::filesystem::remove_all(path_, ec);
boost::system::error_code ec;
boost::filesystem::remove_all(path_, ec);
// TODO: warn/notify if ec set ?
}

View File

@@ -4,9 +4,10 @@
#include <xrpl/core/JobTypes.h>
#include <xrpl/json/json_value.h>
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
@@ -42,7 +43,7 @@ public:
*/
struct Setup
{
std::filesystem::path perfLog;
boost::filesystem::path perfLog;
// log_interval is in milliseconds to support faster testing.
milliseconds logInterval{seconds(1)};
};
@@ -147,7 +148,7 @@ public:
};
PerfLog::Setup
setup_PerfLog(Section const& section, std::filesystem::path const& configDir);
setup_PerfLog(Section const& section, boost::filesystem::path const& configDir);
std::unique_ptr<PerfLog>
make_PerfLog(

View File

@@ -20,6 +20,10 @@ removeTokenOffersWithLimit(
Keylet const& directory,
std::size_t maxDeletableOffers);
/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID);
/** Finds the specified token in the owner's token directory. */
std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);

View File

@@ -54,8 +54,9 @@ read_varint(void const* buf, std::size_t buflen, std::size_t& t)
return 1;
}
auto const used = n;
while (n--)
while (n > 0)
{
--n;
auto const d = p[n];
auto const t0 = t;
t *= 127;

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (Cleanup3_2_0, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -130,6 +130,19 @@ public:
return sle_->at(sfFlags);
}
/**
* @brief Check if a specific flag is set.
*
* @param f The flag bitmask to check
* @return true if all bits in f are set in the flags field
*/
[[nodiscard]]
bool
isFlag(std::uint32_t f) const
{
return sle_->isFlag(f);
}
/**
* @brief Get the underlying SLE object.
*

View File

@@ -6,7 +6,8 @@
#include <xrpl/rdb/DBInit.h>
#include <xrpl/rdb/SociDB.h>
#include <filesystem>
#include <boost/filesystem/path.hpp>
#include <mutex>
#include <optional>
#include <string>
@@ -71,7 +72,7 @@ public:
StartUpType startUp = StartUpType::Normal;
bool standAlone = false;
std::filesystem::path dataDir;
boost::filesystem::path dataDir;
// Indicates whether or not to return the `globalPragma`
// from commonPragma()
bool useGlobalPragma = false;
@@ -134,7 +135,7 @@ public:
template <std::size_t N, std::size_t M>
DatabaseCon(
std::filesystem::path const& dataDir,
boost::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
@@ -146,7 +147,7 @@ public:
// Use this constructor to setup checkpointing
template <std::size_t N, std::size_t M>
DatabaseCon(
std::filesystem::path const& dataDir,
boost::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
@@ -181,7 +182,7 @@ private:
template <std::size_t N, std::size_t M>
DatabaseCon(
std::filesystem::path const& pPath,
boost::filesystem::path const& pPath,
std::vector<std::string> const* commonPragma,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/TxSearched.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>
namespace xrpl {

View File

@@ -4,6 +4,8 @@
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/server/Manifest.h>
#include <boost/filesystem.hpp>
namespace xrpl {
struct SavedState

View File

@@ -11,7 +11,6 @@ float-cast-overflow:external
float-divide-by-zero:external
function:external
implicit-integer-sign-change:external
implicit-signed-integer-truncation::external
implicit-signed-integer-truncation:external
implicit-unsigned-integer-truncation:external
integer-divide-by-zero:external
@@ -71,145 +70,15 @@ vla-bound:boost
vptr_check:boost
vptr:boost
# Google protobuf
# Google protobuf - intentional overflows in hash functions
undefined:protobuf
# Suppress UBSan errors in xrpld code by source file path
undefined:src/libxrpl/basics/base64.cpp
undefined:src/libxrpl/basics/Number.cpp
undefined:src/libxrpl/beast/utility/beast_Journal.cpp
undefined:src/libxrpl/crypto/RFC1751.cpp
undefined:src/libxrpl/ledger/ApplyView.cpp
undefined:src/libxrpl/ledger/View.cpp
undefined:src/libxrpl/protocol/Permissions.cpp
undefined:src/libxrpl/protocol/STAmount.cpp
undefined:src/libxrpl/protocol/STPathSet.cpp
undefined:src/libxrpl/protocol/tokens.cpp
undefined:src/libxrpl/shamap/SHAMap.cpp
undefined:src/test/app/Batch_test.cpp
undefined:src/test/app/Invariants_test.cpp
undefined:src/test/app/NFToken_test.cpp
undefined:src/test/app/Offer_test.cpp
undefined:src/test/app/Path_test.cpp
undefined:src/test/basics/XRPAmount_test.cpp
undefined:src/test/beast/LexicalCast_test.cpp
undefined:src/test/jtx/impl/acctdelete.cpp
undefined:src/test/ledger/SkipList_test.cpp
undefined:src/test/rpc/Subscribe_test.cpp
undefined:src/tests/libxrpl/basics/RangeSet.cpp
undefined:src/xrpld/app/main/BasicApp.cpp
undefined:src/xrpld/app/main/BasicApp.cpp
undefined:src/xrpld/app/misc/detail/AmendmentTable.cpp
undefined:src/xrpld/app/misc/NetworkOPs.cpp
undefined:src/libxrpl/json/json_value.cpp
undefined:src/xrpld/app/paths/detail/StrandFlow.h
undefined:src/xrpld/app/tx/detail/NFTokenMint.cpp
undefined:src/xrpld/app/tx/detail/OracleSet.cpp
undefined:src/xrpld/core/detail/JobQueue.cpp
undefined:src/xrpld/core/detail/Workers.cpp
undefined:src/xrpld/rpc/detail/Role.cpp
undefined:src/xrpld/rpc/handlers/GetAggregatePrice.cpp
undefined:xrpl/basics/base_uint.h
undefined:xrpl/basics/DecayingSample.h
undefined:xrpl/beast/test/yield_to.h
undefined:xrpl/beast/xor_shift_engine.h
undefined:xrpl/nodestore/detail/varint.h
undefined:xrpl/peerfinder/detail/Counts.h
undefined:xrpl/protocol/nft.h
# basic_string.h:483:51: runtime error: unsigned integer overflow
unsigned-integer-overflow:basic_string.h
unsigned-integer-overflow:bits/chrono.h
unsigned-integer-overflow:bits/random.h
unsigned-integer-overflow:bits/random.tcc
unsigned-integer-overflow:bits/stl_algobase.h
unsigned-integer-overflow:bits/uniform_int_dist.h
unsigned-integer-overflow:string_view
# runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'std::size_t' (aka 'unsigned long')
unsigned-integer-overflow:src/libxrpl/basics/base64.cpp
unsigned-integer-overflow:src/libxrpl/basics/Number.cpp
unsigned-integer-overflow:src/libxrpl/crypto/RFC1751.cpp
unsigned-integer-overflow:rc/libxrpl/json/json_value.cpp
unsigned-integer-overflow:src/libxrpl/ledger/ApplyView.cpp
unsigned-integer-overflow:src/libxrpl/ledger/View.cpp
unsigned-integer-overflow:src/libxrpl/protocol/Permissions.cpp
unsigned-integer-overflow:src/libxrpl/protocol/STAmount.cpp
unsigned-integer-overflow:src/libxrpl/protocol/STPathSet.cpp
unsigned-integer-overflow:src/libxrpl/protocol/tokens.cpp
unsigned-integer-overflow:src/libxrpl/shamap/SHAMap.cpp
unsigned-integer-overflow:src/test/app/Batch_test.cpp
unsigned-integer-overflow:src/test/app/Invariants_test.cpp
unsigned-integer-overflow:src/test/app/NFToken_test.cpp
unsigned-integer-overflow:src/test/app/Offer_test.cpp
unsigned-integer-overflow:src/test/app/Path_test.cpp
unsigned-integer-overflow:src/test/basics/XRPAmount_test.cpp
unsigned-integer-overflow:src/test/beast/LexicalCast_test.cpp
unsigned-integer-overflow:src/test/jtx/impl/acctdelete.cpp
unsigned-integer-overflow:src/test/ledger/SkipList_test.cpp
unsigned-integer-overflow:src/test/rpc/Subscribe_test.cpp
unsigned-integer-overflow:src/tests/libxrpl/basics/RangeSet.cpp
unsigned-integer-overflow:src/xrpld/app/main/BasicApp.cpp
unsigned-integer-overflow:src/xrpld/app/misc/detail/AmendmentTable.cpp
unsigned-integer-overflow:src/xrpld/app/misc/NetworkOPs.cpp
unsigned-integer-overflow:src/xrpld/app/paths/detail/StrandFlow.h
unsigned-integer-overflow:src/xrpld/app/tx/detail/NFTokenMint.cpp
unsigned-integer-overflow:src/xrpld/app/tx/detail/OracleSet.cpp
unsigned-integer-overflow:src/xrpld/rpc/detail/Role.cpp
unsigned-integer-overflow:src/xrpld/rpc/handlers/GetAggregatePrice.cpp
unsigned-integer-overflow:xrpl/basics/base_uint.h
unsigned-integer-overflow:xrpl/basics/DecayingSample.h
unsigned-integer-overflow:xrpl/beast/test/yield_to.h
unsigned-integer-overflow:xrpl/beast/xor_shift_engine.h
unsigned-integer-overflow:xrpl/nodestore/detail/varint.h
unsigned-integer-overflow:xrpl/peerfinder/detail/Counts.h
unsigned-integer-overflow:xrpl/protocol/nft.h
# Xrpld intentional overflows and operations
# STAmount uses intentional negation of INT64_MIN and overflow in arithmetic
signed-integer-overflow:src/libxrpl/protocol/STAmount.cpp
unsigned-integer-overflow:src/libxrpl/protocol/STAmount.cpp
# XRPAmount test intentional overflows
signed-integer-overflow:src/test/basics/XRPAmount_test.cpp
# Peerfinder intentional overflow in counter arithmetic
unsigned-integer-overflow:src/xrpld/peerfinder/detail/Counts.h
# Signed integer overflow suppressions
signed-integer-overflow:src/test/beast/LexicalCast_test.cpp
# External library suppressions
unsigned-integer-overflow:nudb/detail/xxhash.hpp
# Loan_test.cpp intentional underflow in test arithmetic
unsigned-integer-overflow:src/test/app/Loan_test.cpp
undefined:src/test/app/Loan_test.cpp
# Source tree restructured paths (libxrpl/tx/transactors/)
# These duplicate the xrpld/app/tx/detail entries above for the new layout
unsigned-integer-overflow:src/libxrpl/tx/transactors/oracle/OracleSet.cpp
undefined:src/libxrpl/tx/transactors/oracle/OracleSet.cpp
unsigned-integer-overflow:src/libxrpl/tx/transactors/nft/NFTokenMint.cpp
undefined:src/libxrpl/tx/transactors/nft/NFTokenMint.cpp
# Protobuf intentional overflows in hash functions
# Protobuf uses intentional unsigned overflow for hash computation (stringpiece.h:393)
unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h
# gRPC intentional overflows
# gRPC uses intentional overflow in timer calculations
# gRPC intentional overflows in timer calculations
unsigned-integer-overflow:grpc
unsigned-integer-overflow:timer_manager.cc
# Standard library intentional overflows
# These are intentional overflows in random number generation and character conversion
unsigned-integer-overflow:__random/seed_seq.h
unsigned-integer-overflow:__charconv/traits.h
# Suppress errors in RocksDB
# RocksDB uses intentional unsigned integer overflows in hash functions and CRC calculations
# RocksDB intentional unsigned integer overflows in hash functions and CRC calculations
unsigned-integer-overflow:rocks*/*/util/xxhash.h
unsigned-integer-overflow:rocks*/*/util/xxph3.h
unsigned-integer-overflow:rocks*/*/util/hash.cc
@@ -221,13 +90,14 @@ unsigned-integer-overflow:rocks*/*/table/format.cc
unsigned-integer-overflow:rocks*/*/table/block_based/block_based_table_builder.cc
unsigned-integer-overflow:rocks*/*/table/block_based/reader_common.cc
unsigned-integer-overflow:rocks*/*/db/version_set.cc
# RocksDB misaligned loads (intentional for performance on ARM64)
alignment:rocks*/*/util/crc32c_arm64.cc
undefined:rocks*/*/util/crc32c_arm64.cc
undefined:rocks*/*/util/xxhash.h
# nudb intentional overflows in hash functions
unsigned-integer-overflow:nudb/detail/xxhash.hpp
alignment:nudb/detail/xxhash.hpp
undefined:nudb
# Snappy compression library intentional overflows
unsigned-integer-overflow:snappy.cc
@@ -239,10 +109,40 @@ unsigned-integer-overflow:absl/base/internal/low_level_alloc.cc
unsigned-integer-overflow:absl/hash/internal/hash.h
unsigned-integer-overflow:absl/container/internal/raw_hash_set.h
# Standard library intentional overflows in chrono duration arithmetic
# Standard library intentional overflows
unsigned-integer-overflow:basic_string.h
unsigned-integer-overflow:bits/chrono.h
unsigned-integer-overflow:bits/random.h
unsigned-integer-overflow:bits/random.tcc
unsigned-integer-overflow:bits/stl_algobase.h
unsigned-integer-overflow:bits/uniform_int_dist.h
unsigned-integer-overflow:string_view
unsigned-integer-overflow:__random/seed_seq.h
unsigned-integer-overflow:__charconv/traits.h
unsigned-integer-overflow:__chrono/duration.h
# Suppress undefined errors in RocksDB and nudb
undefined:rocks.*/*/util/crc32c_arm64.cc
undefined:rocks.*/*/util/xxhash.h
undefined:nudb
# =============================================================================
# Rippled code suppressions
# =============================================================================
# Signed integer negation (-value) in amount types.
# INT64_MIN cannot occur in practice due to domain invariants (mantissa ranges
# are well within int64_t bounds), but UBSan flags the pattern as potential
# signed overflow. Narrowed to operator- to avoid suppressing unrelated
# overflows anywhere in a stack trace containing these type names.
signed-integer-overflow:operator-*IOUAmount*
signed-integer-overflow:operator-*XRPAmount*
signed-integer-overflow:operator-*MPTAmount*
signed-integer-overflow:operator-*STAmount*
# STAmount::operator+ signed addition — operands are bounded by total supply
# (~10^17 for XRP, ~10^18 for MPT) so overflow cannot occur in practice.
signed-integer-overflow:operator+*STAmount*
# STAmount::getRate uses unsigned shift and addition
unsigned-integer-overflow:*STAmount*getRate*
# STAmount::serialize uses unsigned bitwise operations
unsigned-integer-overflow:*STAmount*serialize*
# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation)
unsigned-integer-overflow:cipheredTaxon

View File

@@ -2,18 +2,20 @@
#include <xrpl/basics/contract.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <archive.h>
#include <archive_entry.h>
#include <cstddef>
#include <filesystem>
#include <memory>
#include <stdexcept>
namespace xrpl {
void
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst)
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst)
{
if (!is_regular_file(src))
Throw<std::runtime_error>("Invalid source file");

View File

@@ -1,24 +1,29 @@
#include <xrpl/basics/FileUtilities.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/system/detail/errc.hpp>
#include <boost/system/detail/error_code.hpp>
#include <boost/system/errc.hpp>
#include <cerrno>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <ios>
#include <iterator>
#include <optional>
#include <string>
#include <system_error>
namespace xrpl {
std::string
getFileContents(
std::error_code& ec,
std::filesystem::path const& sourcePath,
boost::system::error_code& ec,
boost::filesystem::path const& sourcePath,
std::optional<std::size_t> maxSize)
{
using namespace std::filesystem;
using namespace boost::filesystem;
using namespace boost::system::errc;
path const fullPath{canonical(sourcePath, ec)};
if (ec)
@@ -27,15 +32,15 @@ getFileContents(
if (maxSize && (file_size(fullPath, ec) > *maxSize || ec))
{
if (!ec)
ec = make_error_code(std::errc::file_too_large);
ec = make_error_code(file_too_large);
return {};
}
std::ifstream fileStream(fullPath, std::ios::in);
std::ifstream fileStream(fullPath.string(), std::ios::in);
if (!fileStream)
{
ec.assign(errno, std::generic_category());
ec = make_error_code(static_cast<errc_t>(errno));
return {};
}
@@ -44,7 +49,7 @@ getFileContents(
if (fileStream.bad())
{
ec.assign(errno, std::generic_category());
ec = make_error_code(static_cast<errc_t>(errno));
return {};
}
@@ -53,15 +58,18 @@ getFileContents(
void
writeFileContents(
std::error_code& ec,
std::filesystem::path const& destPath,
boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::string const& contents)
{
std::ofstream fileStream(destPath, std::ios::out | std::ios::trunc);
using namespace boost::filesystem;
using namespace boost::system::errc;
std::ofstream fileStream(destPath.string(), std::ios::out | std::ios::trunc);
if (!fileStream)
{
ec.assign(errno, std::generic_category());
ec = make_error_code(static_cast<errc_t>(errno));
return;
}
@@ -69,7 +77,7 @@ writeFileContents(
if (fileStream.bad())
{
ec.assign(errno, std::generic_category());
ec = make_error_code(static_cast<errc_t>(errno));
return;
}
}

View File

@@ -5,10 +5,10 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/path.hpp>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
@@ -53,7 +53,7 @@ Logs::File::isOpen() const noexcept
}
bool
Logs::File::open(std::filesystem::path const& path)
Logs::File::open(boost::filesystem::path const& path)
{
close();
@@ -112,7 +112,7 @@ Logs::Logs(beast::severities::Severity thresh) : thresh_(thresh) // default sev
}
bool
Logs::open(std::filesystem::path const& pathToLogFile)
Logs::open(boost::filesystem::path const& pathToLogFile)
{
return file_.open(pathToLogFile);
}

View File

@@ -107,7 +107,7 @@ encode(void* dest, void const* src, std::size_t len)
char const* in = static_cast<char const*>(src);
auto const tab = base64::get_alphabet();
for (auto n = len / 3; n != 0u; --n)
for (auto n = len / 3; n > 0; --n)
{
*out++ = tab[(in[0] & 0xfc) >> 2];
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)];

View File

@@ -621,6 +621,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
return deletedOffersCount;
}
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID)
{
std::size_t totalOffers = 0;
{
Dir const buys(view, keylet::nft_buys(nftokenID));
for (auto iter = buys.begin(); iter != buys.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > maxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
{
Dir const sells(view, keylet::nft_sells(nftokenID));
for (auto iter = sells.begin(); iter != sells.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > maxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
return tesSUCCESS;
}
bool
deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
{

View File

@@ -15,6 +15,8 @@
#include <xrpl/nodestore/detail/EncodedBlob.h>
#include <xrpl/nodestore/detail/codec.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/system/detail/errc.hpp>
#include <nudb/context.hpp>
@@ -33,14 +35,12 @@
#include <cstdint>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
@@ -131,7 +131,7 @@ public:
void
open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (db_.is_open())
{
// LCOV_EXCL_START
@@ -194,12 +194,11 @@ public:
if (deletePath_)
{
std::error_code fsec;
std::filesystem::remove_all(name_, fsec);
if (fsec)
boost::filesystem::remove_all(name_, ec);
if (ec)
{
JLOG(j_.fatal()) << "Filesystem remove_all of " << name_
<< " failed with: " << fsec.message();
JLOG(j_.fatal())
<< "Filesystem remove_all of " << name_ << " failed with: " << ec.message();
}
}
}
@@ -375,7 +374,7 @@ private:
static std::size_t
parseBlockSize(std::string const& name, Section const& keyValues, beast::Journal journal)
{
using namespace std::filesystem;
using namespace boost::filesystem;
auto const folder = path(name);
auto const kp = (folder / "nudb.key").string();

View File

@@ -8,6 +8,9 @@
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Types.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <rocksdb/advanced_options.h>
#include <rocksdb/cache.h>
#include <rocksdb/compression_type.h>
@@ -23,7 +26,6 @@
#include <bit>
#include <cstddef>
#include <filesystem>
#include <functional>
#include <stdexcept>
#include <string>
@@ -263,8 +265,8 @@ public:
m_db.reset();
if (m_deletePath)
{
std::filesystem::path const dir = m_name;
std::filesystem::remove_all(dir);
boost::filesystem::path const dir = m_name;
boost::filesystem::remove_all(dir);
}
}
}

View File

@@ -4,11 +4,13 @@
#include <xrpl/core/JobQueue.h>
#include <xrpl/core/ServiceRegistry.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <soci/blob.h>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <mutex>
#include <stdexcept>
#include <string>
@@ -42,8 +44,8 @@ getSociSqliteInit(std::string const& name, std::string const& dir, std::string c
Throw<std::runtime_error>(
"Sqlite databases must specify a dir and a name. Name: " + name + " Dir: " + dir);
}
std::filesystem::path file(dir);
if (std::filesystem::is_directory(file))
boost::filesystem::path file(dir);
if (is_directory(file))
file /= name + ext;
return file.string();
}

View File

@@ -5,12 +5,13 @@
#include <xrpl/rdb/DBInit.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp> // IWYU pragma: keep
#include <soci/into.h>
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <memory>
@@ -19,7 +20,7 @@ namespace xrpl {
bool
doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j)
{
std::filesystem::path const dbPath = setup.dataDir / TxDBName;
boost::filesystem::path const dbPath = setup.dataDir / TxDBName;
uintmax_t const dbSize = file_size(dbPath);
XRPL_ASSERT(dbSize != static_cast<uintmax_t>(-1), "xrpl::doVacuumDB : file_size succeeded");

View File

@@ -7,6 +7,8 @@
#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <boost/filesystem/operations.hpp>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/grpcpp.h>
@@ -14,14 +16,9 @@
#include <grpcpp/support/status.h>
#include <chrono>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <ios>
#include <memory>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
@@ -259,23 +256,9 @@ public:
TemporaryTLSCertificates()
{
auto tmpDir = std::filesystem::temp_directory_path();
std::random_device rd;
constexpr std::size_t maxAttempts = 100;
for (std::size_t attempt = 0; attempt < maxAttempts; ++attempt)
{
std::error_code ec;
std::ostringstream oss;
oss << kCERTS_DIR_PREFIX << std::hex << std::setfill('0') << std::setw(8) << rd();
tempDir_ = tmpDir / oss.str();
if (!std::filesystem::exists(tempDir_, ec) && !ec)
break;
tempDir_.clear();
}
if (tempDir_.empty())
{
throw std::runtime_error(
"Unable to generate a unique temporary TLS certificate directory");
}
auto uniqueDirName =
boost::filesystem::unique_path(std::string(kCERTS_DIR_PREFIX) + "%%%%%%%%");
tempDir_ = tmpDir / uniqueDirName.string();
std::filesystem::create_directories(tempDir_);
writeFile(tempDir_ / kCA_CERT_FILENAME, kCA_CERT_CONTENT);

View File

@@ -18,16 +18,16 @@
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/erase.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/system/detail/error_code.hpp>
#include <cassert>
#include <filesystem>
#include <fstream>
#include <ios>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
namespace xrpl {
@@ -139,7 +139,7 @@ class LedgerLoad_test : public beast::unit_test::suite
{
testcase("Load ledger: Bad Files");
using namespace test::jtx;
using namespace std::filesystem;
using namespace boost::filesystem;
// empty path
except([&] {
@@ -161,8 +161,8 @@ class LedgerLoad_test : public beast::unit_test::suite
});
// make a corrupted version of the ledger file (last 10 bytes removed).
std::error_code ec;
auto ledgerFileCorrupt = std::filesystem::path{sd.dbPath} / "ledgerdata_bad.json";
boost::system::error_code ec;
auto ledgerFileCorrupt = boost::filesystem::path{sd.dbPath} / "ledgerdata_bad.json";
copy_file(sd.ledgerFile, ledgerFileCorrupt, copy_options::overwrite_existing, ec);
if (!BEAST_EXPECTS(!ec, ec.message()))
return;

View File

@@ -21,12 +21,14 @@
#include <xrpl/server/Manifest.h>
#include <xrpl/server/Wallet.h>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <limits>
#include <memory>
#include <optional>
@@ -53,18 +55,18 @@ private:
}
static void
cleanupDatabaseDir(std::filesystem::path const& dbPath)
cleanupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (!exists(dbPath) || !is_directory(dbPath) || !is_empty(dbPath))
return;
remove(dbPath);
}
static void
setupDatabaseDir(std::filesystem::path const& dbPath)
setupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (!exists(dbPath))
{
create_directory(dbPath);
@@ -77,10 +79,10 @@ private:
Throw<std::runtime_error>("Cannot create directory: " + dbPath.string());
}
}
static std::filesystem::path
static boost::filesystem::path
getDatabasePath()
{
return std::filesystem::current_path() / "manifest_test_databases";
return boost::filesystem::current_path() / "manifest_test_databases";
}
public:
@@ -349,7 +351,7 @@ public:
BEAST_EXPECT(loaded.revoked(pk));
}
}
std::filesystem::remove(getDatabasePath() / std::filesystem::path(dbName));
boost::filesystem::remove(getDatabasePath() / boost::filesystem::path(dbName));
}
void

View File

@@ -21,9 +21,10 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem/path.hpp>
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <limits>
#include <map>
#include <memory>
@@ -490,7 +491,7 @@ public:
makeBackendRotating(jtx::Env& env, NodeStoreScheduler& scheduler, std::string path)
{
Section section{env.app().config().section(ConfigSection::nodeDatabase())};
std::filesystem::path newPath;
boost::filesystem::path newPath;
if (!BEAST_EXPECT(path.size()))
return {};

View File

@@ -15,12 +15,13 @@
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <date/date.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <memory>
#include <ostream>
@@ -600,7 +601,7 @@ public:
detail::default_effective_overlap,
60 * 24}}); // max of 24 hours
}
using namespace std::filesystem;
using namespace boost::filesystem;
for (auto const& file : directory_iterator(good.subdir()))
{
remove_all(file);

View File

@@ -4,7 +4,8 @@
#include <xrpl/basics/FileUtilities.h>
#include <xrpl/beast/unit_test/suite.h>
#include <system_error>
#include <boost/system/detail/errc.hpp>
#include <boost/system/detail/error_code.hpp>
namespace xrpl {
@@ -15,13 +16,14 @@ public:
testGetFileContents()
{
using namespace xrpl::detail;
using namespace boost::system;
constexpr char const* expectedContents = "This file is very short. That's all we need.";
FileDirGuard const file(
*this, "test_file", "test.txt", "This is temporary text that should get overwritten");
std::error_code ec;
error_code ec;
auto const path = file.file();
writeFileContents(ec, path, expectedContents);
@@ -44,7 +46,7 @@ public:
{
// Test with small max
auto const bad = getFileContents(ec, path, 16);
BEAST_EXPECT(ec && ec == std::errc::file_too_large);
BEAST_EXPECT(ec && ec.value() == boost::system::errc::file_too_large);
BEAST_EXPECT(bad.empty());
}
}

View File

@@ -15,10 +15,14 @@
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem/file_status.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/system/detail/error_code.hpp>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <ios>
#include <iterator>
@@ -27,7 +31,6 @@
#include <ostream>
#include <random>
#include <string>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
@@ -40,7 +43,7 @@ class PerfLog_test : public beast::unit_test::suite
{
enum class WithFile : bool { no = false, yes = true };
using path = std::filesystem::path;
using path = boost::filesystem::path;
// We're only using Env for its Journal. That Journal gives better
// coverage in unit tests.
@@ -63,14 +66,14 @@ class PerfLog_test : public beast::unit_test::suite
// The error code is intentionally ignored: if the path doesn't
// exist (the common case on a clean runner) remove_all returns
// an error, and that's fine — there's nothing to clean up.
using namespace std::filesystem;
std::error_code ec;
using namespace boost::filesystem;
boost::system::error_code ec;
remove_all(logDir(), ec);
}
~Fixture()
{
using namespace std::filesystem;
using namespace boost::filesystem;
auto const dir{logDir()};
auto const file{logFile()};
@@ -93,7 +96,7 @@ class PerfLog_test : public beast::unit_test::suite
static path
logDir()
{
using namespace std::filesystem;
using namespace boost::filesystem;
return temp_directory_path() / "perf_log_test_dir";
}
@@ -126,7 +129,7 @@ class PerfLog_test : public beast::unit_test::suite
static void
wait()
{
using namespace std::filesystem;
using namespace boost::filesystem;
auto const path = logFile();
if (!exists(path))
@@ -198,7 +201,7 @@ public:
void
testFileCreation()
{
using namespace std::filesystem;
using namespace boost::filesystem;
{
// Verify a PerfLog creates its file when constructed.
@@ -253,23 +256,22 @@ public:
// Construct and write protect a file to prevent PerfLog
// from creating its file.
std::error_code ec;
std::filesystem::create_directories(fixture.logDir(), ec);
boost::system::error_code ec;
boost::filesystem::create_directories(fixture.logDir(), ec);
if (!BEAST_EXPECT(!ec))
return;
auto fileWriteable = [](std::filesystem::path const& p) -> bool {
auto fileWriteable = [](boost::filesystem::path const& p) -> bool {
return std::ofstream{p.c_str(), std::ios::out | std::ios::app}.is_open();
};
if (!BEAST_EXPECT(fileWriteable(fixture.logFile())))
return;
std::filesystem::permissions(
boost::filesystem::permissions(
fixture.logFile(),
std::filesystem::perms::owner_write | std::filesystem::perms::others_write |
std::filesystem::perms::group_write,
std::filesystem::perm_options::remove);
perms::remove_perms | perms::owner_write | perms::others_write |
perms::group_write);
// If the test is running as root, then the write protect may have
// no effect. Make sure write protect worked before proceeding.
@@ -293,11 +295,9 @@ public:
perfLog->stop();
// Fix file permissions so the file can be cleaned up.
std::filesystem::permissions(
boost::filesystem::permissions(
fixture.logFile(),
std::filesystem::perms::owner_write | std::filesystem::perms::others_write |
std::filesystem::perms::group_write,
std::filesystem::perm_options::add);
perms::add_perms | perms::owner_write | perms::others_write | perms::group_write);
}
}
@@ -962,7 +962,7 @@ public:
// We can't fully test rotate because unit tests must run on Windows,
// and Windows doesn't (may not?) support rotate. But at least call
// the interface and see that it doesn't crash.
using namespace std::filesystem;
using namespace boost::filesystem;
Fixture fixture{env_.app(), j_};
BEAST_EXPECT(!exists(fixture.logDir()));

View File

@@ -24,7 +24,7 @@ public:
testInteger(IntType in)
{
std::string s;
IntType out(in + 1);
IntType out = static_cast<IntType>(~in); // Ensure out != in
expect(lexicalCastChecked(s, in));
expect(lexicalCastChecked(out, s));

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/SystemParameters.h> // IWYU pragma: keep
#include <xrpl/server/Port.h>
#include <boost/filesystem/operations.hpp>
#include <boost/format.hpp> // IWYU pragma: keep
#include <boost/format/free_funcs.hpp>
#include <boost/lexical_cast/bad_lexical_cast.hpp>
@@ -19,7 +20,6 @@
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <optional>
#include <ostream>
@@ -179,7 +179,7 @@ public:
[[nodiscard]] bool
dataDirExists() const
{
return std::filesystem::is_directory(dataDir_);
return boost::filesystem::is_directory(dataDir_);
}
[[nodiscard]] bool
@@ -192,7 +192,7 @@ public:
{
try
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (rmDataDir_)
rmDir(dataDir_);
}
@@ -273,7 +273,7 @@ public:
class Config_test final : public TestSuite
{
private:
using path = std::filesystem::path;
using path = boost::filesystem::path;
public:
void
@@ -309,7 +309,7 @@ port_wss_admin
{
testcase("config_file");
using namespace std::filesystem;
using namespace boost::filesystem;
auto const cwd = current_path();
// Test both config file names.
@@ -425,7 +425,7 @@ port_wss_admin
{
testcase("database_path");
using namespace std::filesystem;
using namespace boost::filesystem;
{
boost::format cc("[database_path]\n%1%\n");
@@ -601,7 +601,7 @@ main
{
testcase("validators_file");
using namespace std::filesystem;
using namespace boost::filesystem;
{
// load should throw for missing specified validators file
boost::format cc("[validators_file]\n%1%\n");

View File

@@ -6,6 +6,8 @@
#include <xrpl/rdb/SociDB.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/optional/optional.hpp>
#include <soci/into.h>
@@ -16,7 +18,6 @@
#include <cstdint>
#include <cstring>
#include <exception>
#include <filesystem>
#include <iterator>
#include <limits>
#include <stdexcept>
@@ -29,7 +30,7 @@ class SociDB_test final : public TestSuite
{
private:
static void
setupSQLiteConfig(BasicConfig& config, std::filesystem::path const& dbPath)
setupSQLiteConfig(BasicConfig& config, boost::filesystem::path const& dbPath)
{
config.overwrite("sqdb", "backend", "sqlite");
auto value = dbPath.string();
@@ -38,18 +39,18 @@ private:
}
static void
cleanupDatabaseDir(std::filesystem::path const& dbPath)
cleanupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (!exists(dbPath) || !is_directory(dbPath) || !is_empty(dbPath))
return;
remove(dbPath);
}
static void
setupDatabaseDir(std::filesystem::path const& dbPath)
setupDatabaseDir(boost::filesystem::path const& dbPath)
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (!exists(dbPath))
{
create_directory(dbPath);
@@ -62,10 +63,10 @@ private:
Throw<std::runtime_error>("Cannot create directory: " + dbPath.string());
}
}
static std::filesystem::path
static boost::filesystem::path
getDatabasePath()
{
return std::filesystem::current_path() / "socidb_test_databases";
return boost::filesystem::current_path() / "socidb_test_databases";
}
public:
@@ -155,7 +156,7 @@ public:
checkValues(s);
}
{
namespace bfs = std::filesystem;
namespace bfs = boost::filesystem;
// Remove the database
bfs::path const dbPath(sc.connectionString());
if (bfs::is_regular_file(dbPath))
@@ -285,7 +286,7 @@ public:
#endif
}
{
namespace bfs = std::filesystem;
namespace bfs = boost::filesystem;
// Remove the database
bfs::path const dbPath(sc.connectionString());
if (bfs::is_regular_file(dbPath))
@@ -337,7 +338,7 @@ public:
s << "SELECT LedgerSeq FROM Ledgers;", soci::into(ledgersLS);
BEAST_EXPECT(ledgersLS.size() == numRows);
}
namespace bfs = std::filesystem;
namespace bfs = boost::filesystem;
// Remove the database
bfs::path const dbPath(sc.connectionString());
if (bfs::is_regular_file(dbPath))

View File

@@ -4,7 +4,8 @@
#include <xrpl/basics/contract.h>
#include <filesystem>
#include <boost/filesystem.hpp>
#include <fstream>
namespace xrpl::detail {
@@ -15,7 +16,7 @@ namespace xrpl::detail {
class DirGuard
{
protected:
using path = std::filesystem::path;
using path = boost::filesystem::path;
private:
path subDir_;
@@ -42,7 +43,7 @@ public:
DirGuard(beast::unit_test::suite& test, path subDir, bool useCounter = true)
: subDir_(std::move(subDir)), test_(test)
{
using namespace std::filesystem;
using namespace boost::filesystem;
static auto subDirCounter = 0;
if (useCounter)
@@ -68,7 +69,7 @@ public:
{
try
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (rmSubDir_)
rmDir(subDir_);
@@ -125,7 +126,7 @@ public:
{
try
{
using namespace std::filesystem;
using namespace boost::filesystem;
if (exists(file_))
{
remove(file_);
@@ -155,7 +156,7 @@ public:
[[nodiscard]] bool
fileExists() const
{
return std::filesystem::exists(file_);
return boost::filesystem::exists(file_);
}
};

View File

@@ -8,9 +8,12 @@ add_custom_target(xrpl.tests)
# Test helpers
add_library(xrpl.helpers.test STATIC)
target_sources(xrpl.helpers.test PRIVATE helpers/TestSink.cpp)
target_sources(
xrpl.helpers.test
PRIVATE helpers/Account.cpp helpers/TestSink.cpp helpers/TxTest.cpp
)
target_include_directories(xrpl.helpers.test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xrpl.helpers.test PRIVATE xrpl.libxrpl)
target_link_libraries(xrpl.helpers.test PUBLIC xrpl.libxrpl gtest::gtest)
# Common library dependencies for the rest of the tests.
add_library(xrpl.imports.test INTERFACE)
@@ -32,6 +35,10 @@ xrpl_add_test(json)
target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test)
add_dependencies(xrpl.tests xrpl.test.json)
xrpl_add_test(tx)
target_link_libraries(xrpl.test.tx PRIVATE xrpl.imports.test)
add_dependencies(xrpl.tests xrpl.test.tx)
xrpl_add_test(protocol_autogen)
target_link_libraries(xrpl.test.protocol_autogen PRIVATE xrpl.imports.test)
add_dependencies(xrpl.tests xrpl.test.protocol_autogen)

View File

@@ -0,0 +1,19 @@
#include <helpers/Account.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Seed.h>
namespace xrpl::test {
Account const Account::master{"masterpassphrase"};
Account::Account(std::string_view name, KeyType type)
: name_(name)
, keyPair_(generateKeyPair(type, generateSeed(name_)))
, id_(calcAccountID(keyPair_.first))
{
}
} // namespace xrpl::test

View File

@@ -0,0 +1,81 @@
#pragma once
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <string>
#include <string_view>
#include <utility>
namespace xrpl::test {
/**
* @brief A test account with cryptographic keys.
*
* Generates keys deterministically from a name, making tests reproducible.
* The same name always produces the same AccountID and keys.
*/
class Account
{
public:
/**
* @brief The master account that holds all XRP in genesis.
*
* This account is created in the genesis ledger with all 100 billion XRP.
* It uses the well-known seed "masterpassphrase".
*/
static Account const master;
/**
* @brief Create an account from a name.
*
* Keys are derived deterministically from the name.
*
* @param name Human-readable name for the account.
* @param type Key type to use (defaults to secp256k1).
*/
explicit Account(std::string_view name, KeyType type = KeyType::secp256k1);
/** @brief Return the human-readable name. */
std::string const&
name() const noexcept
{
return name_;
}
/** @brief Return the AccountID. */
AccountID const&
id() const noexcept
{
return id_;
}
/** @brief Return the public key. */
PublicKey const&
pk() const noexcept
{
return keyPair_.first;
}
/** @brief Return the secret key. */
SecretKey const&
sk() const noexcept
{
return keyPair_.second;
}
/** @brief Implicit conversion to AccountID. */
operator AccountID const&() const noexcept
{
return id_;
}
private:
std::string name_;
std::pair<PublicKey, SecretKey> keyPair_;
AccountID id_;
};
} // namespace xrpl::test

View File

@@ -0,0 +1,132 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <helpers/Account.h>
#include <concepts>
#include <string>
#include <string_view>
#include <type_traits>
namespace xrpl::test {
/**
* @brief Represents an IOU (issued currency) for testing.
*
* Provides a clear, explicit API for creating currencies issued by an account.
* This replaces the cryptic `Account::operator[]` from the jtx framework.
*
* @code
* Account gw("gateway");
* IOU USD("USD", gw);
*
* auto issue = USD.issue(); // Get the Issue
* auto asset = USD.asset(); // Get the Asset
* auto amt = USD.amount(100); // Get STAmount of 100 USD
* @endcode
*/
class IOU
{
public:
/**
* @brief Construct an IOU from a currency code and issuing account.
* @param currencyCode A 3-character ISO currency code (e.g., "USD").
* @param issuer The account that issues this currency.
*/
IOU(std::string_view currencyCode, Account const& issuer)
: currency_(to_currency(std::string(currencyCode))), issuer_(issuer.id())
{
XRPL_ASSERT(!isXRP(currency_), "IOU: currency code must not resolve to XRP");
}
/**
* @brief Construct an IOU from a Currency and issuing account.
* @param currency The Currency object.
* @param issuer The account that issues this currency.
*/
IOU(Currency currency, Account const& issuer)
: currency_(std::move(currency)), issuer_(issuer.id())
{
XRPL_ASSERT(!isXRP(currency_), "IOU: currency code must not resolve to XRP");
}
/**
* @brief Get the Issue (currency + issuer pair).
* @return An Issue object representing this IOU.
*/
[[nodiscard]] Issue
issue() const
{
return Issue{currency_, issuer_};
}
/**
* @brief Get the Asset.
* @return An Asset object representing this IOU.
*/
[[nodiscard]] Asset
asset() const
{
return Asset{issue()};
}
/**
* @brief Create an STAmount of this IOU.
*
* Works with any arithmetic type (int, double, etc.) by converting
* to string and parsing. This matches the jtx IOU behaviour.
*
* @tparam T An arithmetic type.
* @param value The amount as any arithmetic type.
* @return An STAmount representing value units of this IOU.
*/
template <typename T>
requires std::is_arithmetic_v<T>
[[nodiscard]] STAmount
amount(T value) const
{
return amountFromString(issue(), to_string(value));
}
/**
* @brief Create an STAmount of this IOU from a Number.
* @param value The amount as a Number.
* @return An STAmount representing value units of this IOU.
*/
[[nodiscard]] STAmount
amount(Number const& value) const
{
return STAmount{issue(), value};
}
/**
* @brief Get the currency.
* @return The currency.
*/
[[nodiscard]] Currency const&
currency() const
{
return currency_;
}
/**
* @brief Get the issuer account ID.
* @return The issuer's AccountID.
*/
[[nodiscard]] AccountID const&
issuer() const
{
return issuer_;
}
private:
Currency currency_;
AccountID issuer_;
};
} // namespace xrpl::test

View File

@@ -0,0 +1,111 @@
#pragma once
#include <xrpl/basics/chrono.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/shamap/Family.h>
#include <memory>
namespace xrpl {
namespace test {
/** Test implementation of Family for unit tests.
Uses an in-memory NodeStore database and simple caches.
The missingNode methods throw since tests shouldn't encounter missing nodes.
*/
class TestFamily : public Family
{
private:
std::unique_ptr<NodeStore::Database> db_;
TestStopwatch clock_;
std::shared_ptr<FullBelowCache> fbCache_;
std::shared_ptr<TreeNodeCache> tnCache_;
NodeStore::DummyScheduler scheduler_;
beast::Journal j_;
public:
explicit TestFamily(beast::Journal j)
: fbCache_(std::make_shared<FullBelowCache>("TestFamily full below cache", clock_, j))
, tnCache_(
std::make_shared<TreeNodeCache>(
"TestFamily tree node cache",
65536,
std::chrono::minutes{1},
clock_,
j))
, j_(j)
{
Section config;
config.set("type", "memory");
config.set("path", "TestFamily");
db_ = NodeStore::Manager::instance().make_Database(megabytes(4), scheduler_, 1, config, j);
}
NodeStore::Database&
db() override
{
return *db_;
}
NodeStore::Database const&
db() const override
{
return *db_;
}
beast::Journal const&
journal() override
{
return j_;
}
std::shared_ptr<FullBelowCache>
getFullBelowCache() override
{
return fbCache_;
}
std::shared_ptr<TreeNodeCache>
getTreeNodeCache() override
{
return tnCache_;
}
void
sweep() override
{
fbCache_->sweep();
tnCache_->sweep();
}
void
missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) override
{
Throw<std::runtime_error>("TestFamily: missing node (by seq)");
}
void
missingNodeAcquireByHash(uint256 const& refHash, std::uint32_t refNum) override
{
Throw<std::runtime_error>("TestFamily: missing node (by hash)");
}
void
reset() override
{
fbCache_->reset();
tnCache_->reset();
}
/** Access the test clock for time manipulation in tests. */
TestStopwatch&
clock()
{
return clock_;
}
};
} // namespace test
} // namespace xrpl

View File

@@ -0,0 +1,378 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/core/HashRouter.h>
#include <xrpl/core/NetworkIDService.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/PendingSaves.h>
#include <xrpl/server/LoadFeeTrack.h>
#include <boost/asio/io_context.hpp>
#include <helpers/TestFamily.h>
#include <helpers/TestSink.h>
#include <optional>
#include <stdexcept>
namespace xrpl {
namespace test {
/** Logs implementation that creates TestSink instances. */
class TestLogs : public Logs
{
public:
explicit TestLogs(beast::severities::Severity level = beast::severities::kWarning) : Logs(level)
{
}
std::unique_ptr<beast::Journal::Sink>
makeSink(std::string const&, beast::severities::Severity threshold) override
{
return std::make_unique<TestSink>(threshold);
}
};
/** Simple NetworkIDService implementation for tests. */
class TestNetworkIDService final : public NetworkIDService
{
public:
explicit TestNetworkIDService(std::uint32_t networkID = 0) : networkID_(networkID)
{
}
[[nodiscard]] std::uint32_t
getNetworkID() const noexcept override
{
return networkID_;
}
private:
std::uint32_t networkID_;
};
/** Test implementation of ServiceRegistry for unit tests.
This class provides real implementations for services that can be
instantiated from libxrpl (such as Logs, io_context, caches), and
throws std::logic_error for services that require the full Application.
Tests can subclass this to provide additional services they need.
*/
class TestServiceRegistry : public ServiceRegistry
{
TestLogs logs_{beast::severities::kWarning};
boost::asio::io_context io_context_;
TestFamily family_{logs_.journal("TestFamily")};
LoadFeeTrack feeTrack_{logs_.journal("LoadFeeTrack")};
TestNetworkIDService networkIDService_;
HashRouter hashRouter_{HashRouter::Setup{}, stopwatch()};
NodeCache tempNodeCache_{
"TempNodeCache",
16384,
std::chrono::minutes{1},
stopwatch(),
logs_.journal("TaggedCache")};
CachedSLEs cachedSLEs_{
"CachedSLEs",
16384,
std::chrono::minutes{1},
stopwatch(),
logs_.journal("TaggedCache")};
PendingSaves pendingSaves_;
std::optional<uint256> trapTxID_;
public:
TestServiceRegistry() = default;
~TestServiceRegistry() override = default;
// Core infrastructure services
CollectorManager&
getCollectorManager() override
{
throw std::logic_error("TestServiceRegistry::getCollectorManager() not implemented");
}
Family&
getNodeFamily() override
{
return family_;
}
TimeKeeper&
getTimeKeeper() override
{
throw std::logic_error("TestServiceRegistry::timeKeeper() not implemented");
}
JobQueue&
getJobQueue() override
{
throw std::logic_error("TestServiceRegistry::getJobQueue() not implemented");
}
NodeCache&
getTempNodeCache() override
{
return tempNodeCache_;
}
CachedSLEs&
getCachedSLEs() override
{
return cachedSLEs_;
}
NetworkIDService&
getNetworkIDService() override
{
return networkIDService_;
}
// Protocol and validation services
AmendmentTable&
getAmendmentTable() override
{
throw std::logic_error("TestServiceRegistry::getAmendmentTable() not implemented");
}
HashRouter&
getHashRouter() override
{
return hashRouter_;
}
LoadFeeTrack&
getFeeTrack() override
{
return feeTrack_;
}
LoadManager&
getLoadManager() override
{
throw std::logic_error("TestServiceRegistry::getLoadManager() not implemented");
}
RCLValidations&
getValidations() override
{
throw std::logic_error("TestServiceRegistry::getValidations() not implemented");
}
ValidatorList&
getValidators() override
{
throw std::logic_error("TestServiceRegistry::validators() not implemented");
}
ValidatorSite&
getValidatorSites() override
{
throw std::logic_error("TestServiceRegistry::validatorSites() not implemented");
}
ManifestCache&
getValidatorManifests() override
{
throw std::logic_error("TestServiceRegistry::validatorManifests() not implemented");
}
ManifestCache&
getPublisherManifests() override
{
throw std::logic_error("TestServiceRegistry::publisherManifests() not implemented");
}
// Network services
Overlay&
getOverlay() override
{
throw std::logic_error("TestServiceRegistry::overlay() not implemented");
}
Cluster&
getCluster() override
{
throw std::logic_error("TestServiceRegistry::cluster() not implemented");
}
PeerReservationTable&
getPeerReservations() override
{
throw std::logic_error("TestServiceRegistry::peerReservations() not implemented");
}
Resource::Manager&
getResourceManager() override
{
throw std::logic_error("TestServiceRegistry::getResourceManager() not implemented");
}
// Storage services
NodeStore::Database&
getNodeStore() override
{
throw std::logic_error("TestServiceRegistry::getNodeStore() not implemented");
}
SHAMapStore&
getSHAMapStore() override
{
throw std::logic_error("TestServiceRegistry::getSHAMapStore() not implemented");
}
RelationalDatabase&
getRelationalDatabase() override
{
throw std::logic_error("TestServiceRegistry::getRelationalDatabase() not implemented");
}
// Ledger services
InboundLedgers&
getInboundLedgers() override
{
throw std::logic_error("TestServiceRegistry::getInboundLedgers() not implemented");
}
InboundTransactions&
getInboundTransactions() override
{
throw std::logic_error("TestServiceRegistry::getInboundTransactions() not implemented");
}
TaggedCache<uint256, AcceptedLedger>&
getAcceptedLedgerCache() override
{
throw std::logic_error("TestServiceRegistry::getAcceptedLedgerCache() not implemented");
}
LedgerMaster&
getLedgerMaster() override
{
throw std::logic_error("TestServiceRegistry::getLedgerMaster() not implemented");
}
LedgerCleaner&
getLedgerCleaner() override
{
throw std::logic_error("TestServiceRegistry::getLedgerCleaner() not implemented");
}
LedgerReplayer&
getLedgerReplayer() override
{
throw std::logic_error("TestServiceRegistry::getLedgerReplayer() not implemented");
}
PendingSaves&
getPendingSaves() override
{
return pendingSaves_;
}
OpenLedger&
getOpenLedger() override
{
throw std::logic_error("TestServiceRegistry::openLedger() not implemented");
}
OpenLedger const&
getOpenLedger() const override
{
throw std::logic_error("TestServiceRegistry::openLedger() const not implemented");
}
// Transaction and operation services
NetworkOPs&
getOPs() override
{
throw std::logic_error("TestServiceRegistry::getOPs() not implemented");
}
OrderBookDB&
getOrderBookDB() override
{
throw std::logic_error("TestServiceRegistry::getOrderBookDB() not implemented");
}
TransactionMaster&
getMasterTransaction() override
{
throw std::logic_error("TestServiceRegistry::getMasterTransaction() not implemented");
}
TxQ&
getTxQ() override
{
throw std::logic_error("TestServiceRegistry::getTxQ() not implemented");
}
PathRequestManager&
getPathRequestManager() override
{
throw std::logic_error("TestServiceRegistry::getPathRequestManager() not implemented");
}
// Server services
ServerHandler&
getServerHandler() override
{
throw std::logic_error("TestServiceRegistry::getServerHandler() not implemented");
}
perf::PerfLog&
getPerfLog() override
{
throw std::logic_error("TestServiceRegistry::getPerfLog() not implemented");
}
// Configuration and state
bool
isStopping() const override
{
return false;
}
beast::Journal
getJournal(std::string const& name) override
{
return logs_.journal(name);
}
boost::asio::io_context&
getIOContext() override
{
return io_context_;
}
Logs&
getLogs() override
{
return logs_;
}
std::optional<uint256> const&
getTrapTxID() const override
{
return trapTxID_;
}
DatabaseCon&
getWalletDB() override
{
throw std::logic_error("TestServiceRegistry::getWalletDB() not implemented");
}
// Temporary: Get the underlying Application
Application&
getApp() override
{
throw std::logic_error(
"TestServiceRegistry::app() not implemented - no Application available in tests");
}
};
} // namespace test
} // namespace xrpl

View File

@@ -0,0 +1,252 @@
#include <helpers/TxTest.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/contract.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/CanonicalTXSet.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol_autogen/ledger_entries/AccountRoot.h>
#include <xrpl/protocol_autogen/ledger_entries/RippleState.h>
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
#include <xrpl/protocol_autogen/transactions/Payment.h>
#include <xrpl/tx/apply.h>
#include <helpers/Account.h>
#include <helpers/IOU.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
namespace xrpl::test {
//------------------------------------------------------------------------------
// Feature helpers
//------------------------------------------------------------------------------
FeatureBitset
allFeatures()
{
static FeatureBitset const features = [] {
auto const& sa = allAmendments();
std::vector<uint256> feats;
feats.reserve(sa.size());
for ([[maybe_unused]] auto const& [name, _] : sa)
{
if (auto const f = getRegisteredFeature(name); f.has_value())
feats.push_back(*f);
}
return FeatureBitset(feats);
}();
return features;
}
//------------------------------------------------------------------------------
// TxTest
//------------------------------------------------------------------------------
TxTest::TxTest(std::optional<FeatureBitset> features)
{
// Convert FeatureBitset to unordered_set for Rules constructor
auto const featureBits = features.value_or(allFeatures());
foreachFeature(featureBits, [&](uint256 const& f) { featureSet_.insert(f); });
// Create rules with the specified features
rules_.emplace(featureSet_);
// Default fees for testing
Fees const fees{XRPAmount{10}, XRPAmount{10000000}, XRPAmount{2000000}};
// Create a genesis ledger as the base
closedLedger_ = std::make_shared<Ledger>(
create_genesis,
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
*rules_,
fees,
std::vector<uint256>{featureSet_.begin(), featureSet_.end()},
registry_.getNodeFamily());
// Initialize time from the genesis ledger
now_ = closedLedger_->header().closeTime;
// Create an open view on top of the genesis ledger
openLedger_ =
std::make_shared<OpenView>(open_ledger, closedLedger_.get(), *rules_, closedLedger_);
}
bool
TxTest::isEnabled(uint256 const& feature) const
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return rules_->enabled(feature);
}
Rules const&
TxTest::getRules() const
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return *rules_;
}
[[nodiscard]] TxResult
TxTest::submit(std::shared_ptr<STTx const> stx)
{
auto result = apply(registry_, *openLedger_, *stx, tapNONE, registry_.getJournal("apply"));
// Track successfully applied transactions for canonical reordering on close
// We make a copy since the TransactionBase doesn't own the STTx
if (result.applied)
pendingTxs_.push_back(stx);
return TxResult{
.ter = result.ter,
.applied = result.applied,
.metadata = std::move(result).metadata,
.tx = std::move(stx)};
}
void
TxTest::createAccount(Account const& account, XRPAmount xrp, uint32_t accountFlags)
{
auto const paymentTer =
submit(transactions::PaymentBuilder{Account::master, account, xrp}, Account::master).ter;
if (paymentTer != tesSUCCESS)
{
throw std::runtime_error("TxTest::createAccount: failed to create account");
}
close();
if (accountFlags != 0)
{
auto const accountSetTer =
submit(transactions::AccountSetBuilder{account}.setSetFlag(accountFlags), account).ter;
if (accountSetTer != tesSUCCESS)
{
throw std::runtime_error("TxTest::createAccount: failed to set account flags");
}
close();
}
}
ledger_entries::AccountRoot
TxTest::getAccountRoot(AccountID const& id) const
{
auto const sle = getOpenLedger().read(keylet::account(id));
if (!sle)
Throw<std::runtime_error>("TxTest::getAccountRoot: account not found");
return ledger_entries::AccountRoot{std::const_pointer_cast<SLE const>(sle)};
}
OpenView&
TxTest::getOpenLedger()
{
return *openLedger_;
}
OpenView const&
TxTest::getOpenLedger() const
{
return *openLedger_;
}
ReadView const&
TxTest::getClosedLedger() const
{
return *closedLedger_;
}
void
TxTest::close()
{
// Build a new closed ledger from the previous closed ledger,
// similar to how buildLedgerImpl works:
// 1. Create a new Ledger from the previous closed ledger
// 2. Re-apply transactions in canonical order
// 3. Mark it as accepted/immutable
auto const& prevLedger = *closedLedger_;
auto const ledgerCloseTime = now_ + prevLedger.header().closeTimeResolution;
now_ = ledgerCloseTime;
auto newLedger = std::make_shared<Ledger>(prevLedger, ledgerCloseTime);
CanonicalTXSet txSet(prevLedger.header().hash);
for (auto const& tx : pendingTxs_)
txSet.insert(tx);
{
OpenView accum(&*newLedger);
for (auto const& [key, tx] : txSet)
{
auto result = apply(registry_, accum, *tx, tapNONE, registry_.getJournal("apply"));
if (!result.applied)
{
throw std::runtime_error("TxTest::close: failed to apply transaction");
}
}
accum.apply(*newLedger);
}
newLedger->setAccepted(ledgerCloseTime, newLedger->header().closeTimeResolution, true);
closedLedger_ = newLedger;
pendingTxs_.clear();
openLedger_ =
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
std::make_shared<OpenView>(open_ledger, closedLedger_.get(), *rules_, closedLedger_);
}
void
TxTest::advanceTime(NetClock::duration duration)
{
now_ += duration;
}
NetClock::time_point
TxTest::getCloseTime() const
{
return now_;
}
STAmount
TxTest::getBalance(AccountID const& account, IOU const& iou) const
{
auto const sle = openLedger_->read(keylet::line(account, iou.issue()));
if (!sle)
return STAmount{iou.issue(), 0};
auto const rippleState = ledger_entries::RippleState{sle};
auto balance = rippleState.getBalance();
if (iou.issue().account == account)
{
throw std::logic_error("TxTest::getBalance: account is issuer");
}
balance.get<Issue>().account = iou.issue().account;
if (account > iou.issue().account)
balance.negate();
return balance;
}
} // namespace xrpl::test

View File

@@ -0,0 +1,364 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/protocol_autogen/ledger_entries/AccountRoot.h>
#include <xrpl/tx/applySteps.h>
#include <helpers/Account.h>
#include <helpers/IOU.h>
#include <helpers/TestServiceRegistry.h>
#include <cmath>
#include <concepts>
#include <memory>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <unordered_set>
#include <vector>
namespace xrpl::test {
//------------------------------------------------------------------------------
// Amount helpers
//------------------------------------------------------------------------------
/**
* @brief Convert XRP to drops (integral types).
* @param xrp The amount in XRP.
* @return The equivalent amount in drops as XRPAmount.
*/
template <std::integral T>
constexpr XRPAmount
XRP(T xrp)
{
return XRPAmount{static_cast<std::int64_t>(xrp) * DROPS_PER_XRP.drops()};
}
/**
* @brief Convert XRP to drops (floating point types).
* @param xrp The amount in XRP (may be fractional).
* @return The equivalent amount in drops as XRPAmount.
*/
template <std::floating_point T>
XRPAmount
XRP(T xrp)
{
return XRPAmount{static_cast<std::int64_t>(std::round(xrp * DROPS_PER_XRP.drops()))};
}
/**
* @brief Convert XRP to drops (Number type).
* @param xrp The amount in XRP as a Number.
* @return The equivalent amount in drops as XRPAmount.
*/
inline XRPAmount
XRP(Number const& xrp)
{
return XRPAmount{static_cast<std::int64_t>(xrp * DROPS_PER_XRP.drops())};
}
//------------------------------------------------------------------------------
// Flag helpers
//------------------------------------------------------------------------------
/**
* @brief Convert AccountSet flag (asf) to LedgerState flag (lsf).
* @param asf The AccountSet flag value.
* @return The corresponding LedgerState flag.
* @throws std::runtime_error if the flag is not supported.
*
* Supported flags:
* asfRequireDest, asfRequireAuth, asfDisallowXRP, asfDisableMaster,
* asfNoFreeze, asfGlobalFreeze, asfDefaultRipple, asfDepositAuth,
* asfAllowTrustLineClawback, asfDisallowIncomingCheck,
* asfDisallowIncomingNFTokenOffer, asfDisallowIncomingPayChan,
* asfDisallowIncomingTrustline, asfAllowTrustLineLocking
*/
constexpr std::uint32_t
asfToLsf(std::uint32_t asf)
{
switch (asf)
{
case asfRequireDest:
return lsfRequireDestTag;
case asfRequireAuth:
return lsfRequireAuth;
case asfDisallowXRP:
return lsfDisallowXRP;
case asfDisableMaster:
return lsfDisableMaster;
case asfNoFreeze:
return lsfNoFreeze;
case asfGlobalFreeze:
return lsfGlobalFreeze;
case asfDefaultRipple:
return lsfDefaultRipple;
case asfDepositAuth:
return lsfDepositAuth;
case asfAllowTrustLineClawback:
return lsfAllowTrustLineClawback;
case asfDisallowIncomingCheck:
return lsfDisallowIncomingCheck;
case asfDisallowIncomingNFTokenOffer:
return lsfDisallowIncomingNFTokenOffer;
case asfDisallowIncomingPayChan:
return lsfDisallowIncomingPayChan;
case asfDisallowIncomingTrustline:
return lsfDisallowIncomingTrustline;
case asfAllowTrustLineLocking:
return lsfAllowTrustLineLocking;
default:
throw std::runtime_error("Unknown asf flag");
}
}
//------------------------------------------------------------------------------
// Feature helpers
//------------------------------------------------------------------------------
/**
* @brief Returns all testable amendments.
* @note This is similar to jtx::testable_amendments() but for the TxTest framework.
*/
FeatureBitset
allFeatures();
//------------------------------------------------------------------------------
// TxResult
//------------------------------------------------------------------------------
/**
* @brief Result of a transaction submission in TxTest.
*
* Contains the TER code, whether the transaction was applied,
* optional metadata, and a reference to the submitted transaction.
* Use standard gtest macros (EXPECT_EQ, EXPECT_TRUE, etc.) to verify results.
*/
struct TxResult
{
TER ter; /**< The transaction engine result code. */
bool applied; /**< Whether the transaction was applied to the ledger. */
std::optional<TxMeta> metadata; /**< Transaction metadata, if available. */
std::shared_ptr<STTx const> tx; /**< Pointer to the submitted transaction. */
};
/**
* @brief A lightweight transaction testing harness.
*
* Unlike the JTx framework which requires a full Application and RPC layer,
* TxTest applies transactions directly to an OpenView using the transactor
* pipeline (preflight -> preclaim -> doApply).
*
* This makes it suitable for:
* - Unit testing individual transactors
* - Testing transaction validation logic
* - Fast, focused tests without full server infrastructure
*
* @code
* TxTest env;
* env.submit(paymentTx).expectSuccess();
* env.submit(badTx).expectTer(tecNO_ENTRY);
* @endcode
*/
class TxTest
{
public:
/**
* @brief Construct a TxTest environment.
*
* Creates a genesis ledger and an open view on top of it.
*
* @param features Optional set of features to enable. If not specified,
* uses all testable amendments.
*/
explicit TxTest(std::optional<FeatureBitset> features = std::nullopt);
/**
* @brief Check if a feature is enabled.
* @param feature The feature to check.
* @return True if the feature is enabled.
*/
[[nodiscard]] bool
isEnabled(uint256 const& feature) const;
/**
* @brief Get the current rules.
* @return The current consensus rules.
*/
[[nodiscard]] Rules const&
getRules() const;
/**
* @brief Submit a transaction from a builder.
*
* Convenience overload that accepts transaction builders.
* Automatically sets sequence and fee before submission.
*
* @tparam T A type derived from TransactionBuilderBase.
* @param builder The transaction builder.
* @param signer The account to sign with.
* @return TxResult containing the result code, applied status, and metadata.
*/
template <typename T>
requires std::
derived_from<std::decay_t<T>, transactions::TransactionBuilderBase<std::decay_t<T>>>
[[nodiscard]] TxResult
submit(T&& builder, Account const& signer)
{
auto const& obj = builder.getSTObject();
auto accountId = obj[sfAccount];
// Only set sequence if not using a ticket (ticket sets sequence to 0)
if (!obj.isFieldPresent(sfTicketSequence))
{
builder.setSequence(getAccountRoot(accountId).getSequence());
}
else
{
builder.setSequence(0);
}
builder.setFee(XRPAmount(10));
return submit(builder.build(signer.pk(), signer.sk()).getSTTx());
}
/**
* @brief Submit a transaction to the open ledger.
*
* Applies the transaction through the full transactor pipeline:
* preflight -> preclaim -> doApply -> invariant checks
*
* Invariant checks are automatically run after doApply. If any
* invariant fails, the result will be tecINVARIANT_FAILED.
*
* @param stx The transaction to submit.
* @return TxResult containing the result code, applied status, and metadata.
*/
[[nodiscard]] TxResult
submit(std::shared_ptr<STTx const> stx);
/**
* @brief Create a new account in the ledger.
*
* Sends a Payment from the master account to create and fund the account.
* Closes the ledger after creation. If accountFlags is non-zero, submits
* an AccountSet transaction and closes again.
*
* @param account The account to create.
* @param xrp The initial XRP balance.
* @param accountFlags Optional account flags to set. Defaults to 0
* (no flags).
*/
void
createAccount(Account const& account, XRPAmount xrp, uint32_t accountFlags = 0);
/**
* @brief Get the account root object from the current open ledger.
* @param id The account ID.
* @return The AccountRoot ledger entry.
* @throws std::runtime_error if the account does not exist.
* @todo Once we make keylet strongly typed, we can ditch this method.
*/
[[nodiscard]] ledger_entries::AccountRoot
getAccountRoot(AccountID const& id) const;
/**
* @brief Get the current open ledger view.
* @return A mutable reference to the open ledger.
*/
[[nodiscard]] OpenView&
getOpenLedger();
/**
* @brief Get the current open ledger view (const).
* @return A const reference to the open ledger.
*/
[[nodiscard]] OpenView const&
getOpenLedger() const;
/**
* @brief Get the closed (base) ledger view.
* @return A const reference to the closed ledger.
*/
[[nodiscard]] ReadView const&
getClosedLedger() const;
/**
* @brief Close the current ledger.
*
* Creates a new closed ledger from the current open ledger.
* All pending transactions are re-applied in canonical order.
*/
void
close();
/**
* @brief Advance time without closing the ledger.
*
* Useful for testing time-dependent features like escrow release
* times or offer expirations.
*
* @param duration The amount of time to advance.
*/
void
advanceTime(NetClock::duration duration);
/**
* @brief Get the current ledger close time.
* @return The current close time.
*/
[[nodiscard]] NetClock::time_point
getCloseTime() const;
/**
* @brief Get the balance of an IOU for an account.
*
* Returns the balance from the perspective of the specified account.
* If the trust line doesn't exist, returns zero.
*
* @param account The account to check.
* @param iou The IOU to check the balance for.
* @return The balance as an STAmount.
* @todo Once we make keylet strongly typed, we can ditch this method.
*/
[[nodiscard]] STAmount
getBalance(AccountID const& account, IOU const& iou) const;
/**
* @brief Get the service registry.
* @return A reference to the service registry.
*/
ServiceRegistry&
getServiceRegistry()
{
return registry_;
}
private:
TestServiceRegistry registry_;
std::unordered_set<uint256, beast::uhash<>> featureSet_;
std::optional<Rules> rules_;
std::shared_ptr<Ledger const> closedLedger_;
std::shared_ptr<OpenView> openLedger_;
/** Transactions submitted to the open ledger, for canonical reordering on close. */
std::vector<std::shared_ptr<STTx const>> pendingTxs_;
/** Current time (can be advanced arbitrarily for testing). */
NetClock::time_point now_;
};
} // namespace xrpl::test

View File

@@ -0,0 +1,804 @@
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol_autogen/ledger_entries/AccountRoot.h>
#include <xrpl/protocol_autogen/transactions/Payment.h>
#include <xrpl/protocol_autogen/transactions/SetRegularKey.h>
#include <xrpl/protocol_autogen/transactions/SignerListSet.h>
#include <xrpl/protocol_autogen/transactions/TicketCreate.h>
#include <xrpl/protocol_autogen/transactions/TrustSet.h>
#include <gtest/gtest.h>
#include <helpers/Account.h>
#include <helpers/IOU.h>
#include <helpers/TxTest.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::test {
TEST(AccountSet, NullAccountSet)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10));
auto& view = env.getOpenLedger();
// ask for the ledger entry - account root, to check its flags
auto sle = view.read(keylet::account(alice));
EXPECT_NE(sle, nullptr);
ledger_entries::AccountRoot const accountRoot(sle);
EXPECT_EQ(accountRoot.getFlags(), 0);
}
TEST(AccountSet, MostFlags)
{
Account const alice("alice");
TxTest env;
env.createAccount(alice, XRP(10000));
// Give alice a regular key so she can legally set and clear
// her asfDisableMaster flag.
Account const aliceRegularKey{"aliceRegularKey", KeyType::secp256k1};
env.createAccount(aliceRegularKey, XRP(10000));
env.close();
EXPECT_EQ(
env.submit(transactions::SetRegularKeyBuilder{alice}.setRegularKey(aliceRegularKey), alice)
.ter,
tesSUCCESS);
env.close();
auto testFlags = [&alice, &aliceRegularKey, &env](
std::initializer_list<std::uint32_t> goodFlags) {
std::uint32_t const orig_flags = env.getAccountRoot(alice).getFlags();
for (std::uint32_t flag{1u}; flag < std::numeric_limits<std::uint32_t>::digits; ++flag)
{
if (flag == asfNoFreeze)
{
// The asfNoFreeze flag can't be cleared. It is tested
// elsewhere.
continue;
}
if (flag == asfAuthorizedNFTokenMinter)
{
// The asfAuthorizedNFTokenMinter flag requires the
// presence or absence of the sfNFTokenMinter field in
// the transaction. It is tested elsewhere.
continue;
}
if (flag == asfDisallowIncomingCheck || flag == asfDisallowIncomingPayChan ||
flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline)
{
// These flags are part of the DisallowIncoming amendment
// and are tested elsewhere
continue;
}
if (flag == asfAllowTrustLineClawback)
{
// The asfAllowTrustLineClawback flag can't be cleared. It
// is tested elsewhere.
continue;
}
if (flag == asfAllowTrustLineLocking)
{
// These flags are part of the AllowTokenLocking amendment
// and are tested elsewhere
continue;
}
if (std::ranges::find(goodFlags, flag) != goodFlags.end())
{
// Good flag
EXPECT_FALSE(env.getAccountRoot(alice).isFlag(asfToLsf(flag)));
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(flag), alice).ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).isFlag(asfToLsf(flag)));
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}.setClearFlag(flag),
aliceRegularKey)
.ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).isFlag(asfToLsf(flag)));
std::uint32_t const now_flags = env.getAccountRoot(alice).getFlags();
EXPECT_EQ(now_flags, orig_flags);
}
else
{
// Bad flag
EXPECT_EQ(env.getAccountRoot(alice).getFlags(), orig_flags);
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(flag), alice).ter,
tesSUCCESS);
env.close();
EXPECT_EQ(env.getAccountRoot(alice).getFlags(), orig_flags);
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}.setClearFlag(flag),
aliceRegularKey)
.ter,
tesSUCCESS);
env.close();
EXPECT_EQ(env.getAccountRoot(alice).getFlags(), orig_flags);
}
}
};
testFlags({
asfRequireDest,
asfRequireAuth,
asfDisallowXRP,
asfGlobalFreeze,
asfDisableMaster,
asfDefaultRipple,
asfDepositAuth,
});
}
TEST(AccountSet, SetAndResetAccountTxnID)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
std::uint32_t const orig_flags = env.getAccountRoot(alice).getFlags();
// asfAccountTxnID is special and not actually set as a flag,
// so we check the field presence instead
EXPECT_FALSE(env.getAccountRoot(alice).hasAccountTxnID());
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfAccountTxnID), alice).ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).hasAccountTxnID());
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setClearFlag(asfAccountTxnID), alice).ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).hasAccountTxnID());
std::uint32_t const now_flags = env.getAccountRoot(alice).getFlags();
EXPECT_EQ(now_flags, orig_flags);
}
TEST(AccountSet, SetNoFreeze)
{
TxTest env;
Account const alice("alice");
Account const eric("eric");
env.createAccount(alice, XRP(10000));
env.close();
// Set eric as alice's regular key (eric doesn't need to be funded)
EXPECT_EQ(
env.submit(transactions::SetRegularKeyBuilder{alice}.setRegularKey(eric), alice).ter,
tesSUCCESS);
env.close();
// Verify alice doesn't have NoFreeze flag
EXPECT_FALSE(env.getAccountRoot(alice).isFlag(lsfNoFreeze));
// Setting NoFreeze with regular key should fail - requires master key
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfNoFreeze), eric).ter,
tecNEED_MASTER_KEY);
env.close();
// Setting NoFreeze with master key should succeed
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfNoFreeze), alice).ter,
tesSUCCESS);
env.close();
// Verify alice now has NoFreeze flag
EXPECT_TRUE(env.getAccountRoot(alice).isFlag(lsfNoFreeze));
// Try to clear NoFreeze - transaction succeeds but flag remains set
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setClearFlag(asfNoFreeze), alice).ter,
tesSUCCESS);
env.close();
// Verify flag is still set (NoFreeze cannot be cleared once set)
EXPECT_TRUE(env.getAccountRoot(alice).isFlag(lsfNoFreeze));
}
TEST(AccountSet, Domain)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
// The Domain field is represented as the hex string of the lowercase
// ASCII of the domain. For example, the domain example.com would be
// represented as "6578616d706c652e636f6d".
//
// To remove the Domain field from an account, send an AccountSet with
// the Domain set to an empty string.
std::string const domain = "example.com";
// Set domain
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setDomain(makeSlice(domain)), alice).ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).hasDomain());
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*env.getAccountRoot(alice).getDomain(), makeSlice(domain));
// Clear domain by setting empty
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setDomain(Slice{}), alice).ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).hasDomain());
// The upper limit on the length is 256 bytes
// (defined as DOMAIN_BYTES_MAX in SetAccount)
// test the edge cases: 255, 256, 257.
std::size_t const maxLength = 256;
for (std::size_t len = maxLength - 1; len <= maxLength + 1; ++len)
{
std::string const domain2 = std::string(len - domain.length() - 1, 'a') + "." + domain;
EXPECT_EQ(domain2.length(), len);
if (len <= maxLength)
{
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}.setDomain(makeSlice(domain2)), alice)
.ter,
tesSUCCESS);
env.close();
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*env.getAccountRoot(alice).getDomain(), makeSlice(domain2));
}
else
{
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}.setDomain(makeSlice(domain2)), alice)
.ter,
telBAD_DOMAIN);
env.close();
}
}
}
TEST(AccountSet, MessageKey)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
// Generate a random ed25519 key pair for the message key
auto const rkp = randomKeyPair(KeyType::ed25519);
// Set the message key
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setMessageKey(rkp.first.slice()), alice)
.ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).hasMessageKey());
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*env.getAccountRoot(alice).getMessageKey(), rkp.first.slice());
// Clear the message key by setting to empty
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setMessageKey(Slice{}), alice).ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).hasMessageKey());
// Try to set an invalid public key - should fail
using namespace std::string_literals;
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}.setMessageKey(
makeSlice("NOT_REALLY_A_PUBKEY"s)),
alice)
.ter,
telBAD_PUBLIC_KEY);
}
TEST(AccountSet, WalletID)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
std::string_view const locator =
"9633EC8AF54F16B5286DB1D7B519EF49EEFC050C0C8AC4384F1D88ACD1BFDF05";
uint256 locatorHash{};
EXPECT_TRUE(locatorHash.parseHex(locator));
// Set the wallet locator
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setWalletLocator(locatorHash), alice).ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).hasWalletLocator());
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*env.getAccountRoot(alice).getWalletLocator(), locatorHash);
// Clear the wallet locator by setting to zero
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setWalletLocator(beast::zero), alice).ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).hasWalletLocator());
}
TEST(AccountSet, EmailHash)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
std::string_view const mh = "5F31A79367DC3137FADA860C05742EE6";
uint128 emailHash{};
EXPECT_TRUE(emailHash.parseHex(mh));
// Set the email hash
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setEmailHash(emailHash), alice).ter,
tesSUCCESS);
env.close();
EXPECT_TRUE(env.getAccountRoot(alice).hasEmailHash());
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*env.getAccountRoot(alice).getEmailHash(), emailHash);
// Clear the email hash by setting to zero
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setEmailHash(beast::zero), alice).ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(env.getAccountRoot(alice).hasEmailHash());
}
TEST(AccountSet, TransferRate)
{
struct TestCase
{
double set;
TER code;
double get;
};
// Test data: {rate to set, expected TER, expected stored rate}
std::vector<TestCase> const testData = {
{1.0, tesSUCCESS, 1.0},
{1.1, tesSUCCESS, 1.1},
{2.0, tesSUCCESS, 2.0},
{2.1, temBAD_TRANSFER_RATE, 2.0}, // > 2.0 is invalid
{0.0, tesSUCCESS, 1.0}, // 0 clears the rate (default = 1.0)
{2.0, tesSUCCESS, 2.0},
{0.9, temBAD_TRANSFER_RATE, 2.0}, // < 1.0 is invalid
};
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
for (auto const& r : testData)
{
auto const rateValue = static_cast<std::uint32_t>(QUALITY_ONE * r.set);
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setTransferRate(rateValue), alice)
.ter,
r.code);
env.close();
// If the field is not present, expect the default value (1.0)
if (!env.getAccountRoot(alice).hasTransferRate())
{
EXPECT_EQ(r.get, 1.0);
}
else
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(
*env.getAccountRoot(alice).getTransferRate(),
static_cast<std::uint32_t>(r.get * QUALITY_ONE));
}
}
}
TEST(AccountSet, BadInputs)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
// Setting and clearing the same flag is invalid
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfDisallowXRP)
.setClearFlag(asfDisallowXRP),
alice)
.ter,
temINVALID_FLAG);
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfRequireAuth)
.setClearFlag(asfRequireAuth),
alice)
.ter,
temINVALID_FLAG);
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfRequireDest)
.setClearFlag(asfRequireDest),
alice)
.ter,
temINVALID_FLAG);
// Setting asf flag while also using corresponding tf flag is invalid
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfDisallowXRP)
.setFlags(tfAllowXRP),
alice)
.ter,
temINVALID_FLAG);
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfRequireAuth)
.setFlags(tfOptionalAuth),
alice)
.ter,
temINVALID_FLAG);
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfRequireDest)
.setFlags(tfOptionalDestTag),
alice)
.ter,
temINVALID_FLAG);
// Using invalid flags (mask) is invalid
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{alice}
.setSetFlag(asfRequireDest)
.setFlags(tfAccountSetMask),
alice)
.ter,
temINVALID_FLAG);
// Disabling master key without an alternative key is invalid
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfDisableMaster), alice).ter,
tecNO_ALTERNATIVE_KEY);
}
TEST(AccountSet, RequireAuthWithDir)
{
TxTest env;
Account const alice("alice");
Account const bob("bob");
env.createAccount(alice, XRP(10000));
env.close();
// alice should have an empty directory
EXPECT_TRUE(dirIsEmpty(env.getClosedLedger(), keylet::ownerDir(alice.id())));
// Give alice a signer list, then there will be stuff in the directory
// Build the SignerEntries array
STArray signerEntries(1);
{
signerEntries.push_back(STObject::makeInnerObject(sfSignerEntry));
STObject& entry = signerEntries.back();
entry[sfAccount] = bob.id();
entry[sfSignerWeight] = std::uint16_t{1};
}
EXPECT_EQ(
env.submit(
transactions::SignerListSetBuilder{alice, 1}.setSignerEntries(signerEntries), alice)
.ter,
tesSUCCESS);
env.close();
EXPECT_FALSE(dirIsEmpty(env.getClosedLedger(), keylet::ownerDir(alice.id())));
// Setting RequireAuth should fail because alice has owner objects
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfRequireAuth), alice).ter,
tecOWNERS);
// Remove the signer list (quorum = 0, no entries)
EXPECT_EQ(env.submit(transactions::SignerListSetBuilder{alice, 0}, alice).ter, tesSUCCESS);
env.close();
EXPECT_TRUE(dirIsEmpty(env.getClosedLedger(), keylet::ownerDir(alice.id())));
// Now setting RequireAuth should succeed
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setSetFlag(asfRequireAuth), alice).ter,
tesSUCCESS);
}
TEST(AccountSet, Ticket)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
// Get alice's current sequence - the ticket will be created at seq + 1
std::uint32_t const aliceSeqBefore = env.getAccountRoot(alice.id()).getSequence();
std::uint32_t const ticketSeq = aliceSeqBefore + 1;
// Create a ticket
EXPECT_EQ(env.submit(transactions::TicketCreateBuilder{alice, 1}, alice).ter, tesSUCCESS);
env.close();
// Verify alice has 1 owner object (the ticket)
EXPECT_EQ(env.getAccountRoot(alice.id()).getOwnerCount(), 1u);
// Verify ticket exists
EXPECT_TRUE(env.getClosedLedger().exists(keylet::ticket(alice.id(), ticketSeq)));
// Try using a ticket that alice doesn't have
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setTicketSequence(ticketSeq + 1), alice)
.ter,
terPRE_TICKET);
env.close();
// Verify ticket still exists
EXPECT_TRUE(env.getClosedLedger().exists(keylet::ticket(alice.id(), ticketSeq)));
// Get alice's sequence before using the ticket
std::uint32_t const aliceSeq = env.getAccountRoot(alice.id()).getSequence();
// Actually use alice's ticket (noop AccountSet)
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setTicketSequence(ticketSeq), alice).ter,
tesSUCCESS);
env.close();
// Verify ticket is consumed (no owner objects)
EXPECT_EQ(env.getAccountRoot(alice.id()).getOwnerCount(), 0u);
EXPECT_FALSE(env.getClosedLedger().exists(keylet::ticket(alice.id(), ticketSeq)));
// Verify alice's sequence did NOT advance (ticket use doesn't increment seq)
EXPECT_EQ(env.getAccountRoot(alice.id()).getSequence(), aliceSeq);
// Try re-using a ticket that alice already used
EXPECT_EQ(
env.submit(transactions::AccountSetBuilder{alice}.setTicketSequence(ticketSeq), alice).ter,
tefNO_TICKET);
}
TEST(AccountSet, BadSigningKey)
{
TxTest env;
Account const alice("alice");
env.createAccount(alice, XRP(10000));
env.close();
// Build a valid transaction first, then corrupt the signing key
auto stx = transactions::AccountSetBuilder{alice}
.setSequence(env.getAccountRoot(alice.id()).getSequence())
.setFee(XRPAmount{10})
.build(alice.pk(), alice.sk())
.getSTTx();
// Create a copy with a bad signing key
STObject obj = *stx;
obj.setFieldVL(sfSigningPubKey, makeSlice(std::string("badkey")));
auto result = env.submit(std::make_shared<STTx>(std::move(obj)));
EXPECT_EQ(result.ter, temBAD_SIGNATURE);
EXPECT_FALSE(result.applied);
}
TEST(AccountSet, Gateway)
{
Account const alice("alice");
Account const bob("bob");
Account const gw("gateway");
IOU const USD("USD", gw);
// Test gateway with a variety of allowed transfer rates
for (double transferRate = 1.0; transferRate <= 2.0; transferRate += 0.03125)
{
TxTest env;
env.createAccount(gw, XRP(10000), asfDefaultRipple);
env.createAccount(alice, XRP(10000), asfDefaultRipple);
env.createAccount(bob, XRP(10000), asfDefaultRipple);
env.close();
// Set up trust lines: alice and bob trust gw for USD
EXPECT_EQ(
env.submit(transactions::TrustSetBuilder{alice}.setLimitAmount(USD.amount(10)), alice)
.ter,
tesSUCCESS);
EXPECT_EQ(
env.submit(transactions::TrustSetBuilder{bob}.setLimitAmount(USD.amount(10)), bob).ter,
tesSUCCESS);
env.close();
// Set transfer rate on the gateway
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{gw}.setTransferRate(
static_cast<std::uint32_t>(transferRate * QUALITY_ONE)),
gw)
.ter,
tesSUCCESS);
env.close();
// Calculate the amount with transfer rate applied
auto const amount = USD.amount(1);
Rate const rate(static_cast<std::uint32_t>(transferRate * QUALITY_ONE));
auto const amountWithRate = multiply(amount, rate);
// Gateway pays alice 10 USD
EXPECT_EQ(
env.submit(transactions::PaymentBuilder{gw, alice, USD.amount(10)}, gw).ter,
tesSUCCESS);
env.close();
// Alice pays bob 1 USD (with sendmax to cover transfer fee)
EXPECT_EQ(
env.submit(
transactions::PaymentBuilder{alice, bob, USD.amount(1)}.setSendMax(
USD.amount(10)),
alice)
.ter,
tesSUCCESS);
env.close();
// Check balances
EXPECT_EQ(env.getBalance(alice.id(), USD), USD.amount(10) - amountWithRate);
EXPECT_EQ(env.getBalance(bob.id(), USD), USD.amount(1));
}
// Test out-of-bounds legacy transfer rates (4.0 and 4.294967295)
// These require direct ledger modification since the transactor blocks them
for (std::uint32_t const transferRate : {4000000000U, 4294967295U})
{
TxTest env;
env.createAccount(gw, XRP(10000), asfDefaultRipple);
env.createAccount(alice, XRP(10000), asfDefaultRipple);
env.createAccount(bob, XRP(10000), asfDefaultRipple);
env.close();
// Set up trust lines
EXPECT_EQ(
env.submit(transactions::TrustSetBuilder{alice}.setLimitAmount(USD.amount(10)), alice)
.ter,
tesSUCCESS);
EXPECT_EQ(
env.submit(transactions::TrustSetBuilder{bob}.setLimitAmount(USD.amount(10)), bob).ter,
tesSUCCESS);
env.close();
// Set an acceptable transfer rate first (we'll hack it later)
EXPECT_EQ(
env.submit(
transactions::AccountSetBuilder{gw}.setTransferRate(
static_cast<std::uint32_t>(2.0 * QUALITY_ONE)),
gw)
.ter,
tesSUCCESS);
env.close();
// Directly modify the ledger to set an out-of-bounds transfer rate
// This bypasses the transactor's validation
auto& view = env.getOpenLedger();
auto slePtr = view.read(keylet::account(gw.id()));
ASSERT_NE(slePtr, nullptr);
auto sleCopy = std::make_shared<SLE>(*slePtr);
(*sleCopy)[sfTransferRate] = transferRate;
view.rawReplace(sleCopy);
// Calculate the amount with the legacy transfer rate
auto const amount = USD.amount(1);
auto const amountWithRate = multiply(amount, Rate(transferRate));
// Gateway pays alice 10 USD
EXPECT_EQ(
env.submit(transactions::PaymentBuilder{gw, alice, USD.amount(10)}, gw).ter,
tesSUCCESS);
// Alice pays bob 1 USD
EXPECT_EQ(
env.submit(
transactions::PaymentBuilder{alice, bob, amount}.setSendMax(USD.amount(10)),
alice)
.ter,
tesSUCCESS);
// Check balances
EXPECT_EQ(env.getBalance(alice.id(), USD), USD.amount(10) - amountWithRate);
EXPECT_EQ(env.getBalance(bob.id(), USD), amount);
}
}
} // namespace xrpl::test

View File

@@ -0,0 +1,8 @@
#include <gtest/gtest.h>
int
main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -78,11 +78,9 @@
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/resource/Charge.h>

View File

@@ -12,10 +12,10 @@ BasicApp::BasicApp(std::size_t numberOfThreads)
work_.emplace(boost::asio::make_work_guard(io_context_));
threads_.reserve(numberOfThreads);
while ((numberOfThreads--) != 0u)
for (std::size_t i = 0; i < numberOfThreads; ++i)
{
threads_.emplace_back([this, numberOfThreads]() {
beast::setCurrentThreadName("io svc #" + std::to_string(numberOfThreads));
threads_.emplace_back([this, i]() {
beast::setCurrentThreadName("io svc #" + std::to_string(i));
this->io_context_.run();
});
}

View File

@@ -48,7 +48,6 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
@@ -614,7 +613,7 @@ GRPCServerImpl::createServerCredentials()
try
{
std::error_code ec;
boost::system::error_code ec;
grpc::SslServerCredentialsOptions sslOpts;
grpc::SslServerCredentialsOptions::PemKeyCertPair keyCertPair;

View File

@@ -8,7 +8,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STValidation.h>

View File

@@ -24,23 +24,19 @@
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <iomanip>
#include <ios>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
@@ -372,11 +368,11 @@ void
SHAMapStoreImp::dbPaths()
{
Section const section{app_.config().section(ConfigSection::nodeDatabase())};
std::filesystem::path dbPath = get(section, "path");
boost::filesystem::path dbPath = get(section, "path");
if (std::filesystem::exists(dbPath))
if (boost::filesystem::exists(dbPath))
{
if (!std::filesystem::is_directory(dbPath))
if (!boost::filesystem::is_directory(dbPath))
{
journal_.error() << "node db path must be a directory. " << dbPath.string();
Throw<std::runtime_error>("node db path must be a directory.");
@@ -384,7 +380,7 @@ SHAMapStoreImp::dbPaths()
}
else
{
std::filesystem::create_directories(dbPath);
boost::filesystem::create_directories(dbPath);
}
SavedState state = state_db_.getState();
@@ -395,8 +391,8 @@ SHAMapStoreImp::dbPaths()
return false;
// Check if configured "path" matches stored directory path
using namespace std::filesystem;
auto const stored{std::filesystem::path(sPath)};
using namespace boost::filesystem;
auto const stored{path(sPath)};
if (stored.parent_path() == dbPath)
return false;
@@ -414,9 +410,9 @@ SHAMapStoreImp::dbPaths()
bool writableDbExists = false;
bool archiveDbExists = false;
std::vector<std::filesystem::path> pathsToDelete;
for (std::filesystem::directory_iterator it(dbPath);
it != std::filesystem::directory_iterator();
std::vector<boost::filesystem::path> pathsToDelete;
for (boost::filesystem::directory_iterator it(dbPath);
it != boost::filesystem::directory_iterator();
++it)
{
if (state.writableDb.compare(it->path().string()) == 0)
@@ -437,7 +433,7 @@ SHAMapStoreImp::dbPaths()
(!archiveDbExists && !state.archiveDb.empty()) || (writableDbExists != archiveDbExists) ||
state.writableDb.empty() != state.archiveDb.empty())
{
std::filesystem::path stateDbPathName = app_.config().legacy("database_path");
boost::filesystem::path stateDbPathName = app_.config().legacy("database_path");
stateDbPathName /= dbName_;
stateDbPathName += "*";
@@ -459,15 +455,15 @@ SHAMapStoreImp::dbPaths()
}
// The necessary directories exist. Now, remove any others.
for (std::filesystem::path const& p : pathsToDelete)
std::filesystem::remove_all(p);
for (boost::filesystem::path const& p : pathsToDelete)
boost::filesystem::remove_all(p);
}
std::unique_ptr<NodeStore::Backend>
SHAMapStoreImp::makeBackendRotating(std::string path)
{
Section section{app_.config().section(ConfigSection::nodeDatabase())};
std::filesystem::path newPath;
boost::filesystem::path newPath;
if (!path.empty())
{
@@ -475,24 +471,10 @@ SHAMapStoreImp::makeBackendRotating(std::string path)
}
else
{
std::filesystem::path const p = get(section, "path");
std::random_device rd;
constexpr std::size_t maxAttempts = 100;
for (std::size_t attempt = 0; attempt < maxAttempts; ++attempt)
{
std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(8) << rd() << std::setw(8) << rd();
auto const candidate =
std::filesystem::path((p / dbPrefix_).string() + "." + oss.str());
std::error_code existsEc;
if (!std::filesystem::exists(candidate, existsEc) && !existsEc)
{
newPath = candidate;
break;
}
}
if (newPath.empty())
Throw<std::runtime_error>("Unable to generate a unique rotating backend path");
boost::filesystem::path p = get(section, "path");
p /= dbPrefix_;
p += ".%%%%";
newPath = boost::filesystem::unique_path(p);
}
section.set("path", newPath.string());

View File

@@ -12,7 +12,6 @@
#include <boost/thread/shared_mutex.hpp>
#include <filesystem>
#include <mutex>
#include <shared_mutex>
@@ -205,7 +204,7 @@ class ValidatorList
ManifestCache& validatorManifests_;
ManifestCache& publisherManifests_;
TimeKeeper& timeKeeper_;
std::filesystem::path const dataPath_;
boost::filesystem::path const dataPath_;
beast::Journal const j_;
std::shared_mutex mutable mutex_;
using lock_guard = std::lock_guard<decltype(mutex_)>;
@@ -804,7 +803,7 @@ private:
/** Get the filename used for caching UNLs
*/
std::filesystem::path
boost::filesystem::path
getCacheFileName(lock_guard const&, PublicKey const& pubKey) const;
/** Build a Json representation of the collection, suitable for

View File

@@ -29,8 +29,12 @@
#include <xrpl/server/Manifest.h>
#include <xrpl/server/NetworkOPs.h>
#include <boost/filesystem/operations.hpp>
#include <boost/regex/v5/regex.hpp>
#include <boost/regex/v5/regex_match.hpp>
#include <boost/system/detail/errc.hpp>
#include <boost/system/detail/error_code.hpp>
#include <boost/system/errc.hpp>
#include <xrpl.pb.h>
@@ -39,7 +43,6 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <iterator>
#include <limits>
@@ -51,7 +54,6 @@
#include <shared_mutex>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
@@ -286,7 +288,7 @@ ValidatorList::load(
return true;
}
std::filesystem::path
boost::filesystem::path
ValidatorList::getCacheFileName(ValidatorList::lock_guard const&, PublicKey const& pubKey) const
{
return dataPath_ / (filePrefix_ + strHex(pubKey));
@@ -370,9 +372,9 @@ ValidatorList::cacheValidatorFile(ValidatorList::lock_guard const& lock, PublicK
if (dataPath_.empty())
return;
std::filesystem::path const filename = getCacheFileName(lock, pubKey);
boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
std::error_code ec;
boost::system::error_code ec;
Json::Value value = buildFileData(strHex(pubKey), publisherLists_.at(pubKey), j_);
// xrpld should be the only process writing to this file, so
@@ -1281,7 +1283,8 @@ std::vector<std::string>
ValidatorList::loadLists()
{
using namespace std::string_literals;
using namespace std::filesystem;
using namespace boost::filesystem;
using namespace boost::system::errc;
std::lock_guard const lock{mutex_};
@@ -1289,12 +1292,12 @@ ValidatorList::loadLists()
sites.reserve(publisherLists_.size());
for (auto const& [pubKey, publisherCollection] : publisherLists_)
{
std::error_code ec;
boost::system::error_code ec;
if (publisherCollection.status == PublisherStatus::available)
continue;
std::filesystem::path const filename = getCacheFileName(lock, pubKey);
boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
auto const fullPath{canonical(filename, ec)};
if (ec)
@@ -1305,7 +1308,7 @@ ValidatorList::loadLists()
{
// Treat an empty file as a missing file, because
// nobody else is going to write it.
ec = make_error_code(std::errc::no_such_file_or_directory);
ec = make_error_code(no_such_file_or_directory);
}
if (ec)
continue;

View File

@@ -161,7 +161,11 @@ ValidatorSite::load(
{
try
{
sites_.emplace_back(uri);
// This is not super efficient, but it doesn't happen often.
bool found = std::ranges::any_of(
sites_, [&uri](auto const& site) { return site.loadedResource->uri == uri; });
if (!found)
sites_.emplace_back(uri);
}
catch (std::exception const& e)
{
@@ -222,7 +226,17 @@ ValidatorSite::setTimer(
std::lock_guard<std::mutex> const& site_lock,
std::lock_guard<std::mutex> const& state_lock)
{
auto next = std::ranges::min_element(
if (!sites_.empty() && //
std::ranges::all_of(
sites_, [](auto const& site) { return site.lastRefreshStatus.has_value(); }))
{
// If all of the sites have been handled at least once (including
// errors and timeouts), call missingSite, which will load the cache
// files for any lists that are still unavailable.
missingSite(site_lock);
}
auto const next = std::ranges::min_element(
sites_, [](Site const& a, Site const& b) { return a.nextRefresh < b.nextRefresh; });
if (next != sites_.end())
@@ -333,7 +347,7 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec)
// processes a network error. Usually, this function runs first,
// but on extremely rare occasions, the response handler can run
// first, which will leave activeResource empty.
auto const& site = sites_[siteIdx];
auto& site = sites_[siteIdx];
if (site.activeResource)
{
JLOG(j_.warn()) << "Request for " << site.activeResource->uri << " took too long";
@@ -341,6 +355,9 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec)
else
JLOG(j_.error()) << "Request took too long, but a response has "
"already been processed";
if (!site.lastRefreshStatus)
site.lastRefreshStatus.emplace(
Site::Status{clock_type::now(), ListDisposition::invalid, "timeout"});
}
std::lock_guard const lock_state{state_mutex_};

View File

@@ -37,8 +37,10 @@
#include <xrpl/rdb/RelationalDatabase.h>
#include <xrpl/rdb/SociDB.h>
#include <boost/filesystem/operations.hpp>
#include <boost/format/free_funcs.hpp>
#include <boost/optional/optional.hpp>
#include <boost/system/detail/error_code.hpp>
#include <soci/blob.h>
#include <soci/into.h>
@@ -51,7 +53,6 @@
#include <cstddef>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <functional>
#include <limits>
#include <map>
@@ -60,7 +61,6 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
#include <variant>
#include <vector>
@@ -1290,8 +1290,8 @@ getTransaction(
bool
dbHasSpace(soci::session& session, Config const& config, beast::Journal j)
{
std::filesystem::space_info const space =
std::filesystem::space(config.legacy("database_path"));
boost::filesystem::space_info const space =
boost::filesystem::space(config.legacy("database_path"));
if (space.available < megabytes(512))
{
@@ -1302,9 +1302,9 @@ dbHasSpace(soci::session& session, Config const& config, beast::Journal j)
if (config.useTxTables())
{
DatabaseCon::Setup const dbSetup = setup_DatabaseCon(config);
std::filesystem::path const dbPath = dbSetup.dataDir / TxDBName;
std::error_code ec;
std::optional<std::uint64_t> dbSize = std::filesystem::file_size(dbPath, ec);
boost::filesystem::path const dbPath = dbSetup.dataDir / TxDBName;
boost::system::error_code ec;
std::optional<std::uint64_t> dbSize = boost::filesystem::file_size(dbPath, ec);
if (ec)
{
JLOG(j.error()) << "Error checking transaction db file size: " << ec.message();

View File

@@ -9,8 +9,9 @@
#include <xrpl/protocol/SystemParameters.h> // VFALCO Breaks levelization
#include <xrpl/rdb/DatabaseCon.h>
#include <boost/filesystem.hpp> // VFALCO FIX: This include should not be here
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <unordered_set>
@@ -81,17 +82,17 @@ public:
static char const* const validatorsFileName;
/** Returns the full path and filename of the debug log file. */
[[nodiscard]] std::filesystem::path
[[nodiscard]] boost::filesystem::path
getDebugLogFile() const;
private:
std::filesystem::path CONFIG_FILE;
boost::filesystem::path CONFIG_FILE;
public:
std::filesystem::path CONFIG_DIR;
boost::filesystem::path CONFIG_DIR;
private:
std::filesystem::path DEBUG_LOGFILE;
boost::filesystem::path DEBUG_LOGFILE;
void
load();

View File

@@ -22,19 +22,21 @@
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format/free_funcs.hpp>
#include <boost/multiprecision/detail/endian.hpp>
#include <boost/predef.h>
#include <boost/regex.hpp> // IWYU pragma: keep
#include <boost/regex/v5/regex.hpp>
#include <boost/regex/v5/regex_match.hpp>
#include <boost/system/detail/error_code.hpp>
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <iterator>
#include <limits>
@@ -44,7 +46,6 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <thread>
#include <type_traits>
#include <utility>
@@ -312,13 +313,13 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
// directory, use the current working directory as the
// config directory and that with "db" as the data
// directory.
std::filesystem::path dataDir;
boost::filesystem::path dataDir;
if (!strConf.empty())
{
// --conf=<path> : everything is relative that file.
CONFIG_FILE = strConf;
CONFIG_DIR = std::filesystem::absolute(CONFIG_FILE);
CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE);
CONFIG_DIR.remove_filename();
dataDir = CONFIG_DIR / databaseDirName;
}
@@ -329,13 +330,13 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
// Check if either of the config files exist in the current working
// directory, in which case the databases will be stored in a
// subdirectory.
CONFIG_DIR = std::filesystem::current_path();
CONFIG_DIR = boost::filesystem::current_path();
dataDir = CONFIG_DIR / databaseDirName;
CONFIG_FILE = CONFIG_DIR / configFileName;
if (std::filesystem::exists(CONFIG_FILE))
if (boost::filesystem::exists(CONFIG_FILE))
break;
CONFIG_FILE = CONFIG_DIR / configLegacyName;
if (std::filesystem::exists(CONFIG_FILE))
if (boost::filesystem::exists(CONFIG_FILE))
break;
// Check if the home directory is set, and optionally the XDG config
@@ -362,10 +363,10 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
dataDir = strXdgDataHome + "/" + systemName();
CONFIG_DIR = strXdgConfigHome + "/" + systemName();
CONFIG_FILE = CONFIG_DIR / configFileName;
if (std::filesystem::exists(CONFIG_FILE))
if (boost::filesystem::exists(CONFIG_FILE))
break;
CONFIG_FILE = CONFIG_DIR / configLegacyName;
if (std::filesystem::exists(CONFIG_FILE))
if (boost::filesystem::exists(CONFIG_FILE))
break;
}
@@ -373,7 +374,7 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
dataDir = "/var/opt/" + systemName();
CONFIG_DIR = "/etc/opt/" + systemName();
CONFIG_FILE = CONFIG_DIR / configFileName;
if (std::filesystem::exists(CONFIG_FILE))
if (boost::filesystem::exists(CONFIG_FILE))
break;
CONFIG_FILE = CONFIG_DIR / configLegacyName;
} while (false);
@@ -386,7 +387,7 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
std::string const dbPath(legacy("database_path"));
if (!dbPath.empty())
{
dataDir = std::filesystem::path(dbPath);
dataDir = boost::filesystem::path(dbPath);
}
else if (RUN_STANDALONE)
{
@@ -396,13 +397,13 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
if (!dataDir.empty())
{
std::error_code ec;
std::filesystem::create_directories(dataDir, ec);
boost::system::error_code ec;
boost::filesystem::create_directories(dataDir, ec);
if (ec)
Throw<std::runtime_error>(boost::str(boost::format("Can not create %s") % dataDir));
legacy("database_path", std::filesystem::absolute(dataDir).string());
legacy("database_path", boost::filesystem::absolute(dataDir).string());
}
HTTPClient::initializeSSLContext(
@@ -455,7 +456,7 @@ Config::load()
if (!QUIET)
std::cerr << "Loading: " << CONFIG_FILE << "\n";
std::error_code ec;
boost::system::error_code ec;
auto const fileContents = getFileContents(ec, CONFIG_FILE);
if (ec)
@@ -508,8 +509,8 @@ Config::loadFromString(std::string const& fileContents)
std::string dbPath;
if (getSingleSection(secConfig, "database_path", dbPath, j_))
{
std::filesystem::path const p(dbPath);
legacy("database_path", std::filesystem::absolute(p).string());
boost::filesystem::path const p(dbPath);
legacy("database_path", boost::filesystem::absolute(p).string());
}
}
@@ -952,7 +953,7 @@ Config::loadFromString(std::string const& fileContents)
// If no path was specified, then look for validators.txt
// in the same directory as the config file, but don't complain
// if we can't find it.
std::filesystem::path validatorsFile;
boost::filesystem::path validatorsFile;
if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_))
{
@@ -967,7 +968,7 @@ Config::loadFromString(std::string const& fileContents)
if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty())
validatorsFile = CONFIG_DIR / validatorsFile;
if (!std::filesystem::exists(validatorsFile))
if (!boost::filesystem::exists(validatorsFile))
{
Throw<std::runtime_error>(
"The file specified in [" SECTION_VALIDATORS_FILE
@@ -976,8 +977,8 @@ Config::loadFromString(std::string const& fileContents)
validatorsFile.string());
}
else if (
!std::filesystem::is_regular_file(validatorsFile) &&
!std::filesystem::is_symlink(validatorsFile))
!boost::filesystem::is_regular_file(validatorsFile) &&
!boost::filesystem::is_symlink(validatorsFile))
{
Throw<std::runtime_error>(
"Invalid file specified in [" SECTION_VALIDATORS_FILE "]: " +
@@ -990,24 +991,24 @@ Config::loadFromString(std::string const& fileContents)
if (!validatorsFile.empty())
{
if (!std::filesystem::exists(validatorsFile))
if (!boost::filesystem::exists(validatorsFile))
{
validatorsFile.clear();
}
else if (
!std::filesystem::is_regular_file(validatorsFile) &&
!std::filesystem::is_symlink(validatorsFile))
!boost::filesystem::is_regular_file(validatorsFile) &&
!boost::filesystem::is_symlink(validatorsFile))
{
validatorsFile.clear();
}
}
}
if (!validatorsFile.empty() && std::filesystem::exists(validatorsFile) &&
(std::filesystem::is_regular_file(validatorsFile) ||
std::filesystem::is_symlink(validatorsFile)))
if (!validatorsFile.empty() && boost::filesystem::exists(validatorsFile) &&
(boost::filesystem::is_regular_file(validatorsFile) ||
boost::filesystem::is_symlink(validatorsFile)))
{
std::error_code ec;
boost::system::error_code ec;
auto const data = getFileContents(ec, validatorsFile);
if (ec)
{
@@ -1133,7 +1134,7 @@ Config::loadFromString(std::string const& fileContents)
}
}
std::filesystem::path
boost::filesystem::path
Config::getDebugLogFile() const
{
auto log_file = DEBUG_LOGFILE;
@@ -1142,17 +1143,17 @@ Config::getDebugLogFile() const
{
// Unless an absolute path for the log file is specified, the
// path is relative to the config file directory.
log_file = CONFIG_DIR / log_file;
log_file = boost::filesystem::absolute(log_file, CONFIG_DIR);
}
if (!log_file.empty())
{
auto log_dir = log_file.parent_path();
if (!std::filesystem::is_directory(log_dir))
if (!boost::filesystem::is_directory(log_dir))
{
std::error_code ec;
std::filesystem::create_directories(log_dir, ec);
boost::system::error_code ec;
boost::filesystem::create_directories(log_dir, ec);
// If we fail, we warn but continue so that the calling code can
// decide how to handle this situation.

View File

@@ -8,6 +8,9 @@
namespace xrpl::PeerFinder {
/** Direction of a slot count adjustment. */
enum class CountAdjustment : int { Decrement = -1, Increment = 1 };
/** Manages the count of available connections for the various slots. */
class Counts
{
@@ -16,14 +19,14 @@ public:
void
add(Slot const& s)
{
adjust(s, 1);
adjust(s, CountAdjustment::Increment);
}
/** Removes the slot state and properties from the slot counts. */
void
remove(Slot const& s)
{
adjust(s, -1);
adjust(s, CountAdjustment::Decrement);
}
/** Returns `true` if the slot can become active. */
@@ -207,21 +210,40 @@ public:
//--------------------------------------------------------------------------
private:
/** Increments or decrements a counter based on the adjustment direction. */
template <typename T>
static void
adjustCounter(T& counter, CountAdjustment dir)
{
switch (dir)
{
case CountAdjustment::Increment:
++counter;
break;
case CountAdjustment::Decrement:
--counter;
break;
}
}
// Adjusts counts based on the specified slot, in the direction indicated.
// Using ++/-- instead of += on std::size_t counters avoids UBSan
// unsigned-integer-overflow from implicit conversion of -1 to SIZE_MAX.
// A decrement on a zero counter is a real bug that UBSan should catch.
void
adjust(Slot const& s, int const n)
adjust(Slot const& s, CountAdjustment const dir)
{
if (s.fixed())
m_fixed += n;
adjustCounter(m_fixed, dir);
if (s.reserved())
m_reserved += n;
adjustCounter(m_reserved, dir);
switch (s.state())
{
case Slot::accept:
XRPL_ASSERT(s.inbound(), "xrpl::PeerFinder::Counts::adjust : input is inbound");
m_acceptCount += n;
adjustCounter(m_acceptCount, dir);
break;
case Slot::connect:
@@ -230,28 +252,28 @@ private:
!s.inbound(),
"xrpl::PeerFinder::Counts::adjust : input is not "
"inbound");
m_attempts += n;
adjustCounter(m_attempts, dir);
break;
case Slot::active:
if (s.fixed())
m_fixed_active += n;
adjustCounter(m_fixed_active, dir);
if (!s.fixed() && !s.reserved())
{
if (s.inbound())
{
m_in_active += n;
adjustCounter(m_in_active, dir);
}
else
{
m_out_active += n;
adjustCounter(m_out_active, dir);
}
}
m_active += n;
adjustCounter(m_active, dir);
break;
case Slot::closing:
m_closingCount += n;
adjustCounter(m_closingCount, dir);
break;
// LCOV_EXCL_START

View File

@@ -13,9 +13,11 @@
#include <xrpl/json/json_writer.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem/operations.hpp>
#include <boost/system/detail/error_code.hpp>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <ios>
#include <memory>
@@ -23,7 +25,6 @@
#include <ostream>
#include <set>
#include <string>
#include <system_error>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -215,10 +216,10 @@ PerfLogImp::openLog()
logFile_.close();
auto logDir = setup_.perfLog.parent_path();
if (!std::filesystem::is_directory(logDir))
if (!boost::filesystem::is_directory(logDir))
{
std::error_code ec;
std::filesystem::create_directories(logDir, ec);
boost::system::error_code ec;
boost::filesystem::create_directories(logDir, ec);
if (ec)
{
JLOG(j_.fatal()) << "Unable to create performance log "
@@ -473,17 +474,17 @@ PerfLogImp::stop()
//-----------------------------------------------------------------------------
PerfLog::Setup
setup_PerfLog(Section const& section, std::filesystem::path const& configDir)
setup_PerfLog(Section const& section, boost::filesystem::path const& configDir)
{
PerfLog::Setup setup;
std::string perfLog;
set(perfLog, "perf_log", section);
if (!perfLog.empty())
{
setup.perfLog = std::filesystem::path(perfLog);
setup.perfLog = boost::filesystem::path(perfLog);
if (setup.perfLog.is_relative())
{
setup.perfLog = configDir / setup.perfLog;
setup.perfLog = boost::filesystem::absolute(setup.perfLog, configDir);
}
}