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