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  case 3:
141  if (j.isMember(jss::result) &&
142  (j[jss::result][jss::status] == "success") &&
143  (j[jss::result][jss::transactions].size() == 2) &&
144  (j[jss::result][jss::transactions][0u][jss::tx_json]
145  [jss::TransactionType] == jss::AccountSet))
146  {
147  auto const& payment =
148  j[jss::result][jss::transactions][1u];
149 
150  return (payment.isMember(jss::tx_json)) &&
151  (payment[jss::tx_json][jss::TransactionType] ==
152  jss::Payment) &&
153  (payment[jss::tx_json][jss::DeliverMax] ==
154  "10000000010") &&
155  (!payment[jss::tx_json].isMember(jss::Amount)) &&
156  (!payment[jss::tx_json].isMember(jss::hash)) &&
157  (payment[jss::hash] ==
158  "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
159  "ECF0D4CE981D0A8") &&
160  (payment[jss::validated] == true) &&
161  (payment[jss::ledger_index] == 3) &&
162  (payment[jss::ledger_hash] ==
163  "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
164  "580A5AFDD727E33") &&
165  (payment[jss::close_time_iso] ==
166  "2000-01-01T00:00:10Z");
167  }
168  else
169  return false;
170 
171  default:
172  return false;
173  }
174  };
175 
176  auto noTxs = [](Json::Value const& j) {
177  return j.isMember(jss::result) &&
178  (j[jss::result][jss::status] == "success") &&
179  (j[jss::result][jss::transactions].size() == 0);
180  };
181 
182  auto isErr = [](Json::Value const& j, error_code_i code) {
183  return j.isMember(jss::result) &&
184  j[jss::result].isMember(jss::error) &&
185  j[jss::result][jss::error] == RPC::get_error_info(code).token;
186  };
187 
188  Json::Value jParms;
189  jParms[jss::api_version] = apiVersion;
190 
191  BEAST_EXPECT(isErr(
192  env.rpc("json", "account_tx", to_string(jParms)),
194 
195  jParms[jss::account] = "0xDEADBEEF";
196 
197  BEAST_EXPECT(isErr(
198  env.rpc("json", "account_tx", to_string(jParms)),
200 
201  jParms[jss::account] = A1.human();
202  BEAST_EXPECT(hasTxs(
203  env.rpc(apiVersion, "json", "account_tx", to_string(jParms))));
204 
205  // Ledger min/max index
206  {
207  Json::Value p{jParms};
208  p[jss::ledger_index_min] = -1;
209  p[jss::ledger_index_max] = -1;
210  BEAST_EXPECT(hasTxs(
211  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
212 
213  p[jss::ledger_index_min] = 0;
214  p[jss::ledger_index_max] = 100;
215  if (apiVersion < 2u)
216  BEAST_EXPECT(hasTxs(
217  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
218  else
219  BEAST_EXPECT(isErr(
220  env.rpc("json", "account_tx", to_string(p)),
222 
223  p[jss::ledger_index_min] = 1;
224  p[jss::ledger_index_max] = 2;
225  if (apiVersion < 2u)
226  BEAST_EXPECT(
227  noTxs(env.rpc("json", "account_tx", to_string(p))));
228  else
229  BEAST_EXPECT(isErr(
230  env.rpc("json", "account_tx", to_string(p)),
232 
233  p[jss::ledger_index_min] = 2;
234  p[jss::ledger_index_max] = 1;
235  BEAST_EXPECT(isErr(
236  env.rpc("json", "account_tx", to_string(p)),
237  (apiVersion == 1 ? rpcLGR_IDXS_INVALID
239  }
240  // Ledger index min only
241  {
242  Json::Value p{jParms};
243  p[jss::ledger_index_min] = -1;
244  BEAST_EXPECT(hasTxs(
245  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
246 
247  p[jss::ledger_index_min] = 1;
248  if (apiVersion < 2u)
249  BEAST_EXPECT(hasTxs(
250  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
251  else
252  BEAST_EXPECT(isErr(
253  env.rpc("json", "account_tx", to_string(p)),
255 
256  p[jss::ledger_index_min] = env.current()->info().seq;
257  BEAST_EXPECT(isErr(
258  env.rpc("json", "account_tx", to_string(p)),
259  (apiVersion == 1 ? rpcLGR_IDXS_INVALID
261  }
262 
263  // Ledger index max only
264  {
265  Json::Value p{jParms};
266  p[jss::ledger_index_max] = -1;
267  BEAST_EXPECT(hasTxs(
268  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
269 
270  p[jss::ledger_index_max] = env.current()->info().seq;
271  if (apiVersion < 2u)
272  BEAST_EXPECT(hasTxs(
273  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
274  else
275  BEAST_EXPECT(isErr(
276  env.rpc("json", "account_tx", to_string(p)),
278 
279  p[jss::ledger_index_max] = 3;
280  BEAST_EXPECT(hasTxs(
281  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
282 
283  p[jss::ledger_index_max] = env.closed()->info().seq;
284  BEAST_EXPECT(hasTxs(
285  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
286 
287  p[jss::ledger_index_max] = env.closed()->info().seq - 1;
288  BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
289  }
290 
291  // Ledger Sequence
292  {
293  Json::Value p{jParms};
294 
295  p[jss::ledger_index] = env.closed()->info().seq;
296  BEAST_EXPECT(hasTxs(
297  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
298 
299  p[jss::ledger_index] = env.closed()->info().seq - 1;
300  BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
301 
302  p[jss::ledger_index] = env.current()->info().seq;
303  BEAST_EXPECT(isErr(
304  env.rpc("json", "account_tx", to_string(p)),
306 
307  p[jss::ledger_index] = env.current()->info().seq + 1;
308  BEAST_EXPECT(isErr(
309  env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
310  }
311 
312  // Ledger Hash
313  {
314  Json::Value p{jParms};
315 
316  p[jss::ledger_hash] = to_string(env.closed()->info().hash);
317  BEAST_EXPECT(hasTxs(
318  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
319 
320  p[jss::ledger_hash] = to_string(env.closed()->info().parentHash);
321  BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
322  }
323 
324  // Ledger index max/min/index all specified
325  // ERRORS out with invalid Parenthesis
326  {
327  jParms[jss::account] = "0xDEADBEEF";
328  jParms[jss::account] = A1.human();
329  Json::Value p{jParms};
330 
331  p[jss::ledger_index_max] = -1;
332  p[jss::ledger_index_min] = -1;
333  p[jss::ledger_index] = -1;
334 
335  if (apiVersion < 2u)
336  BEAST_EXPECT(hasTxs(
337  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
338  else
339  BEAST_EXPECT(isErr(
340  env.rpc("json", "account_tx", to_string(p)),
342  }
343 
344  // Ledger index max only
345  {
346  Json::Value p{jParms};
347  p[jss::ledger_index_max] = env.current()->info().seq;
348  if (apiVersion < 2u)
349  BEAST_EXPECT(hasTxs(
350  env.rpc(apiVersion, "json", "account_tx", to_string(p))));
351  else
352  BEAST_EXPECT(isErr(
353  env.rpc("json", "account_tx", to_string(p)),
355  }
356  // test binary and forward for bool/non bool values
357  {
358  Json::Value p{jParms};
359  p[jss::binary] = "asdf";
360  if (apiVersion < 2u)
361  {
362  Json::Value result{env.rpc("json", "account_tx", to_string(p))};
363  BEAST_EXPECT(result[jss::result][jss::status] == "success");
364  }
365  else
366  BEAST_EXPECT(isErr(
367  env.rpc("json", "account_tx", to_string(p)),
369 
370  p[jss::binary] = true;
371  Json::Value result{env.rpc("json", "account_tx", to_string(p))};
372  BEAST_EXPECT(result[jss::result][jss::status] == "success");
373 
374  p[jss::forward] = "true";
375  if (apiVersion < 2u)
376  BEAST_EXPECT(result[jss::result][jss::status] == "success");
377  else
378  BEAST_EXPECT(isErr(
379  env.rpc("json", "account_tx", to_string(p)),
381 
382  p[jss::forward] = false;
383  result = env.rpc("json", "account_tx", to_string(p));
384  BEAST_EXPECT(result[jss::result][jss::status] == "success");
385  }
386  }
387 
388  void
390  {
391  // Get results for all transaction types that can be associated
392  // with an account. Start by generating all transaction types.
393  using namespace test::jtx;
394  using namespace std::chrono_literals;
395 
396  Env env(*this);
397  Account const alice{"alice"};
398  Account const alie{"alie"};
399  Account const gw{"gw"};
400  auto const USD{gw["USD"]};
401 
402  env.fund(XRP(1000000), alice, gw);
403  env.close();
404 
405  // AccountSet
406  env(noop(alice));
407 
408  // Payment
409  env(pay(alice, gw, XRP(100)));
410 
411  // Regular key set
412  env(regkey(alice, alie));
413  env.close();
414 
415  // Trust and Offers
416  env(trust(alice, USD(200)), sig(alie));
417  std::uint32_t const offerSeq{env.seq(alice)};
418  env(offer(alice, USD(50), XRP(150)), sig(alie));
419  env.close();
420 
421  env(offer_cancel(alice, offerSeq), sig(alie));
422  env.close();
423 
424  // SignerListSet
425  env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
426 
427  // Escrow
428  {
429  // Create an escrow. Requires either a CancelAfter or FinishAfter.
430  auto escrow = [](Account const& account,
431  Account const& to,
432  STAmount const& amount) {
433  Json::Value escro;
434  escro[jss::TransactionType] = jss::EscrowCreate;
435  escro[jss::Flags] = tfUniversal;
436  escro[jss::Account] = account.human();
437  escro[jss::Destination] = to.human();
438  escro[jss::Amount] = amount.getJson(JsonOptions::none);
439  return escro;
440  };
441 
442  NetClock::time_point const nextTime{env.now() + 2s};
443 
444  Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
445  escrowWithFinish[sfFinishAfter.jsonName] =
446  nextTime.time_since_epoch().count();
447 
448  std::uint32_t const escrowFinishSeq{env.seq(alice)};
449  env(escrowWithFinish, sig(alie));
450 
451  Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
452  escrowWithCancel[sfFinishAfter.jsonName] =
453  nextTime.time_since_epoch().count();
454  escrowWithCancel[sfCancelAfter.jsonName] =
455  nextTime.time_since_epoch().count() + 1;
456 
457  std::uint32_t const escrowCancelSeq{env.seq(alice)};
458  env(escrowWithCancel, sig(alie));
459  env.close();
460 
461  {
462  Json::Value escrowFinish;
463  escrowFinish[jss::TransactionType] = jss::EscrowFinish;
464  escrowFinish[jss::Flags] = tfUniversal;
465  escrowFinish[jss::Account] = alice.human();
466  escrowFinish[sfOwner.jsonName] = alice.human();
467  escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
468  env(escrowFinish, sig(alie));
469  }
470  {
471  Json::Value escrowCancel;
472  escrowCancel[jss::TransactionType] = jss::EscrowCancel;
473  escrowCancel[jss::Flags] = tfUniversal;
474  escrowCancel[jss::Account] = alice.human();
475  escrowCancel[sfOwner.jsonName] = alice.human();
476  escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
477  env(escrowCancel, sig(alie));
478  }
479  env.close();
480  }
481 
482  // PayChan
483  {
484  std::uint32_t payChanSeq{env.seq(alice)};
485  Json::Value payChanCreate;
486  payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
487  payChanCreate[jss::Flags] = tfUniversal;
488  payChanCreate[jss::Account] = alice.human();
489  payChanCreate[jss::Destination] = gw.human();
490  payChanCreate[jss::Amount] =
491  XRP(500).value().getJson(JsonOptions::none);
492  payChanCreate[sfSettleDelay.jsonName] =
493  NetClock::duration{100s}.count();
494  payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
495  env(payChanCreate, sig(alie));
496  env.close();
497 
498  std::string const payChanIndex{
499  strHex(keylet::payChan(alice, gw, payChanSeq).key)};
500 
501  {
502  Json::Value payChanFund;
503  payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
504  payChanFund[jss::Flags] = tfUniversal;
505  payChanFund[jss::Account] = alice.human();
506  payChanFund[sfChannel.jsonName] = payChanIndex;
507  payChanFund[jss::Amount] =
508  XRP(200).value().getJson(JsonOptions::none);
509  env(payChanFund, sig(alie));
510  env.close();
511  }
512  {
513  Json::Value payChanClaim;
514  payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
515  payChanClaim[jss::Flags] = tfClose;
516  payChanClaim[jss::Account] = gw.human();
517  payChanClaim[sfChannel.jsonName] = payChanIndex;
518  payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
519  env(payChanClaim);
520  env.close();
521  }
522  }
523 
524  // Check
525  {
526  auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
527  env(check::create(alice, gw, XRP(300)), sig(alie));
528 
529  auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
530  env(check::create(gw, alice, XRP(200)));
531  env.close();
532 
533  env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
534  env(check::cancel(alice, aliceCheckId), sig(alie));
535  env.close();
536  }
537  {
538  // Deposit preauthorization with a Ticket.
539  std::uint32_t const tktSeq{env.seq(alice) + 1};
540  env(ticket::create(alice, 1), sig(alie));
541  env.close();
542 
543  env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
544  env.close();
545  }
546 
547  // Setup is done. Look at the transactions returned by account_tx.
548  Json::Value params;
549  params[jss::account] = alice.human();
550  params[jss::ledger_index_min] = -1;
551  params[jss::ledger_index_max] = -1;
552 
553  Json::Value const result{
554  env.rpc("json", "account_tx", to_string(params))};
555 
556  BEAST_EXPECT(result[jss::result][jss::status] == "success");
557  BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
558 
559  Json::Value const& txs{result[jss::result][jss::transactions]};
560 
561  // clang-format off
562  // Do a sanity check on each returned transaction. They should
563  // be returned in the reverse order of application to the ledger.
564  static const NodeSanity sanity[]{
565  // txType, created, deleted, modified
566  {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
567  {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
568  {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
569  {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
570  {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
571  {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
572  {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
573  {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
574  {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
575  {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
576  {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
577  {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
578  {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
579  {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
580  {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
581  {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
582  {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
583  {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
584  {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
585  {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
586  {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
587  {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
588  };
589  // clang-format on
590 
591  BEAST_EXPECT(
592  std::size(sanity) == result[jss::result][jss::transactions].size());
593 
594  for (unsigned int index{0}; index < std::size(sanity); ++index)
595  {
596  checkSanity(txs[index], sanity[index]);
597  }
598  }
599 
600  void
602  {
603  // Verify that if an account is resurrected then the account_tx RPC
604  // command still recovers all transactions on that account before
605  // and after resurrection.
606  using namespace test::jtx;
607  using namespace std::chrono_literals;
608 
609  Env env(*this);
610  Account const alice{"alice"};
611  Account const becky{"becky"};
612 
613  env.fund(XRP(10000), alice, becky);
614  env.close();
615 
616  // Verify that becky's account root is present.
617  Keylet const beckyAcctKey{keylet::account(becky.id())};
618  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
619 
620  // becky does an AccountSet .
621  env(noop(becky));
622 
623  // Close enough ledgers to be able to delete becky's account.
624  std::uint32_t const ledgerCount{
625  env.current()->seq() + 257 - env.seq(becky)};
626 
627  for (std::uint32_t i = 0; i < ledgerCount; ++i)
628  env.close();
629 
630  auto const beckyPreDelBalance{env.balance(becky)};
631 
632  auto const acctDelFee{drops(env.current()->fees().increment)};
633  env(acctdelete(becky, alice), fee(acctDelFee));
634  env.close();
635 
636  // Verify that becky's account root is gone.
637  BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
638  env.close();
639 
640  // clang-format off
641  // Do a sanity check on each returned transaction. They should
642  // be returned in the reverse order of application to the ledger.
643  //
644  // Note that the first two transactions in sanity have not occurred
645  // yet. We'll see those after becky's account is resurrected.
646  static const NodeSanity sanity[]
647  {
648  // txType, created, deleted, modified
649 /* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
650 /* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
651 /* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
652 /* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
653 /* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
654 /* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
655  };
656  // clang-format on
657 
658  // Verify that we can recover becky's account_tx information even
659  // after the account is deleted.
660  {
661  Json::Value params;
662  params[jss::account] = becky.human();
663  params[jss::ledger_index_min] = -1;
664  params[jss::ledger_index_max] = -1;
665 
666  Json::Value const result{
667  env.rpc("json", "account_tx", to_string(params))};
668 
669  BEAST_EXPECT(result[jss::result][jss::status] == "success");
670  BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
671 
672  // The first two transactions listed in sanity haven't happened yet.
673  constexpr unsigned int beckyDeletedOffest = 2;
674  BEAST_EXPECT(
675  std::size(sanity) ==
676  result[jss::result][jss::transactions].size() +
677  beckyDeletedOffest);
678 
679  Json::Value const& txs{result[jss::result][jss::transactions]};
680 
681  for (unsigned int index = beckyDeletedOffest;
682  index < std::size(sanity);
683  ++index)
684  {
685  checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
686  }
687  }
688 
689  // All it takes is a large enough XRP payment to resurrect
690  // becky's account. Try too small a payment.
691  env(pay(alice,
692  becky,
693  drops(env.current()->fees().accountReserve(0)) - XRP(1)),
695  env.close();
696 
697  // Actually resurrect becky's account.
698  env(pay(alice, becky, XRP(45)));
699  env.close();
700 
701  // becky's account root should be back.
702  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
703  BEAST_EXPECT(env.balance(becky) == XRP(45));
704 
705  // becky pays alice.
706  env(pay(becky, alice, XRP(20)));
707  env.close();
708 
709  // Setup is done. Look at the transactions returned by account_tx.
710  // Verify that account_tx locates all of becky's transactions.
711  Json::Value params;
712  params[jss::account] = becky.human();
713  params[jss::ledger_index_min] = -1;
714  params[jss::ledger_index_max] = -1;
715 
716  Json::Value const result{
717  env.rpc("json", "account_tx", to_string(params))};
718 
719  BEAST_EXPECT(result[jss::result][jss::status] == "success");
720  BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
721 
722  BEAST_EXPECT(
723  std::size(sanity) == result[jss::result][jss::transactions].size());
724 
725  Json::Value const& txs{result[jss::result][jss::transactions]};
726 
727  for (unsigned int index = 0; index < std::size(sanity); ++index)
728  {
729  checkSanity(txs[index], sanity[index]);
730  }
731  }
732 
733 public:
734  void
735  run() override
736  {
739  testContents();
741  }
742 };
743 BEAST_DEFINE_TESTSUITE(AccountTx, app, ripple);
744 
745 } // namespace test
746 } // 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::test::jtx::Env::rpc
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:711
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:173
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:781
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:389
ripple::rpcLGR_NOT_FOUND
@ rpcLGR_NOT_FOUND
Definition: ErrorCodes.h:72
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:165
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:274
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:735
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:143
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:601
ripple::test::jtx::Env::now
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:264
ripple::keylet::payChan
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:334
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:291
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:311
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::RPC::ErrorInfo::token
Json::StaticString token
Definition: ErrorCodes.h:205
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)