Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
ServerInfo.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2023, 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"
23#include "data/DBHelpers.hpp"
24#include "etl/ETLServiceInterface.hpp"
26#include "feed/SubscriptionManagerInterface.hpp"
27#include "rpc/Errors.hpp"
28#include "rpc/JS.hpp"
29#include "rpc/common/Specs.hpp"
30#include "rpc/common/Types.hpp"
31#include "util/Assert.hpp"
32#include "util/build/Build.hpp"
33
34#include <boost/json/conversion.hpp>
35#include <boost/json/object.hpp>
36#include <boost/json/value.hpp>
37#include <fmt/format.h>
38#include <xrpl/basics/chrono.h>
39#include <xrpl/basics/strHex.h>
40#include <xrpl/protocol/BuildInfo.h>
41#include <xrpl/protocol/ErrorCodes.h>
42#include <xrpl/protocol/Fees.h>
43#include <xrpl/protocol/Protocol.h>
44#include <xrpl/protocol/jss.h>
45
46#include <chrono>
47#include <cstddef>
48#include <cstdint>
49#include <functional>
50#include <memory>
51#include <optional>
52#include <string>
53
54namespace rpc {
55class Counters;
56} // namespace rpc
57
58namespace rpc {
59
65template <typename CountersType>
67 static constexpr auto kBACKEND_COUNTERS_KEY = "backend_counters";
68
69 std::shared_ptr<BackendInterface> backend_;
70 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
71 std::shared_ptr<etl::LoadBalancerInterface> balancer_;
72 std::shared_ptr<etl::ETLServiceInterface const> etl_;
73 std::reference_wrapper<CountersType const> counters_;
74
75public:
79 struct Input {
80 bool backendCounters = false;
81 };
82
86 struct AdminSection {
87 boost::json::object counters;
88 std::optional<boost::json::object> backendCounters;
89 boost::json::object subscriptions;
90 boost::json::object etl;
91 };
92
97 uint32_t age = 0;
98 std::string hash;
99 ripple::LedgerIndex seq = {};
100 std::optional<ripple::Fees> fees = std::nullopt;
101 };
102
107 std::size_t size = 0;
108 bool isEnabled = false;
109 bool isFull = false;
110 ripple::LedgerIndex latestLedgerSeq = {};
111 float objectHitRate = 1.0;
112 float successorHitRate = 1.0;
113 };
114
118 struct InfoSection {
119 std::optional<AdminSection> adminSection = std::nullopt;
120 std::string completeLedgers;
121 uint32_t loadFactor = 1u;
122 std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
123 std::chrono::seconds uptime = {};
124 std::string clioVersion = util::build::getClioVersionString();
125 std::string xrplVersion = ripple::BuildInfo::getVersionString();
126 std::optional<boost::json::object> rippledInfo = std::nullopt;
127 ValidatedLedgerSection validatedLedger = {};
128 CacheSection cache = {};
129 bool isAmendmentBlocked = false;
130 bool isCorruptionDetected = false;
131 };
132
136 struct Output {
137 InfoSection info = {};
138
139 // validated should be sent via framework
140 bool validated = true;
141 };
142
143 using Result = HandlerReturnType<Output>;
144
155 std::shared_ptr<BackendInterface> const& backend,
156 std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions,
157 std::shared_ptr<etl::LoadBalancerInterface> const& balancer,
158 std::shared_ptr<etl::ETLServiceInterface const> const& etl,
159 CountersType const& counters
160 )
161 : backend_(backend)
162 , subscriptions_(subscriptions)
163 , balancer_(balancer)
164 , etl_(etl)
165 , counters_(std::cref(counters))
166 {
167 }
168
175 static RpcSpecConstRef
176 spec([[maybe_unused]] uint32_t apiVersion)
177 {
178 static RpcSpec const kRPC_SPEC = {};
179 return kRPC_SPEC;
180 }
181
189 Result
190 process(Input const& input, Context const& ctx) const
191 {
192 using namespace rpc;
193 using namespace std::chrono;
194
195 auto const range = backend_->fetchLedgerRange();
196 ASSERT(range.has_value(), "ServerInfo's ledger range must be available");
197
198 auto const lgrInfo = backend_->fetchLedgerBySequence(range->maxSequence, ctx.yield);
199 if (not lgrInfo.has_value())
200 return Error{Status{RippledError::rpcINTERNAL}};
201
202 auto const fees = backend_->fetchFees(lgrInfo->seq, ctx.yield);
203 if (not fees.has_value())
204 return Error{Status{RippledError::rpcINTERNAL}};
205
206 auto output = Output{};
207 auto const sinceEpoch =
208 duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
209 auto const age = static_cast<int32_t>(sinceEpoch) -
210 static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
211 static_cast<int32_t>(kRIPPLE_EPOCH_START);
212
213 output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
214
215 if (ctx.isAdmin) {
216 output.info.adminSection = {
217 .counters = counters_.get().report(),
218 .backendCounters =
219 input.backendCounters ? std::make_optional(backend_->stats()) : std::nullopt,
220 .subscriptions = subscriptions_->report(),
221 .etl = etl_->getInfo()
222 };
223 }
224
225 auto const serverInfoRippled = balancer_->forwardToRippled(
226 {{"command", "server_info"}}, ctx.clientIp, ctx.isAdmin, ctx.yield
227 );
228
229 if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) {
230 if (serverInfoRippled->contains(JS(result)) &&
231 serverInfoRippled->at(JS(result)).as_object().contains(JS(info))) {
232 output.info.rippledInfo =
233 serverInfoRippled->at(JS(result)).as_object().at(JS(info)).as_object();
234 }
235 }
236
237 output.info.validatedLedger.age = age < 0 ? 0 : age;
238 output.info.validatedLedger.hash = ripple::strHex(lgrInfo->hash);
239 output.info.validatedLedger.seq = lgrInfo->seq;
240 output.info.validatedLedger.fees = fees;
241 output.info.cache.size = backend_->cache().size();
242 output.info.cache.isFull = backend_->cache().isFull();
243 output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
244 output.info.cache.objectHitRate = backend_->cache().getObjectHitRate();
245 output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate();
246 output.info.cache.isEnabled = not backend_->cache().isDisabled();
247 output.info.uptime = counters_.get().uptime();
248 output.info.isAmendmentBlocked = etl_->isAmendmentBlocked();
249 output.info.isCorruptionDetected = etl_->isCorruptionDetected();
250
251 return output;
252 }
253
254private:
255 friend void
256 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
257 {
258 using boost::json::value_from;
259
260 jv = {
261 {JS(info), value_from(output.info)},
262 {JS(validated), output.validated},
263 };
264 }
265
266 friend void
267 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info)
268 {
269 using boost::json::value_from;
270 using ripple::to_string;
271
272 jv = {
273 {JS(complete_ledgers), info.completeLedgers},
274 {JS(load_factor), info.loadFactor},
275 {JS(time), to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
276 {JS(uptime), info.uptime.count()},
277 {"clio_version", info.clioVersion},
278 {"libxrpl_version", info.xrplVersion},
279 {JS(validated_ledger), value_from(info.validatedLedger)},
280 {"cache", value_from(info.cache)},
281 };
282
283 if (info.isAmendmentBlocked)
284 jv.as_object()[JS(amendment_blocked)] = true;
285
286 if (info.isCorruptionDetected)
287 jv.as_object()["corruption_detected"] = true;
288
289 if (info.rippledInfo) {
290 auto const& rippledInfo = info.rippledInfo.value();
291
292 if (rippledInfo.contains(JS(load_factor)))
293 jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor));
294 if (rippledInfo.contains(JS(validation_quorum)))
295 jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
296 if (rippledInfo.contains(JS(build_version)))
297 jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version));
298 if (rippledInfo.contains(JS(network_id)))
299 jv.as_object()[JS(network_id)] = rippledInfo.at(JS(network_id));
300 }
301
302 if (info.adminSection) {
303 jv.as_object()["etl"] = info.adminSection->etl;
304 jv.as_object()[JS(counters)] = info.adminSection->counters;
305 jv.as_object()[JS(counters)].as_object()["subscriptions"] =
306 info.adminSection->subscriptions;
307 if (info.adminSection->backendCounters.has_value()) {
308 jv.as_object()[kBACKEND_COUNTERS_KEY] = *info.adminSection->backendCounters;
309 }
310 }
311 }
312
313 friend void
314 tag_invoke(
315 boost::json::value_from_tag,
316 boost::json::value& jv,
317 ValidatedLedgerSection const& validated
318 )
319 {
320 jv = {
321 {JS(age), validated.age},
322 {JS(hash), validated.hash},
323 {JS(seq), validated.seq},
324 {JS(base_fee_xrp), validated.fees->base.decimalXRP()},
325 {JS(reserve_base_xrp), validated.fees->reserve.decimalXRP()},
326 {JS(reserve_inc_xrp), validated.fees->increment.decimalXRP()},
327 };
328 }
329
330 friend void
331 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CacheSection const& cache)
332 {
333 jv = {
334 {"size", cache.size},
335 {"is_enabled", cache.isEnabled},
336 {"is_full", cache.isFull},
337 {"latest_ledger_seq", cache.latestLedgerSeq},
338 {"object_hit_rate", cache.objectHitRate},
339 {"successor_hit_rate", cache.successorHitRate},
340 };
341 }
342
343 friend Input
344 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
345 {
346 auto input = BaseServerInfoHandler::Input{};
347 auto const jsonObject = jv.as_object();
348 if (jsonObject.contains(kBACKEND_COUNTERS_KEY) &&
349 jsonObject.at(kBACKEND_COUNTERS_KEY).is_bool())
350 input.backendCounters = jv.at(kBACKEND_COUNTERS_KEY).as_bool();
351 return input;
352 }
353};
354
362
363} // namespace rpc
static constexpr std::uint32_t kRIPPLE_EPOCH_START
The ripple epoch start timestamp. Midnight on 1st January 2000.
Definition DBHelpers.hpp:292
Contains common functionality for handling the server_info command.
Definition ServerInfo.hpp:66
Result process(Input const &input, Context const &ctx) const
Process the ServerInfo command.
Definition ServerInfo.hpp:190
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition ServerInfo.hpp:176
BaseServerInfoHandler(std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< feed::SubscriptionManagerInterface > const &subscriptions, std::shared_ptr< etl::LoadBalancerInterface > const &balancer, std::shared_ptr< etl::ETLServiceInterface const > const &etl, CountersType const &counters)
Construct a new BaseServerInfoHandler object.
Definition ServerInfo.hpp:154
Holds information about successful, failed, forwarded, etc. RPC handler calls.
Definition Counters.hpp:41
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:37
RpcSpec const & RpcSpecConstRef
An alias for a const reference to RpcSpec.
Definition Specs.hpp:150
std::expected< OutputType, Status > HandlerReturnType
Return type for each individual handler.
Definition Types.hpp:81
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, BookChange const &change)
Implementation of value_from for BookChange type.
Definition BookChangesHelper.hpp:252
std::unexpected< Status > Error
The type that represents just the error part of MaybeError.
Definition Types.hpp:75
BaseServerInfoHandler< Counters > ServerInfoHandler
The server_info command asks the Clio server for a human-readable version of various information abou...
Definition ServerInfo.hpp:361
A struct to hold the admin section of the output.
Definition ServerInfo.hpp:86
A struct to hold the cache section of the output.
Definition ServerInfo.hpp:106
A struct to hold the info section of the output.
Definition ServerInfo.hpp:118
A struct to hold the input data for the command.
Definition ServerInfo.hpp:79
A struct to hold the output data of the command.
Definition ServerInfo.hpp:136
A struct to hold the validated ledger section of the output.
Definition ServerInfo.hpp:96
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
Represents a Specification of an entire RPC command.
Definition Specs.hpp:102
A status returned from any RPC handler.
Definition Errors.hpp:84