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:
Bronek Kozicki
2023-10-24 23:57:49 +01:00
committed by GitHub
parent 3e5f770a38
commit 1eac4d2c07
14 changed files with 367 additions and 60 deletions

View File

@@ -43,7 +43,7 @@ namespace ripple {
namespace perf {
PerfLogImp::Counters::Counters(
std::vector<char const*> const& labels,
std::set<char const*> const& labels,
JobTypes const& jobTypes)
{
{

View File

@@ -113,9 +113,7 @@ class PerfLogImp : public PerfLog
std::unordered_map<std::uint64_t, MethodStart> methods_;
mutable std::mutex methodsMutex_;
Counters(
std::vector<char const*> const& labels,
JobTypes const& jobTypes);
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes);
Json::Value
countersJson() const;
Json::Value

View File

@@ -30,6 +30,7 @@
#include <ripple/rpc/Role.h>
#include <ripple/rpc/Status.h>
#include <ripple/rpc/impl/Handler.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace Json {
class Object;
@@ -58,23 +59,15 @@ public:
void
writeResult(Object&);
static char const*
name()
{
return "ledger";
}
static constexpr char name[] = "ledger";
static Role
role()
{
return Role::USER;
}
static constexpr unsigned minApiVer = RPC::apiMinimumSupportedVersion;
static Condition
condition()
{
return NO_CONDITION;
}
static constexpr unsigned maxApiVer = RPC::apiMaximumValidVersion;
static constexpr Role role = Role::USER;
static constexpr Condition condition = NO_CONDITION;
private:
JsonContext& context_;

View File

@@ -46,23 +46,15 @@ public:
setVersion(obj, apiVersion_, betaEnabled_);
}
static char const*
name()
{
return "version";
}
static constexpr char const* name = "version";
static Role
role()
{
return Role::USER;
}
static constexpr unsigned minApiVer = RPC::apiMinimumSupportedVersion;
static Condition
condition()
{
return NO_CONDITION;
}
static constexpr unsigned maxApiVer = RPC::apiMaximumValidVersion;
static constexpr Role role = Role::USER;
static constexpr Condition condition = NO_CONDITION;
private:
unsigned int apiVersion_;

View File

@@ -17,11 +17,14 @@
*/
//==============================================================================
#include <ripple/basics/contract.h>
#include <ripple/rpc/handlers/Handlers.h>
#include <ripple/rpc/handlers/Version.h>
#include <ripple/rpc/impl/Handler.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <map>
namespace ripple {
namespace RPC {
namespace {
@@ -47,6 +50,9 @@ template <class Object, class HandlerImpl>
Status
handle(JsonContext& context, Object& object)
{
assert(
context.apiVersion >= HandlerImpl::minApiVer &&
context.apiVersion <= HandlerImpl::maxApiVer);
HandlerImpl handler(context);
auto status = handler.check();
@@ -55,7 +61,20 @@ handle(JsonContext& context, Object& object)
else
handler.writeResult(object);
return status;
};
}
template <typename HandlerImpl>
Handler
handlerFrom()
{
return {
HandlerImpl::name,
&handle<Json::Value, HandlerImpl>,
HandlerImpl::role,
HandlerImpl::condition,
HandlerImpl::minApiVer,
HandlerImpl::maxApiVer};
}
Handler const handlerArray[]{
// Some handlers not specified here are added to the table via addHandler()
@@ -110,7 +129,7 @@ Handler const handlerArray[]{
NEEDS_CURRENT_LEDGER},
{"ledger_data", byRef(&doLedgerData), Role::USER, NO_CONDITION},
{"ledger_entry", byRef(&doLedgerEntry), Role::USER, NO_CONDITION},
{"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION},
{"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION, 1, 1},
{"ledger_request", byRef(&doLedgerRequest), Role::ADMIN, NO_CONDITION},
{"log_level", byRef(&doLogLevel), Role::ADMIN, NO_CONDITION},
{"logrotate", byRef(&doLogRotate), Role::ADMIN, NO_CONDITION},
@@ -156,7 +175,7 @@ Handler const handlerArray[]{
NEEDS_CURRENT_LEDGER},
{"transaction_entry", byRef(&doTransactionEntry), Role::USER, NO_CONDITION},
{"tx", byRef(&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION},
{"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION},
{"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION, 1, 1},
{"tx_reduce_relay", byRef(&doTxReduceRelay), Role::USER, NO_CONDITION},
{"unl_list", byRef(&doUnlList), Role::ADMIN, NO_CONDITION},
{"validation_create",
@@ -178,14 +197,42 @@ Handler const handlerArray[]{
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]] bool
overlappingApiVersion(
std::pair<handler_table_t::iterator, handler_table_t::iterator> range,
unsigned minVer,
unsigned maxVer)
{
assert(minVer <= maxVer);
assert(maxVer <= RPC::apiMaximumValidVersion);
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(const Handler (&entries)[N])
{
for (std::size_t i = 0; i < N; ++i)
for (auto const& entry : entries)
{
auto const& entry = entries[i];
assert(table_.find(entry.name_) == table_.end());
table_[entry.name_] = entry;
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.
@@ -201,7 +248,7 @@ public:
return handlerTable;
}
Handler const*
[[nodiscard]] Handler const*
getHandler(unsigned version, bool betaEnabled, std::string const& name)
const
{
@@ -209,36 +256,48 @@ public:
version > (betaEnabled ? RPC::apiBetaVersion
: RPC::apiMaximumSupportedVersion))
return nullptr;
auto i = table_.find(name);
return i == table_.end() ? nullptr : &i->second;
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;
}
std::vector<char const*>
[[nodiscard]] std::set<char const*>
getHandlerNames() const
{
std::vector<char const*> ret;
ret.reserve(table_.size());
std::set<char const*> ret;
for (auto const& i : table_)
ret.push_back(i.second.name_);
ret.insert(i.second.name_);
return ret;
}
private:
std::map<std::string, Handler> table_;
handler_table_t table_;
template <class HandlerImpl>
void
addHandler()
{
assert(table_.find(HandlerImpl::name()) == table_.end());
static_assert(HandlerImpl::minApiVer <= HandlerImpl::maxApiVer);
static_assert(HandlerImpl::maxApiVer <= RPC::apiMaximumValidVersion);
static_assert(
RPC::apiMinimumSupportedVersion <= HandlerImpl::minApiVer);
Handler h;
h.name_ = HandlerImpl::name();
h.valueMethod_ = &handle<Json::Value, HandlerImpl>;
h.role_ = HandlerImpl::role();
h.condition_ = HandlerImpl::condition();
if (overlappingApiVersion(
table_.equal_range(HandlerImpl::name),
HandlerImpl::minApiVer,
HandlerImpl::maxApiVer))
LogicError(
std::string("Handler for ") + HandlerImpl::name +
" overlaps with an existing handler");
table_[HandlerImpl::name()] = h;
table_.insert({HandlerImpl::name, handlerFrom<HandlerImpl>()});
}
};
@@ -250,11 +309,11 @@ getHandler(unsigned version, bool betaEnabled, std::string const& name)
return HandlerTable::instance().getHandler(version, betaEnabled, name);
}
std::vector<char const*>
std::set<char const*>
getHandlerNames()
{
return HandlerTable::instance().getHandlerNames();
};
}
} // namespace RPC
} // namespace ripple

View File

@@ -25,6 +25,7 @@
#include <ripple/core/Config.h>
#include <ripple/rpc/RPCHandler.h>
#include <ripple/rpc/Status.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/impl/Tuning.h>
#include <vector>
@@ -52,6 +53,9 @@ struct Handler
Method<Json::Value> valueMethod_;
Role role_;
RPC::Condition condition_;
unsigned minApiVer_ = apiMinimumSupportedVersion;
unsigned maxApiVer_ = apiMaximumValidVersion;
};
Handler const*
@@ -70,7 +74,7 @@ makeObjectValue(
}
/** Return names of all methods. */
std::vector<char const*>
std::set<char const*>
getHandlerNames();
template <class T>

View File

@@ -242,10 +242,12 @@ constexpr unsigned int apiVersionIfUnspecified = 1;
constexpr unsigned int apiMinimumSupportedVersion = 1;
constexpr unsigned int apiMaximumSupportedVersion = 1;
constexpr unsigned int apiBetaVersion = 2;
constexpr unsigned int apiMaximumValidVersion = apiBetaVersion;
static_assert(apiMinimumSupportedVersion >= apiVersionIfUnspecified);
static_assert(apiMaximumSupportedVersion >= apiMinimumSupportedVersion);
static_assert(apiBetaVersion >= apiMaximumSupportedVersion);
static_assert(apiMaximumValidVersion >= apiMaximumSupportedVersion);
template <class Object>
void