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 "feed/SubscriptionManagerInterface.hpp"
25#include "rpc/Errors.hpp"
26#include "rpc/JS.hpp"
27#include "rpc/common/Specs.hpp"
28#include "rpc/common/Types.hpp"
29#include "util/Assert.hpp"
30#include "util/build/Build.hpp"
31
32#include <boost/json/conversion.hpp>
33#include <boost/json/object.hpp>
34#include <boost/json/value.hpp>
35#include <fmt/core.h>
36#include <xrpl/basics/chrono.h>
37#include <xrpl/basics/strHex.h>
38#include <xrpl/protocol/BuildInfo.h>
39#include <xrpl/protocol/ErrorCodes.h>
40#include <xrpl/protocol/Fees.h>
41#include <xrpl/protocol/Protocol.h>
42#include <xrpl/protocol/jss.h>
43
44#include <chrono>
45#include <cstddef>
46#include <cstdint>
47#include <functional>
48#include <memory>
49#include <optional>
50#include <string>
51
52namespace etl {
53class ETLService;
54class LoadBalancer;
55} // namespace etl
56namespace rpc {
57class Counters;
58} // namespace rpc
59
60namespace rpc {
61
69template <typename LoadBalancerType, typename ETLServiceType, typename CountersType>
71 static constexpr auto kBACKEND_COUNTERS_KEY = "backend_counters";
72
73 std::shared_ptr<BackendInterface> backend_;
74 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
75 std::shared_ptr<LoadBalancerType> balancer_;
76 std::shared_ptr<ETLServiceType const> etl_;
77 std::reference_wrapper<CountersType const> counters_;
78
79public:
83 struct Input {
84 bool backendCounters = false;
85 };
86
90 struct AdminSection {
91 boost::json::object counters;
92 std::optional<boost::json::object> backendCounters;
93 boost::json::object subscriptions;
94 boost::json::object etl;
95 };
96
101 uint32_t age = 0;
102 std::string hash;
103 ripple::LedgerIndex seq = {};
104 std::optional<ripple::Fees> fees = std::nullopt;
105 };
106
111 std::size_t size = 0;
112 bool isEnabled = false;
113 bool isFull = false;
114 ripple::LedgerIndex latestLedgerSeq = {};
115 float objectHitRate = 1.0;
116 float successorHitRate = 1.0;
117 };
118
122 struct InfoSection {
123 std::optional<AdminSection> adminSection = std::nullopt;
124 std::string completeLedgers;
125 uint32_t loadFactor = 1u;
126 std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
127 std::chrono::seconds uptime = {};
128 std::string clioVersion = util::build::getClioVersionString();
129 std::string xrplVersion = ripple::BuildInfo::getVersionString();
130 std::optional<boost::json::object> rippledInfo = std::nullopt;
131 ValidatedLedgerSection validatedLedger = {};
132 CacheSection cache = {};
133 bool isAmendmentBlocked = false;
134 bool isCorruptionDetected = false;
135 };
136
140 struct Output {
141 InfoSection info = {};
142
143 // validated should be sent via framework
144 bool validated = true;
145 };
146
147 using Result = HandlerReturnType<Output>;
148
159 std::shared_ptr<BackendInterface> const& backend,
160 std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions,
161 std::shared_ptr<LoadBalancerType> const& balancer,
162 std::shared_ptr<ETLServiceType const> const& etl,
163 CountersType const& counters
164 )
165 : backend_(backend)
166 , subscriptions_(subscriptions)
167 , balancer_(balancer)
168 , etl_(etl)
169 , counters_(std::cref(counters))
170 {
171 }
172
179 static RpcSpecConstRef
180 spec([[maybe_unused]] uint32_t apiVersion)
181 {
182 static RpcSpec const kRPC_SPEC = {};
183 return kRPC_SPEC;
184 }
185
193 Result
194 process(Input input, Context const& ctx) const
195 {
196 using namespace rpc;
197 using namespace std::chrono;
198
199 auto const range = backend_->fetchLedgerRange();
200 ASSERT(range.has_value(), "ServerInfo's ledger range must be available");
201
202 auto const lgrInfo = backend_->fetchLedgerBySequence(range->maxSequence, ctx.yield);
203 if (not lgrInfo.has_value())
204 return Error{Status{RippledError::rpcINTERNAL}};
205
206 auto const fees = backend_->fetchFees(lgrInfo->seq, ctx.yield);
207 if (not fees.has_value())
208 return Error{Status{RippledError::rpcINTERNAL}};
209
210 auto output = Output{};
211 auto const sinceEpoch = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
212 auto const age = static_cast<int32_t>(sinceEpoch) -
213 static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
214 static_cast<int32_t>(kRIPPLE_EPOCH_START);
215
216 output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
217
218 if (ctx.isAdmin) {
219 output.info.adminSection = {
220 .counters = counters_.get().report(),
221 .backendCounters = input.backendCounters ? std::make_optional(backend_->stats()) : std::nullopt,
222 .subscriptions = subscriptions_->report(),
223 .etl = etl_->getInfo()
224 };
225 }
226
227 auto const serverInfoRippled =
228 balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.isAdmin, ctx.yield);
229
230 if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) {
231 if (serverInfoRippled->contains(JS(result)) &&
232 serverInfoRippled->at(JS(result)).as_object().contains(JS(info))) {
233 output.info.rippledInfo = 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"] = info.adminSection->subscriptions;
306 if (info.adminSection->backendCounters.has_value()) {
307 jv.as_object()[kBACKEND_COUNTERS_KEY] = *info.adminSection->backendCounters;
308 }
309 }
310 }
311
312 friend void
313 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, ValidatedLedgerSection const& validated)
314 {
315 jv = {
316 {JS(age), validated.age},
317 {JS(hash), validated.hash},
318 {JS(seq), validated.seq},
319 {JS(base_fee_xrp), validated.fees->base.decimalXRP()},
320 {JS(reserve_base_xrp), validated.fees->reserve.decimalXRP()},
321 {JS(reserve_inc_xrp), validated.fees->increment.decimalXRP()},
322 };
323 }
324
325 friend void
326 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CacheSection const& cache)
327 {
328 jv = {
329 {"size", cache.size},
330 {"is_enabled", cache.isEnabled},
331 {"is_full", cache.isFull},
332 {"latest_ledger_seq", cache.latestLedgerSeq},
333 {"object_hit_rate", cache.objectHitRate},
334 {"successor_hit_rate", cache.successorHitRate},
335 };
336 }
337
338 friend Input
339 tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
340 {
341 auto input = BaseServerInfoHandler::Input{};
342 auto const jsonObject = jv.as_object();
343 if (jsonObject.contains(kBACKEND_COUNTERS_KEY) && jsonObject.at(kBACKEND_COUNTERS_KEY).is_bool())
344 input.backendCounters = jv.at(kBACKEND_COUNTERS_KEY).as_bool();
345 return input;
346 }
347};
348
356
357} // namespace rpc
static constexpr std::uint32_t kRIPPLE_EPOCH_START
The ripple epoch start timestamp. Midnight on 1st January 2000.
Definition DBHelpers.hpp:318
Contains common functionality for handling the server_info command.
Definition ServerInfo.hpp:70
static RpcSpecConstRef spec(uint32_t apiVersion)
Returns the API specification for the command.
Definition ServerInfo.hpp:180
Result process(Input input, Context const &ctx) const
Process the ServerInfo command.
Definition ServerInfo.hpp:194
BaseServerInfoHandler(std::shared_ptr< BackendInterface > const &backend, std::shared_ptr< feed::SubscriptionManagerInterface > const &subscriptions, std::shared_ptr< LoadBalancerType > const &balancer, std::shared_ptr< ETLServiceType const > const &etl, CountersType const &counters)
Construct a new BaseServerInfoHandler object.
Definition ServerInfo.hpp:158
This namespace contains everything to do with the ETL and ETL sources.
Definition CacheLoader.hpp:36
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:36
RpcSpec const & RpcSpecConstRef
An alias for a const reference to RpcSpec.
Definition Specs.hpp:145
std::expected< OutputType, Status > HandlerReturnType
Return type for each individual handler.
Definition Types.hpp:81
std::unexpected< Status > Error
The type that represents just the error part of MaybeError.
Definition Types.hpp:75
A struct to hold the admin section of the output.
Definition ServerInfo.hpp:90
A struct to hold the cache section of the output.
Definition ServerInfo.hpp:110
A struct to hold the info section of the output.
Definition ServerInfo.hpp:122
A struct to hold the input data for the command.
Definition ServerInfo.hpp:83
A struct to hold the output data of the command.
Definition ServerInfo.hpp:140
A struct to hold the validated ledger section of the output.
Definition ServerInfo.hpp:100
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:98
A status returned from any RPC handler.
Definition Errors.hpp:82