mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-19 11:15:50 +00:00
Fix race condition and ub (#631)
This commit is contained in:
@@ -119,6 +119,7 @@ if(BUILD_TESTS)
|
||||
unittests/rpc/BaseTests.cpp
|
||||
unittests/rpc/RPCHelpersTest.cpp
|
||||
unittests/rpc/CountersTest.cpp
|
||||
unittests/rpc/AdminVerificationTest.cpp
|
||||
## RPC handlers
|
||||
unittests/rpc/handlers/DefaultProcessorTests.cpp
|
||||
unittests/rpc/handlers/TestHandlerTests.cpp
|
||||
|
||||
@@ -96,9 +96,9 @@ SimpleCache::getPredecessor(ripple::uint256 const& key, uint32_t seq) const
|
||||
std::optional<Blob>
|
||||
SimpleCache::get(ripple::uint256 const& key, uint32_t seq) const
|
||||
{
|
||||
std::shared_lock lck{mtx_};
|
||||
if (seq > latestSeq_)
|
||||
return {};
|
||||
std::shared_lock lck{mtx_};
|
||||
objectReqCounter_++;
|
||||
auto e = map_.find(key);
|
||||
if (e == map_.end())
|
||||
|
||||
@@ -56,8 +56,8 @@ struct TransactionAndMetadata
|
||||
{
|
||||
Blob transaction;
|
||||
Blob metadata;
|
||||
std::uint32_t ledgerSequence;
|
||||
std::uint32_t date;
|
||||
std::uint32_t ledgerSequence = 0;
|
||||
std::uint32_t date = 0;
|
||||
|
||||
TransactionAndMetadata() = default;
|
||||
TransactionAndMetadata(
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <etl/ReportingETL.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/RPCEngine.h>
|
||||
#include <rpc/common/impl/HandlerProvider.h>
|
||||
#include <webserver/Listener.h>
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/JS.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
170
src/rpc/RPC.cpp
170
src/rpc/RPC.cpp
@@ -18,8 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <etl/ETLSource.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/common/impl/HandlerProvider.h>
|
||||
#include <webserver/HttpBase.h>
|
||||
#include <webserver/WsBase.h>
|
||||
@@ -32,12 +31,6 @@ using namespace std;
|
||||
using namespace clio;
|
||||
using namespace RPC;
|
||||
|
||||
// local to compilation unit loggers
|
||||
namespace {
|
||||
clio::Logger gPerfLog{"Performance"};
|
||||
clio::Logger gLog{"RPC"};
|
||||
} // namespace
|
||||
|
||||
namespace RPC {
|
||||
|
||||
optional<Web::Context>
|
||||
@@ -92,165 +85,4 @@ make_HttpContext(
|
||||
return make_optional<Web::Context>(yc, command, 1, array.at(0).as_object(), nullptr, tagFactory, range, clientIp);
|
||||
}
|
||||
|
||||
static unordered_set<string> forwardCommands{
|
||||
"submit",
|
||||
"submit_multisigned",
|
||||
"fee",
|
||||
"ledger_closed",
|
||||
"ledger_current",
|
||||
"ripple_path_find",
|
||||
"manifest",
|
||||
"channel_authorize",
|
||||
"channel_verify"};
|
||||
|
||||
RPCEngine::RPCEngine(
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider)
|
||||
: backend_{backend}
|
||||
, subscriptions_{subscriptions}
|
||||
, balancer_{balancer}
|
||||
, dosGuard_{std::cref(dosGuard)}
|
||||
, workQueue_{std::ref(workQueue)}
|
||||
, counters_{std::ref(counters)}
|
||||
, handlerTable_{handlerProvider}
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<RPCEngine>
|
||||
RPCEngine::make_RPCEngine(
|
||||
clio::Config const& config,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider)
|
||||
{
|
||||
return std::make_shared<RPCEngine>(
|
||||
backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider);
|
||||
}
|
||||
|
||||
bool
|
||||
RPCEngine::validHandler(string const& method) const
|
||||
{
|
||||
return handlerTable_.contains(method) || forwardCommands.contains(method);
|
||||
}
|
||||
|
||||
bool
|
||||
RPCEngine::isClioOnly(string const& method) const
|
||||
{
|
||||
return handlerTable_.isClioOnly(method);
|
||||
}
|
||||
|
||||
bool
|
||||
RPCEngine::shouldForwardToRippled(Web::Context const& ctx) const
|
||||
{
|
||||
auto request = ctx.params;
|
||||
|
||||
if (isClioOnly(ctx.method))
|
||||
return false;
|
||||
|
||||
if (forwardCommands.find(ctx.method) != forwardCommands.end())
|
||||
return true;
|
||||
|
||||
if (specifiesCurrentOrClosedLedger(request))
|
||||
return true;
|
||||
|
||||
if (ctx.method == "account_info" && request.contains("queue") && request.at("queue").as_bool())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Result
|
||||
RPCEngine::buildResponse(Web::Context const& ctx)
|
||||
{
|
||||
if (shouldForwardToRippled(ctx))
|
||||
{
|
||||
auto toForward = ctx.params;
|
||||
toForward["command"] = ctx.method;
|
||||
|
||||
auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
|
||||
notifyForwarded(ctx.method);
|
||||
|
||||
if (!res)
|
||||
return Status{RippledError::rpcFAILED_TO_FORWARD};
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
if (backend_->isTooBusy())
|
||||
{
|
||||
gLog.error() << "Database is too busy. Rejecting request";
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
}
|
||||
|
||||
auto const method = handlerTable_.getHandler(ctx.method);
|
||||
if (!method)
|
||||
return Status{RippledError::rpcUNKNOWN_COMMAND};
|
||||
|
||||
try
|
||||
{
|
||||
gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method << '`';
|
||||
|
||||
auto const isAdmin = ctx.clientIp == "127.0.0.1"; // TODO: this should be a strategy
|
||||
auto const context = Context{ctx.yield, ctx.session, isAdmin, ctx.clientIp};
|
||||
auto const v = (*method).process(ctx.params, context);
|
||||
|
||||
gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
|
||||
|
||||
if (v)
|
||||
return v->as_object();
|
||||
else
|
||||
return Status{v.error()};
|
||||
}
|
||||
catch (InvalidParamsError const& err)
|
||||
{
|
||||
return Status{RippledError::rpcINVALID_PARAMS, err.what()};
|
||||
}
|
||||
catch (AccountNotFoundError const& err)
|
||||
{
|
||||
return Status{RippledError::rpcACT_NOT_FOUND, err.what()};
|
||||
}
|
||||
catch (Backend::DatabaseTimeout const& t)
|
||||
{
|
||||
gLog.error() << "Database timeout";
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
}
|
||||
catch (exception const& err)
|
||||
{
|
||||
gLog.error() << ctx.tag() << " caught exception: " << err.what();
|
||||
return Status{RippledError::rpcINTERNAL};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RPCEngine::notifyComplete(std::string const& method, std::chrono::microseconds const& duration)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcComplete(method, duration);
|
||||
}
|
||||
|
||||
void
|
||||
RPCEngine::notifyErrored(std::string const& method)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcErrored(method);
|
||||
}
|
||||
|
||||
void
|
||||
RPCEngine::notifyForwarded(std::string const& method)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcForwarded(method);
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
150
src/rpc/RPC.h
150
src/rpc/RPC.h
@@ -20,34 +20,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <config/Config.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/Errors.h>
|
||||
#include <rpc/HandlerTable.h>
|
||||
#include <rpc/common/AnyHandler.h>
|
||||
#include <util/Taggable.h>
|
||||
#include <webserver/Context.h>
|
||||
#include <webserver/DOSGuard.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
/*
|
||||
* This file contains various classes necessary for executing RPC handlers.
|
||||
* Context gives the handlers access to various other parts of the application
|
||||
* Status is used to report errors.
|
||||
* And lastly, there are various functions for making Contexts, Statuses and
|
||||
* serializing Status to JSON.
|
||||
* This file is meant to contain any class or function that code outside of the
|
||||
* rpc folder needs to use. For helper functions or classes used within the rpc
|
||||
* folder, use RPCHelpers.h.
|
||||
* Context gives the handlers access to various other parts of the application Status is used to report errors.
|
||||
* And lastly, there are various functions for making Contexts, Statuses and serializing Status to JSON.
|
||||
* This file is meant to contain any class or function that code outside of the rpc folder needs to use. For helper
|
||||
* functions or classes used within the rpc folder, use RPCHelpers.h.
|
||||
*/
|
||||
|
||||
class WsBase;
|
||||
@@ -57,26 +47,6 @@ class ReportingETL;
|
||||
|
||||
namespace RPC {
|
||||
|
||||
struct AccountCursor
|
||||
{
|
||||
ripple::uint256 index;
|
||||
std::uint32_t hint;
|
||||
|
||||
std::string
|
||||
toString() const
|
||||
{
|
||||
return ripple::strHex(index) + "," + std::to_string(hint);
|
||||
}
|
||||
|
||||
bool
|
||||
isNonZero() const
|
||||
{
|
||||
return index.isNonZero() || hint != 0;
|
||||
}
|
||||
};
|
||||
|
||||
using Result = std::variant<Status, boost::json::object>;
|
||||
|
||||
std::optional<Web::Context>
|
||||
make_WsContext(
|
||||
boost::asio::yield_context& yc,
|
||||
@@ -94,114 +64,4 @@ make_HttpContext(
|
||||
Backend::LedgerRange const& range,
|
||||
std::string const& clientIp);
|
||||
|
||||
/**
|
||||
* @brief The RPC engine that ties all RPC-related functionality together
|
||||
*/
|
||||
class RPCEngine
|
||||
{
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManager> subscriptions_;
|
||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||
std::reference_wrapper<clio::DOSGuard const> dosGuard_;
|
||||
std::reference_wrapper<WorkQueue> workQueue_;
|
||||
std::reference_wrapper<Counters> counters_;
|
||||
|
||||
HandlerTable handlerTable_;
|
||||
|
||||
public:
|
||||
RPCEngine(
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider);
|
||||
|
||||
static std::shared_ptr<RPCEngine>
|
||||
make_RPCEngine(
|
||||
clio::Config const& config,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider);
|
||||
|
||||
/**
|
||||
* @brief Main request processor routine
|
||||
* @param ctx The @ref Context of the request
|
||||
*/
|
||||
Result
|
||||
buildResponse(Web::Context const& ctx);
|
||||
|
||||
/**
|
||||
* @brief Used to schedule request processing onto the work queue
|
||||
* @param func The lambda to execute when this request is handled
|
||||
* @param ip The ip address for which this request is being executed
|
||||
*/
|
||||
template <typename Fn>
|
||||
bool
|
||||
post(Fn&& func, std::string const& ip)
|
||||
{
|
||||
return workQueue_.get().postCoro(std::forward<Fn>(func), dosGuard_.get().isWhiteListed(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method was executed
|
||||
* @param method
|
||||
* @param duration The time it took to execute the method specified in
|
||||
* microseconds
|
||||
*/
|
||||
void
|
||||
notifyComplete(std::string const& method, std::chrono::microseconds const& duration);
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method failed to execute
|
||||
* @param method
|
||||
*/
|
||||
void
|
||||
notifyErrored(std::string const& method);
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method execution was forwarded to rippled
|
||||
* @param method
|
||||
*/
|
||||
void
|
||||
notifyForwarded(std::string const& method);
|
||||
|
||||
private:
|
||||
bool
|
||||
shouldForwardToRippled(Web::Context const& ctx) const;
|
||||
|
||||
bool
|
||||
isClioOnly(std::string const& method) const;
|
||||
|
||||
bool
|
||||
validHandler(std::string const& method) const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void
|
||||
logDuration(Web::Context const& ctx, T const& dur)
|
||||
{
|
||||
using boost::json::serialize;
|
||||
|
||||
static clio::Logger log{"RPC"};
|
||||
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
|
||||
auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
|
||||
auto const msg =
|
||||
fmt::format("Request processing duration = {} milliseconds. request = {}", millis, serialize(ctx.params));
|
||||
|
||||
if (seconds > 10)
|
||||
log.error() << ctx.tag() << msg;
|
||||
else if (seconds > 1)
|
||||
log.warn() << ctx.tag() << msg;
|
||||
else
|
||||
log.info() << ctx.tag() << msg;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
275
src/rpc/RPCEngine.h
Normal file
275
src/rpc/RPCEngine.h
Normal file
@@ -0,0 +1,275 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2022, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <config/Config.h>
|
||||
#include <etl/ETLSource.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/Errors.h>
|
||||
#include <rpc/HandlerTable.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/AnyHandler.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/impl/AdminVerificationStrategy.h>
|
||||
#include <util/Taggable.h>
|
||||
#include <webserver/Context.h>
|
||||
#include <webserver/DOSGuard.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
class WsBase;
|
||||
class SubscriptionManager;
|
||||
class ETLLoadBalancer;
|
||||
class ReportingETL;
|
||||
|
||||
namespace RPC {
|
||||
|
||||
/**
|
||||
* @brief The RPC engine that ties all RPC-related functionality together
|
||||
*/
|
||||
template <typename AdminVerificationStrategyType>
|
||||
class RPCEngineBase
|
||||
{
|
||||
clio::Logger perfLog_{"Performance"};
|
||||
clio::Logger log_{"RPC"};
|
||||
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManager> subscriptions_;
|
||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||
std::reference_wrapper<clio::DOSGuard const> dosGuard_;
|
||||
std::reference_wrapper<WorkQueue> workQueue_;
|
||||
std::reference_wrapper<Counters> counters_;
|
||||
|
||||
HandlerTable handlerTable_;
|
||||
AdminVerificationStrategyType adminVerifier_;
|
||||
|
||||
public:
|
||||
RPCEngineBase(
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider)
|
||||
: backend_{backend}
|
||||
, subscriptions_{subscriptions}
|
||||
, balancer_{balancer}
|
||||
, dosGuard_{std::cref(dosGuard)}
|
||||
, workQueue_{std::ref(workQueue)}
|
||||
, counters_{std::ref(counters)}
|
||||
, handlerTable_{handlerProvider}
|
||||
{
|
||||
}
|
||||
|
||||
static std::shared_ptr<RPCEngineBase>
|
||||
make_RPCEngine(
|
||||
clio::Config const& config,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<ReportingETL> const& etl,
|
||||
clio::DOSGuard const& dosGuard,
|
||||
WorkQueue& workQueue,
|
||||
Counters& counters,
|
||||
std::shared_ptr<HandlerProvider const> const& handlerProvider)
|
||||
{
|
||||
return std::make_shared<RPCEngineBase>(
|
||||
backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main request processor routine
|
||||
* @param ctx The @ref Context of the request
|
||||
*/
|
||||
Result
|
||||
buildResponse(Web::Context const& ctx)
|
||||
{
|
||||
if (shouldForwardToRippled(ctx))
|
||||
{
|
||||
auto toForward = ctx.params;
|
||||
toForward["command"] = ctx.method;
|
||||
|
||||
auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
|
||||
notifyForwarded(ctx.method);
|
||||
|
||||
if (!res)
|
||||
return Status{RippledError::rpcFAILED_TO_FORWARD};
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
if (backend_->isTooBusy())
|
||||
{
|
||||
log_.error() << "Database is too busy. Rejecting request";
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
}
|
||||
|
||||
auto const method = handlerTable_.getHandler(ctx.method);
|
||||
if (!method)
|
||||
return Status{RippledError::rpcUNKNOWN_COMMAND};
|
||||
|
||||
try
|
||||
{
|
||||
perfLog_.debug() << ctx.tag() << " start executing rpc `" << ctx.method << '`';
|
||||
|
||||
auto const isAdmin = adminVerifier_.isAdmin(ctx.clientIp);
|
||||
auto const context = Context{ctx.yield, ctx.session, isAdmin, ctx.clientIp};
|
||||
auto const v = (*method).process(ctx.params, context);
|
||||
|
||||
perfLog_.debug() << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
|
||||
|
||||
if (v)
|
||||
return v->as_object();
|
||||
else
|
||||
return Status{v.error()};
|
||||
}
|
||||
catch (InvalidParamsError const& err)
|
||||
{
|
||||
return Status{RippledError::rpcINVALID_PARAMS, err.what()};
|
||||
}
|
||||
catch (AccountNotFoundError const& err)
|
||||
{
|
||||
return Status{RippledError::rpcACT_NOT_FOUND, err.what()};
|
||||
}
|
||||
catch (Backend::DatabaseTimeout const& t)
|
||||
{
|
||||
log_.error() << "Database timeout";
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
}
|
||||
catch (std::exception const& err)
|
||||
{
|
||||
log_.error() << ctx.tag() << " caught exception: " << err.what();
|
||||
return Status{RippledError::rpcINTERNAL};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Used to schedule request processing onto the work queue
|
||||
* @param func The lambda to execute when this request is handled
|
||||
* @param ip The ip address for which this request is being executed
|
||||
*/
|
||||
template <typename Fn>
|
||||
bool
|
||||
post(Fn&& func, std::string const& ip)
|
||||
{
|
||||
return workQueue_.get().postCoro(std::forward<Fn>(func), dosGuard_.get().isWhiteListed(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method was executed
|
||||
* @param method
|
||||
* @param duration The time it took to execute the method specified in
|
||||
* microseconds
|
||||
*/
|
||||
void
|
||||
notifyComplete(std::string const& method, std::chrono::microseconds const& duration)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcComplete(method, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method failed to execute
|
||||
* @param method
|
||||
*/
|
||||
void
|
||||
notifyErrored(std::string const& method)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcErrored(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify the system that specified method execution was forwarded to rippled
|
||||
* @param method
|
||||
*/
|
||||
void
|
||||
notifyForwarded(std::string const& method)
|
||||
{
|
||||
if (validHandler(method))
|
||||
counters_.get().rpcForwarded(method);
|
||||
}
|
||||
|
||||
private:
|
||||
bool
|
||||
shouldForwardToRippled(Web::Context const& ctx) const
|
||||
{
|
||||
auto request = ctx.params;
|
||||
|
||||
if (isClioOnly(ctx.method))
|
||||
return false;
|
||||
|
||||
if (isForwardCommand(ctx.method))
|
||||
return true;
|
||||
|
||||
if (specifiesCurrentOrClosedLedger(request))
|
||||
return true;
|
||||
|
||||
if (ctx.method == "account_info" && request.contains("queue") && request.at("queue").as_bool())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isForwardCommand(std::string const& method) const
|
||||
{
|
||||
static std::unordered_set<std::string> const FORWARD_COMMANDS{
|
||||
"submit",
|
||||
"submit_multisigned",
|
||||
"fee",
|
||||
"ledger_closed",
|
||||
"ledger_current",
|
||||
"ripple_path_find",
|
||||
"manifest",
|
||||
"channel_authorize",
|
||||
"channel_verify",
|
||||
};
|
||||
|
||||
return FORWARD_COMMANDS.contains(method);
|
||||
}
|
||||
|
||||
bool
|
||||
isClioOnly(std::string const& method) const
|
||||
{
|
||||
return handlerTable_.isClioOnly(method);
|
||||
}
|
||||
|
||||
bool
|
||||
validHandler(std::string const& method) const
|
||||
{
|
||||
return handlerTable_.contains(method) || isForwardCommand(method);
|
||||
}
|
||||
};
|
||||
|
||||
using RPCEngine = RPCEngineBase<detail::IPAdminVerificationStrategy>;
|
||||
|
||||
} // namespace RPC
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Errors.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <util/Profiler.h>
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* This file contains a variety of utility functions used when executing
|
||||
* the handlers
|
||||
* This file contains a variety of utility functions used when executing the handlers.
|
||||
*/
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
@@ -30,9 +29,11 @@
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/JS.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <webserver/Context.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
@@ -281,4 +282,24 @@ traverseTransactions(
|
||||
std::optional<Backend::TransactionsCursor> const&,
|
||||
boost::asio::yield_context& yield)> transactionFetcher);
|
||||
|
||||
template <class T>
|
||||
void
|
||||
logDuration(Web::Context const& ctx, T const& dur)
|
||||
{
|
||||
using boost::json::serialize;
|
||||
|
||||
static clio::Logger log{"RPC"};
|
||||
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
|
||||
auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
|
||||
auto const msg =
|
||||
fmt::format("Request processing duration = {} milliseconds. request = {}", millis, serialize(ctx.params));
|
||||
|
||||
if (seconds > 10)
|
||||
log.error() << ctx.tag() << msg;
|
||||
else if (seconds > 1)
|
||||
log.warn() << ctx.tag() << msg;
|
||||
else
|
||||
log.info() << ctx.tag() << msg;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <rpc/Errors.h>
|
||||
#include <util/Expected.h>
|
||||
|
||||
#include <ripple/basics/base_uint.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
@@ -71,6 +73,26 @@ struct Context
|
||||
std::string clientIp;
|
||||
};
|
||||
|
||||
using Result = std::variant<Status, boost::json::object>;
|
||||
|
||||
struct AccountCursor
|
||||
{
|
||||
ripple::uint256 index;
|
||||
std::uint32_t hint;
|
||||
|
||||
std::string
|
||||
toString() const
|
||||
{
|
||||
return ripple::strHex(index) + "," + std::to_string(hint);
|
||||
}
|
||||
|
||||
bool
|
||||
isNonZero() const
|
||||
{
|
||||
return index.isNonZero() || hint != 0;
|
||||
}
|
||||
};
|
||||
|
||||
class AnyHandler;
|
||||
|
||||
class HandlerProvider
|
||||
|
||||
42
src/rpc/common/impl/AdminVerificationStrategy.h
Normal file
42
src/rpc/common/impl/AdminVerificationStrategy.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace RPC::detail {
|
||||
|
||||
class IPAdminVerificationStrategy final
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Checks whether request is from a host that is considered authorized as admin.
|
||||
*
|
||||
* @param ip The ip addr of the client
|
||||
* @return true if authorized; false otherwise
|
||||
*/
|
||||
bool
|
||||
isAdmin(std::string_view ip) const
|
||||
{
|
||||
return ip == "127.0.0.1";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace RPC::detail
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/handlers/AccountLines.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/handlers/NoRippleCheck.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <main/Build.h>
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/RPCEngine.h>
|
||||
#include <rpc/WorkQueue.h>
|
||||
#include <util/Profiler.h>
|
||||
#include <util/Taggable.h>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/RPC.h>
|
||||
#include <rpc/RPCEngine.h>
|
||||
#include <rpc/WorkQueue.h>
|
||||
#include <subscriptions/Message.h>
|
||||
#include <subscriptions/SubscriptionManager.h>
|
||||
|
||||
40
unittests/rpc/AdminVerificationTest.cpp
Normal file
40
unittests/rpc/AdminVerificationTest.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/Fixtures.h>
|
||||
|
||||
#include <rpc/common/impl/AdminVerificationStrategy.h>
|
||||
|
||||
#include <boost/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class RPCAdminVerificationTest : public NoLoggerFixture
|
||||
{
|
||||
protected:
|
||||
RPC::detail::IPAdminVerificationStrategy strat_;
|
||||
};
|
||||
|
||||
TEST_F(RPCAdminVerificationTest, IsAdminOnlyForIP_127_0_0_1)
|
||||
{
|
||||
EXPECT_TRUE(strat_.isAdmin("127.0.0.1"));
|
||||
EXPECT_FALSE(strat_.isAdmin("127.0.0.2"));
|
||||
EXPECT_FALSE(strat_.isAdmin("127"));
|
||||
EXPECT_FALSE(strat_.isAdmin(""));
|
||||
EXPECT_FALSE(strat_.isAdmin("localhost"));
|
||||
}
|
||||
Reference in New Issue
Block a user