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/rpc/Context.h>
28#include <xrpld/rpc/DeliveredAmount.h>
29#include <xrpld/rpc/detail/RPCHelpers.h>
30
31#include <xrpl/ledger/View.h>
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 seamlessly 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.isNull())
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 if (!indexValue.isConvertibleTo(Json::stringValue))
388 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
389
390 auto const index = indexValue.asString();
391
392 if (index == "current" || index.empty())
393 return getLedger(ledger, LedgerShortcut::CURRENT, context);
394
395 if (index == "validated")
396 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
397
398 if (index == "closed")
399 return getLedger(ledger, LedgerShortcut::CLOSED, context);
400
401 std::uint32_t val;
402 if (!beast::lexicalCastChecked(val, index))
403 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
404
405 return getLedger(ledger, val, context);
406}
407} // namespace
408
409template <class T, class R>
410Status
412{
413 R& request = context.params;
414 return ledgerFromSpecifier(ledger, request.ledger(), context);
415}
416
417// explicit instantiation of above function
418template Status
419ledgerFromRequest<>(
422
423// explicit instantiation of above function
424template Status
425ledgerFromRequest<>(
428
429// explicit instantiation of above function
430template Status
431ledgerFromRequest<>(
434
435template <class T>
436Status
438 T& ledger,
439 org::xrpl::rpc::v1::LedgerSpecifier const& specifier,
440 Context& context)
441{
442 ledger.reset();
443
444 using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
445 LedgerCase ledgerCase = specifier.ledger_case();
446 switch (ledgerCase)
447 {
448 case LedgerCase::kHash: {
449 if (auto hash = uint256::fromVoidChecked(specifier.hash()))
450 {
451 return getLedger(ledger, *hash, context);
452 }
453 return {rpcINVALID_PARAMS, "ledgerHashMalformed"};
454 }
455 case LedgerCase::kSequence:
456 return getLedger(ledger, specifier.sequence(), context);
457 case LedgerCase::kShortcut:
458 [[fallthrough]];
459 case LedgerCase::LEDGER_NOT_SET: {
460 auto const shortcut = specifier.shortcut();
461 if (shortcut ==
462 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
463 {
464 return getLedger(ledger, LedgerShortcut::VALIDATED, context);
465 }
466 else
467 {
468 if (shortcut ==
469 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
470 shortcut ==
471 org::xrpl::rpc::v1::LedgerSpecifier::
472 SHORTCUT_UNSPECIFIED)
473 {
474 return getLedger(ledger, LedgerShortcut::CURRENT, context);
475 }
476 else if (
477 shortcut ==
478 org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
479 {
480 return getLedger(ledger, LedgerShortcut::CLOSED, context);
481 }
482 }
483 }
484 }
485
486 return Status::OK;
487}
488
489template <class T>
490Status
491getLedger(T& ledger, uint256 const& ledgerHash, Context& context)
492{
493 ledger = context.ledgerMaster.getLedgerByHash(ledgerHash);
494 if (ledger == nullptr)
495 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
496 return Status::OK;
497}
498
499template <class T>
500Status
501getLedger(T& ledger, uint32_t ledgerIndex, Context& context)
502{
503 ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex);
504 if (ledger == nullptr)
505 {
506 auto cur = context.ledgerMaster.getCurrentLedger();
507 if (cur->info().seq == ledgerIndex)
508 {
509 ledger = cur;
510 }
511 }
512
513 if (ledger == nullptr)
514 return {rpcLGR_NOT_FOUND, "ledgerNotFound"};
515
516 if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() &&
517 isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
518 {
519 ledger.reset();
520 if (context.apiVersion == 1)
521 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
522 return {rpcNOT_SYNCED, "notSynced"};
523 }
524
525 return Status::OK;
526}
527
528template <class T>
529Status
530getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
531{
532 if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
533 {
534 if (context.apiVersion == 1)
535 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
536 return {rpcNOT_SYNCED, "notSynced"};
537 }
538
539 if (shortcut == LedgerShortcut::VALIDATED)
540 {
541 ledger = context.ledgerMaster.getValidatedLedger();
542 if (ledger == nullptr)
543 {
544 if (context.apiVersion == 1)
545 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
546 return {rpcNOT_SYNCED, "notSynced"};
547 }
548
549 XRPL_ASSERT(
550 !ledger->open(), "ripple::RPC::getLedger : validated is not open");
551 }
552 else
553 {
554 if (shortcut == LedgerShortcut::CURRENT)
555 {
556 ledger = context.ledgerMaster.getCurrentLedger();
557 XRPL_ASSERT(
558 ledger->open(), "ripple::RPC::getLedger : current is open");
559 }
560 else if (shortcut == LedgerShortcut::CLOSED)
561 {
562 ledger = context.ledgerMaster.getClosedLedger();
563 XRPL_ASSERT(
564 !ledger->open(), "ripple::RPC::getLedger : closed is not open");
565 }
566 else
567 {
568 return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
569 }
570
571 if (ledger == nullptr)
572 {
573 if (context.apiVersion == 1)
574 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
575 return {rpcNOT_SYNCED, "notSynced"};
576 }
577
578 static auto const minSequenceGap = 10;
579
580 if (ledger->info().seq + minSequenceGap <
582 {
583 ledger.reset();
584 if (context.apiVersion == 1)
585 return {rpcNO_NETWORK, "InsufficientNetworkMode"};
586 return {rpcNOT_SYNCED, "notSynced"};
587 }
588 }
589 return Status::OK;
590}
591
592// Explicit instantiation of above three functions
593template Status
594getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
595
596template Status
597getLedger<>(
599 LedgerShortcut shortcut,
600 Context&);
601
602template Status
603getLedger<>(std::shared_ptr<ReadView const>&, uint256 const&, Context&);
604
605// The previous version of the lookupLedger command would accept the
606// "ledger_index" argument as a string and silently treat it as a request to
607// return the current ledger which, while not strictly wrong, could cause a lot
608// of confusion.
609//
610// The code now robustly validates the input and ensures that the only possible
611// values for the "ledger_index" parameter are the index of a ledger passed as
612// an integer or one of the strings "current", "closed" or "validated".
613// Additionally, the code ensures that the value passed in "ledger_hash" is a
614// string and a valid hash. Invalid values will return an appropriate error
615// code.
616//
617// In the absence of the "ledger_hash" or "ledger_index" parameters, the code
618// assumes that "ledger_index" has the value "current".
619//
620// Returns a Json::objectValue. If there was an error, it will be in that
621// return value. Otherwise, the object contains the field "validated" and
622// optionally the fields "ledger_hash", "ledger_index" and
623// "ledger_current_index", if they are defined.
624Status
627 JsonContext& context,
628 Json::Value& result)
629{
630 if (auto status = ledgerFromRequest(ledger, context))
631 return status;
632
633 auto& info = ledger->info();
634
635 if (!ledger->open())
636 {
637 result[jss::ledger_hash] = to_string(info.hash);
638 result[jss::ledger_index] = info.seq;
639 }
640 else
641 {
642 result[jss::ledger_current_index] = info.seq;
643 }
644
645 result[jss::validated] = context.ledgerMaster.isValidated(*ledger);
646 return Status::OK;
647}
648
651{
652 Json::Value result;
653 if (auto status = lookupLedger(ledger, context, result))
654 status.inject(result);
655
656 return result;
657}
658
661{
662 hash_set<AccountID> result;
663 for (auto const& jv : jvArray)
664 {
665 if (!jv.isString())
666 return hash_set<AccountID>();
667 auto const id = parseBase58<AccountID>(jv.asString());
668 if (!id)
669 return hash_set<AccountID>();
670 result.insert(*id);
671 }
672 return result;
673}
674
675void
676injectSLE(Json::Value& jv, SLE const& sle)
677{
678 jv = sle.getJson(JsonOptions::none);
679 if (sle.getType() == ltACCOUNT_ROOT)
680 {
681 if (sle.isFieldPresent(sfEmailHash))
682 {
683 auto const& hash = sle.getFieldH128(sfEmailHash);
684 Blob const b(hash.begin(), hash.end());
685 std::string md5 = strHex(makeSlice(b));
686 boost::to_lower(md5);
687 // VFALCO TODO Give a name and move this constant
688 // to a more visible location. Also
689 // shouldn't this be https?
690 jv[jss::urlgravatar] =
691 str(boost::format("http://www.gravatar.com/avatar/%s") % md5);
692 }
693 }
694 else
695 {
696 jv[jss::Invalid] = true;
697 }
698}
699
702 unsigned int& limit,
704 JsonContext const& context)
705{
706 limit = range.rdefault;
707 if (auto const& jvLimit = context.params[jss::limit])
708 {
709 if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0)))
710 return RPC::expected_field_error(jss::limit, "unsigned integer");
711
712 limit = jvLimit.asUInt();
713 if (!isUnlimited(context.role))
714 limit = std::max(range.rmin, std::min(range.rmax, limit));
715 }
716 return std::nullopt;
717}
718
721{
722 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
723 // non-standard way. While rippled never encode seeds that way, we
724 // try to detect such keys to avoid user confusion.
725 if (!value.isString())
726 return std::nullopt;
727
728 auto const result = decodeBase58Token(value.asString(), TokenType::None);
729
730 if (result.size() == 18 &&
731 static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
732 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
733 return Seed(makeSlice(result.substr(2)));
734
735 return std::nullopt;
736}
737
740{
741 using string_to_seed_t =
743 using seed_match_t = std::pair<char const*, string_to_seed_t>;
744
745 static seed_match_t const seedTypes[]{
746 {jss::passphrase.c_str(),
747 [](std::string const& s) { return parseGenericSeed(s); }},
748 {jss::seed.c_str(),
749 [](std::string const& s) { return parseBase58<Seed>(s); }},
750 {jss::seed_hex.c_str(), [](std::string const& s) {
751 uint128 i;
752 if (i.parseHex(s))
753 return std::optional<Seed>(Slice(i.data(), i.size()));
754 return std::optional<Seed>{};
755 }}};
756
757 // Identify which seed type is in use.
758 seed_match_t const* seedType = nullptr;
759 int count = 0;
760 for (auto const& t : seedTypes)
761 {
762 if (params.isMember(t.first))
763 {
764 ++count;
765 seedType = &t;
766 }
767 }
768
769 if (count != 1)
770 {
771 error = RPC::make_param_error(
772 "Exactly one of the following must be specified: " +
773 std::string(jss::passphrase) + ", " + std::string(jss::seed) +
774 " or " + std::string(jss::seed_hex));
775 return std::nullopt;
776 }
777
778 // Make sure a string is present
779 auto const& param = params[seedType->first];
780 if (!param.isString())
781 {
782 error = RPC::expected_field_error(seedType->first, "string");
783 return std::nullopt;
784 }
785
786 auto const fieldContents = param.asString();
787
788 // Convert string to seed.
789 std::optional<Seed> seed = seedType->second(fieldContents);
790
791 if (!seed)
792 error = rpcError(rpcBAD_SEED);
793
794 return seed;
795}
796
799 Json::Value const& params,
800 Json::Value& error,
801 unsigned int apiVersion)
802{
803 bool const has_key_type = params.isMember(jss::key_type);
804
805 // All of the secret types we allow, but only one at a time.
806 static char const* const secretTypes[]{
807 jss::passphrase.c_str(),
808 jss::secret.c_str(),
809 jss::seed.c_str(),
810 jss::seed_hex.c_str()};
811
812 // Identify which secret type is in use.
813 char const* secretType = nullptr;
814 int count = 0;
815 for (auto t : secretTypes)
816 {
817 if (params.isMember(t))
818 {
819 ++count;
820 secretType = t;
821 }
822 }
823
824 if (count == 0 || secretType == nullptr)
825 {
826 error = RPC::missing_field_error(jss::secret);
827 return {};
828 }
829
830 if (count > 1)
831 {
832 error = RPC::make_param_error(
833 "Exactly one of the following must be specified: " +
834 std::string(jss::passphrase) + ", " + std::string(jss::secret) +
835 ", " + std::string(jss::seed) + " or " +
836 std::string(jss::seed_hex));
837 return {};
838 }
839
842
843 if (has_key_type)
844 {
845 if (!params[jss::key_type].isString())
846 {
847 error = RPC::expected_field_error(jss::key_type, "string");
848 return {};
849 }
850
851 keyType = keyTypeFromString(params[jss::key_type].asString());
852
853 if (!keyType)
854 {
855 if (apiVersion > 1u)
857 else
858 error = RPC::invalid_field_error(jss::key_type);
859 return {};
860 }
861
862 // using strcmp as pointers may not match (see
863 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
864 if (strcmp(secretType, jss::secret.c_str()) == 0)
865 {
866 error = RPC::make_param_error(
867 "The secret field is not allowed if " +
868 std::string(jss::key_type) + " is used.");
869 return {};
870 }
871 }
872
873 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
874 // non-standard way. While we never encode seeds that way, we try
875 // to detect such keys to avoid user confusion.
876 // using strcmp as pointers may not match (see
877 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
878 if (strcmp(secretType, jss::seed_hex.c_str()) != 0)
879 {
880 seed = RPC::parseRippleLibSeed(params[secretType]);
881
882 if (seed)
883 {
884 // If the user passed in an Ed25519 seed but *explicitly*
885 // requested another key type, return an error.
887 {
888 error = RPC::make_error(
889 rpcBAD_SEED, "Specified seed is for an Ed25519 wallet.");
890 return {};
891 }
892
893 keyType = KeyType::ed25519;
894 }
895 }
896
897 if (!keyType)
898 keyType = KeyType::secp256k1;
899
900 if (!seed)
901 {
902 if (has_key_type)
903 seed = getSeedFromRPC(params, error);
904 else
905 {
906 if (!params[jss::secret].isString())
907 {
908 error = RPC::expected_field_error(jss::secret, "string");
909 return {};
910 }
911
912 seed = parseGenericSeed(params[jss::secret].asString());
913 }
914 }
915
916 if (!seed)
917 {
918 if (!contains_error(error))
919 {
920 error = RPC::make_error(
922 }
923
924 return {};
925 }
926
927 if (keyType != KeyType::secp256k1 && keyType != KeyType::ed25519)
928 LogicError("keypairForSignature: invalid key type");
929
930 return generateKeyPair(*keyType, *seed);
931}
932
935{
937 if (params.isMember(jss::type))
938 {
939 static constexpr auto types = std::to_array<
941#pragma push_macro("LEDGER_ENTRY")
942#undef LEDGER_ENTRY
943
944#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \
945 {jss::name, jss::rpcName, tag},
946
947#include <xrpl/protocol/detail/ledger_entries.macro>
948
949#undef LEDGER_ENTRY
950#pragma pop_macro("LEDGER_ENTRY")
951 });
952
953 auto const& p = params[jss::type];
954 if (!p.isString())
955 {
956 result.first = RPC::Status{
957 rpcINVALID_PARAMS, "Invalid field 'type', not string."};
958 XRPL_ASSERT(
959 result.first.type() == RPC::Status::Type::error_code_i,
960 "ripple::RPC::chooseLedgerEntryType : first valid result type");
961 return result;
962 }
963
964 // Use the passed in parameter to find a ledger type based on matching
965 // against the canonical name (case-insensitive) or the RPC name
966 // (case-sensitive).
967 auto const filter = p.asString();
968 auto const iter =
969 std::ranges::find_if(types, [&filter](decltype(types.front())& t) {
970 return boost::iequals(std::get<0>(t), filter) ||
971 std::get<1>(t) == filter;
972 });
973 if (iter == types.end())
974 {
975 result.first =
976 RPC::Status{rpcINVALID_PARAMS, "Invalid field 'type'."};
977 XRPL_ASSERT(
978 result.first.type() == RPC::Status::Type::error_code_i,
979 "ripple::RPC::chooseLedgerEntryType : second valid result "
980 "type");
981 return result;
982 }
983 result.second = std::get<2>(*iter);
984 }
985 return result;
986}
987
988bool
990{
991 switch (type)
992 {
993 case LedgerEntryType::ltAMENDMENTS:
994 case LedgerEntryType::ltDIR_NODE:
995 case LedgerEntryType::ltFEE_SETTINGS:
996 case LedgerEntryType::ltLEDGER_HASHES:
997 case LedgerEntryType::ltNEGATIVE_UNL:
998 return false;
999 default:
1000 return true;
1001 }
1002}
1003
1005beast::SemanticVersion const goodVersion("1.0.0");
1006beast::SemanticVersion const lastVersion("1.0.0");
1007
1008unsigned int
1009getAPIVersionNumber(Json::Value const& jv, bool betaEnabled)
1010{
1011 static Json::Value const minVersion(RPC::apiMinimumSupportedVersion);
1012 static Json::Value const invalidVersion(RPC::apiInvalidVersion);
1013
1014 Json::Value const maxVersion(
1016 Json::Value requestedVersion(RPC::apiVersionIfUnspecified);
1017 if (jv.isObject())
1018 {
1019 requestedVersion = jv.get(jss::api_version, requestedVersion);
1020 }
1021 if (!(requestedVersion.isInt() || requestedVersion.isUInt()) ||
1022 requestedVersion < minVersion || requestedVersion > maxVersion)
1023 {
1024 requestedVersion = invalidVersion;
1025 }
1026 return requestedVersion.asUInt();
1027}
1028
1031{
1032 auto const hasHash = context.params.isMember(jss::ledger_hash);
1033 auto const hasIndex = context.params.isMember(jss::ledger_index);
1034 std::uint32_t ledgerIndex = 0;
1035
1036 auto& ledgerMaster = context.app.getLedgerMaster();
1037 LedgerHash ledgerHash;
1038
1039 if ((hasHash && hasIndex) || !(hasHash || hasIndex))
1040 {
1041 return RPC::make_param_error(
1042 "Exactly one of ledger_hash and ledger_index can be set.");
1043 }
1044
1046
1047 if (hasHash)
1048 {
1049 auto const& jsonHash = context.params[jss::ledger_hash];
1050 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
1051 return RPC::invalid_field_error(jss::ledger_hash);
1052 }
1053 else
1054 {
1055 auto const& jsonIndex = context.params[jss::ledger_index];
1056 if (!jsonIndex.isInt())
1057 return RPC::invalid_field_error(jss::ledger_index);
1058
1059 // We need a validated ledger to get the hash from the sequence
1060 if (ledgerMaster.getValidatedLedgerAge() >
1062 {
1063 if (context.apiVersion == 1)
1064 return rpcError(rpcNO_CURRENT);
1065 return rpcError(rpcNOT_SYNCED);
1066 }
1067
1068 ledgerIndex = jsonIndex.asInt();
1069 auto ledger = ledgerMaster.getValidatedLedger();
1070
1071 if (ledgerIndex >= ledger->info().seq)
1072 return RPC::make_param_error("Ledger index too large");
1073 if (ledgerIndex <= 0)
1074 return RPC::make_param_error("Ledger index too small");
1075
1076 auto const j = context.app.journal("RPCHandler");
1077 // Try to get the hash of the desired ledger from the validated
1078 // ledger
1079 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1080 if (!neededHash)
1081 {
1082 // Find a ledger more likely to have the hash of the desired
1083 // ledger
1084 auto const refIndex = getCandidateLedger(ledgerIndex);
1085 auto refHash = hashOfSeq(*ledger, refIndex, j);
1086 XRPL_ASSERT(
1087 refHash,
1088 "ripple::RPC::getLedgerByContext : nonzero ledger hash");
1089
1090 ledger = ledgerMaster.getLedgerByHash(*refHash);
1091 if (!ledger)
1092 {
1093 // We don't have the ledger we need to figure out which
1094 // ledger they want. Try to get it.
1095
1096 if (auto il = context.app.getInboundLedgers().acquire(
1097 *refHash, refIndex, InboundLedger::Reason::GENERIC))
1098 {
1099 Json::Value jvResult = RPC::make_error(
1101 "acquiring ledger containing requested index");
1102 jvResult[jss::acquiring] =
1103 getJson(LedgerFill(*il, &context));
1104 return jvResult;
1105 }
1106
1107 if (auto il = context.app.getInboundLedgers().find(*refHash))
1108 {
1109 Json::Value jvResult = RPC::make_error(
1111 "acquiring ledger containing requested index");
1112 jvResult[jss::acquiring] = il->getJson(0);
1113 return jvResult;
1114 }
1115
1116 // Likely the app is shutting down
1117 return Json::Value();
1118 }
1119
1120 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1121 }
1122 XRPL_ASSERT(
1123 neededHash,
1124 "ripple::RPC::getLedgerByContext : nonzero needed hash");
1125 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
1126 }
1127
1128 // Try to get the desired ledger
1129 // Verify all nodes even if we think we have it
1130 auto ledger = context.app.getInboundLedgers().acquire(
1131 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
1132
1133 // In standalone mode, accept the ledger from the ledger cache
1134 if (!ledger && context.app.config().standalone())
1135 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
1136
1137 if (ledger)
1138 return ledger;
1139
1140 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
1141 return il->getJson(0);
1142
1143 return RPC::make_error(
1144 rpcNOT_READY, "findCreate failed to return an inbound ledger");
1145}
1146
1147} // namespace RPC
1148} // namespace ripple
T begin(T... args)
Represents a JSON value.
Definition json_value.h:149
bool isString() const
UInt asUInt() const
bool isObject() const
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...
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:336
virtual std::shared_ptr< Ledger const > acquire(uint256 const &hash, std::uint32_t seq, InboundLedger::Reason)=0
virtual std::shared_ptr< InboundLedger > find(LedgerHash const &hash)=0
std::shared_ptr< Ledger const > getValidatedLedger()
std::shared_ptr< ReadView const > getCurrentLedger()
bool isValidated(ReadView const &ledger)
std::shared_ptr< Ledger const > getClosedLedger()
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
std::shared_ptr< Ledger const > getLedgerByHash(uint256 const &hash)
LedgerIndex getValidLedgerIndex()
A view into a ledger.
Definition ReadView.h:51
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
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 is_same_v
T max(T... args)
T min(T... args)
@ stringValue
UTF-8 string value.
Definition json_value.h:42
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
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)
error_code_i accountFromStringWithCode(AccountID &result, std::string const &strIdent, bool bStrict)
Decode account ID from string.
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
std::optional< Seed > getSeedFromRPC(Json::Value const &params, Json::Value &error)
beast::SemanticVersion const lastVersion("1.0.0")
Definition RPCHelpers.h:209
static constexpr auto apiMaximumSupportedVersion
Definition ApiVersion.h:57
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
std::variant< std::shared_ptr< Ledger const >, Json::Value > getLedgerByContext(RPC::JsonContext &context)
Return a ledger based on ledger_hash or ledger_index, or an RPC error.
Json::Value invalid_field_error(std::string const &name)
Definition ErrorCodes.h:325
bool isAccountObjectsValidType(LedgerEntryType const &type)
Check if the type is a valid filtering type for account_objects method.
static constexpr std::integral_constant< unsigned, Version > apiVersion
Definition ApiVersion.h:53
bool isRelatedToAccount(ReadView const &ledger, std::shared_ptr< SLE const > const &sle, AccountID const &accountID)
Tests if a SLE is owned by accountID.
void injectSLE(Json::Value &jv, SLE const &sle)
Inject JSON describing ledger entry.
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:271
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...
static constexpr auto apiBetaVersion
Definition ApiVersion.h:61
std::pair< RPC::Status, LedgerEntryType > chooseLedgerEntryType(Json::Value const &params)
std::string invalid_field_message(std::string const &name)
Definition ErrorCodes.h:313
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.
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...
beast::SemanticVersion const goodVersion("1.0.0")
Definition RPCHelpers.h:208
Json::Value accountFromString(AccountID &result, std::string const &strIdent, bool bStrict)
static constexpr auto apiVersionIfUnspecified
Definition ApiVersion.h:58
Json::Value expected_field_error(std::string const &name, std::string const &type)
Definition ErrorCodes.h:349
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext &context, Json::Value &result)
Look up a ledger from a request and fill a Json::Result with the data representing a ledger.
Status ledgerFromSpecifier(T &ledger, org::xrpl::rpc::v1::LedgerSpecifier const &specifier, Context &context)
std::optional< AccountID > accountFromStringStrict(std::string const &account)
Get an AccountID from an account ID or public key.
hash_set< AccountID > parseAccountIds(Json::Value const &jvArray)
static constexpr auto apiInvalidVersion
Definition ApiVersion.h:55
std::uint64_t getStartHint(std::shared_ptr< SLE const > const &sle, AccountID const &accountID)
Gets the start hint for traversing account objects.
static constexpr auto apiMinimumSupportedVersion
Definition ApiVersion.h:56
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:283
std::optional< Seed > parseRippleLibSeed(Json::Value const &value)
std::optional< std::pair< PublicKey, SecretKey > > keypairForSignature(Json::Value const &params, Json::Value &error, unsigned int apiVersion)
Charge const feeHeavyBurdenRPC
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:190
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:380
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:403
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:411
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:330
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
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:429
@ 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.
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:958
AccountID calcAccountID(PublicKey const &pk)
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.
@ ltANY
A special type, matching any ledger entry type.
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.
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
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)