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/misc/Transaction.h>
5#include <xrpld/app/paths/TrustLine.h>
6#include <xrpld/app/rdb/RelationalDatabase.h>
7#include <xrpld/app/tx/detail/NFTokenUtils.h>
8#include <xrpld/rpc/Context.h>
9#include <xrpld/rpc/DeliveredAmount.h>
10#include <xrpld/rpc/detail/RPCHelpers.h>
11
12#include <xrpl/ledger/View.h>
13#include <xrpl/protocol/AccountID.h>
14#include <xrpl/protocol/RPCErr.h>
15#include <xrpl/protocol/nftPageMask.h>
16#include <xrpl/resource/Fees.h>
17
18#include <boost/algorithm/string/case_conv.hpp>
19#include <boost/algorithm/string/predicate.hpp>
20
21namespace ripple {
22namespace RPC {
23
24namespace {
25
26bool
27isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
28{
29 if (standalone)
30 return false;
31
32 return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
33}
34
35template <class T>
37ledgerFromRequest(T& ledger, JsonContext& context)
38{
39 ledger.reset();
40
41 auto& params = context.params;
42
43 auto indexValue = params[jss::ledger_index];
44 auto hashValue = params[jss::ledger_hash];
45
46 // We need to support the legacy "ledger" field.
47 auto& legacyLedger = params[jss::ledger];
48 if (legacyLedger)
49 {
50 if (legacyLedger.asString().size() > 12)
51 hashValue = legacyLedger;
52 else
53 indexValue = legacyLedger;
54 }
55
56 if (!hashValue.isNull())
57 {
58 if (!hashValue.isString())
59 return {rpcINVALID_PARAMS, "ledgerHashNotString"};
60
61 uint256 ledgerHash;
62 if (!ledgerHash.parseHex(hashValue.asString()))
63 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
64 return getLedger(ledger, ledgerHash, context);
65 }
66
67 if (!indexValue.isConvertibleTo(Json::stringValue))
68 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
69
70 auto const index = indexValue.asString();
71
72 if (index == "current" || index.empty())
73 return getLedger(ledger, LedgerShortcut::CURRENT, context);
74
75 if (index == "validated")
76 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
77
78 if (index == "closed")
79 return getLedger(ledger, LedgerShortcut::CLOSED, context);
80
81 std::uint32_t val;
82 if (!beast::lexicalCastChecked(val, index))
83 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
84
85 return getLedger(ledger, val, context);
86}
87} // namespace
88
89template <class T, class R>
92{
93 R& request = context.params;
94 return ledgerFromSpecifier(ledger, request.ledger(), context);
95}
96
97// explicit instantiation of above function
98template Status
99ledgerFromRequest<>(
102
103// explicit instantiation of above function
104template Status
105ledgerFromRequest<>(
108
109// explicit instantiation of above function
110template Status
111ledgerFromRequest<>(
114
115template <class T>
116Status
118 T& ledger,
119 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
120 Context& context)
121{
122 ledger.reset();
123
124 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
125 LedgerCase ledgerCase = specifier.ledger_case();
126 switch (ledgerCase)
127 {
128 case LedgerCase::kHash: {
129 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
130 {
131 return getLedger(ledger, *hash, context);
132 }
133 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
134 }
135 case LedgerCase::kSequence:
136 return getLedger(ledger, specifier.sequence(), context);
137 case LedgerCase::kShortcut:
138 [[fallthrough]];
139 case LedgerCase::LEDGER_NOT_SET: {
140 auto const shortcut = specifier.shortcut();
141 if (shortcut ==
142 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
143 {
144 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
145 }
146 else
147 {
148 if (shortcut ==
149 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
150 shortcut ==
151 org::xrpl::rpc::v1::LedgerSpecifier::
152 SHORTCUT_UNSPECIFIED)
153 {
154 return getLedger(ledger, LedgerShortcut::CURRENT, context);
155 }
156 else if (
157 shortcut ==
158 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
159 {
160 return getLedger(ledger, LedgerShortcut::CLOSED, context);
161 }
162 }
163 }
164 }
165
166 return Status::OK;
167}
168
169template <class T>
170Status
171getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
172{
173 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
174 if (ledger == nullptr)
175 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
176 return Status::OK;
177}
178
179template <class T>
180Status
181getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
182{
183 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
184 if (ledger == nullptr)
185 {
186 auto cur = context.ledgerMaster.getCurrentLedger();
187 if (cur->info().seq == ledgerIndex)
188 {
189 ledger = cur;
190 }
191 }
192
193 if (ledger == nullptr)
194 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
195
196 if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
197 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
198 {
199 ledger.reset();
200 if (context.apiVersion == 1)
201 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
202 return {rpcNOT_SYNCED, "notSynced"};
203 }
204
205 return Status::OK;
206}
207
208template <class T>
209Status
210getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
211{
212 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
213 {
214 if (context.apiVersion == 1)
215 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
216 return {rpcNOT_SYNCED, "notSynced"};
217 }
218
219 if (shortcut == LedgerShortcut::VALIDATED)
220 {
221 ledger = context.ledgerMaster.getValidatedLedger();
222 if (ledger == nullptr)
223 {
224 if (context.apiVersion == 1)
225 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
226 return {rpcNOT_SYNCED, "notSynced"};
227 }
228
229 XRPL_ASSERT(
230 !ledger->open(), "ripple::RPC::getLedger : validated is not open");
231 }
232 else
233 {
234 if (shortcut == LedgerShortcut::CURRENT)
235 {
236 ledger = context.ledgerMaster.getCurrentLedger();
237 XRPL_ASSERT(
238 ledger->open(), "ripple::RPC::getLedger : current is open");
239 }
240 else if (shortcut == LedgerShortcut::CLOSED)
241 {
242 ledger = context.ledgerMaster.getClosedLedger();
243 XRPL_ASSERT(
244 !ledger->open(), "ripple::RPC::getLedger : closed is not open");
245 }
246 else
247 {
248 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
249 }
250
251 if (ledger == nullptr)
252 {
253 if (context.apiVersion == 1)
254 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
255 return {rpcNOT_SYNCED, "notSynced"};
256 }
257
258 static auto const minSequenceGap = 10;
259
260 if (ledger->info().seq + minSequenceGap <
262 {
263 ledger.reset();
264 if (context.apiVersion == 1)
265 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
266 return {rpcNOT_SYNCED, "notSynced"};
267 }
268 }
269 return Status::OK;
270}
271
272// Explicit instantiation of above three functions
273template Status
274getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
275
276template Status
277getLedger<>(
279 LedgerShortcut shortcut,
280 Context&);
281
282template Status
283getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
284
285// The previous version of the lookupLedger command would accept the
286// "ledger_index" argument as a string and silently treat it as a request to
287// return the current ledger which, while not strictly wrong, could cause a lot
288// of confusion.
289//
290// The code now robustly validates the input and ensures that the only possible
291// values for the "ledger_index" parameter are the index of a ledger passed as
292// an integer or one of the strings "current", "closed" or "validated".
293// Additionally, the code ensures that the value passed in "ledger_hash" is a
294// string and a valid hash. Invalid values will return an appropriate error
295// code.
296//
297// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
298// assumes that "ledger_index" has the value "current".
299//
300// Returns a Json::objectValue. If there was an error, it will be in that
301// return value. Otherwise, the object contains the field "validated" and
302// optionally the fields "ledger_hash", "ledger_index" and
303// "ledger_current_index", if they are defined.
304Status
307 JsonContext& context,
308 Json::Value& result)
309{
310 if (auto status = ledgerFromRequest(ledger, context))
311 return status;
312
313 auto& info = ledger->info();
314
315 if (!ledger->open())
316 {
317 result[jss::ledger_hash] = to_string(info.hash);
318 result[jss::ledger_index] = info.seq;
319 }
320 else
321 {
322 result[jss::ledger_current_index] = info.seq;
323 }
324
325 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
326 return Status::OK;
327}
328
331{
332 Json::Value result;
333 if (auto status = lookupLedger(ledger, context, result))
334 status.inject(result);
335
336 return result;
337}
338
341{
342 auto const hasHash = context.params.isMember(jss::ledger_hash);
343 auto const hasIndex = context.params.isMember(jss::ledger_index);
344 std::uint32_t ledgerIndex = 0;
345
346 auto& ledgerMaster = context.app.getLedgerMaster();
347 LedgerHash ledgerHash;
348
349 if ((hasHash && hasIndex) || !(hasHash || hasIndex))
350 {
352 "Exactly one of ledger_hash and ledger_index can be set.");
353 }
354
356
357 if (hasHash)
358 {
359 auto const& jsonHash = context.params[jss::ledger_hash];
360 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
361 return RPC::invalid_field_error(jss::ledger_hash);
362 }
363 else
364 {
365 auto const& jsonIndex = context.params[jss::ledger_index];
366 if (!jsonIndex.isInt())
367 return RPC::invalid_field_error(jss::ledger_index);
368
369 // We need a validated ledger to get the hash from the sequence
370 if (ledgerMaster.getValidatedLedgerAge() >
372 {
373 if (context.apiVersion == 1)
374 return rpcError(rpcNO_CURRENT);
375 return rpcError(rpcNOT_SYNCED);
376 }
377
378 ledgerIndex = jsonIndex.asInt();
379 auto ledger = ledgerMaster.getValidatedLedger();
380
381 if (ledgerIndex >= ledger->info().seq)
382 return RPC::make_param_error("Ledger index too large");
383 if (ledgerIndex <= 0)
384 return RPC::make_param_error("Ledger index too small");
385
386 auto const j = context.app.journal("RPCHandler");
387 // Try to get the hash of the desired ledger from the validated
388 // ledger
389 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
390 if (!neededHash)
391 {
392 // Find a ledger more likely to have the hash of the desired
393 // ledger
394 auto const refIndex = getCandidateLedger(ledgerIndex);
395 auto refHash = hashOfSeq(*ledger, refIndex, j);
396 XRPL_ASSERT(
397 refHash,
398 "ripple::RPC::getLedgerByContext : nonzero ledger hash");
399
400 ledger = ledgerMaster.getLedgerByHash(*refHash);
401 if (!ledger)
402 {
403 // We don't have the ledger we need to figure out which
404 // ledger they want. Try to get it.
405
406 if (auto il = context.app.getInboundLedgers().acquire(
407 *refHash, refIndex, InboundLedger::Reason::GENERIC))
408 {
409 Json::Value jvResult = RPC::make_error(
411 "acquiring ledger containing requested index");
412 jvResult[jss::acquiring] =
413 getJson(LedgerFill(*il, &context));
414 return jvResult;
415 }
416
417 if (auto il = context.app.getInboundLedgers().find(*refHash))
418 {
419 Json::Value jvResult = RPC::make_error(
421 "acquiring ledger containing requested index");
422 jvResult[jss::acquiring] = il->getJson(0);
423 return jvResult;
424 }
425
426 // Likely the app is shutting down
427 return Json::Value();
428 }
429
430 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
431 }
432 XRPL_ASSERT(
433 neededHash,
434 "ripple::RPC::getLedgerByContext : nonzero needed hash");
435 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
436 }
437
438 // Try to get the desired ledger
439 // Verify all nodes even if we think we have it
440 auto ledger = context.app.getInboundLedgers().acquire(
441 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
442
443 // In standalone mode, accept the ledger from the ledger cache
444 if (!ledger && context.app.config().standalone())
445 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
446
447 if (ledger)
448 return ledger;
449
450 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
451 return il->getJson(0);
452
453 return RPC::make_error(
454 rpcNOT_READY, "findCreate failed to return an inbound ledger");
455}
456
457} // namespace RPC
458} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
bool isMember(char const *key) const
Return true if the object has a member named key.
virtual Config & config()=0
virtual beast::Journal journal(std::string const &name)=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
bool standalone() const
Definition Config.h:317
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 > getValidatedLedger()
std::shared_ptr< ReadView const > getCurrentLedger()
bool isValidated(ReadView const &ledger)
std::shared_ptr< Ledger const > getClosedLedger()
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
std::shared_ptr< Ledger const > getLedgerByHash(uint256 const &hash)
LedgerIndex getValidLedgerIndex()
static std::optional< base_uint > fromVoidChecked(T const &from)
Definition base_uint.h:307
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:484
@ stringValue
UTF-8 string value.
Definition json_value.h:24
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Status
Return codes from Backend operations.
Status ledgerFromRequest(T &ledger, GRPCContext< R > &context)
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
std::variant< std::shared_ptr< Ledger const >, Json::Value > getLedgerByContext(RPC::JsonContext &context)
Return a ledger based on ledger_hash or ledger_index, or an RPC error.
Json::Value invalid_field_error(std::string const &name)
Definition ErrorCodes.h:306
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:252
Status getLedger(T &ledger, uint256 const &ledgerHash, Context &context)
Get ledger by hash If there is no error in the return value, the ledger pointer will have been filled...
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext &context, Json::Value &result)
Look up a ledger from a request and fill a Json::Result with the data representing a ledger.
Status ledgerFromSpecifier(T &ledger, org::xrpl::rpc::v1::LedgerSpecifier const &specifier, Context &context)
Charge const feeHeavyBurdenRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:410
@ rpcNO_NETWORK
Definition ErrorCodes.h:47
@ rpcNOT_READY
Definition ErrorCodes.h:41
@ rpcNO_CURRENT
Definition ErrorCodes.h:46
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:53
@ rpcNOT_SYNCED
Definition ErrorCodes.h:48
base_uint< 256 > uint256
Definition base_uint.h:539
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:942
Json::Value rpcError(int iError)
Definition RPCErr.cpp:12
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
@ ledgerMaster
ledger master data for signing
The context of information needed to call an RPC.
Definition Context.h:20
unsigned int apiVersion
Definition Context.h:30
Resource::Charge & loadType
Definition Context.h:23
Application & app
Definition Context.h:22
LedgerMaster & ledgerMaster
Definition Context.h:25
Status represents the results of an operation that might fail.
Definition Status.h:21
static constexpr Code OK
Definition Status.h:27