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/protocol/AccountID.h>
18#include <xrpl/protocol/Asset.h>
19#include <xrpl/protocol/ErrorCodes.h>
20#include <xrpl/protocol/Indexes.h>
21#include <xrpl/protocol/Issue.h>
22#include <xrpl/protocol/KeyType.h>
23#include <xrpl/protocol/Keylet.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
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
69bool
70isRelatedToAccount(ReadView const& ledger, SLE::const_ref sle, AccountID const& accountID)
71{
72 if (sle->getType() == ltRIPPLE_STATE)
73 {
74 return (sle->getFieldAmount(sfLowLimit).getIssuer() == accountID) ||
75 (sle->getFieldAmount(sfHighLimit).getIssuer() == accountID);
76 }
77 if (sle->isFieldPresent(sfAccount))
78 {
79 // If there's an sfAccount present, also test the sfDestination, if
80 // present. This will match objects such as Escrows (ltESCROW), Payment
81 // Channels (ltPAYCHAN), and Checks (ltCHECK) because those are added to
82 // the Destination account's directory. It intentionally EXCLUDES
83 // NFToken Offers (ltNFTOKEN_OFFER). NFToken Offers are NOT added to the
84 // Destination account's directory.
85 return sle->getAccountID(sfAccount) == accountID ||
86 (sle->isFieldPresent(sfDestination) && sle->getAccountID(sfDestination) == accountID);
87 }
88 if (sle->getType() == ltSIGNER_LIST)
89 {
90 Keylet const accountSignerList = keylet::signerList(accountID);
91 return sle->key() == accountSignerList.key;
92 }
93 if (sle->getType() == ltNFTOKEN_OFFER)
94 {
95 // Do not check the sfDestination field. NFToken Offers are NOT added to
96 // the Destination account's directory.
97 return sle->getAccountID(sfOwner) == accountID;
98 }
99
100 return false;
101}
102
105{
106 hash_set<AccountID> result;
107 for (auto const& jv : jvArray)
108 {
109 if (!jv.isString())
110 return hash_set<AccountID>();
111 auto const id = parseBase58<AccountID>(jv.asString());
112 if (!id)
113 return hash_set<AccountID>();
114 result.insert(*id);
115 }
116 return result;
117}
118
120readLimitField(unsigned int& limit, tuning::LimitRange const& range, JsonContext const& context)
121{
122 limit = range.rDefault;
123 if (!context.params.isMember(jss::limit) || context.params[jss::limit].isNull())
124 return std::nullopt;
125
126 auto const& jvLimit = context.params[jss::limit];
127 if (!jvLimit.isUInt() && (!jvLimit.isInt() || jvLimit.asInt() < 0))
128 return rpc::expectedFieldError(jss::limit, "unsigned integer");
129
130 limit = jvLimit.asUInt();
131 if (limit == 0)
132 return rpc::invalidFieldError(jss::limit);
133
134 if (!isUnlimited(context.role))
135 limit = std::max(range.rmin, std::min(range.rmax, limit));
136
137 return std::nullopt;
138}
139
142{
143 // XrplLib encodes seed used to generate an Ed25519 wallet in a
144 // non-standard way. While xrpld never encode seeds that way, we
145 // try to detect such keys to avoid user confusion.
146 if (!value.isString())
147 return std::nullopt;
148
149 auto const result = decodeBase58Token(value.asString(), TokenType::None);
150
151 if (result.size() == 18 && static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
152 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
153 return Seed(makeSlice(result.substr(2)));
154
155 return std::nullopt;
156}
157
160{
161 using string_to_seed_t = std::function<std::optional<Seed>(std::string const&)>;
162 using seed_match_t = std::pair<char const*, string_to_seed_t>;
163
164 static seed_match_t const kSeedTypes[]{
165 {jss::passphrase.cStr(), [](std::string const& s) { return parseGenericSeed(s); }},
166 {jss::seed.cStr(), [](std::string const& s) { return parseBase58<Seed>(s); }},
167 {jss::seed_hex.cStr(), [](std::string const& s) {
168 uint128 i;
169 if (i.parseHex(s))
170 return std::optional<Seed>(Slice(i.data(), i.size()));
171 return std::optional<Seed>{};
172 }}};
173
174 // Identify which seed type is in use.
175 seed_match_t const* seedType = nullptr;
176 int count = 0;
177 for (auto const& t : kSeedTypes)
178 {
179 if (params.isMember(t.first))
180 {
181 ++count;
182 seedType = &t;
183 }
184 }
185
186 if (count != 1)
187 {
188 error = rpc::makeParamError(
189 "Exactly one of the following must be specified: " + std::string(jss::passphrase) +
190 ", " + std::string(jss::seed) + " or " + std::string(jss::seed_hex));
191 return std::nullopt;
192 }
193
194 // Make sure a string is present
195 auto const& param = params[seedType->first];
196 if (!param.isString())
197 {
198 error = rpc::expectedFieldError(seedType->first, "string");
199 return std::nullopt;
200 }
201
202 auto const fieldContents = param.asString();
203
204 // Convert string to seed.
205 std::optional<Seed> seed = seedType->second(fieldContents);
206
207 if (!seed)
208 error = rpcError(RpcBadSeed);
209
210 return seed;
211}
212
214keypairForSignature(json::Value const& params, json::Value& error, unsigned int apiVersion)
215{
216 bool const hasKeyType = params.isMember(jss::key_type);
217
218 // All of the secret types we allow, but only one at a time.
219 static char const* const kSecretTypes[]{
220 jss::passphrase.cStr(), jss::secret.cStr(), jss::seed.cStr(), jss::seed_hex.cStr()};
221
222 // Identify which secret type is in use.
223 char const* secretType = nullptr;
224 int count = 0;
225 for (auto t : kSecretTypes)
226 {
227 if (params.isMember(t))
228 {
229 ++count;
230 secretType = t;
231 }
232 }
233
234 if (count == 0 || secretType == nullptr)
235 {
236 error = rpc::missingFieldError(jss::secret);
237 return {};
238 }
239
240 if (count > 1)
241 {
242 error = rpc::makeParamError(
243 "Exactly one of the following must be specified: " + std::string(jss::passphrase) +
244 ", " + std::string(jss::secret) + ", " + std::string(jss::seed) + " or " +
245 std::string(jss::seed_hex));
246 return {};
247 }
248
251
252 if (hasKeyType)
253 {
254 if (!params[jss::key_type].isString())
255 {
256 error = rpc::expectedFieldError(jss::key_type, "string");
257 return {};
258 }
259
260 keyType = keyTypeFromString(params[jss::key_type].asString());
261
262 if (!keyType)
263 {
264 if (apiVersion > 1u)
265 {
267 }
268 else
269 {
270 error = rpc::invalidFieldError(jss::key_type);
271 }
272 return {};
273 }
274
275 // using strcmp as pointers may not match (see
276 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
277 if (strcmp(secretType, jss::secret.cStr()) == 0)
278 {
279 error = rpc::makeParamError(
280 "The secret field is not allowed if " + std::string(jss::key_type) + " is used.");
281 return {};
282 }
283 }
284
285 // XrplLib encodes seed used to generate an Ed25519 wallet in a
286 // non-standard way. While we never encode seeds that way, we try
287 // to detect such keys to avoid user confusion.
288 // using strcmp as pointers may not match (see
289 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
290 if (strcmp(secretType, jss::seed_hex.cStr()) != 0)
291 {
292 seed = rpc::parseXrplLibSeed(params[secretType]);
293
294 if (seed)
295 {
296 // If the user passed in an Ed25519 seed but *explicitly*
297 // requested another key type, return an error.
299 {
300 error = rpc::makeError(RpcBadSeed, "Specified seed is for an Ed25519 wallet.");
301 return {};
302 }
303
304 keyType = KeyType::Ed25519;
305 }
306 }
307
308 if (!keyType)
309 keyType = KeyType::Secp256k1;
310
311 if (!seed)
312 {
313 if (hasKeyType)
314 {
315 seed = getSeedFromRPC(params, error);
316 }
317 else
318 {
319 if (!params[jss::secret].isString())
320 {
321 error = rpc::expectedFieldError(jss::secret, "string");
322 return {};
323 }
324
325 seed = parseGenericSeed(params[jss::secret].asString());
326 }
327 }
328
329 if (!seed)
330 {
331 if (!containsError(error))
332 {
334 }
335
336 return {};
337 }
338
339 if (keyType != KeyType::Secp256k1 && keyType != KeyType::Ed25519)
340 logicError("keypairForSignature: invalid key type");
341
342 return generateKeyPair(*keyType, *seed);
343}
344
347{
349 if (params.isMember(jss::type))
350 {
351 static constexpr auto kTypes =
352 std::to_array<std::tuple<char const*, char const*, LedgerEntryType>>({
353#pragma push_macro("LEDGER_ENTRY")
354#undef LEDGER_ENTRY
355
356#define LEDGER_ENTRY(tag, value, name, rpcName, ...) {jss::name, jss::rpcName, tag},
357
358#include <xrpl/protocol/detail/ledger_entries.macro>
359
360#undef LEDGER_ENTRY
361#pragma pop_macro("LEDGER_ENTRY")
362 });
363
364 auto const& p = params[jss::type];
365 if (!p.isString())
366 {
367 result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type', not string."};
368 XRPL_ASSERT(
369 result.first.type() == rpc::Status::Type::ErrorCodeI,
370 "xrpl::rpc::chooseLedgerEntryType : first valid result type");
371 return result;
372 }
373
374 // Use the passed in parameter to find a ledger type based on matching
375 // against the canonical name (case-insensitive) or the RPC name
376 // (case-sensitive).
377 auto const filter = p.asString();
378 auto const iter = std::ranges::find_if(kTypes, [&filter](decltype(kTypes.front())& t) {
379 return boost::iequals(std::get<0>(t), filter) || std::get<1>(t) == filter;
380 });
381 if (iter == kTypes.end())
382 {
383 result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type'."};
384 XRPL_ASSERT(
385 result.first.type() == rpc::Status::Type::ErrorCodeI,
386 "xrpl::rpc::chooseLedgerEntryType : second valid result "
387 "type");
388 return result;
389 }
390 result.second = std::get<2>(*iter);
391 }
392 return result;
393}
394
395bool
397{
398 switch (type)
399 {
400 case LedgerEntryType::ltAMENDMENTS:
401 case LedgerEntryType::ltDIR_NODE:
402 case LedgerEntryType::ltFEE_SETTINGS:
403 case LedgerEntryType::ltLEDGER_HASHES:
404 case LedgerEntryType::ltNEGATIVE_UNL:
405 return false;
406 default:
407 return true;
408 }
409}
410
413 Asset& asset,
414 json::Value const& params,
415 json::StaticString const& name,
417{
418 auto const& jv = params[name];
419 auto const [issuerError, assetError] = [&]() {
420 if (name == jss::taker_pays)
423 }();
424
425 if (jv.isMember(jss::mpt_issuance_id) &&
426 (jv.isMember(jss::currency) || jv.isMember(jss::issuer)))
427 {
428 JLOG(j.info()) << std::format("Bad {} currency or MPT.", name.cStr());
429 return RpcInvalidParams;
430 }
431
432 if (jv.isMember(jss::currency))
433 {
434 Issue issue = xrpIssue();
435 // Parse mandatory currency.
436 if (!jv.isMember(jss::currency) ||
437 !toCurrency(issue.currency, jv[jss::currency].asString()))
438 {
439 JLOG(j.info()) << std::format("Bad {} currency.", name.cStr());
440 return assetError;
441 }
442
443 // Parse optional issuer.
444 if (((jv.isMember(jss::issuer)) &&
445 (!jv[jss::issuer].isString() || !toIssuer(issue.account, jv[jss::issuer].asString())))
446 // Don't allow illegal issuers.
447 || (!issue.currency != !issue.account) || noAccount() == issue.account)
448 {
449 JLOG(j.info()) << std::format("Bad {} issuer.", name.cStr());
450 return issuerError;
451 }
452 asset = issue;
453 }
454 else if (jv.isMember(jss::mpt_issuance_id))
455 {
456 MPTID mptid;
457 if (!mptid.parseHex(jv[jss::mpt_issuance_id].asString()))
458 return assetError;
459 asset = mptid;
460 }
461 else
462 {
463 JLOG(j.info()) << std::format("Neither {} currency or MPT is present.", name.cStr());
464 return assetError;
465 }
466
467 return RpcSuccess;
468}
469
470} // namespace xrpl::rpc
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
std::shared_ptr< STLedgerEntry const > const & const_ref
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 signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:326
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.
std::uint64_t getStartHint(SLE::const_ref sle, AccountID const &accountID)
Gets the start hint for traversing account objects.
bool isRelatedToAccount(ReadView const &ledger, SLE::const_ref sle, AccountID const &accountID)
Tests if a ledger entry (SLE) is owned by the specified account.
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
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.
hash_set< 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.
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
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
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
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
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
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)