mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Compare commits
8 Commits
nd-allow-m
...
fix-online
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61b364ec82 | ||
|
|
7fae6c3eb7 | ||
|
|
70dd2a0f0e | ||
|
|
d15063bca4 | ||
|
|
998ae5535b | ||
|
|
4c41d32276 | ||
|
|
a2137d5436 | ||
|
|
a84d72a7f7 |
1
.github/actions/xahau-ga-build/action.yml
vendored
1
.github/actions/xahau-ga-build/action.yml
vendored
@@ -59,6 +59,7 @@ runs:
|
||||
${{ runner.os }}-ccache-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-${{ inputs.configuration }}-${{ inputs.main_branch }}
|
||||
${{ runner.os }}-ccache-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-${{ inputs.configuration }}-
|
||||
${{ runner.os }}-ccache-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-
|
||||
${{ runner.os }}-ccache-v${{ inputs.cache_version }}-
|
||||
|
||||
- name: Configure project
|
||||
shell: bash
|
||||
|
||||
@@ -54,6 +54,7 @@ runs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-conan-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-${{ hashFiles('**/conanfile.txt', '**/conanfile.py') }}-
|
||||
${{ runner.os }}-conan-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-
|
||||
${{ runner.os }}-conan-v${{ inputs.cache_version }}-
|
||||
|
||||
- name: Export custom recipes
|
||||
shell: bash
|
||||
|
||||
19
.github/workflows/xahau-ga-nix.yml
vendored
19
.github/workflows/xahau-ga-nix.yml
vendored
@@ -22,14 +22,13 @@ jobs:
|
||||
configuration: [Debug]
|
||||
include:
|
||||
- compiler: gcc
|
||||
cc: gcc-13
|
||||
cxx: g++-13
|
||||
compiler_id: gcc-13
|
||||
compiler_version: 13
|
||||
cc: gcc-11
|
||||
cxx: g++-11
|
||||
compiler_id: gcc-11
|
||||
env:
|
||||
build_dir: .build
|
||||
# Bump this number to invalidate all caches globally.
|
||||
CACHE_VERSION: 2
|
||||
CACHE_VERSION: 1
|
||||
MAIN_BRANCH_NAME: dev
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -59,8 +58,12 @@ jobs:
|
||||
conan profile update env.CXX=/usr/bin/${{ matrix.cxx }} default
|
||||
conan profile update conf.tools.build:compiler_executables='{"c": "/usr/bin/${{ matrix.cc }}", "cpp": "/usr/bin/${{ matrix.cxx }}"}' default
|
||||
|
||||
# Set compiler version from matrix
|
||||
conan profile update settings.compiler.version=${{ matrix.compiler_version }} default
|
||||
# Set correct compiler version based on matrix.compiler
|
||||
if [ "${{ matrix.compiler }}" = "gcc" ]; then
|
||||
conan profile update settings.compiler.version=11 default
|
||||
elif [ "${{ matrix.compiler }}" = "clang" ]; then
|
||||
conan profile update settings.compiler.version=14 default
|
||||
fi
|
||||
# Display profile for verification
|
||||
conan profile show default
|
||||
|
||||
@@ -117,4 +120,4 @@ jobs:
|
||||
else
|
||||
echo "Error: rippled executable not found in ${{ env.build_dir }}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -994,11 +994,6 @@ if (tests)
|
||||
subdir: resource
|
||||
#]===============================]
|
||||
src/test/resource/Logic_test.cpp
|
||||
#[===============================[
|
||||
test sources:
|
||||
subdir: rdb
|
||||
#]===============================]
|
||||
src/test/rdb/RelationalDatabase_test.cpp
|
||||
#[===============================[
|
||||
test sources:
|
||||
subdir: rpc
|
||||
|
||||
@@ -186,10 +186,6 @@ test.protocol > ripple.crypto
|
||||
test.protocol > ripple.json
|
||||
test.protocol > ripple.protocol
|
||||
test.protocol > test.toplevel
|
||||
test.rdb > ripple.app
|
||||
test.rdb > ripple.core
|
||||
test.rdb > test.jtx
|
||||
test.rdb > test.toplevel
|
||||
test.resource > ripple.basics
|
||||
test.resource > ripple.beast
|
||||
test.resource > ripple.resource
|
||||
|
||||
@@ -1063,16 +1063,14 @@
|
||||
# RWDB is recommended for Validator and Peer nodes that are not required to
|
||||
# store history.
|
||||
#
|
||||
# Required keys for NuDB and RocksDB:
|
||||
# RWDB maintains its high speed regardless of the amount of history
|
||||
# stored. Online delete should NOT be used instead RWDB will use the
|
||||
# ledger_history config value to determine how many ledgers to keep in memory.
|
||||
#
|
||||
# Required keys for NuDB, RWDB and RocksDB:
|
||||
#
|
||||
# path Location to store the database
|
||||
#
|
||||
# Required keys for RWDB:
|
||||
#
|
||||
# online_delete Required. RWDB stores data in memory and will
|
||||
# grow unbounded without online_delete. See the
|
||||
# online_delete section below.
|
||||
#
|
||||
# Required keys for Cassandra:
|
||||
#
|
||||
# contact_points IP of a node in the Cassandra cluster
|
||||
@@ -1112,17 +1110,7 @@
|
||||
# if sufficient IOPS capacity is available.
|
||||
# Default 0.
|
||||
#
|
||||
# online_delete for RWDB, NuDB and RocksDB:
|
||||
#
|
||||
# online_delete Minimum value of 256. Enable automatic purging
|
||||
# of older ledger information. Maintain at least this
|
||||
# number of ledger records online. Must be greater
|
||||
# than or equal to ledger_history.
|
||||
#
|
||||
# REQUIRED for RWDB to prevent out-of-memory errors.
|
||||
# Optional for NuDB and RocksDB.
|
||||
#
|
||||
# Optional keys for NuDB and RocksDB:
|
||||
# Optional keys for NuDB or RocksDB:
|
||||
#
|
||||
# earliest_seq The default is 32570 to match the XRP ledger
|
||||
# network's earliest allowed sequence. Alternate
|
||||
@@ -1132,7 +1120,12 @@
|
||||
# it must be defined with the same value in both
|
||||
# sections.
|
||||
#
|
||||
|
||||
# online_delete Minimum value of 256. Enable automatic purging
|
||||
# of older ledger information. Maintain at least this
|
||||
# number of ledger records online. Must be greater
|
||||
# than or equal to ledger_history. If using RWDB
|
||||
# this value is ignored.
|
||||
#
|
||||
# These keys modify the behavior of online_delete, and thus are only
|
||||
# relevant if online_delete is defined and non-zero:
|
||||
#
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
@@ -28,8 +28,8 @@ private:
|
||||
|
||||
struct AccountTxData
|
||||
{
|
||||
std::map<uint32_t, std::vector<AccountTx>>
|
||||
ledgerTxMap; // ledgerSeq -> vector of transactions
|
||||
std::map<uint32_t, AccountTxs>
|
||||
ledgerTxMap; // ledgerSeq -> vector of AccountTx
|
||||
};
|
||||
|
||||
Application& app_;
|
||||
@@ -304,6 +304,46 @@ public:
|
||||
// Overwrite Current Ledger
|
||||
ledgers_[seq] = std::move(ledgerData);
|
||||
ledgerHashToSeq_[ledger->info().hash] = seq;
|
||||
|
||||
// Automatic cleanup based on LEDGER_HISTORY (ported from
|
||||
// FlatmapDatabase)
|
||||
if (current)
|
||||
{
|
||||
auto const cutoffSeq =
|
||||
ledger->info().seq > app_.config().LEDGER_HISTORY
|
||||
? ledger->info().seq - app_.config().LEDGER_HISTORY
|
||||
: 0;
|
||||
|
||||
if (cutoffSeq > 0)
|
||||
{
|
||||
// Delete old ledgers before cutoff
|
||||
auto it = ledgers_.begin();
|
||||
while (it != ledgers_.end() && it->first < cutoffSeq)
|
||||
{
|
||||
// Clean up transactions from this ledger
|
||||
for (const auto& [txHash, _] : it->second.transactions)
|
||||
{
|
||||
transactionMap_.erase(txHash);
|
||||
}
|
||||
ledgerHashToSeq_.erase(it->second.info.hash);
|
||||
it = ledgers_.erase(it);
|
||||
}
|
||||
|
||||
// Clean up account transactions before cutoff
|
||||
for (auto& [_, accountData] : accountTxMap_)
|
||||
{
|
||||
auto txIt = accountData.ledgerTxMap.begin();
|
||||
while (txIt != accountData.ledgerTxMap.end() &&
|
||||
txIt->first < cutoffSeq)
|
||||
{
|
||||
txIt = accountData.ledgerTxMap.erase(txIt);
|
||||
}
|
||||
}
|
||||
|
||||
app_.getLedgerMaster().clearPriorLedgers(cutoffSeq);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -445,108 +485,55 @@ public:
|
||||
return true; // In-memory database always has space
|
||||
}
|
||||
|
||||
// Red-black tree node overhead per map entry
|
||||
static constexpr size_t MAP_NODE_OVERHEAD = 40;
|
||||
|
||||
private:
|
||||
std::uint64_t
|
||||
getBytesUsedLedger_unlocked() const
|
||||
{
|
||||
std::uint64_t size = 0;
|
||||
|
||||
// Count structural overhead of ledger storage including map node
|
||||
// overhead Note: sizeof(LedgerData) includes the map container for
|
||||
// transactions, but not the actual transaction data
|
||||
size += ledgers_.size() *
|
||||
(sizeof(LedgerIndex) + sizeof(LedgerData) + MAP_NODE_OVERHEAD);
|
||||
|
||||
// Add the transaction map nodes inside each ledger (ledger's view of
|
||||
// its transactions)
|
||||
for (const auto& [_, ledgerData] : ledgers_)
|
||||
{
|
||||
size += ledgerData.transactions.size() *
|
||||
(sizeof(uint256) + sizeof(AccountTx) + MAP_NODE_OVERHEAD);
|
||||
}
|
||||
|
||||
// Count the ledger hash to sequence lookup map
|
||||
size += ledgerHashToSeq_.size() *
|
||||
(sizeof(uint256) + sizeof(LedgerIndex) + MAP_NODE_OVERHEAD);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
getBytesUsedTransaction_unlocked() const
|
||||
{
|
||||
if (!useTxTables_)
|
||||
return 0;
|
||||
|
||||
std::uint64_t size = 0;
|
||||
|
||||
// Count structural overhead of transaction map
|
||||
// sizeof(AccountTx) is just the size of two shared_ptrs (~32 bytes)
|
||||
size += transactionMap_.size() *
|
||||
(sizeof(uint256) + sizeof(AccountTx) + MAP_NODE_OVERHEAD);
|
||||
|
||||
// Add actual transaction and metadata data sizes
|
||||
for (const auto& [_, accountTx] : transactionMap_)
|
||||
{
|
||||
if (accountTx.first)
|
||||
size += accountTx.first->getSTransaction()
|
||||
->getSerializer()
|
||||
.peekData()
|
||||
.size();
|
||||
if (accountTx.second)
|
||||
size += accountTx.second->getAsObject()
|
||||
.getSerializer()
|
||||
.peekData()
|
||||
.size();
|
||||
}
|
||||
|
||||
// Count structural overhead of account transaction index
|
||||
// The actual transaction data is already counted above from
|
||||
// transactionMap_
|
||||
for (const auto& [accountId, accountData] : accountTxMap_)
|
||||
{
|
||||
size +=
|
||||
sizeof(accountId) + sizeof(AccountTxData) + MAP_NODE_OVERHEAD;
|
||||
for (const auto& [ledgerSeq, txVector] : accountData.ledgerTxMap)
|
||||
{
|
||||
// Use capacity() to account for actual allocated memory
|
||||
size += sizeof(ledgerSeq) + MAP_NODE_OVERHEAD;
|
||||
size += txVector.capacity() * sizeof(AccountTx);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public:
|
||||
std::uint32_t
|
||||
getKBUsedAll() override
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
// Total = base object + ledger infrastructure + transaction data
|
||||
std::uint64_t size = sizeof(*this) + getBytesUsedLedger_unlocked() +
|
||||
getBytesUsedTransaction_unlocked();
|
||||
|
||||
return static_cast<std::uint32_t>(size / 1024);
|
||||
std::uint32_t size = sizeof(*this);
|
||||
size += ledgers_.size() * (sizeof(LedgerIndex) + sizeof(LedgerData));
|
||||
size +=
|
||||
ledgerHashToSeq_.size() * (sizeof(uint256) + sizeof(LedgerIndex));
|
||||
size += transactionMap_.size() * (sizeof(uint256) + sizeof(AccountTx));
|
||||
for (const auto& [_, accountData] : accountTxMap_)
|
||||
{
|
||||
size += sizeof(AccountID) + sizeof(AccountTxData);
|
||||
for (const auto& [_, txVector] : accountData.ledgerTxMap)
|
||||
{
|
||||
size += sizeof(uint32_t) + txVector.size() * sizeof(AccountTx);
|
||||
}
|
||||
}
|
||||
return size / 1024;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getKBUsedLedger() override
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return static_cast<std::uint32_t>(getBytesUsedLedger_unlocked() / 1024);
|
||||
std::uint32_t size = 0;
|
||||
size += ledgers_.size() * (sizeof(LedgerIndex) + sizeof(LedgerData));
|
||||
size +=
|
||||
ledgerHashToSeq_.size() * (sizeof(uint256) + sizeof(LedgerIndex));
|
||||
return size / 1024;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getKBUsedTransaction() override
|
||||
{
|
||||
if (!useTxTables_)
|
||||
return 0;
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return static_cast<std::uint32_t>(
|
||||
getBytesUsedTransaction_unlocked() / 1024);
|
||||
std::uint32_t size = 0;
|
||||
size += transactionMap_.size() * (sizeof(uint256) + sizeof(AccountTx));
|
||||
for (const auto& [_, accountData] : accountTxMap_)
|
||||
{
|
||||
size += sizeof(AccountID) + sizeof(AccountTxData);
|
||||
for (const auto& [_, txVector] : accountData.ledgerTxMap)
|
||||
{
|
||||
size += sizeof(uint32_t) + txVector.size() * sizeof(AccountTx);
|
||||
}
|
||||
}
|
||||
return size / 1024;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -699,7 +686,7 @@ public:
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
AccountTx const accountTx = *innerRIt;
|
||||
const AccountTx& accountTx = *innerRIt;
|
||||
std::uint32_t const inLedger = rangeCheckedCast<std::uint32_t>(
|
||||
accountTx.second->getLgrSeq());
|
||||
accountTx.first->setLedger(inLedger);
|
||||
@@ -856,9 +843,9 @@ public:
|
||||
for (; txIt != txEnd; ++txIt)
|
||||
{
|
||||
std::uint32_t const ledgerSeq = txIt->first;
|
||||
std::uint32_t txnSeq = 0;
|
||||
for (const auto& accountTx : txIt->second)
|
||||
for (size_t txnSeq = 0; txnSeq < txIt->second.size(); ++txnSeq)
|
||||
{
|
||||
const auto& accountTx = txIt->second[txnSeq];
|
||||
if (lookingForMarker)
|
||||
{
|
||||
if (findLedger == ledgerSeq && findSeq == txnSeq)
|
||||
@@ -866,15 +853,13 @@ public:
|
||||
lookingForMarker = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
++txnSeq;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (numberOfResults == 0)
|
||||
{
|
||||
newmarker = {
|
||||
rangeCheckedCast<std::uint32_t>(ledgerSeq), txnSeq};
|
||||
rangeCheckedCast<std::uint32_t>(ledgerSeq),
|
||||
static_cast<std::uint32_t>(txnSeq)};
|
||||
return {newmarker, total};
|
||||
}
|
||||
|
||||
@@ -895,7 +880,6 @@ public:
|
||||
std::move(rawMeta));
|
||||
--numberOfResults;
|
||||
++total;
|
||||
++txnSeq;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -911,11 +895,10 @@ public:
|
||||
for (; rtxIt != rtxEnd; ++rtxIt)
|
||||
{
|
||||
std::uint32_t const ledgerSeq = rtxIt->first;
|
||||
std::uint32_t txnSeq = rtxIt->second.size() - 1;
|
||||
for (auto innerRIt = rtxIt->second.rbegin();
|
||||
innerRIt != rtxIt->second.rend();
|
||||
++innerRIt)
|
||||
for (int txnSeq = rtxIt->second.size() - 1; txnSeq >= 0;
|
||||
--txnSeq)
|
||||
{
|
||||
const auto& accountTx = rtxIt->second[txnSeq];
|
||||
if (lookingForMarker)
|
||||
{
|
||||
if (findLedger == ledgerSeq && findSeq == txnSeq)
|
||||
@@ -923,19 +906,16 @@ public:
|
||||
lookingForMarker = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
--txnSeq;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (numberOfResults == 0)
|
||||
{
|
||||
newmarker = {
|
||||
rangeCheckedCast<std::uint32_t>(ledgerSeq), txnSeq};
|
||||
rangeCheckedCast<std::uint32_t>(ledgerSeq),
|
||||
static_cast<std::uint32_t>(txnSeq)};
|
||||
return {newmarker, total};
|
||||
}
|
||||
|
||||
const auto& accountTx = *innerRIt;
|
||||
Blob rawTxn = accountTx.first->getSTransaction()
|
||||
->getSerializer()
|
||||
.peekData();
|
||||
@@ -953,7 +933,6 @@ public:
|
||||
std::move(rawMeta));
|
||||
--numberOfResults;
|
||||
++total;
|
||||
--txnSeq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,9 @@ public:
|
||||
boost::beast::iequals(
|
||||
get(section(SECTION_RELATIONAL_DB), "backend"), "rwdb")) ||
|
||||
(!section("node_db").empty() &&
|
||||
boost::beast::iequals(get(section("node_db"), "type"), "rwdb"));
|
||||
(boost::beast::iequals(get(section("node_db"), "type"), "rwdb") ||
|
||||
boost::beast::iequals(
|
||||
get(section("node_db"), "type"), "memory")));
|
||||
// RHNOTE: memory type is not selected for here because it breaks
|
||||
// tests
|
||||
return isMem;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
[[nodiscard]] std::uint64_t
|
||||
getMemorySize()
|
||||
{
|
||||
@@ -53,6 +54,7 @@ getMemorySize()
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
#endif
|
||||
@@ -62,6 +64,7 @@ getMemorySize()
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
[[nodiscard]] std::uint64_t
|
||||
getMemorySize()
|
||||
{
|
||||
@@ -70,6 +73,7 @@ getMemorySize()
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
|
||||
@@ -81,6 +85,7 @@ getMemorySize()
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
[[nodiscard]] std::uint64_t
|
||||
getMemorySize()
|
||||
{
|
||||
@@ -93,11 +98,13 @@ getMemorySize()
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
#endif
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// clang-format off
|
||||
// The configurable node sizes are "tiny", "small", "medium", "large", "huge"
|
||||
inline constexpr std::array<std::pair<SizedItem, std::array<int, 5>>, 13>
|
||||
@@ -1000,23 +1007,6 @@ Config::loadFromString(std::string const& fileContents)
|
||||
"the maximum number of allowed peers (peers_max)");
|
||||
}
|
||||
}
|
||||
|
||||
if (!RUN_STANDALONE)
|
||||
{
|
||||
auto db_section = section(ConfigSection::nodeDatabase());
|
||||
if (auto type = get(db_section, "type", ""); type == "rwdb")
|
||||
{
|
||||
if (auto delete_interval = get(db_section, "online_delete", 0);
|
||||
delete_interval == 0)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"RWDB (in-memory backend) requires online_delete to "
|
||||
"prevent OOM "
|
||||
"Exception: standalone mode (used by tests) doesn't need "
|
||||
"online_delete");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::path
|
||||
@@ -1081,4 +1071,5 @@ setup_FeeVote(Section const& section)
|
||||
}
|
||||
return setup;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -321,7 +321,7 @@ public:
|
||||
HSFEE,
|
||||
ter(temMALFORMED));
|
||||
|
||||
env(ripple::test::jtx::hook(alice, std::vector<Json::Value>{}, 0),
|
||||
env(ripple::test::jtx::hook(alice, {{}}, 0),
|
||||
M("Must have a non-empty hooks field"),
|
||||
HSFEE,
|
||||
ter(temMALFORMED));
|
||||
|
||||
@@ -1206,97 +1206,6 @@ r.ripple.com:51235
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRWDBOnlineDelete()
|
||||
{
|
||||
testcase("RWDB online_delete validation");
|
||||
|
||||
// Test 1: RWDB without online_delete in standalone mode (should
|
||||
// succeed)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
"[node_db]\n"
|
||||
"type=rwdb\n"
|
||||
"path=main\n";
|
||||
c.setupControl(true, true, true); // standalone = true
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
pass(); // Should succeed
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
fail("Should not throw in standalone mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: RWDB without online_delete NOT in standalone mode (should
|
||||
// throw)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
"[node_db]\n"
|
||||
"type=rwdb\n"
|
||||
"path=main\n";
|
||||
c.setupControl(true, true, false); // standalone = false
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail("Expected exception for RWDB without online_delete");
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find(
|
||||
"RWDB (in-memory backend) requires online_delete") !=
|
||||
std::string::npos);
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: RWDB with online_delete NOT in standalone mode (should
|
||||
// succeed)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
"[node_db]\n"
|
||||
"type=rwdb\n"
|
||||
"path=main\n"
|
||||
"online_delete=256\n";
|
||||
c.setupControl(true, true, false); // standalone = false
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
pass(); // Should succeed
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
fail("Should not throw when online_delete is configured");
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Non-RWDB without online_delete NOT in standalone mode (should
|
||||
// succeed)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
"[node_db]\n"
|
||||
"type=NuDB\n"
|
||||
"path=main\n";
|
||||
c.setupControl(true, true, false); // standalone = false
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
pass(); // Should succeed
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
fail("Should not throw for non-RWDB backends");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testOverlay()
|
||||
{
|
||||
@@ -1386,7 +1295,6 @@ r.ripple.com:51235
|
||||
testComments();
|
||||
testGetters();
|
||||
testAmendment();
|
||||
testRWDBOnlineDelete();
|
||||
testOverlay();
|
||||
testNetworkID();
|
||||
}
|
||||
|
||||
@@ -1,756 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/rdb/RelationalDatabase.h>
|
||||
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <chrono>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class RelationalDatabase_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
// Helper to get SQLiteDatabase* (works for both SQLite and RWDB since RWDB
|
||||
// inherits from SQLiteDatabase)
|
||||
static SQLiteDatabase*
|
||||
getInterface(Application& app)
|
||||
{
|
||||
return dynamic_cast<SQLiteDatabase*>(&app.getRelationalDatabase());
|
||||
}
|
||||
|
||||
static SQLiteDatabase*
|
||||
getInterface(RelationalDatabase& db)
|
||||
{
|
||||
return dynamic_cast<SQLiteDatabase*>(&db);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Config>
|
||||
makeConfig(std::string const& backend)
|
||||
{
|
||||
auto config = test::jtx::envconfig();
|
||||
// Sqlite backend doesn't need a database_path as it will just use
|
||||
// in-memory databases when in standalone mode anyway.
|
||||
config->overwrite(SECTION_RELATIONAL_DB, "backend", backend);
|
||||
return config;
|
||||
}
|
||||
|
||||
public:
|
||||
RelationalDatabase_test() = default;
|
||||
|
||||
void
|
||||
testBasicInitialization(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Basic initialization and empty database - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
// Test empty database state
|
||||
BEAST_EXPECT(db.getMinLedgerSeq() == 2);
|
||||
BEAST_EXPECT(db.getMaxLedgerSeq() == 2);
|
||||
BEAST_EXPECT(db.getNewestLedgerInfo()->seq == 2);
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
|
||||
if (sqliteDb)
|
||||
{
|
||||
BEAST_EXPECT(!sqliteDb->getTransactionsMinLedgerSeq().has_value());
|
||||
BEAST_EXPECT(
|
||||
!sqliteDb->getAccountTransactionsMinLedgerSeq().has_value());
|
||||
|
||||
auto ledgerCount = sqliteDb->getLedgerCountMinMax();
|
||||
BEAST_EXPECT(ledgerCount.numberOfRows == 1);
|
||||
BEAST_EXPECT(ledgerCount.minLedgerSequence == 2);
|
||||
BEAST_EXPECT(ledgerCount.maxLedgerSequence == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerSequenceOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Ledger sequence operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
// Create initial ledger
|
||||
Account alice("alice");
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// Test basic sequence operations
|
||||
auto minSeq = db.getMinLedgerSeq();
|
||||
auto maxSeq = db.getMaxLedgerSeq();
|
||||
|
||||
BEAST_EXPECT(minSeq.has_value());
|
||||
BEAST_EXPECT(maxSeq.has_value());
|
||||
BEAST_EXPECT(*minSeq == 2);
|
||||
BEAST_EXPECT(*maxSeq == 3);
|
||||
|
||||
// Create more ledgers
|
||||
env(pay(alice, Account("bob"), XRP(1000)));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, Account("carol"), XRP(500)));
|
||||
env.close();
|
||||
|
||||
// Verify sequence updates
|
||||
minSeq = db.getMinLedgerSeq();
|
||||
maxSeq = db.getMaxLedgerSeq();
|
||||
|
||||
BEAST_EXPECT(*minSeq == 2);
|
||||
BEAST_EXPECT(*maxSeq == 5);
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
if (sqliteDb)
|
||||
{
|
||||
auto ledgerCount = sqliteDb->getLedgerCountMinMax();
|
||||
BEAST_EXPECT(ledgerCount.numberOfRows == 4);
|
||||
BEAST_EXPECT(ledgerCount.minLedgerSequence == 2);
|
||||
BEAST_EXPECT(ledgerCount.maxLedgerSequence == 5);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerInfoOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Ledger info retrieval operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto* db = getInterface(env.app());
|
||||
|
||||
Account alice("alice");
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// Test getNewestLedgerInfo
|
||||
auto newestLedger = db->getNewestLedgerInfo();
|
||||
BEAST_EXPECT(newestLedger.has_value());
|
||||
BEAST_EXPECT(newestLedger->seq == 3);
|
||||
|
||||
// Test getLedgerInfoByIndex
|
||||
auto ledgerByIndex = db->getLedgerInfoByIndex(3);
|
||||
BEAST_EXPECT(ledgerByIndex.has_value());
|
||||
BEAST_EXPECT(ledgerByIndex->seq == 3);
|
||||
BEAST_EXPECT(ledgerByIndex->hash == newestLedger->hash);
|
||||
|
||||
// Test getLedgerInfoByHash
|
||||
auto ledgerByHash = db->getLedgerInfoByHash(newestLedger->hash);
|
||||
BEAST_EXPECT(ledgerByHash.has_value());
|
||||
BEAST_EXPECT(ledgerByHash->seq == 3);
|
||||
BEAST_EXPECT(ledgerByHash->hash == newestLedger->hash);
|
||||
|
||||
// Test getLimitedOldestLedgerInfo
|
||||
auto oldestLedger = db->getLimitedOldestLedgerInfo(2);
|
||||
BEAST_EXPECT(oldestLedger.has_value());
|
||||
BEAST_EXPECT(oldestLedger->seq == 2);
|
||||
|
||||
// Test getLimitedNewestLedgerInfo
|
||||
auto limitedNewest = db->getLimitedNewestLedgerInfo(2);
|
||||
BEAST_EXPECT(limitedNewest.has_value());
|
||||
BEAST_EXPECT(limitedNewest->seq == 3);
|
||||
|
||||
// Test invalid queries
|
||||
auto invalidLedger = db->getLedgerInfoByIndex(999);
|
||||
BEAST_EXPECT(!invalidLedger.has_value());
|
||||
|
||||
uint256 invalidHash;
|
||||
auto invalidHashLedger = db->getLedgerInfoByHash(invalidHash);
|
||||
BEAST_EXPECT(!invalidHashLedger.has_value());
|
||||
}
|
||||
|
||||
void
|
||||
testHashOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Hash retrieval operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
Account alice("alice");
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
env(pay(alice, Account("bob"), XRP(1000)));
|
||||
env.close();
|
||||
|
||||
// Test getHashByIndex
|
||||
auto hash1 = db.getHashByIndex(3);
|
||||
auto hash2 = db.getHashByIndex(4);
|
||||
|
||||
BEAST_EXPECT(hash1 != uint256());
|
||||
BEAST_EXPECT(hash2 != uint256());
|
||||
BEAST_EXPECT(hash1 != hash2);
|
||||
|
||||
// Test getHashesByIndex (single)
|
||||
auto hashPair = db.getHashesByIndex(4);
|
||||
BEAST_EXPECT(hashPair.has_value());
|
||||
BEAST_EXPECT(hashPair->ledgerHash == hash2);
|
||||
BEAST_EXPECT(hashPair->parentHash == hash1);
|
||||
|
||||
// Test getHashesByIndex (range)
|
||||
auto hashRange = db.getHashesByIndex(3, 4);
|
||||
BEAST_EXPECT(hashRange.size() == 2);
|
||||
BEAST_EXPECT(hashRange[3].ledgerHash == hash1);
|
||||
BEAST_EXPECT(hashRange[4].ledgerHash == hash2);
|
||||
BEAST_EXPECT(hashRange[4].parentHash == hash1);
|
||||
|
||||
// Test invalid hash queries
|
||||
auto invalidHash = db.getHashByIndex(999);
|
||||
BEAST_EXPECT(invalidHash == uint256());
|
||||
|
||||
auto invalidHashPair = db.getHashesByIndex(999);
|
||||
BEAST_EXPECT(!invalidHashPair.has_value());
|
||||
|
||||
auto emptyRange = db.getHashesByIndex(10, 5); // max < min
|
||||
BEAST_EXPECT(emptyRange.empty());
|
||||
}
|
||||
|
||||
void
|
||||
testTransactionOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Transaction storage and retrieval - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
Account alice("alice");
|
||||
Account bob("bob");
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Test initial transaction counts after funding
|
||||
auto initialTxCount = sqliteDb->getTransactionCount();
|
||||
auto initialAcctTxCount = sqliteDb->getAccountTransactionCount();
|
||||
|
||||
BEAST_EXPECT(initialTxCount == 4);
|
||||
BEAST_EXPECT(initialAcctTxCount == 6);
|
||||
|
||||
// Create transactions
|
||||
env(pay(alice, bob, XRP(1000)));
|
||||
env.close();
|
||||
|
||||
env(pay(bob, alice, XRP(500)));
|
||||
env.close();
|
||||
|
||||
// Test transaction counts after creation
|
||||
auto txCount = sqliteDb->getTransactionCount();
|
||||
auto acctTxCount = sqliteDb->getAccountTransactionCount();
|
||||
|
||||
BEAST_EXPECT(txCount == 6);
|
||||
BEAST_EXPECT(acctTxCount == 10);
|
||||
|
||||
// Test transaction retrieval
|
||||
uint256 invalidTxId;
|
||||
error_code_i ec;
|
||||
auto invalidTxResult =
|
||||
sqliteDb->getTransaction(invalidTxId, std::nullopt, ec);
|
||||
BEAST_EXPECT(std::holds_alternative<TxSearched>(invalidTxResult));
|
||||
|
||||
// Test transaction history
|
||||
auto txHistory = db.getTxHistory(0);
|
||||
|
||||
BEAST_EXPECT(!txHistory.empty());
|
||||
BEAST_EXPECT(txHistory.size() == 6);
|
||||
|
||||
// Test with valid transaction range
|
||||
auto minSeq = sqliteDb->getTransactionsMinLedgerSeq();
|
||||
auto maxSeq = db.getMaxLedgerSeq();
|
||||
|
||||
if (minSeq && maxSeq)
|
||||
{
|
||||
ClosedInterval<std::uint32_t> range(*minSeq, *maxSeq);
|
||||
auto rangeResult = sqliteDb->getTransaction(invalidTxId, range, ec);
|
||||
auto searched = std::get<TxSearched>(rangeResult);
|
||||
BEAST_EXPECT(
|
||||
searched == TxSearched::all || searched == TxSearched::some);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAccountTransactionOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Account transaction operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
Account alice("alice");
|
||||
Account bob("bob");
|
||||
Account carol("carol");
|
||||
|
||||
env.fund(XRP(10000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Create multiple transactions involving alice
|
||||
env(pay(alice, bob, XRP(1000)));
|
||||
env.close();
|
||||
|
||||
env(pay(bob, alice, XRP(500)));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, carol, XRP(250)));
|
||||
env.close();
|
||||
|
||||
auto minSeq = db.getMinLedgerSeq();
|
||||
auto maxSeq = db.getMaxLedgerSeq();
|
||||
|
||||
if (!minSeq || !maxSeq)
|
||||
return;
|
||||
|
||||
// Test getOldestAccountTxs
|
||||
RelationalDatabase::AccountTxOptions options{
|
||||
alice.id(), *minSeq, *maxSeq, 0, 10, false};
|
||||
|
||||
auto oldestTxs = sqliteDb->getOldestAccountTxs(options);
|
||||
BEAST_EXPECT(oldestTxs.size() == 5);
|
||||
|
||||
// Test getNewestAccountTxs
|
||||
auto newestTxs = sqliteDb->getNewestAccountTxs(options);
|
||||
BEAST_EXPECT(newestTxs.size() == 5);
|
||||
|
||||
// Test binary format versions
|
||||
auto oldestTxsB = sqliteDb->getOldestAccountTxsB(options);
|
||||
BEAST_EXPECT(oldestTxsB.size() == 5);
|
||||
|
||||
auto newestTxsB = sqliteDb->getNewestAccountTxsB(options);
|
||||
BEAST_EXPECT(newestTxsB.size() == 5);
|
||||
|
||||
// Test with limit
|
||||
options.limit = 1;
|
||||
auto limitedTxs = sqliteDb->getOldestAccountTxs(options);
|
||||
BEAST_EXPECT(limitedTxs.size() == 1);
|
||||
|
||||
// Test with offset
|
||||
options.limit = 10;
|
||||
options.offset = 1;
|
||||
auto offsetTxs = sqliteDb->getOldestAccountTxs(options);
|
||||
BEAST_EXPECT(offsetTxs.size() == 4);
|
||||
|
||||
// Test with invalid account
|
||||
{
|
||||
Account invalidAccount("invalid");
|
||||
RelationalDatabase::AccountTxOptions invalidOptions{
|
||||
invalidAccount.id(), *minSeq, *maxSeq, 0, 10, false};
|
||||
auto invalidAccountTxs =
|
||||
sqliteDb->getOldestAccountTxs(invalidOptions);
|
||||
BEAST_EXPECT(invalidAccountTxs.empty());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAccountTransactionPaging(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Account transaction paging operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
Account alice("alice");
|
||||
Account bob("bob");
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Create multiple transactions for paging
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
env(pay(alice, bob, XRP(100 + i)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto minSeq = db.getMinLedgerSeq();
|
||||
auto maxSeq = db.getMaxLedgerSeq();
|
||||
|
||||
if (!minSeq || !maxSeq)
|
||||
return;
|
||||
|
||||
RelationalDatabase::AccountTxPageOptions pageOptions{
|
||||
alice.id(), *minSeq, *maxSeq, std::nullopt, 2, false};
|
||||
|
||||
// Test oldestAccountTxPage
|
||||
auto [oldestPage, oldestMarker] =
|
||||
sqliteDb->oldestAccountTxPage(pageOptions);
|
||||
|
||||
BEAST_EXPECT(oldestPage.size() == 2);
|
||||
BEAST_EXPECT(oldestMarker.has_value() == true);
|
||||
|
||||
// Test newestAccountTxPage
|
||||
auto [newestPage, newestMarker] =
|
||||
sqliteDb->newestAccountTxPage(pageOptions);
|
||||
|
||||
BEAST_EXPECT(newestPage.size() == 2);
|
||||
BEAST_EXPECT(newestMarker.has_value() == true);
|
||||
|
||||
// Test binary versions
|
||||
auto [oldestPageB, oldestMarkerB] =
|
||||
sqliteDb->oldestAccountTxPageB(pageOptions);
|
||||
BEAST_EXPECT(oldestPageB.size() == 2);
|
||||
|
||||
auto [newestPageB, newestMarkerB] =
|
||||
sqliteDb->newestAccountTxPageB(pageOptions);
|
||||
BEAST_EXPECT(newestPageB.size() == 2);
|
||||
|
||||
// Test with marker continuation
|
||||
if (oldestMarker.has_value())
|
||||
{
|
||||
pageOptions.marker = oldestMarker;
|
||||
auto [continuedPage, continuedMarker] =
|
||||
sqliteDb->oldestAccountTxPage(pageOptions);
|
||||
BEAST_EXPECT(continuedPage.size() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDeletionOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Deletion operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
Account alice("alice");
|
||||
Account bob("bob");
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Create multiple ledgers and transactions
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
env(pay(alice, bob, XRP(100 + i)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto initialTxCount = sqliteDb->getTransactionCount();
|
||||
BEAST_EXPECT(initialTxCount == 7);
|
||||
auto initialAcctTxCount = sqliteDb->getAccountTransactionCount();
|
||||
BEAST_EXPECT(initialAcctTxCount == 12);
|
||||
auto initialLedgerCount = sqliteDb->getLedgerCountMinMax();
|
||||
BEAST_EXPECT(initialLedgerCount.numberOfRows == 5);
|
||||
|
||||
auto maxSeq = db.getMaxLedgerSeq();
|
||||
if (!maxSeq || *maxSeq <= 2)
|
||||
return;
|
||||
|
||||
// Test deleteTransactionByLedgerSeq
|
||||
sqliteDb->deleteTransactionByLedgerSeq(*maxSeq);
|
||||
auto txCountAfterDelete = sqliteDb->getTransactionCount();
|
||||
BEAST_EXPECT(txCountAfterDelete == 6);
|
||||
|
||||
// Test deleteTransactionsBeforeLedgerSeq
|
||||
sqliteDb->deleteTransactionsBeforeLedgerSeq(*maxSeq - 1);
|
||||
auto txCountAfterBulkDelete = sqliteDb->getTransactionCount();
|
||||
BEAST_EXPECT(txCountAfterBulkDelete == 1);
|
||||
|
||||
// Test deleteAccountTransactionsBeforeLedgerSeq
|
||||
sqliteDb->deleteAccountTransactionsBeforeLedgerSeq(*maxSeq - 1);
|
||||
auto acctTxCountAfterDelete = sqliteDb->getAccountTransactionCount();
|
||||
BEAST_EXPECT(acctTxCountAfterDelete == 4);
|
||||
|
||||
// Test deleteBeforeLedgerSeq
|
||||
auto minSeq = db.getMinLedgerSeq();
|
||||
if (minSeq)
|
||||
{
|
||||
sqliteDb->deleteBeforeLedgerSeq(*minSeq + 1);
|
||||
auto ledgerCountAfterDelete = sqliteDb->getLedgerCountMinMax();
|
||||
BEAST_EXPECT(ledgerCountAfterDelete.numberOfRows == 4);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDatabaseSpaceOperations(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Database space and size operations - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Test size queries
|
||||
auto allKB = sqliteDb->getKBUsedAll();
|
||||
auto ledgerKB = sqliteDb->getKBUsedLedger();
|
||||
auto txKB = sqliteDb->getKBUsedTransaction();
|
||||
|
||||
if (backend == "rwdb")
|
||||
{
|
||||
// RWDB reports actual data memory (rounded down to KB)
|
||||
// Initially should be < 1KB, so rounds down to 0
|
||||
// Note: These are 0 due to rounding, not because there's literally
|
||||
// no data
|
||||
BEAST_EXPECT(allKB == 0); // < 1024 bytes rounds to 0 KB
|
||||
BEAST_EXPECT(ledgerKB == 0); // < 1024 bytes rounds to 0 KB
|
||||
BEAST_EXPECT(txKB == 0); // < 1024 bytes rounds to 0 KB
|
||||
}
|
||||
else
|
||||
{
|
||||
// SQLite reports cache/engine memory which has overhead even when
|
||||
// empty Just verify the functions return reasonable values
|
||||
BEAST_EXPECT(allKB >= 0);
|
||||
BEAST_EXPECT(ledgerKB >= 0);
|
||||
BEAST_EXPECT(txKB >= 0);
|
||||
}
|
||||
|
||||
// Create some data and verify size increases
|
||||
Account alice("alice");
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
auto newAllKB = sqliteDb->getKBUsedAll();
|
||||
auto newLedgerKB = sqliteDb->getKBUsedLedger();
|
||||
auto newTxKB = sqliteDb->getKBUsedTransaction();
|
||||
|
||||
if (backend == "rwdb")
|
||||
{
|
||||
// RWDB reports actual data memory
|
||||
// After adding data, should see some increase
|
||||
BEAST_EXPECT(newAllKB >= 1); // Should have at least 1KB total
|
||||
BEAST_EXPECT(
|
||||
newTxKB >= 0); // Transactions added (might still be < 1KB)
|
||||
BEAST_EXPECT(
|
||||
newLedgerKB >= 0); // Ledger data (might still be < 1KB)
|
||||
|
||||
// Key relationships
|
||||
BEAST_EXPECT(newAllKB >= newLedgerKB + newTxKB); // Total >= parts
|
||||
BEAST_EXPECT(newAllKB >= allKB); // Should increase or stay same
|
||||
BEAST_EXPECT(newTxKB >= txKB); // Should increase or stay same
|
||||
}
|
||||
else
|
||||
{
|
||||
// SQLite: Memory usage should not decrease after adding data
|
||||
// Values might increase due to cache growth
|
||||
BEAST_EXPECT(newAllKB >= allKB);
|
||||
BEAST_EXPECT(newLedgerKB >= ledgerKB);
|
||||
BEAST_EXPECT(newTxKB >= txKB);
|
||||
|
||||
// SQLite's getKBUsedAll is global memory, should be >= parts
|
||||
BEAST_EXPECT(newAllKB >= newLedgerKB);
|
||||
BEAST_EXPECT(newAllKB >= newTxKB);
|
||||
}
|
||||
|
||||
// Test space availability
|
||||
// Both SQLite and RWDB use in-memory databases in standalone mode,
|
||||
// so file-based space checks don't apply to either backend.
|
||||
// Skip these checks for both.
|
||||
|
||||
// if (backend == "rwdb")
|
||||
// {
|
||||
// BEAST_EXPECT(db.ledgerDbHasSpace(env.app().config()));
|
||||
// BEAST_EXPECT(db.transactionDbHasSpace(env.app().config()));
|
||||
// }
|
||||
|
||||
// Test database closure operations (should not throw)
|
||||
try
|
||||
{
|
||||
sqliteDb->closeLedgerDB();
|
||||
sqliteDb->closeTransactionDB();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(false); // Should not throw
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTransactionMinLedgerSeq(
|
||||
std::string const& backend,
|
||||
std::unique_ptr<Config> config)
|
||||
{
|
||||
testcase("Transaction minimum ledger sequence tracking - " + backend);
|
||||
|
||||
using namespace test::jtx;
|
||||
config->LEDGER_HISTORY = 1000;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
auto& db = env.app().getRelationalDatabase();
|
||||
|
||||
auto* sqliteDb = getInterface(db);
|
||||
BEAST_EXPECT(sqliteDb != nullptr);
|
||||
if (!sqliteDb)
|
||||
return;
|
||||
|
||||
// Initially should have no transactions
|
||||
BEAST_EXPECT(!sqliteDb->getTransactionsMinLedgerSeq().has_value());
|
||||
BEAST_EXPECT(
|
||||
!sqliteDb->getAccountTransactionsMinLedgerSeq().has_value());
|
||||
|
||||
Account alice("alice");
|
||||
Account bob("bob");
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Create first transaction
|
||||
env(pay(alice, bob, XRP(1000)));
|
||||
env.close();
|
||||
|
||||
auto txMinSeq = sqliteDb->getTransactionsMinLedgerSeq();
|
||||
auto acctTxMinSeq = sqliteDb->getAccountTransactionsMinLedgerSeq();
|
||||
BEAST_EXPECT(txMinSeq.has_value());
|
||||
BEAST_EXPECT(acctTxMinSeq.has_value());
|
||||
BEAST_EXPECT(*txMinSeq == 3);
|
||||
BEAST_EXPECT(*acctTxMinSeq == 3);
|
||||
|
||||
// Create more transactions
|
||||
env(pay(bob, alice, XRP(500)));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, bob, XRP(250)));
|
||||
env.close();
|
||||
|
||||
// Min sequences should remain the same (first transaction ledger)
|
||||
auto newTxMinSeq = sqliteDb->getTransactionsMinLedgerSeq();
|
||||
auto newAcctTxMinSeq = sqliteDb->getAccountTransactionsMinLedgerSeq();
|
||||
BEAST_EXPECT(newTxMinSeq == txMinSeq);
|
||||
BEAST_EXPECT(newAcctTxMinSeq == acctTxMinSeq);
|
||||
}
|
||||
|
||||
std::vector<std::string> static getBackends(std::string const& unittest_arg)
|
||||
{
|
||||
// Valid backends
|
||||
static const std::set<std::string> validBackends = {"sqlite", "rwdb"};
|
||||
|
||||
// Default to all valid backends if no arg specified
|
||||
if (unittest_arg.empty())
|
||||
return {validBackends.begin(), validBackends.end()};
|
||||
|
||||
std::set<std::string> backends; // Use set to avoid duplicates
|
||||
std::stringstream ss(unittest_arg);
|
||||
std::string backend;
|
||||
|
||||
while (std::getline(ss, backend, ','))
|
||||
{
|
||||
if (!backend.empty())
|
||||
{
|
||||
// Validate backend
|
||||
if (validBackends.contains(backend))
|
||||
{
|
||||
backends.insert(backend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return as vector (sorted due to set)
|
||||
return {backends.begin(), backends.end()};
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
auto backends = getBackends(arg());
|
||||
|
||||
if (backends.empty())
|
||||
{
|
||||
fail("no valid backend specified: '" + arg() + "'");
|
||||
}
|
||||
|
||||
for (auto const& backend : backends)
|
||||
{
|
||||
testBasicInitialization(backend, makeConfig(backend));
|
||||
testLedgerSequenceOperations(backend, makeConfig(backend));
|
||||
testLedgerInfoOperations(backend, makeConfig(backend));
|
||||
testHashOperations(backend, makeConfig(backend));
|
||||
testTransactionOperations(backend, makeConfig(backend));
|
||||
testAccountTransactionOperations(backend, makeConfig(backend));
|
||||
testAccountTransactionPaging(backend, makeConfig(backend));
|
||||
testDeletionOperations(backend, makeConfig(backend));
|
||||
testDatabaseSpaceOperations(backend, makeConfig(backend));
|
||||
testTransactionMinLedgerSeq(backend, makeConfig(backend));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(RelationalDatabase, rdb, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
#ifndef TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||
#define TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -82,13 +82,7 @@ SuiteJournalSink::write(
|
||||
|
||||
// Only write the string if the level at least equals the threshold.
|
||||
if (level >= threshold())
|
||||
{
|
||||
// std::endl flushes → sync() → str()/str("") race in shared buffer →
|
||||
// crashes
|
||||
static std::mutex log_mutex;
|
||||
std::lock_guard lock(log_mutex);
|
||||
suite_.log << s << partition_ << text << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
class SuiteJournal
|
||||
|
||||
Reference in New Issue
Block a user