mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-21 02:55:50 +00:00
756 lines
24 KiB
C++
756 lines
24 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
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
|