mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
APIv2: remove tx_history and ledger_header (#4759)
Remove `tx_history` and `ledger_header` methods from API version 2. Update `RPC::Handler` to allow for methods (or method implementations) to be API version specific. This partially resolves #4727. We can now store multiple handlers with the same name, as long as they belong to different (non-overlapping) API versions. This necessarily impacts the handler lookup algorithm and its complexity; however, there is no performance loss on x86_64 architecture, and only minimal performance loss on arm64 (around 10ns). This design change gives us extra flexibility evolving the API in the future, including other parts of #4727. In API version 2, `tx_history` and `ledger_header` are no longer recognised; if they are called, `rippled` will return error `unknownCmd` Resolve #3638 Resolve #3539
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/rpc/impl/Handler.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -309,7 +310,8 @@ public:
|
||||
|
||||
// Get the all the labels we can use for RPC interfaces without
|
||||
// causing an assert.
|
||||
std::vector<char const*> labels{ripple::RPC::getHandlerNames()};
|
||||
std::vector<char const*> labels =
|
||||
test::jtx::make_vector(ripple::RPC::getHandlerNames());
|
||||
std::shuffle(labels.begin(), labels.end(), default_prng());
|
||||
|
||||
// Get two IDs to associate with each label. Errors tend to happen at
|
||||
|
||||
@@ -27,10 +27,19 @@
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <ranges>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
// Helper to make vector from iterable
|
||||
auto
|
||||
make_vector(auto const& input) requires std::ranges::range<decltype(input)>
|
||||
{
|
||||
return std::vector(std::ranges::begin(input), std::ranges::end(input));
|
||||
}
|
||||
|
||||
// Functions used in debugging
|
||||
Json::Value
|
||||
getAccountOffers(Env& env, AccountID const& acct, bool current = false);
|
||||
|
||||
132
src/test/rpc/Handler_test.cpp
Normal file
132
src/test/rpc/Handler_test.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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/beast/unit_test.h>
|
||||
#include <ripple/rpc/impl/Handler.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
|
||||
namespace ripple::test {
|
||||
|
||||
// NOTE: there should be no need for this function;
|
||||
// `std::cout << some_duration` should just work if built with a compliant
|
||||
// C++20 compiler. Sadly, we are not using one, as of today
|
||||
// TODO: remove this operator<< overload when we bump compiler version
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, std::chrono::nanoseconds ns)
|
||||
{
|
||||
return (os << ns.count() << "ns");
|
||||
}
|
||||
|
||||
// NOTE This is a rather naive effort at a microbenchmark. Ideally we want
|
||||
// Google Benchmark, or something similar. Also, this actually does not belong
|
||||
// to unit tests, as it makes little sense to run it in conditions very
|
||||
// dissimilar to how rippled will normally work.
|
||||
// TODO as https://github.com/XRPLF/rippled/issues/4765
|
||||
|
||||
class Handler_test : public beast::unit_test::suite
|
||||
{
|
||||
auto
|
||||
time(std::size_t n, auto f, auto prng) -> auto
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
assert(n > 0);
|
||||
double sum = 0;
|
||||
double sum_squared = 0;
|
||||
std::size_t j = 0;
|
||||
while (j < n)
|
||||
{
|
||||
// Generate 100 inputs upfront, separated from the inner loop
|
||||
std::array<decltype(prng()), 100> inputs = {};
|
||||
for (auto& i : inputs)
|
||||
{
|
||||
i = prng();
|
||||
}
|
||||
|
||||
// Take 100 samples, then sort and throw away 35 from each end,
|
||||
// using only middle 30. This helps to reduce measurement noise.
|
||||
std::array<long, 100> samples = {};
|
||||
for (std::size_t k = 0; k < 100; ++k)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
f(inputs[k]);
|
||||
samples[k] = (std::chrono::steady_clock::now() - start).count();
|
||||
}
|
||||
|
||||
std::sort(samples.begin(), samples.end());
|
||||
for (std::size_t k = 35; k < 65; ++k)
|
||||
{
|
||||
j += 1;
|
||||
sum += samples[k];
|
||||
sum_squared += (samples[k] * samples[k]);
|
||||
}
|
||||
}
|
||||
|
||||
const double mean_squared = (sum * sum) / (j * j);
|
||||
return std::make_tuple(
|
||||
clock::duration{static_cast<long>(sum / j)},
|
||||
clock::duration{
|
||||
static_cast<long>(std::sqrt((sum_squared / j) - mean_squared))},
|
||||
j);
|
||||
}
|
||||
|
||||
void
|
||||
reportLookupPerformance()
|
||||
{
|
||||
testcase("Handler lookup performance");
|
||||
|
||||
std::random_device dev;
|
||||
std::ranlux48 prng(dev());
|
||||
|
||||
std::vector<const char*> names =
|
||||
test::jtx::make_vector(ripple::RPC::getHandlerNames());
|
||||
|
||||
std::uniform_int_distribution<std::size_t> distr{0, names.size() - 1};
|
||||
|
||||
std::size_t dummy = 0;
|
||||
auto const [mean, stdev, n] = time(
|
||||
1'000'000,
|
||||
[&](std::size_t i) {
|
||||
auto const d = RPC::getHandler(1, false, names[i]);
|
||||
dummy = dummy + i + (int)d->role_;
|
||||
},
|
||||
[&]() -> std::size_t { return distr(prng); });
|
||||
|
||||
std::cout << "mean=" << mean << " stdev=" << stdev << " N=" << n
|
||||
<< '\n';
|
||||
|
||||
BEAST_EXPECT(dummy != 0);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
reportLookupPerformance();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(Handler, test, ripple);
|
||||
|
||||
} // namespace ripple::test
|
||||
91
src/test/rpc/LedgerHeader_test.cpp
Normal file
91
src/test/rpc/LedgerHeader_test.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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/protocol/jss.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class LedgerHeader_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testSimpleCurrent()
|
||||
{
|
||||
testcase("Current ledger");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(no_admin)};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::api_version] = 1;
|
||||
params[jss::ledger_index] = "current";
|
||||
auto const result =
|
||||
env.client().invoke("ledger_header", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(result.isMember("ledger"));
|
||||
BEAST_EXPECT(result[jss::ledger][jss::closed] == false);
|
||||
BEAST_EXPECT(result[jss::validated] == false);
|
||||
}
|
||||
|
||||
void
|
||||
testSimpleValidated()
|
||||
{
|
||||
testcase("Validated ledger");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(no_admin)};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::api_version] = 1;
|
||||
params[jss::ledger_index] = "validated";
|
||||
auto const result =
|
||||
env.client().invoke("ledger_header", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(result.isMember("ledger"));
|
||||
BEAST_EXPECT(result[jss::ledger][jss::closed] == true);
|
||||
BEAST_EXPECT(result[jss::validated] == true);
|
||||
}
|
||||
|
||||
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("ledger_header", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::error] == "unknownCmd");
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testSimpleCurrent();
|
||||
testSimpleValidated();
|
||||
testCommandRetired();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LedgerHeader, rpc, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -54,6 +54,21 @@ class TransactionHistory_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -148,6 +163,7 @@ public:
|
||||
{
|
||||
testBadInput();
|
||||
testRequest();
|
||||
testCommandRetired();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user