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
135{
136 hash_set<AccountID> result;
137 for (auto const& jv : jvArray)
138 {
139 if (!jv.isString())
140 return hash_set<AccountID>();
141 auto const id = parseBase58<AccountID>(jv.asString());
142 if (!id)
143 return hash_set<AccountID>();
144 result.insert(*id);
145 }
146 return result;
147}
148
149void
150injectSLE(Json::Value& jv, SLE const& sle)
151{
152 jv = sle.getJson(JsonOptions::none);
153 if (sle.getType() == ltACCOUNT_ROOT)
154 {
155 if (sle.isFieldPresent(sfEmailHash))
156 {
157 auto const& hash = sle.getFieldH128(sfEmailHash);
158 Blob const b(hash.begin(), hash.end());
159 std::string md5 = strHex(makeSlice(b));
160 boost::to_lower(md5);
161 // VFALCO TODO Give a name and move this constant
162 // to a more visible location. Also
163 // shouldn't this be https?
164 jv[jss::urlgravatar] =
165 str(boost::format("http://www.gravatar.com/avatar/%s") % md5);
166 }
167 }
168 else
169 {
170 jv[jss::Invalid] = true;
171 }
172}
173
176 unsigned int& limit,
178 JsonContext const& context)
179{
180 limit = range.rdefault;
181 if (!context.params.isMember(jss::limit) ||
182 context.params[jss::limit].isNull())
183 return std::nullopt;
184
185 auto const& jvLimit = context.params[jss::limit];
186 if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0)))
187 return RPC::expected_field_error(jss::limit, "unsigned integer");
188
189 limit = jvLimit.asUInt();
190 if (limit == 0)
191 return RPC::invalid_field_error(jss::limit);
192
193 if (!isUnlimited(context.role))
194 limit = std::max(range.rmin, std::min(range.rmax, limit));
195
196 return std::nullopt;
197}
198
201{
202 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
203 // non-standard way. While rippled never encode seeds that way, we
204 // try to detect such keys to avoid user confusion.
205 if (!value.isString())
206 return std::nullopt;
207
208 auto const result = decodeBase58Token(value.asString(), TokenType::None);
209
210 if (result.size() == 18 &&
211 static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
212 static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
213 return Seed(makeSlice(result.substr(2)));
214
215 return std::nullopt;
216}
217
220{
221 using string_to_seed_t =
223 using seed_match_t = std::pair<char const*, string_to_seed_t>;
224
225 static seed_match_t const seedTypes[]{
226 {jss::passphrase.c_str(),
227 [](std::string const& s) { return parseGenericSeed(s); }},
228 {jss::seed.c_str(),
229 [](std::string const& s) { return parseBase58<Seed>(s); }},
230 {jss::seed_hex.c_str(), [](std::string const& s) {
231 uint128 i;
232 if (i.parseHex(s))
233 return std::optional<Seed>(Slice(i.data(), i.size()));
234 return std::optional<Seed>{};
235 }}};
236
237 // Identify which seed type is in use.
238 seed_match_t const* seedType = nullptr;
239 int count = 0;
240 for (auto const& t : seedTypes)
241 {
242 if (params.isMember(t.first))
243 {
244 ++count;
245 seedType = &t;
246 }
247 }
248
249 if (count != 1)
250 {
251 error = RPC::make_param_error(
252 "Exactly one of the following must be specified: " +
253 std::string(jss::passphrase) + ", " + std::string(jss::seed) +
254 " or " + std::string(jss::seed_hex));
255 return std::nullopt;
256 }
257
258 // Make sure a string is present
259 auto const& param = params[seedType->first];
260 if (!param.isString())
261 {
262 error = RPC::expected_field_error(seedType->first, "string");
263 return std::nullopt;
264 }
265
266 auto const fieldContents = param.asString();
267
268 // Convert string to seed.
269 std::optional<Seed> seed = seedType->second(fieldContents);
270
271 if (!seed)
272 error = rpcError(rpcBAD_SEED);
273
274 return seed;
275}
276
279 Json::Value const& params,
280 Json::Value& error,
281 unsigned int apiVersion)
282{
283 bool const has_key_type = params.isMember(jss::key_type);
284
285 // All of the secret types we allow, but only one at a time.
286 static char const* const secretTypes[]{
287 jss::passphrase.c_str(),
288 jss::secret.c_str(),
289 jss::seed.c_str(),
290 jss::seed_hex.c_str()};
291
292 // Identify which secret type is in use.
293 char const* secretType = nullptr;
294 int count = 0;
295 for (auto t : secretTypes)
296 {
297 if (params.isMember(t))
298 {
299 ++count;
300 secretType = t;
301 }
302 }
303
304 if (count == 0 || secretType == nullptr)
305 {
306 error = RPC::missing_field_error(jss::secret);
307 return {};
308 }
309
310 if (count > 1)
311 {
312 error = RPC::make_param_error(
313 "Exactly one of the following must be specified: " +
314 std::string(jss::passphrase) + ", " + std::string(jss::secret) +
315 ", " + std::string(jss::seed) + " or " +
316 std::string(jss::seed_hex));
317 return {};
318 }
319
322
323 if (has_key_type)
324 {
325 if (!params[jss::key_type].isString())
326 {
327 error = RPC::expected_field_error(jss::key_type, "string");
328 return {};
329 }
330
331 keyType = keyTypeFromString(params[jss::key_type].asString());
332
333 if (!keyType)
334 {
335 if (apiVersion > 1u)
337 else
338 error = RPC::invalid_field_error(jss::key_type);
339 return {};
340 }
341
342 // using strcmp as pointers may not match (see
343 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
344 if (strcmp(secretType, jss::secret.c_str()) == 0)
345 {
346 error = RPC::make_param_error(
347 "The secret field is not allowed if " +
348 std::string(jss::key_type) + " is used.");
349 return {};
350 }
351 }
352
353 // ripple-lib encodes seed used to generate an Ed25519 wallet in a
354 // non-standard way. While we never encode seeds that way, we try
355 // to detect such keys to avoid user confusion.
356 // using strcmp as pointers may not match (see
357 // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem)
358 if (strcmp(secretType, jss::seed_hex.c_str()) != 0)
359 {
360 seed = RPC::parseRippleLibSeed(params[secretType]);
361
362 if (seed)
363 {
364 // If the user passed in an Ed25519 seed but *explicitly*
365 // requested another key type, return an error.
367 {
368 error = RPC::make_error(
369 rpcBAD_SEED, "Specified seed is for an Ed25519 wallet.");
370 return {};
371 }
372
373 keyType = KeyType::ed25519;
374 }
375 }
376
377 if (!keyType)
378 keyType = KeyType::secp256k1;
379
380 if (!seed)
381 {
382 if (has_key_type)
383 seed = getSeedFromRPC(params, error);
384 else
385 {
386 if (!params[jss::secret].isString())
387 {
388 error = RPC::expected_field_error(jss::secret, "string");
389 return {};
390 }
391
392 seed = parseGenericSeed(params[jss::secret].asString());
393 }
394 }
395
396 if (!seed)
397 {
398 if (!contains_error(error))
399 {
400 error = RPC::make_error(
402 }
403
404 return {};
405 }
406
407 if (keyType != KeyType::secp256k1 && keyType != KeyType::ed25519)
408 LogicError("keypairForSignature: invalid key type");
409
410 return generateKeyPair(*keyType, *seed);
411}
412
415{
417 if (params.isMember(jss::type))
418 {
419 static constexpr auto types = std::to_array<
421#pragma push_macro("LEDGER_ENTRY")
422#undef LEDGER_ENTRY
423
424#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \
425 {jss::name, jss::rpcName, tag},
426
427#include <xrpl/protocol/detail/ledger_entries.macro>
428
429#undef LEDGER_ENTRY
430#pragma pop_macro("LEDGER_ENTRY")
431 });
432
433 auto const& p = params[jss::type];
434 if (!p.isString())
435 {
436 result.first = RPC::Status{
437 rpcINVALID_PARAMS, "Invalid field 'type', not string."};
438 XRPL_ASSERT(
439 result.first.type() == RPC::Status::Type::error_code_i,
440 "ripple::RPC::chooseLedgerEntryType : first valid result type");
441 return result;
442 }
443
444 // Use the passed in parameter to find a ledger type based on matching
445 // against the canonical name (case-insensitive) or the RPC name
446 // (case-sensitive).
447 auto const filter = p.asString();
448 auto const iter =
449 std::ranges::find_if(types, [&filter](decltype(types.front())& t) {
450 return boost::iequals(std::get<0>(t), filter) ||
451 std::get<1>(t) == filter;
452 });
453 if (iter == types.end())
454 {
455 result.first =
456 RPC::Status{rpcINVALID_PARAMS, "Invalid field 'type'."};
457 XRPL_ASSERT(
458 result.first.type() == RPC::Status::Type::error_code_i,
459 "ripple::RPC::chooseLedgerEntryType : second valid result "
460 "type");
461 return result;
462 }
463 result.second = std::get<2>(*iter);
464 }
465 return result;
466}
467
468bool
470{
471 switch (type)
472 {
473 case LedgerEntryType::ltAMENDMENTS:
474 case LedgerEntryType::ltDIR_NODE:
475 case LedgerEntryType::ltFEE_SETTINGS:
476 case LedgerEntryType::ltLEDGER_HASHES:
477 case LedgerEntryType::ltNEGATIVE_UNL:
478 return false;
479 default:
480 return true;
481 }
482}
483
484} // namespace RPC
485} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
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.
A view into a ledger.
Definition ReadView.h:32
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
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
T find_if(T... args)
T insert(T... args)
T is_same_v
T max(T... args)
T min(T... args)
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
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.
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
std::pair< RPC::Status, LedgerEntryType > chooseLedgerEntryType(Json::Value const &params)
std::string invalid_field_message(std::string const &name)
Definition ErrorCodes.h:294
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
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)
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:311
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
@ rpcACT_MALFORMED
Definition ErrorCodes.h:71
@ rpcBAD_KEY_TYPE
Definition ErrorCodes.h:114
@ rpcSUCCESS
Definition ErrorCodes.h:25
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcBAD_SEED
Definition ErrorCodes.h:80
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
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
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.
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
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)