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/ResponseExpirationCache.hpp"
31#include "util/log/Logger.hpp"
32#include "web/Context.hpp"
33#include "web/dosguard/DOSGuardInterface.hpp"
34
35#include <boost/asio/spawn.hpp>
36#include <boost/iterator/transform_iterator.hpp>
37#include <boost/json.hpp>
38#include <boost/json/object.hpp>
39#include <fmt/format.h>
40#include <xrpl/protocol/ErrorCodes.h>
41
42#include <chrono>
43#include <cstdint>
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<etl::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<etl::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>(
141 config, backend, balancer, dosGuard, workQueue, counters, handlerProvider
142 );
143 }
144
151 Result
153 {
154 if (forwardingProxy_.shouldForward(ctx)) {
155 // Disallow forwarding of the admin api, only user api is allowed for security reasons.
156 if (isAdminCmd(ctx.method, ctx.params))
157 return Result{Status{RippledError::rpcNO_PERMISSION}};
158
159 return forwardingProxy_.forward(ctx);
160 }
161
162 if (not ctx.isAdmin and responseCache_) {
163 if (auto res = responseCache_->get(ctx.method); res.has_value())
164 return Result{std::move(res).value()};
165 }
166
167 if (backend_->isTooBusy()) {
168 LOG(log_.error()) << "Database is too busy. Rejecting request";
169 notifyTooBusy(); // TODO: should we add ctx.method if we have it?
170 return Result{Status{RippledError::rpcTOO_BUSY}};
171 }
172
173 auto const method = handlerProvider_->getHandler(ctx.method);
174 if (!method) {
176 return Result{Status{RippledError::rpcUNKNOWN_COMMAND}};
177 }
178
179 try {
180 LOG(perfLog_.debug()) << ctx.tag() << " start executing rpc `" << ctx.method << '`';
181
182 auto const context = Context{
183 .yield = ctx.yield,
184 .session = ctx.session,
185 .isAdmin = ctx.isAdmin,
186 .clientIp = ctx.clientIp,
187 .apiVersion = ctx.apiVersion
188 };
189 auto v = (*method).process(ctx.params, context);
190
191 LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
192
193 if (not v) {
194 notifyErrored(ctx.method);
195 } else if (not ctx.isAdmin and responseCache_) {
196 responseCache_->put(ctx.method, v.result->as_object());
197 }
198
199 return Result{std::move(v)};
200 } catch (data::DatabaseTimeout const& t) {
201 LOG(log_.error()) << "Database timeout";
203
204 return Result{Status{RippledError::rpcTOO_BUSY}};
205 } catch (std::exception const& ex) {
206 LOG(log_.error()) << ctx.tag() << "Caught exception: " << ex.what();
208
209 return Result{Status{RippledError::rpcINTERNAL}};
210 }
211 }
212
221 template <typename FnType>
222 bool
223 post(FnType&& func, std::string const& ip)
224 {
225 return workQueue_.get().postCoro(
226 std::forward<FnType>(func), dosGuard_.get().isWhiteListed(ip)
227 );
228 }
229
237 void
239 web::Context const& context,
240 std::chrono::microseconds const& duration,
241 bool isForwarded
242 )
243 {
244 if (validHandler(context.method)) {
245 counters_.get().rpcComplete(context.method, duration);
246 if (not isForwarded) {
247 counters_.get().recordLedgerRequest(context.params, context.range.maxSequence);
248 }
249 }
250 }
251
258 void
259 recordLedgerMetrics(boost::json::object const& params, std::uint32_t currentLedgerSequence)
260 {
261 counters_.get().recordLedgerRequest(params, currentLedgerSequence);
262 }
263
272 void
273 notifyFailed(std::string const& method)
274 {
275 // FIXME: seems like this is not used?
276 if (validHandler(method))
277 counters_.get().rpcFailed(method);
278 }
279
287 void
288 notifyErrored(std::string const& method)
289 {
290 if (validHandler(method))
291 counters_.get().rpcErrored(method);
292 }
293
297 void
299 {
300 counters_.get().onTooBusy();
301 }
302
308 void
310 {
311 counters_.get().onNotReady();
312 }
313
317 void
319 {
320 counters_.get().onBadSyntax();
321 }
322
327 void
329 {
330 counters_.get().onUnknownCommand();
331 }
332
336 void
338 {
339 counters_.get().onInternalError();
340 }
341
342private:
343 bool
344 validHandler(std::string const& method) const
345 {
346 return handlerProvider_->contains(method) || forwardingProxy_.isProxied(method);
347 }
348
349 Result
350 buildResponseImpl(web::Context const& ctx)
351 {
352 if (backend_->isTooBusy()) {
353 LOG(log_.error()) << "Database is too busy. Rejecting request";
354 notifyTooBusy(); // TODO: should we add ctx.method if we have it?
355 return Result{Status{RippledError::rpcTOO_BUSY}};
356 }
357
358 auto const method = handlerProvider_->getHandler(ctx.method);
359 if (!method) {
361 return Result{Status{RippledError::rpcUNKNOWN_COMMAND}};
362 }
363
364 try {
365 LOG(perfLog_.debug()) << ctx.tag() << " start executing rpc `" << ctx.method << '`';
366
367 auto const context = Context{
368 .yield = ctx.yield,
369 .session = ctx.session,
370 .isAdmin = ctx.isAdmin,
371 .clientIp = ctx.clientIp,
372 .apiVersion = ctx.apiVersion
373 };
374 auto v = (*method).process(ctx.params, context);
375
376 LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
377
378 if (not v) {
379 notifyErrored(ctx.method);
380 }
381
382 return Result{std::move(v)};
383 } catch (data::DatabaseTimeout const& t) {
384 LOG(log_.error()) << "Database timeout";
386
387 return Result{Status{RippledError::rpcTOO_BUSY}};
388 } catch (std::exception const& ex) {
389 LOG(log_.error()) << ctx.tag() << "Caught exception: " << ex.what();
391
392 return Result{Status{RippledError::rpcINTERNAL}};
393 }
394 }
395};
396
397} // namespace rpc
Represents a database timeout error.
Definition BackendInterface.hpp:59
void notifyNotReady()
Notify the system that the RPC system was not ready to handle an incoming request.
Definition RPCEngine.hpp:309
void notifyInternalError()
Notify the system that the incoming request lead to an internal error (unrecoverable).
Definition RPCEngine.hpp:337
void notifyErrored(std::string const &method)
Notify the system that specified method failed due to some unrecoverable error.
Definition RPCEngine.hpp:288
void notifyUnknownCommand()
Notify the system that the incoming request specified an unknown/unsupported method/command.
Definition RPCEngine.hpp:328
static std::shared_ptr< RPCEngine > makeRPCEngine(util::config::ClioConfigDefinition const &config, std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< etl::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
void notifyBadSyntax()
Notify the system that the incoming request did not specify the RPC method/command.
Definition RPCEngine.hpp:318
void recordLedgerMetrics(boost::json::object const &params, std::uint32_t currentLedgerSequence)
Record ledger request metrics.
Definition RPCEngine.hpp:259
void notifyFailed(std::string const &method)
Notify the system that specified method failed to execute due to a recoverable user error.
Definition RPCEngine.hpp:273
void notifyComplete(web::Context const &context, std::chrono::microseconds const &duration, bool isForwarded)
Notify the system that specified method was executed and record ledger metrics.
Definition RPCEngine.hpp:238
void notifyTooBusy()
Notify the system that the RPC system is too busy to handle an incoming request.
Definition RPCEngine.hpp:298
Result buildResponse(web::Context const &ctx)
Main request processor routine.
Definition RPCEngine.hpp:152
RPCEngine(util::config::ClioConfigDefinition const &config, std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< etl::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
bool post(FnType &&func, std::string const &ip)
Used to schedule request processing onto the work queue.
Definition RPCEngine.hpp:223
An asynchronous, thread-safe queue for RPC requests.
Definition WorkQueue.hpp:62
Definition ForwardingProxy.hpp:41
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:96
Pump error(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::ERR severity.
Definition Logger.cpp:517
BaseTagDecorator const & tag() const
Getter for tag decorator.
Definition Taggable.hpp:283
All the config data will be stored and extracted from this class.
Definition ConfigDefinition.hpp:50
static std::chrono::milliseconds toMilliseconds(float value)
Method to convert a float seconds value to milliseconds.
Definition ConfigDefinition.cpp:128
T get(std::string_view fullKey) const
Returns the specified value of given string if value exists.
Definition ConfigDefinition.hpp:104
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:1614
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:84
Context that is used by the Webserver to pass around information about an incoming request.
Definition Context.hpp:41