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