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 {rpcINVALID_PARAMS, expected_field_message(fieldName, "hex string")};
36 return getLedger(ledger, ledgerHash, context);
37}
38
39template <class T>
41ledgerFromIndex(
42 T& ledger,
43 Json::Value indexValue,
44 Context const& context,
45 Json::StaticString const fieldName)
46{
47 auto const index = indexValue.asString();
48
49 if (index == "current" || index.empty())
50 return getLedger(ledger, LedgerShortcut::Current, context);
51
52 if (index == "validated")
53 return getLedger(ledger, LedgerShortcut::Validated, context);
54
55 if (index == "closed")
56 return getLedger(ledger, LedgerShortcut::Closed, context);
57
58 std::uint32_t iVal = 0;
59 if (!beast::lexicalCastChecked(iVal, index))
60 return {rpcINVALID_PARAMS, expected_field_message(fieldName, "string or number")};
61
62 return getLedger(ledger, iVal, context);
63}
64
65template <class T>
67ledgerFromRequest(T& ledger, JsonContext const& context)
68{
69 ledger.reset();
70
71 auto& params = context.params;
72 auto const hasLedger = context.params.isMember(jss::ledger);
73 auto const hasHash = context.params.isMember(jss::ledger_hash);
74 auto const hasIndex = context.params.isMember(jss::ledger_index);
75
76 if ((hasLedger + hasHash + hasIndex) > 1)
77 {
78 // while `ledger` is still supported, it is deprecated
79 // and therefore shouldn't be mentioned in the error message
80 if (hasLedger)
81 {
82 return {
84 "Exactly one of 'ledger', 'ledger_hash', or "
85 "'ledger_index' can be specified."};
86 }
87 return {
89 "Exactly one of 'ledger_hash' or "
90 "'ledger_index' can be specified."};
91 }
92
93 // We need to support the legacy "ledger" field.
94 if (hasLedger)
95 {
96 auto& legacyLedger = params[jss::ledger];
97 if (!legacyLedger.isString() && !legacyLedger.isUInt() && !legacyLedger.isInt())
98 {
99 return {rpcINVALID_PARAMS, expected_field_message(jss::ledger, "string or number")};
100 }
101 if (legacyLedger.isString() && legacyLedger.asString().size() == 64)
102 {
103 return ledgerFromHash(ledger, legacyLedger, context, jss::ledger);
104 }
105
106 return ledgerFromIndex(ledger, legacyLedger, context, jss::ledger);
107 }
108
109 if (hasHash)
110 {
111 auto const& ledgerHash = params[jss::ledger_hash];
112 if (!ledgerHash.isString())
113 return {rpcINVALID_PARAMS, expected_field_message(jss::ledger_hash, "hex string")};
114 return ledgerFromHash(ledger, ledgerHash, context, jss::ledger_hash);
115 }
116
117 if (hasIndex)
118 {
119 auto const& ledgerIndex = params[jss::ledger_index];
120 if (!ledgerIndex.isString() && !ledgerIndex.isUInt() && !ledgerIndex.isInt())
121 {
122 return {
123 rpcINVALID_PARAMS, expected_field_message(jss::ledger_index, "string or number")};
124 }
125 return ledgerFromIndex(ledger, ledgerIndex, context, jss::ledger_index);
126 }
127
128 // nothing specified, `index` has a default setting
129 return getLedger(ledger, LedgerShortcut::Current, context);
130}
131} // namespace
132
133template <class T, class R>
134Status
135ledgerFromRequest(T& ledger, GRPCContext<R> const& context)
136{
137 R const& request = context.params;
138 return ledgerFromSpecifier(ledger, request.ledger(), context);
139}
140
141// explicit instantiation of above function
142template Status
143ledgerFromRequest<>(
146
147// explicit instantiation of above function
148template Status
149ledgerFromRequest<>(
152
153// explicit instantiation of above function
154template Status
155ledgerFromRequest<>(
158
159template <class T>
160Status
162 T& ledger,
163 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
164 Context const& context)
165{
166 ledger.reset();
167
168 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
169 LedgerCase const ledgerCase = specifier.ledger_case();
170 switch (ledgerCase)
171 {
172 case LedgerCase::kHash: {
173 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
174 {
175 return getLedger(ledger, *hash, context);
176 }
177 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
178 }
179 case LedgerCase::kSequence:
180 return getLedger(ledger, specifier.sequence(), context);
181 case LedgerCase::kShortcut:
182 [[fallthrough]];
183 case LedgerCase::LEDGER_NOT_SET: {
184 auto const shortcut = specifier.shortcut();
185 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
186 {
187 return getLedger(ledger, LedgerShortcut::Validated, context);
188 }
189
190 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
191 shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED)
192 {
193 return getLedger(ledger, LedgerShortcut::Current, context);
194 }
195 if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
196 {
197 return getLedger(ledger, LedgerShortcut::Closed, context);
198 }
199 }
200 }
201
202 return Status::OK;
203}
204
205template <class T>
206Status
207getLedger(T& ledger, uint256 const& ledgerHash, Context const& context)
208{
209 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
210 if (ledger == nullptr)
211 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
212 return Status::OK;
213}
214
215template <class T>
216Status
217getLedger(T& ledger, uint32_t ledgerIndex, Context const& context)
218{
219 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
220 if (ledger == nullptr)
221 {
222 auto cur = context.ledgerMaster.getCurrentLedger();
223 if (cur->header().seq == ledgerIndex)
224 {
225 ledger = cur;
226 }
227 }
228
229 if (ledger == nullptr)
230 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
231
232 if (ledger->header().seq > context.ledgerMaster.getValidLedgerIndex() &&
233 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
234 {
235 ledger.reset();
236 if (context.apiVersion == 1)
237 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
238 return {rpcNOT_SYNCED, "notSynced"};
239 }
240
241 return Status::OK;
242}
243
244template <class T>
245Status
246getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
247{
248 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
249 {
250 if (context.apiVersion == 1)
251 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
252 return {rpcNOT_SYNCED, "notSynced"};
253 }
254
255 if (shortcut == LedgerShortcut::Validated)
256 {
257 ledger = context.ledgerMaster.getValidatedLedger();
258 if (ledger == nullptr)
259 {
260 if (context.apiVersion == 1)
261 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
262 return {rpcNOT_SYNCED, "notSynced"};
263 }
264
265 XRPL_ASSERT(!ledger->open(), "xrpl::RPC::getLedger : validated is not open");
266 }
267 else
268 {
269 if (shortcut == LedgerShortcut::Current)
270 {
271 ledger = context.ledgerMaster.getCurrentLedger();
272 XRPL_ASSERT(ledger->open(), "xrpl::RPC::getLedger : current is open");
273 }
274 else if (shortcut == LedgerShortcut::Closed)
275 {
276 ledger = context.ledgerMaster.getClosedLedger();
277 XRPL_ASSERT(!ledger->open(), "xrpl::RPC::getLedger : closed is not open");
278 }
279 else
280 {
281 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
282 }
283
284 if (ledger == nullptr)
285 {
286 if (context.apiVersion == 1)
287 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
288 return {rpcNOT_SYNCED, "notSynced"};
289 }
290
291 static auto const minSequenceGap = 10;
292
293 if (ledger->header().seq + minSequenceGap < context.ledgerMaster.getValidLedgerIndex())
294 {
295 ledger.reset();
296 if (context.apiVersion == 1)
297 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
298 return {rpcNOT_SYNCED, "notSynced"};
299 }
300 }
301 return Status::OK;
302}
303
304// Explicit instantiation of above three functions
305template Status
306getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context const&);
307
308template Status
309getLedger<>(std::shared_ptr<ReadView const>&, LedgerShortcut shortcut, Context const&);
310
311template Status
312getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context const&);
313
314// The previous version of the lookupLedger command would accept the
315// "ledger_index" argument as a string and silently treat it as a request to
316// return the current ledger which, while not strictly wrong, could cause a lot
317// of confusion.
318//
319// The code now robustly validates the input and ensures that the only possible
320// values for the "ledger_index" parameter are the index of a ledger passed as
321// an integer or one of the strings "current", "closed" or "validated".
322// Additionally, the code ensures that the value passed in "ledger_hash" is a
323// string and a valid hash. Invalid values will return an appropriate error
324// code.
325//
326// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
327// assumes that "ledger_index" has the value "current".
328//
329// Returns a Json::objectValue. If there was an error, it will be in that
330// return value. Otherwise, the object contains the field "validated" and
331// optionally the fields "ledger_hash", "ledger_index" and
332// "ledger_current_index", if they are defined.
333Status
336 JsonContext const& context,
337 Json::Value& result)
338{
339 if (auto status = ledgerFromRequest(ledger, context))
340 return status;
341
342 auto& info = ledger->header();
343
344 if (!ledger->open())
345 {
346 result[jss::ledger_hash] = to_string(info.hash);
347 result[jss::ledger_index] = info.seq;
348 }
349 else
350 {
351 result[jss::ledger_current_index] = info.seq;
352 }
353
354 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
355 return Status::OK;
356}
357
360{
361 Json::Value result;
362 if (auto status = lookupLedger(ledger, context, result))
363 status.inject(result);
364
365 return result;
366}
367
370{
371 auto const hasHash = context.params.isMember(jss::ledger_hash);
372 auto const hasIndex = context.params.isMember(jss::ledger_index);
373 std::uint32_t ledgerIndex = 0;
374
375 auto& ledgerMaster = context.app.getLedgerMaster();
376 LedgerHash ledgerHash;
377
378 if ((static_cast<int>(hasHash) + static_cast<int>(hasIndex)) != 1)
379 {
380 return Unexpected(
382 "Exactly one of 'ledger_hash' or "
383 "'ledger_index' can be specified."));
384 }
385
386 if (hasHash)
387 {
388 auto const& jsonHash = context.params.get(jss::ledger_hash, Json::nullValue);
389 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
390 return Unexpected(RPC::expected_field_error(jss::ledger_hash, "hex string"));
391 }
392 else
393 {
394 auto const& jsonIndex = context.params.get(jss::ledger_index, Json::nullValue);
395 if (!jsonIndex.isInt() && !jsonIndex.isUInt())
396 return Unexpected(RPC::expected_field_error(jss::ledger_index, "number"));
397
398 // We need a validated ledger to get the hash from the sequence
399 if (ledgerMaster.getValidatedLedgerAge() > RPC::Tuning::maxValidatedLedgerAge)
400 {
401 if (context.apiVersion == 1)
404 }
405
406 ledgerIndex = jsonIndex.asInt();
407 auto ledger = ledgerMaster.getValidatedLedger();
408
409 if (ledgerIndex >= ledger->header().seq)
410 return Unexpected(RPC::make_param_error("Ledger index too large"));
411 if (ledgerIndex <= 0)
412 return Unexpected(RPC::make_param_error("Ledger index too small"));
413
414 auto const j = context.app.getJournal("RPCHandler");
415 // Try to get the hash of the desired ledger from the validated
416 // ledger
417 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
418 if (!neededHash)
419 {
420 // Find a ledger more likely to have the hash of the desired
421 // ledger
422 auto const refIndex = getCandidateLedger(ledgerIndex);
423 auto refHash = hashOfSeq(*ledger, refIndex, j);
424 XRPL_ASSERT(refHash, "xrpl::RPC::getOrAcquireLedger : nonzero ledger hash");
425
426 ledger = ledgerMaster.getLedgerByHash(*refHash);
427 if (!ledger)
428 {
429 // We don't have the ledger we need to figure out which
430 // ledger they want. Try to get it.
431
432 if (auto il = context.app.getInboundLedgers().acquire(
433 *refHash, refIndex, InboundLedger::Reason::GENERIC))
434 {
435 Json::Value jvResult = RPC::make_error(
436 rpcLGR_NOT_FOUND, "acquiring ledger containing requested index");
437 jvResult[jss::acquiring] = getJson(LedgerFill(*il, &context));
438 return Unexpected(jvResult);
439 }
440
441 if (auto il = context.app.getInboundLedgers().find(*refHash))
442 {
443 Json::Value jvResult = RPC::make_error(
444 rpcLGR_NOT_FOUND, "acquiring ledger containing requested index");
445 jvResult[jss::acquiring] = il->getJson(0);
446 return Unexpected(jvResult);
447 }
448
449 // Likely the app is shutting down
450 return Unexpected(Json::Value());
451 }
452
453 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
454 }
455 XRPL_ASSERT(neededHash, "xrpl::RPC::getOrAcquireLedger : nonzero needed hash");
456 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
457 }
458
459 // Try to get the desired ledger
460 // Verify all nodes even if we think we have it
461 auto ledger = context.app.getInboundLedgers().acquire(
462 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
463
464 // In standalone mode, accept the ledger from the ledger cache
465 if (!ledger && context.app.config().standalone())
466 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
467
468 if (ledger)
469 return ledger;
470
471 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
472 return Unexpected(il->getJson(0));
473
474 return Unexpected(
475 RPC::make_error(rpcNOT_READY, "findCreate failed to return an inbound ledger"));
476}
477
478} // namespace RPC
479} // namespace xrpl
Lightweight wrapper to tag static string.
Definition json_value.h:44
Represents a JSON value.
Definition json_value.h:130
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
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
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:476
static std::optional< base_uint > fromVoidChecked(T const &from)
Definition base_uint.h:300
@ nullValue
'null' value
Definition json_value.h:19
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:285
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:297
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:219
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:5
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:602
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.
base_uint< 256 > uint256
Definition base_uint.h:531
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:103
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:232
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ledgerMaster
ledger master data for signing
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:52
@ rpcNO_NETWORK
Definition ErrorCodes.h:46
@ rpcNO_CURRENT
Definition ErrorCodes.h:45
@ rpcNOT_SYNCED
Definition ErrorCodes.h:47
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:64
@ rpcNOT_READY
Definition ErrorCodes.h:40
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:20
static constexpr Code OK
Definition Status.h:26