Support API versioning

This commit is contained in:
Peng Wang
2019-11-11 10:48:51 -05:00
committed by Nik Bougalis
parent 79e9085dd1
commit 2aa11fa41d
16 changed files with 714 additions and 53 deletions

View File

@@ -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*>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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);