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 <memory>
34#include <optional>
35#include <type_traits>
36#include <utility>
37#include <variant>
38
39namespace xrpl {
40
47
48// parses args into a ledger specifier, or returns a Json object on error
51{
52 json::Value response;
53 // if ledger_index_min or max is specified, then ledger_hash or ledger_index
54 // should not be specified. Error out if it is
55 if (context.apiVersion > 1u)
56 {
57 if ((params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max)) &&
58 (params.isMember(jss::ledger_hash) || params.isMember(jss::ledger_index)))
59 {
60 RPC::Status const status{RpcInvalidParams, "invalidParams"};
61 status.inject(response);
62 return response;
63 }
64 }
65 if (params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max))
66 {
67 uint32_t const min =
68 params.isMember(jss::ledger_index_min) && params[jss::ledger_index_min].asInt() >= 0
69 ? params[jss::ledger_index_min].asUInt()
70 : 0;
71 uint32_t const max =
72 params.isMember(jss::ledger_index_max) && params[jss::ledger_index_max].asInt() >= 0
73 ? params[jss::ledger_index_max].asUInt()
74 : UINT32_MAX;
75
76 return LedgerRange{.min = min, .max = max};
77 }
78 if (params.isMember(jss::ledger_hash))
79 {
80 auto& hashValue = params[jss::ledger_hash];
81 if (!hashValue.isString())
82 {
83 RPC::Status const status{RpcInvalidParams, "ledgerHashNotString"};
84 status.inject(response);
85 return response;
86 }
87
88 LedgerHash hash;
89 if (!hash.parseHex(hashValue.asString()))
90 {
91 RPC::Status const status{RpcInvalidParams, "ledgerHashMalformed"};
92 status.inject(response);
93 return response;
94 }
95 return hash;
96 }
97 if (params.isMember(jss::ledger_index))
98 {
99 LedgerSpecifier ledger;
100 if (params[jss::ledger_index].isNumeric())
101 {
102 ledger = params[jss::ledger_index].asUInt();
103 }
104 else
105 {
106 std::string const ledgerStr = params[jss::ledger_index].asString();
107
108 if (ledgerStr == "current" || ledgerStr.empty())
109 {
111 }
112 else if (ledgerStr == "closed")
113 {
114 ledger = LedgerShortcut::Closed;
115 }
116 else if (ledgerStr == "validated")
117 {
119 }
120 else
121 {
122 RPC::Status const status{RpcInvalidParams, "ledger_index string malformed"};
123 status.inject(response);
124 return response;
125 }
126 }
127 return ledger;
128 }
130}
131
134{
135 std::uint32_t uValidatedMin = 0;
136 std::uint32_t uValidatedMax = 0;
137 bool const bValidated = context.ledgerMaster.getValidatedRange(uValidatedMin, uValidatedMax);
138
139 if (!bValidated)
140 {
141 // Don't have a validated ledger range.
142 if (context.apiVersion == 1)
143 return RpcLgrIdxsInvalid;
144 return RpcNotSynced;
145 }
146
147 std::uint32_t uLedgerMin = uValidatedMin;
148 std::uint32_t uLedgerMax = uValidatedMax;
149 // Does request specify a ledger or ledger range?
150 if (ledgerSpecifier)
151 {
152 auto status = std::visit(
153 [&](auto const& ls) -> RPC::Status {
154 using T = std::decay_t<decltype(ls)>;
155 if constexpr (std::is_same_v<T, LedgerRange>)
156 {
157 // if ledger_index_min or ledger_index_max is out of
158 // valid ledger range, error out. exclude -1 as
159 // it is a valid input
160 if (context.apiVersion > 1u)
161 {
162 if ((ls.max > uValidatedMax && ls.max != -1) ||
163 (ls.min < uValidatedMin && ls.min != 0))
164 {
165 return RpcLgrIdxMalformed;
166 }
167 }
168 if (ls.min > uValidatedMin)
169 {
170 uLedgerMin = ls.min;
171 }
172 if (ls.max < uValidatedMax)
173 {
174 uLedgerMax = ls.max;
175 }
176 if (uLedgerMax < uLedgerMin)
177 {
178 if (context.apiVersion == 1)
179 return RpcLgrIdxsInvalid;
180 return RpcInvalidLgrRange;
181 }
182 }
183 else
184 {
186 auto status = getLedger(ledgerView, ls, context);
187 if (!ledgerView)
188 {
189 return status;
190 }
191
192 bool const validated = context.ledgerMaster.isValidated(*ledgerView);
193
194 if (!validated || ledgerView->header().seq > uValidatedMax ||
195 ledgerView->header().seq < uValidatedMin)
196 {
197 return RpcLgrNotValidated;
198 }
199 uLedgerMin = uLedgerMax = ledgerView->header().seq;
200 }
201 return RPC::Status::kOK;
202 },
203 *ledgerSpecifier);
204
205 if (status)
206 return status;
207 }
208 return LedgerRange{.min = uLedgerMin, .max = uLedgerMax};
209}
210
213{
215
216 AccountTxResult result;
217
218 auto lgrRange = getLedgerRange(context, args.ledger);
219 if (auto stat = std::get_if<RPC::Status>(&lgrRange))
220 {
221 // An error occurred getting the requested ledger range
222 return {result, *stat};
223 }
224
225 result.ledgerRange = std::get<LedgerRange>(lgrRange);
226
227 result.marker = args.marker;
228
230 .account = args.account,
231 .ledgerRange = result.ledgerRange,
232 .marker = result.marker,
233 .limit = args.limit,
234 .bAdmin = isUnlimited(context.role)};
235
236 auto& db = context.app.getRelationalDatabase();
237
238 if (args.binary)
239 {
240 if (args.forward)
241 {
242 auto [tx, marker] = db.oldestAccountTxPageB(options);
243 result.transactions = tx;
244 result.marker = marker;
245 }
246 else
247 {
248 auto [tx, marker] = db.newestAccountTxPageB(options);
249 result.transactions = tx;
250 result.marker = marker;
251 }
252 }
253 else
254 {
255 if (args.forward)
256 {
257 auto [tx, marker] = db.oldestAccountTxPage(options);
258 result.transactions = tx;
259 result.marker = marker;
260 }
261 else
262 {
263 auto [tx, marker] = db.newestAccountTxPage(options);
264 result.transactions = tx;
265 result.marker = marker;
266 }
267 }
268
269 result.limit = args.limit;
270 JLOG(context.j.debug()) << __func__ << " : finished";
271
272 return {result, RpcSuccess};
273}
274
278 AccountTxArgs const& args,
279 RPC::JsonContext const& context)
280{
281 json::Value response;
282 RPC::Status const& error = res.second;
283 if (error.toErrorCode() != RpcSuccess)
284 {
285 error.inject(response);
286 }
287 else
288 {
289 AccountTxResult const& result = res.first;
290 response[jss::validated] = true;
291 response[jss::limit] = result.limit;
292 response[jss::account] = context.params[jss::account].asString();
293 response[jss::ledger_index_min] = result.ledgerRange.min;
294 response[jss::ledger_index_max] = result.ledgerRange.max;
295
296 json::Value& jvTxns = (response[jss::transactions] = json::ValueType::Array);
297
298 if (auto txnsData = std::get_if<TxnsData>(&result.transactions))
299 {
300 XRPL_ASSERT(!args.binary, "xrpl::populateJsonResponse : binary is not set");
301
302 for (auto const& [txn, txnMeta] : *txnsData)
303 {
304 if (txn)
305 {
307 jvObj[jss::validated] = true;
308
309 auto const jsonTx = (context.apiVersion > 1 ? jss::tx_json : jss::tx);
310 if (context.apiVersion > 1)
311 {
312 jvObj[jsonTx] = txn->getJson(
313 static_cast<JsonOptions::underlying_t>(
315 static_cast<JsonOptions::underlying_t>(
317 false);
318 jvObj[jss::hash] = to_string(txn->getID());
319 jvObj[jss::ledger_index] = txn->getLedger();
320 jvObj[jss::ledger_hash] =
321 to_string(context.ledgerMaster.getHashBySeq(txn->getLedger()));
322
323 if (auto closeTime =
324 context.ledgerMaster.getCloseTimeBySeq(txn->getLedger()))
325 jvObj[jss::close_time_iso] = toStringIso(*closeTime);
326 }
327 else
328 {
329 jvObj[jsonTx] = txn->getJson(JsonOptions::Values::IncludeDate);
330 }
331
332 auto const& sttx = txn->getSTransaction();
333 RPC::insertDeliverMax(jvObj[jsonTx], sttx->getTxnType(), context.apiVersion);
334 if (txnMeta)
335 {
336 jvObj[jss::meta] = txnMeta->getJson(JsonOptions::Values::IncludeDate);
337 insertDeliveredAmount(jvObj[jss::meta], context, txn, *txnMeta);
338 RPC::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
339 RPC::insertMPTokenIssuanceID(jvObj[jss::meta], sttx, *txnMeta);
340 }
341 else
342 {
343 // LCOV_EXCL_START
344 UNREACHABLE(
345 "xrpl::populateJsonResponse : missing "
346 "transaction metadata");
347 // LCOV_EXCL_STOP
348 }
349 }
350 }
351 }
352 else
353 {
354 XRPL_ASSERT(args.binary, "xrpl::populateJsonResponse : binary is set");
355
356 for (auto const& binaryData : std::get<TxnsDataBinary>(result.transactions))
357 {
359
360 jvObj[jss::tx_blob] = strHex(std::get<0>(binaryData));
361 auto const jsonMeta = (context.apiVersion > 1 ? jss::meta_blob : jss::meta);
362 jvObj[jsonMeta] = strHex(std::get<1>(binaryData));
363 jvObj[jss::ledger_index] = std::get<2>(binaryData);
364 jvObj[jss::validated] = true;
365 }
366 }
367
368 if (result.marker)
369 {
370 response[jss::marker] = json::ValueType::Object;
371 response[jss::marker][jss::ledger] = result.marker->ledgerSeq;
372 response[jss::marker][jss::seq] = result.marker->txnSeq;
373 }
374 }
375
376 JLOG(context.j.debug()) << __func__ << " : finished";
377 return response;
378}
379
380// {
381// account: account,
382// ledger_index_min: ledger_index // optional, defaults to earliest
383// ledger_index_max: ledger_index, // optional, defaults to latest
384// binary: boolean, // optional, defaults to false
385// forward: boolean, // optional, defaults to false
386// limit: integer, // optional
387// marker: object {ledger: ledger_index, seq: txn_sequence} // optional,
388// resume previous query
389// }
392{
393 if (!context.app.config().useTxTables())
394 return rpcError(RpcNotEnabled);
395
396 auto& params = context.params;
397 AccountTxArgs args;
398 json::Value response;
399
400 // The document[https://xrpl.org/account_tx.html#account_tx] states that
401 // binary and forward params are both boolean values, however, assigning any
402 // string value works. Do not allow this. This check is for api Version 2
403 // onwards only
404 if (context.apiVersion > 1u && params.isMember(jss::binary) && !params[jss::binary].isBool())
405 {
406 return RPC::invalidFieldError(jss::binary);
407 }
408 if (context.apiVersion > 1u && params.isMember(jss::forward) && !params[jss::forward].isBool())
409 {
410 return RPC::invalidFieldError(jss::forward);
411 }
412
413 if (auto const err = RPC::readLimitField(args.limit, RPC::Tuning::kAccountTx, context))
414 return *err;
415
416 args.binary = params.isMember(jss::binary) && params[jss::binary].asBool();
417 args.forward = params.isMember(jss::forward) && params[jss::forward].asBool();
418
419 if (!params.isMember(jss::account))
420 return RPC::missingFieldError(jss::account);
421
422 if (!params[jss::account].isString())
423 return RPC::invalidFieldError(jss::account);
424
425 auto const account = parseBase58<AccountID>(params[jss::account].asString());
426 if (!account)
428
429 args.account = *account;
430
431 auto parseRes = parseLedgerArgs(context, params);
432 if (auto jv = std::get_if<json::Value>(&parseRes))
433 {
434 return *jv;
435 }
436
437 args.ledger = std::get<std::optional<LedgerSpecifier>>(parseRes);
438
439 if (params.isMember(jss::marker))
440 {
441 auto& token = params[jss::marker];
442 if (!token.isMember(jss::ledger) || !token.isMember(jss::seq) ||
443 !token[jss::ledger].isConvertibleTo(json::ValueType::UInt) ||
444 !token[jss::seq].isConvertibleTo(json::ValueType::UInt))
445 {
446 RPC::Status const status{
448 "invalid marker. Provide ledger index via ledger field, and "
449 "transaction sequence number via seq field"};
450 status.inject(response);
451 return response;
452 }
453 args.marker = {
454 .ledgerSeq = token[jss::ledger].asUInt(), .txnSeq = token[jss::seq].asUInt()};
455 }
456
457 auto res = doAccountTxHelp(context, args);
458 JLOG(context.j.debug()) << __func__ << " populating response";
459 return populateJsonResponse(res, args, context);
460}
461
462} // namespace xrpl
Stream debug() const
Definition Journal.h:297
Represents a JSON value.
Definition json_value.h:130
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:507
bool useTxTables() const
Definition Config.h:322
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:21
@ Array
array value (ordered list)
Definition json_value.h:25
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
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:273
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
Charge const kFeeMediumBurdenRpc
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::variant< std::optional< LedgerSpecifier >, json::Value > parseLedgerArgs(RPC::Context &context, json::Value const &params)
Definition AccountTx.cpp:50
RelationalDatabase::AccountTxs TxnsData
Definition AccountTx.cpp:41
@ RpcSuccess
Definition ErrorCodes.h:26
@ RpcActMalformed
Definition ErrorCodes.h:72
@ RpcNotSynced
Definition ErrorCodes.h:49
@ RpcLgrIdxsInvalid
Definition ErrorCodes.h:94
@ RpcNotEnabled
Definition ErrorCodes.h:41
@ RpcInvalidParams
Definition ErrorCodes.h:66
@ RpcLgrNotValidated
Definition ErrorCodes.h:55
@ RpcInvalidLgrRange
Definition ErrorCodes.h:118
@ RpcLgrIdxMalformed
Definition ErrorCodes.h:95
RelationalDatabase::AccountTxResult AccountTxResult
Definition AccountTx.cpp:45
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
json::Value doAccountTx(RPC::JsonContext &context)
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
RelationalDatabase::MetaTxsList TxnsDataBinary
Definition AccountTx.cpp:42
RelationalDatabase::txnMetaLedgerType TxnDataBinary
Definition AccountTx.cpp:43
@ 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:633
std::string toStringIso(date::sys_time< Duration > tp)
Definition chrono.h:68
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
uint256 LedgerHash
std::variant< LedgerRange, RPC::Status > getLedgerRange(RPC::Context &context, std::optional< LedgerSpecifier > const &ledgerSpecifier)
json::Value populateJsonResponse(std::pair< AccountTxResult, RPC::Status > const &res, AccountTxArgs const &args, RPC::JsonContext const &context)
RelationalDatabase::AccountTxArgs AccountTxArgs
Definition AccountTx.cpp:44
std::pair< AccountTxResult, RPC::Status > doAccountTxHelp(RPC::Context &context, AccountTxArgs const &args)
RelationalDatabase::LedgerSpecifier LedgerSpecifier
Definition AccountTx.cpp:46
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:115
unsigned int underlying_t
Definition STBase.h:18
The context of information needed to call an RPC.
Definition Context.h:19
beast::Journal const j
Definition Context.h:20
Application & app
Definition Context.h:21
Resource::Charge & loadType
Definition Context.h:22
unsigned int apiVersion
Definition Context.h:29
LedgerMaster & ledgerMaster
Definition Context.h:24
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
void inject(json::Value &object) const
Apply the Status to a JsonObject.
Definition Status.h:89
ErrorCodeI toErrorCode() const
Returns the Status as an error_code_i.
Definition Status.h:80
std::optional< AccountTxMarker > marker
std::optional< LedgerSpecifier > ledger
std::variant< AccountTxs, MetaTxsList > transactions
std::optional< AccountTxMarker > marker
T visit(T... args)