Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
ServerInfo.hpp
1#pragma once
2
3#include "data/BackendInterface.hpp"
4#include "data/DBHelpers.hpp"
5#include "etl/ETLServiceInterface.hpp"
7#include "feed/SubscriptionManagerInterface.hpp"
8#include "rpc/JS.hpp"
9#include "rpc/common/SpecBackend.hpp"
10#include "rpc/common/Types.hpp"
11#include "util/Assert.hpp"
12#include "util/Concepts.hpp"
13#include "util/build/Build.hpp"
14
15#include <boost/json/conversion.hpp>
16#include <boost/json/object.hpp>
17#include <boost/json/value.hpp>
18#include <fmt/format.h>
19#include <rpcspec/Errors.hpp>
20#include <rpcspec/handlers/server_info/Types.hpp>
21#include <xrpl/basics/chrono.h>
22#include <xrpl/basics/strHex.h>
23#include <xrpl/protocol/BuildInfo.h>
24#include <xrpl/protocol/Fees.h>
25#include <xrpl/protocol/Protocol.h>
26#include <xrpl/protocol/jss.h>
27
28#include <chrono>
29#include <cstddef>
30#include <cstdint>
31#include <functional>
32#include <memory>
33#include <optional>
34#include <string>
35#include <utility>
36
37namespace rpc {
38class Counters;
39} // namespace rpc
40
41namespace rpc {
42
49template <typename CountersType, util::SomeSystemClock ClockType = std::chrono::system_clock>
50class BaseServerInfoHandler : public rpc::HandlerFor<rpc::spec::handlers::server_info::Input> {
51 static constexpr auto kBackendCountersKey = "backend_counters";
52
53 std::shared_ptr<BackendInterface> backend_;
54 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
55 std::shared_ptr<etl::LoadBalancerInterface> balancer_;
56 std::shared_ptr<etl::ETLServiceInterface const> etl_;
57 std::reference_wrapper<CountersType const> counters_;
58
59public:
63 struct AdminSection {
64 boost::json::object counters;
65 std::optional<boost::json::object> backendCounters;
66 boost::json::object subscriptions;
67 boost::json::object etl;
68 };
69
74 uint32_t age = 0;
75 std::string hash;
76 xrpl::LedgerIndex seq = {};
77 std::optional<xrpl::Fees> fees = std::nullopt;
78 };
79
83 struct CacheSection {
84 std::size_t size = 0;
85 bool isEnabled = false;
86 bool isFull = false;
87 xrpl::LedgerIndex latestLedgerSeq = {};
88 float objectHitRate = 1.0;
89 float successorHitRate = 1.0;
90 };
91
95 struct InfoSection {
96 std::optional<AdminSection> adminSection = std::nullopt;
97 std::string completeLedgers;
98 uint32_t loadFactor = 1u;
99 std::chrono::time_point<std::chrono::system_clock> time = ClockType::now();
100 std::chrono::seconds uptime = {};
101 std::string clioVersion = util::build::getClioVersionString();
102 std::string xrplVersion = xrpl::build_info::getVersionString();
103 std::optional<boost::json::object> rippledInfo = std::nullopt;
104 ValidatedLedgerSection validatedLedger = {};
105 CacheSection cache = {};
106 bool isAmendmentBlocked = false;
107 bool isCorruptionDetected = false;
108 };
109
113 struct Output {
114 InfoSection info = {};
115
116 // validated should be sent via framework
117 bool validated = true;
118 };
119
120 using Result = HandlerReturnType<Output>;
121
132 std::shared_ptr<BackendInterface> backend,
133 std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
134 std::shared_ptr<etl::LoadBalancerInterface> balancer,
135 std::shared_ptr<etl::ETLServiceInterface const> etl,
136 CountersType const& counters
137 )
138 : backend_(std::move(backend))
139 , subscriptions_(std::move(subscriptions))
140 , balancer_(std::move(balancer))
141 , etl_(std::move(etl))
142 , counters_(std::cref(counters))
143 {
144 }
145
153 [[nodiscard]] Result
154 process(Input const& input, Context const& ctx) const
155 {
156 using namespace rpc;
157 using namespace std::chrono;
158
159 auto const range = backend_->fetchLedgerRange();
160 ASSERT(range.has_value(), "ServerInfo's ledger range must be available");
161
162 auto const lgrInfo = backend_->fetchLedgerBySequence(
163 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
164 range->maxSequence,
165 ctx.yield
166 );
167 if (not lgrInfo.has_value())
168 return Error{Status{XrpldError::RpcInternal}};
169
170 auto const fees = backend_->fetchFees(lgrInfo->seq, ctx.yield);
171 if (not fees.has_value())
172 return Error{Status{XrpldError::RpcInternal}};
173
174 auto output = Output{};
175 auto const sinceEpoch = duration_cast<seconds>(output.info.time.time_since_epoch()).count();
176 auto const age = static_cast<int32_t>(sinceEpoch) -
177 static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
178 static_cast<int32_t>(kRippleEpochStart);
179
180 // NOLINTBEGIN(bugprone-unchecked-optional-access)
181 output.info.completeLedgers = fmt::format("{}-{}", range->minSequence, range->maxSequence);
182 // NOLINTEND(bugprone-unchecked-optional-access)
183
184 if (ctx.isAdmin) {
185 output.info.adminSection = {
186 .counters = counters_.get().report(),
187 .backendCounters =
188 input.backendCounters ? std::make_optional(backend_->stats()) : std::nullopt,
189 .subscriptions = subscriptions_->report(),
190 .etl = etl_->getInfo()
191 };
192 }
193
194 auto const serverInfoRippled = balancer_->forwardToRippled(
195 {{"command", "server_info"}}, ctx.clientIp, ctx.isAdmin, ctx.yield
196 );
197
198 if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) {
199 if (serverInfoRippled->contains(JS(result)) &&
200 serverInfoRippled->at(JS(result)).as_object().contains(JS(info))) {
201 output.info.rippledInfo =
202 serverInfoRippled->at(JS(result)).as_object().at(JS(info)).as_object();
203 }
204 }
205
206 output.info.validatedLedger.age = age < 0 ? 0 : age;
207 output.info.validatedLedger.hash = xrpl::strHex(lgrInfo->hash);
208 output.info.validatedLedger.seq = lgrInfo->seq;
209 output.info.validatedLedger.fees = fees;
210 output.info.cache.size = backend_->cache().size();
211 output.info.cache.isFull = backend_->cache().isFull();
212 output.info.cache.latestLedgerSeq = backend_->cache().latestLedgerSequence();
213 output.info.cache.objectHitRate = backend_->cache().getObjectHitRate();
214 output.info.cache.successorHitRate = backend_->cache().getSuccessorHitRate();
215 output.info.cache.isEnabled = not backend_->cache().isDisabled();
216 output.info.uptime = counters_.get().uptime();
217 output.info.isAmendmentBlocked = etl_->isAmendmentBlocked();
218 output.info.isCorruptionDetected = etl_->isCorruptionDetected();
219
220 return output;
221 }
222
223private:
224 friend void
225 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output)
226 {
227 using boost::json::value_from;
228
229 jv = {
230 {JS(info), value_from(output.info)},
231 {JS(validated), output.validated},
232 };
233 }
234
235 friend void
236 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info)
237 {
238 using boost::json::value_from;
239 jv = {
240 {JS(complete_ledgers), info.completeLedgers},
241 {JS(load_factor), info.loadFactor},
242 {JS(time), xrpl::to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
243 {JS(uptime), info.uptime.count()},
244 {"clio_version", info.clioVersion},
245 {"libxrpl_version", info.xrplVersion},
246 {JS(validated_ledger), value_from(info.validatedLedger)},
247 {"cache", value_from(info.cache)},
248 };
249
250 if (info.isAmendmentBlocked)
251 jv.as_object()[JS(amendment_blocked)] = true;
252
253 if (info.isCorruptionDetected)
254 jv.as_object()["corruption_detected"] = true;
255
256 if (info.rippledInfo) {
257 auto const& rippledInfo = *info.rippledInfo;
258
259 if (rippledInfo.contains(JS(load_factor)))
260 jv.as_object()[JS(load_factor)] = rippledInfo.at(JS(load_factor));
261 if (rippledInfo.contains(JS(validation_quorum)))
262 jv.as_object()[JS(validation_quorum)] = rippledInfo.at(JS(validation_quorum));
263 if (rippledInfo.contains(JS(build_version)))
264 jv.as_object()["rippled_version"] = rippledInfo.at(JS(build_version));
265 if (rippledInfo.contains(JS(network_id)))
266 jv.as_object()[JS(network_id)] = rippledInfo.at(JS(network_id));
267 }
268
269 if (info.adminSection) {
270 jv.as_object()["etl"] = info.adminSection->etl;
271 jv.as_object()[JS(counters)] = info.adminSection->counters;
272 jv.as_object()[JS(counters)].as_object()["subscriptions"] =
273 info.adminSection->subscriptions;
274 if (info.adminSection->backendCounters.has_value()) {
275 jv.as_object()[kBackendCountersKey] = *info.adminSection->backendCounters;
276 }
277 }
278 }
279
280 friend void
281 tag_invoke(
282 boost::json::value_from_tag,
283 boost::json::value& jv,
284 ValidatedLedgerSection const& validated
285 )
286 {
287 // NOLINTBEGIN(bugprone-unchecked-optional-access)
288 jv = {
289 {JS(age), validated.age},
290 {JS(hash), validated.hash},
291 {JS(seq), validated.seq},
292 {JS(base_fee_xrp), validated.fees->base.decimalXRP()},
293 {JS(reserve_base_xrp), validated.fees->reserve.decimalXRP()},
294 {JS(reserve_inc_xrp), validated.fees->increment.decimalXRP()},
295 };
296 // NOLINTEND(bugprone-unchecked-optional-access)
297 }
298
299 friend void
300 tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CacheSection const& cache)
301 {
302 jv = {
303 {"size", cache.size},
304 {"is_enabled", cache.isEnabled},
305 {"is_full", cache.isFull},
306 {"latest_ledger_seq", cache.latestLedgerSeq},
307 {"object_hit_rate", cache.objectHitRate},
308 {"successor_hit_rate", cache.successorHitRate},
309 };
310 }
311};
312
320
321} // namespace rpc
static constexpr std::uint32_t kRippleEpochStart
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:50
Result process(Input const &input, Context const &ctx) const
Process the ServerInfo command.
Definition ServerInfo.hpp:154
BaseServerInfoHandler(std::shared_ptr< BackendInterface > backend, std::shared_ptr< feed::SubscriptionManagerInterface > subscriptions, std::shared_ptr< etl::LoadBalancerInterface > balancer, std::shared_ptr< etl::ETLServiceInterface const > etl, CountersType const &counters)
Construct a new BaseServerInfoHandler object.
Definition ServerInfo.hpp:131
Holds information about successful, failed, forwarded, etc. RPC handler calls.
Definition Counters.hpp:22
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:17
std::expected< OutputType, Status > HandlerReturnType
Return type for each individual handler.
Definition Types.hpp:61
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:233
spec::HandlerFor< InputT, boost::json::value > HandlerFor
The spec-driven base every typed handler inherits.
Definition SpecBackend.hpp:24
std::unexpected< Status > Error
The type that represents just the error part of MaybeError.
Definition Types.hpp:55
BaseServerInfoHandler< Counters > ServerInfoHandler
The server_info command asks the Clio server for a human-readable version of various information abou...
Definition ServerInfo.hpp:319
A struct to hold the admin section of the output.
Definition ServerInfo.hpp:63
A struct to hold the cache section of the output.
Definition ServerInfo.hpp:83
A struct to hold the info section of the output.
Definition ServerInfo.hpp:95
A struct to hold the output data of the command.
Definition ServerInfo.hpp:113
A struct to hold the validated ledger section of the output.
Definition ServerInfo.hpp:73
Context of an RPC call.
Definition Types.hpp:98
Result type used to return responses or error statuses to the Webserver subsystem.
Definition Types.hpp:109