rippled
Loading...
Searching...
No Matches
RPCLedgerHelpers.cpp
1#include <xrpld/app/ledger/LedgerMaster.h>
2#include <xrpld/app/ledger/LedgerToJson.h>
3#include <xrpld/app/ledger/OpenLedger.h>
4#include <xrpld/app/main/Application.h>
5#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
6
7#include <xrpl/protocol/RPCErr.h>
8
9#include <boost/algorithm/string/case_conv.hpp>
10
11namespace xrpl {
12namespace RPC {
13
14namespace {
15
16bool
17isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
18{
19 if (standalone)
20 return false;
21
22 return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
23}
24
25template <class T>
27ledgerFromHash(
28 T& ledger,
29 Json::Value hash,
30 Context const& context,
31 Json::StaticString const fieldName)
32{
33 uint256 ledgerHash;
34 if (!ledgerHash.parseHex(hash.asString()))
35 return {
36 rpcINVALID_PARAMS, expected_field_message(fieldName, "hex string")};
37 return getLedger(ledger, ledgerHash, context);
38}
39
40template <class T>
42ledgerFromIndex(
43 T& ledger,
44 Json::Value indexValue,
45 Context const& context,
46 Json::StaticString const fieldName)
47{
48 auto const index = indexValue.asString();
49
50 if (index == "current" || index.empty())
51 return getLedger(ledger, LedgerShortcut::Current, context);
52
53 if (index == "validated")
54 return getLedger(ledger, LedgerShortcut::Validated, context);
55
56 if (index == "closed")
57 return getLedger(ledger, LedgerShortcut::Closed, context);
58
59 std::uint32_t iVal;
60 if (!beast::lexicalCastChecked(iVal, index))
61 return {
63 expected_field_message(fieldName, "string or number")};
64
65 return getLedger(ledger, iVal, context);
66}
67
68template <class T>
70ledgerFromRequest(T& ledger, JsonContext const& context)
71{
72 ledger.reset();
73
74 auto& params = context.params;
75 auto const hasLedger = context.params.isMember(jss::ledger);
76 auto const hasHash = context.params.isMember(jss::ledger_hash);
77 auto const hasIndex = context.params.isMember(jss::ledger_index);
78
79 if ((hasLedger + hasHash + hasIndex) > 1)
80 {
81 // while `ledger` is still supported, it is deprecated
82 // and therefore shouldn't be mentioned in the error message
83 if (hasLedger)
84 return {
86 "Exactly one of 'ledger', 'ledger_hash', or "
87 "'ledger_index' can be specified."};
88 return {
90 "Exactly one of 'ledger_hash' or "
91 "'ledger_index' can be specified."};
92 }
93
94 // We need to support the legacy "ledger" field.
95 if (hasLedger)
96 {
97 auto& legacyLedger = params[jss::ledger];
98 if (!legacyLedger.isString() && !legacyLedger.isUInt() &&
99 !legacyLedger.isInt())
100 {
101 return {
103 expected_field_message(jss::ledger, "string or number")};
104 }
105 if (legacyLedger.isString() && legacyLedger.asString().size() == 64)
106 return ledgerFromHash(ledger, legacyLedger, context, jss::ledger);
107 else
108 return ledgerFromIndex(ledger, legacyLedger, context, jss::ledger);
109 }
110
111 if (hasHash)
112 {
113 auto const& ledgerHash = params[jss::ledger_hash];
114 if (!ledgerHash.isString())
115 return {
117 expected_field_message(jss::ledger_hash, "hex string")};
118 return ledgerFromHash(ledger, ledgerHash, context, jss::ledger_hash);
119 }
120
121 if (hasIndex)
122 {
123 auto const& ledgerIndex = params[jss::ledger_index];
124 if (!ledgerIndex.isString() && !ledgerIndex.isUInt() &&
125 !ledgerIndex.isInt())
126 {
127 return {
129 expected_field_message(jss::ledger_index, "string or number")};
130 }
131 return ledgerFromIndex(ledger, ledgerIndex, context, jss::ledger_index);
132 }
133
134 // nothing specified, `index` has a default setting
135 return getLedger(ledger, LedgerShortcut::Current, context);
136}
137} // namespace
138
139template <class T, class R>
140Status
141ledgerFromRequest(T& ledger, GRPCContext<R> const& context)
142{
143 R const& request = context.params;
144 return ledgerFromSpecifier(ledger, request.ledger(), context);
145}
146
147// explicit instantiation of above function
148template Status
149ledgerFromRequest<>(
152
153// explicit instantiation of above function
154template Status
155ledgerFromRequest<>(
158
159// explicit instantiation of above function
160template Status
161ledgerFromRequest<>(
164
165template <class T>
166Status
168 T& ledger,
169 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
170 Context const& context)
171{
172 ledger.reset();
173
174 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
175 LedgerCase ledgerCase = specifier.ledger_case();
176 switch (ledgerCase)
177 {
178 case LedgerCase::kHash: {
179 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
180 {
181 return getLedger(ledger, *hash, context);
182 }
183 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
184 }
185 case LedgerCase::kSequence:
186 return getLedger(ledger, specifier.sequence(), context);
187 case LedgerCase::kShortcut:
188 [[fallthrough]];
189 case LedgerCase::LEDGER_NOT_SET: {
190 auto const shortcut = specifier.shortcut();
191 if (shortcut ==
192 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
193 {
194 return getLedger(ledger, LedgerShortcut::Validated, context);
195 }
196 else
197 {
198 if (shortcut ==
199 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
200 shortcut ==
201 org::xrpl::rpc::v1::LedgerSpecifier::
202 SHORTCUT_UNSPECIFIED)
203 {
204 return getLedger(ledger, LedgerShortcut::Current, context);
205 }
206 else if (
207 shortcut ==
208 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
209 {
210 return getLedger(ledger, LedgerShortcut::Closed, context);
211 }
212 }
213 }
214 }
215
216 return Status::OK;
217}
218
219template <class T>
220Status
221getLedger(T& ledger, uint256 const& ledgerHash, Context const& context)
222{
223 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
224 if (ledger == nullptr)
225 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
226 return Status::OK;
227}
228
229template <class T>
230Status
231getLedger(T& ledger, uint32_t ledgerIndex, Context const& context)
232{
233 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
234 if (ledger == nullptr)
235 {
236 auto cur = context.ledgerMaster.getCurrentLedger();
237 if (cur->header().seq == ledgerIndex)
238 {
239 ledger = cur;
240 }
241 }
242
243 if (ledger == nullptr)
244 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
245
246 if (ledger->header().seq > context.ledgerMaster.getValidLedgerIndex() &&
247 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
248 {
249 ledger.reset();
250 if (context.apiVersion == 1)
251 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
252 return {rpcNOT_SYNCED, "notSynced"};
253 }
254
255 return Status::OK;
256}
257
258template <class T>
259Status
260getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
261{
262 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
263 {
264 if (context.apiVersion == 1)
265 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
266 return {rpcNOT_SYNCED, "notSynced"};
267 }
268
269 if (shortcut == LedgerShortcut::Validated)
270 {
271 ledger = context.ledgerMaster.getValidatedLedger();
272 if (ledger == nullptr)
273 {
274 if (context.apiVersion == 1)
275 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
276 return {rpcNOT_SYNCED, "notSynced"};
277 }
278
279 XRPL_ASSERT(
280 !ledger->open(), "xrpl::RPC::getLedger : validated is not open");
281 }
282 else
283 {
284 if (shortcut == LedgerShortcut::Current)
285 {
286 ledger = context.ledgerMaster.getCurrentLedger();
287 XRPL_ASSERT(
288 ledger->open(), "xrpl::RPC::getLedger : current is open");
289 }
290 else if (shortcut == LedgerShortcut::Closed)
291 {
292 ledger = context.ledgerMaster.getClosedLedger();
293 XRPL_ASSERT(
294 !ledger->open(), "xrpl::RPC::getLedger : closed is not open");
295 }
296 else
297 {
298 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
299 }
300
301 if (ledger == nullptr)
302 {
303 if (context.apiVersion == 1)
304 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
305 return {rpcNOT_SYNCED, "notSynced"};
306 }
307
308 static auto const minSequenceGap = 10;
309
310 if (ledger->header().seq + minSequenceGap <
312 {
313 ledger.reset();
314 if (context.apiVersion == 1)
315 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
316 return {rpcNOT_SYNCED, "notSynced"};
317 }
318 }
319 return Status::OK;
320}
321
322// Explicit instantiation of above three functions
323template Status
324getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context const&);
325
326template Status
327getLedger<>(
329 LedgerShortcut shortcut,
330 Context const&);
331
332template Status
333getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context const&);
334
335// The previous version of the lookupLedger command would accept the
336// "ledger_index" argument as a string and silently treat it as a request to
337// return the current ledger which, while not strictly wrong, could cause a lot
338// of confusion.
339//
340// The code now robustly validates the input and ensures that the only possible
341// values for the "ledger_index" parameter are the index of a ledger passed as
342// an integer or one of the strings "current", "closed" or "validated".
343// Additionally, the code ensures that the value passed in "ledger_hash" is a
344// string and a valid hash. Invalid values will return an appropriate error
345// code.
346//
347// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
348// assumes that "ledger_index" has the value "current".
349//
350// Returns a Json::objectValue. If there was an error, it will be in that
351// return value. Otherwise, the object contains the field "validated" and
352// optionally the fields "ledger_hash", "ledger_index" and
353// "ledger_current_index", if they are defined.
354Status
357 JsonContext const& context,
358 Json::Value& result)
359{
360 if (auto status = ledgerFromRequest(ledger, context))
361 return status;
362
363 auto& info = ledger->header();
364
365 if (!ledger->open())
366 {
367 result[jss::ledger_hash] = to_string(info.hash);
368 result[jss::ledger_index] = info.seq;
369 }
370 else
371 {
372 result[jss::ledger_current_index] = info.seq;
373 }
374
375 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
376 return Status::OK;
377}
378
382 JsonContext const& context)
383{
384 Json::Value result;
385 if (auto status = lookupLedger(ledger, context, result))
386 status.inject(result);
387
388 return result;
389}
390
393{
394 auto const hasHash = context.params.isMember(jss::ledger_hash);
395 auto const hasIndex = context.params.isMember(jss::ledger_index);
396 std::uint32_t ledgerIndex = 0;
397
398 auto& ledgerMaster = context.app.getLedgerMaster();
399 LedgerHash ledgerHash;
400
401 if ((hasHash + hasIndex) != 1)
402 {
403 return Unexpected(
404 RPC::make_param_error("Exactly one of 'ledger_hash' or "
405 "'ledger_index' can be specified."));
406 }
407
408 if (hasHash)
409 {
410 auto const& jsonHash =
411 context.params.get(jss::ledger_hash, Json::nullValue);
412 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
413 return Unexpected(
414 RPC::expected_field_error(jss::ledger_hash, "hex string"));
415 }
416 else
417 {
418 auto const& jsonIndex =
419 context.params.get(jss::ledger_index, Json::nullValue);
420 if (!jsonIndex.isInt() && !jsonIndex.isUInt())
421 return Unexpected(
422 RPC::expected_field_error(jss::ledger_index, "number"));
423
424 // We need a validated ledger to get the hash from the sequence
425 if (ledgerMaster.getValidatedLedgerAge() >
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 Unexpected(RPC::make_param_error("Ledger index too large"));
438 if (ledgerIndex <= 0)
439 return Unexpected(RPC::make_param_error("Ledger index too small"));
440
441 auto const j = context.app.journal("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(
452 refHash, "xrpl::RPC::getOrAcquireLedger : nonzero ledger hash");
453
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::make_error(
465 "acquiring ledger containing requested index");
466 jvResult[jss::acquiring] =
467 getJson(LedgerFill(*il, &context));
468 return Unexpected(jvResult);
469 }
470
471 if (auto il = context.app.getInboundLedgers().find(*refHash))
472 {
473 Json::Value jvResult = RPC::make_error(
475 "acquiring ledger containing requested index");
476 jvResult[jss::acquiring] = il->getJson(0);
477 return Unexpected(jvResult);
478 }
479
480 // Likely the app is shutting down
481 return Unexpected(Json::Value());
482 }
483
484 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
485 }
486 XRPL_ASSERT(
487 neededHash, "xrpl::RPC::getOrAcquireLedger : nonzero needed hash");
488 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
489 }
490
491 // Try to get the desired ledger
492 // Verify all nodes even if we think we have it
493 auto ledger = context.app.getInboundLedgers().acquire(
494 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
495
496 // In standalone mode, accept the ledger from the ledger cache
497 if (!ledger && context.app.config().standalone())
498 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
499
500 if (ledger)
501 return ledger;
502
503 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
504 return Unexpected(il->getJson(0));
505
507 rpcNOT_READY, "findCreate failed to return an inbound ledger"));
508}
509
510} // namespace RPC
511} // namespace xrpl
Lightweight wrapper to tag static string.
Definition json_value.h:45
Represents a JSON value.
Definition json_value.h:131
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.
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
virtual Config & config()=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual beast::Journal journal(std::string const &name)=0
bool standalone() const
Definition Config.h:318
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)
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:484
static std::optional< base_uint > fromVoidChecked(T const &from)
Definition base_uint.h:307
@ nullValue
'null' value
Definition json_value.h:20
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Status
Return codes from Backend operations.
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:296
Status ledgerFromSpecifier(T &ledger, org::xrpl::rpc::v1::LedgerSpecifier const &specifier, Context const &context)
Retrieves a ledger based on a LedgerSpecifier.
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.
Json::Value expected_field_error(std::string const &name, std::string const &type)
Definition ErrorCodes.h:308
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:230
Status ledgerFromRequest(T &ledger, GRPCContext< R > const &context)
Retrieves a ledger from a gRPC request context.
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.
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Status getLedger(T &ledger, uint256 const &ledgerHash, Context const &context)
Retrieves a ledger by its hash.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
base_uint< 256 > uint256
Definition base_uint.h:539
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ledgerMaster
ledger master data for signing
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:53
@ rpcNO_NETWORK
Definition ErrorCodes.h:47
@ rpcNO_CURRENT
Definition ErrorCodes.h:46
@ rpcNOT_SYNCED
Definition ErrorCodes.h:48
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcNOT_READY
Definition ErrorCodes.h:41
The context of information needed to call an RPC.
Definition Context.h:20
Application & app
Definition Context.h:22
unsigned int apiVersion
Definition Context.h:30
LedgerMaster & ledgerMaster
Definition Context.h:25
RequestType params
Definition Context.h:52
Json::Value params
Definition Context.h:44
Status represents the results of an operation that might fail.
Definition Status.h:21
static constexpr Code OK
Definition Status.h:27