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