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/LedgerShortcut.h>
20#include <xrpl/protocol/RPCErr.h>
21#include <xrpl/protocol/RippleLedgerHash.h>
22#include <xrpl/protocol/jss.h>
23
24#include <org/xrpl/rpc/v1/get_ledger.pb.h> // IWYU pragma: keep
25#include <org/xrpl/rpc/v1/get_ledger_data.pb.h> // IWYU pragma: keep
26#include <org/xrpl/rpc/v1/get_ledger_entry.pb.h> // IWYU pragma: keep
27#include <org/xrpl/rpc/v1/ledger.pb.h>
28
29#include <cstdint>
30#include <expected>
31#include <memory>
32
33namespace xrpl::rpc {
34
35namespace {
36
37bool
38isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
39{
40 if (standalone)
41 return false;
42
43 return ledgerMaster.getValidatedLedgerAge() > tuning::kMaxValidatedLedgerAge;
44}
45
46template <class T>
48ledgerFromHash(
49 T& ledger,
50 json::Value hash,
51 Context const& context,
52 json::StaticString const fieldName)
53{
54 uint256 ledgerHash;
55 if (!ledgerHash.parseHex(hash.asString()))
56 return {RpcInvalidParams, expectedFieldMessage(fieldName, "hex string")};
57 return getLedger(ledger, ledgerHash, context);
58}
59
60template <class T>
62ledgerFromIndex(
63 T& ledger,
64 json::Value indexValue,
65 Context const& context,
66 json::StaticString const fieldName)
67{
68 auto const index = indexValue.asString();
69
70 if (index == "current" || index.empty())
71 return getLedger(ledger, LedgerShortcut::Current, context);
72
73 if (index == "validated")
74 return getLedger(ledger, LedgerShortcut::Validated, context);
75
76 if (index == "closed")
77 return getLedger(ledger, LedgerShortcut::Closed, context);
78
79 std::uint32_t iVal = 0;
80 if (!beast::lexicalCastChecked(iVal, index))
81 return {RpcInvalidParams, expectedFieldMessage(fieldName, "string or number")};
82
83 return getLedger(ledger, iVal, context);
84}
85
86template <class T>
88ledgerFromRequest(T& ledger, JsonContext const& context)
89{
90 ledger.reset();
91
92 auto& params = context.params;
93 auto const hasLedger = context.params.isMember(jss::ledger);
94 auto const hasHash = context.params.isMember(jss::ledger_hash);
95 auto const hasIndex = context.params.isMember(jss::ledger_index);
96
97 if ((hasLedger + hasHash + hasIndex) > 1)
98 {
99 // while `ledger` is still supported, it is deprecated
100 // and therefore shouldn't be mentioned in the error message
101 if (hasLedger)
102 {
103 return {
105 "Exactly one of 'ledger', 'ledger_hash', or "
106 "'ledger_index' can be specified."};
107 }
108 return {
110 "Exactly one of 'ledger_hash' or "
111 "'ledger_index' can be specified."};
112 }
113
114 // We need to support the legacy "ledger" field.
115 if (hasLedger)
116 {
117 auto& legacyLedger = params[jss::ledger];
118 if (!legacyLedger.isString() && !legacyLedger.isUInt() && !legacyLedger.isInt())
119 {
120 return {RpcInvalidParams, expectedFieldMessage(jss::ledger, "string or number")};
121 }
122 if (legacyLedger.isString() && legacyLedger.asString().size() == 64)
123 {
124 return ledgerFromHash(ledger, legacyLedger, context, jss::ledger);
125 }
126
127 return ledgerFromIndex(ledger, legacyLedger, context, jss::ledger);
128 }
129
130 if (hasHash)
131 {
132 auto const& ledgerHash = params[jss::ledger_hash];
133 if (!ledgerHash.isString())
134 return {RpcInvalidParams, expectedFieldMessage(jss::ledger_hash, "hex string")};
135 return ledgerFromHash(ledger, ledgerHash, context, jss::ledger_hash);
136 }
137
138 if (hasIndex)
139 {
140 auto const& ledgerIndex = params[jss::ledger_index];
141 if (!ledgerIndex.isString() && !ledgerIndex.isUInt() && !ledgerIndex.isInt())
142 {
143 return {RpcInvalidParams, expectedFieldMessage(jss::ledger_index, "string or number")};
144 }
145 return ledgerFromIndex(ledger, ledgerIndex, context, jss::ledger_index);
146 }
147
148 // nothing specified, `index` has a default setting
149 return getLedger(ledger, LedgerShortcut::Current, context);
150}
151} // namespace
152
153template <class T, class R>
154Status
155ledgerFromRequest(T& ledger, GRPCContext<R> const& context)
156{
157 R const& request = context.params;
158 return ledgerFromSpecifier(ledger, request.ledger(), context);
159}
160
161// explicit instantiation of above function
162template Status
166
167// explicit instantiation of above function
168template Status
172
173// explicit instantiation of above function
174template Status
178
179template <class T>
180Status
182 T& ledger,
183 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
184 Context const& context)
185{
186 ledger.reset();
187
188 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
189 LedgerCase const ledgerCase = specifier.ledger_case();
190 switch (ledgerCase)
191 {
192 case LedgerCase::kHash: {
193 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
194 {
195 return getLedger(ledger, *hash, context);
196 }
197 return {RpcInvalidParams, "ledgerHashMalformed"};
198 }
199 case LedgerCase::kSequence:
200 return getLedger(ledger, specifier.sequence(), context);
201 case LedgerCase::kShortcut:
202 [[fallthrough]];
203 case LedgerCase::LEDGER_NOT_SET: {
204 auto const shortcut = specifier.shortcut();
205 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
206 {
207 return getLedger(ledger, LedgerShortcut::Validated, context);
208 }
209
210 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
211 shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED)
212 {
213 return getLedger(ledger, LedgerShortcut::Current, context);
214 }
215 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
216 {
217 return getLedger(ledger, LedgerShortcut::Closed, context);
218 }
219 }
220 }
221
222 return Status::kOK;
223}
224
225template <class T>
226Status
227getLedger(T& ledger, uint256 const& ledgerHash, Context const& context)
228{
229 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
230 if (ledger == nullptr)
231 return {RpcLgrNotFound, "ledgerNotFound"};
232 return Status::kOK;
233}
234
235template <class T>
236Status
237getLedger(T& ledger, uint32_t ledgerIndex, Context const& context)
238{
239 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
240 if (ledger == nullptr)
241 {
242 auto cur = context.ledgerMaster.getCurrentLedger();
243 if (cur->header().seq == ledgerIndex)
244 {
245 ledger = cur;
246 }
247 }
248
249 if (ledger == nullptr)
250 return {RpcLgrNotFound, "ledgerNotFound"};
251
252 if (ledger->header().seq > context.ledgerMaster.getValidLedgerIndex() &&
253 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
254 {
255 ledger.reset();
256 if (context.apiVersion == 1)
257 return {RpcNoNetwork, "InsufficientNetworkMode"};
258 return {RpcNotSynced, "notSynced"};
259 }
260
261 return Status::kOK;
262}
263
264template <class T>
265Status
266getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
267{
268 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
269 {
270 if (context.apiVersion == 1)
271 return {RpcNoNetwork, "InsufficientNetworkMode"};
272 return {RpcNotSynced, "notSynced"};
273 }
274
275 if (shortcut == LedgerShortcut::Validated)
276 {
277 ledger = context.ledgerMaster.getValidatedLedger();
278 if (ledger == nullptr)
279 {
280 if (context.apiVersion == 1)
281 return {RpcNoNetwork, "InsufficientNetworkMode"};
282 return {RpcNotSynced, "notSynced"};
283 }
284
285 XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : validated is not open");
286 }
287 else
288 {
289 if (shortcut == LedgerShortcut::Current)
290 {
291 ledger = context.ledgerMaster.getCurrentLedger();
292 XRPL_ASSERT(ledger->open(), "xrpl::rpc::getLedger : current is open");
293 }
294 else if (shortcut == LedgerShortcut::Closed)
295 {
296 ledger = context.ledgerMaster.getClosedLedger();
297 XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : closed is not open");
298 }
299 else
300 {
301 return {RpcInvalidParams, "ledgerIndexMalformed"};
302 }
303
304 if (ledger == nullptr)
305 {
306 if (context.apiVersion == 1)
307 return {RpcNoNetwork, "InsufficientNetworkMode"};
308 return {RpcNotSynced, "notSynced"};
309 }
310
311 static auto const kMinSequenceGap = 10;
312
313 if (ledger->header().seq + kMinSequenceGap < context.ledgerMaster.getValidLedgerIndex())
314 {
315 ledger.reset();
316 if (context.apiVersion == 1)
317 return {RpcNoNetwork, "InsufficientNetworkMode"};
318 return {RpcNotSynced, "notSynced"};
319 }
320 }
321 return Status::kOK;
322}
323
324// Explicit instantiation of above three functions
325template Status
327
328template Status
330
331template Status
333
334// explicit instantiation of ledgerFromSpecifier
335template Status
338 org::xrpl::rpc::v1::LedgerSpecifier const&,
339 Context const&);
340
341// The previous version of the lookupLedger command would accept the
342// "ledger_index" argument as a string and silently treat it as a request to
343// return the current ledger which, while not strictly wrong, could cause a lot
344// of confusion.
345//
346// The code now robustly validates the input and ensures that the only possible
347// values for the "ledger_index" parameter are the index of a ledger passed as
348// an integer or one of the strings "current", "closed" or "validated".
349// Additionally, the code ensures that the value passed in "ledger_hash" is a
350// string and a valid hash. Invalid values will return an appropriate error
351// code.
352//
353// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
354// assumes that "ledger_index" has the value "current".
355//
356// Returns a json::ValueType::Object. If there was an error, it will be in that
357// return value. Otherwise, the object contains the field "validated" and
358// optionally the fields "ledger_hash", "ledger_index" and
359// "ledger_current_index", if they are defined.
360Status
363 JsonContext const& context,
364 json::Value& result)
365{
366 if (auto status = ledgerFromRequest(ledger, context))
367 return status;
368
369 auto& info = ledger->header();
370
371 if (!ledger->open())
372 {
373 result[jss::ledger_hash] = to_string(info.hash);
374 result[jss::ledger_index] = info.seq;
375 }
376 else
377 {
378 result[jss::ledger_current_index] = info.seq;
379 }
380
381 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
382 return Status::kOK;
383}
384
387{
388 json::Value result;
389 if (auto status = lookupLedger(ledger, context, result))
390 status.inject(result);
391
392 return result;
393}
394
395std::expected<std::shared_ptr<Ledger const>, json::Value>
397{
398 auto const hasHash = context.params.isMember(jss::ledger_hash);
399 auto const hasIndex = context.params.isMember(jss::ledger_index);
400 std::uint32_t ledgerIndex = 0;
401
402 auto& ledgerMaster = context.app.getLedgerMaster();
403 LedgerHash ledgerHash;
404
405 if ((static_cast<int>(hasHash) + static_cast<int>(hasIndex)) != 1)
406 {
407 return std::unexpected(
409 "Exactly one of 'ledger_hash' or "
410 "'ledger_index' can be specified."));
411 }
412
413 if (hasHash)
414 {
415 auto const& jsonHash = context.params.get(jss::ledger_hash, json::ValueType::Null);
416 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
417 return std::unexpected(rpc::expectedFieldError(jss::ledger_hash, "hex string"));
418 }
419 else
420 {
421 auto const& jsonIndex = context.params.get(jss::ledger_index, json::ValueType::Null);
422 if (!jsonIndex.isInt() && !jsonIndex.isUInt())
423 return std::unexpected(rpc::expectedFieldError(jss::ledger_index, "number"));
424
425 // We need a validated ledger to get the hash from the sequence
426 if (ledgerMaster.getValidatedLedgerAge() > rpc::tuning::kMaxValidatedLedgerAge)
427 {
428 if (context.apiVersion == 1)
431 }
432
433 ledgerIndex = jsonIndex.asInt();
434 auto ledger = ledgerMaster.getValidatedLedger();
435
436 if (ledgerIndex >= ledger->header().seq)
437 return std::unexpected(rpc::makeParamError("Ledger index too large"));
438 if (ledgerIndex <= 0)
439 return std::unexpected(rpc::makeParamError("Ledger index too small"));
440
441 auto const j = context.app.getJournal("RPCHandler");
442 // Try to get the hash of the desired ledger from the validated
443 // ledger
444 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
445 if (!neededHash)
446 {
447 // Find a ledger more likely to have the hash of the desired
448 // ledger
449 auto const refIndex = getCandidateLedger(ledgerIndex);
450 auto refHash = hashOfSeq(*ledger, refIndex, j);
451 XRPL_ASSERT(refHash, "xrpl::rpc::getOrAcquireLedger : nonzero ledger hash");
452
453 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
454 ledger = ledgerMaster.getLedgerByHash(*refHash);
455 if (!ledger)
456 {
457 // We don't have the ledger we need to figure out which
458 // ledger they want. Try to get it.
459
460 if (auto il = context.app.getInboundLedgers().acquire(
461 *refHash, refIndex, InboundLedger::Reason::GENERIC))
462 {
463 json::Value jvResult = rpc::makeError(
464 RpcLgrNotFound, "acquiring ledger containing requested index");
465 jvResult[jss::acquiring] = getJson(LedgerFill(*il, &context));
466 return std::unexpected(jvResult);
467 }
468
469 if (auto il = context.app.getInboundLedgers().find(*refHash))
470 // NOLINTEND(bugprone-unchecked-optional-access)
471 {
472 json::Value jvResult = rpc::makeError(
473 RpcLgrNotFound, "acquiring ledger containing requested index");
474 jvResult[jss::acquiring] = il->getJson(0);
475 return std::unexpected(jvResult);
476 }
477
478 // Likely the app is shutting down
480 }
481
482 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
483 }
484 XRPL_ASSERT(neededHash, "xrpl::rpc::getOrAcquireLedger : nonzero needed hash");
485 ledgerHash = neededHash ? *neededHash : beast::kZero; // kludge
486 }
487
488 // Try to get the desired ledger
489 // Verify all nodes even if we think we have it
490 auto ledger = context.app.getInboundLedgers().acquire(
491 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
492
493 // In standalone mode, accept the ledger from the ledger cache
494 if (!ledger && context.app.config().standalone())
495 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
496
497 if (ledger)
498 return ledger;
499
500 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
501 return std::unexpected(il->getJson(0));
502
503 return std::unexpected(
504 rpc::makeError(RpcNotReady, "findCreate failed to return an inbound ledger"));
505}
506
507} // 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)
std::shared_ptr< Ledger const > getClosedLedger()
std::shared_ptr< Ledger const > getValidatedLedger()
bool isValidated(ReadView const &ledger)
LedgerIndex getValidLedgerIndex()
std::shared_ptr< ReadView const > getCurrentLedger()
std::shared_ptr< Ledger const > getLedgerByHash(uint256 const &hash)
virtual beast::Journal getJournal(std::string const &name)=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
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 ledgerFromRequest(T &ledger, GRPCContext< R > const &context)
Retrieves a ledger from a gRPC request context.
Status getLedger(T &ledger, uint256 const &ledgerHash, Context const &context)
Retrieves a ledger by its hash.
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.
@ 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
LedgerShortcut
Enumeration of ledger shortcuts for specifying which ledger to use.
@ Closed
The most recently closed ledger (may not be validated).
@ Current
The current working ledger (open, not yet closed).
@ Validated
The most recently validated ledger.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:124
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:279
uint256 LedgerHash
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
BaseUInt< 256 > uint256
Definition base_uint.h:580
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)