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 (!context.params.isMember(jss::limit) ||
708 context.params[jss::limit].isNull())
709 return std::nullopt;
710
711 auto const& jvLimit = context.params[jss::limit];
712 if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0)))
713 return RPC::expected_field_error(jss::limit, "unsigned integer");
714
715 limit = jvLimit.asUInt();
716 if (limit == 0)
717 return RPC::invalid_field_error(jss::limit);
718
719 if (!isUnlimited(context.role))
720 limit = std::max(range.rmin, std::min(range.rmax, limit));
721
722 return std::nullopt;
723}
724
727{
728 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
729 // non-standard way. While rippled never encode seeds that way, we
730 // try to detect such keys to avoid user confusion.
731 if (!value.isString())
732 return std::nullopt;
733
734 auto const result = decodeBase58Token(value.asString(), TokenType::None);
735
736 if (result.size() == 18 &&
737 static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
738 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
739 return Seed(makeSlice(result.substr(2)));
740
741 return std::nullopt;
742}
743
746{
747 using string_to_seed_t =
749 using seed_match_t = std::pair<char const*, string_to_seed_t>;
750
751 static seed_match_t const seedTypes[]{
752 {jss::passphrase.c_str(),
753 [](std::string const& s) { return parseGenericSeed(s); }},
754 {jss::seed.c_str(),
755 [](std::string const& s) { return parseBase58<Seed>(s); }},
756 {jss::seed_hex.c_str(), [](std::string const& s) {
757 uint128 i;
758 if (i.parseHex(s))
759 return std::optional<Seed>(Slice(i.data(), i.size()));
760 return std::optional<Seed>{};
761 }}};
762
763 // Identify which seed type is in use.
764 seed_match_t const* seedType = nullptr;
765 int count = 0;
766 for (auto const& t : seedTypes)
767 {
768 if (params.isMember(t.first))
769 {
770 ++count;
771 seedType = &t;
772 }
773 }
774
775 if (count != 1)
776 {
777 error = RPC::make_param_error(
778 "Exactly one of the following must be specified: " +
779 std::string(jss::passphrase) + ", " + std::string(jss::seed) +
780 " or " + std::string(jss::seed_hex));
781 return std::nullopt;
782 }
783
784 // Make sure a string is present
785 auto const& param = params[seedType->first];
786 if (!param.isString())
787 {
788 error = RPC::expected_field_error(seedType->first, "string");
789 return std::nullopt;
790 }
791
792 auto const fieldContents = param.asString();
793
794 // Convert string to seed.
795 std::optional<Seed> seed = seedType->second(fieldContents);
796
797 if (!seed)
798 error = rpcError(rpcBAD_SEED);
799
800 return seed;
801}
802
805 Json::Value const& params,
806 Json::Value& error,
807 unsigned int apiVersion)
808{
809 bool const has_key_type = params.isMember(jss::key_type);
810
811 // All of the secret types we allow, but only one at a time.
812 static char const* const secretTypes[]{
813 jss::passphrase.c_str(),
814 jss::secret.c_str(),
815 jss::seed.c_str(),
816 jss::seed_hex.c_str()};
817
818 // Identify which secret type is in use.
819 char const* secretType = nullptr;
820 int count = 0;
821 for (auto t : secretTypes)
822 {
823 if (params.isMember(t))
824 {
825 ++count;
826 secretType = t;
827 }
828 }
829
830 if (count == 0 || secretType == nullptr)
831 {
832 error = RPC::missing_field_error(jss::secret);
833 return {};
834 }
835
836 if (count > 1)
837 {
838 error = RPC::make_param_error(
839 "Exactly one of the following must be specified: " +
840 std::string(jss::passphrase) + ", " + std::string(jss::secret) +
841 ", " + std::string(jss::seed) + " or " +
842 std::string(jss::seed_hex));
843 return {};
844 }
845
848
849 if (has_key_type)
850 {
851 if (!params[jss::key_type].isString())
852 {
853 error = RPC::expected_field_error(jss::key_type, "string");
854 return {};
855 }
856
857 keyType = keyTypeFromString(params[jss::key_type].asString());
858
859 if (!keyType)
860 {
861 if (apiVersion > 1u)
863 else
864 error = RPC::invalid_field_error(jss::key_type);
865 return {};
866 }
867
868 // using strcmp as pointers may not match (see
869 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
870 if (strcmp(secretType, jss::secret.c_str()) == 0)
871 {
872 error = RPC::make_param_error(
873 "The secret field is not allowed if " +
874 std::string(jss::key_type) + " is used.");
875 return {};
876 }
877 }
878
879 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
880 // non-standard way. While we never encode seeds that way, we try
881 // to detect such keys to avoid user confusion.
882 // using strcmp as pointers may not match (see
883 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
884 if (strcmp(secretType, jss::seed_hex.c_str()) != 0)
885 {
886 seed = RPC::parseRippleLibSeed(params[secretType]);
887
888 if (seed)
889 {
890 // If the user passed in an Ed25519 seed but *explicitly*
891 // requested another key type, return an error.
893 {
894 error = RPC::make_error(
895 rpcBAD_SEED, "Specified seed is for an Ed25519 wallet.");
896 return {};
897 }
898
899 keyType = KeyType::ed25519;
900 }
901 }
902
903 if (!keyType)
904 keyType = KeyType::secp256k1;
905
906 if (!seed)
907 {
908 if (has_key_type)
909 seed = getSeedFromRPC(params, error);
910 else
911 {
912 if (!params[jss::secret].isString())
913 {
914 error = RPC::expected_field_error(jss::secret, "string");
915 return {};
916 }
917
918 seed = parseGenericSeed(params[jss::secret].asString());
919 }
920 }
921
922 if (!seed)
923 {
924 if (!contains_error(error))
925 {
926 error = RPC::make_error(
928 }
929
930 return {};
931 }
932
933 if (keyType != KeyType::secp256k1 && keyType != KeyType::ed25519)
934 LogicError("keypairForSignature: invalid key type");
935
936 return generateKeyPair(*keyType, *seed);
937}
938
941{
943 if (params.isMember(jss::type))
944 {
945 static constexpr auto types = std::to_array<
947#pragma push_macro("LEDGER_ENTRY")
948#undef LEDGER_ENTRY
949
950#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \
951 {jss::name, jss::rpcName, tag},
952
953#include <xrpl/protocol/detail/ledger_entries.macro>
954
955#undef LEDGER_ENTRY
956#pragma pop_macro("LEDGER_ENTRY")
957 });
958
959 auto const& p = params[jss::type];
960 if (!p.isString())
961 {
962 result.first = RPC::Status{
963 rpcINVALID_PARAMS, "Invalid field 'type', not string."};
964 XRPL_ASSERT(
965 result.first.type() == RPC::Status::Type::error_code_i,
966 "ripple::RPC::chooseLedgerEntryType : first valid result type");
967 return result;
968 }
969
970 // Use the passed in parameter to find a ledger type based on matching
971 // against the canonical name (case-insensitive) or the RPC name
972 // (case-sensitive).
973 auto const filter = p.asString();
974 auto const iter =
975 std::ranges::find_if(types, [&filter](decltype(types.front())& t) {
976 return boost::iequals(std::get<0>(t), filter) ||
977 std::get<1>(t) == filter;
978 });
979 if (iter == types.end())
980 {
981 result.first =
982 RPC::Status{rpcINVALID_PARAMS, "Invalid field 'type'."};
983 XRPL_ASSERT(
984 result.first.type() == RPC::Status::Type::error_code_i,
985 "ripple::RPC::chooseLedgerEntryType : second valid result "
986 "type");
987 return result;
988 }
989 result.second = std::get<2>(*iter);
990 }
991 return result;
992}
993
994bool
996{
997 switch (type)
998 {
999 case LedgerEntryType::ltAMENDMENTS:
1000 case LedgerEntryType::ltDIR_NODE:
1001 case LedgerEntryType::ltFEE_SETTINGS:
1002 case LedgerEntryType::ltLEDGER_HASHES:
1003 case LedgerEntryType::ltNEGATIVE_UNL:
1004 return false;
1005 default:
1006 return true;
1007 }
1008}
1009
1011beast::SemanticVersion const goodVersion("1.0.0");
1012beast::SemanticVersion const lastVersion("1.0.0");
1013
1014unsigned int
1015getAPIVersionNumber(Json::Value const& jv, bool betaEnabled)
1016{
1017 static Json::Value const minVersion(RPC::apiMinimumSupportedVersion);
1018 static Json::Value const invalidVersion(RPC::apiInvalidVersion);
1019
1020 Json::Value const maxVersion(
1022 Json::Value requestedVersion(RPC::apiVersionIfUnspecified);
1023 if (jv.isObject())
1024 {
1025 requestedVersion = jv.get(jss::api_version, requestedVersion);
1026 }
1027 if (!(requestedVersion.isInt() || requestedVersion.isUInt()) ||
1028 requestedVersion < minVersion || requestedVersion > maxVersion)
1029 {
1030 requestedVersion = invalidVersion;
1031 }
1032 return requestedVersion.asUInt();
1033}
1034
1037{
1038 auto const hasHash = context.params.isMember(jss::ledger_hash);
1039 auto const hasIndex = context.params.isMember(jss::ledger_index);
1040 std::uint32_t ledgerIndex = 0;
1041
1042 auto& ledgerMaster = context.app.getLedgerMaster();
1043 LedgerHash ledgerHash;
1044
1045 if ((hasHash && hasIndex) || !(hasHash || hasIndex))
1046 {
1047 return RPC::make_param_error(
1048 "Exactly one of ledger_hash and ledger_index can be set.");
1049 }
1050
1052
1053 if (hasHash)
1054 {
1055 auto const& jsonHash = context.params[jss::ledger_hash];
1056 if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
1057 return RPC::invalid_field_error(jss::ledger_hash);
1058 }
1059 else
1060 {
1061 auto const& jsonIndex = context.params[jss::ledger_index];
1062 if (!jsonIndex.isInt())
1063 return RPC::invalid_field_error(jss::ledger_index);
1064
1065 // We need a validated ledger to get the hash from the sequence
1066 if (ledgerMaster.getValidatedLedgerAge() >
1068 {
1069 if (context.apiVersion == 1)
1070 return rpcError(rpcNO_CURRENT);
1071 return rpcError(rpcNOT_SYNCED);
1072 }
1073
1074 ledgerIndex = jsonIndex.asInt();
1075 auto ledger = ledgerMaster.getValidatedLedger();
1076
1077 if (ledgerIndex >= ledger->info().seq)
1078 return RPC::make_param_error("Ledger index too large");
1079 if (ledgerIndex <= 0)
1080 return RPC::make_param_error("Ledger index too small");
1081
1082 auto const j = context.app.journal("RPCHandler");
1083 // Try to get the hash of the desired ledger from the validated
1084 // ledger
1085 auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1086 if (!neededHash)
1087 {
1088 // Find a ledger more likely to have the hash of the desired
1089 // ledger
1090 auto const refIndex = getCandidateLedger(ledgerIndex);
1091 auto refHash = hashOfSeq(*ledger, refIndex, j);
1092 XRPL_ASSERT(
1093 refHash,
1094 "ripple::RPC::getLedgerByContext : nonzero ledger hash");
1095
1096 ledger = ledgerMaster.getLedgerByHash(*refHash);
1097 if (!ledger)
1098 {
1099 // We don't have the ledger we need to figure out which
1100 // ledger they want. Try to get it.
1101
1102 if (auto il = context.app.getInboundLedgers().acquire(
1103 *refHash, refIndex, InboundLedger::Reason::GENERIC))
1104 {
1105 Json::Value jvResult = RPC::make_error(
1107 "acquiring ledger containing requested index");
1108 jvResult[jss::acquiring] =
1109 getJson(LedgerFill(*il, &context));
1110 return jvResult;
1111 }
1112
1113 if (auto il = context.app.getInboundLedgers().find(*refHash))
1114 {
1115 Json::Value jvResult = RPC::make_error(
1117 "acquiring ledger containing requested index");
1118 jvResult[jss::acquiring] = il->getJson(0);
1119 return jvResult;
1120 }
1121
1122 // Likely the app is shutting down
1123 return Json::Value();
1124 }
1125
1126 neededHash = hashOfSeq(*ledger, ledgerIndex, j);
1127 }
1128 XRPL_ASSERT(
1129 neededHash,
1130 "ripple::RPC::getLedgerByContext : nonzero needed hash");
1131 ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
1132 }
1133
1134 // Try to get the desired ledger
1135 // Verify all nodes even if we think we have it
1136 auto ledger = context.app.getInboundLedgers().acquire(
1137 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
1138
1139 // In standalone mode, accept the ledger from the ledger cache
1140 if (!ledger && context.app.config().standalone())
1141 ledger = ledgerMaster.getLedgerByHash(ledgerHash);
1142
1143 if (ledger)
1144 return ledger;
1145
1146 if (auto il = context.app.getInboundLedgers().find(ledgerHash))
1147 return il->getJson(0);
1148
1149 return RPC::make_error(
1150 rpcNOT_READY, "findCreate failed to return an inbound ledger");
1151}
1152
1153} // namespace RPC
1154} // 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 isNull() const
isNull() tests to see if this field is null.
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=JsonOptions::none) 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:961
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)