Clio develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
RPCEngine.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2022, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include "data/BackendInterface.hpp"
24#include "rpc/Errors.hpp"
25#include "rpc/RPCHelpers.hpp"
26#include "rpc/WorkQueue.hpp"
27#include "rpc/common/HandlerProvider.hpp"
28#include "rpc/common/Types.hpp"
29#include "rpc/common/impl/ForwardingProxy.hpp"
30#include "util/OverloadSet.hpp"
31#include "util/ResponseExpirationCache.hpp"
32#include "util/log/Logger.hpp"
33#include "web/Context.hpp"
34#include "web/dosguard/DOSGuardInterface.hpp"
35
36#include <boost/asio/spawn.hpp>
37#include <boost/iterator/transform_iterator.hpp>
38#include <boost/json.hpp>
39#include <boost/json/object.hpp>
40#include <fmt/format.h>
41#include <xrpl/protocol/ErrorCodes.h>
42
43#include <chrono>
44#include <exception>
45#include <functional>
46#include <memory>
47#include <optional>
48#include <string>
49#include <unordered_set>
50#include <utility>
51
55namespace rpc {
56
60template <typename CountersType>
61class RPCEngine {
62 util::Logger perfLog_{"Performance"};
63 util::Logger log_{"RPC"};
64
65 std::shared_ptr<BackendInterface> backend_;
66 std::reference_wrapper<web::dosguard::DOSGuardInterface const> dosGuard_;
67 std::reference_wrapper<WorkQueue> workQueue_;
68 std::reference_wrapper<CountersType> counters_;
69
70 std::shared_ptr<HandlerProvider const> handlerProvider_;
71
73
74 std::optional<util::ResponseExpirationCache> responseCache_;
75
76public:
90 std::shared_ptr<BackendInterface> const& backend,
91 std::shared_ptr<etlng::LoadBalancerInterface> const& balancer,
93 WorkQueue& workQueue,
94 CountersType& counters,
95 std::shared_ptr<HandlerProvider const> const& handlerProvider
96 )
97 : backend_{backend}
98 , dosGuard_{std::cref(dosGuard)}
99 , workQueue_{std::ref(workQueue)}
100 , counters_{std::ref(counters)}
101 , handlerProvider_{handlerProvider}
102 , forwardingProxy_{balancer, counters, handlerProvider}
103 {
104 // Let main thread catch the exception if config type is wrong
105 auto const cacheTimeout = config.get<float>("rpc.cache_timeout");
106
107 if (cacheTimeout > 0.f) {
108 LOG(log_.info()) << fmt::format("Init RPC Cache, timeout: {} seconds", cacheTimeout);
109
110 responseCache_.emplace(
112 std::unordered_set<std::string>{"server_info"}
113 );
114 }
115 }
116
129 static std::shared_ptr<RPCEngine>
132 std::shared_ptr<BackendInterface> const& backend,
133 std::shared_ptr<etlng::LoadBalancerInterface> const& balancer,
134 web::dosguard::DOSGuardInterface const& dosGuard,
135 WorkQueue& workQueue,
136 CountersType& counters,
137 std::shared_ptr<HandlerProvider const> const& handlerProvider
138 )
139 {
140 return std::make_shared<RPCEngine>(config, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
141 }
142
149 Result
151 {
152 if (forwardingProxy_.shouldForward(ctx)) {
153 // Disallow forwarding of the admin api, only user api is allowed for security reasons.
154 if (isAdminCmd(ctx.method, ctx.params))
155 return Result{Status{RippledError::rpcNO_PERMISSION}};
156
157 return forwardingProxy_.forward(ctx);
158 }
159
160 if (not ctx.isAdmin and responseCache_) {
161 if (auto res = responseCache_->get(ctx.method); res.has_value())
162 return Result{std::move(res).value()};
163 }
164
165 if (backend_->isTooBusy()) {
166 LOG(log_.error()) << "Database is too busy. Rejecting request";
167 notifyTooBusy(); // TODO: should we add ctx.method if we have it?
168 return Result{Status{RippledError::rpcTOO_BUSY}};
169 }
170
171 auto const method = handlerProvider_->getHandler(ctx.method);
172 if (!method) {
174 return Result{Status{RippledError::rpcUNKNOWN_COMMAND}};
175 }
176
177 try {
178 LOG(perfLog_.debug()) << ctx.tag() << " start executing rpc `" << ctx.method << '`';
179
180 auto const context = Context{
181 .yield = ctx.yield,
182 .session = ctx.session,
183 .isAdmin = ctx.isAdmin,
184 .clientIp = ctx.clientIp,
185 .apiVersion = ctx.apiVersion
186 };
187 auto v = (*method).process(ctx.params, context);
188
189 LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
190
191 if (not v) {
192 notifyErrored(ctx.method);
193 } else if (not ctx.isAdmin and responseCache_) {
194 responseCache_->put(ctx.method, v.result->as_object());
195 }
196
197 return Result{std::move(v)};
198 } catch (data::DatabaseTimeout const& t) {
199 LOG(log_.error()) << "Database timeout";
201
202 return Result{Status{RippledError::rpcTOO_BUSY}};
203 } catch (std::exception const& ex) {
204 LOG(log_.error()) << ctx.tag() << "Caught exception: " << ex.what();
206
207 return Result{Status{RippledError::rpcINTERNAL}};
208 }
209 }
210
219 template <typename FnType>
220 bool
221 post(FnType&& func, std::string const& ip)
222 {
223 return workQueue_.get().postCoro(std::forward<FnType>(func), dosGuard_.get().isWhiteListed(ip));
224 }
225
232 void
233 notifyComplete(std::string const& method, std::chrono::microseconds const& duration)
234 {
235 if (validHandler(method))
236 counters_.get().rpcComplete(method, duration);
237 }
238
246 void
247 notifyFailed(std::string const& method)
248 {
249 // FIXME: seems like this is not used?
250 if (validHandler(method))
251 counters_.get().rpcFailed(method);
252 }
253
261 void
262 notifyErrored(std::string const& method)
263 {
264 if (validHandler(method))
265 counters_.get().rpcErrored(method);
266 }
267
271 void
273 {
274 counters_.get().onTooBusy();
275 }
276
282 void
284 {
285 counters_.get().onNotReady();
286 }
287
291 void
293 {
294 counters_.get().onBadSyntax();
295 }
296
300 void
302 {
303 counters_.get().onUnknownCommand();
304 }
305
309 void
311 {
312 counters_.get().onInternalError();
313 }
314
315private:
316 bool
317 validHandler(std::string const& method) const
318 {
319 return handlerProvider_->contains(method) || forwardingProxy_.isProxied(method);
320 }
321
322 Result
323 buildResponseImpl(web::Context const& ctx)
324 {
325 if (backend_->isTooBusy()) {
326 LOG(log_.error()) << "Database is too busy. Rejecting request";
327 notifyTooBusy(); // TODO: should we add ctx.method if we have it?
328 return Result{Status{RippledError::rpcTOO_BUSY}};
329 }
330
331 auto const method = handlerProvider_->getHandler(ctx.method);
332 if (!method) {
334 return Result{Status{RippledError::rpcUNKNOWN_COMMAND}};
335 }
336
337 try {
338 LOG(perfLog_.debug()) << ctx.tag() << " start executing rpc `" << ctx.method << '`';
339
340 auto const context = Context{
341 .yield = ctx.yield,
342 .session = ctx.session,
343 .isAdmin = ctx.isAdmin,
344 .clientIp = ctx.clientIp,
345 .apiVersion = ctx.apiVersion
346 };
347 auto v = (*method).process(ctx.params, context);
348
349 LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
350
351 if (not v) {
352 notifyErrored(ctx.method);
353 }
354
355 return Result{std::move(v)};
356 } catch (data::DatabaseTimeout const& t) {
357 LOG(log_.error()) << "Database timeout";
359
360 return Result{Status{RippledError::rpcTOO_BUSY}};
361 } catch (std::exception const& ex) {
362 LOG(log_.error()) << ctx.tag() << "Caught exception: " << ex.what();
364
365 return Result{Status{RippledError::rpcINTERNAL}};
366 }
367 }
368};
369
370} // namespace rpc
Represents a database timeout error.
Definition BackendInterface.hpp:59
The RPC engine that ties all RPC-related functionality together.
Definition RPCEngine.hpp:61
void notifyNotReady()
Notify the system that the RPC system was not ready to handle an incoming request.
Definition RPCEngine.hpp:283
RPCEngine(util::config::ClioConfigDefinition const &config, std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< etlng::LoadBalancerInterface > const &balancer, web::dosguard::DOSGuardInterface const &dosGuard, WorkQueue &workQueue, CountersType &counters, std::shared_ptr< HandlerProvider const > const &handlerProvider)
Construct a new RPCEngine object.
Definition RPCEngine.hpp:88
void notifyInternalError()
Notify the system that the incoming request lead to an internal error (unrecoverable).
Definition RPCEngine.hpp:310
void notifyErrored(std::string const &method)
Notify the system that specified method failed due to some unrecoverable error.
Definition RPCEngine.hpp:262
void notifyUnknownCommand()
Notify the system that the incoming request specified an unknown/unsupported method/command.
Definition RPCEngine.hpp:301
void notifyBadSyntax()
Notify the system that the incoming request did not specify the RPC method/command.
Definition RPCEngine.hpp:292
void notifyFailed(std::string const &method)
Notify the system that specified method failed to execute due to a recoverable user error.
Definition RPCEngine.hpp:247
void notifyTooBusy()
Notify the system that the RPC system is too busy to handle an incoming request.
Definition RPCEngine.hpp:272
void notifyComplete(std::string const &method, std::chrono::microseconds const &duration)
Notify the system that specified method was executed.
Definition RPCEngine.hpp:233
Result buildResponse(web::Context const &ctx)
Main request processor routine.
Definition RPCEngine.hpp:150
static std::shared_ptr< RPCEngine > makeRPCEngine(util::config::ClioConfigDefinition const &config, std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< etlng::LoadBalancerInterface > const &balancer, web::dosguard::DOSGuardInterface const &dosGuard, WorkQueue &workQueue, CountersType &counters, std::shared_ptr< HandlerProvider const > const &handlerProvider)
Factory function to create a new instance of the RPC engine.
Definition RPCEngine.hpp:130
bool post(FnType &&func, std::string const &ip)
Used to schedule request processing onto the work queue.
Definition RPCEngine.hpp:221
An asynchronous, thread-safe queue for RPC requests.
Definition WorkQueue.hpp:48
Definition ForwardingProxy.hpp:41
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:111
Pump error(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::ERR severity.
Definition Logger.cpp:322
Pump debug(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::DBG severity.
Definition Logger.cpp:307
Pump info(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::NFO severity.
Definition Logger.cpp:312
BaseTagDecorator const & tag() const
Getter for tag decorator.
Definition Taggable.hpp:280
All the config data will be stored and extracted from this class.
Definition ConfigDefinition.hpp:54
static std::chrono::milliseconds toMilliseconds(float value)
Method to convert a float seconds value to milliseconds.
Definition ConfigDefinition.cpp:121
T get(std::string_view fullKey) const
Returns the specified value of given string if value exists.
Definition ConfigDefinition.hpp:108
The interface of a denial of service guard.
Definition DOSGuardInterface.hpp:46
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:37
bool isAdminCmd(std::string const &method, boost::json::object const &request)
Check whether a request requires administrative privileges on rippled side.
Definition RPCHelpers.cpp:1518
Context of an RPC call.
Definition Types.hpp:118
Result type used to return responses or error statuses to the Webserver subsystem.
Definition Types.hpp:129
A status returned from any RPC handler.
Definition Errors.hpp:83
Context that is used by the Webserver to pass around information about an incoming request.
Definition Context.hpp:40