xrpld
Loading...
Searching...
No Matches
RPCHelpers.cpp
1#include <xrpld/rpc/detail/RPCHelpers.h>
2
3#include <xrpld/rpc/Context.h>
4#include <xrpld/rpc/DeliveredAmount.h>
5#include <xrpld/rpc/Role.h>
6#include <xrpld/rpc/Status.h>
7#include <xrpld/rpc/detail/Tuning.h>
8
9#include <xrpl/basics/Log.h>
10#include <xrpl/basics/Slice.h>
11#include <xrpl/basics/UnorderedContainers.h>
12#include <xrpl/basics/base_uint.h>
13#include <xrpl/basics/contract.h>
14#include <xrpl/beast/utility/Journal.h>
15#include <xrpl/beast/utility/instrumentation.h>
16#include <xrpl/core/ServiceRegistry.h>
17#include <xrpl/ledger/ReadView.h>
18#include <xrpl/protocol/AccountID.h>
19#include <xrpl/protocol/Asset.h>
20#include <xrpl/protocol/ErrorCodes.h>
21#include <xrpl/protocol/Indexes.h>
22#include <xrpl/protocol/Issue.h>
23#include <xrpl/protocol/KeyType.h>
24#include <xrpl/protocol/LedgerFormats.h>
25#include <xrpl/protocol/PublicKey.h>
26#include <xrpl/protocol/RPCErr.h>
27#include <xrpl/protocol/SField.h>
28#include <xrpl/protocol/SecretKey.h>
29#include <xrpl/protocol/Seed.h>
30#include <xrpl/protocol/UintTypes.h>
31#include <xrpl/protocol/jss.h>
32#include <xrpl/protocol/tokens.h>
33
34#include <boost/algorithm/string/predicate.hpp>
35
36#include <algorithm>
37#include <array>
38#include <cstdint>
39#include <cstring>
40#include <format>
41#include <functional>
42#include <optional>
43#include <tuple>
44#include <utility>
45
46namespace xrpl::rpc {
47
48std::uint64_t
49getStartHint(SLE::ConstRef sle, AccountID const& accountID)
50{
51 if (sle->getType() == ltRIPPLE_STATE)
52 {
53 if (sle->getFieldAmount(sfLowLimit).getIssuer() == accountID)
54 {
55 return sle->getFieldU64(sfLowNode);
56 }
57 if (sle->getFieldAmount(sfHighLimit).getIssuer() == accountID)
58 {
59 return sle->getFieldU64(sfHighNode);
60 }
61 }
62
63 if (!sle->isFieldPresent(sfOwnerNode))
64 return 0;
65
66 return sle->getFieldU64(sfOwnerNode);
67}
68
69namespace {
70
71// UINT64 sf*Node fields that record a page number in an owner directory.
72// Keep in sync with sfields.macro; the xrpl.rpc.RPCHelpers test enforces it.
73constexpr std::array<SField const*, 9> kOwnerDirNodeFields{
74 &sfOwnerNode,
75 &sfLowNode,
76 &sfHighNode,
77 &sfDestinationNode,
78 &sfIssuerNode,
79 &sfSubjectNode,
80 &sfSponseeNode,
81 &sfLoanBrokerNode,
82 &sfVaultNode,
83};
84
85} // namespace
86
87bool
89{
90 return std::ranges::contains(kOwnerDirNodeFields, &field);
91}
92
93bool
94isRelatedToAccount(ReadView const& ledger, SLE::ConstRef sle, AccountID const& accountID)
95{
96 // Marker validator for account_lines / account_offers / account_channels
97 // pagination: probes each owner-directory page-hint field on `sle` and
98 // returns true iff `sle`'s key is present on that page in `accountID`'s
99 // owner directory. Bounded by kOwnerDirNodeFields.size() ledger reads.
100 auto const ownerDir = keylet::ownerDir(accountID);
101
102 auto const pageContainsKey = [&](std::uint64_t node) {
103 auto const page = ledger.read(keylet::page(ownerDir, node));
104 return page && std::ranges::contains(page->getFieldV256(sfIndexes), sle->key());
105 };
106
107 return std::ranges::any_of(kOwnerDirNodeFields, [&](SField const* field) {
108 return sle->isFieldPresent(*field) && pageContainsKey(sle->getFieldU64(*field));
109 });
110}
111
114{
115 HashSet<AccountID> result;
116 for (auto const& jv : jvArray)
117 {
118 if (!jv.isString())
119 return HashSet<AccountID>();
120 auto const id = parseBase58<AccountID>(jv.asString());
121 if (!id)
122 return HashSet<AccountID>();
123 result.insert(*id);
124 }
125 return result;
126}
127
130{
131 HashSet<MPTID> result;
132 for (auto const& jv : jvArray)
133 {
134 if (!jv.isString())
135 return HashSet<MPTID>();
136
137 auto const mptIssuanceIdStr = jv.asString();
138 MPTID mptIssuanceID;
139 if (!mptIssuanceID.parseHex(mptIssuanceIdStr))
140 return HashSet<MPTID>();
141
142 result.insert(mptIssuanceID);
143 }
144 return result;
145}
146
148readLimitField(unsigned int& limit, tuning::LimitRange const& range, JsonContext const& context)
149{
150 limit = range.rDefault;
151 if (!context.params.isMember(jss::limit) || context.params[jss::limit].isNull())
152 return std::nullopt;
153
154 auto const& jvLimit = context.params[jss::limit];
155 if (!jvLimit.isUInt() && (!jvLimit.isInt() || jvLimit.asInt() < 0))
156 return rpc::expectedFieldError(jss::limit, "unsigned integer");
157
158 limit = jvLimit.asUInt();
159 if (limit == 0)
160 return rpc::invalidFieldError(jss::limit);
161
162 if (!isUnlimited(context.role))
163 limit = std::max(range.rmin, std::min(range.rmax, limit));
164
165 return std::nullopt;
166}
167
170{
171 // XrplLib encodes seed used to generate an Ed25519 wallet in a
172 // non-standard way. While xrpld never encode seeds that way, we
173 // try to detect such keys to avoid user confusion.
174 if (!value.isString())
175 return std::nullopt;
176
177 auto const result = decodeBase58Token(value.asString(), TokenType::None);
178
179 if (result.size() == 18 && static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
180 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
181 return Seed(makeSlice(result.substr(2)));
182
183 return std::nullopt;
184}
185
188{
189 using StringToSeedT = std::function<std::optional<Seed>(std::string const&)>;
190 using SeedMatchT = std::pair<char const*, StringToSeedT>;
191
192 static SeedMatchT const kSeedTypes[]{
193 {jss::passphrase.cStr(), [](std::string const& s) { return parseGenericSeed(s); }},
194 {jss::seed.cStr(), [](std::string const& s) { return parseBase58<Seed>(s); }},
195 {jss::seed_hex.cStr(), [](std::string const& s) {
196 UInt128 i;
197 if (i.parseHex(s))
198 return std::optional<Seed>(Slice(i.data(), i.size()));
199 return std::optional<Seed>{};
200 }}};
201
202 // Identify which seed type is in use.
203 SeedMatchT const* seedType = nullptr;
204 int count = 0;
205 for (auto const& t : kSeedTypes)
206 {
207 if (params.isMember(t.first))
208 {
209 ++count;
210 seedType = &t;
211 }
212 }
213
214 if (count != 1)
215 {
216 error = rpc::makeParamError(
217 "Exactly one of the following must be specified: " + std::string(jss::passphrase) +
218 ", " + std::string(jss::seed) + " or " + std::string(jss::seed_hex));
219 return std::nullopt;
220 }
221
222 // Make sure a string is present
223 auto const& param = params[seedType->first];
224 if (!param.isString())
225 {
226 error = rpc::expectedFieldError(seedType->first, "string");
227 return std::nullopt;
228 }
229
230 auto const fieldContents = param.asString();
231
232 // Convert string to seed.
233 std::optional<Seed> seed = seedType->second(fieldContents);
234
235 if (!seed)
236 error = rpcError(RpcBadSeed);
237
238 return seed;
239}
240
242keypairForSignature(json::Value const& params, json::Value& error, unsigned int apiVersion)
243{
244 bool const hasKeyType = params.isMember(jss::key_type);
245
246 // All of the secret types we allow, but only one at a time.
247 static char const* const kSecretTypes[]{
248 jss::passphrase.cStr(), jss::secret.cStr(), jss::seed.cStr(), jss::seed_hex.cStr()};
249
250 // Identify which secret type is in use.
251 char const* secretType = nullptr;
252 int count = 0;
253 for (auto t : kSecretTypes)
254 {
255 if (params.isMember(t))
256 {
257 ++count;
258 secretType = t;
259 }
260 }
261
262 if (count == 0 || secretType == nullptr)
263 {
264 error = rpc::missingFieldError(jss::secret);
265 return {};
266 }
267
268 if (count > 1)
269 {
270 error = rpc::makeParamError(
271 "Exactly one of the following must be specified: " + std::string(jss::passphrase) +
272 ", " + std::string(jss::secret) + ", " + std::string(jss::seed) + " or " +
273 std::string(jss::seed_hex));
274 return {};
275 }
276
279
280 if (hasKeyType)
281 {
282 if (!params[jss::key_type].isString())
283 {
284 error = rpc::expectedFieldError(jss::key_type, "string");
285 return {};
286 }
287
288 keyType = keyTypeFromString(params[jss::key_type].asString());
289
290 if (!keyType)
291 {
292 if (apiVersion > 1u)
293 {
295 }
296 else
297 {
298 error = rpc::invalidFieldError(jss::key_type);
299 }
300 return {};
301 }
302
303 // using strcmp as pointers may not match (see
304 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
305 if (strcmp(secretType, jss::secret.cStr()) == 0)
306 {
307 error = rpc::makeParamError(
308 "The secret field is not allowed if " + std::string(jss::key_type) + " is used.");
309 return {};
310 }
311 }
312
313 // XrplLib encodes seed used to generate an Ed25519 wallet in a
314 // non-standard way. While we never encode seeds that way, we try
315 // to detect such keys to avoid user confusion.
316 // using strcmp as pointers may not match (see
317 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
318 if (strcmp(secretType, jss::seed_hex.cStr()) != 0)
319 {
320 seed = rpc::parseXrplLibSeed(params[secretType]);
321
322 if (seed)
323 {
324 // If the user passed in an Ed25519 seed but *explicitly*
325 // requested another key type, return an error.
327 {
328 error = rpc::makeError(RpcBadSeed, "Specified seed is for an Ed25519 wallet.");
329 return {};
330 }
331
332 keyType = KeyType::Ed25519;
333 }
334 }
335
336 if (!keyType)
337 keyType = KeyType::Secp256k1;
338
339 if (!seed)
340 {
341 if (hasKeyType)
342 {
343 seed = getSeedFromRPC(params, error);
344 }
345 else
346 {
347 if (!params[jss::secret].isString())
348 {
349 error = rpc::expectedFieldError(jss::secret, "string");
350 return {};
351 }
352
353 seed = parseGenericSeed(params[jss::secret].asString());
354 }
355 }
356
357 if (!seed)
358 {
359 if (!containsError(error))
360 {
362 }
363
364 return {};
365 }
366
367 if (keyType != KeyType::Secp256k1 && keyType != KeyType::Ed25519)
368 logicError("keypairForSignature: invalid key type");
369
370 return generateKeyPair(*keyType, *seed);
371}
372
375{
377 if (params.isMember(jss::type))
378 {
379 static constexpr auto kTypes =
380 std::to_array<std::tuple<char const*, char const*, LedgerEntryType>>({
381#pragma push_macro("LEDGER_ENTRY")
382#undef LEDGER_ENTRY
383
384#define LEDGER_ENTRY(tag, value, name, rpcName, ...) {jss::name, jss::rpcName, tag},
385
386#include <xrpl/protocol/detail/ledger_entries.macro>
387
388#undef LEDGER_ENTRY
389#pragma pop_macro("LEDGER_ENTRY")
390 });
391
392 auto const& p = params[jss::type];
393 if (!p.isString())
394 {
395 result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type', not string."};
396 XRPL_ASSERT(
397 result.first.type() == rpc::Status::Type::ErrorCodeI,
398 "xrpl::rpc::chooseLedgerEntryType : first valid result type");
399 return result;
400 }
401
402 // Use the passed in parameter to find a ledger type based on matching
403 // against the canonical name (case-insensitive) or the RPC name
404 // (case-sensitive).
405 auto const filter = p.asString();
406 auto const iter = std::ranges::find_if(kTypes, [&filter](decltype(kTypes.front())& t) {
407 return boost::iequals(std::get<0>(t), filter) || std::get<1>(t) == filter;
408 });
409 if (iter == kTypes.end())
410 {
411 result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type'."};
412 XRPL_ASSERT(
413 result.first.type() == rpc::Status::Type::ErrorCodeI,
414 "xrpl::rpc::chooseLedgerEntryType : second valid result "
415 "type");
416 return result;
417 }
418 result.second = std::get<2>(*iter);
419 }
420 return result;
421}
422
423bool
425{
426 switch (type)
427 {
428 case LedgerEntryType::ltAMENDMENTS:
429 case LedgerEntryType::ltDIR_NODE:
430 case LedgerEntryType::ltFEE_SETTINGS:
431 case LedgerEntryType::ltLEDGER_HASHES:
432 case LedgerEntryType::ltNEGATIVE_UNL:
433 return false;
434 default:
435 return true;
436 }
437}
438
441 Asset& asset,
442 json::Value const& params,
443 json::StaticString const& name,
445{
446 auto const& jv = params[name];
447 auto const [issuerError, assetError] = [&]() {
448 if (name == jss::taker_pays)
451 }();
452
453 if (jv.isMember(jss::mpt_issuance_id) &&
454 (jv.isMember(jss::currency) || jv.isMember(jss::issuer)))
455 {
456 JLOG(j.info()) << std::format("Bad {} currency or MPT.", name.cStr());
457 return RpcInvalidParams;
458 }
459
460 if (jv.isMember(jss::currency))
461 {
462 Issue issue = xrpIssue();
463 // Parse mandatory currency.
464 if (!jv.isMember(jss::currency) ||
465 !toCurrency(issue.currency, jv[jss::currency].asString()))
466 {
467 JLOG(j.info()) << std::format("Bad {} currency.", name.cStr());
468 return assetError;
469 }
470
471 // Parse optional issuer.
472 if (((jv.isMember(jss::issuer)) &&
473 (!jv[jss::issuer].isString() || !toIssuer(issue.account, jv[jss::issuer].asString())))
474 // Don't allow illegal issuers.
475 || (!issue.currency != !issue.account) || noAccount() == issue.account)
476 {
477 JLOG(j.info()) << std::format("Bad {} issuer.", name.cStr());
478 return issuerError;
479 }
480 asset = issue;
481 }
482 else if (jv.isMember(jss::mpt_issuance_id))
483 {
484 MPTID mptid;
485 if (!mptid.parseHex(jv[jss::mpt_issuance_id].asString()))
486 return assetError;
487 asset = mptid;
488 }
489 else
490 {
491 JLOG(j.info()) << std::format("Neither {} currency or MPT is present.", name.cStr());
492 return assetError;
493 }
494
495 return RpcSuccess;
496}
497
498} // namespace xrpl::rpc
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream info() const
Definition Journal.h:350
Lightweight wrapper to tag static string.
Definition json_value.h:48
constexpr char const * cStr() const
Definition json_value.h:61
Represents a JSON value.
Definition json_value.h:117
bool isNull() const
isNull() tests to see if this field is null.
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.
pointer data()
Definition base_uint.h:117
static constexpr std::size_t size()
Definition base_uint.h:548
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:525
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
AccountID account
Definition Issue.h:21
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
Identifies fields.
Definition SField.h:132
std::shared_ptr< STLedgerEntry const > const & ConstRef
Seeds are used to generate deterministic secret keys.
Definition Seed.h:19
An immutable linear range of bytes.
Definition Slice.h:28
T find_if(T... args)
T format(T... args)
T insert(T... args)
T make_pair(T... args)
T max(T... args)
T min(T... args)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet page(UInt256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
Definition Indexes.cpp:409
API version numbers used in later API versions.
Definition ApiVersion.h:36
json::Value expectedFieldError(std::string const &name, std::string const &type)
Definition ErrorCodes.h:309
bool containsError(json::Value const &json)
Returns true if the json contains an rpc error specification.
std::pair< rpc::Status, LedgerEntryType > chooseLedgerEntryType(json::Value const &params)
Chooses the ledger entry type based on RPC parameters.
bool isOwnerDirNodeField(SField const &field)
Checks whether an SField is a UINT64 sf*Node owner-directory page-hint field probed by isRelatedToAcc...
std::uint64_t getStartHint(SLE::ConstRef sle, AccountID const &accountID)
Gets the start hint for traversing account objects.
std::string invalidFieldMessage(std::string const &name)
Definition ErrorCodes.h:273
json::Value makeParamError(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:231
json::Value invalidFieldError(std::string const &name)
Definition ErrorCodes.h:285
HashSet< MPTID > parseMPTIssuanceIDs(json::Value const &jvArray)
Parses an array of MPT issuance IDs from a JSON value.
ErrorCodeI parseSubUnsubJson(Asset &asset, json::Value const &params, json::StaticString const &name, beast::Journal j)
Parse subscribe/unsubscribe parameters.
std::optional< json::Value > readLimitField(unsigned int &limit, tuning::LimitRange const &range, JsonContext const &context)
Retrieves the limit value from a JsonContext or sets a default.
HashSet< AccountID > parseAccountIds(json::Value const &jvArray)
Parses an array of account IDs from a JSON value.
json::Value missingFieldError(std::string const &name)
Definition ErrorCodes.h:243
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
bool isRelatedToAccount(ReadView const &ledger, SLE::ConstRef sle, AccountID const &accountID)
Tests if a ledger entry (SLE) is owned by the specified account.
std::optional< Seed > getSeedFromRPC(json::Value const &params, json::Value &error)
Extracts a Seed from RPC parameters.
std::optional< Seed > parseXrplLibSeed(json::Value const &value)
Parses a XrplLib seed from RPC parameters.
std::optional< std::pair< PublicKey, SecretKey > > keypairForSignature(json::Value const &params, json::Value &error, unsigned int apiVersion)
Generates a keypair for signature from RPC parameters.
bool isAccountObjectsValidType(LedgerEntryType const &type)
Checks if the type is a valid filtering type for the account_objects method.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
ErrorCodeI
Definition ErrorCodes.h:23
@ RpcDstAmtMalformed
Definition ErrorCodes.h:89
@ RpcBadKeyType
Definition ErrorCodes.h:116
@ RpcSuccess
Definition ErrorCodes.h:27
@ RpcSrcCurMalformed
Definition ErrorCodes.h:107
@ RpcInvalidParams
Definition ErrorCodes.h:67
@ RpcBadSeed
Definition ErrorCodes.h:82
@ RpcSrcIsrMalformed
Definition ErrorCodes.h:108
@ RpcDstIsrMalformed
Definition ErrorCodes.h:91
std::optional< KeyType > keyTypeFromString(std::string const &s)
Definition KeyType.h:14
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
ClosedInterval< T > range(T low, T high)
Create a closed range interval.
Definition RangeSet.h:37
BaseUInt< 128 > UInt128
Definition base_uint.h:578
bool toCurrency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
void logicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
std::unordered_set< Value, Hash, Pred, Allocator > HashSet
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
AccountID const & noAccount()
A placeholder for empty accounts.
LedgerEntryType
Identifiers for on-ledger objects.
@ ltANY
A special type, matching any ledger entry type.
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:115
std::string decodeBase58Token(std::string const &s, TokenType type)
Definition tokens.cpp:191
bool toIssuer(AccountID &, std::string const &)
Convert hex or base58 string to AccountID.
std::optional< Seed > parseGenericSeed(std::string const &str, bool rfc1751=true)
Attempt to parse a string as a seed.
Definition Seed.cpp:79
json::Value params
Definition Context.h:51
Status represents the results of an operation that might fail.
Definition Status.h:27
static constexpr Code kOK
Definition Status.h:33
Represents RPC limit parameter values that have a min, default and max.
T value_or(T... args)