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