rippled
Loading...
Searching...
No Matches
RPCHelpers.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2014 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/LedgerMaster.h>
21#include <xrpld/app/ledger/LedgerToJson.h>
22#include <xrpld/app/ledger/OpenLedger.h>
23#include <xrpld/app/misc/Transaction.h>
24#include <xrpld/app/paths/TrustLine.h>
25#include <xrpld/app/rdb/RelationalDatabase.h>
26#include <xrpld/app/tx/detail/NFTokenUtils.h>
27#include <xrpld/ledger/View.h>
28#include <xrpld/rpc/Context.h>
29#include <xrpld/rpc/DeliveredAmount.h>
30#include <xrpld/rpc/detail/RPCHelpers.h>
31
32#include <xrpl/protocol/AccountID.h>
33#include <xrpl/protocol/RPCErr.h>
34#include <xrpl/protocol/nftPageMask.h>
35#include <xrpl/resource/Fees.h>
36
37#include <boost/algorithm/string/case_conv.hpp>
38#include <boost/algorithm/string/predicate.hpp>
39
40namespace ripple {
41namespace RPC {
42
45{
47
48 auto const publicKey =
49 parseBase58<PublicKey>(TokenType::AccountPublic, account);
50
51 if (publicKey)
52 result = calcAccountID(*publicKey);
53 else
54 result = parseBase58<AccountID>(account);
55
56 return result;
57}
58
61 AccountID& result,
62 std::string const& strIdent,
63 bool bStrict)
64{
65 if (auto accountID = accountFromStringStrict(strIdent))
66 {
67 result = *accountID;
68 return rpcSUCCESS;
69 }
70
71 if (bStrict)
72 return rpcACT_MALFORMED;
73
74 // We allow the use of the seeds which is poor practice
75 // and merely for debugging convenience.
76 auto const seed = parseGenericSeed(strIdent);
77
78 if (!seed)
79 return rpcBAD_SEED;
80
81 auto const keypair = generateKeyPair(KeyType::secp256k1, *seed);
82
83 result = calcAccountID(keypair.first);
84 return rpcSUCCESS;
85}
86
88accountFromString(AccountID& result, std::string const& strIdent, bool bStrict)
89{
90 error_code_i code = accountFromStringWithCode(result, strIdent, bStrict);
91 if (code != rpcSUCCESS)
92 return rpcError(code);
93 else
94 return Json::objectValue;
95}
96
99{
100 if (sle->getType() == ltRIPPLE_STATE)
101 {
102 if (sle->getFieldAmount(sfLowLimit).getIssuer() == accountID)
103 return sle->getFieldU64(sfLowNode);
104 else if (sle->getFieldAmount(sfHighLimit).getIssuer() == accountID)
105 return sle->getFieldU64(sfHighNode);
106 }
107
108 if (!sle->isFieldPresent(sfOwnerNode))
109 return 0;
110
111 return sle->getFieldU64(sfOwnerNode);
112}
113
114bool
116 ReadView const& ledger,
118 AccountID const& accountID)
119{
120 if (sle->getType() == ltRIPPLE_STATE)
121 {
122 return (sle->getFieldAmount(sfLowLimit).getIssuer() == accountID) ||
123 (sle->getFieldAmount(sfHighLimit).getIssuer() == accountID);
124 }
125 else if (sle->isFieldPresent(sfAccount))
126 {
127 // If there's an sfAccount present, also test the sfDestination, if
128 // present. This will match objects such as Escrows (ltESCROW), Payment
129 // Channels (ltPAYCHAN), and Checks (ltCHECK) because those are added to
130 // the Destination account's directory. It intentionally EXCLUDES
131 // NFToken Offers (ltNFTOKEN_OFFER). NFToken Offers are NOT added to the
132 // Destination account's directory.
133 return sle->getAccountID(sfAccount) == accountID ||
134 (sle->isFieldPresent(sfDestination) &&
135 sle->getAccountID(sfDestination) == accountID);
136 }
137 else if (sle->getType() == ltSIGNER_LIST)
138 {
139 Keylet const accountSignerList = keylet::signers(accountID);
140 return sle->key() == accountSignerList.key;
141 }
142 else if (sle->getType() == ltNFTOKEN_OFFER)
143 {
144 // Do not check the sfDestination field. NFToken Offers are NOT added to
145 // the Destination account's directory.
146 return sle->getAccountID(sfOwner) == accountID;
147 }
148
149 return false;
150}
151
152bool
154 ReadView const& ledger,
155 AccountID const& account,
157 uint256 dirIndex,
158 uint256 entryIndex,
159 std::uint32_t const limit,
160 Json::Value& jvResult)
161{
162 // check if dirIndex is valid
163 if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
164 return false;
165
166 auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
167 LedgerEntryType ledgerType) {
168 auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
169 return it != typeFilter.end();
170 };
171
172 // if dirIndex != 0, then all NFTs have already been returned. only
173 // iterate NFT pages if the filter says so AND dirIndex == 0
174 bool iterateNFTPages =
175 (!typeFilter.has_value() ||
176 typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
177 dirIndex == beast::zero;
178
179 Keylet const firstNFTPage = keylet::nftpage_min(account);
180
181 // we need to check the marker to see if it is an NFTTokenPage index.
182 if (iterateNFTPages && entryIndex != beast::zero)
183 {
184 // if it is we will try to iterate the pages up to the limit
185 // and then change over to the owner directory
186
187 if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
188 iterateNFTPages = false;
189 }
190
191 auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
192
193 // this is a mutable version of limit, used to seemlessly switch
194 // to iterating directory entries when nftokenpages are exhausted
195 uint32_t mlimit = limit;
196
197 // iterate NFTokenPages preferentially
198 if (iterateNFTPages)
199 {
200 Keylet const first = entryIndex == beast::zero
201 ? firstNFTPage
202 : Keylet{ltNFTOKEN_PAGE, entryIndex};
203
204 Keylet const last = keylet::nftpage_max(account);
205
206 // current key
207 uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
208
209 // current page
210 auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
211
212 while (cp)
213 {
214 jvObjects.append(cp->getJson(JsonOptions::none));
215 auto const npm = (*cp)[~sfNextPageMin];
216 if (npm)
217 cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
218 else
219 cp = nullptr;
220
221 if (--mlimit == 0)
222 {
223 if (cp)
224 {
225 jvResult[jss::limit] = limit;
226 jvResult[jss::marker] = std::string("0,") + to_string(ck);
227 return true;
228 }
229 }
230
231 if (!npm)
232 break;
233
234 ck = *npm;
235 }
236
237 // if execution reaches here then we're about to transition
238 // to iterating the root directory (and the conventional
239 // behaviour of this RPC function.) Therefore we should
240 // zero entryIndex so as not to terribly confuse things.
241 entryIndex = beast::zero;
242 }
243
244 auto const root = keylet::ownerDir(account);
245 auto found = false;
246
247 if (dirIndex.isZero())
248 {
249 dirIndex = root.key;
250 found = true;
251 }
252
253 auto dir = ledger.read({ltDIR_NODE, dirIndex});
254 if (!dir)
255 {
256 // it's possible the user had nftoken pages but no
257 // directory entries. If there's no nftoken page, we will
258 // give empty array for account_objects.
259 if (mlimit >= limit)
260 jvResult[jss::account_objects] = Json::arrayValue;
261
262 // non-zero dirIndex validity was checked in the beginning of this
263 // function; by this point, it should be zero. This function returns
264 // true regardless of nftoken page presence; if absent, account_objects
265 // is already set as an empty array. Notice we will only return false in
266 // this function when entryIndex can not be found, indicating an invalid
267 // marker error.
268 return true;
269 }
270
271 std::uint32_t i = 0;
272 for (;;)
273 {
274 auto const& entries = dir->getFieldV256(sfIndexes);
275 auto iter = entries.begin();
276
277 if (!found)
278 {
279 iter = std::find(iter, entries.end(), entryIndex);
280 if (iter == entries.end())
281 return false;
282
283 found = true;
284 }
285
286 // it's possible that the returned NFTPages exactly filled the
287 // response. Check for that condition.
288 if (i == mlimit && mlimit < limit)
289 {
290 jvResult[jss::limit] = limit;
291 jvResult[jss::marker] =
292 to_string(dirIndex) + ',' + to_string(*iter);
293 return true;
294 }
295
296 for (; iter != entries.end(); ++iter)
297 {
298 auto const sleNode = ledger.read(keylet::child(*iter));
299
300 if (!typeFilter.has_value() ||
301 typeMatchesFilter(typeFilter.value(), sleNode->getType()))
302 {
303 jvObjects.append(sleNode->getJson(JsonOptions::none));
304 }
305
306 if (++i == mlimit)
307 {
308 if (++iter != entries.end())
309 {
310 jvResult[jss::limit] = limit;
311 jvResult[jss::marker] =
312 to_string(dirIndex) + ',' + to_string(*iter);
313 return true;
314 }
315
316 break;
317 }
318 }
319
320 auto const nodeIndex = dir->getFieldU64(sfIndexNext);
321 if (nodeIndex == 0)
322 return true;
323
324 dirIndex = keylet::page(root, nodeIndex).key;
325 dir = ledger.read({ltDIR_NODE, dirIndex});
326 if (!dir)
327 return true;
328
329 if (i == mlimit)
330 {
331 auto const& e = dir->getFieldV256(sfIndexes);
332 if (!e.empty())
333 {
334 jvResult[jss::limit] = limit;
335 jvResult[jss::marker] =
336 to_string(dirIndex) + ',' + to_string(*e.begin());
337 }
338
339 return true;
340 }
341 }
342}
343
344namespace {
345
346bool
347isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
348{
349 if (standalone)
350 return false;
351
352 return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge;
353}
354
355template <class T>
356Status
357ledgerFromRequest(T& ledger, JsonContext& context)
358{
359 ledger.reset();
360
361 auto& params = context.params;
362
363 auto indexValue = params[jss::ledger_index];
364 auto hashValue = params[jss::ledger_hash];
365
366 // We need to support the legacy "ledger" field.
367 auto& legacyLedger = params[jss::ledger];
368 if (legacyLedger)
369 {
370 if (legacyLedger.asString().size() > 12)
371 hashValue = legacyLedger;
372 else
373 indexValue = legacyLedger;
374 }
375
376 if (hashValue)
377 {
378 if (!hashValue.isString())
379 return {rpcINVALID_PARAMS, "ledgerHashNotString"};
380
381 uint256 ledgerHash;
382 if (!ledgerHash.parseHex(hashValue.asString()))
383 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
384 return getLedger(ledger, ledgerHash, context);
385 }
386
387 auto const index = indexValue.asString();
388
389 if (index == "current" || index.empty())
390 return getLedger(ledger, LedgerShortcut::CURRENT, context);
391
392 if (index == "validated")
393 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
394
395 if (index == "closed")
396 return getLedger(ledger, LedgerShortcut::CLOSED, context);
397
398 std::uint32_t iVal;
399 if (beast::lexicalCastChecked(iVal, index))
400 return getLedger(ledger, iVal, context);
401
402 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
403}
404} // namespace
405
406template <class T, class R>
407Status
409{
410 R& request = context.params;
411 return ledgerFromSpecifier(ledger, request.ledger(), context);
412}
413
414// explicit instantiation of above function
415template Status
416ledgerFromRequest<>(
419
420// explicit instantiation of above function
421template Status
422ledgerFromRequest<>(
425
426// explicit instantiation of above function
427template Status
428ledgerFromRequest<>(
431
432template <class T>
433Status
435 T& ledger,
436 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
437 Context& context)
438{
439 ledger.reset();
440
441 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
442 LedgerCase ledgerCase = specifier.ledger_case();
443 switch (ledgerCase)
444 {
445 case LedgerCase::kHash: {
446 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
447 {
448 return getLedger(ledger, *hash, context);
449 }
450 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
451 }
452 case LedgerCase::kSequence:
453 return getLedger(ledger, specifier.sequence(), context);
454 case LedgerCase::kShortcut:
455 [[fallthrough]];
456 case LedgerCase::LEDGER_NOT_SET: {
457 auto const shortcut = specifier.shortcut();
458 if (shortcut ==
459 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
460 {
461 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
462 }
463 else
464 {
465 if (shortcut ==
466 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
467 shortcut ==
468 org::xrpl::rpc::v1::LedgerSpecifier::
469 SHORTCUT_UNSPECIFIED)
470 {
471 return getLedger(ledger, LedgerShortcut::CURRENT, context);
472 }
473 else if (
474 shortcut ==
475 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
476 {
477 return getLedger(ledger, LedgerShortcut::CLOSED, context);
478 }
479 }
480 }
481 }
482
483 return Status::OK;
484}
485
486template <class T>
487Status
488getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
489{
490 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
491 if (ledger == nullptr)
492 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
493 return Status::OK;
494}
495
496template <class T>
497Status
498getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
499{
500 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
501 if (ledger == nullptr)
502 {
503 auto cur = context.ledgerMaster.getCurrentLedger();
504 if (cur->info().seq == ledgerIndex)
505 {
506 ledger = cur;
507 }
508 }
509
510 if (ledger == nullptr)
511 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
512
513 if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
514 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
515 {
516 ledger.reset();
517 if (context.apiVersion == 1)
518 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
519 return {rpcNOT_SYNCED, "notSynced"};
520 }
521
522 return Status::OK;
523}
524
525template <class T>
526Status
527getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
528{
529 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
530 {
531 if (context.apiVersion == 1)
532 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
533 return {rpcNOT_SYNCED, "notSynced"};
534 }
535
536 if (shortcut == LedgerShortcut::VALIDATED)
537 {
538 ledger = context.ledgerMaster.getValidatedLedger();
539 if (ledger == nullptr)
540 {
541 if (context.apiVersion == 1)
542 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
543 return {rpcNOT_SYNCED, "notSynced"};
544 }
545
546 XRPL_ASSERT(
547 !ledger->open(), "ripple::RPC::getLedger : validated is not open");
548 }
549 else
550 {
551 if (shortcut == LedgerShortcut::CURRENT)
552 {
553 ledger = context.ledgerMaster.getCurrentLedger();
554 XRPL_ASSERT(
555 ledger->open(), "ripple::RPC::getLedger : current is open");
556 }
557 else if (shortcut == LedgerShortcut::CLOSED)
558 {
559 ledger = context.ledgerMaster.getClosedLedger();
560 XRPL_ASSERT(
561 !ledger->open(), "ripple::RPC::getLedger : closed is not open");
562 }
563 else
564 {
565 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
566 }
567
568 if (ledger == nullptr)
569 {
570 if (context.apiVersion == 1)
571 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
572 return {rpcNOT_SYNCED, "notSynced"};
573 }
574
575 static auto const minSequenceGap = 10;
576
577 if (ledger->info().seq + minSequenceGap <
579 {
580 ledger.reset();
581 if (context.apiVersion == 1)
582 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
583 return {rpcNOT_SYNCED, "notSynced"};
584 }
585 }
586 return Status::OK;
587}
588
589// Explicit instantiaion of above three functions
590template Status
591getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
592
593template Status
594getLedger<>(
596 LedgerShortcut shortcut,
597 Context&);
598
599template Status
600getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
601
602// The previous version of the lookupLedger command would accept the
603// "ledger_index" argument as a string and silently treat it as a request to
604// return the current ledger which, while not strictly wrong, could cause a lot
605// of confusion.
606//
607// The code now robustly validates the input and ensures that the only possible
608// values for the "ledger_index" parameter are the index of a ledger passed as
609// an integer or one of the strings "current", "closed" or "validated".
610// Additionally, the code ensures that the value passed in "ledger_hash" is a
611// string and a valid hash. Invalid values will return an appropriate error
612// code.
613//
614// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
615// assumes that "ledger_index" has the value "current".
616//
617// Returns a Json::objectValue. If there was an error, it will be in that
618// return value. Otherwise, the object contains the field "validated" and
619// optionally the fields "ledger_hash", "ledger_index" and
620// "ledger_current_index", if they are defined.
621Status
624 JsonContext& context,
625 Json::Value& result)
626{
627 if (auto status = ledgerFromRequest(ledger, context))
628 return status;
629
630 auto& info = ledger->info();
631
632 if (!ledger->open())
633 {
634 result[jss::ledger_hash] = to_string(info.hash);
635 result[jss::ledger_index] = info.seq;
636 }
637 else
638 {
639 result[jss::ledger_current_index] = info.seq;
640 }
641
642 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
643 return Status::OK;
644}
645
648{
649 Json::Value result;
650 if (auto status = lookupLedger(ledger, context, result))
651 status.inject(result);
652
653 return result;
654}
655
658{
659 hash_set<AccountID> result;
660 for (auto const& jv : jvArray)
661 {
662 if (!jv.isString())
663 return hash_set<AccountID>();
664 auto const id = parseBase58<AccountID>(jv.asString());
665 if (!id)
666 return hash_set<AccountID>();
667 result.insert(*id);
668 }
669 return result;
670}
671
672void
673injectSLE(Json::Value& jv, SLE const& sle)
674{
675 jv = sle.getJson(JsonOptions::none);
676 if (sle.getType() == ltACCOUNT_ROOT)
677 {
678 if (sle.isFieldPresent(sfEmailHash))
679 {
680 auto const& hash = sle.getFieldH128(sfEmailHash);
681 Blob const b(hash.begin(), hash.end());
682 std::string md5 = strHex(makeSlice(b));
683 boost::to_lower(md5);
684 // VFALCO TODO Give a name and move this constant
685 // to a more visible location. Also
686 // shouldn't this be https?
687 jv[jss::urlgravatar] =
688 str(boost::format("http://www.gravatar.com/avatar/%s") % md5);
689 }
690 }
691 else
692 {
693 jv[jss::Invalid] = true;
694 }
695}
696
699 unsigned int& limit,
701 JsonContext const& context)
702{
703 limit = range.rdefault;
704 if (auto const& jvLimit = context.params[jss::limit])
705 {
706 if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0)))
707 return RPC::expected_field_error(jss::limit, "unsigned integer");
708
709 limit = jvLimit.asUInt();
710 if (!isUnlimited(context.role))
711 limit = std::max(range.rmin, std::min(range.rmax, limit));
712 }
713 return std::nullopt;
714}
715
718{
719 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
720 // non-standard way. While rippled never encode seeds that way, we
721 // try to detect such keys to avoid user confusion.
722 if (!value.isString())
723 return std::nullopt;
724
725 auto const result = decodeBase58Token(value.asString(), TokenType::None);
726
727 if (result.size() == 18 &&
728 static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
729 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
730 return Seed(makeSlice(result.substr(2)));
731
732 return std::nullopt;
733}
734
737{
738 using string_to_seed_t =
740 using seed_match_t = std::pair<char const*, string_to_seed_t>;
741
742 static seed_match_t const seedTypes[]{
743 {jss::passphrase.c_str(),
744 [](std::string const& s) { return parseGenericSeed(s); }},
745 {jss::seed.c_str(),
746 [](std::string const& s) { return parseBase58<Seed>(s); }},
747 {jss::seed_hex.c_str(), [](std::string const& s) {
748 uint128 i;
749 if (i.parseHex(s))
750 return std::optional<Seed>(Slice(i.data(), i.size()));
751 return std::optional<Seed>{};
752 }}};
753
754 // Identify which seed type is in use.
755 seed_match_t const* seedType = nullptr;
756 int count = 0;
757 for (auto const& t : seedTypes)
758 {
759 if (params.isMember(t.first))
760 {
761 ++count;
762 seedType = &t;
763 }
764 }
765
766 if (count != 1)
767 {
768 error = RPC::make_param_error(
769 "Exactly one of the following must be specified: " +
770 std::string(jss::passphrase) + ", " + std::string(jss::seed) +
771 " or " + std::string(jss::seed_hex));
772 return std::nullopt;
773 }
774
775 // Make sure a string is present
776 auto const& param = params[seedType->first];
777 if (!param.isString())
778 {
779 error = RPC::expected_field_error(seedType->first, "string");
780 return std::nullopt;
781 }
782
783 auto const fieldContents = param.asString();
784
785 // Convert string to seed.
786 std::optional<Seed> seed = seedType->second(fieldContents);
787
788 if (!seed)
789 error = rpcError(rpcBAD_SEED);
790
791 return seed;
792}
793
796 Json::Value const& params,
797 Json::Value& error,
798 unsigned int apiVersion)
799{
800 bool const has_key_type = params.isMember(jss::key_type);
801
802 // All of the secret types we allow, but only one at a time.
803 static char const* const secretTypes[]{
804 jss::passphrase.c_str(),
805 jss::secret.c_str(),
806 jss::seed.c_str(),
807 jss::seed_hex.c_str()};
808
809 // Identify which secret type is in use.
810 char const* secretType = nullptr;
811 int count = 0;
812 for (auto t : secretTypes)
813 {
814 if (params.isMember(t))
815 {
816 ++count;
817 secretType = t;
818 }
819 }
820
821 if (count == 0 || secretType == nullptr)
822 {
823 error = RPC::missing_field_error(jss::secret);
824 return {};
825 }
826
827 if (count > 1)
828 {
829 error = RPC::make_param_error(
830 "Exactly one of the following must be specified: " +
831 std::string(jss::passphrase) + ", " + std::string(jss::secret) +
832 ", " + std::string(jss::seed) + " or " +
833 std::string(jss::seed_hex));
834 return {};
835 }
836
839
840 if (has_key_type)
841 {
842 if (!params[jss::key_type].isString())
843 {
844 error = RPC::expected_field_error(jss::key_type, "string");
845 return {};
846 }
847
848 keyType = keyTypeFromString(params[jss::key_type].asString());
849
850 if (!keyType)
851 {
852 if (apiVersion > 1u)
854 else
855 error = RPC::invalid_field_error(jss::key_type);
856 return {};
857 }
858
859 // using strcmp as pointers may not match (see
860 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
861 if (strcmp(secretType, jss::secret.c_str()) == 0)
862 {
863 error = RPC::make_param_error(
864 "The secret field is not allowed if " +
865 std::string(jss::key_type) + " is used.");
866 return {};
867 }
868 }
869
870 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
871 // non-standard way. While we never encode seeds that way, we try
872 // to detect such keys to avoid user confusion.
873 // using strcmp as pointers may not match (see
874 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
875 if (strcmp(secretType, jss::seed_hex.c_str()) != 0)
876 {
877 seed = RPC::parseRippleLibSeed(params[secretType]);
878
879 if (seed)
880 {
881 // If the user passed in an Ed25519 seed but *explicitly*
882 // requested another key type, return an error.
884 {
885 error = RPC::make_error(
886 rpcBAD_SEED, "Specified seed is for an Ed25519 wallet.");
887 return {};
888 }
889
890 keyType = KeyType::ed25519;
891 }
892 }
893
894 if (!keyType)
895 keyType = KeyType::secp256k1;
896
897 if (!seed)
898 {
899 if (has_key_type)
900 seed = getSeedFromRPC(params, error);
901 else
902 {
903 if (!params[jss::secret].isString())
904 {
905 error = RPC::expected_field_error(jss::secret, "string");
906 return {};
907 }
908
909 seed = parseGenericSeed(params[jss::secret].asString());
910 }
911 }
912
913 if (!seed)
914 {
915 if (!contains_error(error))
916 {
917 error = RPC::make_error(
919 }
920
921 return {};
922 }
923
924 if (keyType != KeyType::secp256k1 && keyType != KeyType::ed25519)
925 LogicError("keypairForSignature: invalid key type");
926
927 return generateKeyPair(*keyType, *seed);
928}
929
932{
934 if (params.isMember(jss::type))
935 {
936 static constexpr auto types = std::to_array<
938#pragma push_macro("LEDGER_ENTRY")
939#undef LEDGER_ENTRY
940
941#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \
942 {jss::name, jss::rpcName, tag},
943
944#include <xrpl/protocol/detail/ledger_entries.macro>
945
946#undef LEDGER_ENTRY
947#pragma pop_macro("LEDGER_ENTRY")
948 });
949
950 auto const& p = params[jss::type];
951 if (!p.isString())
952 {
953 result.first = RPC::Status{
954 rpcINVALID_PARAMS, "Invalid field 'type', not string."};
955 XRPL_ASSERT(
956 result.first.type() == RPC::Status::Type::error_code_i,
957 "ripple::RPC::chooseLedgerEntryType : first valid result type");
958 return result;
959 }
960
961 // Use the passed in parameter to find a ledger type based on matching
962 // against the canonical name (case-insensitive) or the RPC name
963 // (case-sensitive).
964 auto const filter = p.asString();
965 const auto iter =
966 std::ranges::find_if(types, [&filter](decltype(types.front())& t) {
967 return boost::iequals(std::get<0>(t), filter) ||
968 std::get<1>(t) == filter;
969 });
970 if (iter == types.end())
971 {
972 result.first =
973 RPC::Status{rpcINVALID_PARAMS, "Invalid field 'type'."};
974 XRPL_ASSERT(
975 result.first.type() == RPC::Status::Type::error_code_i,
976 "ripple::RPC::chooseLedgerEntryType : second valid result "
977 "type");
978 return result;
979 }
980 result.second = std::get<2>(*iter);
981 }
982 return result;
983}
984
985bool
987{
988 switch (type)
989 {
990 case LedgerEntryType::ltAMENDMENTS:
991 case LedgerEntryType::ltDIR_NODE:
992 case LedgerEntryType::ltFEE_SETTINGS:
993 case LedgerEntryType::ltLEDGER_HASHES:
994 case LedgerEntryType::ltNEGATIVE_UNL:
995 return false;
996 default:
997 return true;
998 }
999}
1000
1002beast::SemanticVersion const goodVersion("1.0.0");
1003beast::SemanticVersion const lastVersion("1.0.0");
1004
1005unsigned int
1006getAPIVersionNumber(Json::Value const& jv, bool betaEnabled)
1007{
1008 static Json::Value const minVersion(RPC::apiMinimumSupportedVersion);
1009 static Json::Value const invalidVersion(RPC::apiInvalidVersion);
1010
1011 Json::Value const maxVersion(
1013 Json::Value requestedVersion(RPC::apiVersionIfUnspecified);
1014 if (jv.isObject())
1015 {
1016 requestedVersion = jv.get(jss::api_version, requestedVersion);
1017 }
1018 if (!(requestedVersion.isInt() || requestedVersion.isUInt()) ||
1019 requestedVersion < minVersion || requestedVersion > maxVersion)
1020 {
1021 requestedVersion = invalidVersion;
1022 }
1023 return requestedVersion.asUInt();
1024}
1025
1028{
1029 auto const hasHash = context.params.isMember(jss::ledger_hash);
1030 auto const hasIndex = context.params.isMember(jss::ledger_index);
1031 std::uint32_t ledgerIndex = 0;
1032
1033 auto& ledgerMaster = context.app.getLedgerMaster();
1034 LedgerHash ledgerHash;
1035
1036 if ((hasHash && hasIndex) || !(hasHash || hasIndex))
1037 {
1038 return RPC::make_param_error(
1039 "Exactly one of ledger_hash and ledger_index can be set.");
1040 }
1041
1043
1044 if (hasHash)
1045 {
1046 auto const& jsonHash = context.params[jss::ledger_hash];
1047 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
1048 return RPC::invalid_field_error(jss::ledger_hash);
1049 }
1050 else
1051 {
1052 auto const& jsonIndex = context.params[jss::ledger_index];
1053 if (!jsonIndex.isInt())
1054 return RPC::invalid_field_error(jss::ledger_index);
1055
1056 // We need a validated ledger to get the hash from the sequence
1057 if (ledgerMaster.getValidatedLedgerAge() >
1059 {
1060 if (context.apiVersion == 1)
1061 return rpcError(rpcNO_CURRENT);
1062 return rpcError(rpcNOT_SYNCED);
1063 }
1064
1065 ledgerIndex = jsonIndex.asInt();
1066 auto ledger = ledgerMaster.getValidatedLedger();
1067
1068 if (ledgerIndex >= ledger->info().seq)
1069 return RPC::make_param_error("Ledger index too large");
1070 if (ledgerIndex <= 0)
1071 return RPC::make_param_error("Ledger index too small");
1072
1073 auto const j = context.app.journal("RPCHandler");
1074 // Try to get the hash of the desired ledger from the validated
1075 // ledger
1076 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1077 if (!neededHash)
1078 {
1079 // Find a ledger more likely to have the hash of the desired
1080 // ledger
1081 auto const refIndex = getCandidateLedger(ledgerIndex);
1082 auto refHash = hashOfSeq(*ledger, refIndex, j);
1083 XRPL_ASSERT(
1084 refHash,
1085 "ripple::RPC::getLedgerByContext : nonzero ledger hash");
1086
1087 ledger = ledgerMaster.getLedgerByHash(*refHash);
1088 if (!ledger)
1089 {
1090 // We don't have the ledger we need to figure out which
1091 // ledger they want. Try to get it.
1092
1093 if (auto il = context.app.getInboundLedgers().acquire(
1094 *refHash, refIndex, InboundLedger::Reason::GENERIC))
1095 {
1096 Json::Value jvResult = RPC::make_error(
1098 "acquiring ledger containing requested index");
1099 jvResult[jss::acquiring] =
1100 getJson(LedgerFill(*il, &context));
1101 return jvResult;
1102 }
1103
1104 if (auto il = context.app.getInboundLedgers().find(*refHash))
1105 {
1106 Json::Value jvResult = RPC::make_error(
1108 "acquiring ledger containing requested index");
1109 jvResult[jss::acquiring] = il->getJson(0);
1110 return jvResult;
1111 }
1112
1113 // Likely the app is shutting down
1114 return Json::Value();
1115 }
1116
1117 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1118 }
1119 XRPL_ASSERT(
1120 neededHash,
1121 "ripple::RPC::getLedgerByContext : nonzero needed hash");
1122 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
1123 }
1124
1125 // Try to get the desired ledger
1126 // Verify all nodes even if we think we have it
1127 auto ledger = context.app.getInboundLedgers().acquire(
1128 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
1129
1130 // In standalone mode, accept the ledger from the ledger cache
1131 if (!ledger && context.app.config().standalone())
1132 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
1133
1134 if (ledger)
1135 return ledger;
1136
1137 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
1138 return il->getJson(0);
1139
1140 return RPC::make_error(
1141 rpcNOT_READY, "findCreate failed to return an inbound ledger");
1142}
1143} // namespace RPC
1144} // namespace ripple
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:148
Value get(UInt index, const Value &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Definition: json_value.cpp:847
bool isString() const
UInt asUInt() const
Definition: json_value.cpp:551
bool isObject() const
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isUInt() const
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
bool isInt() const
Definition: json_value.cpp:998
A Semantic Version number.
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:337
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()
Definition: LedgerMaster.h:79
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
std::shared_ptr< Ledger const > getLedgerByHash(uint256 const &hash)
LedgerIndex getValidLedgerIndex()
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
LedgerEntryType getType() const
Json::Value getJson(JsonOptions options) const override
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
uint128 getFieldH128(SField const &field) const
Definition: STObject.cpp:627
Seeds are used to generate deterministic secret keys.
Definition: Seed.h:34
An immutable linear range of bytes.
Definition: Slice.h:46
base_uint next() const
Definition: base_uint.h:455
static std::optional< base_uint > fromVoidChecked(T const &from)
Definition: base_uint.h:326
pointer data()
Definition: base_uint.h:125
static constexpr std::size_t size()
Definition: base_uint.h:526
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:503
bool isZero() const
Definition: base_uint.h:540
T end(T... args)
T find(T... args)
T insert(T... args)
T max(T... args)
T min(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Definition: LexicalCast.h:202
Status
Return codes from Backend operations.
beast::SemanticVersion const firstVersion("1.0.0")
API version numbers used in API version 1.
Definition: RPCHelpers.h:207
Status ledgerFromRequest(T &ledger, GRPCContext< R > &context)
Definition: RPCHelpers.cpp:408
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
error_code_i accountFromStringWithCode(AccountID &result, std::string const &strIdent, bool bStrict)
Decode account ID from string.
Definition: RPCHelpers.cpp:60
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:203
std::optional< Seed > getSeedFromRPC(Json::Value const &params, Json::Value &error)
Definition: RPCHelpers.cpp:736
beast::SemanticVersion const lastVersion("1.0.0")
Definition: RPCHelpers.h:209
static constexpr auto apiMaximumSupportedVersion
Definition: ApiVersion.h:58
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:187
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:315
bool isAccountObjectsValidType(LedgerEntryType const &type)
Check if the type is a valid filtering type for account_objects method.
Definition: RPCHelpers.cpp:986
static constexpr std::integral_constant< unsigned, Version > apiVersion
Definition: ApiVersion.h:54
bool isRelatedToAccount(ReadView const &ledger, std::shared_ptr< SLE const > const &sle, AccountID const &accountID)
Tests if a SLE is owned by accountID.
Definition: RPCHelpers.cpp:115
void injectSLE(Json::Value &jv, SLE const &sle)
Inject JSON describing ledger entry.
Definition: RPCHelpers.cpp:673
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition: ErrorCodes.h:261
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...
Definition: RPCHelpers.cpp:488
static constexpr auto apiBetaVersion
Definition: ApiVersion.h:62
std::pair< RPC::Status, LedgerEntryType > chooseLedgerEntryType(Json::Value const &params)
Definition: RPCHelpers.cpp:931
std::string invalid_field_message(std::string const &name)
Definition: ErrorCodes.h:303
bool getAccountObjects(ReadView const &ledger, AccountID const &account, std::optional< std::vector< LedgerEntryType > > const &typeFilter, uint256 dirIndex, uint256 entryIndex, std::uint32_t const limit, Json::Value &jvResult)
Gathers all objects for an account in a ledger.
Definition: RPCHelpers.cpp:153
std::optional< Json::Value > readLimitField(unsigned int &limit, Tuning::LimitRange const &range, JsonContext const &context)
Retrieve the limit value from a JsonContext, or set a default - then restrict the limit by max and mi...
Definition: RPCHelpers.cpp:698
beast::SemanticVersion const goodVersion("1.0.0")
Definition: RPCHelpers.h:208
Json::Value accountFromString(AccountID &result, std::string const &strIdent, bool bStrict)
Definition: RPCHelpers.cpp:88
static constexpr auto apiVersionIfUnspecified
Definition: ApiVersion.h:59
Json::Value expected_field_error(std::string const &name, std::string const &type)
Definition: ErrorCodes.h:339
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.
Definition: RPCHelpers.cpp:622
Status ledgerFromSpecifier(T &ledger, org::xrpl::rpc::v1::LedgerSpecifier const &specifier, Context &context)
Definition: RPCHelpers.cpp:434
std::optional< AccountID > accountFromStringStrict(std::string const &account)
Get an AccountID from an account ID or public key.
Definition: RPCHelpers.cpp:44
hash_set< AccountID > parseAccountIds(Json::Value const &jvArray)
Definition: RPCHelpers.cpp:657
static constexpr auto apiInvalidVersion
Definition: ApiVersion.h:56
std::uint64_t getStartHint(std::shared_ptr< SLE const > const &sle, AccountID const &accountID)
Gets the start hint for traversing account objects.
Definition: RPCHelpers.cpp:98
static constexpr auto apiMinimumSupportedVersion
Definition: ApiVersion.h:57
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:273
std::optional< Seed > parseRippleLibSeed(Json::Value const &value)
Definition: RPCHelpers.cpp:717
std::optional< std::pair< PublicKey, SecretKey > > keypairForSignature(Json::Value const &params, Json::Value &error, unsigned int apiVersion)
Definition: RPCHelpers.cpp:795
Charge const feeHeavyBurdenRPC
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:182
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition: Indexes.cpp:372
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:395
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:403
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:366
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition: Indexes.cpp:322
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::optional< KeyType > keyTypeFromString(std::string const &s)
Definition: KeyType.h:34
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:327
error_code_i
Definition: ErrorCodes.h:40
@ rpcNO_NETWORK
Definition: ErrorCodes.h:66
@ rpcACT_MALFORMED
Definition: ErrorCodes.h:90
@ rpcBAD_KEY_TYPE
Definition: ErrorCodes.h:133
@ rpcNOT_READY
Definition: ErrorCodes.h:60
@ rpcSUCCESS
Definition: ErrorCodes.h:44
@ rpcNO_CURRENT
Definition: ErrorCodes.h:65
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
@ rpcLGR_NOT_FOUND
Definition: ErrorCodes.h:72
@ rpcBAD_SEED
Definition: ErrorCodes.h:99
@ rpcNOT_SYNCED
Definition: ErrorCodes.h:67
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
Definition: SecretKey.cpp:369
base_uint< 256 > uint256
Definition: base_uint.h:558
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition: View.cpp:836
AccountID calcAccountID(PublicKey const &pk)
Definition: AccountID.cpp:168
Json::Value rpcError(int iError)
Definition: RPCErr.cpp:31
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition: Role.cpp:125
std::string decodeBase58Token(std::string const &s, TokenType type)
Definition: tokens.cpp:209
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:244
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
ClosedInterval< T > range(T low, T high)
Create a closed range interval.
Definition: RangeSet.h:54
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
@ ltANY
A special type, matching any ledger entry type.
Definition: LedgerFormats.h:78
Number root(Number f, unsigned d)
Definition: Number.cpp:636
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
@ ledgerMaster
ledger master data for signing
std::optional< Seed > parseGenericSeed(std::string const &str, bool rfc1751=true)
Attempt to parse a string as a seed.
Definition: Seed.cpp:97
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:37
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
The context of information needed to call an RPC.
Definition: Context.h:39
unsigned int apiVersion
Definition: Context.h:49
Resource::Charge & loadType
Definition: Context.h:42
Application & app
Definition: Context.h:41
LedgerMaster & ledgerMaster
Definition: Context.h:44
RequestType params
Definition: Context.h:71
Json::Value params
Definition: Context.h:63
Status represents the results of an operation that might fail.
Definition: Status.h:40
static constexpr Code OK
Definition: Status.h:46
Represents RPC limit parameter values that have a min, default and max.
T value_or(T... args)