rippled
Loading...
Searching...
No Matches
AccountTx_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/envconfig.h>
3
4#include <xrpl/beast/unit_test.h>
5#include <xrpl/beast/unit_test/suite.h>
6#include <xrpl/protocol/ErrorCodes.h>
7#include <xrpl/protocol/TxFlags.h>
8#include <xrpl/protocol/jss.h>
9
10#include <boost/container/flat_set.hpp>
11
12namespace xrpl {
13
14namespace test {
15
17{
18 // A data structure used to describe the basic structure of a
19 // transactions array node as returned by the account_tx RPC command.
21 {
22 int const index;
24 boost::container::flat_set<std::string> created;
25 boost::container::flat_set<std::string> deleted;
26 boost::container::flat_set<std::string> modified;
27
29 int idx,
30 Json::StaticString const& t,
34 : index(idx), txType(t)
35 {
36 auto buildSet = [](auto&& init) {
37 boost::container::flat_set<std::string> r;
38 r.reserve(init.size());
39 for (auto&& s : init)
40 r.insert(s);
41 return r;
42 };
43
44 created = buildSet(c);
45 deleted = buildSet(d);
46 modified = buildSet(m);
47 }
48 };
49
50 // A helper method tests can use to validate returned JSON vs NodeSanity.
51 void
53 {
54 BEAST_EXPECT(txNode[jss::validated].asBool() == true);
55 BEAST_EXPECT(txNode[jss::tx][sfTransactionType.jsonName].asString() == sane.txType);
56
57 // Make sure all of the expected node types are present.
58 boost::container::flat_set<std::string> createdNodes;
59 boost::container::flat_set<std::string> deletedNodes;
60 boost::container::flat_set<std::string> modifiedNodes;
61
62 for (Json::Value const& metaNode : txNode[jss::meta][sfAffectedNodes.jsonName])
63 {
64 if (metaNode.isMember(sfCreatedNode.jsonName))
65 createdNodes.insert(metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName].asString());
66
67 else if (metaNode.isMember(sfDeletedNode.jsonName))
68 deletedNodes.insert(metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName].asString());
69
70 else if (metaNode.isMember(sfModifiedNode.jsonName))
71 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName][sfLedgerEntryType.jsonName].asString());
72
73 else
74 fail("Unexpected or unlabeled node type in metadata.", __FILE__, __LINE__);
75 }
76
77 BEAST_EXPECT(createdNodes == sane.created);
78 BEAST_EXPECT(deletedNodes == sane.deleted);
79 BEAST_EXPECT(modifiedNodes == sane.modified);
80 };
81
82 void
83 testParameters(unsigned int apiVersion)
84 {
85 testcase("Parameters APIv" + std::to_string(apiVersion));
86 using namespace test::jtx;
87
88 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
89 cfg->FEES.reference_fee = 10;
90 return cfg;
91 }));
92 Account A1{"A1"};
93 env.fund(XRP(10000), A1);
94 env.close();
95
96 // Ledger 3 has the two txs associated with funding the account
97 // All other ledgers have no txs
98
99 auto hasTxs = [apiVersion](Json::Value const& j) {
100 switch (apiVersion)
101 {
102 case 1:
103 return j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
104 (j[jss::result][jss::transactions].size() == 2) &&
105 (j[jss::result][jss::transactions][0u][jss::tx][jss::TransactionType] == jss::AccountSet) &&
106 (j[jss::result][jss::transactions][1u][jss::tx][jss::TransactionType] == jss::Payment) &&
107 (j[jss::result][jss::transactions][1u][jss::tx][jss::DeliverMax] == "10000000010") &&
108 (j[jss::result][jss::transactions][1u][jss::tx][jss::Amount] ==
109 j[jss::result][jss::transactions][1u][jss::tx][jss::DeliverMax]);
110 case 2:
111 case 3:
112 if (j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
113 (j[jss::result][jss::transactions].size() == 2) &&
114 (j[jss::result][jss::transactions][0u][jss::tx_json][jss::TransactionType] == jss::AccountSet))
115 {
116 auto const& payment = j[jss::result][jss::transactions][1u];
117
118 return (payment.isMember(jss::tx_json)) &&
119 (payment[jss::tx_json][jss::TransactionType] == jss::Payment) &&
120 (payment[jss::tx_json][jss::DeliverMax] == "10000000010") &&
121 (!payment[jss::tx_json].isMember(jss::Amount)) &&
122 (!payment[jss::tx_json].isMember(jss::hash)) &&
123 (payment[jss::hash] ==
124 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
125 "ECF0D4CE981D0A8") &&
126 (payment[jss::validated] == true) && (payment[jss::ledger_index] == 3) &&
127 (payment[jss::ledger_hash] ==
128 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
129 "580A5AFDD727E33") &&
130 (payment[jss::close_time_iso] == "2000-01-01T00:00:10Z");
131 }
132 else
133 return false;
134
135 default:
136 return false;
137 }
138 };
139
140 auto noTxs = [](Json::Value const& j) {
141 return j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
142 (j[jss::result][jss::transactions].size() == 0);
143 };
144
145 auto isErr = [](Json::Value const& j, error_code_i code) {
146 return j.isMember(jss::result) && j[jss::result].isMember(jss::error) &&
147 j[jss::result][jss::error] == RPC::get_error_info(code).token;
148 };
149
150 Json::Value jParams;
151 jParams[jss::api_version] = apiVersion;
152
153 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), rpcINVALID_PARAMS));
154
155 jParams[jss::account] = "0xDEADBEEF";
156
157 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), rpcACT_MALFORMED));
158
159 jParams[jss::account] = A1.human();
160 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(jParams))));
161
162 // Ledger min/max index
163 {
164 Json::Value p{jParams};
165 p[jss::ledger_index_min] = -1;
166 p[jss::ledger_index_max] = -1;
167 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
168
169 p[jss::ledger_index_min] = 0;
170 p[jss::ledger_index_max] = 100;
171 if (apiVersion < 2u)
172 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
173 else
174 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_IDX_MALFORMED));
175
176 p[jss::ledger_index_min] = 1;
177 p[jss::ledger_index_max] = 2;
178 if (apiVersion < 2u)
179 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
180 else
181 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_IDX_MALFORMED));
182
183 p[jss::ledger_index_min] = 2;
184 p[jss::ledger_index_max] = 1;
185 BEAST_EXPECT(isErr(
186 env.rpc("json", "account_tx", to_string(p)),
187 (apiVersion == 1 ? rpcLGR_IDXS_INVALID : rpcINVALID_LGR_RANGE)));
188 }
189 // Ledger index min only
190 {
191 Json::Value p{jParams};
192 p[jss::ledger_index_min] = -1;
193 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
194
195 p[jss::ledger_index_min] = 1;
196 if (apiVersion < 2u)
197 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
198 else
199 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_IDX_MALFORMED));
200
201 p[jss::ledger_index_min] = env.current()->header().seq;
202 BEAST_EXPECT(isErr(
203 env.rpc("json", "account_tx", to_string(p)),
204 (apiVersion == 1 ? rpcLGR_IDXS_INVALID : rpcINVALID_LGR_RANGE)));
205 }
206
207 // Ledger index max only
208 {
209 Json::Value p{jParams};
210 p[jss::ledger_index_max] = -1;
211 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
212
213 p[jss::ledger_index_max] = env.current()->header().seq;
214 if (apiVersion < 2u)
215 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
216 else
217 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_IDX_MALFORMED));
218
219 p[jss::ledger_index_max] = 3;
220 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
221
222 p[jss::ledger_index_max] = env.closed()->header().seq;
223 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
224
225 p[jss::ledger_index_max] = env.closed()->header().seq - 1;
226 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
227 }
228
229 // Ledger Sequence
230 {
231 Json::Value p{jParams};
232
233 p[jss::ledger_index] = env.closed()->header().seq;
234 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
235
236 p[jss::ledger_index] = env.closed()->header().seq - 1;
237 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
238
239 p[jss::ledger_index] = env.current()->header().seq;
240 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_VALIDATED));
241
242 p[jss::ledger_index] = env.current()->header().seq + 1;
243 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
244 }
245
246 // Ledger Hash
247 {
248 Json::Value p{jParams};
249
250 p[jss::ledger_hash] = to_string(env.closed()->header().hash);
251 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
252
253 p[jss::ledger_hash] = to_string(env.closed()->header().parentHash);
254 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
255 }
256
257 // Ledger index max/min/index all specified
258 // ERRORS out with invalid Parenthesis
259 {
260 jParams[jss::account] = "0xDEADBEEF";
261 jParams[jss::account] = A1.human();
262 Json::Value p{jParams};
263
264 p[jss::ledger_index_max] = -1;
265 p[jss::ledger_index_min] = -1;
266 p[jss::ledger_index] = -1;
267
268 if (apiVersion < 2u)
269 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
270 else
271 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcINVALID_PARAMS));
272 }
273
274 // Ledger index max only
275 {
276 Json::Value p{jParams};
277 p[jss::ledger_index_max] = env.current()->header().seq;
278 if (apiVersion < 2u)
279 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
280 else
281 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcLGR_IDX_MALFORMED));
282 }
283 // test account non-string
284 {
285 auto testInvalidAccountParam = [&](auto const& param) {
286 Json::Value params;
287 params[jss::account] = param;
288 auto jrr = env.rpc("json", "account_tx", to_string(params))[jss::result];
289 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
290 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
291 };
292
293 testInvalidAccountParam(1);
294 testInvalidAccountParam(1.1);
295 testInvalidAccountParam(true);
296 testInvalidAccountParam(Json::Value(Json::nullValue));
297 testInvalidAccountParam(Json::Value(Json::objectValue));
298 testInvalidAccountParam(Json::Value(Json::arrayValue));
299 }
300 // test binary and forward for bool/non bool values
301 {
302 Json::Value p{jParams};
303 p[jss::binary] = "asdf";
304 if (apiVersion < 2u)
305 {
306 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
307 BEAST_EXPECT(result[jss::result][jss::status] == "success");
308 }
309 else
310 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcINVALID_PARAMS));
311
312 p[jss::binary] = true;
313 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
314 BEAST_EXPECT(result[jss::result][jss::status] == "success");
315
316 p[jss::forward] = "true";
317 if (apiVersion < 2u)
318 BEAST_EXPECT(result[jss::result][jss::status] == "success");
319 else
320 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcINVALID_PARAMS));
321
322 p[jss::forward] = false;
323 result = env.rpc("json", "account_tx", to_string(p));
324 BEAST_EXPECT(result[jss::result][jss::status] == "success");
325 }
326 // test limit with malformed values
327 {
328 Json::Value p{jParams};
329
330 // Test case: limit = 0 should fail (below minimum)
331 p[jss::limit] = 0;
332 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), rpcINVALID_PARAMS));
333
334 // Test case: limit = 1.2 should fail (not an integer)
335 p[jss::limit] = 1.2;
336 BEAST_EXPECT(
337 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
338 RPC::expected_field_message(jss::limit, "unsigned integer"));
339
340 // Test case: limit = "10" should fail (string instead of integer)
341 p[jss::limit] = "10";
342 BEAST_EXPECT(
343 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
344 RPC::expected_field_message(jss::limit, "unsigned integer"));
345
346 // Test case: limit = true should fail (boolean instead of integer)
347 p[jss::limit] = true;
348 BEAST_EXPECT(
349 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
350 RPC::expected_field_message(jss::limit, "unsigned integer"));
351
352 // Test case: limit = false should fail (boolean instead of integer)
353 p[jss::limit] = false;
354 BEAST_EXPECT(
355 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
356 RPC::expected_field_message(jss::limit, "unsigned integer"));
357
358 // Test case: limit = -1 should fail (negative number)
359 p[jss::limit] = -1;
360 BEAST_EXPECT(
361 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
362 RPC::expected_field_message(jss::limit, "unsigned integer"));
363
364 // Test case: limit = [] should fail (array instead of integer)
365 p[jss::limit] = Json::Value(Json::arrayValue);
366 BEAST_EXPECT(
367 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
368 RPC::expected_field_message(jss::limit, "unsigned integer"));
369
370 // Test case: limit = {} should fail (object instead of integer)
371 p[jss::limit] = Json::Value(Json::objectValue);
372 BEAST_EXPECT(
373 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
374 RPC::expected_field_message(jss::limit, "unsigned integer"));
375
376 // Test case: limit = "malformed" should fail (malformed string)
377 p[jss::limit] = "malformed";
378 BEAST_EXPECT(
379 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
380 RPC::expected_field_message(jss::limit, "unsigned integer"));
381
382 // Test case: limit = ["limit"] should fail (array with string)
383 p[jss::limit] = Json::Value(Json::arrayValue);
384 p[jss::limit].append("limit");
385 BEAST_EXPECT(
386 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
387 RPC::expected_field_message(jss::limit, "unsigned integer"));
388
389 // Test case: limit = {"limit": 10} should fail (object with
390 // property)
391 p[jss::limit] = Json::Value(Json::objectValue);
392 p[jss::limit][jss::limit] = 10;
393 BEAST_EXPECT(
394 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
395 RPC::expected_field_message(jss::limit, "unsigned integer"));
396
397 // Test case: limit = 10 should succeed (valid integer)
398 p[jss::limit] = 10;
399 BEAST_EXPECT(env.rpc("json", "account_tx", to_string(p))[jss::result][jss::status] == "success");
400 }
401 }
402
403 void
405 {
406 testcase("Contents");
407
408 // Get results for all transaction types that can be associated
409 // with an account. Start by generating all transaction types.
410 using namespace test::jtx;
411 using namespace std::chrono_literals;
412
413 Env env(*this);
414 Account const alice{"alice"};
415 Account const alie{"alie"};
416 Account const gw{"gw"};
417 auto const USD{gw["USD"]};
418
419 env.fund(XRP(1000000), alice, gw);
420 env.close();
421
422 // AccountSet
423 env(noop(alice));
424
425 // Payment
426 env(pay(alice, gw, XRP(100)));
427
428 // Regular key set
429 env(regkey(alice, alie));
430 env.close();
431
432 // Trust and Offers
433 env(trust(alice, USD(200)), sig(alie));
434 std::uint32_t const offerSeq{env.seq(alice)};
435 env(offer(alice, USD(50), XRP(150)), sig(alie));
436 env.close();
437
438 env(offer_cancel(alice, offerSeq), sig(alie));
439 env.close();
440
441 // SignerListSet
442 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
443
444 // Escrow
445 {
446 // Create an escrow. Requires either a CancelAfter or FinishAfter.
447 auto escrow = [](Account const& account, Account const& to, STAmount const& amount) {
448 Json::Value escrow;
449 escrow[jss::TransactionType] = jss::EscrowCreate;
450 escrow[jss::Account] = account.human();
451 escrow[jss::Destination] = to.human();
452 escrow[jss::Amount] = amount.getJson(JsonOptions::none);
453 return escrow;
454 };
455
456 NetClock::time_point const nextTime{env.now() + 2s};
457
458 Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
459 escrowWithFinish[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
460
461 std::uint32_t const escrowFinishSeq{env.seq(alice)};
462 env(escrowWithFinish, sig(alie));
463
464 Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
465 escrowWithCancel[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
466 escrowWithCancel[sfCancelAfter.jsonName] = nextTime.time_since_epoch().count() + 1;
467
468 std::uint32_t const escrowCancelSeq{env.seq(alice)};
469 env(escrowWithCancel, sig(alie));
470 env.close();
471
472 {
473 Json::Value escrowFinish;
474 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
475 escrowFinish[jss::Account] = alice.human();
476 escrowFinish[sfOwner.jsonName] = alice.human();
477 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
478 env(escrowFinish, sig(alie));
479 }
480 {
481 Json::Value escrowCancel;
482 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
483 escrowCancel[jss::Account] = alice.human();
484 escrowCancel[sfOwner.jsonName] = alice.human();
485 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
486 env(escrowCancel, sig(alie));
487 }
488 env.close();
489 }
490
491 // PayChan
492 {
493 std::uint32_t payChanSeq{env.seq(alice)};
494 Json::Value payChanCreate;
495 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
496 payChanCreate[jss::Account] = alice.human();
497 payChanCreate[jss::Destination] = gw.human();
498 payChanCreate[jss::Amount] = XRP(500).value().getJson(JsonOptions::none);
499 payChanCreate[sfSettleDelay.jsonName] = NetClock::duration{100s}.count();
500 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
501 env(payChanCreate, sig(alie));
502 env.close();
503
504 std::string const payChanIndex{strHex(keylet::payChan(alice, gw, payChanSeq).key)};
505
506 {
507 Json::Value payChanFund;
508 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
509 payChanFund[jss::Account] = alice.human();
510 payChanFund[sfChannel.jsonName] = payChanIndex;
511 payChanFund[jss::Amount] = XRP(200).value().getJson(JsonOptions::none);
512 env(payChanFund, sig(alie));
513 env.close();
514 }
515 {
516 Json::Value payChanClaim;
517 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
518 payChanClaim[jss::Flags] = tfClose;
519 payChanClaim[jss::Account] = gw.human();
520 payChanClaim[sfChannel.jsonName] = payChanIndex;
521 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
522 env(payChanClaim);
523 env.close();
524 }
525 }
526
527 // Check
528 {
529 auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
530 env(check::create(alice, gw, XRP(300)), sig(alie));
531
532 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
533 env(check::create(gw, alice, XRP(200)));
534 env.close();
535
536 env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
537 env(check::cancel(alice, aliceCheckId), sig(alie));
538 env.close();
539 }
540 {
541 // Deposit pre-authorization with a Ticket.
542 std::uint32_t const tktSeq{env.seq(alice) + 1};
543 env(ticket::create(alice, 1), sig(alie));
544 env.close();
545
546 env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
547 env.close();
548 }
549
550 // Setup is done. Look at the transactions returned by account_tx.
551 Json::Value params;
552 params[jss::account] = alice.human();
553 params[jss::ledger_index_min] = -1;
554 params[jss::ledger_index_max] = -1;
555
556 Json::Value const result{env.rpc("json", "account_tx", to_string(params))};
557
558 BEAST_EXPECT(result[jss::result][jss::status] == "success");
559 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
560
561 Json::Value const& txs{result[jss::result][jss::transactions]};
562
563 // clang-format off
564 // Do a sanity check on each returned transaction. They should
565 // be returned in the reverse order of application to the ledger.
566 static const NodeSanity sanity[]{
567 // txType, created, deleted, modified
568 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
569 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
570 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
571 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
572 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
573 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
574 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
575 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
576 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
577 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
578 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
579 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
580 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
581 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
582 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
583 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
584 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
585 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
586 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
587 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
588 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
589 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
590 };
591 // clang-format on
592
593 BEAST_EXPECT(std::size(sanity) == result[jss::result][jss::transactions].size());
594
595 for (unsigned int index{0}; index < std::size(sanity); ++index)
596 {
597 checkSanity(txs[index], sanity[index]);
598 }
599 }
600
601 void
603 {
604 testcase("AccountDelete");
605
606 // Verify that if an account is resurrected then the account_tx RPC
607 // command still recovers all transactions on that account before
608 // and after resurrection.
609 using namespace test::jtx;
610 using namespace std::chrono_literals;
611
612 Env env(*this);
613 Account const alice{"alice"};
614 Account const becky{"becky"};
615
616 env.fund(XRP(10000), alice, becky);
617 env.close();
618
619 // Verify that becky's account root is present.
620 Keylet const beckyAcctKey{keylet::account(becky.id())};
621 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
622
623 // becky does an AccountSet .
624 env(noop(becky));
625
626 // Close enough ledgers to be able to delete becky's account.
627 std::uint32_t const ledgerCount{env.current()->seq() + 257 - env.seq(becky)};
628
629 for (std::uint32_t i = 0; i < ledgerCount; ++i)
630 env.close();
631
632 auto const beckyPreDelBalance{env.balance(becky)};
633
634 auto const acctDelFee{drops(env.current()->fees().increment)};
635 env(acctdelete(becky, alice), fee(acctDelFee));
636 env.close();
637
638 // Verify that becky's account root is gone.
639 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
640 env.close();
641
642 // clang-format off
643 // Do a sanity check on each returned transaction. They should
644 // be returned in the reverse order of application to the ledger.
645 //
646 // Note that the first two transactions in sanity have not occurred
647 // yet. We'll see those after becky's account is resurrected.
648 static const NodeSanity sanity[]
649 {
650 // txType, created, deleted, modified
651/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
652/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
653/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
654/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
655/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
656/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
657 };
658 // clang-format on
659
660 // Verify that we can recover becky's account_tx information even
661 // after the account is deleted.
662 {
663 Json::Value params;
664 params[jss::account] = becky.human();
665 params[jss::ledger_index_min] = -1;
666 params[jss::ledger_index_max] = -1;
667
668 Json::Value const result{env.rpc("json", "account_tx", to_string(params))};
669
670 BEAST_EXPECT(result[jss::result][jss::status] == "success");
671 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
672
673 // The first two transactions listed in sanity haven't happened yet.
674 constexpr unsigned int beckyDeletedOffset = 2;
675 BEAST_EXPECT(std::size(sanity) == result[jss::result][jss::transactions].size() + beckyDeletedOffset);
676
677 Json::Value const& txs{result[jss::result][jss::transactions]};
678
679 for (unsigned int index = beckyDeletedOffset; index < std::size(sanity); ++index)
680 {
681 checkSanity(txs[index - beckyDeletedOffset], sanity[index]);
682 }
683 }
684
685 // All it takes is a large enough XRP payment to resurrect
686 // becky's account. Try too small a payment.
687 env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - XRP(1)), ter(tecNO_DST_INSUF_XRP));
688 env.close();
689
690 // Actually resurrect becky's account.
691 env(pay(alice, becky, XRP(45)));
692 env.close();
693
694 // becky's account root should be back.
695 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
696 BEAST_EXPECT(env.balance(becky) == XRP(45));
697
698 // becky pays alice.
699 env(pay(becky, alice, XRP(20)));
700 env.close();
701
702 // Setup is done. Look at the transactions returned by account_tx.
703 // Verify that account_tx locates all of becky's transactions.
704 Json::Value params;
705 params[jss::account] = becky.human();
706 params[jss::ledger_index_min] = -1;
707 params[jss::ledger_index_max] = -1;
708
709 Json::Value const result{env.rpc("json", "account_tx", to_string(params))};
710
711 BEAST_EXPECT(result[jss::result][jss::status] == "success");
712 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
713
714 BEAST_EXPECT(std::size(sanity) == result[jss::result][jss::transactions].size());
715
716 Json::Value const& txs{result[jss::result][jss::transactions]};
717
718 for (unsigned int index = 0; index < std::size(sanity); ++index)
719 {
720 checkSanity(txs[index], sanity[index]);
721 }
722 }
723
724 void
726 {
727 testcase("MPT");
728
729 using namespace test::jtx;
730 using namespace std::chrono_literals;
731
732 auto cfg = makeConfig();
733 cfg->FEES.reference_fee = 10;
734 Env env(*this, std::move(cfg));
735
736 Account const alice{"alice"};
737 Account const bob{"bob"};
738 Account const carol{"carol"};
739
740 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
741
742 // check the latest mpt-related txn is in alice's account history
743 auto const checkAliceAcctTx = [&](size_t size, Json::StaticString txType) {
744 Json::Value params;
745 params[jss::account] = alice.human();
746 params[jss::limit] = 100;
747 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
748
749 BEAST_EXPECT(jv[jss::transactions].size() == size);
750 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
751 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
752
753 std::string const txHash{env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
754 BEAST_EXPECT(tx0[jss::hash] == txHash);
755 };
756
757 // alice creates issuance
758 mptAlice.create(
759 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer});
760
761 checkAliceAcctTx(3, jss::MPTokenIssuanceCreate);
762
763 // bob creates a MPToken;
764 mptAlice.authorize({.account = bob});
765 checkAliceAcctTx(4, jss::MPTokenAuthorize);
766 env.close();
767
768 // TODO: windows pipeline fails validation for the hardcoded ledger hash
769 // due to having different test config, it can be uncommented after
770 // figuring out what happened
771 //
772 // ledger hash should be fixed regardless any change to account history
773 // BEAST_EXPECT(
774 // to_string(env.closed()->header().hash) ==
775 // "0BD507BB87D3C0E73B462485E6E381798A8C82FC49BF17FE39C60E08A1AF035D");
776
777 // alice authorizes bob
778 mptAlice.authorize({.account = alice, .holder = bob});
779 checkAliceAcctTx(5, jss::MPTokenAuthorize);
780
781 // carol creates a MPToken;
782 mptAlice.authorize({.account = carol});
783 checkAliceAcctTx(6, jss::MPTokenAuthorize);
784
785 // alice authorizes carol
786 mptAlice.authorize({.account = alice, .holder = carol});
787 checkAliceAcctTx(7, jss::MPTokenAuthorize);
788
789 // alice pays bob 100 tokens
790 mptAlice.pay(alice, bob, 100);
791 checkAliceAcctTx(8, jss::Payment);
792
793 // bob pays carol 10 tokens
794 mptAlice.pay(bob, carol, 10);
795 checkAliceAcctTx(9, jss::Payment);
796 }
797
798public:
799 void
807};
808BEAST_DEFINE_TESTSUITE(AccountTx, rpc, xrpl);
809
810} // namespace test
811} // namespace xrpl
T bind_front(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:44
Represents a JSON value.
Definition json_value.h:130
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:516
void run() override
Runs the suite.
void testParameters(unsigned int apiVersion)
void checkSanity(Json::Value const &txNode, NodeSanity const &sane)
Immutable cryptographic account descriptor.
Definition Account.h:19
A transaction testing environment.
Definition Env.h:119
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:92
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:240
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:158
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:792
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:475
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
NetClock::time_point now()
Returns the current network time.
Definition Env.h:274
Set the fee on a JTx.
Definition fee.h:17
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:15
Set the regular signature on a JTx.
Definition sig.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
Set a ticket sequence on a JTx.
Definition ticket.h:28
@ nullValue
'null' value
Definition json_value.h:19
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:280
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:346
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:293
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Definition check.cpp:14
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition check.cpp:38
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:15
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:23
auto const amount
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:34
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:132
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:129
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:133
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:144
constexpr std::uint32_t tfClose
Definition TxFlags.h:115
@ txNode
transaction plus metadata
@ tecNO_DST_INSUF_XRP
Definition TER.h:272
error_code_i
Definition ErrorCodes.h:20
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:52
@ rpcLGR_NOT_VALIDATED
Definition ErrorCodes.h:53
@ rpcLGR_IDX_MALFORMED
Definition ErrorCodes.h:93
@ rpcLGR_IDXS_INVALID
Definition ErrorCodes.h:92
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:64
@ rpcACT_MALFORMED
Definition ErrorCodes.h:70
@ rpcINVALID_LGR_RANGE
Definition ErrorCodes.h:116
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
Json::StaticString token
Definition ErrorCodes.h:185
boost::container::flat_set< std::string > modified
NodeSanity(int idx, Json::StaticString const &t, std::initializer_list< char const * > c, std::initializer_list< char const * > d, std::initializer_list< char const * > m)
boost::container::flat_set< std::string > created
boost::container::flat_set< std::string > deleted
T to_string(T... args)