mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-24 15:50:55 +00:00
Compare commits
4 Commits
pratik/std
...
bthomee/rp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3cc014398 | ||
|
|
f0900d4f49 | ||
|
|
7a269a33d5 | ||
|
|
fb36b0adc0 |
@@ -82,7 +82,6 @@ test.app > xrpl.tx
|
||||
test.basics > test.jtx
|
||||
test.basics > xrpl.basics
|
||||
test.basics > xrpl.core
|
||||
test.basics > xrpld.rpc
|
||||
test.basics > xrpl.json
|
||||
test.basics > xrpl.protocol
|
||||
test.beast > xrpl.basics
|
||||
@@ -286,10 +285,10 @@ xrpld.perflog > xrpl.basics
|
||||
xrpld.perflog > xrpl.config
|
||||
xrpld.perflog > xrpl.core
|
||||
xrpld.perflog > xrpld.app
|
||||
xrpld.perflog > xrpld.rpc
|
||||
xrpld.perflog > xrpl.json
|
||||
xrpld.perflog > xrpl.nodestore
|
||||
xrpld.perflog > xrpl.protocol
|
||||
xrpld.perflog > xrpl.server
|
||||
xrpld.rpc > xrpl.basics
|
||||
xrpld.rpc > xrpl.config
|
||||
xrpld.rpc > xrpl.core
|
||||
|
||||
@@ -163,4 +163,31 @@ toUInt64(std::string const& s);
|
||||
bool
|
||||
isProperlyFormedTomlDomain(std::string_view domain);
|
||||
|
||||
/**
|
||||
* Whether a view can be passed on as a C string.
|
||||
*
|
||||
* A view is only usable that way if it covers a whole null-terminated string
|
||||
* rather than a slice of one, since a reader given just the pointer stops at
|
||||
* the first null. This asks exactly that question: rebuild the view from its
|
||||
* data() as a C string, as such a reader would, and see if it comes back
|
||||
* unchanged. A slice comes back longer, having run past its own end.
|
||||
*
|
||||
* The byte after a view is not part of it, so this is only well defined when
|
||||
* @p str points into storage that is known to hold a null somewhere at or
|
||||
* after its end -- a string literal, or the buffer of a std::string. For a
|
||||
* view built from a bare pointer and length it reads out of bounds, and in a
|
||||
* constant expression that is a compile error rather than undefined behaviour.
|
||||
*
|
||||
* @param str The view to test.
|
||||
* @return Whether @p str is null-terminated.
|
||||
*/
|
||||
constexpr bool
|
||||
isNullTerminated(std::string_view str)
|
||||
{
|
||||
// Reading past the view is the point, so the usual warning about data() not
|
||||
// being null-terminated does not apply.
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
|
||||
return std::string_view{str.data()} == str;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace beast {
|
||||
class Journal;
|
||||
@@ -67,7 +68,7 @@ public:
|
||||
* @param requestId Unique identifier to track command
|
||||
*/
|
||||
virtual void
|
||||
rpcStart(std::string const& method, std::uint64_t requestId) = 0;
|
||||
rpcStart(std::string_view method, std::uint64_t requestId) = 0;
|
||||
|
||||
/**
|
||||
* Log successful finish of RPC call
|
||||
@@ -76,7 +77,7 @@ public:
|
||||
* @param requestId Unique identifier to track command
|
||||
*/
|
||||
virtual void
|
||||
rpcFinish(std::string const& method, std::uint64_t requestId) = 0;
|
||||
rpcFinish(std::string_view method, std::uint64_t requestId) = 0;
|
||||
|
||||
/**
|
||||
* Log errored RPC call
|
||||
@@ -85,7 +86,7 @@ public:
|
||||
* @param requestId Unique identifier to track command
|
||||
*/
|
||||
virtual void
|
||||
rpcError(std::string const& method, std::uint64_t requestId) = 0;
|
||||
rpcError(std::string_view method, std::uint64_t requestId) = 0;
|
||||
|
||||
/**
|
||||
* Log queued job
|
||||
@@ -150,10 +151,20 @@ public:
|
||||
PerfLog::Setup
|
||||
setupPerfLog(Section const& section, std::filesystem::path const& configDir);
|
||||
|
||||
/**
|
||||
* @param methodNames The RPC methods to count, one counter per name. Each name
|
||||
* must be a view of a whole, null-terminated string literal rather than
|
||||
* a slice of one, because the counters are reported as JSON keys that
|
||||
* borrow the name and read it as a C string. The names must outlive the
|
||||
* returned object, which holds views of them. Callers pass
|
||||
* rpc::getHandlerNames(); it is an argument so that this layer needs to
|
||||
* know nothing about the RPC dispatch table.
|
||||
*/
|
||||
std::unique_ptr<PerfLog>
|
||||
makePerfLog(
|
||||
PerfLog::Setup const& setup,
|
||||
Application& app,
|
||||
std::span<std::string_view const> methodNames,
|
||||
beast::Journal journal,
|
||||
std::function<void()>&& signalStop);
|
||||
|
||||
@@ -161,7 +172,7 @@ template <typename Func, class Rep, class Period>
|
||||
auto
|
||||
measureDurationAndLog(
|
||||
Func&& func,
|
||||
std::string const& actionDescription,
|
||||
std::string_view actionDescription,
|
||||
std::chrono::duration<Rep, Period> maxDelay,
|
||||
beast::Journal const& journal)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
@@ -16,6 +13,7 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
@@ -26,7 +24,9 @@
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <random>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
@@ -42,6 +42,21 @@ class PerfLog_test : public beast::unit_test::Suite
|
||||
|
||||
using path = std::filesystem::path;
|
||||
|
||||
// The method names to count. PerfLog treats these as opaque keys, so these
|
||||
// are made up rather than taken from the RPC dispatch table: this test then
|
||||
// needs to know nothing about the RPC layer, and does not silently change
|
||||
// shape when a method is added or removed.
|
||||
//
|
||||
// They are string literals because PerfLog stores views of these names and
|
||||
// reads them back as C strings, so they must be null-terminated and outlive
|
||||
// the PerfLog. Sorted, because the counters are reported in sorted order.
|
||||
static constexpr std::array kMethodNames{
|
||||
std::string_view{"method_a"},
|
||||
std::string_view{"method_b"},
|
||||
std::string_view{"method_c"},
|
||||
std::string_view{"method_d"},
|
||||
std::string_view{"method_e"}};
|
||||
|
||||
// We're only using Env for its Journal. That Journal gives better
|
||||
// coverage in unit tests.
|
||||
test::jtx::Env env_{*this, test::jtx::envconfig(), nullptr, beast::Severity::Disabled};
|
||||
@@ -114,7 +129,7 @@ class PerfLog_test : public beast::unit_test::Suite
|
||||
{
|
||||
perf::PerfLog::Setup const setup{
|
||||
.perfLog = withFile == WithFile::No ? "" : logFile(), .logInterval = logInterval()};
|
||||
return perf::makePerfLog(setup, app, j, [this]() {
|
||||
return perf::makePerfLog(setup, app, kMethodNames, j, [this]() {
|
||||
signalStop();
|
||||
return;
|
||||
});
|
||||
@@ -310,9 +325,11 @@ public:
|
||||
auto perfLog{fixture.perfLog(withFile)};
|
||||
perfLog->start();
|
||||
|
||||
// Get the all the labels we can use for RPC interfaces without
|
||||
// causing an assert.
|
||||
std::vector<char const*> labels = test::jtx::makeVector(xrpl::rpc::getHandlerNames());
|
||||
// The labels we can use for RPC interfaces without causing an assert:
|
||||
// exactly those the PerfLog was constructed with. Copied into a vector
|
||||
// because they are shuffled below and then paired positionally with the
|
||||
// request ids.
|
||||
auto labels = std::ranges::to<std::vector>(kMethodNames);
|
||||
std::shuffle(labels.begin(), labels.end(), defaultPrng());
|
||||
|
||||
// Get two IDs to associate with each label. Errors tend to happen at
|
||||
@@ -347,7 +364,7 @@ public:
|
||||
for (auto& label : labels)
|
||||
{
|
||||
// Expect every label in labels to have the same contents.
|
||||
json::Value const& counter{countersJson[label]};
|
||||
json::Value const& counter{countersJson[std::string{label}]};
|
||||
BEAST_EXPECT(counter[jss::duration_us] == "0");
|
||||
BEAST_EXPECT(counter[jss::errored] == "0");
|
||||
BEAST_EXPECT(counter[jss::finished] == "0");
|
||||
@@ -404,7 +421,7 @@ public:
|
||||
// their durations with the appropriate labels.
|
||||
{
|
||||
// The first label is special. It should have "errored" : "0".
|
||||
json::Value const& first = rpc[labels[0]];
|
||||
json::Value const& first = rpc[std::string{labels[0]}];
|
||||
BEAST_EXPECT(first[jss::duration_us] != "0");
|
||||
BEAST_EXPECT(first[jss::errored] == "0");
|
||||
BEAST_EXPECT(first[jss::finished] == "1");
|
||||
@@ -415,7 +432,7 @@ public:
|
||||
std::uint64_t prevDur = std::numeric_limits<std::uint64_t>::max();
|
||||
for (int i = 1; i < labels.size(); ++i)
|
||||
{
|
||||
json::Value const& counter{rpc[labels[i]]};
|
||||
json::Value const& counter{rpc[std::string{labels[i]}]};
|
||||
std::uint64_t const dur{jsonToUInt64(counter[jss::duration_us])};
|
||||
BEAST_EXPECT(dur != 0 && dur < prevDur);
|
||||
prevDur = dur;
|
||||
@@ -447,7 +464,7 @@ public:
|
||||
BEAST_EXPECT(only.size() == 2);
|
||||
BEAST_EXPECT(only.isObject());
|
||||
BEAST_EXPECT(only[jss::duration_us] != "0");
|
||||
BEAST_EXPECT(only[jss::method] == labels[0]);
|
||||
BEAST_EXPECT(only[jss::method] == std::string{labels[0]});
|
||||
};
|
||||
|
||||
// Validate the final state of the PerfLog.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -21,17 +22,17 @@ namespace perf {
|
||||
class PerfLogTest : public PerfLog
|
||||
{
|
||||
void
|
||||
rpcStart(std::string const& method, std::uint64_t requestId) override
|
||||
rpcStart(std::string_view method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcFinish(std::string const& method, std::uint64_t requestId) override
|
||||
rpcFinish(std::string_view method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcError(std::string const& method, std::uint64_t dur) override
|
||||
rpcError(std::string_view method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -316,13 +315,6 @@ auto const kData = JTxFieldWrapper<BlobField>(sfData);
|
||||
|
||||
auto const kAmount = JTxFieldWrapper<StAmountField>(sfAmount);
|
||||
|
||||
template <std::ranges::range Input>
|
||||
auto
|
||||
makeVector(Input const& 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);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -13,8 +12,8 @@
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
// cspell: words stdev
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -88,21 +87,41 @@ class Handler_test : public beast::unit_test::Suite
|
||||
std::random_device dev;
|
||||
std::ranlux48 prng(dev());
|
||||
|
||||
std::vector<char const*> names = test::jtx::makeVector(xrpl::rpc::getHandlerNames());
|
||||
// Contiguous, so the timed loop's pick-a-name-by-index costs nothing
|
||||
// and the measurement reflects getHandler() alone.
|
||||
auto const names = xrpl::rpc::getHandlerNames();
|
||||
|
||||
std::uniform_int_distribution<std::size_t> distr{0, names.size() - 1};
|
||||
|
||||
// The lowest version still served. Asking for one outside the supported
|
||||
// range would make getHandler() return at its bounds check, without
|
||||
// searching, and the benchmark would then be timing that check.
|
||||
constexpr unsigned kVersion = rpc::kApiMinimumSupportedVersion;
|
||||
|
||||
std::size_t dummy = 0;
|
||||
std::size_t misses = 0;
|
||||
auto const [mean, stdev, n] = time(
|
||||
1'000'000,
|
||||
[&](std::size_t i) {
|
||||
auto const d = rpc::getHandler(1, false, names[i]);
|
||||
auto const d = rpc::getHandler(kVersion, false, names[i]);
|
||||
if (d == nullptr)
|
||||
{
|
||||
++misses;
|
||||
return;
|
||||
}
|
||||
dummy = dummy + i + (int)d->role;
|
||||
},
|
||||
[&]() -> std::size_t { return distr(prng); });
|
||||
|
||||
std::cout << "mean=" << mean << " stdev=" << stdev << " N=" << n << '\n';
|
||||
|
||||
// A miss means the timed call did no lookup, so the figure above is not
|
||||
// a measurement of one. Every name comes from getHandlerNames(), so a
|
||||
// handler answering at kVersion is the only way this holds.
|
||||
BEAST_EXPECTS(
|
||||
misses == 0,
|
||||
std::to_string(misses) + " of " + std::to_string(n) + " lookups at API version " +
|
||||
std::to_string(kVersion) + " found no handler, so nothing was measured");
|
||||
BEAST_EXPECT(dummy != 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <test/jtx/utility.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/RPCCall.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
@@ -12,6 +15,8 @@
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
@@ -5923,10 +5928,38 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Every name in the command-line table must also name a server method, so
|
||||
// that a command accepted on the command line can actually be dispatched.
|
||||
//
|
||||
// The reverse does not hold: a server method needs no command-line form.
|
||||
// Three command-line names are also exempt because they are wrappers that
|
||||
// forward a caller-supplied method rather than naming one themselves.
|
||||
void
|
||||
testCommandLineNamesAreDispatchable()
|
||||
{
|
||||
testcase("Command-line methods are dispatchable");
|
||||
|
||||
static constexpr std::array kWrappers{
|
||||
rpc::method::kInternal, rpc::method::kJson, rpc::method::kJson2};
|
||||
|
||||
auto const dispatchable = rpc::getHandlerNames();
|
||||
BEAST_EXPECT(!dispatchable.empty());
|
||||
|
||||
for (auto const& name : commandLineMethodNames())
|
||||
{
|
||||
if (std::ranges::find(kWrappers, name) != kWrappers.end())
|
||||
continue;
|
||||
|
||||
// Both name lists are sorted, so a binary search suffices.
|
||||
BEAST_EXPECTS(std::ranges::binary_search(dispatchable, name), std::string{name});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
forAllApiVersions([this](unsigned apiVersion) { testRPCCall(apiVersion); });
|
||||
testCommandLineNamesAreDispatchable();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <xrpld/rpc/RPCHandler.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/ServerHandler.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
#include <xrpld/rpc/detail/PathRequestManager.h>
|
||||
#include <xrpld/rpc/detail/Pathfinder.h>
|
||||
#include <xrpld/shamap/NodeFamily.h>
|
||||
@@ -320,6 +321,7 @@ public:
|
||||
perf::makePerfLog(
|
||||
perf::setupPerfLog(config_->section(Sections::kPerf), config_->configDir),
|
||||
*this,
|
||||
rpc::getHandlerNames(),
|
||||
logs_->journal("PerfLog"),
|
||||
[this] { signalStop("PerfLog"); }))
|
||||
, txMaster_(*this)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
@@ -16,6 +17,7 @@
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -25,8 +27,9 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -34,14 +37,25 @@
|
||||
|
||||
namespace xrpl::perf {
|
||||
|
||||
PerfLogImp::Counters::Counters(std::set<char const*> const& labels, JobTypes const& jobTypes)
|
||||
PerfLogImp::Counters::Counters(std::span<std::string_view const> labels, JobTypes const& jobTypes)
|
||||
{
|
||||
{
|
||||
// populateRpc
|
||||
rpc.reserve(labels.size());
|
||||
for (std::string const label : labels)
|
||||
for (auto const& label : labels)
|
||||
{
|
||||
auto const inserted = rpc.emplace(label, Rpc()).second;
|
||||
// countersJson() reports these through json::StaticString, which
|
||||
// reads them as C strings, so each must be a view of a whole string
|
||||
// literal rather than a slice of one. Checked here, where the names
|
||||
// arrive, rather than on every report. Callers pass a compile-time
|
||||
// constant list and this runs once, before any thread starts, so a
|
||||
// bad name faults every startup rather than some later request.
|
||||
XRPL_ASSERT(
|
||||
isNullTerminated(label),
|
||||
"xrpl::perf::PerfLogImp::Counters::Counters : label is "
|
||||
"null-terminated");
|
||||
|
||||
auto const inserted = rpc.try_emplace(label).second;
|
||||
if (!inserted)
|
||||
{
|
||||
// Ensure that no other function populates this entry.
|
||||
@@ -100,7 +114,11 @@ PerfLogImp::Counters::countersJson() const
|
||||
totalRpc.errored += value.errored;
|
||||
p[jss::duration_us] = std::to_string(value.duration.count());
|
||||
totalRpc.duration += value.duration;
|
||||
rpcobj[proc.first] = p;
|
||||
// The key outlives the program and, per the constructor's assert, is
|
||||
// null-terminated, so it can be borrowed as a C string rather than
|
||||
// duplicated into the object.
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
|
||||
rpcobj[json::StaticString{proc.first.data()}] = p;
|
||||
}
|
||||
|
||||
if (totalRpc.started != 0u)
|
||||
@@ -195,7 +213,9 @@ PerfLogImp::Counters::currentJson() const
|
||||
for (auto m : methods)
|
||||
{
|
||||
json::Value methodobj(json::ValueType::Object);
|
||||
methodobj[jss::method] = m.first;
|
||||
// Borrowed as a C string, as in countersJson() above.
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
|
||||
methodobj[jss::method] = json::StaticString{m.first.data()};
|
||||
methodobj[jss::duration_us] =
|
||||
std::to_string(std::chrono::duration_cast<microseconds>(present - m.second).count());
|
||||
methodsArray.append(methodobj);
|
||||
@@ -299,9 +319,14 @@ PerfLogImp::report()
|
||||
PerfLogImp::PerfLogImp(
|
||||
Setup setup,
|
||||
Application& app,
|
||||
std::span<std::string_view const> methodNames,
|
||||
beast::Journal journal,
|
||||
std::function<void()>&& signalStop)
|
||||
: setup_(std::move(setup)), app_(app), j_(journal), signalStop_(std::move(signalStop))
|
||||
: setup_(std::move(setup))
|
||||
, app_(app)
|
||||
, j_(journal)
|
||||
, signalStop_(std::move(signalStop))
|
||||
, counters_(methodNames, JobTypes::instance())
|
||||
{
|
||||
openLog();
|
||||
}
|
||||
@@ -312,7 +337,7 @@ PerfLogImp::~PerfLogImp()
|
||||
}
|
||||
|
||||
void
|
||||
PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
|
||||
PerfLogImp::rpcStart(std::string_view method, std::uint64_t const requestId)
|
||||
{
|
||||
auto counter = counters_.rpc.find(method);
|
||||
if (counter == counters_.rpc.end())
|
||||
@@ -328,11 +353,11 @@ PerfLogImp::rpcStart(std::string const& method, std::uint64_t const requestId)
|
||||
++counter->second.value.started;
|
||||
}
|
||||
std::scoped_lock const lock(counters_.methodsMutex);
|
||||
counters_.methods[requestId] = {counter->first.c_str(), steady_clock::now()};
|
||||
counters_.methods[requestId] = {counter->first, steady_clock::now()};
|
||||
}
|
||||
|
||||
void
|
||||
PerfLogImp::rpcEnd(std::string const& method, std::uint64_t const requestId, bool finish)
|
||||
PerfLogImp::rpcEnd(std::string_view method, std::uint64_t const requestId, bool finish)
|
||||
{
|
||||
auto counter = counters_.rpc.find(method);
|
||||
if (counter == counters_.rpc.end())
|
||||
@@ -501,10 +526,11 @@ std::unique_ptr<PerfLog>
|
||||
makePerfLog(
|
||||
PerfLog::Setup const& setup,
|
||||
Application& app,
|
||||
std::span<std::string_view const> methodNames,
|
||||
beast::Journal journal,
|
||||
std::function<void()>&& signalStop)
|
||||
{
|
||||
return std::make_unique<PerfLogImp>(setup, app, journal, std::move(signalStop));
|
||||
return std::make_unique<PerfLogImp>(setup, app, methodNames, journal, std::move(signalStop));
|
||||
}
|
||||
|
||||
} // namespace xrpl::perf
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/core/JobTypes.h>
|
||||
@@ -15,8 +13,9 @@
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -59,7 +58,8 @@ class PerfLogImp : public PerfLog
|
||||
struct Counters
|
||||
{
|
||||
public:
|
||||
using MethodStart = std::pair<char const*, steady_time_point>;
|
||||
using MethodStart = std::pair<std::string_view, steady_time_point>;
|
||||
|
||||
/**
|
||||
* RPC performance counters.
|
||||
*/
|
||||
@@ -91,14 +91,18 @@ class PerfLogImp : public PerfLog
|
||||
|
||||
// rpc and jq do not need mutex protection because all
|
||||
// keys and values are created before more threads are started.
|
||||
std::unordered_map<std::string, Locked<Rpc>> rpc;
|
||||
//
|
||||
// Every key is a view of a method name constant, which outlives the
|
||||
// program, so the map neither copies a name to store it nor needs one
|
||||
// materialized to look it up.
|
||||
std::unordered_map<std::string_view, Locked<Rpc>> rpc;
|
||||
std::unordered_map<JobType, Locked<Jq>> jq;
|
||||
std::vector<std::pair<JobType, steady_time_point>> jobs;
|
||||
mutable std::mutex jobsMutex;
|
||||
std::unordered_map<std::uint64_t, MethodStart> methods;
|
||||
mutable std::mutex methodsMutex;
|
||||
|
||||
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes);
|
||||
Counters(std::span<std::string_view const> labels, JobTypes const& jobTypes);
|
||||
json::Value
|
||||
countersJson() const;
|
||||
json::Value
|
||||
@@ -109,7 +113,7 @@ class PerfLogImp : public PerfLog
|
||||
Application& app_;
|
||||
beast::Journal const j_;
|
||||
std::function<void()> const signalStop_;
|
||||
Counters counters_{xrpl::rpc::getHandlerNames(), JobTypes::instance()};
|
||||
Counters counters_;
|
||||
std::ofstream logFile_;
|
||||
std::thread thread_;
|
||||
std::mutex mutex_;
|
||||
@@ -126,28 +130,35 @@ class PerfLogImp : public PerfLog
|
||||
void
|
||||
report();
|
||||
void
|
||||
rpcEnd(std::string const& method, std::uint64_t const requestId, bool finish);
|
||||
rpcEnd(std::string_view method, std::uint64_t const requestId, bool finish);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param methodNames The RPC methods to count, one counter per name. The
|
||||
* names must outlive this object, which holds views of them. Passed
|
||||
* in rather than looked up here so that this layer needs to know
|
||||
* nothing about the RPC dispatch table.
|
||||
*/
|
||||
PerfLogImp(
|
||||
Setup setup,
|
||||
Application& app,
|
||||
std::span<std::string_view const> methodNames,
|
||||
beast::Journal journal,
|
||||
std::function<void()>&& signalStop);
|
||||
|
||||
~PerfLogImp() override;
|
||||
|
||||
void
|
||||
rpcStart(std::string const& method, std::uint64_t const requestId) override;
|
||||
rpcStart(std::string_view method, std::uint64_t const requestId) override;
|
||||
|
||||
void
|
||||
rpcFinish(std::string const& method, std::uint64_t const requestId) override
|
||||
rpcFinish(std::string_view method, std::uint64_t const requestId) override
|
||||
{
|
||||
rpcEnd(method, requestId, true);
|
||||
}
|
||||
|
||||
void
|
||||
rpcError(std::string const& method, std::uint64_t const requestId) override
|
||||
rpcError(std::string_view method, std::uint64_t const requestId) override
|
||||
{
|
||||
rpcEnd(method, requestId, false);
|
||||
}
|
||||
|
||||
93
src/xrpld/rpc/MethodNames.h
Normal file
93
src/xrpld/rpc/MethodNames.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc::method {
|
||||
|
||||
/**
|
||||
* Names of the RPC methods the server accepts.
|
||||
*
|
||||
* Defined here so the dispatch table in Handler.cpp and the command-line parser
|
||||
* table in RPCCall.cpp name each method through the same constant, and cannot
|
||||
* drift apart.
|
||||
*/
|
||||
|
||||
inline constexpr std::string_view kAccountChannels{"account_channels"};
|
||||
inline constexpr std::string_view kAccountCurrencies{"account_currencies"};
|
||||
inline constexpr std::string_view kAccountInfo{"account_info"};
|
||||
inline constexpr std::string_view kAccountLines{"account_lines"};
|
||||
inline constexpr std::string_view kAccountNfts{"account_nfts"};
|
||||
inline constexpr std::string_view kAccountObjects{"account_objects"};
|
||||
inline constexpr std::string_view kAccountOffers{"account_offers"};
|
||||
inline constexpr std::string_view kAccountTx{"account_tx"};
|
||||
inline constexpr std::string_view kAmmInfo{"amm_info"};
|
||||
inline constexpr std::string_view kBlacklist{"blacklist"}; // no command-line form
|
||||
inline constexpr std::string_view kBookChanges{"book_changes"};
|
||||
inline constexpr std::string_view kBookOffers{"book_offers"};
|
||||
inline constexpr std::string_view kCanDelete{"can_delete"};
|
||||
inline constexpr std::string_view kChannelAuthorize{"channel_authorize"};
|
||||
inline constexpr std::string_view kChannelVerify{"channel_verify"};
|
||||
inline constexpr std::string_view kConnect{"connect"};
|
||||
inline constexpr std::string_view kConsensusInfo{"consensus_info"};
|
||||
inline constexpr std::string_view kDepositAuthorized{"deposit_authorized"};
|
||||
inline constexpr std::string_view kFeature{"feature"};
|
||||
inline constexpr std::string_view kFee{"fee"}; // no command-line form
|
||||
inline constexpr std::string_view kFetchInfo{"fetch_info"};
|
||||
inline constexpr std::string_view kGatewayBalances{"gateway_balances"};
|
||||
inline constexpr std::string_view kGetAggregatePrice{
|
||||
"get_aggregate_price"}; // no command-line form
|
||||
inline constexpr std::string_view kGetCounts{"get_counts"};
|
||||
inline constexpr std::string_view kInternal{"internal"}; // command-line wrapper
|
||||
inline constexpr std::string_view kJson{"json"}; // command-line wrapper
|
||||
inline constexpr std::string_view kJson2{"json2"}; // command-line wrapper
|
||||
inline constexpr std::string_view kLedger{"ledger"};
|
||||
inline constexpr std::string_view kLedgerAccept{"ledger_accept"};
|
||||
inline constexpr std::string_view kLedgerCleaner{"ledger_cleaner"}; // no command-line form
|
||||
inline constexpr std::string_view kLedgerClosed{"ledger_closed"};
|
||||
inline constexpr std::string_view kLedgerCurrent{"ledger_current"};
|
||||
inline constexpr std::string_view kLedgerData{"ledger_data"}; // no command-line form
|
||||
inline constexpr std::string_view kLedgerEntry{"ledger_entry"};
|
||||
inline constexpr std::string_view kLedgerHeader{"ledger_header"};
|
||||
inline constexpr std::string_view kLedgerRequest{"ledger_request"};
|
||||
inline constexpr std::string_view kLogLevel{"log_level"};
|
||||
inline constexpr std::string_view kLogrotate{"logrotate"};
|
||||
inline constexpr std::string_view kManifest{"manifest"};
|
||||
inline constexpr std::string_view kNftBuyOffers{"nft_buy_offers"}; // no command-line form
|
||||
inline constexpr std::string_view kNftSellOffers{"nft_sell_offers"}; // no command-line form
|
||||
inline constexpr std::string_view kNorippleCheck{"noripple_check"}; // no command-line form
|
||||
inline constexpr std::string_view kOwnerInfo{"owner_info"};
|
||||
inline constexpr std::string_view kPathFind{"path_find"};
|
||||
inline constexpr std::string_view kPeerReservationsAdd{"peer_reservations_add"};
|
||||
inline constexpr std::string_view kPeerReservationsDel{"peer_reservations_del"};
|
||||
inline constexpr std::string_view kPeerReservationsList{"peer_reservations_list"};
|
||||
inline constexpr std::string_view kPeers{"peers"};
|
||||
inline constexpr std::string_view kPing{"ping"};
|
||||
inline constexpr std::string_view kPrint{"print"};
|
||||
inline constexpr std::string_view kRandom{"random"};
|
||||
inline constexpr std::string_view kRipplePathFind{"ripple_path_find"};
|
||||
inline constexpr std::string_view kServerDefinitions{"server_definitions"};
|
||||
inline constexpr std::string_view kServerInfo{"server_info"};
|
||||
inline constexpr std::string_view kServerState{"server_state"};
|
||||
inline constexpr std::string_view kSign{"sign"};
|
||||
inline constexpr std::string_view kSignFor{"sign_for"};
|
||||
inline constexpr std::string_view kSimulate{"simulate"};
|
||||
inline constexpr std::string_view kStop{"stop"};
|
||||
inline constexpr std::string_view kSubmit{"submit"};
|
||||
inline constexpr std::string_view kSubmitMultisigned{"submit_multisigned"};
|
||||
inline constexpr std::string_view kSubscribe{"subscribe"};
|
||||
inline constexpr std::string_view kTransactionEntry{"transaction_entry"};
|
||||
inline constexpr std::string_view kTx{"tx"};
|
||||
inline constexpr std::string_view kTxHistory{"tx_history"};
|
||||
inline constexpr std::string_view kTxReduceRelay{"tx_reduce_relay"}; // no command-line form
|
||||
inline constexpr std::string_view kUnlList{"unl_list"};
|
||||
inline constexpr std::string_view kUnsubscribe{"unsubscribe"};
|
||||
inline constexpr std::string_view kValidationCreate{"validation_create"};
|
||||
inline constexpr std::string_view kValidatorInfo{"validator_info"};
|
||||
inline constexpr std::string_view kValidatorListSites{
|
||||
"validator_list_sites"}; // no command-line form
|
||||
inline constexpr std::string_view kValidators{"validators"}; // no command-line form
|
||||
inline constexpr std::string_view kVaultInfo{"vault_info"};
|
||||
inline constexpr std::string_view kVersion{"version"};
|
||||
inline constexpr std::string_view kWalletPropose{"wallet_propose"};
|
||||
|
||||
} // namespace xrpl::rpc::method
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -56,6 +58,15 @@ rpcCmdToJson(
|
||||
unsigned int apiVersion,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* Return the names of all methods accepted on the command line.
|
||||
*
|
||||
* The names view refers to storage that outlives the program, so it is safe to
|
||||
* hold on to.
|
||||
*/
|
||||
std::span<std::string_view const>
|
||||
commandLineMethodNames();
|
||||
|
||||
/**
|
||||
* Internal invocation of RPC client.
|
||||
* Used by both xrpld command line as well as xrpld unit tests
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
@@ -19,6 +19,6 @@ Status
|
||||
doCommand(rpc::JsonContext&, json::Value&);
|
||||
|
||||
Role
|
||||
roleRequired(unsigned int version, bool betaEnabled, std::string const& method);
|
||||
roleRequired(unsigned int version, bool betaEnabled, std::string_view method);
|
||||
|
||||
} // namespace xrpl::rpc
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
#include <xrpld/rpc/handlers/ledger/Ledger.h>
|
||||
#include <xrpld/rpc/handlers/server_info/Version.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Adjust an old-style handler to be call-by-reference.
|
||||
*
|
||||
* The handler is a template parameter rather than an argument, so that byRef
|
||||
* names a plain function instead of returning a closure over it.
|
||||
*/
|
||||
template <typename Function>
|
||||
Handler::Method<json::Value>
|
||||
byRef(Function const& f)
|
||||
template <json::Value (*Function)(JsonContext&)>
|
||||
Status
|
||||
byRef(JsonContext& context, json::Value& result)
|
||||
{
|
||||
return [f](JsonContext& context, json::Value& result) {
|
||||
result = f(context);
|
||||
if (result.type() != json::ValueType::Object)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::rpc::byRef : result is object");
|
||||
result = rpc::makeObjectValue(result);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
result = Function(context);
|
||||
if (result.type() != json::ValueType::Object)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::rpc::byRef : result is object");
|
||||
result = rpc::makeObjectValue(result);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return Status();
|
||||
};
|
||||
return Status();
|
||||
}
|
||||
|
||||
template <class Object, class HandlerImpl>
|
||||
template <class HandlerImpl>
|
||||
Status
|
||||
handle(JsonContext& context, Object& object)
|
||||
handle(JsonContext& context, json::Value& object)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
context.apiVersion >= HandlerImpl::minApiVer &&
|
||||
@@ -65,427 +68,399 @@ handle(JsonContext& context, Object& object)
|
||||
}
|
||||
|
||||
template <typename HandlerImpl>
|
||||
Handler
|
||||
constexpr Handler
|
||||
handlerFrom()
|
||||
{
|
||||
static_assert(HandlerImpl::minApiVer <= HandlerImpl::maxApiVer);
|
||||
static_assert(HandlerImpl::maxApiVer <= rpc::kApiMaximumValidVersion);
|
||||
static_assert(rpc::kApiMinimumSupportedVersion <= HandlerImpl::minApiVer);
|
||||
|
||||
return {
|
||||
HandlerImpl::name,
|
||||
&handle<json::Value, HandlerImpl>,
|
||||
&handle<HandlerImpl>,
|
||||
HandlerImpl::role,
|
||||
HandlerImpl::condition,
|
||||
HandlerImpl::minApiVer,
|
||||
HandlerImpl::maxApiVer};
|
||||
}
|
||||
|
||||
Handler const kHandlerArray[]{
|
||||
// Some handlers not specified here are added to the table via addHandler()
|
||||
// The handlers, in whatever order reads best. getHandler() searches kHandlers
|
||||
// below, which is this array sorted; the order here carries no meaning.
|
||||
constexpr Handler kHandlerArray[]{
|
||||
// Request-response methods
|
||||
{.name = "account_info",
|
||||
.valueMethod = byRef(&doAccountInfo),
|
||||
{.name = method::kAccountInfo,
|
||||
.valueMethod = &byRef<&doAccountInfo>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_currencies",
|
||||
.valueMethod = byRef(&doAccountCurrencies),
|
||||
{.name = method::kAccountCurrencies,
|
||||
.valueMethod = &byRef<&doAccountCurrencies>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_lines",
|
||||
.valueMethod = byRef(&doAccountLines),
|
||||
{.name = method::kAccountLines,
|
||||
.valueMethod = &byRef<&doAccountLines>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_channels",
|
||||
.valueMethod = byRef(&doAccountChannels),
|
||||
{.name = method::kAccountChannels,
|
||||
.valueMethod = &byRef<&doAccountChannels>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_nfts",
|
||||
.valueMethod = byRef(&doAccountNFTs),
|
||||
{.name = method::kAccountNfts,
|
||||
.valueMethod = &byRef<&doAccountNFTs>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_objects",
|
||||
.valueMethod = byRef(&doAccountObjects),
|
||||
{.name = method::kAccountObjects,
|
||||
.valueMethod = &byRef<&doAccountObjects>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_offers",
|
||||
.valueMethod = byRef(&doAccountOffers),
|
||||
{.name = method::kAccountOffers,
|
||||
.valueMethod = &byRef<&doAccountOffers>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "account_tx",
|
||||
.valueMethod = byRef(&doAccountTx),
|
||||
{.name = method::kAccountTx,
|
||||
.valueMethod = &byRef<&doAccountTx>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "amm_info",
|
||||
.valueMethod = byRef(&doAMMInfo),
|
||||
{.name = method::kAmmInfo,
|
||||
.valueMethod = &byRef<&doAMMInfo>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "blacklist",
|
||||
.valueMethod = byRef(&doBlackList),
|
||||
{.name = method::kBlacklist,
|
||||
.valueMethod = &byRef<&doBlackList>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "book_changes",
|
||||
.valueMethod = byRef(&doBookChanges),
|
||||
{.name = method::kBookChanges,
|
||||
.valueMethod = &byRef<&doBookChanges>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "book_offers",
|
||||
.valueMethod = byRef(&doBookOffers),
|
||||
{.name = method::kBookOffers,
|
||||
.valueMethod = &byRef<&doBookOffers>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "can_delete",
|
||||
.valueMethod = byRef(&doCanDelete),
|
||||
{.name = method::kCanDelete,
|
||||
.valueMethod = &byRef<&doCanDelete>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "channel_authorize",
|
||||
.valueMethod = byRef(&doChannelAuthorize),
|
||||
{.name = method::kChannelAuthorize,
|
||||
.valueMethod = &byRef<&doChannelAuthorize>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "channel_verify",
|
||||
.valueMethod = byRef(&doChannelVerify),
|
||||
{.name = method::kChannelVerify,
|
||||
.valueMethod = &byRef<&doChannelVerify>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "connect",
|
||||
.valueMethod = byRef(&doConnect),
|
||||
{.name = method::kConnect,
|
||||
.valueMethod = &byRef<&doConnect>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "consensus_info",
|
||||
.valueMethod = byRef(&doConsensusInfo),
|
||||
{.name = method::kConsensusInfo,
|
||||
.valueMethod = &byRef<&doConsensusInfo>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "deposit_authorized",
|
||||
.valueMethod = byRef(&doDepositAuthorized),
|
||||
{.name = method::kDepositAuthorized,
|
||||
.valueMethod = &byRef<&doDepositAuthorized>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "feature",
|
||||
.valueMethod = byRef(&doFeature),
|
||||
{.name = method::kFeature,
|
||||
.valueMethod = &byRef<&doFeature>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "fee",
|
||||
.valueMethod = byRef(&doFee),
|
||||
{.name = method::kFee,
|
||||
.valueMethod = &byRef<&doFee>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "fetch_info",
|
||||
.valueMethod = byRef(&doFetchInfo),
|
||||
{.name = method::kFetchInfo,
|
||||
.valueMethod = &byRef<&doFetchInfo>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "gateway_balances",
|
||||
.valueMethod = byRef(&doGatewayBalances),
|
||||
{.name = method::kGatewayBalances,
|
||||
.valueMethod = &byRef<&doGatewayBalances>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "get_counts",
|
||||
.valueMethod = byRef(&doGetCounts),
|
||||
{.name = method::kGetCounts,
|
||||
.valueMethod = &byRef<&doGetCounts>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "get_aggregate_price",
|
||||
.valueMethod = byRef(&doGetAggregatePrice),
|
||||
{.name = method::kGetAggregatePrice,
|
||||
.valueMethod = &byRef<&doGetAggregatePrice>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "ledger_accept",
|
||||
.valueMethod = byRef(&doLedgerAccept),
|
||||
{.name = method::kLedgerAccept,
|
||||
.valueMethod = &byRef<&doLedgerAccept>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "ledger_cleaner",
|
||||
.valueMethod = byRef(&doLedgerCleaner),
|
||||
{.name = method::kLedgerCleaner,
|
||||
.valueMethod = &byRef<&doLedgerCleaner>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NeedsNetworkConnection},
|
||||
{.name = "ledger_closed",
|
||||
.valueMethod = byRef(&doLedgerClosed),
|
||||
{.name = method::kLedgerClosed,
|
||||
.valueMethod = &byRef<&doLedgerClosed>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsClosedLedger},
|
||||
{.name = "ledger_current",
|
||||
.valueMethod = byRef(&doLedgerCurrent),
|
||||
{.name = method::kLedgerCurrent,
|
||||
.valueMethod = &byRef<&doLedgerCurrent>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "ledger_data",
|
||||
.valueMethod = byRef(&doLedgerData),
|
||||
{.name = method::kLedgerData,
|
||||
.valueMethod = &byRef<&doLedgerData>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "ledger_entry",
|
||||
.valueMethod = byRef(&doLedgerEntry),
|
||||
{.name = method::kLedgerEntry,
|
||||
.valueMethod = &byRef<&doLedgerEntry>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "ledger_header",
|
||||
.valueMethod = byRef(&doLedgerHeader),
|
||||
{.name = method::kLedgerHeader,
|
||||
.valueMethod = &byRef<&doLedgerHeader>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition,
|
||||
.minApiVer = 1,
|
||||
.maxApiVer = 1},
|
||||
{.name = "ledger_request",
|
||||
.valueMethod = byRef(&doLedgerRequest),
|
||||
{.name = method::kLedgerRequest,
|
||||
.valueMethod = &byRef<&doLedgerRequest>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "log_level",
|
||||
.valueMethod = byRef(&doLogLevel),
|
||||
{.name = method::kLogLevel,
|
||||
.valueMethod = &byRef<&doLogLevel>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "logrotate",
|
||||
.valueMethod = byRef(&doLogRotate),
|
||||
{.name = method::kLogrotate,
|
||||
.valueMethod = &byRef<&doLogRotate>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "manifest",
|
||||
.valueMethod = byRef(&doManifest),
|
||||
{.name = method::kManifest,
|
||||
.valueMethod = &byRef<&doManifest>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "nft_buy_offers",
|
||||
.valueMethod = byRef(&doNFTBuyOffers),
|
||||
{.name = method::kNftBuyOffers,
|
||||
.valueMethod = &byRef<&doNFTBuyOffers>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "nft_sell_offers",
|
||||
.valueMethod = byRef(&doNFTSellOffers),
|
||||
{.name = method::kNftSellOffers,
|
||||
.valueMethod = &byRef<&doNFTSellOffers>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "noripple_check",
|
||||
.valueMethod = byRef(&doNoRippleCheck),
|
||||
{.name = method::kNorippleCheck,
|
||||
.valueMethod = &byRef<&doNoRippleCheck>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "owner_info",
|
||||
.valueMethod = byRef(&doOwnerInfo),
|
||||
{.name = method::kOwnerInfo,
|
||||
.valueMethod = &byRef<&doOwnerInfo>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "peers",
|
||||
.valueMethod = byRef(&doPeers),
|
||||
{.name = method::kPeers,
|
||||
.valueMethod = &byRef<&doPeers>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "path_find",
|
||||
.valueMethod = byRef(&doPathFind),
|
||||
{.name = method::kPathFind,
|
||||
.valueMethod = &byRef<&doPathFind>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "ping",
|
||||
.valueMethod = byRef(&doPing),
|
||||
{.name = method::kPing,
|
||||
.valueMethod = &byRef<&doPing>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "print",
|
||||
.valueMethod = byRef(&doPrint),
|
||||
{.name = method::kPrint,
|
||||
.valueMethod = &byRef<&doPrint>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
// { "profile", byRef (&doProfile), Role::USER,
|
||||
// NEEDS_CURRENT_LEDGER },
|
||||
{.name = "random",
|
||||
.valueMethod = byRef(&doRandom),
|
||||
{.name = method::kRandom,
|
||||
.valueMethod = &byRef<&doRandom>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "peer_reservations_add",
|
||||
.valueMethod = byRef(&doPeerReservationsAdd),
|
||||
{.name = method::kPeerReservationsAdd,
|
||||
.valueMethod = &byRef<&doPeerReservationsAdd>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "peer_reservations_del",
|
||||
.valueMethod = byRef(&doPeerReservationsDel),
|
||||
{.name = method::kPeerReservationsDel,
|
||||
.valueMethod = &byRef<&doPeerReservationsDel>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "peer_reservations_list",
|
||||
.valueMethod = byRef(&doPeerReservationsList),
|
||||
{.name = method::kPeerReservationsList,
|
||||
.valueMethod = &byRef<&doPeerReservationsList>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "ripple_path_find",
|
||||
.valueMethod = byRef(&doRipplePathFind),
|
||||
{.name = method::kRipplePathFind,
|
||||
.valueMethod = &byRef<&doRipplePathFind>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "server_definitions",
|
||||
.valueMethod = byRef(&doServerDefinitions),
|
||||
{.name = method::kServerDefinitions,
|
||||
.valueMethod = &byRef<&doServerDefinitions>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "server_info",
|
||||
.valueMethod = byRef(&doServerInfo),
|
||||
{.name = method::kServerInfo,
|
||||
.valueMethod = &byRef<&doServerInfo>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "server_state",
|
||||
.valueMethod = byRef(&doServerState),
|
||||
{.name = method::kServerState,
|
||||
.valueMethod = &byRef<&doServerState>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "sign",
|
||||
.valueMethod = byRef(&doSign),
|
||||
{.name = method::kSign,
|
||||
.valueMethod = &byRef<&doSign>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "sign_for",
|
||||
.valueMethod = byRef(&doSignFor),
|
||||
{.name = method::kSignFor,
|
||||
.valueMethod = &byRef<&doSignFor>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "simulate",
|
||||
.valueMethod = byRef(&doSimulate),
|
||||
{.name = method::kSimulate,
|
||||
.valueMethod = &byRef<&doSimulate>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "stop",
|
||||
.valueMethod = byRef(&doStop),
|
||||
{.name = method::kStop,
|
||||
.valueMethod = &byRef<&doStop>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "submit",
|
||||
.valueMethod = byRef(&doSubmit),
|
||||
{.name = method::kSubmit,
|
||||
.valueMethod = &byRef<&doSubmit>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "submit_multisigned",
|
||||
.valueMethod = byRef(&doSubmitMultiSigned),
|
||||
{.name = method::kSubmitMultisigned,
|
||||
.valueMethod = &byRef<&doSubmitMultiSigned>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsCurrentLedger},
|
||||
{.name = "transaction_entry",
|
||||
.valueMethod = byRef(&doTransactionEntry),
|
||||
{.name = method::kTransactionEntry,
|
||||
.valueMethod = &byRef<&doTransactionEntry>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "tx",
|
||||
.valueMethod = byRef(&doTxJson),
|
||||
{.name = method::kTx,
|
||||
.valueMethod = &byRef<&doTxJson>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NeedsNetworkConnection},
|
||||
{.name = "tx_history",
|
||||
.valueMethod = byRef(&doTxHistory),
|
||||
{.name = method::kTxHistory,
|
||||
.valueMethod = &byRef<&doTxHistory>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition,
|
||||
.minApiVer = 1,
|
||||
.maxApiVer = 1},
|
||||
{.name = "tx_reduce_relay",
|
||||
.valueMethod = byRef(&doTxReduceRelay),
|
||||
{.name = method::kTxReduceRelay,
|
||||
.valueMethod = &byRef<&doTxReduceRelay>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "unl_list",
|
||||
.valueMethod = byRef(&doUnlList),
|
||||
{.name = method::kUnlList,
|
||||
.valueMethod = &byRef<&doUnlList>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "validation_create",
|
||||
.valueMethod = byRef(&doValidationCreate),
|
||||
{.name = method::kValidationCreate,
|
||||
.valueMethod = &byRef<&doValidationCreate>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "validators",
|
||||
.valueMethod = byRef(&doValidators),
|
||||
{.name = method::kValidators,
|
||||
.valueMethod = &byRef<&doValidators>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "validator_list_sites",
|
||||
.valueMethod = byRef(&doValidatorListSites),
|
||||
{.name = method::kValidatorListSites,
|
||||
.valueMethod = &byRef<&doValidatorListSites>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "validator_info",
|
||||
.valueMethod = byRef(&doValidatorInfo),
|
||||
{.name = method::kValidatorInfo,
|
||||
.valueMethod = &byRef<&doValidatorInfo>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "vault_info",
|
||||
.valueMethod = byRef(&doVaultInfo),
|
||||
{.name = method::kVaultInfo,
|
||||
.valueMethod = &byRef<&doVaultInfo>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "wallet_propose",
|
||||
.valueMethod = byRef(&doWalletPropose),
|
||||
{.name = method::kWalletPropose,
|
||||
.valueMethod = &byRef<&doWalletPropose>,
|
||||
.role = Role::ADMIN,
|
||||
.condition = Condition::NoCondition},
|
||||
// Event methods
|
||||
{.name = "subscribe",
|
||||
.valueMethod = byRef(&doSubscribe),
|
||||
{.name = method::kSubscribe,
|
||||
.valueMethod = &byRef<&doSubscribe>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
{.name = "unsubscribe",
|
||||
.valueMethod = byRef(&doUnsubscribe),
|
||||
{.name = method::kUnsubscribe,
|
||||
.valueMethod = &byRef<&doUnsubscribe>,
|
||||
.role = Role::USER,
|
||||
.condition = Condition::NoCondition},
|
||||
};
|
||||
|
||||
class HandlerTable
|
||||
{
|
||||
private:
|
||||
using handler_table_t = std::multimap<std::string, Handler>;
|
||||
|
||||
// Use with equal_range to enforce that API range of a newly added handler
|
||||
// does not overlap with API range of an existing handler with same name
|
||||
[[nodiscard]] static bool
|
||||
overlappingApiVersion(
|
||||
std::pair<handler_table_t::iterator, handler_table_t::iterator> range,
|
||||
unsigned minVer,
|
||||
unsigned maxVer)
|
||||
{
|
||||
XRPL_ASSERT(minVer <= maxVer, "xrpl::rpc::HandlerTable : valid API version range");
|
||||
XRPL_ASSERT(
|
||||
maxVer <= rpc::kApiMaximumValidVersion,
|
||||
"xrpl::rpc::HandlerTable : valid max API version");
|
||||
|
||||
return std::any_of(
|
||||
range.first,
|
||||
range.second, //
|
||||
[minVer, maxVer](auto const& item) {
|
||||
return item.second.minApiVer <= maxVer && item.second.maxApiVer >= minVer;
|
||||
});
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
explicit HandlerTable(Handler const (&entries)[N])
|
||||
{
|
||||
for (auto const& entry : entries)
|
||||
{
|
||||
if (overlappingApiVersion(
|
||||
table_.equal_range(entry.name), entry.minApiVer, entry.maxApiVer))
|
||||
{
|
||||
logicError(
|
||||
std::string("Handler for ") + entry.name +
|
||||
" overlaps with an existing handler");
|
||||
}
|
||||
|
||||
table_.insert({entry.name, entry});
|
||||
}
|
||||
|
||||
// This is where the new-style handlers are added.
|
||||
addHandler<LedgerHandler>();
|
||||
addHandler<VersionHandler>();
|
||||
}
|
||||
|
||||
public:
|
||||
static HandlerTable const&
|
||||
instance()
|
||||
{
|
||||
static HandlerTable const kHandlerTable(kHandlerArray);
|
||||
return kHandlerTable;
|
||||
}
|
||||
|
||||
[[nodiscard]] Handler const*
|
||||
getHandler(unsigned version, bool betaEnabled, std::string const& name) const
|
||||
{
|
||||
if (version < rpc::kApiMinimumSupportedVersion ||
|
||||
version > (betaEnabled ? rpc::kApiBetaVersion : rpc::kApiMaximumSupportedVersion))
|
||||
return nullptr;
|
||||
|
||||
auto const range = table_.equal_range(name);
|
||||
auto const i = std::find_if(range.first, range.second, [version](auto const& entry) {
|
||||
return entry.second.minApiVer <= version && version <= entry.second.maxApiVer;
|
||||
});
|
||||
|
||||
return i == range.second ? nullptr : &i->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::set<char const*>
|
||||
getHandlerNames() const
|
||||
{
|
||||
std::set<char const*> ret;
|
||||
for (auto const& i : table_)
|
||||
ret.insert(i.second.name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
handler_table_t table_;
|
||||
|
||||
template <class HandlerImpl>
|
||||
void
|
||||
addHandler()
|
||||
{
|
||||
static_assert(HandlerImpl::minApiVer <= HandlerImpl::maxApiVer);
|
||||
static_assert(HandlerImpl::maxApiVer <= rpc::kApiMaximumValidVersion);
|
||||
static_assert(rpc::kApiMinimumSupportedVersion <= HandlerImpl::minApiVer);
|
||||
|
||||
if (overlappingApiVersion(
|
||||
table_.equal_range(HandlerImpl::name),
|
||||
HandlerImpl::minApiVer,
|
||||
HandlerImpl::maxApiVer))
|
||||
{
|
||||
logicError(
|
||||
std::string("Handler for ") + HandlerImpl::name +
|
||||
" overlaps with an existing handler");
|
||||
}
|
||||
|
||||
table_.insert({HandlerImpl::name, handlerFrom<HandlerImpl>()});
|
||||
}
|
||||
// The class-based handlers, which carry their name and API range as static
|
||||
// members rather than as a table entry, so they cannot go in the array above.
|
||||
constexpr Handler kClassHandlerArray[]{
|
||||
handlerFrom<LedgerHandler>(),
|
||||
handlerFrom<VersionHandler>(),
|
||||
};
|
||||
|
||||
// The whole dispatch table: the two arrays above, concatenated. Their sizes are
|
||||
// taken from the arrays so that adding a handler to either needs no change here.
|
||||
constexpr auto kHandlers = [] {
|
||||
std::array<Handler, std::size(kHandlerArray) + std::size(kClassHandlerArray)> all{};
|
||||
auto const out = std::ranges::copy(kHandlerArray, all.begin()).out;
|
||||
std::ranges::copy(kClassHandlerArray, out);
|
||||
|
||||
// Sorted by name, so a handler can be found by binary search.
|
||||
std::ranges::sort(all, {}, &Handler::name);
|
||||
return all;
|
||||
}();
|
||||
|
||||
// getHandler() relies on this being sorted to binary search it, and
|
||||
// kHandlerNames below inherits the order.
|
||||
static_assert(
|
||||
std::ranges::is_sorted(kHandlers, {}, &Handler::name),
|
||||
"xrpl::rpc : kHandlers must be sorted by name");
|
||||
|
||||
// A name must select exactly one handler, otherwise a request would have two
|
||||
// answers. Where a method's behaviour differs by API version, the handler
|
||||
// branches on context.apiVersion rather than being registered once per range.
|
||||
// Checked here, at compile time, rather than on the first dispatch.
|
||||
static_assert(
|
||||
[]() {
|
||||
for (std::size_t i = 0; i < kHandlers.size(); ++i)
|
||||
{
|
||||
auto const& h = kHandlers[i];
|
||||
if (h.name.empty() || h.valueMethod == nullptr || h.minApiVer > h.maxApiVer ||
|
||||
h.maxApiVer > rpc::kApiMaximumValidVersion ||
|
||||
h.minApiVer < rpc::kApiMinimumSupportedVersion)
|
||||
return false;
|
||||
|
||||
// Sorted, so a repeat can only be of the preceding entry.
|
||||
if (i > 0 && kHandlers[i - 1].name == h.name)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}(),
|
||||
"xrpl::rpc : every handler needs a unique name, a method, and a valid API "
|
||||
"version range");
|
||||
|
||||
// The names are handed to json::StaticString, and read as C strings from there,
|
||||
// so each must be a view of a whole string literal rather than a slice of one.
|
||||
static_assert(
|
||||
std::ranges::all_of(kHandlers, isNullTerminated, &Handler::name),
|
||||
"xrpl::rpc : every handler name must be null-terminated");
|
||||
|
||||
// The handler names, which are already distinct and sorted.
|
||||
constexpr auto kHandlerNames = [] {
|
||||
std::array<std::string_view, kHandlers.size()> names{};
|
||||
std::ranges::transform(kHandlers, names.begin(), &Handler::name);
|
||||
return names;
|
||||
}();
|
||||
|
||||
} // namespace
|
||||
|
||||
Handler const*
|
||||
getHandler(unsigned version, bool betaEnabled, std::string const& name)
|
||||
getHandler(unsigned version, bool betaEnabled, std::string_view name)
|
||||
{
|
||||
return HandlerTable::instance().getHandler(version, betaEnabled, name);
|
||||
if (version < rpc::kApiMinimumSupportedVersion ||
|
||||
version > (betaEnabled ? rpc::kApiBetaVersion : rpc::kApiMaximumSupportedVersion))
|
||||
return nullptr;
|
||||
|
||||
// Names are unique, so the binary search finds the only candidate; it then
|
||||
// answers this request only if it serves this version.
|
||||
auto const i = std::ranges::lower_bound(kHandlers, name, {}, &Handler::name);
|
||||
if (i == kHandlers.end() || i->name != name)
|
||||
return nullptr;
|
||||
|
||||
if (i->minApiVer <= version && version <= i->maxApiVer)
|
||||
return &*i;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::set<char const*>
|
||||
std::span<std::string_view const>
|
||||
getHandlerNames()
|
||||
{
|
||||
return HandlerTable::instance().getHandlerNames();
|
||||
return kHandlerNames;
|
||||
}
|
||||
|
||||
} // namespace xrpl::rpc
|
||||
|
||||
@@ -12,13 +12,8 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace json {
|
||||
class Object;
|
||||
} // namespace json
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
@@ -32,11 +27,14 @@ enum class Condition {
|
||||
|
||||
struct Handler
|
||||
{
|
||||
template <class JsonValue>
|
||||
using Method = std::function<Status(JsonContext&, JsonValue&)>;
|
||||
// A plain function pointer, not a std::function: every method is a free
|
||||
// function known at compile time, so nothing needs to be captured. This
|
||||
// also keeps Handler a literal type, letting the dispatch table be built
|
||||
// and checked at compile time.
|
||||
using Method = Status (*)(JsonContext&, json::Value&);
|
||||
|
||||
char const* name;
|
||||
Method<json::Value> valueMethod;
|
||||
std::string_view name;
|
||||
Method valueMethod;
|
||||
Role role;
|
||||
rpc::Condition condition;
|
||||
|
||||
@@ -45,7 +43,7 @@ struct Handler
|
||||
};
|
||||
|
||||
Handler const*
|
||||
getHandler(unsigned int version, bool betaEnabled, std::string const&);
|
||||
getHandler(unsigned int version, bool betaEnabled, std::string_view name);
|
||||
|
||||
/**
|
||||
* Return a json::ValueType::Object with a single entry.
|
||||
@@ -60,9 +58,12 @@ makeObjectValue(Value const& value, json::StaticString const& field = jss::messa
|
||||
}
|
||||
|
||||
/**
|
||||
* Return names of all methods.
|
||||
* Return the names of all methods, sorted and without duplicates.
|
||||
*
|
||||
* The names view refers to storage that outlives the program, so it is safe to
|
||||
* hold on to.
|
||||
*/
|
||||
std::set<char const*>
|
||||
std::span<std::string_view const>
|
||||
getHandlerNames();
|
||||
|
||||
template <class T>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpld/rpc/RPCCall.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/ServerHandler.h>
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
@@ -44,10 +45,13 @@
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -56,6 +60,27 @@ namespace xrpl {
|
||||
|
||||
class RPCParser;
|
||||
|
||||
namespace {
|
||||
|
||||
// One command the command line accepts: the method it names, the parser that
|
||||
// turns arguments into a request, and how many arguments that parser needs.
|
||||
//
|
||||
// Declared out here, rather than nested in RPCParser, so that the defaults
|
||||
// below can be used: a default member initializer is not available while the
|
||||
// enclosing class is still incomplete, which is when the table is built.
|
||||
struct Command
|
||||
{
|
||||
// For a command that accepts any number of parameters.
|
||||
static constexpr unsigned kUnlimitedParams = std::numeric_limits<unsigned>::max();
|
||||
|
||||
std::string_view name;
|
||||
json::Value (RPCParser::*parse)(json::Value const& jvParams);
|
||||
unsigned minParams = 0;
|
||||
unsigned maxParams = kUnlimitedParams;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// HTTP protocol
|
||||
//
|
||||
@@ -1239,7 +1264,249 @@ private:
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// An omitted minParams means the command takes no arguments; an omitted
|
||||
// maxParams means it takes any number. See Command.
|
||||
//
|
||||
// The commands, in whatever order reads best. parseCommand() searches
|
||||
// kSortedCommands below, so the order here carries no meaning.
|
||||
static constexpr auto kCommandArray = std::to_array<Command>({
|
||||
// Request-response methods
|
||||
// - Returns an error, or the request.
|
||||
// - To modify the method, provide a new method in the request.
|
||||
{.name = rpc::method::kAccountCurrencies,
|
||||
.parse = &RPCParser::parseAccountCurrencies,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = rpc::method::kAccountInfo,
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = rpc::method::kAccountLines,
|
||||
.parse = &RPCParser::parseAccountLines,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = rpc::method::kAccountChannels,
|
||||
.parse = &RPCParser::parseAccountChannels,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = rpc::method::kAccountNfts,
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = rpc::method::kAccountObjects,
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = rpc::method::kAccountOffers,
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kAccountTx,
|
||||
.parse = &RPCParser::parseAccountTransactions,
|
||||
.minParams = 1,
|
||||
.maxParams = 8},
|
||||
{.name = rpc::method::kAmmInfo,
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kVaultInfo,
|
||||
.parse = &RPCParser::parseVault,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kBookChanges,
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kBookOffers,
|
||||
.parse = &RPCParser::parseBookOffers,
|
||||
.minParams = 2,
|
||||
.maxParams = 7},
|
||||
{.name = rpc::method::kCanDelete, .parse = &RPCParser::parseCanDelete, .maxParams = 1},
|
||||
{.name = rpc::method::kChannelAuthorize,
|
||||
.parse = &RPCParser::parseChannelAuthorize,
|
||||
.minParams = 3,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kChannelVerify,
|
||||
.parse = &RPCParser::parseChannelVerify,
|
||||
.minParams = 4,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kConnect,
|
||||
.parse = &RPCParser::parseConnect,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kConsensusInfo, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kDepositAuthorized,
|
||||
.parse = &RPCParser::parseDepositAuthorized,
|
||||
.minParams = 2,
|
||||
.maxParams = 11},
|
||||
{.name = rpc::method::kFeature, .parse = &RPCParser::parseFeature, .maxParams = 2},
|
||||
{.name = rpc::method::kFetchInfo, .parse = &RPCParser::parseFetchInfo, .maxParams = 1},
|
||||
{.name = rpc::method::kGatewayBalances,
|
||||
.parse = &RPCParser::parseGatewayBalances,
|
||||
.minParams = 1},
|
||||
{.name = rpc::method::kGetCounts, .parse = &RPCParser::parseGetCounts, .maxParams = 1},
|
||||
{.name = rpc::method::kJson,
|
||||
.parse = &RPCParser::parseJson,
|
||||
.minParams = 2,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kJson2,
|
||||
.parse = &RPCParser::parseJson2,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kLedger, .parse = &RPCParser::parseLedger, .maxParams = 2},
|
||||
{.name = rpc::method::kLedgerAccept, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kLedgerClosed, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kLedgerCurrent, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kLedgerEntry,
|
||||
.parse = &RPCParser::parseLedgerEntry,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kLedgerHeader,
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kLedgerRequest,
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kLogLevel, .parse = &RPCParser::parseLogLevel, .maxParams = 2},
|
||||
{.name = rpc::method::kLogrotate, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kManifest,
|
||||
.parse = &RPCParser::parseManifest,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kOwnerInfo,
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = rpc::method::kPeers, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kPing, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kPrint, .parse = &RPCParser::parseAsIs, .maxParams = 1},
|
||||
{.name = rpc::method::kRandom, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kPeerReservationsAdd,
|
||||
.parse = &RPCParser::parsePeerReservationsAdd,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kPeerReservationsDel,
|
||||
.parse = &RPCParser::parsePeerReservationsDel,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kPeerReservationsList,
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.maxParams = 0},
|
||||
{.name = rpc::method::kRipplePathFind,
|
||||
.parse = &RPCParser::parseRipplePathFind,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kServerDefinitions,
|
||||
.parse = &RPCParser::parseServerDefinitions,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kServerInfo, .parse = &RPCParser::parseServerInfo, .maxParams = 1},
|
||||
{.name = rpc::method::kServerState, .parse = &RPCParser::parseServerInfo, .maxParams = 1},
|
||||
{.name = rpc::method::kSign,
|
||||
.parse = &RPCParser::parseSignSubmit,
|
||||
.minParams = 2,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kSignFor,
|
||||
.parse = &RPCParser::parseSignFor,
|
||||
.minParams = 3,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kStop, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kSimulate,
|
||||
.parse = &RPCParser::parseSimulate,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kSubmit,
|
||||
.parse = &RPCParser::parseSignSubmit,
|
||||
.minParams = 1,
|
||||
.maxParams = 4},
|
||||
{.name = rpc::method::kSubmitMultisigned,
|
||||
.parse = &RPCParser::parseSubmitMultiSigned,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kTransactionEntry,
|
||||
.parse = &RPCParser::parseTransactionEntry,
|
||||
.minParams = 2,
|
||||
.maxParams = 2},
|
||||
{.name = rpc::method::kTx, .parse = &RPCParser::parseTx, .minParams = 1, .maxParams = 4},
|
||||
{.name = rpc::method::kTxHistory,
|
||||
.parse = &RPCParser::parseTxHistory,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kUnlList, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kValidationCreate,
|
||||
.parse = &RPCParser::parseValidationCreate,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kValidatorInfo, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kVersion, .parse = &RPCParser::parseAsIs, .maxParams = 0},
|
||||
{.name = rpc::method::kWalletPropose,
|
||||
.parse = &RPCParser::parseWalletPropose,
|
||||
.maxParams = 1},
|
||||
{.name = rpc::method::kInternal, .parse = &RPCParser::parseInternal, .minParams = 1},
|
||||
|
||||
// Event methods -- rejected outright below, so any parameters will do
|
||||
{.name = rpc::method::kPathFind, .parse = &RPCParser::parseEvented},
|
||||
{.name = rpc::method::kSubscribe, .parse = &RPCParser::parseEvented},
|
||||
{.name = rpc::method::kUnsubscribe, .parse = &RPCParser::parseEvented},
|
||||
});
|
||||
|
||||
// kCommandArray sorted by name, so a command can be found by binary search.
|
||||
static constexpr auto kSortedCommands = [] {
|
||||
auto commands = kCommandArray;
|
||||
std::ranges::sort(commands, {}, &Command::name);
|
||||
return commands;
|
||||
}();
|
||||
|
||||
// parseCommand() relies on this being sorted to binary search it, and
|
||||
// kCommandNames below inherits the order.
|
||||
static_assert(
|
||||
std::ranges::is_sorted(kSortedCommands, {}, &Command::name),
|
||||
"xrpl::RPCParser : kSortedCommands must be sorted");
|
||||
|
||||
// The command names, which are already distinct and sorted.
|
||||
static constexpr auto kCommandNames = [] {
|
||||
std::array<std::string_view, kSortedCommands.size()> names{};
|
||||
std::ranges::transform(kSortedCommands, names.begin(), &Command::name);
|
||||
return names;
|
||||
}();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Names of every method the command line accepts.
|
||||
*/
|
||||
static std::span<std::string_view const>
|
||||
methodNames()
|
||||
{
|
||||
return kCommandNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the command table is well formed.
|
||||
*
|
||||
* A name must select exactly one command, and must name a method the server
|
||||
* can dispatch -- otherwise the command line would accept something that
|
||||
* then goes nowhere. The latter is checked by RPCCall_test, which can see
|
||||
* the handler table; here we can at least rule out duplicates and gaps.
|
||||
*
|
||||
* This is a function the static_assert below the class calls, rather than
|
||||
* the assert itself, because reading a pointer to a member of RPCParser is
|
||||
* only a constant expression once RPCParser is complete.
|
||||
*/
|
||||
static constexpr bool
|
||||
commandsValid()
|
||||
{
|
||||
for (std::size_t i = 0; i < kSortedCommands.size(); ++i)
|
||||
{
|
||||
auto const& command = kSortedCommands[i];
|
||||
if (command.name.empty() || command.parse == nullptr ||
|
||||
command.minParams > command.maxParams)
|
||||
return false;
|
||||
if (i > 0 && kSortedCommands[i - 1].name == command.name)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
explicit RPCParser(unsigned apiVersion, beast::Journal j) : apiVersion_(apiVersion), j_(j)
|
||||
@@ -1251,7 +1518,7 @@ public:
|
||||
// Convert a rpc method and params to a request.
|
||||
// <-- { method: xyz, params: [... ] } or { error: ..., ... }
|
||||
json::Value
|
||||
parseCommand(std::string strMethod, json::Value jvParams, bool allowAnyCommand)
|
||||
parseCommand(std::string_view strMethod, json::Value const& jvParams, bool allowAnyCommand)
|
||||
{
|
||||
if (auto stream = j_.trace())
|
||||
{
|
||||
@@ -1259,254 +1526,37 @@ public:
|
||||
stream << "Params: " << jvParams;
|
||||
}
|
||||
|
||||
struct Command
|
||||
auto const found = std::ranges::lower_bound(kSortedCommands, strMethod, {}, &Command::name);
|
||||
|
||||
if (found == kSortedCommands.end() || found->name != strMethod)
|
||||
{
|
||||
char const* name;
|
||||
parseFuncPtr parse;
|
||||
int minParams;
|
||||
int maxParams;
|
||||
};
|
||||
// The command could not be found
|
||||
if (!allowAnyCommand)
|
||||
return rpcError(RpcUnknownCommand);
|
||||
|
||||
static constexpr Command kCommands[] = {
|
||||
// Request-response methods
|
||||
// - Returns an error, or the request.
|
||||
// - To modify the method, provide a new method in the request.
|
||||
{.name = "account_currencies",
|
||||
.parse = &RPCParser::parseAccountCurrencies,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = "account_info",
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = "account_lines",
|
||||
.parse = &RPCParser::parseAccountLines,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = "account_channels",
|
||||
.parse = &RPCParser::parseAccountChannels,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = "account_nfts",
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = "account_objects",
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 5},
|
||||
{.name = "account_offers",
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 4},
|
||||
{.name = "account_tx",
|
||||
.parse = &RPCParser::parseAccountTransactions,
|
||||
.minParams = 1,
|
||||
.maxParams = 8},
|
||||
{.name = "amm_info", .parse = &RPCParser::parseAsIs, .minParams = 1, .maxParams = 2},
|
||||
{.name = "vault_info", .parse = &RPCParser::parseVault, .minParams = 1, .maxParams = 2},
|
||||
{.name = "book_changes",
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "book_offers",
|
||||
.parse = &RPCParser::parseBookOffers,
|
||||
.minParams = 2,
|
||||
.maxParams = 7},
|
||||
{.name = "can_delete",
|
||||
.parse = &RPCParser::parseCanDelete,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "channel_authorize",
|
||||
.parse = &RPCParser::parseChannelAuthorize,
|
||||
.minParams = 3,
|
||||
.maxParams = 4},
|
||||
{.name = "channel_verify",
|
||||
.parse = &RPCParser::parseChannelVerify,
|
||||
.minParams = 4,
|
||||
.maxParams = 4},
|
||||
{.name = "connect", .parse = &RPCParser::parseConnect, .minParams = 1, .maxParams = 2},
|
||||
{.name = "consensus_info",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "deposit_authorized",
|
||||
.parse = &RPCParser::parseDepositAuthorized,
|
||||
.minParams = 2,
|
||||
.maxParams = 11},
|
||||
{.name = "feature", .parse = &RPCParser::parseFeature, .minParams = 0, .maxParams = 2},
|
||||
{.name = "fetch_info",
|
||||
.parse = &RPCParser::parseFetchInfo,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "gateway_balances",
|
||||
.parse = &RPCParser::parseGatewayBalances,
|
||||
.minParams = 1,
|
||||
.maxParams = -1},
|
||||
{.name = "get_counts",
|
||||
.parse = &RPCParser::parseGetCounts,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "json", .parse = &RPCParser::parseJson, .minParams = 2, .maxParams = 2},
|
||||
{.name = "json2", .parse = &RPCParser::parseJson2, .minParams = 1, .maxParams = 1},
|
||||
{.name = "ledger", .parse = &RPCParser::parseLedger, .minParams = 0, .maxParams = 2},
|
||||
{.name = "ledger_accept",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "ledger_closed",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "ledger_current",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "ledger_entry",
|
||||
.parse = &RPCParser::parseLedgerEntry,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = "ledger_header",
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "ledger_request",
|
||||
.parse = &RPCParser::parseLedgerId,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "log_level",
|
||||
.parse = &RPCParser::parseLogLevel,
|
||||
.minParams = 0,
|
||||
.maxParams = 2},
|
||||
{.name = "logrotate", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "manifest",
|
||||
.parse = &RPCParser::parseManifest,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "owner_info",
|
||||
.parse = &RPCParser::parseAccountItems,
|
||||
.minParams = 1,
|
||||
.maxParams = 3},
|
||||
{.name = "peers", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "ping", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "print", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 1},
|
||||
// { "profile", &RPCParser::parseProfile, 1, 9
|
||||
// },
|
||||
{.name = "random", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "peer_reservations_add",
|
||||
.parse = &RPCParser::parsePeerReservationsAdd,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = "peer_reservations_del",
|
||||
.parse = &RPCParser::parsePeerReservationsDel,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "peer_reservations_list",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "ripple_path_find",
|
||||
.parse = &RPCParser::parseRipplePathFind,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = "server_definitions",
|
||||
.parse = &RPCParser::parseServerDefinitions,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "server_info",
|
||||
.parse = &RPCParser::parseServerInfo,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "server_state",
|
||||
.parse = &RPCParser::parseServerInfo,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "sign", .parse = &RPCParser::parseSignSubmit, .minParams = 2, .maxParams = 4},
|
||||
{.name = "sign_for", .parse = &RPCParser::parseSignFor, .minParams = 3, .maxParams = 4},
|
||||
{.name = "stop", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "simulate",
|
||||
.parse = &RPCParser::parseSimulate,
|
||||
.minParams = 1,
|
||||
.maxParams = 2},
|
||||
{.name = "submit",
|
||||
.parse = &RPCParser::parseSignSubmit,
|
||||
.minParams = 1,
|
||||
.maxParams = 4},
|
||||
{.name = "submit_multisigned",
|
||||
.parse = &RPCParser::parseSubmitMultiSigned,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "transaction_entry",
|
||||
.parse = &RPCParser::parseTransactionEntry,
|
||||
.minParams = 2,
|
||||
.maxParams = 2},
|
||||
{.name = "tx", .parse = &RPCParser::parseTx, .minParams = 1, .maxParams = 4},
|
||||
{.name = "tx_history",
|
||||
.parse = &RPCParser::parseTxHistory,
|
||||
.minParams = 1,
|
||||
.maxParams = 1},
|
||||
{.name = "unl_list", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "validation_create",
|
||||
.parse = &RPCParser::parseValidationCreate,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "validator_info",
|
||||
.parse = &RPCParser::parseAsIs,
|
||||
.minParams = 0,
|
||||
.maxParams = 0},
|
||||
{.name = "version", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
|
||||
{.name = "wallet_propose",
|
||||
.parse = &RPCParser::parseWalletPropose,
|
||||
.minParams = 0,
|
||||
.maxParams = 1},
|
||||
{.name = "internal",
|
||||
.parse = &RPCParser::parseInternal,
|
||||
.minParams = 1,
|
||||
.maxParams = -1},
|
||||
|
||||
// Event methods
|
||||
{.name = "path_find",
|
||||
.parse = &RPCParser::parseEvented,
|
||||
.minParams = -1,
|
||||
.maxParams = -1},
|
||||
{.name = "subscribe",
|
||||
.parse = &RPCParser::parseEvented,
|
||||
.minParams = -1,
|
||||
.maxParams = -1},
|
||||
{.name = "unsubscribe",
|
||||
.parse = &RPCParser::parseEvented,
|
||||
.minParams = -1,
|
||||
.maxParams = -1},
|
||||
};
|
||||
|
||||
auto const count = jvParams.size();
|
||||
|
||||
for (auto const& command : kCommands)
|
||||
{
|
||||
if (strMethod == command.name)
|
||||
{
|
||||
if ((command.minParams >= 0 && count < command.minParams) ||
|
||||
(command.maxParams >= 0 && count > command.maxParams))
|
||||
{
|
||||
JLOG(j_.debug()) << "Wrong number of parameters for " << command.name
|
||||
<< " minimum=" << command.minParams
|
||||
<< " maximum=" << command.maxParams << " actual=" << count;
|
||||
|
||||
return rpcError(RpcBadSyntax);
|
||||
}
|
||||
|
||||
return (this->*(command.parse))(jvParams);
|
||||
}
|
||||
return parseAsIs(jvParams);
|
||||
}
|
||||
|
||||
// The command could not be found
|
||||
if (!allowAnyCommand)
|
||||
return rpcError(RpcUnknownCommand);
|
||||
auto const count = jvParams.size();
|
||||
if (count < found->minParams || count > found->maxParams)
|
||||
{
|
||||
JLOG(j_.debug()) << "Wrong number of parameters for " << found->name
|
||||
<< " minimum=" << found->minParams << " maximum=" << found->maxParams
|
||||
<< " actual=" << count;
|
||||
|
||||
return parseAsIs(jvParams);
|
||||
return rpcError(RpcBadSyntax);
|
||||
}
|
||||
|
||||
return (this->*(found->parse))(jvParams);
|
||||
}
|
||||
};
|
||||
|
||||
// See the comment on commandsValid() for why this is out here.
|
||||
static_assert(
|
||||
RPCParser::commandsValid(),
|
||||
"xrpl::RPCParser : every command needs a unique name, a parser, and a valid "
|
||||
"parameter count range");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
@@ -1614,6 +1664,12 @@ struct RPCCallImp
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::span<std::string_view const>
|
||||
commandLineMethodNames()
|
||||
{
|
||||
return RPCParser::methodNames();
|
||||
}
|
||||
|
||||
// Used internally by rpcClient.
|
||||
json::Value
|
||||
rpcCmdToJson(
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
@@ -153,9 +154,8 @@ fillHandler(JsonContext& context, Handler const*& result)
|
||||
return RpcSuccess;
|
||||
}
|
||||
|
||||
template <class Object, class Method>
|
||||
Status
|
||||
callMethod(JsonContext& context, Method method, std::string const& name, Object& result)
|
||||
callMethod(JsonContext& context, Handler::Method method, std::string_view name, json::Value& result)
|
||||
{
|
||||
static std::atomic<std::uint64_t> kRequestId{0};
|
||||
auto& perfLog = context.app.getPerfLog();
|
||||
@@ -163,7 +163,8 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object&
|
||||
try
|
||||
{
|
||||
perfLog.rpcStart(name, curId);
|
||||
auto v = context.app.getJobQueue().makeLoadEvent(JtGeneric, "cmd:" + name);
|
||||
auto v =
|
||||
context.app.getJobQueue().makeLoadEvent(JtGeneric, std::string{"cmd:"}.append(name));
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto ret = method(context, result);
|
||||
@@ -199,32 +200,28 @@ doCommand(rpc::JsonContext& context, json::Value& result)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (auto method = handler->valueMethod)
|
||||
// No null check on the method: every entry in the dispatch table carries
|
||||
// one, which a static_assert there enforces at compile time.
|
||||
if (!context.headers.user.empty() || !context.headers.forwardedFor.empty())
|
||||
{
|
||||
if (!context.headers.user.empty() || !context.headers.forwardedFor.empty())
|
||||
{
|
||||
JLOG(context.j.debug())
|
||||
<< "start command: " << handler->name << ", user: " << context.headers.user
|
||||
<< ", forwarded for: " << context.headers.forwardedFor;
|
||||
JLOG(context.j.debug()) << "start command: " << handler->name
|
||||
<< ", user: " << context.headers.user
|
||||
<< ", forwarded for: " << context.headers.forwardedFor;
|
||||
|
||||
auto ret = callMethod(context, method, handler->name, result);
|
||||
auto const ret = callMethod(context, handler->valueMethod, handler->name, result);
|
||||
|
||||
JLOG(context.j.debug())
|
||||
<< "finish command: " << handler->name << ", user: " << context.headers.user
|
||||
<< ", forwarded for: " << context.headers.forwardedFor;
|
||||
JLOG(context.j.debug()) << "finish command: " << handler->name
|
||||
<< ", user: " << context.headers.user
|
||||
<< ", forwarded for: " << context.headers.forwardedFor;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto ret = callMethod(context, method, handler->name, result);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return RpcUnknownCommand;
|
||||
return callMethod(context, handler->valueMethod, handler->name, result);
|
||||
}
|
||||
|
||||
Role
|
||||
roleRequired(unsigned int version, bool betaEnabled, std::string const& method)
|
||||
roleRequired(unsigned int version, bool betaEnabled, std::string_view method)
|
||||
{
|
||||
auto handler = rpc::getHandler(version, betaEnabled, method);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/TxQ.h> // IWYU pragma: keep
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
@@ -12,12 +13,9 @@
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace json {
|
||||
class Object;
|
||||
} // namespace json
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
struct JsonContext;
|
||||
@@ -40,7 +38,7 @@ public:
|
||||
writeResult(json::Value&);
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static constexpr char name[] = "ledger";
|
||||
static constexpr std::string_view name = method::kLedger;
|
||||
|
||||
static constexpr unsigned minApiVer = rpc::kApiMinimumSupportedVersion;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpld/app/main/Application.h> // IWYU pragma: keep
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
@@ -9,6 +10,8 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
class VersionHandler
|
||||
@@ -32,7 +35,7 @@ public:
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static constexpr char const* name = "version";
|
||||
static constexpr std::string_view name = method::kVersion;
|
||||
|
||||
static constexpr unsigned minApiVer = rpc::kApiMinimumSupportedVersion;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user