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