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