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/Role.h>
8#include <xrpld/rpc/Status.h>
9#include <xrpld/rpc/detail/RPCHelpers.h>
10#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
11#include <xrpld/rpc/detail/SyntheticFields.h>
12#include <xrpld/rpc/detail/Tuning.h>
13
14#include <xrpl/basics/Log.h>
15#include <xrpl/basics/base_uint.h>
16#include <xrpl/basics/chrono.h>
17#include <xrpl/basics/strHex.h>
18#include <xrpl/beast/utility/instrumentation.h>
19#include <xrpl/json/json_value.h>
20#include <xrpl/ledger/ReadView.h>
21#include <xrpl/protocol/AccountID.h>
22#include <xrpl/protocol/ErrorCodes.h>
23#include <xrpl/protocol/RPCErr.h>
24#include <xrpl/protocol/RippleLedgerHash.h>
25#include <xrpl/protocol/jss.h>
26#include <xrpl/rdb/RelationalDatabase.h>
27#include <xrpl/resource/Fees.h>
28
29#include <rpcspec/Ledger.hpp>
30
31#include <cstdint>
32#include <expected>
33#include <memory>
34#include <optional>
35#include <type_traits>
36#include <utility>
37#include <variant>
38
39namespace xrpl {
40
41static std::expected<DelegateFilter, json::Value>
42parseDelegateFilter(json::Value const& delegateNode)
43{
44 if (!delegateNode.isObject())
45 return std::unexpected(rpc::invalidFieldError(jss::delegate));
46
47 if (!delegateNode.isMember(jss::delegate_filter) ||
48 !delegateNode[jss::delegate_filter].isString())
49 return std::unexpected(rpc::invalidFieldError(jss::delegate_filter));
50
51 auto const& delegateFilterStr = delegateNode[jss::delegate_filter].asString();
52
53 auto typeResult = [&] -> std::expected<DelegateType, json::Value> {
54 if (delegateFilterStr == "actor")
56
57 if (delegateFilterStr == "authorizer")
59
60 return std::unexpected(rpc::invalidFieldError(jss::delegate_filter));
61 }();
62
63 if (!typeResult)
64 return std::unexpected(typeResult.error());
65
66 DelegateType const type = *typeResult;
67
68 std::optional<AccountID> counterparty;
69 if (delegateNode.isMember(jss::counter_party))
70 {
71 if (!delegateNode[jss::counter_party].isString())
72 return std::unexpected(rpc::invalidFieldError(jss::counter_party));
73
74 counterparty = parseBase58<AccountID>(delegateNode[jss::counter_party].asString());
75
76 if (!counterparty)
78 }
79
80 return DelegateFilter{.type = type, .counterparty = counterparty};
81}
82
87
88// Moved here from RelationalDatabase, where nothing but this handler used them, so that
89// libxrpl does not depend on the spec for LedgerShortcut. Temporary: the spec's
90// account_tx Input replaces both once account_tx is migrated to the spec.
93
104
105// parses args into a ledger specifier, or returns a Json object on error
108{
109 json::Value response;
110 // if ledger_index_min or max is specified, then ledger_hash or ledger_index
111 // should not be specified. Error out if it is
112 if (context.apiVersion > 1u)
113 {
114 if ((params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max)) &&
115 (params.isMember(jss::ledger_hash) || params.isMember(jss::ledger_index)))
116 {
117 rpc::Status const status{RpcInvalidParams, "invalidParams"};
118 status.inject(response);
119 return response;
120 }
121 }
122 if (params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max))
123 {
124 uint32_t const min =
125 params.isMember(jss::ledger_index_min) && params[jss::ledger_index_min].asInt() >= 0
126 ? params[jss::ledger_index_min].asUInt()
127 : 0;
128 uint32_t const max =
129 params.isMember(jss::ledger_index_max) && params[jss::ledger_index_max].asInt() >= 0
130 ? params[jss::ledger_index_max].asUInt()
131 : UINT32_MAX;
132
133 return LedgerRange{.min = min, .max = max};
134 }
135 if (params.isMember(jss::ledger_hash))
136 {
137 auto& hashValue = params[jss::ledger_hash];
138 if (!hashValue.isString())
139 {
140 rpc::Status const status{RpcInvalidParams, "ledgerHashNotString"};
141 status.inject(response);
142 return response;
143 }
144
145 LedgerHash hash;
146 if (!hash.parseHex(hashValue.asString()))
147 {
148 rpc::Status const status{RpcInvalidParams, "ledgerHashMalformed"};
149 status.inject(response);
150 return response;
151 }
152 return hash;
153 }
154 if (params.isMember(jss::ledger_index))
155 {
156 LedgerSpecifier ledger;
157 if (params[jss::ledger_index].isNumeric())
158 {
159 ledger = params[jss::ledger_index].asUInt();
160 }
161 else
162 {
163 std::string const ledgerStr = params[jss::ledger_index].asString();
164
165 if (ledgerStr == "current" || ledgerStr.empty())
166 {
167 ledger = ::rpc::spec::LedgerShortcut::Current;
168 }
169 else if (ledgerStr == "closed")
170 {
171 ledger = ::rpc::spec::LedgerShortcut::Closed;
172 }
173 else if (ledgerStr == "validated")
174 {
175 ledger = ::rpc::spec::LedgerShortcut::Validated;
176 }
177 else
178 {
179 rpc::Status const status{RpcInvalidParams, "ledger_index string malformed"};
180 status.inject(response);
181 return response;
182 }
183 }
184 return ledger;
185 }
187}
188
191{
192 std::uint32_t uValidatedMin = 0;
193 std::uint32_t uValidatedMax = 0;
194 bool const bValidated = context.ledgerMaster.getValidatedRange(uValidatedMin, uValidatedMax);
195
196 if (!bValidated)
197 {
198 // Don't have a validated ledger range.
199 if (context.apiVersion == 1)
200 return RpcLgrIdxsInvalid;
201 return RpcNotSynced;
202 }
203
204 std::uint32_t uLedgerMin = uValidatedMin;
205 std::uint32_t uLedgerMax = uValidatedMax;
206 // Does request specify a ledger or ledger range?
207 if (ledgerSpecifier)
208 {
209 auto status = std::visit(
210 [&](auto const& ls) -> rpc::Status {
211 using T = std::decay_t<decltype(ls)>;
212 if constexpr (std::is_same_v<T, LedgerRange>)
213 {
214 // if ledger_index_min or ledger_index_max is out of
215 // valid ledger range, error out. exclude -1 as
216 // it is a valid input
217 if (context.apiVersion > 1u)
218 {
219 if ((ls.max > uValidatedMax && ls.max != -1) ||
220 (ls.min < uValidatedMin && ls.min != 0))
221 {
222 return RpcLgrIdxMalformed;
223 }
224 }
225 if (ls.min > uValidatedMin)
226 {
227 uLedgerMin = ls.min;
228 }
229 if (ls.max < uValidatedMax)
230 {
231 uLedgerMax = ls.max;
232 }
233 if (uLedgerMax < uLedgerMin)
234 {
235 if (context.apiVersion == 1)
236 return RpcLgrIdxsInvalid;
237 return RpcInvalidLgrRange;
238 }
239 }
240 else
241 {
243 auto status = getLedger(ledgerView, ls, context);
244 if (!ledgerView)
245 {
246 return status;
247 }
248
249 bool const validated = context.ledgerMaster.isValidated(*ledgerView);
250
251 if (!validated || ledgerView->header().seq > uValidatedMax ||
252 ledgerView->header().seq < uValidatedMin)
253 {
254 return RpcLgrNotValidated;
255 }
256 uLedgerMin = uLedgerMax = ledgerView->header().seq;
257 }
258 return rpc::Status::kOK;
259 },
260 *ledgerSpecifier);
261
262 if (status)
263 return status;
264 }
265 return LedgerRange{.min = uLedgerMin, .max = uLedgerMax};
266}
267
270{
272
273 AccountTxResult result;
274
275 auto lgrRange = getLedgerRange(context, args.ledger);
276 if (auto stat = std::get_if<rpc::Status>(&lgrRange))
277 {
278 // An error occurred getting the requested ledger range
279 return {result, *stat};
280 }
281
282 result.ledgerRange = std::get<LedgerRange>(lgrRange);
283
284 result.marker = args.marker;
285
287 .account = args.account,
288 .ledgerRange = result.ledgerRange,
289 .marker = result.marker,
290 .limit = args.limit,
291 .bAdmin = isUnlimited(context.role),
292 .delegate = args.delegate};
293
294 auto& db = context.app.getRelationalDatabase();
295
296 if (args.binary)
297 {
298 if (args.forward)
299 {
300 auto [tx, marker] = db.oldestAccountTxPageB(options);
301 result.transactions = tx;
302 result.marker = marker;
303 }
304 else
305 {
306 auto [tx, marker] = db.newestAccountTxPageB(options);
307 result.transactions = tx;
308 result.marker = marker;
309 }
310 }
311 else
312 {
313 if (args.forward)
314 {
315 auto [tx, marker] = db.oldestAccountTxPage(options);
316 result.transactions = tx;
317 result.marker = marker;
318 }
319 else
320 {
321 auto [tx, marker] = db.newestAccountTxPage(options);
322 result.transactions = tx;
323 result.marker = marker;
324 }
325 }
326
327 result.limit = args.limit;
328 JLOG(context.j.debug()) << __func__ << " : finished";
329
330 return {result, RpcSuccess};
331}
332
336 AccountTxArgs const& args,
337 rpc::JsonContext const& context)
338{
339 json::Value response;
340 rpc::Status const& error = res.second;
341 if (error.toErrorCode() != RpcSuccess)
342 {
343 error.inject(response);
344 }
345 else
346 {
347 AccountTxResult const& result = res.first;
348 response[jss::validated] = true;
349 response[jss::limit] = result.limit;
350 response[jss::account] = context.params[jss::account].asString();
351 response[jss::ledger_index_min] = result.ledgerRange.min;
352 response[jss::ledger_index_max] = result.ledgerRange.max;
353
354 json::Value& jvTxns = (response[jss::transactions] = json::ValueType::Array);
355
356 if (auto txnsData = std::get_if<TxnsData>(&result.transactions))
357 {
358 XRPL_ASSERT(!args.binary, "xrpl::populateJsonResponse : binary is not set");
359
360 for (auto const& [txn, txnMeta] : *txnsData)
361 {
362 if (txn)
363 {
365 jvObj[jss::validated] = true;
366
367 auto const jsonTx = (context.apiVersion > 1 ? jss::tx_json : jss::tx);
368 if (context.apiVersion > 1)
369 {
370 jvObj[jsonTx] = txn->getJson(
371 static_cast<JsonOptions::UnderlyingT>(
373 static_cast<JsonOptions::UnderlyingT>(
375 false);
376 jvObj[jss::hash] = to_string(txn->getID());
377 jvObj[jss::ledger_index] = txn->getLedger();
378 jvObj[jss::ledger_hash] =
379 to_string(context.ledgerMaster.getHashBySeq(txn->getLedger()));
380
381 if (auto closeTime =
382 context.ledgerMaster.getCloseTimeBySeq(txn->getLedger()))
383 jvObj[jss::close_time_iso] = toStringIso(*closeTime);
384 }
385 else
386 {
387 jvObj[jsonTx] = txn->getJson(JsonOptions::Values::IncludeDate);
388 }
389
390 auto const& sttx = txn->getSTransaction();
391 rpc::insertDeliverMax(jvObj[jsonTx], sttx->getTxnType(), context.apiVersion);
392 if (txnMeta)
393 {
394 jvObj[jss::meta] = txnMeta->getJson(JsonOptions::Values::IncludeDate);
395 rpc::insertAllSyntheticInJson(jvObj[jss::meta], context, sttx, *txnMeta);
396 }
397 else
398 {
399 // LCOV_EXCL_START
400 UNREACHABLE(
401 "xrpl::populateJsonResponse : missing "
402 "transaction metadata");
403 // LCOV_EXCL_STOP
404 }
405 }
406 }
407 }
408 else
409 {
410 XRPL_ASSERT(args.binary, "xrpl::populateJsonResponse : binary is set");
411
412 for (auto const& binaryData : std::get<TxnsDataBinary>(result.transactions))
413 {
415
416 jvObj[jss::tx_blob] = strHex(std::get<0>(binaryData));
417 auto const jsonMeta = (context.apiVersion > 1 ? jss::meta_blob : jss::meta);
418 jvObj[jsonMeta] = strHex(std::get<1>(binaryData));
419 jvObj[jss::ledger_index] = std::get<2>(binaryData);
420 jvObj[jss::validated] = true;
421 }
422 }
423
424 if (result.marker)
425 {
426 response[jss::marker] = json::ValueType::Object;
427 response[jss::marker][jss::ledger] = result.marker->ledgerSeq;
428 response[jss::marker][jss::seq] = result.marker->txnSeq;
429
430 if (args.delegate)
431 response[jss::marker][jss::delegate] = true;
432 }
433 }
434
435 JLOG(context.j.debug()) << __func__ << " : finished";
436 return response;
437}
438
439// {
440// account: account,
441// ledger_index_min: ledger_index // optional, defaults to earliest
442// ledger_index_max: ledger_index, // optional, defaults to latest
443// binary: boolean, // optional, defaults to false
444// forward: boolean, // optional, defaults to false
445// limit: integer, // optional
446// marker: object {ledger: ledger_index, seq: txn_sequence} // optional,
447// resume previous query
448// delegate: object { // optional
449// delegate_filter: string, // required; "actor" or "authorizer"
450// counter_party: account // optional
451// }
452// }
453//
454// Pagination note for delegate-filtered queries: the `delegate` object (both
455// `delegate_filter` and `counter_party`) must be supplied unchanged on every
456// paginated request until the query completes. A marker returned by a
457// delegate-filtered query is only valid for a follow-up request that repeats
458// the same `delegate` object
461{
462 if (!context.app.config().useTxTables())
463 return rpcError(RpcNotEnabled);
464
465 auto& params = context.params;
466 AccountTxArgs args;
467 json::Value response;
468
469 // The document[https://xrpl.org/account_tx.html#account_tx] states that
470 // binary and forward params are both boolean values, however, assigning any
471 // string value works. Do not allow this. This check is for api Version 2
472 // onwards only
473 if (context.apiVersion > 1u && params.isMember(jss::binary) && !params[jss::binary].isBool())
474 {
475 return rpc::invalidFieldError(jss::binary);
476 }
477 if (context.apiVersion > 1u && params.isMember(jss::forward) && !params[jss::forward].isBool())
478 {
479 return rpc::invalidFieldError(jss::forward);
480 }
481
482 if (auto const err = rpc::readLimitField(args.limit, rpc::tuning::kAccountTx, context))
483 return *err;
484
485 args.binary = params.isMember(jss::binary) && params[jss::binary].asBool();
486 args.forward = params.isMember(jss::forward) && params[jss::forward].asBool();
487
488 if (!params.isMember(jss::account))
489 return rpc::missingFieldError(jss::account);
490
491 if (!params[jss::account].isString())
492 return rpc::invalidFieldError(jss::account);
493
494 auto const account = parseBase58<AccountID>(params[jss::account].asString());
495 if (!account)
497
498 args.account = *account;
499
500 auto parseRes = parseLedgerArgs(context, params);
501 if (auto jv = std::get_if<json::Value>(&parseRes))
502 {
503 return *jv;
504 }
505
506 args.ledger = std::get<std::optional<LedgerSpecifier>>(parseRes);
507
508 if (params.isMember(jss::marker))
509 {
510 auto& token = params[jss::marker];
511 if (!token.isMember(jss::ledger) || !token.isMember(jss::seq) ||
512 !token[jss::ledger].isConvertibleTo(json::ValueType::UInt) ||
513 !token[jss::seq].isConvertibleTo(json::ValueType::UInt))
514 {
515 rpc::Status const status{
517 "invalid marker. Provide ledger index via ledger field, and "
518 "transaction sequence number via seq field"};
519 status.inject(response);
520 return response;
521 }
522 args.marker = {
523 .ledgerSeq = token[jss::ledger].asUInt(), .txnSeq = token[jss::seq].asUInt()};
524 }
525
526 if (params.isMember(jss::delegate))
527 {
528 if (auto const filter = parseDelegateFilter(params[jss::delegate]); filter.has_value())
529 {
530 args.delegate = *filter;
531 }
532 else
533 {
534 return filter.error();
535 }
536 }
537
538 // A marker produced by a delegate-filtered query uses a different
539 // pagination cursor than a normal query, so it is only valid when the same
540 // `delegate` object is supplied again. Reject any mismatch so pagination
541 // cannot silently skip or duplicate results.
542 if (args.marker)
543 {
544 bool const markerFromDelegate = params[jss::marker].isMember(jss::delegate) &&
545 params[jss::marker][jss::delegate].isBool() &&
546 params[jss::marker][jss::delegate].asBool();
547 if (markerFromDelegate != args.delegate.has_value())
548 {
549 rpc::Status const status{
551 "Do not mix delegate and non-delegate pagination markers in account_tx; "
552 "repeat the same `delegate` object when using a delegate marker."};
553 status.inject(response);
554 return response;
555 }
556 }
557
558 auto res = doAccountTxHelp(context, args);
559 JLOG(context.j.debug()) << __func__ << " populating response";
560 return populateJsonResponse(res, args, context);
561}
562
563} // 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)
Returns Ledgers we have all the nodes for and are indexed: a contiguous part of getFullValidatedRange...
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)
Tests whether a ledger is on the validated chain, using the skip list when the ledger does not alread...
std::vector< TxnMetaLedgerType > MetaTxsList
std::vector< AccountTx > AccountTxs
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 insertDeliverMax(json::Value &txJson, TxType txnType, unsigned int apiVersion)
Copy Amount field to DeliverMax field in transaction output JSON.
Definition DeliverMax.cpp:9
void insertAllSyntheticInJson(json::Value &metadata, ReadView const &ledger, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
Adds all synthetic fields to transaction metadata JSON.
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:83
@ 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:86
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:42
RelationalDatabase::MetaTxsList TxnsDataBinary
Definition AccountTx.cpp:84
UInt256 LedgerHash
std::variant< LedgerRange, ::rpc::spec::LedgerShortcut, std::uint32_t, uint256 > LedgerSpecifier
Definition AccountTx.cpp:91
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
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
json::Value populateJsonResponse(std::pair< AccountTxResult, rpc::Status > const &res, AccountTxArgs const &args, rpc::JsonContext const &context)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
RelationalDatabase::TxnMetaLedgerType TxnDataBinary
Definition AccountTx.cpp:85
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)
std::optional< RelationalDatabase::AccountTxMarker > marker
std::optional< DelegateFilter > delegate
std::optional< LedgerSpecifier > ledger
Definition AccountTx.cpp:97
unsigned int UnderlyingT
Definition STBase.h:23
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)