mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Support API versioning
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include <ripple/rpc/impl/Handler.h>
|
||||
#include <ripple/rpc/handlers/Handlers.h>
|
||||
#include <ripple/rpc/handlers/Version.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
@@ -129,16 +130,21 @@ class HandlerTable {
|
||||
explicit
|
||||
HandlerTable (const Handler(&entries)[N])
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
for(auto v = RPC::ApiMinimumSupportedVersion; v <= RPC::ApiMaximumSupportedVersion; ++v)
|
||||
{
|
||||
auto const& entry = entries[i];
|
||||
assert (table_.find(entry.name_) == table_.end());
|
||||
table_[entry.name_] = entry;
|
||||
}
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
auto & innerTable = table_[versionToIndex(v)];
|
||||
auto const& entry = entries[i];
|
||||
assert (innerTable.find(entry.name_) == innerTable.end());
|
||||
innerTable[entry.name_] = entry;
|
||||
}
|
||||
|
||||
// This is where the new-style handlers are added.
|
||||
addHandler<LedgerHandler>();
|
||||
addHandler<VersionHandler>();
|
||||
// This is where the new-style handlers are added.
|
||||
// This is also where different versions of handlers are added.
|
||||
addHandler<LedgerHandler>(v);
|
||||
addHandler<VersionHandler>(v);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -148,29 +154,38 @@ class HandlerTable {
|
||||
return handlerTable;
|
||||
}
|
||||
|
||||
Handler const* getHandler(std::string name) const
|
||||
Handler const* getHandler(unsigned version, std::string name) const
|
||||
{
|
||||
auto i = table_.find(name);
|
||||
return i == table_.end() ? nullptr : &i->second;
|
||||
if(version > RPC::ApiMaximumSupportedVersion || version < RPC::ApiMinimumSupportedVersion)
|
||||
return nullptr;
|
||||
auto & innerTable = table_[versionToIndex(version)];
|
||||
auto i = innerTable.find(name);
|
||||
return i == innerTable.end() ? nullptr : &i->second;
|
||||
}
|
||||
|
||||
std::vector<char const*>
|
||||
getHandlerNames() const
|
||||
{
|
||||
std::vector<char const*> ret;
|
||||
ret.reserve(table_.size());
|
||||
for (auto const& i : table_)
|
||||
ret.push_back(i.second.name_);
|
||||
return ret;
|
||||
std::unordered_set<char const*> name_set;
|
||||
for ( int index = 0; index < table_.size(); ++index)
|
||||
{
|
||||
for(auto const& h : table_[index])
|
||||
{
|
||||
name_set.insert(h.second.name_);
|
||||
}
|
||||
}
|
||||
return std::vector<char const*>(name_set.begin(), name_set.end());
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, Handler> table_;
|
||||
std::array<std::map<std::string, Handler>, APINumberVersionSupported> table_;
|
||||
|
||||
template <class HandlerImpl>
|
||||
void addHandler()
|
||||
void addHandler(unsigned version)
|
||||
{
|
||||
assert (table_.find(HandlerImpl::name()) == table_.end());
|
||||
assert (version >= RPC::ApiMinimumSupportedVersion && version <= RPC::ApiMaximumSupportedVersion);
|
||||
auto & innerTable = table_[versionToIndex(version)];
|
||||
assert (innerTable.find(HandlerImpl::name()) == innerTable.end());
|
||||
|
||||
Handler h;
|
||||
h.name_ = HandlerImpl::name();
|
||||
@@ -178,15 +193,20 @@ class HandlerTable {
|
||||
h.role_ = HandlerImpl::role();
|
||||
h.condition_ = HandlerImpl::condition();
|
||||
|
||||
table_[HandlerImpl::name()] = h;
|
||||
innerTable[HandlerImpl::name()] = h;
|
||||
}
|
||||
|
||||
inline unsigned versionToIndex(unsigned version) const
|
||||
{
|
||||
return version - RPC::ApiMinimumSupportedVersion;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Handler const* getHandler(std::string const& name)
|
||||
Handler const* getHandler(unsigned version, std::string const& name)
|
||||
{
|
||||
return HandlerTable::instance().getHandler(name);
|
||||
return HandlerTable::instance().getHandler(version, name);
|
||||
}
|
||||
|
||||
std::vector<char const*>
|
||||
|
||||
@@ -51,7 +51,7 @@ struct Handler
|
||||
RPC::Condition condition_;
|
||||
};
|
||||
|
||||
Handler const* getHandler (std::string const&);
|
||||
Handler const* getHandler (unsigned int version, std::string const&);
|
||||
|
||||
/** Return a Json::objectValue with a single entry. */
|
||||
template <class Value>
|
||||
|
||||
@@ -141,7 +141,7 @@ error_code_i fillHandler (Context& context,
|
||||
|
||||
JLOG (context.j.trace()) << "COMMAND:" << strCommand;
|
||||
JLOG (context.j.trace()) << "REQUEST:" << context.params;
|
||||
auto handler = getHandler(strCommand);
|
||||
auto handler = getHandler(context.apiVersion, strCommand);
|
||||
|
||||
if (!handler)
|
||||
return rpcUNKNOWN_COMMAND;
|
||||
@@ -296,9 +296,9 @@ Status doCommand (
|
||||
return rpcUNKNOWN_COMMAND;
|
||||
}
|
||||
|
||||
Role roleRequired (std::string const& method)
|
||||
Role roleRequired (unsigned int version, std::string const& method)
|
||||
{
|
||||
auto handler = RPC::getHandler(method);
|
||||
auto handler = RPC::getHandler(version, method);
|
||||
|
||||
if (!handler)
|
||||
return Role::FORBID;
|
||||
|
||||
@@ -716,5 +716,24 @@ beast::SemanticVersion const firstVersion("1.0.0");
|
||||
beast::SemanticVersion const goodVersion("1.0.0");
|
||||
beast::SemanticVersion const lastVersion("1.0.0");
|
||||
|
||||
unsigned int getAPIVersionNumber(Json::Value const& jv)
|
||||
{
|
||||
static Json::Value const minVersion (RPC::ApiMinimumSupportedVersion);
|
||||
static Json::Value const maxVersion (RPC::ApiMaximumSupportedVersion);
|
||||
static Json::Value const invalidVersion (RPC::APIInvalidVersion);
|
||||
|
||||
Json::Value requestedVersion(RPC::APIVersionIfUnspecified);
|
||||
if(jv.isObject())
|
||||
{
|
||||
requestedVersion = jv.get (jss::api_version, requestedVersion);
|
||||
}
|
||||
if( !(requestedVersion.isInt() || requestedVersion.isUInt()) ||
|
||||
requestedVersion < minVersion || requestedVersion > maxVersion)
|
||||
{
|
||||
requestedVersion = invalidVersion;
|
||||
}
|
||||
return requestedVersion.asUInt();
|
||||
}
|
||||
|
||||
} // RPC
|
||||
} // ripple
|
||||
|
||||
@@ -114,10 +114,42 @@ parseRippleLibSeed(Json::Value const& params);
|
||||
std::pair<PublicKey, SecretKey>
|
||||
keypairForSignature(Json::Value const& params, Json::Value& error);
|
||||
|
||||
/**
|
||||
* API version numbers used in API version 1
|
||||
*/
|
||||
extern beast::SemanticVersion const firstVersion;
|
||||
extern beast::SemanticVersion const goodVersion;
|
||||
extern beast::SemanticVersion const lastVersion;
|
||||
|
||||
/**
|
||||
* API version numbers used in later API versions
|
||||
*
|
||||
* Requests with a version number in the range
|
||||
* [ApiMinimumSupportedVersion, ApiMaximumSupportedVersion]
|
||||
* are supported.
|
||||
*
|
||||
* Network Requests without explicit version numbers use
|
||||
* APIVersionIfUnspecified. APIVersionIfUnspecified is 1,
|
||||
* because all the RPC requests with a version >= 2 must
|
||||
* explicitly specify the version in the requests.
|
||||
* Note that APIVersionIfUnspecified will be lower than
|
||||
* ApiMinimumSupportedVersion when we stop supporting API
|
||||
* version 1.
|
||||
*
|
||||
* Command line Requests use ApiMaximumSupportedVersion.
|
||||
*/
|
||||
|
||||
constexpr unsigned int APIInvalidVersion = 0;
|
||||
constexpr unsigned int APIVersionIfUnspecified = 1;
|
||||
constexpr unsigned int ApiMinimumSupportedVersion = 1;
|
||||
constexpr unsigned int ApiMaximumSupportedVersion = 1;
|
||||
constexpr unsigned int APINumberVersionSupported = ApiMaximumSupportedVersion -
|
||||
ApiMinimumSupportedVersion + 1;
|
||||
|
||||
static_assert (ApiMinimumSupportedVersion >= APIVersionIfUnspecified);
|
||||
static_assert (ApiMaximumSupportedVersion >= ApiMinimumSupportedVersion);
|
||||
|
||||
|
||||
template <class Object>
|
||||
void
|
||||
setVersion(Object& parent)
|
||||
@@ -131,6 +163,20 @@ setVersion(Object& parent)
|
||||
std::pair<RPC::Status, LedgerEntryType>
|
||||
chooseLedgerEntryType(Json::Value const& params);
|
||||
|
||||
/**
|
||||
* Retrieve the api version number from the json value
|
||||
*
|
||||
* Note that APIInvalidVersion will be returned if
|
||||
* 1) the version number field has a wrong format
|
||||
* 2) the version number retrieved is out of the supported range
|
||||
* 3) the version number is unspecified and
|
||||
* APIVersionIfUnspecified is out of the supported range
|
||||
*
|
||||
* @param value a Json value that may or may not specifies
|
||||
* the api version number
|
||||
* @return the api version number
|
||||
*/
|
||||
unsigned int getAPIVersionNumber(const Json::Value & value);
|
||||
} // RPC
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <ripple/server/Server.h>
|
||||
#include <ripple/server/impl/JSONRPCUtil.h>
|
||||
#include <ripple/rpc/impl/ServerHandlerImp.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/make_SSLContext.h>
|
||||
@@ -398,7 +399,9 @@ ServerHandlerImp::processSession(
|
||||
Resource::Charge loadType = Resource::feeReferenceRPC;
|
||||
try
|
||||
{
|
||||
if ((!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
|
||||
auto apiVersion = RPC::getAPIVersionNumber(jv);
|
||||
if (apiVersion == RPC::APIInvalidVersion ||
|
||||
(!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
|
||||
(jv.isMember(jss::command) && !jv[jss::command].isString()) ||
|
||||
(jv.isMember(jss::method) && !jv[jss::method].isString()) ||
|
||||
(jv.isMember(jss::command) && jv.isMember(jss::method) &&
|
||||
@@ -406,7 +409,8 @@ ServerHandlerImp::processSession(
|
||||
{
|
||||
jr[jss::type] = jss::response;
|
||||
jr[jss::status] = jss::error;
|
||||
jr[jss::error] = jss::missingCommand;
|
||||
jr[jss::error] = apiVersion == RPC::APIInvalidVersion ?
|
||||
jss::invalid_API_version : jss::missingCommand;
|
||||
jr[jss::request] = jv;
|
||||
if (jv.isMember (jss::id))
|
||||
jr[jss::id] = jv[jss::id];
|
||||
@@ -414,12 +418,15 @@ ServerHandlerImp::processSession(
|
||||
jr[jss::jsonrpc] = jv[jss::jsonrpc];
|
||||
if (jv.isMember(jss::ripplerpc))
|
||||
jr[jss::ripplerpc] = jv[jss::ripplerpc];
|
||||
if (jv.isMember(jss::api_version))
|
||||
jr[jss::api_version] = jv[jss::api_version];
|
||||
|
||||
is->getConsumer().charge(Resource::feeInvalidRPC);
|
||||
return jr;
|
||||
}
|
||||
|
||||
auto required = RPC::roleRequired(jv.isMember(jss::command) ?
|
||||
auto required = RPC::roleRequired(apiVersion,
|
||||
jv.isMember(jss::command) ?
|
||||
jv[jss::command].asString() :
|
||||
jv[jss::method].asString());
|
||||
auto role = requestRole(
|
||||
@@ -444,6 +451,7 @@ ServerHandlerImp::processSession(
|
||||
app_.getLedgerMaster(),
|
||||
is->getConsumer(),
|
||||
role,
|
||||
apiVersion,
|
||||
coro,
|
||||
is,
|
||||
{is->user(), is->forwarded_for()}
|
||||
@@ -500,6 +508,9 @@ ServerHandlerImp::processSession(
|
||||
jr[jss::jsonrpc] = jv[jss::jsonrpc];
|
||||
if (jv.isMember(jss::ripplerpc))
|
||||
jr[jss::ripplerpc] = jv[jss::ripplerpc];
|
||||
if (jv.isMember(jss::api_version))
|
||||
jr[jss::api_version] = jv[jss::api_version];
|
||||
|
||||
jr[jss::type] = jss::response;
|
||||
return jr;
|
||||
}
|
||||
@@ -545,6 +556,7 @@ make_json_error(Json::Int code, Json::Value&& message)
|
||||
Json::Int constexpr method_not_found = -32601;
|
||||
Json::Int constexpr server_overloaded = -32604;
|
||||
Json::Int constexpr forbidden = -32605;
|
||||
Json::Int constexpr wrong_version = -32606;
|
||||
|
||||
void
|
||||
ServerHandlerImp::processRequest (Port const& port,
|
||||
@@ -597,11 +609,40 @@ ServerHandlerImp::processRequest (Port const& port,
|
||||
continue;
|
||||
}
|
||||
|
||||
auto apiVersion = RPC::APIVersionIfUnspecified;
|
||||
if (jsonRPC.isMember(jss::params) &&
|
||||
jsonRPC[jss::params].isArray() &&
|
||||
jsonRPC[jss::params].size() > 0 &&
|
||||
jsonRPC[jss::params][0u].isObject())
|
||||
{
|
||||
apiVersion = RPC::getAPIVersionNumber(jsonRPC[jss::params][Json::UInt(0)]);
|
||||
}
|
||||
|
||||
if ( apiVersion == RPC::APIVersionIfUnspecified && batch)
|
||||
{
|
||||
// for batch request, api_version may be at a different level
|
||||
apiVersion = RPC::getAPIVersionNumber(jsonRPC);
|
||||
}
|
||||
|
||||
if(apiVersion == RPC::APIInvalidVersion)
|
||||
{
|
||||
if (!batch)
|
||||
{
|
||||
HTTPReply (400, jss::invalid_API_version.c_str(), output, rpcJ);
|
||||
return;
|
||||
}
|
||||
Json::Value r(Json::objectValue);
|
||||
r[jss::request] = jsonRPC;
|
||||
r[jss::error] = make_json_error(wrong_version, jss::invalid_API_version.c_str());
|
||||
reply.append(r);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
auto role = Role::FORBID;
|
||||
auto required = Role::FORBID;
|
||||
if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
|
||||
required = RPC::roleRequired(jsonRPC[jss::method].asString());
|
||||
required = RPC::roleRequired(apiVersion, jsonRPC[jss::method].asString());
|
||||
|
||||
if (jsonRPC.isMember(jss::params) &&
|
||||
jsonRPC[jss::params].isArray() &&
|
||||
@@ -778,7 +819,7 @@ ServerHandlerImp::processRequest (Port const& port,
|
||||
Resource::Charge loadType = Resource::feeReferenceRPC;
|
||||
|
||||
RPC::Context context {m_journal, params, app_, loadType, m_networkOPs,
|
||||
app_.getLedgerMaster(), usage, role, coro, InfoSub::pointer(),
|
||||
app_.getLedgerMaster(), usage, role, apiVersion, coro, InfoSub::pointer(),
|
||||
{user, forwardedFor}};
|
||||
Json::Value result;
|
||||
RPC::doCommand (context, result);
|
||||
|
||||
Reference in New Issue
Block a user