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