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