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