xrpld
Loading...
Searching...
No Matches
RPCLedgerHelpers.cpp
1#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
2
3#include <xrpld/app/ledger/InboundLedger.h>
4#include <xrpld/app/ledger/InboundLedgers.h>
5#include <xrpld/app/ledger/LedgerMaster.h>
6#include <xrpld/app/ledger/LedgerToJson.h>
7#include <xrpld/app/main/Application.h>
8#include <xrpld/rpc/Context.h>
9#include <xrpld/rpc/Status.h>
10#include <xrpld/rpc/detail/Tuning.h>
11
12#include <xrpl/basics/base_uint.h>
13#include <xrpl/beast/core/LexicalCast.h>
14#include <xrpl/beast/utility/Zero.h>
15#include <xrpl/beast/utility/instrumentation.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/ledger/View.h>
18#include <xrpl/protocol/ErrorCodes.h>
19#include <xrpl/protocol/RPCErr.h>
20#include <xrpl/protocol/RippleLedgerHash.h>
21#include <xrpl/protocol/jss.h>
22
23#include <org/xrpl/rpc/v1/get_ledger.pb.h> // IWYU pragma: keep
24#include <org/xrpl/rpc/v1/get_ledger_data.pb.h> // IWYU pragma: keep
25#include <org/xrpl/rpc/v1/get_ledger_entry.pb.h> // IWYU pragma: keep
26#include <org/xrpl/rpc/v1/ledger.pb.h>
27#include <rpcspec/Ledger.hpp>
28
29#include <cstdint>
30#include <expected>
31#include <memory>
32#include <utility>
33#include <variant>
34
35namespace xrpl::rpc {
36
37namespace {
38
39template <typename... Ts>
40struct Overload : Ts...
41{
42 using Ts::operator()...;
43
44 constexpr Overload(Ts&&... ts) : Ts(std::forward<Ts>(ts))...
45 {
46 }
47};
48
49bool
50isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
51{
52 if (standalone)
53 return false;
54
55 return ledgerMaster.getValidatedLedgerAge() > tuning::kMaxValidatedLedgerAge;
56}
57
58template <class T>
60ledgerFromHash(
61 T& ledger,
62 json::Value hash,
63 Context const& context,
64 json::StaticString const fieldName)
65{
66 UInt256 ledgerHash;
67 if (!ledgerHash.parseHex(hash.asString()))
68 return {RpcInvalidParams, expectedFieldMessage(fieldName, "hex string")};
69 return getLedger(ledger, ledgerHash, context);
70}
71
72template <class T>
74ledgerFromIndex(
75 T& ledger,
76 json::Value indexValue,
77 Context const& context,
78 json::StaticString const fieldName)
79{
80 auto const index = indexValue.asString();
81
82 if (index == "current" || index.empty())
83 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
84
85 if (index == "validated")
86 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Validated, context);
87
88 if (index == "closed")
89 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Closed, context);
90
91 std::uint32_t iVal = 0;
92 if (!beast::lexicalCastChecked(iVal, index))
93 return {RpcInvalidParams, expectedFieldMessage(fieldName, "string or number")};
94
95 return getLedger(ledger, iVal, context);
96}
97
98template <class T>
100ledgerFromRequest(T& ledger, JsonContext const& context)
101{
102 ledger.reset();
103
104 auto& params = context.params;
105 auto const hasLedger = context.params.isMember(jss::ledger);
106 auto const hasHash = context.params.isMember(jss::ledger_hash);
107 auto const hasIndex = context.params.isMember(jss::ledger_index);
108
109 if ((hasLedger + hasHash + hasIndex) > 1)
110 {
111 // while `ledger` is still supported, it is deprecated
112 // and therefore shouldn't be mentioned in the error message
113 if (hasLedger)
114 {
115 return {
117 "Exactly one of 'ledger', 'ledger_hash', or "
118 "'ledger_index' can be specified."};
119 }
120 return {
122 "Exactly one of 'ledger_hash' or "
123 "'ledger_index' can be specified."};
124 }
125
126 // We need to support the legacy "ledger" field.
127 if (hasLedger)
128 {
129 auto& legacyLedger = params[jss::ledger];
130 if (!legacyLedger.isString() && !legacyLedger.isUInt() && !legacyLedger.isInt())
131 {
132 return {RpcInvalidParams, expectedFieldMessage(jss::ledger, "string or number")};
133 }
134 if (legacyLedger.isString() && legacyLedger.asString().size() == 64)
135 {
136 return ledgerFromHash(ledger, legacyLedger, context, jss::ledger);
137 }
138
139 return ledgerFromIndex(ledger, legacyLedger, context, jss::ledger);
140 }
141
142 if (hasHash)
143 {
144 auto const& ledgerHash = params[jss::ledger_hash];
145 if (!ledgerHash.isString())
146 return {RpcInvalidParams, expectedFieldMessage(jss::ledger_hash, "hex string")};
147 return ledgerFromHash(ledger, ledgerHash, context, jss::ledger_hash);
148 }
149
150 if (hasIndex)
151 {
152 auto const& ledgerIndex = params[jss::ledger_index];
153 if (!ledgerIndex.isString() && !ledgerIndex.isUInt() && !ledgerIndex.isInt())
154 {
155 return {RpcInvalidParams, expectedFieldMessage(jss::ledger_index, "string or number")};
156 }
157 return ledgerFromIndex(ledger, ledgerIndex, context, jss::ledger_index);
158 }
159
160 // nothing specified, `index` has a default setting
161 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
162}
163} // namespace
164
165template <class T, class R>
166Status
167ledgerFromRequest(T& ledger, GRPCContext<R> const& context)
168{
169 R const& request = context.params;
170 return ledgerFromSpecifier(ledger, request.ledger(), context);
171}
172
173// explicit instantiation of above function
174template Status
178
179// explicit instantiation of above function
180template Status
184
185// explicit instantiation of above function
186template Status
190
191template <class T>
192Status
194 T& ledger,
195 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
196 Context const& context)
197{
198 ledger.reset();
199
200 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
201 LedgerCase const ledgerCase = specifier.ledger_case();
202 switch (ledgerCase)
203 {
204 case LedgerCase::kHash: {
205 if (auto hash = UInt256::fromVoidChecked(specifier.hash()))
206 {
207 return getLedger(ledger, *hash, context);
208 }
209 return {RpcInvalidParams, "ledgerHashMalformed"};
210 }
211 case LedgerCase::kSequence:
212 return getLedger(ledger, specifier.sequence(), context);
213 case LedgerCase::kShortcut:
214 [[fallthrough]];
215 case LedgerCase::LEDGER_NOT_SET: {
216 auto const shortcut = specifier.shortcut();
217 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
218 {
219 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Validated, context);
220 }
221
222 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
223 shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED)
224 {
225 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
226 }
227 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
228 {
229 return getLedger(ledger, ::rpc::spec::LedgerShortcut::Closed, context);
230 }
231 }
232 }
233
234 return Status::kOK;
235}
236
237template <class T>
238Status
239getLedger(T& ledger, UInt256 const& ledgerHash, Context const& context)
240{
241 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
242 if (ledger == nullptr)
243 return {RpcLgrNotFound, "ledgerNotFound"};
244 return Status::kOK;
245}
246
247template <class T>
248Status
249getLedger(T& ledger, uint32_t ledgerIndex, Context const& context)
250{
251 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
252 if (ledger == nullptr)
253 {
254 auto cur = context.ledgerMaster.getCurrentLedger();
255 if (cur->header().seq == ledgerIndex)
256 {
257 ledger = cur;
258 }
259 }
260
261 if (ledger == nullptr)
262 return {RpcLgrNotFound, "ledgerNotFound"};
263
264 if (ledger->header().seq > context.ledgerMaster.getValidLedgerIndex() &&
265 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
266 {
267 ledger.reset();
268 if (context.apiVersion == 1)
269 return {RpcNoNetwork, "InsufficientNetworkMode"};
270 return {RpcNotSynced, "notSynced"};
271 }
272
273 return Status::kOK;
274}
275
276template <class T>
277Status
278getLedger(T& ledger, ::rpc::spec::LedgerShortcut shortcut, Context const& context)
279{
280 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
281 {
282 if (context.apiVersion == 1)
283 return {RpcNoNetwork, "InsufficientNetworkMode"};
284 return {RpcNotSynced, "notSynced"};
285 }
286
287 if (shortcut == ::rpc::spec::LedgerShortcut::Validated)
288 {
289 ledger = context.ledgerMaster.getValidatedLedger();
290 if (ledger == nullptr)
291 {
292 if (context.apiVersion == 1)
293 return {RpcNoNetwork, "InsufficientNetworkMode"};
294 return {RpcNotSynced, "notSynced"};
295 }
296
297 XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : validated is not open");
298 }
299 else
300 {
301 if (shortcut == ::rpc::spec::LedgerShortcut::Current)
302 {
303 ledger = context.ledgerMaster.getCurrentLedger();
304 XRPL_ASSERT(ledger->open(), "xrpl::rpc::getLedger : current is open");
305 }
306 else if (shortcut == ::rpc::spec::LedgerShortcut::Closed)
307 {
308 ledger = context.ledgerMaster.getClosedLedger();
309 XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : closed is not open");
310 }
311 else
312 {
313 return {RpcInvalidParams, "ledgerIndexMalformed"};
314 }
315
316 if (ledger == nullptr)
317 {
318 if (context.apiVersion == 1)
319 return {RpcNoNetwork, "InsufficientNetworkMode"};
320 return {RpcNotSynced, "notSynced"};
321 }
322
323 static auto const kMinSequenceGap = 10;
324
325 if (ledger->header().seq + kMinSequenceGap < context.ledgerMaster.getValidLedgerIndex())
326 {
327 ledger.reset();
328 if (context.apiVersion == 1)
329 return {RpcNoNetwork, "InsufficientNetworkMode"};
330 return {RpcNotSynced, "notSynced"};
331 }
332 }
333 return Status::kOK;
334}
335
336template <class T>
337Status
338getLedger(T& ledger, ::rpc::spec::LedgerSpecifier const& specifier, Context const& context)
339{
340 return std::visit(
341 Overload{
342 [&](std::monostate) {
343 return getLedger(ledger, ::rpc::spec::kDefaultLedgerShortcut, context);
344 },
345 [&](auto const& value) { return getLedger(ledger, value, context); },
346 },
347 specifier.value);
348}
349
350// Explicit instantiation of above four functions
351template Status
353
354template Status
355getLedger<>(std::shared_ptr<ReadView const>&, ::rpc::spec::LedgerShortcut shortcut, Context const&);
356
357template Status
359
360template Status
361getLedger<>(std::shared_ptr<ReadView const>&, ::rpc::spec::LedgerSpecifier const&, Context const&);
362
363// explicit instantiation of ledgerFromSpecifier
364template Status
367 org::xrpl::rpc::v1::LedgerSpecifier const&,
368 Context const&);
369
370// The previous version of the lookupLedger command would accept the
371// "ledger_index" argument as a string and silently treat it as a request to
372// return the current ledger which, while not strictly wrong, could cause a lot
373// of confusion.
374//
375// The code now robustly validates the input and ensures that the only possible
376// values for the "ledger_index" parameter are the index of a ledger passed as
377// an integer or one of the strings "current", "closed" or "validated".
378// Additionally, the code ensures that the value passed in "ledger_hash" is a
379// string and a valid hash. Invalid values will return an appropriate error
380// code.
381//
382// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
383// assumes that "ledger_index" has the value "current".
384//
385// Returns a json::ValueType::Object. If there was an error, it will be in that
386// return value. Otherwise, the object contains the field "validated" and
387// optionally the fields "ledger_hash", "ledger_index" and
388// "ledger_current_index", if they are defined.
389Status
392 JsonContext const& context,
393 json::Value& result)
394{
395 if (auto status = ledgerFromRequest(ledger, context))
396 return status;
397
398 auto& info = ledger->header();
399
400 if (!ledger->open())
401 {
402 result[jss::ledger_hash] = to_string(info.hash);
403 result[jss::ledger_index] = info.seq;
404 }
405 else
406 {
407 result[jss::ledger_current_index] = info.seq;
408 }
409
410 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
411 return Status::kOK;
412}
413
416{
417 json::Value result;
418 if (auto status = lookupLedger(ledger, context, result))
419 status.inject(result);
420
421 return result;
422}
423
424std::expected<std::shared_ptr<Ledger const>, json::Value>
426{
427 auto const hasHash = context.params.isMember(jss::ledger_hash);
428 auto const hasIndex = context.params.isMember(jss::ledger_index);
429 std::uint32_t ledgerIndex = 0;
430
431 auto& ledgerMaster = context.app.getLedgerMaster();
432 LedgerHash ledgerHash;
433
434 if ((static_cast<int>(hasHash) + static_cast<int>(hasIndex)) != 1)
435 {
436 return std::unexpected(
438 "Exactly one of 'ledger_hash' or "
439 "'ledger_index' can be specified."));
440 }
441
442 if (hasHash)
443 {
444 auto const& jsonHash = context.params.get(jss::ledger_hash, json::ValueType::Null);
445 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
446 return std::unexpected(rpc::expectedFieldError(jss::ledger_hash, "hex string"));
447 }
448 else
449 {
450 auto const& jsonIndex = context.params.get(jss::ledger_index, json::ValueType::Null);
451 if (!jsonIndex.isInt() && !jsonIndex.isUInt())
452 return std::unexpected(rpc::expectedFieldError(jss::ledger_index, "number"));
453
454 // We need a validated ledger to get the hash from the sequence
455 if (ledgerMaster.getValidatedLedgerAge() > rpc::tuning::kMaxValidatedLedgerAge)
456 {
457 if (context.apiVersion == 1)
460 }
461
462 ledgerIndex = jsonIndex.asInt();
463 auto ledger = ledgerMaster.getValidatedLedger();
464
465 if (ledgerIndex >= ledger->header().seq)
466 return std::unexpected(rpc::makeParamError("Ledger index too large"));
467 if (ledgerIndex <= 0)
468 return std::unexpected(rpc::makeParamError("Ledger index too small"));
469
470 auto const j = context.app.getJournal("RPCHandler");
471 // Try to get the hash of the desired ledger from the validated
472 // ledger
473 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
474 if (!neededHash)
475 {
476 // Find a ledger more likely to have the hash of the desired
477 // ledger
478 auto const refIndex = getCandidateLedger(ledgerIndex);
479 auto refHash = hashOfSeq(*ledger, refIndex, j);
480 XRPL_ASSERT(refHash, "xrpl::rpc::getOrAcquireLedger : nonzero ledger hash");
481
482 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
483 ledger = ledgerMaster.getLedgerByHash(*refHash);
484 if (!ledger)
485 {
486 // We don't have the ledger we need to figure out which
487 // ledger they want. Try to get it.
488
489 if (auto il = context.app.getInboundLedgers().acquire(
490 *refHash, refIndex, InboundLedger::Reason::GENERIC))
491 {
492 json::Value jvResult = rpc::makeError(
493 RpcLgrNotFound, "acquiring ledger containing requested index");
494 jvResult[jss::acquiring] = getJson(LedgerFill(*il, &context));
495 return std::unexpected(jvResult);
496 }
497
498 if (auto il = context.app.getInboundLedgers().find(*refHash))
499 // NOLINTEND(bugprone-unchecked-optional-access)
500 {
501 json::Value jvResult = rpc::makeError(
502 RpcLgrNotFound, "acquiring ledger containing requested index");
503 jvResult[jss::acquiring] = il->getJson(0);
504 return std::unexpected(jvResult);
505 }
506
507 // Likely the app is shutting down
509 }
510
511 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
512 }
513 XRPL_ASSERT(neededHash, "xrpl::rpc::getOrAcquireLedger : nonzero needed hash");
514 ledgerHash = neededHash ? *neededHash : beast::kZero; // kludge
515 }
516
517 // Try to get the desired ledger
518 // Verify all nodes even if we think we have it
519 auto ledger = context.app.getInboundLedgers().acquire(
520 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
521
522 // In standalone mode, accept the ledger from the ledger cache
523 if (!ledger && context.app.config().standalone())
524 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
525
526 if (ledger)
527 return ledger;
528
529 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
530 return std::unexpected(il->getJson(0));
531
532 return std::unexpected(
533 rpc::makeError(RpcNotReady, "findCreate failed to return an inbound ledger"));
534}
535
536} // namespace xrpl::rpc
Represents a JSON value.
Definition json_value.h:117
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
virtual Config & config()=0
static std::optional< BaseUInt > fromVoidChecked(T const &from)
Definition base_uint.h:346
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:525
bool standalone() const
virtual std::shared_ptr< Ledger const > acquire(UInt256 const &hash, std::uint32_t seq, InboundLedger::Reason)=0
virtual std::shared_ptr< InboundLedger > find(LedgerHash const &hash)=0
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
Finds a ledger by sequence, preferring the validated chain.
std::shared_ptr< Ledger const > getClosedLedger()
The finalized ledger is the last closed/accepted ledger.
std::shared_ptr< Ledger const > getLedgerByHash(UInt256 const &hash)
std::shared_ptr< Ledger const > getValidatedLedger()
The validated ledger is the last fully validated ledger.
bool isValidated(ReadView const &ledger)
Tests whether a ledger is on the validated chain, using the skip list when the ledger does not alread...
LedgerIndex getValidLedgerIndex()
std::shared_ptr< ReadView const > getCurrentLedger()
The current ledger is the ledger we believe new transactions should go in.
virtual beast::Journal getJournal(std::string const &name)=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
T forward(T... args)
constexpr bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
constexpr Zero kZero
Definition Zero.h:30
@ Null
'null' value
Definition json_value.h:22
API version numbers used in later API versions.
Definition ApiVersion.h:36
json::Value expectedFieldError(std::string const &name, std::string const &type)
Definition ErrorCodes.h:309
std::expected< std::shared_ptr< Ledger const >, json::Value > getOrAcquireLedger(rpc::JsonContext const &context)
Retrieves or acquires a ledger based on the parameters provided in the given JsonContext.
Status getLedger(T &ledger, UInt256 const &ledgerHash, Context const &context)
Retrieves a ledger by its hash.
Status ledgerFromRequest(T &ledger, GRPCContext< R > const &context)
Retrieves a ledger from a gRPC request context.
json::Value makeParamError(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:231
Status ledgerFromSpecifier(T &ledger, org::xrpl::rpc::v1::LedgerSpecifier const &specifier, Context const &context)
Retrieves a ledger based on a LedgerSpecifier.
std::string expectedFieldMessage(std::string const &name, std::string const &type)
Definition ErrorCodes.h:297
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext const &context, json::Value &result)
Looks up a ledger from a request and fills a json::Value with ledger data.
std::optional< UInt256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:323
@ RpcNoNetwork
Definition ErrorCodes.h:49
@ RpcLgrNotFound
Definition ErrorCodes.h:55
@ RpcNotSynced
Definition ErrorCodes.h:50
@ RpcNotReady
Definition ErrorCodes.h:43
@ RpcInvalidParams
Definition ErrorCodes.h:67
@ RpcNoCurrent
Definition ErrorCodes.h:48
UInt256 LedgerHash
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:152
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
BaseUInt< 256 > UInt256
Definition base_uint.h:580
json::Value getJson(LedgerFill const &fill)
Return a new json::Value representing the ledger with given options.
@ LedgerMaster
ledger master data for signing
Definition HashPrefix.h:59
The context of information needed to call an RPC.
Definition Context.h:27
unsigned int apiVersion
Definition Context.h:37
Application & app
Definition Context.h:29
LedgerMaster & ledgerMaster
Definition Context.h:32
RequestType params
Definition Context.h:59
json::Value params
Definition Context.h:51
Status represents the results of an operation that might fail.
Definition Status.h:27
static constexpr Code kOK
Definition Status.h:33
T unexpected(T... args)
T visit(T... args)