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