Files
rippled/src/test/rpc/TransactionHistory_test.cpp
2026-04-17 16:43:49 +00:00

157 lines
5.1 KiB
C++

#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/envconfig.h>
#include <test/jtx/offer.h>
#include <test/jtx/pay.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
#include <boost/container/static_vector.hpp>
#include <cstddef>
#include <string>
#include <unordered_map>
namespace xrpl {
class TransactionHistory_test : public beast::unit_test::suite
{
void
testBadInput()
{
testcase("Invalid request params");
using namespace test::jtx;
Env env{*this, envconfig(no_admin)};
{
// no params
auto const result = env.client().invoke("tx_history", {})[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
}
{
// test at 1 greater than the allowed non-admin limit
Json::Value params{Json::objectValue};
params[jss::start] = 10001; // limited to <= 10000 for non admin
auto const result = env.client().invoke("tx_history", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "noPermission");
BEAST_EXPECT(result[jss::status] == "error");
}
}
void
testCommandRetired()
{
testcase("Command retired from API v2");
using namespace test::jtx;
Env env{*this, envconfig(no_admin)};
Json::Value params{Json::objectValue};
params[jss::api_version] = 2;
auto const result = env.client().invoke("tx_history", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "unknownCmd");
BEAST_EXPECT(result[jss::status] == "error");
}
void
testRequest()
{
testcase("Basic request");
using namespace test::jtx;
Env env{*this};
// create enough transactions to provide some
// history...
size_t const numAccounts = 20;
boost::container::static_vector<Account, numAccounts> accounts;
for (size_t i = 0; i < numAccounts; ++i)
{
accounts.emplace_back("A" + std::to_string(i));
auto const& acct = accounts.back();
env.fund(XRP(10000), acct);
env.close();
if (i > 0)
{
auto const& prev = accounts[i - 1];
env.trust(acct["USD"](1000), prev);
env(pay(acct, prev, acct["USD"](5)));
}
env(offer(acct, XRP(100), acct["USD"](1)));
env.close();
// verify the latest transaction in env (offer)
// is available in tx_history.
Json::Value params{Json::objectValue};
params[jss::start] = 0;
auto result = env.client().invoke("tx_history", params)[jss::result];
if (!BEAST_EXPECT(result[jss::txs].isArray() && result[jss::txs].size() > 0))
return;
// search for a tx in history matching the last offer
bool const txFound = [&] {
auto const toFind = env.tx()->getJson(JsonOptions::none);
for (auto tx : result[jss::txs])
{
tx.removeMember(jss::inLedger);
tx.removeMember(jss::ledger_index);
if (toFind == tx)
return true;
}
return false;
}();
BEAST_EXPECT(txFound);
}
unsigned int start = 0;
unsigned int total = 0;
// also summarize the transaction types in this map
std::unordered_map<std::string, unsigned> typeCounts;
while (start < 120)
{
Json::Value params{Json::objectValue};
params[jss::start] = start;
auto result = env.client().invoke("tx_history", params)[jss::result];
if (!BEAST_EXPECT(result[jss::txs].isArray() && result[jss::txs].size() > 0))
break;
total += result[jss::txs].size();
start += 20;
for (auto const& t : result[jss::txs])
{
typeCounts[t[sfTransactionType.fieldName].asString()]++;
}
}
BEAST_EXPECT(total == 117);
BEAST_EXPECT(typeCounts[jss::AccountSet.c_str()] == 20);
BEAST_EXPECT(typeCounts[jss::TrustSet.c_str()] == 19);
BEAST_EXPECT(typeCounts[jss::Payment.c_str()] == 58);
BEAST_EXPECT(typeCounts[jss::OfferCreate.c_str()] == 20);
// also, try a request with max non-admin start value
{
Json::Value params{Json::objectValue};
params[jss::start] = 10000; // limited to <= 10000 for non admin
auto const result = env.client().invoke("tx_history", params)[jss::result];
BEAST_EXPECT(result[jss::status] == "success");
BEAST_EXPECT(result[jss::index] == 10000);
}
}
public:
void
run() override
{
testBadInput();
testRequest();
testCommandRetired();
}
};
BEAST_DEFINE_TESTSUITE(TransactionHistory, rpc, xrpl);
} // namespace xrpl