xrpld
Loading...
Searching...
No Matches
AccountTx.cpp
1#include <xrpld/app/ledger/LedgerMaster.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/DeliverMax.h>
4#include <xrpld/app/misc/Transaction.h>
5#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
6#include <xrpld/rpc/Context.h>
7#include <xrpld/rpc/DeliveredAmount.h>
8#include <xrpld/rpc/MPTokenIssuanceID.h>
9#include <xrpld/rpc/Role.h>
10#include <xrpld/rpc/Status.h>
11#include <xrpld/rpc/detail/RPCHelpers.h>
12#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
13#include <xrpld/rpc/detail/Tuning.h>
14
15#include <xrpl/basics/Log.h>
16#include <xrpl/basics/base_uint.h>
17#include <xrpl/basics/chrono.h>
18#include <xrpl/basics/strHex.h>
19#include <xrpl/beast/utility/instrumentation.h>
20#include <xrpl/json/json_value.h>
21#include <xrpl/ledger/ReadView.h>
22#include <xrpl/protocol/AccountID.h>
23#include <xrpl/protocol/ErrorCodes.h>
24#include <xrpl/protocol/LedgerShortcut.h>
25#include <xrpl/protocol/NFTSyntheticSerializer.h>
26#include <xrpl/protocol/RPCErr.h>
27#include <xrpl/protocol/RippleLedgerHash.h>
28#include <xrpl/protocol/jss.h>
29#include <xrpl/rdb/RelationalDatabase.h>
30#include <xrpl/resource/Fees.h>
31
32#include <cstdint>
33#include <expected>
34#include <memory>
35#include <optional>
36#include <type_traits>
37#include <utility>
38#include <variant>
39
40namespace xrpl {
41
42static std::expected<DelegateFilter, json::Value>
43parseDelegateFilter(json::Value const& delegateNode)
44{
45 if (!delegateNode.isObject())
46 return std::unexpected(rpc::invalidFieldError(jss::delegate));
47
48 if (!delegateNode.isMember(jss::delegate_filter) ||
49 !delegateNode[jss::delegate_filter].isString())
50 return std::unexpected(rpc::invalidFieldError(jss::delegate_filter));
51
52 auto const& delegateFilterStr = delegateNode[jss::delegate_filter].asString();
53
54 auto typeResult = [&] -> std::expected<DelegateType, json::Value> {
55 if (delegateFilterStr == "actor")
57
58 if (delegateFilterStr == "authorizer")
60
61 return std::unexpected(rpc::invalidFieldError(jss::delegate_filter));
62 }();
63
64 if (!typeResult)
65 return std::unexpected(typeResult.error());
66
67 DelegateType const type = *typeResult;
68
69 std::optional<AccountID> counterparty;
70 if (delegateNode.isMember(jss::counter_party))
71 {
72 if (!delegateNode[jss::counter_party].isString())
73 return std::unexpected(rpc::invalidFieldError(jss::counter_party));
74
75 counterparty = parseBase58<AccountID>(delegateNode[jss::counter_party].asString());
76
77 if (!counterparty)
79 }
80
81 return DelegateFilter{.type = type, .counterparty = counterparty};
82}
83
90
91// parses args into a ledger specifier, or returns a Json object on error
94{
95 json::Value response;
96 // if ledger_index_min or max is specified, then ledger_hash or ledger_index
97 // should not be specified. Error out if it is
98 if (context.apiVersion > 1u)
99 {
100 if ((params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max)) &&
101 (params.isMember(jss::ledger_hash) || params.isMember(jss::ledger_index)))
102 {
103 rpc::Status const status{RpcInvalidParams, "invalidParams"};
104 status.inject(response);
105 return response;
106 }
107 }
108 if (params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max))
109 {
110 uint32_t const min =
111 params.isMember(jss::ledger_index_min) && params[jss::ledger_index_min].asInt() >= 0
112 ? params[jss::ledger_index_min].asUInt()
113 : 0;
114 uint32_t const max =
115 params.isMember(jss::ledger_index_max) && params[jss::ledger_index_max].asInt() >= 0
116 ? params[jss::ledger_index_max].asUInt()
117 : UINT32_MAX;
118
119 return LedgerRange{.min = min, .max = max};
120 }
121 if (params.isMember(jss::ledger_hash))
122 {
123 auto& hashValue = params[jss::ledger_hash];
124 if (!hashValue.isString())
125 {
126 rpc::Status const status{RpcInvalidParams, "ledgerHashNotString"};
127 status.inject(response);
128 return response;
129 }
130
131 LedgerHash hash;
132 if (!hash.parseHex(hashValue.asString()))
133 {
134 rpc::Status const status{RpcInvalidParams, "ledgerHashMalformed"};
135 status.inject(response);
136 return response;
137 }
138 return hash;
139 }
140 if (params.isMember(jss::ledger_index))
141 {
142 LedgerSpecifier ledger;
143 if (params[jss::ledger_index].isNumeric())
144 {
145 ledger = params[jss::ledger_index].asUInt();
146 }
147 else
148 {
149 std::string const ledgerStr = params[jss::ledger_index].asString();
150
151 if (ledgerStr == "current" || ledgerStr.empty())
152 {
154 }
155 else if (ledgerStr == "closed")
156 {
157 ledger = LedgerShortcut::Closed;
158 }
159 else if (ledgerStr == "validated")
160 {
162 }
163 else
164 {
165 rpc::Status const status{RpcInvalidParams, "ledger_index string malformed"};
166 status.inject(response);
167 return response;
168 }
169 }
170 return ledger;
171 }
173}
174
177{
178 std::uint32_t uValidatedMin = 0;
179 std::uint32_t uValidatedMax = 0;
180 bool const bValidated = context.ledgerMaster.getValidatedRange(uValidatedMin, uValidatedMax);
181
182 if (!bValidated)
183 {
184 // Don't have a validated ledger range.
185 if (context.apiVersion == 1)
186 return RpcLgrIdxsInvalid;
187 return RpcNotSynced;
188 }
189
190 std::uint32_t uLedgerMin = uValidatedMin;
191 std::uint32_t uLedgerMax = uValidatedMax;
192 // Does request specify a ledger or ledger range?
193 if (ledgerSpecifier)
194 {
195 auto status = std::visit(
196 [&](auto const& ls) -> rpc::Status {
197 using T = std::decay_t<decltype(ls)>;
198 if constexpr (std::is_same_v<T, LedgerRange>)
199 {
200 // if ledger_index_min or ledger_index_max is out of
201 // valid ledger range, error out. exclude -1 as
202 // it is a valid input
203 if (context.apiVersion > 1u)
204 {
205 if ((ls.max > uValidatedMax && ls.max != -1) ||
206 (ls.min < uValidatedMin && ls.min != 0))
207 {
208 return RpcLgrIdxMalformed;
209 }
210 }
211 if (ls.min > uValidatedMin)
212 {
213 uLedgerMin = ls.min;
214 }
215 if (ls.max < uValidatedMax)
216 {
217 uLedgerMax = ls.max;
218 }
219 if (uLedgerMax < uLedgerMin)
220 {
221 if (context.apiVersion == 1)
222 return RpcLgrIdxsInvalid;
223 return RpcInvalidLgrRange;
224 }
225 }
226 else
227 {
229 auto status = getLedger(ledgerView, ls, context);
230 if (!ledgerView)
231 {
232 return status;
233 }
234
235 bool const validated = context.ledgerMaster.isValidated(*ledgerView);
236
237 if (!validated || ledgerView->header().seq > uValidatedMax ||
238 ledgerView->header().seq < uValidatedMin)
239 {
240 return RpcLgrNotValidated;
241 }
242 uLedgerMin = uLedgerMax = ledgerView->header().seq;
243 }
244 return rpc::Status::kOK;
245 },
246 *ledgerSpecifier);
247
248 if (status)
249 return status;
250 }
251 return LedgerRange{.min = uLedgerMin, .max = uLedgerMax};
252}
253
256{
258
259 AccountTxResult result;
260
261 auto lgrRange = getLedgerRange(context, args.ledger);
262 if (auto stat = std::get_if<rpc::Status>(&lgrRange))
263 {
264 // An error occurred getting the requested ledger range
265 return {result, *stat};
266 }
267
268 result.ledgerRange = std::get<LedgerRange>(lgrRange);
269
270 result.marker = args.marker;
271
273 .account = args.account,
274 .ledgerRange = result.ledgerRange,
275 .marker = result.marker,
276 .limit = args.limit,
277 .bAdmin = isUnlimited(context.role),
278 .delegate = args.delegate};
279
280 auto& db = context.app.getRelationalDatabase();
281
282 if (args.binary)
283 {
284 if (args.forward)
285 {
286 auto [tx, marker] = db.oldestAccountTxPageB(options);
287 result.transactions = tx;
288 result.marker = marker;
289 }
290 else
291 {
292 auto [tx, marker] = db.newestAccountTxPageB(options);
293 result.transactions = tx;
294 result.marker = marker;
295 }
296 }
297 else
298 {
299 if (args.forward)
300 {
301 auto [tx, marker] = db.oldestAccountTxPage(options);
302 result.transactions = tx;
303 result.marker = marker;
304 }
305 else
306 {
307 auto [tx, marker] = db.newestAccountTxPage(options);
308 result.transactions = tx;
309 result.marker = marker;
310 }
311 }
312
313 result.limit = args.limit;
314 JLOG(context.j.debug()) << __func__ << " : finished";
315
316 return {result, RpcSuccess};
317}
318
322 AccountTxArgs const& args,
323 rpc::JsonContext const& context)
324{
325 json::Value response;
326 rpc::Status const& error = res.second;
327 if (error.toErrorCode() != RpcSuccess)
328 {
329 error.inject(response);
330 }
331 else
332 {
333 AccountTxResult const& result = res.first;
334 response[jss::validated] = true;
335 response[jss::limit] = result.limit;
336 response[jss::account] = context.params[jss::account].asString();
337 response[jss::ledger_index_min] = result.ledgerRange.min;
338 response[jss::ledger_index_max] = result.ledgerRange.max;
339
340 json::Value& jvTxns = (response[jss::transactions] = json::ValueType::Array);
341
342 if (auto txnsData = std::get_if<TxnsData>(&result.transactions))
343 {
344 XRPL_ASSERT(!args.binary, "xrpl::populateJsonResponse : binary is not set");
345
346 for (auto const& [txn, txnMeta] : *txnsData)
347 {
348 if (txn)
349 {
351 jvObj[jss::validated] = true;
352
353 auto const jsonTx = (context.apiVersion > 1 ? jss::tx_json : jss::tx);
354 if (context.apiVersion > 1)
355 {
356 jvObj[jsonTx] = txn->getJson(
357 static_cast<JsonOptions::underlying_t>(
359 static_cast<JsonOptions::underlying_t>(
361 false);
362 jvObj[jss::hash] = to_string(txn->getID());
363 jvObj[jss::ledger_index] = txn->getLedger();
364 jvObj[jss::ledger_hash] =
365 to_string(context.ledgerMaster.getHashBySeq(txn->getLedger()));
366
367 if (auto closeTime =
368 context.ledgerMaster.getCloseTimeBySeq(txn->getLedger()))
369 jvObj[jss::close_time_iso] = toStringIso(*closeTime);
370 }
371 else
372 {
373 jvObj[jsonTx] = txn->getJson(JsonOptions::Values::IncludeDate);
374 }
375
376 auto const& sttx = txn->getSTransaction();
377 rpc::insertDeliverMax(jvObj[jsonTx], sttx->getTxnType(), context.apiVersion);
378 if (txnMeta)
379 {
380 jvObj[jss::meta] = txnMeta->getJson(JsonOptions::Values::IncludeDate);
381 insertDeliveredAmount(jvObj[jss::meta], context, txn, *txnMeta);
382 rpc::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
383 rpc::insertMPTokenIssuanceID(jvObj[jss::meta], sttx, *txnMeta);
384 }
385 else
386 {
387 // LCOV_EXCL_START
388 UNREACHABLE(
389 "xrpl::populateJsonResponse : missing "
390 "transaction metadata");
391 // LCOV_EXCL_STOP
392 }
393 }
394 }
395 }
396 else
397 {
398 XRPL_ASSERT(args.binary, "xrpl::populateJsonResponse : binary is set");
399
400 for (auto const& binaryData : std::get<TxnsDataBinary>(result.transactions))
401 {
403
404 jvObj[jss::tx_blob] = strHex(std::get<0>(binaryData));
405 auto const jsonMeta = (context.apiVersion > 1 ? jss::meta_blob : jss::meta);
406 jvObj[jsonMeta] = strHex(std::get<1>(binaryData));
407 jvObj[jss::ledger_index] = std::get<2>(binaryData);
408 jvObj[jss::validated] = true;
409 }
410 }
411
412 if (result.marker)
413 {
414 response[jss::marker] = json::ValueType::Object;
415 response[jss::marker][jss::ledger] = result.marker->ledgerSeq;
416 response[jss::marker][jss::seq] = result.marker->txnSeq;
417
418 if (args.delegate)
419 response[jss::marker][jss::delegate] = true;
420 }
421 }
422
423 JLOG(context.j.debug()) << __func__ << " : finished";
424 return response;
425}
426
427// {
428// account: account,
429// ledger_index_min: ledger_index // optional, defaults to earliest
430// ledger_index_max: ledger_index, // optional, defaults to latest
431// binary: boolean, // optional, defaults to false
432// forward: boolean, // optional, defaults to false
433// limit: integer, // optional
434// marker: object {ledger: ledger_index, seq: txn_sequence} // optional,
435// resume previous query
436// delegate: object { // optional
437// delegate_filter: string, // required; "actor" or "authorizer"
438// counter_party: account // optional
439// }
440// }
441//
442// Pagination note for delegate-filtered queries: the `delegate` object (both
443// `delegate_filter` and `counter_party`) must be supplied unchanged on every
444// paginated request until the query completes. A marker returned by a
445// delegate-filtered query is only valid for a follow-up request that repeats
446// the same `delegate` object
449{
450 if (!context.app.config().useTxTables())
451 return rpcError(RpcNotEnabled);
452
453 auto& params = context.params;
454 AccountTxArgs args;
455 json::Value response;
456
457 // The document[https://xrpl.org/account_tx.html#account_tx] states that
458 // binary and forward params are both boolean values, however, assigning any
459 // string value works. Do not allow this. This check is for api Version 2
460 // onwards only
461 if (context.apiVersion > 1u && params.isMember(jss::binary) && !params[jss::binary].isBool())
462 {
463 return rpc::invalidFieldError(jss::binary);
464 }
465 if (context.apiVersion > 1u && params.isMember(jss::forward) && !params[jss::forward].isBool())
466 {
467 return rpc::invalidFieldError(jss::forward);
468 }
469
470 if (auto const err = rpc::readLimitField(args.limit, rpc::tuning::kAccountTx, context))
471 return *err;
472
473 args.binary = params.isMember(jss::binary) && params[jss::binary].asBool();
474 args.forward = params.isMember(jss::forward) && params[jss::forward].asBool();
475
476 if (!params.isMember(jss::account))
477 return rpc::missingFieldError(jss::account);
478
479 if (!params[jss::account].isString())
480 return rpc::invalidFieldError(jss::account);
481
482 auto const account = parseBase58<AccountID>(params[jss::account].asString());
483 if (!account)
485
486 args.account = *account;
487
488 auto parseRes = parseLedgerArgs(context, params);
489 if (auto jv = std::get_if<json::Value>(&parseRes))
490 {
491 return *jv;
492 }
493
494 args.ledger = std::get<std::optional<LedgerSpecifier>>(parseRes);
495
496 if (params.isMember(jss::marker))
497 {
498 auto& token = params[jss::marker];
499 if (!token.isMember(jss::ledger) || !token.isMember(jss::seq) ||
500 !token[jss::ledger].isConvertibleTo(json::ValueType::UInt) ||
501 !token[jss::seq].isConvertibleTo(json::ValueType::UInt))
502 {
503 rpc::Status const status{
505 "invalid marker. Provide ledger index via ledger field, and "
506 "transaction sequence number via seq field"};
507 status.inject(response);
508 return response;
509 }
510 args.marker = {
511 .ledgerSeq = token[jss::ledger].asUInt(), .txnSeq = token[jss::seq].asUInt()};
512 }
513
514 if (params.isMember(jss::delegate))
515 {
516 if (auto const filter = parseDelegateFilter(params[jss::delegate]); filter.has_value())
517 {
518 args.delegate = *filter;
519 }
520 else
521 {
522 return filter.error();
523 }
524 }
525
526 // A marker produced by a delegate-filtered query uses a different
527 // pagination cursor than a normal query, so it is only valid when the same
528 // `delegate` object is supplied again. Reject any mismatch so pagination
529 // cannot silently skip or duplicate results.
530 if (args.marker)
531 {
532 bool const markerFromDelegate = params[jss::marker].isMember(jss::delegate) &&
533 params[jss::marker][jss::delegate].isBool() &&
534 params[jss::marker][jss::delegate].asBool();
535 if (markerFromDelegate != args.delegate.has_value())
536 {
537 rpc::Status const status{
539 "Do not mix delegate and non-delegate pagination markers in account_tx; "
540 "repeat the same `delegate` object when using a delegate marker."};
541 status.inject(response);
542 return response;
543 }
544 }
545
546 auto res = doAccountTxHelp(context, args);
547 JLOG(context.j.debug()) << __func__ << " populating response";
548 return populateJsonResponse(res, args, context);
549}
550
551} // namespace xrpl
Stream debug() const
Definition Journal.h:344
Represents a JSON value.
Definition json_value.h:117
bool isObject() const
bool isString() const
Value & append(Value const &value)
Append value to array at the end.
UInt asUInt() const
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.
Int asInt() const
virtual Config & config()=0
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:525
bool useTxTables() const
bool getValidatedRange(std::uint32_t &minVal, std::uint32_t &maxVal)
uint256 getHashBySeq(std::uint32_t index)
Get a ledger's hash by sequence number using the cache.
std::optional< NetClock::time_point > getCloseTimeBySeq(LedgerIndex ledgerIndex)
bool isValidated(ReadView const &ledger)
std::vector< AccountTx > AccountTxs
std::vector< txnMetaLedgerType > MetaTxsList
std::variant< LedgerRange, LedgerShortcut, LedgerSequence, LedgerHash > LedgerSpecifier
std::tuple< Blob, Blob, std::uint32_t > txnMetaLedgerType
virtual RelationalDatabase & getRelationalDatabase()=0
T empty(T... args)
T get_if(T... args)
T is_same_v
@ UInt
unsigned integer value
Definition json_value.h:24
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
Charge const kFeeMediumBurdenRpc
static constexpr LimitRange kAccountTx
Limits for the account_tx command.
void insertMPTokenIssuanceID(json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
void insertDeliverMax(json::Value &txJson, TxType txnType, unsigned int apiVersion)
Copy Amount field to DeliverMax field in transaction output JSON.
Definition DeliverMax.cpp:9
void insertNFTSyntheticInJson(json::Value &, std::shared_ptr< STTx const > const &, TxMeta const &)
Adds common synthetic fields to transaction-related JSON responses.
json::Value invalidFieldError(std::string const &name)
Definition ErrorCodes.h:285
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:243
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
RelationalDatabase::AccountTxs TxnsData
Definition AccountTx.cpp:84
@ RpcSuccess
Definition ErrorCodes.h:27
@ RpcActMalformed
Definition ErrorCodes.h:73
@ RpcNotSynced
Definition ErrorCodes.h:50
@ RpcLgrIdxsInvalid
Definition ErrorCodes.h:95
@ RpcNotEnabled
Definition ErrorCodes.h:42
@ RpcInvalidParams
Definition ErrorCodes.h:67
@ RpcLgrNotValidated
Definition ErrorCodes.h:56
@ RpcInvalidLgrRange
Definition ErrorCodes.h:119
@ RpcLgrIdxMalformed
Definition ErrorCodes.h:96
json::Value doAccountTx(rpc::JsonContext &context)
RelationalDatabase::AccountTxResult AccountTxResult
Definition AccountTx.cpp:88
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
std::pair< AccountTxResult, rpc::Status > doAccountTxHelp(rpc::Context &context, AccountTxArgs const &args)
static std::expected< DelegateFilter, json::Value > parseDelegateFilter(json::Value const &delegateNode)
Definition AccountTx.cpp:43
RelationalDatabase::MetaTxsList TxnsDataBinary
Definition AccountTx.cpp:85
RelationalDatabase::txnMetaLedgerType TxnDataBinary
Definition AccountTx.cpp:86
@ Closed
The most recently closed ledger (may not be validated).
@ Current
The current working ledger (open, not yet closed).
@ Validated
The most recently validated ledger.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::string toStringIso(date::sys_time< Duration > tp)
Definition chrono.h:70
DelegateType
Enumeration of possible delegate types that can occur during filtering in account_tx.
@ Authorizer
This account signed and submitted transactions on behalf of another account (this account is the sign...
@ Actor
Another account signed and submitted transactions on behalf of this account (this account is the owne...
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
uint256 LedgerHash
json::Value populateJsonResponse(std::pair< AccountTxResult, rpc::Status > const &res, AccountTxArgs const &args, rpc::JsonContext const &context)
RelationalDatabase::AccountTxArgs AccountTxArgs
Definition AccountTx.cpp:87
RelationalDatabase::LedgerSpecifier LedgerSpecifier
Definition AccountTx.cpp:89
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:115
std::variant< LedgerRange, rpc::Status > getLedgerRange(rpc::Context &context, std::optional< LedgerSpecifier > const &ledgerSpecifier)
std::variant< std::optional< LedgerSpecifier >, json::Value > parseLedgerArgs(rpc::Context &context, json::Value const &params)
Definition AccountTx.cpp:93
unsigned int underlying_t
Definition STBase.h:23
std::optional< AccountTxMarker > marker
std::optional< LedgerSpecifier > ledger
std::optional< DelegateFilter > delegate
std::variant< AccountTxs, MetaTxsList > transactions
std::optional< AccountTxMarker > marker
The context of information needed to call an RPC.
Definition Context.h:27
resource::Charge & loadType
Definition Context.h:30
beast::Journal const j
Definition Context.h:28
unsigned int apiVersion
Definition Context.h:37
Application & app
Definition Context.h:29
LedgerMaster & ledgerMaster
Definition Context.h:32
json::Value params
Definition Context.h:51
Status represents the results of an operation that might fail.
Definition Status.h:27
void inject(json::Value &object) const
Apply the Status to a JsonObject.
Definition Status.h:111
ErrorCodeI toErrorCode() const
Returns the Status as an error_code_i.
Definition Status.h:100
static constexpr Code kOK
Definition Status.h:33
T unexpected(T... args)
T visit(T... args)