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