mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-05 17:56:37 +00:00
852 lines
30 KiB
C++
852 lines
30 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012-2017 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/ledger/LedgerMaster.h>
|
|
#include <ripple/beast/unit_test.h>
|
|
#include <ripple/protocol/jss.h>
|
|
#include <boost/filesystem.hpp>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <test/jtx.h>
|
|
#include <thread>
|
|
|
|
namespace ripple {
|
|
|
|
#pragma pack(push, 1) // pack the struct tightly
|
|
struct TestCATLHeader
|
|
{
|
|
uint32_t magic = 0x4C544143UL;
|
|
uint32_t min_ledger;
|
|
uint32_t max_ledger;
|
|
uint16_t version;
|
|
uint16_t network_id;
|
|
uint64_t filesize = 0; // Total size of the file including header
|
|
std::array<uint8_t, 64> hash = {}; // SHA-512 hash, initially set to zeros
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
class Catalogue_test : public beast::unit_test::suite
|
|
{
|
|
// Helper to create test ledger data with complex state changes
|
|
void
|
|
prepareLedgerData(test::jtx::Env& env, int numLedgers)
|
|
{
|
|
using namespace test::jtx;
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
Account charlie{"charlie"};
|
|
|
|
env.fund(XRP(10000), alice, bob, charlie);
|
|
env.close();
|
|
|
|
// Set up trust lines and issue currency
|
|
env(trust(bob, alice["USD"](1000)));
|
|
env(trust(charlie, bob["EUR"](1000)));
|
|
env.close();
|
|
|
|
env(pay(alice, bob, alice["USD"](500)));
|
|
env.close();
|
|
|
|
// Create and remove an offer to test state deletion
|
|
env(offer(bob, XRP(50), alice["USD"](1)));
|
|
auto offerSeq =
|
|
env.seq(bob) - 1; // Get the sequence of the offer we just created
|
|
env.close();
|
|
|
|
// Cancel the offer
|
|
env(offer_cancel(bob, offerSeq));
|
|
env.close();
|
|
|
|
// Create another offer with same account
|
|
env(offer(bob, XRP(60), alice["USD"](2)));
|
|
env.close();
|
|
|
|
// Create a trust line and then remove it
|
|
env(trust(charlie, bob["EUR"](1000)));
|
|
env.close();
|
|
env(trust(charlie, bob["EUR"](0)));
|
|
env.close();
|
|
|
|
// Recreate the same trust line
|
|
env(trust(charlie, bob["EUR"](2000)));
|
|
env.close();
|
|
|
|
// Additional ledgers with various transactions
|
|
for (int i = 0; i < numLedgers; ++i)
|
|
{
|
|
env(pay(alice, bob, XRP(100)));
|
|
env(offer(bob, XRP(50), alice["USD"](1)));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testCatalogueCreateBadInput(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_create: Invalid parameters");
|
|
using namespace test::jtx;
|
|
Env env{*this, envconfig(), features};
|
|
|
|
// No parameters
|
|
{
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", {})[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Missing min_ledger
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::max_ledger] = 20;
|
|
params[jss::output_file] = "/tmp/test.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Missing max_ledger
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 10;
|
|
params[jss::output_file] = "/tmp/test.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Missing output_file
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 10;
|
|
params[jss::max_ledger] = 20;
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Invalid output path (not absolute)
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 10;
|
|
params[jss::max_ledger] = 20;
|
|
params[jss::output_file] = "test.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// min_ledger > max_ledger
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 20;
|
|
params[jss::max_ledger] = 10;
|
|
params[jss::output_file] = "/tmp/test.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
}
|
|
|
|
void
|
|
testCatalogueCreate(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_create: Basic functionality");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment and some test ledgers
|
|
Env env{*this, envconfig(), features};
|
|
prepareLedgerData(env, 5);
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// Create catalogue
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 3;
|
|
params[jss::max_ledger] = 5;
|
|
params[jss::output_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result[jss::min_ledger] == 3);
|
|
BEAST_EXPECT(result[jss::max_ledger] == 5);
|
|
BEAST_EXPECT(result[jss::output_file] == cataloguePath);
|
|
BEAST_EXPECT(result[jss::file_size].asUInt() > 0);
|
|
BEAST_EXPECT(result[jss::ledgers_written].asUInt() == 3);
|
|
|
|
// Verify file exists and is not empty
|
|
BEAST_EXPECT(boost::filesystem::exists(cataloguePath));
|
|
BEAST_EXPECT(boost::filesystem::file_size(cataloguePath) > 0);
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testCatalogueLoadBadInput(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_load: Invalid parameters");
|
|
using namespace test::jtx;
|
|
Env env{*this, envconfig(), features};
|
|
|
|
// No parameters
|
|
{
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", {})[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Missing input_file
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Invalid input path (not absolute)
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = "test.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
|
|
// Non-existent file
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = "/tmp/nonexistent.catl";
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::error] == "internal");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
}
|
|
|
|
void
|
|
testCatalogueLoadAndVerify(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_load: Load and verify");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment and test data
|
|
Env env{*this, envconfig(), features};
|
|
prepareLedgerData(env, 5);
|
|
|
|
// Store some key state information before catalogue creation
|
|
auto const sourceLedger = env.closed();
|
|
auto const bobKeylet = keylet::account(Account("bob").id());
|
|
auto const charlieKeylet = keylet::account(Account("charlie").id());
|
|
auto const eurTrustKeylet = keylet::line(
|
|
Account("charlie").id(),
|
|
Account("bob").id(),
|
|
Currency(to_currency("EUR")));
|
|
|
|
// Get original state entries
|
|
auto const bobAcct = sourceLedger->read(bobKeylet);
|
|
auto const charlieAcct = sourceLedger->read(charlieKeylet);
|
|
auto const eurTrust = sourceLedger->read(eurTrustKeylet);
|
|
|
|
BEAST_EXPECT(bobAcct != nullptr);
|
|
BEAST_EXPECT(charlieAcct != nullptr);
|
|
BEAST_EXPECT(eurTrust != nullptr);
|
|
|
|
BEAST_EXPECT(
|
|
eurTrust->getFieldAmount(sfLowLimit).mantissa() ==
|
|
2000000000000000ULL);
|
|
|
|
// Get initial complete_ledgers range
|
|
auto const originalCompleteLedgers =
|
|
env.app().getLedgerMaster().getCompleteLedgers();
|
|
|
|
// Create temporary directory for test files
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// First create a catalogue
|
|
uint32_t minLedger = 3;
|
|
uint32_t maxLedger = sourceLedger->info().seq;
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = minLedger;
|
|
params[jss::max_ledger] = maxLedger;
|
|
params[jss::output_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
}
|
|
|
|
// Create a new environment for loading with unique port
|
|
Env loadEnv{
|
|
*this,
|
|
test::jtx::envconfig(test::jtx::port_increment, 3),
|
|
features};
|
|
|
|
// Now load the catalogue
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
loadEnv.client().invoke("catalogue_load", params)[jss::result];
|
|
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result[jss::ledger_min] == minLedger);
|
|
BEAST_EXPECT(result[jss::ledger_max] == maxLedger);
|
|
BEAST_EXPECT(result[jss::ledger_count] == (maxLedger - minLedger + 1));
|
|
|
|
// Verify complete_ledgers reflects loaded ledgers
|
|
auto const newCompleteLedgers =
|
|
loadEnv.app().getLedgerMaster().getCompleteLedgers();
|
|
|
|
BEAST_EXPECT(newCompleteLedgers == originalCompleteLedgers);
|
|
|
|
// Verify the loaded state matches the original
|
|
auto const loadedLedger = loadEnv.closed();
|
|
|
|
// After loading each ledger
|
|
|
|
// Compare all ledgers from 3 to 16 inclusive
|
|
for (std::uint32_t seq = 3; seq <= 16; ++seq)
|
|
{
|
|
auto const sourceLedger =
|
|
env.app().getLedgerMaster().getLedgerByHash(
|
|
env.app().getLedgerMaster().getHashBySeq(seq));
|
|
|
|
auto const loadedLedger =
|
|
loadEnv.app().getLedgerMaster().getLedgerByHash(
|
|
loadEnv.app().getLedgerMaster().getHashBySeq(seq));
|
|
|
|
if (!sourceLedger || !loadedLedger)
|
|
{
|
|
BEAST_EXPECT(false); // Test failure
|
|
continue;
|
|
}
|
|
|
|
// Check basic ledger properties
|
|
BEAST_EXPECT(sourceLedger->info().seq == loadedLedger->info().seq);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().hash == loadedLedger->info().hash);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().txHash == loadedLedger->info().txHash);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().accountHash ==
|
|
loadedLedger->info().accountHash);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().parentHash ==
|
|
loadedLedger->info().parentHash);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().drops == loadedLedger->info().drops);
|
|
|
|
// Check time-related properties
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().closeFlags ==
|
|
loadedLedger->info().closeFlags);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().closeTimeResolution.count() ==
|
|
loadedLedger->info().closeTimeResolution.count());
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().closeTime.time_since_epoch().count() ==
|
|
loadedLedger->info().closeTime.time_since_epoch().count());
|
|
BEAST_EXPECT(
|
|
sourceLedger->info()
|
|
.parentCloseTime.time_since_epoch()
|
|
.count() ==
|
|
loadedLedger->info()
|
|
.parentCloseTime.time_since_epoch()
|
|
.count());
|
|
|
|
// Check validation state
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().validated ==
|
|
loadedLedger->info().validated);
|
|
BEAST_EXPECT(
|
|
sourceLedger->info().accepted == loadedLedger->info().accepted);
|
|
|
|
// Check SLE counts
|
|
std::size_t sourceCount = 0;
|
|
std::size_t loadedCount = 0;
|
|
|
|
for (auto const& sle : sourceLedger->sles)
|
|
{
|
|
sourceCount++;
|
|
}
|
|
|
|
for (auto const& sle : loadedLedger->sles)
|
|
{
|
|
loadedCount++;
|
|
}
|
|
|
|
BEAST_EXPECT(sourceCount == loadedCount);
|
|
|
|
// Check existence of imported keylets
|
|
for (auto const& sle : sourceLedger->sles)
|
|
{
|
|
auto const key = sle->key();
|
|
bool exists = loadedLedger->exists(keylet::unchecked(key));
|
|
BEAST_EXPECT(exists);
|
|
|
|
// If it exists, check the serialized form matches
|
|
if (exists)
|
|
{
|
|
auto loadedSle = loadedLedger->read(keylet::unchecked(key));
|
|
Serializer s1, s2;
|
|
sle->add(s1);
|
|
loadedSle->add(s2);
|
|
bool serializedEqual = (s1.peekData() == s2.peekData());
|
|
BEAST_EXPECT(serializedEqual);
|
|
}
|
|
}
|
|
|
|
// Check for extra keys in loaded ledger that aren't in source
|
|
for (auto const& sle : loadedLedger->sles)
|
|
{
|
|
auto const key = sle->key();
|
|
BEAST_EXPECT(sourceLedger->exists(keylet::unchecked(key)));
|
|
}
|
|
}
|
|
|
|
auto const loadedBobAcct = loadedLedger->read(bobKeylet);
|
|
auto const loadedCharlieAcct = loadedLedger->read(charlieKeylet);
|
|
auto const loadedEurTrust = loadedLedger->read(eurTrustKeylet);
|
|
|
|
BEAST_EXPECT(!!loadedBobAcct);
|
|
BEAST_EXPECT(!!loadedCharlieAcct);
|
|
BEAST_EXPECT(!!loadedEurTrust);
|
|
|
|
// Compare the serialized forms of the state objects
|
|
bool const loaded =
|
|
loadedBobAcct && loadedCharlieAcct && loadedEurTrust;
|
|
|
|
Serializer s1, s2;
|
|
if (loaded)
|
|
{
|
|
bobAcct->add(s1);
|
|
loadedBobAcct->add(s2);
|
|
}
|
|
BEAST_EXPECT(loaded && s1.peekData() == s2.peekData());
|
|
|
|
if (loaded)
|
|
{
|
|
s1.erase();
|
|
s2.erase();
|
|
charlieAcct->add(s1);
|
|
loadedCharlieAcct->add(s2);
|
|
}
|
|
BEAST_EXPECT(loaded && s1.peekData() == s2.peekData());
|
|
|
|
if (loaded)
|
|
{
|
|
s1.erase();
|
|
s2.erase();
|
|
eurTrust->add(s1);
|
|
loadedEurTrust->add(s2);
|
|
}
|
|
|
|
BEAST_EXPECT(loaded && s1.peekData() == s2.peekData());
|
|
|
|
// Verify trust line amount matches
|
|
BEAST_EXPECT(
|
|
loaded &&
|
|
loadedEurTrust->getFieldAmount(sfLowLimit).mantissa() ==
|
|
2000000000000000ULL);
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testNetworkMismatch(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_load: Network ID mismatch");
|
|
using namespace test::jtx;
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// Create environment with different network IDs
|
|
{
|
|
Env env1{
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->NETWORK_ID = 123;
|
|
return cfg;
|
|
}),
|
|
features};
|
|
prepareLedgerData(env1, 5);
|
|
|
|
// Create catalogue with network ID 123
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 3;
|
|
params[jss::max_ledger] = 5;
|
|
params[jss::output_file] = cataloguePath;
|
|
|
|
auto const result = env1.client().invoke(
|
|
"catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Try to load catalogue in environment with different network ID
|
|
Env env2{
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->NETWORK_ID = 456;
|
|
return cfg;
|
|
}),
|
|
features};
|
|
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env2.client().invoke("catalogue_load", params)[jss::result];
|
|
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
}
|
|
}
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testCatalogueHashVerification(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_load: Hash verification");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment and test data
|
|
Env env{*this, envconfig(), features};
|
|
prepareLedgerData(env, 3);
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// Create catalogue
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 3;
|
|
params[jss::max_ledger] = 5;
|
|
params[jss::output_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result.isMember(jss::hash));
|
|
std::string originalHash = result[jss::hash].asString();
|
|
BEAST_EXPECT(!originalHash.empty());
|
|
}
|
|
|
|
// Test 1: Successful hash verification (normal load)
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result.isMember(jss::hash));
|
|
}
|
|
|
|
// Test 2: Corrupt the file and test hash mismatch detection
|
|
{
|
|
// Modify a byte in the middle of the file to cause hash mismatch
|
|
std::fstream file(
|
|
cataloguePath, std::ios::in | std::ios::out | std::ios::binary);
|
|
BEAST_EXPECT(file.good());
|
|
|
|
// Skip header and modify a byte
|
|
file.seekp(sizeof(TestCATLHeader) + 100, std::ios::beg);
|
|
char byte = 0xFF;
|
|
file.write(&byte, 1);
|
|
file.close();
|
|
|
|
// Try to load the corrupted file
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
result[jss::error_message].asString().find(
|
|
"hash verification failed") != std::string::npos);
|
|
}
|
|
|
|
// Test 3: Test ignore_hash parameter
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
params[jss::ignore_hash] = true;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
// This might still fail due to data corruption, but not because of
|
|
// hash verification The important part is that it didn't
|
|
// immediately reject due to hash
|
|
if (result[jss::status] == "error")
|
|
{
|
|
BEAST_EXPECT(
|
|
result[jss::error_message].asString().find(
|
|
"hash verification failed") == std::string::npos);
|
|
}
|
|
}
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testCatalogueFileSize(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_load: File size verification");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment and test data
|
|
Env env{*this, envconfig(), features};
|
|
prepareLedgerData(env, 3);
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// Create catalogue
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::min_ledger] = 3;
|
|
params[jss::max_ledger] = 5;
|
|
params[jss::output_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_create", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result.isMember(jss::file_size));
|
|
uint64_t originalSize = result[jss::file_size].asUInt();
|
|
BEAST_EXPECT(originalSize > 0);
|
|
}
|
|
|
|
// Test 1: Successful file size verification (normal load)
|
|
{
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == jss::success);
|
|
BEAST_EXPECT(result.isMember(jss::file_size));
|
|
}
|
|
|
|
// Test 2: Modify file size in header to cause mismatch
|
|
{
|
|
// Modify the filesize in the header to cause mismatch
|
|
std::fstream file(
|
|
cataloguePath, std::ios::in | std::ios::out | std::ios::binary);
|
|
BEAST_EXPECT(file.good());
|
|
|
|
file.seekp(offsetof(TestCATLHeader, filesize), std::ios::beg);
|
|
uint64_t wrongSize = 12345; // Some arbitrary wrong size
|
|
file.write(
|
|
reinterpret_cast<const char*>(&wrongSize), sizeof(wrongSize));
|
|
file.close();
|
|
|
|
// Try to load the modified file
|
|
Json::Value params{Json::objectValue};
|
|
params[jss::input_file] = cataloguePath;
|
|
|
|
auto const result =
|
|
env.client().invoke("catalogue_load", params)[jss::result];
|
|
BEAST_EXPECT(result[jss::status] == "error");
|
|
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
|
BEAST_EXPECT(
|
|
result[jss::error_message].asString().find(
|
|
"file size mismatch") != std::string::npos);
|
|
}
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testCatalogueCompression(FeatureBitset features)
|
|
{
|
|
testcase("catalogue: Compression levels");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment and test data
|
|
Env env{*this, envconfig(), features};
|
|
prepareLedgerData(env, 5);
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
std::vector<std::pair<std::string, Json::Value>> compressionTests = {
|
|
{"no_compression", Json::Value(0)}, // Level 0 (none)
|
|
{"min_compression", Json::Value(1)}, // Level 1 (minimal)
|
|
{"default_compression", Json::Value(6)}, // Level 6 (default)
|
|
{"max_compression", Json::Value(9)}, // Level 9 (maximum)
|
|
{"boolean_true_compression",
|
|
Json::Value(true)} // Boolean true (should use default level 6)
|
|
};
|
|
|
|
uint64_t prevSize = 0;
|
|
for (const auto& test : compressionTests)
|
|
{
|
|
std::string testName = test.first;
|
|
Json::Value compressionLevel = test.second;
|
|
|
|
auto cataloguePath = (tempDir / (testName + ".catl")).string();
|
|
|
|
// Create catalogue with specific compression level
|
|
Json::Value createParams{Json::objectValue};
|
|
createParams[jss::min_ledger] = 3;
|
|
createParams[jss::max_ledger] = 10;
|
|
createParams[jss::output_file] = cataloguePath;
|
|
createParams[jss::compression_level] = compressionLevel;
|
|
|
|
auto createResult = env.client().invoke(
|
|
"catalogue_create", createParams)[jss::result];
|
|
|
|
BEAST_EXPECT(createResult[jss::status] == jss::success);
|
|
|
|
uint64_t fileSize = createResult[jss::file_size].asUInt();
|
|
BEAST_EXPECT(fileSize > 0);
|
|
|
|
// Load the catalogue to verify it works
|
|
Json::Value loadParams{Json::objectValue};
|
|
loadParams[jss::input_file] = cataloguePath;
|
|
|
|
auto loadResult =
|
|
env.client().invoke("catalogue_load", loadParams)[jss::result];
|
|
BEAST_EXPECT(loadResult[jss::status] == jss::success);
|
|
|
|
// For levels > 0, verify size is smaller than uncompressed (or at
|
|
// least not larger)
|
|
if (prevSize > 0 && compressionLevel.asUInt() > 0)
|
|
{
|
|
BEAST_EXPECT(fileSize <= prevSize);
|
|
}
|
|
|
|
// Store size for comparison with next level
|
|
if (compressionLevel.asUInt() == 0)
|
|
{
|
|
prevSize = fileSize;
|
|
}
|
|
|
|
// Verify compression level in response
|
|
if (compressionLevel.isBool() && compressionLevel.asBool())
|
|
{
|
|
BEAST_EXPECT(
|
|
createResult[jss::compression_level].asUInt() == 6);
|
|
}
|
|
else
|
|
{
|
|
BEAST_EXPECT(
|
|
createResult[jss::compression_level].asUInt() ==
|
|
compressionLevel.asUInt());
|
|
}
|
|
}
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
void
|
|
testCatalogueStatus(FeatureBitset features)
|
|
{
|
|
testcase("catalogue_status: Status reporting");
|
|
using namespace test::jtx;
|
|
|
|
// Create environment
|
|
Env env{*this, envconfig(), features};
|
|
|
|
boost::filesystem::path tempDir =
|
|
boost::filesystem::temp_directory_path() /
|
|
boost::filesystem::unique_path();
|
|
boost::filesystem::create_directories(tempDir);
|
|
|
|
auto cataloguePath = (tempDir / "test.catl").string();
|
|
|
|
// Test 1: Check status when no job is running
|
|
{
|
|
auto result = env.client().invoke(
|
|
"catalogue_status", Json::objectValue)[jss::result];
|
|
std::cout << to_string(result) << "\n";
|
|
BEAST_EXPECT(result[jss::job_status] == "no_job_running");
|
|
}
|
|
|
|
// TODO: add a parallel job test here... if anyone feels thats actually
|
|
// needed
|
|
|
|
boost::filesystem::remove_all(tempDir);
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
using namespace test::jtx;
|
|
FeatureBitset const all{supported_amendments()};
|
|
testCatalogueCreateBadInput(all);
|
|
testCatalogueCreate(all);
|
|
testCatalogueLoadBadInput(all);
|
|
testCatalogueLoadAndVerify(all);
|
|
testNetworkMismatch(all);
|
|
testCatalogueHashVerification(all);
|
|
testCatalogueFileSize(all);
|
|
testCatalogueCompression(all);
|
|
testCatalogueStatus(all);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(Catalogue, rpc, ripple);
|
|
|
|
} // namespace ripple
|