mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-28 14:35:48 +00:00
@@ -704,6 +704,7 @@ if (tests)
|
||||
src/test/app/HashRouter_test.cpp
|
||||
src/test/app/LedgerHistory_test.cpp
|
||||
src/test/app/LedgerLoad_test.cpp
|
||||
src/test/app/LedgerMaster_test.cpp
|
||||
src/test/app/LedgerReplay_test.cpp
|
||||
src/test/app/LoadFeeTrack_test.cpp
|
||||
src/test/app/Manifest_test.cpp
|
||||
|
||||
@@ -291,8 +291,8 @@ public:
|
||||
// Returns the minimum ledger sequence in SQL database, if any.
|
||||
std::optional<LedgerIndex>
|
||||
minSqlSeq();
|
||||
|
||||
// Iff a txn exists at the specified ledger and offset then return its txnid
|
||||
|
||||
// Iff a txn exists at the specified ledger and offset then return its txnid
|
||||
std::optional<uint256>
|
||||
txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex);
|
||||
|
||||
|
||||
@@ -2373,7 +2373,6 @@ LedgerMaster::minSqlSeq()
|
||||
return app_.getRelationalDatabase().getMinLedgerSeq();
|
||||
}
|
||||
|
||||
|
||||
std::optional<uint256>
|
||||
LedgerMaster::txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
|
||||
{
|
||||
@@ -2388,8 +2387,8 @@ LedgerMaster::txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
|
||||
|
||||
for (auto it = lgr->txs.begin(); it != lgr->txs.end(); it++)
|
||||
if (it->first && it->second &&
|
||||
it->second->isFieldPresent(sfTransactionIndex) &&
|
||||
it->second->getFieldU32(sfTransactionIndex) == txnIndex)
|
||||
it->second->isFieldPresent(sfTransactionIndex) &&
|
||||
it->second->getFieldU32(sfTransactionIndex) == txnIndex)
|
||||
return it->first->getTransactionID();
|
||||
|
||||
return {};
|
||||
|
||||
@@ -1267,7 +1267,11 @@ private:
|
||||
jvRequest[jss::max_ledger] = jvParams[2u + offset].asString();
|
||||
}
|
||||
|
||||
jvRequest[jss::transaction] = jvParams[0u].asString();
|
||||
if (jvParams[0u].asString().length() == 16)
|
||||
jvRequest[jss::ctid] = jvParams[0u].asString();
|
||||
else
|
||||
jvRequest[jss::transaction] = jvParams[0u].asString();
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
|
||||
88
src/ripple/rpc/CTID.h
Normal file
88
src/ripple/rpc/CTID.h
Normal file
@@ -0,0 +1,88 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RPC_CTID_H_INCLUDED
|
||||
#define RIPPLE_RPC_CTID_H_INCLUDED
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace RPC {
|
||||
|
||||
inline std::optional<std::string>
|
||||
encodeCTID(
|
||||
uint32_t ledger_seq,
|
||||
uint16_t txn_index,
|
||||
uint16_t network_id) noexcept
|
||||
{
|
||||
if (ledger_seq > 0xFFFFFFF)
|
||||
return {};
|
||||
|
||||
uint64_t ctidValue =
|
||||
((0xC0000000ULL + static_cast<uint64_t>(ledger_seq)) << 32) +
|
||||
(static_cast<uint64_t>(txn_index) << 16) + network_id;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
|
||||
<< ctidValue;
|
||||
return {buffer.str()};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
|
||||
decodeCTID(const T ctid) noexcept
|
||||
{
|
||||
uint64_t ctidValue{0};
|
||||
if constexpr (
|
||||
std::is_same_v<T, std::string> || std::is_same_v<T, char*> ||
|
||||
std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>)
|
||||
{
|
||||
const std::string ctidString(ctid);
|
||||
|
||||
if (ctidString.length() != 16)
|
||||
return {};
|
||||
|
||||
if (!std::regex_match(ctidString, std::regex("^[0-9A-F]+$")))
|
||||
return {};
|
||||
|
||||
ctidValue = std::stoull(ctidString, nullptr, 16);
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
ctidValue = ctid;
|
||||
else
|
||||
return {};
|
||||
|
||||
if (ctidValue > 0xFFFFFFFFFFFFFFFFULL ||
|
||||
(ctidValue & 0xF000000000000000ULL) != 0xC000000000000000ULL)
|
||||
return {};
|
||||
|
||||
uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFFFFFUL;
|
||||
uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
|
||||
uint16_t network_id = ctidValue & 0xFFFFU;
|
||||
return {{ledger_seq, txn_index, network_id}};
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -28,66 +28,13 @@
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/DeliveredAmount.h>
|
||||
#include <ripple/rpc/GRPCHandlers.h>
|
||||
#include <ripple/rpc/CTID.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <charconv>
|
||||
#include <regex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// {
|
||||
// transaction: <hex> |
|
||||
// ctid: <hex>
|
||||
// }
|
||||
|
||||
std::optional<std::string>
|
||||
encodeCTID(uint32_t ledger_seq, uint16_t txn_index, uint16_t network_id) noexcept
|
||||
{
|
||||
if (ledger_seq > 0xFFFFFFF)
|
||||
return {};
|
||||
|
||||
uint64_t ctidValue =
|
||||
((0xC0000000ULL + static_cast<uint64_t>(ledger_seq)) << 32) +
|
||||
(static_cast<uint64_t>(txn_index) << 16) + network_id;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << ctidValue;
|
||||
return {buffer.str()};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
|
||||
decodeCTID(const T ctid) noexcept
|
||||
{
|
||||
uint64_t ctidValue {0};
|
||||
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, char *> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, std::string_view>)
|
||||
{
|
||||
const std::string ctidString(ctid);
|
||||
|
||||
if (ctidString.length() != 16)
|
||||
return {};
|
||||
|
||||
if (!std::regex_match(ctidString, std::regex("^[0-9A-F]+$")))
|
||||
return {};
|
||||
|
||||
ctidValue = std::stoull(ctidString, nullptr, 16);
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
ctidValue = ctid;
|
||||
else
|
||||
return {};
|
||||
|
||||
if (ctidValue > 0xFFFFFFFFFFFFFFFFULL ||
|
||||
(ctidValue & 0xF000000000000000ULL) != 0xC000000000000000ULL)
|
||||
return {};
|
||||
|
||||
uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFFFFFUL;
|
||||
uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
|
||||
uint16_t network_id = ctidValue & 0xFFFFU;
|
||||
return {{ledger_seq, txn_index, network_id}};
|
||||
}
|
||||
|
||||
static bool
|
||||
isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash)
|
||||
{
|
||||
@@ -126,16 +73,19 @@ doTxPostgres(RPC::Context& context, TxArgs const& args)
|
||||
Throw<std::runtime_error>(
|
||||
"Called doTxPostgres yet not in reporting mode");
|
||||
}
|
||||
|
||||
|
||||
TxResult res;
|
||||
res.searchedAll = TxSearched::unknown;
|
||||
|
||||
if (!args.hash)
|
||||
return {res, {rpcNOT_IMPL, "Use of CTIDs on reporting mode is not currently supported."}};
|
||||
|
||||
if (!args.hash)
|
||||
return {
|
||||
res,
|
||||
{rpcNOT_IMPL,
|
||||
"Use of CTIDs on reporting mode is not currently supported."}};
|
||||
|
||||
JLOG(context.j.debug()) << "Fetching from postgres";
|
||||
Transaction::Locator locator = Transaction::locate(*(args.hash), context.app);
|
||||
Transaction::Locator locator =
|
||||
Transaction::locate(*(args.hash), context.app);
|
||||
|
||||
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
|
||||
pair;
|
||||
@@ -258,14 +208,14 @@ doTxHelp(RPC::Context& context, TxArgs args)
|
||||
|
||||
if (args.ctid)
|
||||
{
|
||||
args.hash =
|
||||
context.app.getLedgerMaster().
|
||||
txnIDfromIndex(args.ctid->first, args.ctid->second);
|
||||
if (!args.hash)
|
||||
return {result, rpcTXN_NOT_FOUND};
|
||||
range = ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
|
||||
args.hash = context.app.getLedgerMaster().txnIDfromIndex(
|
||||
args.ctid->first, args.ctid->second);
|
||||
|
||||
if (args.hash)
|
||||
range =
|
||||
ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
|
||||
}
|
||||
|
||||
|
||||
if (args.ledgerRange)
|
||||
{
|
||||
v = context.app.getMasterTransaction().fetch(*(args.hash), range, ec);
|
||||
@@ -321,8 +271,7 @@ doTxHelp(RPC::Context& context, TxArgs args)
|
||||
uint32_t netID = context.app.config().NETWORK_ID;
|
||||
|
||||
if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0xFFFFFFFUL)
|
||||
result.ctid =
|
||||
encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
|
||||
result.ctid = RPC::encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
|
||||
}
|
||||
|
||||
return {result, rpcSUCCESS};
|
||||
@@ -393,19 +342,21 @@ doTxJson(RPC::JsonContext& context)
|
||||
|
||||
TxArgs args;
|
||||
|
||||
if (context.params.isMember(jss::transaction) && context.params.isMember(jss::ctid))
|
||||
if (context.params.isMember(jss::transaction) &&
|
||||
context.params.isMember(jss::ctid))
|
||||
// specifying both is ambiguous
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
|
||||
|
||||
if (context.params.isMember(jss::transaction))
|
||||
{
|
||||
uint256 hash;
|
||||
if (!hash.parseHex(context.params[jss::transaction].asString()))
|
||||
return rpcError(rpcNOT_IMPL);
|
||||
args.hash = hash;
|
||||
} else if (context.params.isMember(jss::ctid)) {
|
||||
auto ctid = decodeCTID(context.params[jss::ctid].asString());
|
||||
}
|
||||
else if (context.params.isMember(jss::ctid))
|
||||
{
|
||||
auto ctid = RPC::decodeCTID(context.params[jss::ctid].asString());
|
||||
if (!ctid)
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
|
||||
@@ -413,9 +364,10 @@ doTxJson(RPC::JsonContext& context)
|
||||
if (net_id != context.app.config().NETWORK_ID)
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "Wrong network. You should submit this request to a node running on NetworkID: " << net_id;
|
||||
return RPC::make_error(
|
||||
rpcWRONG_NETWORK, out.str());
|
||||
out << "Wrong network. You should submit this request to a node "
|
||||
"running on NetworkID: "
|
||||
<< net_id;
|
||||
return RPC::make_error(rpcWRONG_NETWORK, out.str());
|
||||
}
|
||||
args.ctid = {lgr_seq, txn_idx};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <regex>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
|
||||
129
src/test/app/LedgerMaster_test.cpp
Normal file
129
src/test/app/LedgerMaster_test.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 XRPLF
|
||||
|
||||
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/protocol/jss.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class LedgerMaster_test : public beast::unit_test::suite
|
||||
{
|
||||
std::unique_ptr<Config>
|
||||
makeNetworkConfig(uint32_t networkID)
|
||||
{
|
||||
using namespace jtx;
|
||||
return envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->NETWORK_ID = networkID;
|
||||
return cfg;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testTxnIDfromIndex(FeatureBitset features)
|
||||
{
|
||||
testcase("tx_id_from_index");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
test::jtx::Env env{*this, makeNetworkConfig(11111)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// build ledgers
|
||||
std::vector<std::shared_ptr<STTx const>> txns;
|
||||
std::vector<std::shared_ptr<STObject const>> metas;
|
||||
auto const startLegSeq = env.current()->info().seq;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
env(noop(alice));
|
||||
txns.emplace_back(env.tx());
|
||||
env.close();
|
||||
metas.emplace_back(
|
||||
env.closed()->txRead(env.tx()->getTransactionID()).second);
|
||||
}
|
||||
// add last (empty) ledger
|
||||
env.close();
|
||||
auto const endLegSeq = env.closed()->info().seq;
|
||||
|
||||
// test invalid range
|
||||
{
|
||||
std::uint32_t ledgerSeq = -1;
|
||||
std::uint32_t txnIndex = 0;
|
||||
auto result =
|
||||
env.app().getLedgerMaster().txnIDfromIndex(ledgerSeq, txnIndex);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
// test not in ledger
|
||||
{
|
||||
uint32_t txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
|
||||
auto result =
|
||||
env.app().getLedgerMaster().txnIDfromIndex(0, txnIndex);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
// test empty ledger
|
||||
{
|
||||
auto result =
|
||||
env.app().getLedgerMaster().txnIDfromIndex(endLegSeq, 0);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
// ended without result
|
||||
{
|
||||
uint32_t txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
|
||||
auto result = env.app().getLedgerMaster().txnIDfromIndex(
|
||||
endLegSeq + 1, txnIndex);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
// success
|
||||
{
|
||||
uint32_t txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
|
||||
auto result = env.app().getLedgerMaster().txnIDfromIndex(
|
||||
startLegSeq, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("277F4FD89C20B92457FEF05FF63F6405563AD0563C73D967A29727"
|
||||
"72679ADC65"));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testWithFeats(all);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testTxnIDfromIndex(features);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LedgerMaster, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -5746,6 +5746,37 @@ static RPCCallTestData const rpcCallTestArray[] = {
|
||||
|
||||
// tx
|
||||
// --------------------------------------------------------------------------
|
||||
{"tx: ctid. minimal",
|
||||
__LINE__,
|
||||
{"tx", "FFFFFFFFFFFFFFFF", "1", "2"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "tx",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %MAX_API_VER%,
|
||||
"ctid" : "FFFFFFFFFFFFFFFF",
|
||||
"max_ledger" : "2",
|
||||
"min_ledger" : "1"
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"tx: ctid. binary",
|
||||
__LINE__,
|
||||
{"tx", "FFFFFFFFFFFFFFFF", "binary", "1", "2"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "tx",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %MAX_API_VER%,
|
||||
"binary" : true,
|
||||
"ctid" : "FFFFFFFFFFFFFFFF",
|
||||
"max_ledger" : "2",
|
||||
"min_ledger" : "1"
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"tx: minimal.",
|
||||
__LINE__,
|
||||
{"tx", "transaction_hash_is_not_validated"},
|
||||
@@ -6532,6 +6563,16 @@ updateAPIVersionString(const char* const req)
|
||||
return jr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Config>
|
||||
makeNetworkConfig(uint32_t networkID)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
return envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->NETWORK_ID = networkID;
|
||||
return cfg;
|
||||
});
|
||||
}
|
||||
|
||||
class RPCCall_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
@@ -6540,7 +6581,8 @@ public:
|
||||
{
|
||||
testcase << "RPCCall";
|
||||
|
||||
test::jtx::Env env(*this); // Used only for its Journal.
|
||||
test::jtx::Env env(
|
||||
*this, makeNetworkConfig(11111)); // Used only for its Journal.
|
||||
|
||||
// For each RPCCall test.
|
||||
for (RPCCallTestData const& rpcCallTest : rpcCallTestArray)
|
||||
|
||||
@@ -20,16 +20,29 @@
|
||||
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/rpc/CTID.h>
|
||||
#include <optional>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <tuple>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Transaction_test : public beast::unit_test::suite
|
||||
{
|
||||
std::unique_ptr<Config>
|
||||
makeNetworkConfig(uint32_t networkID)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
return envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->NETWORK_ID = networkID;
|
||||
return cfg;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testRangeRequest()
|
||||
testRangeRequest(FeatureBitset features)
|
||||
{
|
||||
testcase("Test Range Request");
|
||||
|
||||
@@ -43,7 +56,7 @@ class Transaction_test : public beast::unit_test::suite
|
||||
const char* EXCESSIVE =
|
||||
RPC::get_error_info(rpcEXCESSIVE_LGR_RANGE).token;
|
||||
|
||||
Env env(*this);
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
@@ -278,11 +291,422 @@ class Transaction_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRangeCTIDRequest(FeatureBitset features)
|
||||
{
|
||||
testcase("ctid_range");
|
||||
|
||||
using namespace test::jtx;
|
||||
using std::to_string;
|
||||
|
||||
const char* COMMAND = jss::tx.c_str();
|
||||
const char* BINARY = jss::binary.c_str();
|
||||
const char* NOT_FOUND = RPC::get_error_info(rpcTXN_NOT_FOUND).token;
|
||||
const char* INVALID = RPC::get_error_info(rpcINVALID_LGR_RANGE).token;
|
||||
const char* EXCESSIVE =
|
||||
RPC::get_error_info(rpcEXCESSIVE_LGR_RANGE).token;
|
||||
|
||||
Env env{*this, makeNetworkConfig(11111)};
|
||||
uint32_t netID = env.app().config().NETWORK_ID;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
std::vector<std::shared_ptr<STTx const>> txns;
|
||||
std::vector<std::shared_ptr<STObject const>> metas;
|
||||
auto const startLegSeq = env.current()->info().seq;
|
||||
for (int i = 0; i < 750; ++i)
|
||||
{
|
||||
env(noop(alice));
|
||||
txns.emplace_back(env.tx());
|
||||
env.close();
|
||||
metas.emplace_back(
|
||||
env.closed()->txRead(env.tx()->getTransactionID()).second);
|
||||
}
|
||||
auto const endLegSeq = env.closed()->info().seq;
|
||||
|
||||
// Find the existing transactions
|
||||
for (size_t i = 0; i < txns.size(); ++i)
|
||||
{
|
||||
auto const& tx = txns[i];
|
||||
auto const& meta = metas[i];
|
||||
uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
*RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
|
||||
BINARY,
|
||||
to_string(startLegSeq),
|
||||
to_string(endLegSeq));
|
||||
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::tx] ==
|
||||
strHex(tx->getSerializer().getData()));
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::meta] ==
|
||||
strHex(meta->getSerializer().getData()));
|
||||
}
|
||||
|
||||
auto const tx = env.jt(noop(alice), seq(env.seq(alice))).stx;
|
||||
auto const ctid =
|
||||
*RPC::encodeCTID(endLegSeq, tx->getSeqProxy().value(), netID);
|
||||
for (int deltaEndSeq = 0; deltaEndSeq < 3; ++deltaEndSeq)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
BINARY,
|
||||
to_string(startLegSeq),
|
||||
to_string(endLegSeq + deltaEndSeq));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == NOT_FOUND);
|
||||
|
||||
if (deltaEndSeq)
|
||||
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
|
||||
else
|
||||
BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
|
||||
}
|
||||
|
||||
// Find transactions outside of provided range.
|
||||
for (size_t i = 0; i < txns.size(); ++i)
|
||||
{
|
||||
// auto const& tx = txns[i];
|
||||
auto const& meta = metas[i];
|
||||
uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
*RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
|
||||
BINARY,
|
||||
to_string(endLegSeq + 1),
|
||||
to_string(endLegSeq + 100));
|
||||
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
|
||||
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
|
||||
}
|
||||
|
||||
const auto deletedLedger = (startLegSeq + endLegSeq) / 2;
|
||||
{
|
||||
// Remove one of the ledgers from the database directly
|
||||
dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
|
||||
->deleteTransactionByLedgerSeq(deletedLedger);
|
||||
}
|
||||
|
||||
for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
BINARY,
|
||||
to_string(startLegSeq),
|
||||
to_string(endLegSeq + deltaEndSeq));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == NOT_FOUND);
|
||||
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
|
||||
}
|
||||
|
||||
// Provide range without providing the `binary`
|
||||
// field. (Tests parameter parsing)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND, ctid, to_string(startLegSeq), to_string(endLegSeq));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == NOT_FOUND);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
|
||||
}
|
||||
|
||||
// Provide range without providing the `binary`
|
||||
// field. (Tests parameter parsing)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
to_string(startLegSeq),
|
||||
to_string(deletedLedger - 1));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == NOT_FOUND);
|
||||
|
||||
BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
|
||||
}
|
||||
|
||||
// Provide range without providing the `binary`
|
||||
// field. (Tests parameter parsing)
|
||||
{
|
||||
auto const& meta = metas[0];
|
||||
uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
*RPC::encodeCTID(endLegSeq, txnIdx, netID),
|
||||
to_string(startLegSeq),
|
||||
to_string(deletedLedger - 1));
|
||||
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (min > max)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
BINARY,
|
||||
to_string(deletedLedger - 1),
|
||||
to_string(startLegSeq));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == INVALID);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (min < 0)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
BINARY,
|
||||
to_string(-1),
|
||||
to_string(deletedLedger - 1));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == INVALID);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (min < 0, max < 0)
|
||||
{
|
||||
auto const result =
|
||||
env.rpc(COMMAND, ctid, BINARY, to_string(-20), to_string(-10));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == INVALID);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (only one value)
|
||||
{
|
||||
auto const result = env.rpc(COMMAND, ctid, BINARY, to_string(20));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == INVALID);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (only one value)
|
||||
{
|
||||
auto const result = env.rpc(COMMAND, ctid, to_string(20));
|
||||
|
||||
// Since we only provided one value for the range,
|
||||
// the interface parses it as a false binary flag,
|
||||
// as single-value ranges are not accepted. Since
|
||||
// the error this causes differs depending on the platform
|
||||
// we don't call out a specific error here.
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
|
||||
// Provide an invalid range: (max - min > 1000)
|
||||
{
|
||||
auto const result = env.rpc(
|
||||
COMMAND,
|
||||
ctid,
|
||||
BINARY,
|
||||
to_string(startLegSeq),
|
||||
to_string(startLegSeq + 1001));
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::status] == jss::error &&
|
||||
result[jss::result][jss::error] == EXCESSIVE);
|
||||
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCTIDValidation(FeatureBitset features)
|
||||
{
|
||||
testcase("ctid_validation");
|
||||
|
||||
using namespace test::jtx;
|
||||
using std::to_string;
|
||||
|
||||
Env env{*this, makeNetworkConfig(11111)};
|
||||
|
||||
// Test case 1: Valid input values
|
||||
auto const expected11 = std::optional<std::string>("CFFFFFFFFFFFFFFF");
|
||||
BEAST_EXPECT(
|
||||
RPC::encodeCTID(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU) == expected11);
|
||||
auto const expected12 = std::optional<std::string>("C000000000000000");
|
||||
BEAST_EXPECT(RPC::encodeCTID(0, 0, 0) == expected12);
|
||||
auto const expected13 = std::optional<std::string>("C000000100020003");
|
||||
BEAST_EXPECT(RPC::encodeCTID(1U, 2U, 3U) == expected13);
|
||||
auto const expected14 = std::optional<std::string>("C0CA2AA7326FFFFF");
|
||||
BEAST_EXPECT(RPC::encodeCTID(13249191UL, 12911U, 65535U) == expected14);
|
||||
|
||||
// Test case 2: ledger_seq greater than 0xFFFFFFF
|
||||
BEAST_EXPECT(!RPC::encodeCTID(0x10000000UL, 0xFFFFU, 0xFFFFU));
|
||||
|
||||
// Test case 3: txn_index greater than 0xFFFF
|
||||
// this test case is impossible in c++ due to the type, left in for
|
||||
// completeness
|
||||
auto const expected3 = std::optional<std::string>("CFFFFFFF0000FFFF");
|
||||
BEAST_EXPECT(
|
||||
RPC::encodeCTID(0xFFFFFFF, (uint16_t)0x10000, 0xFFFF) == expected3);
|
||||
|
||||
// Test case 4: network_id greater than 0xFFFF
|
||||
// this test case is impossible in c++ due to the type, left in for
|
||||
// completeness
|
||||
auto const expected4 = std::optional<std::string>("CFFFFFFFFFFF0000");
|
||||
BEAST_EXPECT(
|
||||
RPC::encodeCTID(0xFFFFFFFUL, 0xFFFFU, (uint16_t)0x10000U) ==
|
||||
expected4);
|
||||
|
||||
// Test case 5: Valid input values
|
||||
auto const expected51 =
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(0, 0, 0));
|
||||
BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51);
|
||||
auto const expected52 =
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(1U, 2U, 3U));
|
||||
BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52);
|
||||
auto const expected53 =
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(13249191UL, 12911U, 49221U));
|
||||
BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53);
|
||||
|
||||
// Test case 6: ctid not a string or big int
|
||||
BEAST_EXPECT(!RPC::decodeCTID(0xCFF));
|
||||
|
||||
// Test case 7: ctid not a hexadecimal string
|
||||
BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG"));
|
||||
|
||||
// Test case 8: ctid not exactly 16 nibbles
|
||||
BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF"));
|
||||
|
||||
// Test case 9: ctid too large to be a valid CTID value
|
||||
BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF"));
|
||||
|
||||
// Test case 10: ctid doesn't start with a C nibble
|
||||
BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF"));
|
||||
|
||||
// Test case 11: Valid input values
|
||||
BEAST_EXPECT(
|
||||
(RPC::decodeCTID(0xCFFFFFFFFFFFFFFFULL) ==
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(0xFFFFFFFUL, 0xFFFFU, 0xFFFFU))));
|
||||
BEAST_EXPECT(
|
||||
(RPC::decodeCTID(0xC000000000000000ULL) ==
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(0, 0, 0))));
|
||||
BEAST_EXPECT(
|
||||
(RPC::decodeCTID(0xC000000100020003ULL) ==
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(1U, 2U, 3U))));
|
||||
BEAST_EXPECT(
|
||||
(RPC::decodeCTID(0xC0CA2AA7326FC045ULL) ==
|
||||
std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
|
||||
std::make_tuple(13249191UL, 12911U, 49221U))));
|
||||
|
||||
// Test case 12: ctid not exactly 16 nibbles
|
||||
BEAST_EXPECT(!RPC::decodeCTID(0xC003FFFFFFFFFFF));
|
||||
|
||||
// Test case 13: ctid too large to be a valid CTID value
|
||||
// this test case is not possible in c++ because it would overflow the
|
||||
// type, left in for completeness
|
||||
// BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
|
||||
|
||||
// Test case 14: ctid doesn't start with a C nibble
|
||||
BEAST_EXPECT(!RPC::decodeCTID(0xFFFFFFFFFFFFFFFFULL));
|
||||
}
|
||||
|
||||
void
|
||||
testCTIDRPC(FeatureBitset features)
|
||||
{
|
||||
testcase("ctid_rpc");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
// test that the ctid AND the hash are in the response
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(11111)};
|
||||
uint32_t netID = env.app().config().NETWORK_ID;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
|
||||
auto const startLegSeq = env.current()->info().seq;
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env(pay(alice, bob, XRP(10)));
|
||||
env.close();
|
||||
|
||||
auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
|
||||
Json::Value jsonTx;
|
||||
jsonTx[jss::binary] = false;
|
||||
jsonTx[jss::ctid] = ctid;
|
||||
jsonTx[jss::id] = 1;
|
||||
auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::ctid] == ctid);
|
||||
BEAST_EXPECT(jrr[jss::hash]);
|
||||
}
|
||||
|
||||
// test that if the network is 65535 the ctid is not in the response
|
||||
{
|
||||
Env env{*this, makeNetworkConfig(65535)};
|
||||
uint32_t netID = env.app().config().NETWORK_ID;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
|
||||
auto const startLegSeq = env.current()->info().seq;
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env(pay(alice, bob, XRP(10)));
|
||||
env.close();
|
||||
|
||||
auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
|
||||
Json::Value jsonTx;
|
||||
jsonTx[jss::binary] = false;
|
||||
jsonTx[jss::ctid] = ctid;
|
||||
jsonTx[jss::id] = 1;
|
||||
auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
|
||||
BEAST_EXPECT(!jrr[jss::ctid]);
|
||||
BEAST_EXPECT(jrr[jss::hash]);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRangeRequest();
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testWithFeats(all);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testRangeRequest(features);
|
||||
testRangeCTIDRequest(features);
|
||||
testCTIDValidation(features);
|
||||
testCTIDRPC(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user