mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-28 22:45:49 +00:00
@@ -704,6 +704,7 @@ if (tests)
|
|||||||
src/test/app/HashRouter_test.cpp
|
src/test/app/HashRouter_test.cpp
|
||||||
src/test/app/LedgerHistory_test.cpp
|
src/test/app/LedgerHistory_test.cpp
|
||||||
src/test/app/LedgerLoad_test.cpp
|
src/test/app/LedgerLoad_test.cpp
|
||||||
|
src/test/app/LedgerMaster_test.cpp
|
||||||
src/test/app/LedgerReplay_test.cpp
|
src/test/app/LedgerReplay_test.cpp
|
||||||
src/test/app/LoadFeeTrack_test.cpp
|
src/test/app/LoadFeeTrack_test.cpp
|
||||||
src/test/app/Manifest_test.cpp
|
src/test/app/Manifest_test.cpp
|
||||||
|
|||||||
@@ -291,8 +291,8 @@ public:
|
|||||||
// Returns the minimum ledger sequence in SQL database, if any.
|
// Returns the minimum ledger sequence in SQL database, if any.
|
||||||
std::optional<LedgerIndex>
|
std::optional<LedgerIndex>
|
||||||
minSqlSeq();
|
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>
|
std::optional<uint256>
|
||||||
txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex);
|
txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex);
|
||||||
|
|
||||||
|
|||||||
@@ -2373,7 +2373,6 @@ LedgerMaster::minSqlSeq()
|
|||||||
return app_.getRelationalDatabase().getMinLedgerSeq();
|
return app_.getRelationalDatabase().getMinLedgerSeq();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::optional<uint256>
|
std::optional<uint256>
|
||||||
LedgerMaster::txnIDfromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
|
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++)
|
for (auto it = lgr->txs.begin(); it != lgr->txs.end(); it++)
|
||||||
if (it->first && it->second &&
|
if (it->first && it->second &&
|
||||||
it->second->isFieldPresent(sfTransactionIndex) &&
|
it->second->isFieldPresent(sfTransactionIndex) &&
|
||||||
it->second->getFieldU32(sfTransactionIndex) == txnIndex)
|
it->second->getFieldU32(sfTransactionIndex) == txnIndex)
|
||||||
return it->first->getTransactionID();
|
return it->first->getTransactionID();
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -1267,7 +1267,11 @@ private:
|
|||||||
jvRequest[jss::max_ledger] = jvParams[2u + offset].asString();
|
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;
|
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/Context.h>
|
||||||
#include <ripple/rpc/DeliveredAmount.h>
|
#include <ripple/rpc/DeliveredAmount.h>
|
||||||
#include <ripple/rpc/GRPCHandlers.h>
|
#include <ripple/rpc/GRPCHandlers.h>
|
||||||
|
#include <ripple/rpc/CTID.h>
|
||||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
namespace ripple {
|
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
|
static bool
|
||||||
isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash)
|
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>(
|
Throw<std::runtime_error>(
|
||||||
"Called doTxPostgres yet not in reporting mode");
|
"Called doTxPostgres yet not in reporting mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
TxResult res;
|
TxResult res;
|
||||||
res.searchedAll = TxSearched::unknown;
|
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";
|
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>>
|
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
|
||||||
pair;
|
pair;
|
||||||
@@ -258,14 +208,14 @@ doTxHelp(RPC::Context& context, TxArgs args)
|
|||||||
|
|
||||||
if (args.ctid)
|
if (args.ctid)
|
||||||
{
|
{
|
||||||
args.hash =
|
args.hash = context.app.getLedgerMaster().txnIDfromIndex(
|
||||||
context.app.getLedgerMaster().
|
args.ctid->first, args.ctid->second);
|
||||||
txnIDfromIndex(args.ctid->first, args.ctid->second);
|
|
||||||
if (!args.hash)
|
if (args.hash)
|
||||||
return {result, rpcTXN_NOT_FOUND};
|
range =
|
||||||
range = ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
|
ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.ledgerRange)
|
if (args.ledgerRange)
|
||||||
{
|
{
|
||||||
v = context.app.getMasterTransaction().fetch(*(args.hash), range, ec);
|
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;
|
uint32_t netID = context.app.config().NETWORK_ID;
|
||||||
|
|
||||||
if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0xFFFFFFFUL)
|
if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0xFFFFFFFUL)
|
||||||
result.ctid =
|
result.ctid = RPC::encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
|
||||||
encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {result, rpcSUCCESS};
|
return {result, rpcSUCCESS};
|
||||||
@@ -393,19 +342,21 @@ doTxJson(RPC::JsonContext& context)
|
|||||||
|
|
||||||
TxArgs args;
|
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
|
// specifying both is ambiguous
|
||||||
return rpcError(rpcINVALID_PARAMS);
|
return rpcError(rpcINVALID_PARAMS);
|
||||||
|
|
||||||
|
|
||||||
if (context.params.isMember(jss::transaction))
|
if (context.params.isMember(jss::transaction))
|
||||||
{
|
{
|
||||||
uint256 hash;
|
uint256 hash;
|
||||||
if (!hash.parseHex(context.params[jss::transaction].asString()))
|
if (!hash.parseHex(context.params[jss::transaction].asString()))
|
||||||
return rpcError(rpcNOT_IMPL);
|
return rpcError(rpcNOT_IMPL);
|
||||||
args.hash = hash;
|
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)
|
if (!ctid)
|
||||||
return rpcError(rpcINVALID_PARAMS);
|
return rpcError(rpcINVALID_PARAMS);
|
||||||
|
|
||||||
@@ -413,9 +364,10 @@ doTxJson(RPC::JsonContext& context)
|
|||||||
if (net_id != context.app.config().NETWORK_ID)
|
if (net_id != context.app.config().NETWORK_ID)
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
out << "Wrong network. You should submit this request to a node running on NetworkID: " << net_id;
|
out << "Wrong network. You should submit this request to a node "
|
||||||
return RPC::make_error(
|
"running on NetworkID: "
|
||||||
rpcWRONG_NETWORK, out.str());
|
<< net_id;
|
||||||
|
return RPC::make_error(rpcWRONG_NETWORK, out.str());
|
||||||
}
|
}
|
||||||
args.ctid = {lgr_seq, txn_idx};
|
args.ctid = {lgr_seq, txn_idx};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
|
||||||
|
#include <ripple/resource/Fees.h>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace RPC {
|
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
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
{"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.",
|
{"tx: minimal.",
|
||||||
__LINE__,
|
__LINE__,
|
||||||
{"tx", "transaction_hash_is_not_validated"},
|
{"tx", "transaction_hash_is_not_validated"},
|
||||||
@@ -6532,6 +6563,16 @@ updateAPIVersionString(const char* const req)
|
|||||||
return jr;
|
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
|
class RPCCall_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -6540,7 +6581,8 @@ public:
|
|||||||
{
|
{
|
||||||
testcase << "RPCCall";
|
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 each RPCCall test.
|
||||||
for (RPCCallTestData const& rpcCallTest : rpcCallTestArray)
|
for (RPCCallTestData const& rpcCallTest : rpcCallTestArray)
|
||||||
|
|||||||
@@ -20,16 +20,29 @@
|
|||||||
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
|
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
|
||||||
#include <ripple/protocol/ErrorCodes.h>
|
#include <ripple/protocol/ErrorCodes.h>
|
||||||
#include <ripple/protocol/jss.h>
|
#include <ripple/protocol/jss.h>
|
||||||
|
#include <ripple/rpc/CTID.h>
|
||||||
|
#include <optional>
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
#include <test/jtx/Env.h>
|
#include <test/jtx/Env.h>
|
||||||
#include <test/jtx/envconfig.h>
|
#include <test/jtx/envconfig.h>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class Transaction_test : public beast::unit_test::suite
|
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
|
void
|
||||||
testRangeRequest()
|
testRangeRequest(FeatureBitset features)
|
||||||
{
|
{
|
||||||
testcase("Test Range Request");
|
testcase("Test Range Request");
|
||||||
|
|
||||||
@@ -43,7 +56,7 @@ class Transaction_test : public beast::unit_test::suite
|
|||||||
const char* EXCESSIVE =
|
const char* EXCESSIVE =
|
||||||
RPC::get_error_info(rpcEXCESSIVE_LGR_RANGE).token;
|
RPC::get_error_info(rpcEXCESSIVE_LGR_RANGE).token;
|
||||||
|
|
||||||
Env env(*this);
|
Env env{*this, features};
|
||||||
auto const alice = Account("alice");
|
auto const alice = Account("alice");
|
||||||
env.fund(XRP(1000), alice);
|
env.fund(XRP(1000), alice);
|
||||||
env.close();
|
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:
|
public:
|
||||||
void
|
void
|
||||||
run() override
|
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