rippled
Ticket_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2016 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/app/misc/Transaction.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/jss.h>
23 #include <test/jtx.h>
24 
25 namespace ripple {
26 
27 class Ticket_test : public beast::unit_test::suite
28 {
32  void
34  {
35  using namespace std::string_literals;
36 
37  Json::Value const& tx{env.tx()->getJson(JsonOptions::none)};
38  {
39  std::string const txType =
40  tx[sfTransactionType.jsonName].asString();
41 
42  if (!BEAST_EXPECTS(
43  txType == jss::TicketCreate,
44  "Unexpected TransactionType: "s + txType))
45  return;
46  }
47 
48  std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
49  if (!BEAST_EXPECTS(
50  count >= 1,
51  "Unexpected ticket count: "s + std::to_string(count)))
52  return;
53 
54  std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
55  std::string const account = tx[sfAccount.jsonName].asString();
56 
57  Json::Value const& metadata = env.meta()->getJson(JsonOptions::none);
58  if (!BEAST_EXPECTS(
61  "tesSUCCESS",
62  "Not metadata for successful TicketCreate."))
63  return;
64 
65  BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
66  BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
67 
68  bool directoryChanged = false;
69  std::uint32_t acctRootFinalSeq = {0};
70  std::vector<std::uint32_t> ticketSeqs;
71  ticketSeqs.reserve(count);
72  for (Json::Value const& node : metadata[sfAffectedNodes.jsonName])
73  {
74  if (node.isMember(sfModifiedNode.jsonName))
75  {
76  Json::Value const& modified = node[sfModifiedNode.jsonName];
77  std::string const entryType =
79  if (entryType == jss::AccountRoot)
80  {
81  auto const& previousFields =
82  modified[sfPreviousFields.jsonName];
83  auto const& finalFields = modified[sfFinalFields.jsonName];
84  {
85  // Verify the account root Sequence did the right thing.
86  std::uint32_t const prevSeq =
87  previousFields[sfSequence.jsonName].asUInt();
88 
89  acctRootFinalSeq =
90  finalFields[sfSequence.jsonName].asUInt();
91 
92  if (txSeq == 0)
93  {
94  // Transaction used a TicketSequence.
95  BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
96  }
97  else
98  {
99  // Transaction used a (plain) Sequence.
100  BEAST_EXPECT(prevSeq == txSeq);
101  BEAST_EXPECT(
102  acctRootFinalSeq == prevSeq + count + 1);
103  }
104  }
105 
106  std::uint32_t const consumedTickets = {
107  txSeq == 0u ? 1u : 0u};
108 
109  // If...
110  // 1. The TicketCount is 1 and
111  // 2. A ticket was consumed by the ticket create, then
112  // 3. The final TicketCount did not change, so the
113  // previous TicketCount is not reported.
114  // But, since the count did not change, we know it equals
115  // the final Ticket count.
116  bool const unreportedPrevTicketCount = {
117  count == 1 && txSeq == 0};
118 
119  // Verify the OwnerCount did the right thing
120  if (unreportedPrevTicketCount)
121  {
122  // The number of Tickets should not have changed, so
123  // the previous OwnerCount should not be reported.
124  BEAST_EXPECT(
125  !previousFields.isMember(sfOwnerCount.jsonName));
126  }
127  else
128  {
129  // Verify the OwnerCount did the right thing.
130  std::uint32_t const prevCount = {
131  previousFields[sfOwnerCount.jsonName].asUInt()};
132 
133  std::uint32_t const finalCount = {
134  finalFields[sfOwnerCount.jsonName].asUInt()};
135 
136  BEAST_EXPECT(
137  prevCount + count - consumedTickets == finalCount);
138  }
139 
140  // Verify TicketCount metadata.
141  BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
142 
143  if (unreportedPrevTicketCount)
144  {
145  // The number of Tickets should not have changed, so
146  // the previous TicketCount should not be reported.
147  BEAST_EXPECT(
148  !previousFields.isMember(sfTicketCount.jsonName));
149  }
150  else
151  {
152  // If the TicketCount was previously present it
153  // should have been greater than zero.
154  std::uint32_t const startCount = {
155  previousFields.isMember(sfTicketCount.jsonName)
156  ? previousFields[sfTicketCount.jsonName]
157  .asUInt()
158  : 0u};
159 
160  BEAST_EXPECT(
161  (startCount == 0u) ^
162  previousFields.isMember(sfTicketCount.jsonName));
163 
164  BEAST_EXPECT(
165  startCount + count - consumedTickets ==
166  finalFields[sfTicketCount.jsonName]);
167  }
168  }
169  else if (entryType == jss::DirectoryNode)
170  {
171  directoryChanged = true;
172  }
173  else
174  {
175  fail(
176  "Unexpected modified node: "s + entryType,
177  __FILE__,
178  __LINE__);
179  }
180  }
181  else if (node.isMember(sfCreatedNode.jsonName))
182  {
183  Json::Value const& created = node[sfCreatedNode.jsonName];
184  std::string const entryType =
186  if (entryType == jss::Ticket)
187  {
188  auto const& newFields = created[sfNewFields.jsonName];
189 
190  BEAST_EXPECT(
191  newFields[sfAccount.jsonName].asString() == account);
192  ticketSeqs.push_back(
193  newFields[sfTicketSequence.jsonName].asUInt());
194  }
195  else if (entryType == jss::DirectoryNode)
196  {
197  directoryChanged = true;
198  }
199  else
200  {
201  fail(
202  "Unexpected created node: "s + entryType,
203  __FILE__,
204  __LINE__);
205  }
206  }
207  else if (node.isMember(sfDeletedNode.jsonName))
208  {
209  Json::Value const& deleted = node[sfDeletedNode.jsonName];
210  std::string const entryType =
212 
213  if (entryType == jss::Ticket)
214  {
215  // Verify the transaction's Sequence == 0.
216  BEAST_EXPECT(txSeq == 0);
217 
218  // Verify the account of the deleted ticket.
219  auto const& finalFields = deleted[sfFinalFields.jsonName];
220  BEAST_EXPECT(
221  finalFields[sfAccount.jsonName].asString() == account);
222 
223  // Verify the deleted ticket has the right TicketSequence.
224  BEAST_EXPECT(
225  finalFields[sfTicketSequence.jsonName].asUInt() ==
226  tx[sfTicketSequence.jsonName].asUInt());
227  }
228  }
229  else
230  {
231  fail(
232  "Unexpected node type in TicketCreate metadata.",
233  __FILE__,
234  __LINE__);
235  }
236  }
237  BEAST_EXPECT(directoryChanged);
238 
239  // Verify that all the expected Tickets were created.
240  BEAST_EXPECT(ticketSeqs.size() == count);
241  std::sort(ticketSeqs.begin(), ticketSeqs.end());
242  BEAST_EXPECT(
243  std::adjacent_find(ticketSeqs.begin(), ticketSeqs.end()) ==
244  ticketSeqs.end());
245  BEAST_EXPECT(*ticketSeqs.rbegin() == acctRootFinalSeq - 1);
246  }
247 
253  void
255  {
256  Json::Value const& tx{env.tx()->getJson(JsonOptions::none)};
257 
258  // Verify that the transaction includes a TicketSequence.
259 
260  // Capture that TicketSequence.
261  // Capture the Account from the transaction
262 
263  // Verify that metadata indicates a tec or a tesSUCCESS.
264 
265  // Walk affected nodes:
266  //
267  // For each deleted node, see if it is a Ticket node. If it is
268  // a Ticket Node being deleted, then assert that the...
269  //
270  // Account == the transaction Account &&
271  // TicketSequence == the transaction TicketSequence
272  //
273  // If a modified node is an AccountRoot, see if it is the transaction
274  // Account. If it is then verify the TicketCount decreased by one.
275  // If the old TicketCount was 1, then the TicketCount field should be
276  // removed from the final fields of the AccountRoot.
277  //
278  // After looking at all nodes verify that exactly one Ticket node
279  // was deleted.
280  BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
281  std::string const account{tx[sfAccount.jsonName].asString()};
282  if (!BEAST_EXPECTS(
283  tx.isMember(sfTicketSequence.jsonName),
284  "Not metadata for a ticket consuming transaction."))
285  return;
286 
287  std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
288 
289  Json::Value const& metadata{env.meta()->getJson(JsonOptions::none)};
290  if (!BEAST_EXPECTS(
291  metadata.isMember(sfTransactionResult.jsonName),
292  "Metadata is missing TransactionResult."))
293  return;
294 
295  {
296  std::string const transactionResult{
297  metadata[sfTransactionResult.jsonName].asString()};
298  if (!BEAST_EXPECTS(
299  transactionResult == "tesSUCCESS" ||
300  transactionResult.compare(0, 3, "tec") == 0,
301  transactionResult + " neither tesSUCCESS nor tec"))
302  return;
303  }
304 
305  BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
306  BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
307 
308  bool acctRootFound{false};
309  std::uint32_t acctRootSeq{0};
310  int ticketsRemoved{0};
311  for (Json::Value const& node : metadata[sfAffectedNodes.jsonName])
312  {
313  if (node.isMember(sfModifiedNode.jsonName))
314  {
315  Json::Value const& modified{node[sfModifiedNode.jsonName]};
316  std::string const entryType =
317  modified[sfLedgerEntryType.jsonName].asString();
318  if (entryType == "AccountRoot" &&
320  .asString() == account)
321  {
322  acctRootFound = true;
323 
324  auto const& previousFields =
325  modified[sfPreviousFields.jsonName];
326  auto const& finalFields = modified[sfFinalFields.jsonName];
327 
328  acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
329 
330  // Check that the TicketCount was present and decremented
331  // by 1. If it decremented to zero, then the field should
332  // be gone.
333  if (!BEAST_EXPECTS(
334  previousFields.isMember(sfTicketCount.jsonName),
335  "AccountRoot previous is missing TicketCount"))
336  return;
337 
338  std::uint32_t const prevTicketCount =
339  previousFields[sfTicketCount.jsonName].asUInt();
340 
341  BEAST_EXPECT(prevTicketCount > 0);
342  if (prevTicketCount == 1)
343  BEAST_EXPECT(
344  !finalFields.isMember(sfTicketCount.jsonName));
345  else
346  BEAST_EXPECT(
347  finalFields.isMember(sfTicketCount.jsonName) &&
348  finalFields[sfTicketCount.jsonName].asUInt() ==
349  prevTicketCount - 1);
350  }
351  }
352  else if (node.isMember(sfDeletedNode.jsonName))
353  {
354  Json::Value const& deleted{node[sfDeletedNode.jsonName]};
355  std::string const entryType{
356  deleted[sfLedgerEntryType.jsonName].asString()};
357 
358  if (entryType == jss::Ticket)
359  {
360  // Verify the account of the deleted ticket.
361  BEAST_EXPECT(
363  .asString() == account);
364 
365  // Verify the deleted ticket has the right TicketSequence.
366  BEAST_EXPECT(
367  deleted[sfFinalFields.jsonName]
369  .asUInt() == ticketSeq);
370 
371  ++ticketsRemoved;
372  }
373  }
374  }
375  BEAST_EXPECT(acctRootFound);
376  BEAST_EXPECT(ticketsRemoved == 1);
377  BEAST_EXPECT(ticketSeq < acctRootSeq);
378  }
379 
380  void
382  {
383  testcase("Feature Not Enabled");
384 
385  using namespace test::jtx;
386  Env env{*this, supported_amendments() - featureTicketBatch};
387 
388  env(ticket::create(env.master, 1), ter(temDISABLED));
389  env.close();
390  env.require(owners(env.master, 0), tickets(env.master, 0));
391 
392  env(noop(env.master), ticket::use(1), ter(temMALFORMED));
393 
394  // Close enough ledgers that the previous transactions are no
395  // longer retried.
396  for (int i = 0; i < 8; ++i)
397  env.close();
398 
399  env.enableFeature(featureTicketBatch);
400  env.close();
401  env.require(owners(env.master, 0), tickets(env.master, 0));
402 
403  std::uint32_t ticketSeq{env.seq(env.master) + 1};
404  env(ticket::create(env.master, 2));
406  env.close();
407  env.require(owners(env.master, 2), tickets(env.master, 2));
408 
409  env(noop(env.master), ticket::use(ticketSeq++));
411  env.close();
412  env.require(owners(env.master, 1), tickets(env.master, 1));
413 
414  env(fset(env.master, asfDisableMaster),
415  ticket::use(ticketSeq++),
416  ter(tecNO_ALTERNATIVE_KEY));
418  env.close();
419  env.require(owners(env.master, 0), tickets(env.master, 0));
420  }
421 
422  void
424  {
425  testcase("Create Tickets that fail Preflight");
426 
427  using namespace test::jtx;
428  Env env{*this};
429 
430  Account const master{env.master};
431 
432  // Exercise boundaries on count.
433  env(ticket::create(master, 0), ter(temINVALID_COUNT));
434  env(ticket::create(master, 251), ter(temINVALID_COUNT));
435 
436  // Exercise fees.
437  std::uint32_t const ticketSeq_A{env.seq(master) + 1};
438  env(ticket::create(master, 1), fee(XRP(10)));
440  env.close();
441  env.require(owners(master, 1), tickets(master, 1));
442 
443  env(ticket::create(master, 1), fee(XRP(-1)), ter(temBAD_FEE));
444 
445  // Exercise flags.
446  std::uint32_t const ticketSeq_B{env.seq(master) + 1};
447  env(ticket::create(master, 1), txflags(tfFullyCanonicalSig));
449  env.close();
450  env.require(owners(master, 2), tickets(master, 2));
451 
452  env(ticket::create(master, 1), txflags(tfSell), ter(temINVALID_FLAG));
453  env.close();
454  env.require(owners(master, 2), tickets(master, 2));
455 
456  // We successfully created 1 ticket earlier. Verify that we can
457  // create 250 tickets in one shot. We must consume one ticket first.
458  env(noop(master), ticket::use(ticketSeq_A));
460  env.close();
461  env.require(owners(master, 1), tickets(master, 1));
462 
463  env(ticket::create(master, 250), ticket::use(ticketSeq_B));
465  env.close();
466  env.require(owners(master, 250), tickets(master, 250));
467  }
468 
469  void
471  {
472  testcase("Create Tickets that fail Preclaim");
473 
474  using namespace test::jtx;
475  {
476  // Create tickets on a non-existent account.
477  Env env{*this};
478  Account alice{"alice"};
479  env.memoize(alice);
480 
481  env(ticket::create(alice, 1),
482  json(jss::Sequence, 1),
483  ter(terNO_ACCOUNT));
484  }
485  {
486  // Exceed the threshold where tickets can no longer be
487  // added to an account.
488  Env env{*this};
489  Account alice{"alice"};
490 
491  env.fund(XRP(100000), alice);
492 
493  std::uint32_t ticketSeq{env.seq(alice) + 1};
494  env(ticket::create(alice, 250));
496  env.close();
497  env.require(owners(alice, 250), tickets(alice, 250));
498 
499  // Note that we can add one more ticket while consuming a ticket
500  // because the final result is still 250 tickets.
501  env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
503  env.close();
504  env.require(owners(alice, 250), tickets(alice, 250));
505 
506  // Adding one more ticket will exceed the threshold.
507  env(ticket::create(alice, 2),
508  ticket::use(ticketSeq + 1),
509  ter(tecDIR_FULL));
510  env.close();
511  env.require(owners(alice, 249), tickets(alice, 249));
512 
513  // Now we can successfully add one more ticket.
514  env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
516  env.close();
517  env.require(owners(alice, 250), tickets(alice, 250));
518 
519  // Since we're at 250, we can't add another ticket using a
520  // sequence.
521  env(ticket::create(alice, 1), ter(tecDIR_FULL));
522  env.close();
523  env.require(owners(alice, 250), tickets(alice, 250));
524  }
525  {
526  // Explore exceeding the ticket threshold from another angle.
527  Env env{*this};
528  Account alice{"alice"};
529 
530  env.fund(XRP(100000), alice);
531  env.close();
532 
533  std::uint32_t ticketSeq_AB{env.seq(alice) + 1};
534  env(ticket::create(alice, 2));
536  env.close();
537  env.require(owners(alice, 2), tickets(alice, 2));
538 
539  // Adding 250 tickets (while consuming one) will exceed the
540  // threshold.
541  env(ticket::create(alice, 250),
542  ticket::use(ticketSeq_AB + 0),
543  ter(tecDIR_FULL));
544  env.close();
545  env.require(owners(alice, 1), tickets(alice, 1));
546 
547  // Adding 250 tickets (without consuming one) will exceed the
548  // threshold.
549  env(ticket::create(alice, 250), ter(tecDIR_FULL));
550  env.close();
551  env.require(owners(alice, 1), tickets(alice, 1));
552 
553  // Alice can now add 250 tickets while consuming one.
554  env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
556  env.close();
557  env.require(owners(alice, 250), tickets(alice, 250));
558  }
559  }
560 
561  void
563  {
564  testcase("Create Ticket Insufficient Reserve");
565 
566  using namespace test::jtx;
567  Env env{*this};
568  Account alice{"alice"};
569 
570  // Fund alice not quite enough to make the reserve for a Ticket.
571  env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
572  env.close();
573 
574  env(ticket::create(alice, 1), ter(tecINSUFFICIENT_RESERVE));
575  env.close();
576  env.require(owners(alice, 0), tickets(alice, 0));
577 
578  // Give alice enough to exactly meet the reserve for one Ticket.
579  env(
580  pay(env.master,
581  alice,
582  env.current()->fees().accountReserve(1) - env.balance(alice)));
583  env.close();
584 
585  env(ticket::create(alice, 1));
587  env.close();
588  env.require(owners(alice, 1), tickets(alice, 1));
589 
590  // Give alice not quite enough to make the reserve for a total of
591  // 250 Tickets.
592  env(
593  pay(env.master,
594  alice,
595  env.current()->fees().accountReserve(250) - drops(1) -
596  env.balance(alice)));
597  env.close();
598 
599  // alice doesn't quite have the reserve for a total of 250
600  // Tickets, so the transaction fails.
601  env(ticket::create(alice, 249), ter(tecINSUFFICIENT_RESERVE));
602  env.close();
603  env.require(owners(alice, 1), tickets(alice, 1));
604 
605  // Give alice enough so she can make the reserve for all 250
606  // Tickets.
607  env(pay(
608  env.master,
609  alice,
610  env.current()->fees().accountReserve(250) - env.balance(alice)));
611  env.close();
612 
613  std::uint32_t const ticketSeq{env.seq(alice) + 1};
614  env(ticket::create(alice, 249));
616  env.close();
617  env.require(owners(alice, 250), tickets(alice, 250));
618  BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
619  }
620 
621  void
623  {
624  testcase("Using Tickets");
625 
626  using namespace test::jtx;
627  Env env{*this};
628  Account alice{"alice"};
629 
630  env.fund(XRP(10000), alice);
631  env.close();
632 
633  // Successfully create tickets (using a sequence)
634  std::uint32_t const ticketSeq_AB{env.seq(alice) + 1};
635  env(ticket::create(alice, 2));
637  env.close();
638  env.require(owners(alice, 2), tickets(alice, 2));
639  BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
640 
641  // You can use a ticket to create one ticket ...
642  std::uint32_t const ticketSeq_C{env.seq(alice)};
643  env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
645  env.close();
646  env.require(owners(alice, 2), tickets(alice, 2));
647  BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
648 
649  // ... you can use a ticket to create multiple tickets ...
650  std::uint32_t const ticketSeq_DE{env.seq(alice)};
651  env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
653  env.close();
654  env.require(owners(alice, 3), tickets(alice, 3));
655  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
656 
657  // ... and you can use a ticket for other things.
658  env(noop(alice), ticket::use(ticketSeq_DE + 0));
660  env.close();
661  env.require(owners(alice, 2), tickets(alice, 2));
662  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
663 
664  env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
666  env.close();
667  env.require(owners(alice, 1), tickets(alice, 1));
668  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
669 
670  env(trust(alice, env.master["USD"](20)), ticket::use(ticketSeq_C));
672  env.close();
673  env.require(owners(alice, 1), tickets(alice, 0));
674  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
675 
676  // Attempt to use a ticket that has already been used.
677  env(noop(alice), ticket::use(ticketSeq_C), ter(tefNO_TICKET));
678  env.close();
679 
680  // Attempt to use a ticket from the future.
681  std::uint32_t const ticketSeq_F{env.seq(alice) + 1};
682  env(noop(alice), ticket::use(ticketSeq_F), ter(terPRE_TICKET));
683  env.close();
684 
685  // Now create the ticket. The retry will consume the new ticket.
686  env(ticket::create(alice, 1));
688  env.close();
689  env.require(owners(alice, 1), tickets(alice, 0));
690  BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
691 
692  // Try a transaction that combines consuming a ticket with
693  // AccountTxnID.
694  std::uint32_t const ticketSeq_G{env.seq(alice) + 1};
695  env(ticket::create(alice, 1));
697  env.close();
698 
699  env(noop(alice),
700  ticket::use(ticketSeq_G),
701  json(R"({"AccountTxnID": "0"})"),
702  ter(temINVALID));
703  env.close();
704  env.require(owners(alice, 2), tickets(alice, 1));
705  }
706 
707  void
709  {
710  // The Transaction database keeps each transaction's sequence number
711  // in an entry (called "FromSeq"). Until the introduction of tickets
712  // each sequence stored for a given account would always be unique.
713  // With the advent of tickets there could be lots of entries
714  // with zero.
715  //
716  // We really don't expect those zeros to cause any problems since
717  // there are no indexes that use "FromSeq". But it still seems
718  // prudent to exercise this a bit to see if tickets cause any obvious
719  // harm.
720  testcase("Transaction Database With Tickets");
721 
722  using namespace test::jtx;
723  Env env{*this};
724  Account alice{"alice"};
725 
726  env.fund(XRP(10000), alice);
727  env.close();
728 
729  // Lambda that returns the hash of the most recent transaction.
730  auto getTxID = [&env, this]() -> uint256 {
731  std::shared_ptr<STTx const> tx{env.tx()};
732  if (!BEAST_EXPECTS(tx, "Transaction not found"))
733  Throw<std::invalid_argument>("Invalid transaction ID");
734 
735  return tx->getTransactionID();
736  };
737 
738  // A note about the metadata created by these transactions.
739  //
740  // We _could_ check the metadata on these transactions. However
741  // checking the metadata has the side effect of advancing the ledger.
742  // So if we check the metadata we don't get to look at several
743  // transactions in the same ledger. Therefore a specific choice was
744  // made to not check the metadata on these transactions.
745 
746  // Successfully create several tickets (using a sequence).
747  std::uint32_t ticketSeq{env.seq(alice)};
748  static constexpr std::uint32_t ticketCount{10};
749  env(ticket::create(alice, ticketCount));
750  uint256 const txHash_1{getTxID()};
751 
752  // Just for grins use the tickets in reverse from largest to smallest.
753  ticketSeq += ticketCount;
754  env(noop(alice), ticket::use(--ticketSeq));
755  uint256 const txHash_2{getTxID()};
756 
757  env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
758  uint256 const txHash_3{getTxID()};
759 
760  env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
761  uint256 const txHash_4{getTxID()};
762 
763  // Close the ledger so we look at transactions from a couple of
764  // different ledgers.
765  env.close();
766 
767  env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
768  uint256 const txHash_5{getTxID()};
769 
770  env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
771  uint256 const txHash_6{getTxID()};
772 
773  env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
774  uint256 const txHash_7{getTxID()};
775 
776  env(noop(alice), ticket::use(--ticketSeq));
777  uint256 const txHash_8{getTxID()};
778 
779  env.close();
780 
781  // Checkout what's in the Transaction database. We go straight
782  // to the database. Most of our interfaces cache transactions
783  // in memory. So if we use normal interfaces we would get the
784  // transactions from memory rather than from the database.
785 
786  // Lambda to verify a transaction pulled from the Transaction database.
787  auto checkTxFromDB = [&env, this](
788  uint256 const& txID,
789  std::uint32_t ledgerSeq,
790  std::uint32_t txSeq,
791  boost::optional<std::uint32_t> ticketSeq,
792  TxType txType) {
793  error_code_i txErrCode{rpcSUCCESS};
794 
795  using TxPair = std::
796  pair<std::shared_ptr<Transaction>, std::shared_ptr<TxMeta>>;
798  Transaction::load(txID, env.app(), txErrCode);
799 
800  BEAST_EXPECT(txErrCode == rpcSUCCESS);
801  if (auto txPtr = std::get_if<TxPair>(&maybeTx))
802  {
803  std::shared_ptr<Transaction>& tx = txPtr->first;
804  BEAST_EXPECT(tx->getLedger() == ledgerSeq);
805  std::shared_ptr<STTx const> const& sttx = tx->getSTransaction();
806  BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
807  if (ticketSeq)
808  BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
809  BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
810  }
811  else
812  {
813  fail("Expected transaction was not found");
814  }
815  };
816 
817  // txID ledgerSeq txSeq ticketSeq txType
818  checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
819  checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
820  checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
821  checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
822 
823  checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
824  checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
825  checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
826  checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
827  }
828 
829 public:
830  void
831  run() override
832  {
839  }
840 };
841 
842 BEAST_DEFINE_TESTSUITE(Ticket, tx, ripple);
843 
844 } // namespace ripple
ripple::sfOwnerCount
const SF_UINT32 sfOwnerCount
ripple::tefNO_TICKET
@ tefNO_TICKET
Definition: TER.h:162
ripple::terPRE_TICKET
@ terPRE_TICKET
Definition: TER.h:201
std::string
STL class.
std::shared_ptr
STL class.
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:372
ripple::Ticket_test::testTransactionDatabaseWithTickets
void testTransactionDatabaseWithTickets()
Definition: Ticket_test.cpp:708
std::vector::reserve
T reserve(T... args)
ripple::sfSequence
const SF_UINT32 sfSequence
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::Ticket_test::checkTicketCreateMeta
void checkTicketCreateMeta(test::jtx::Env &env)
Validate metadata for a successful CreateTicket transaction.
Definition: Ticket_test.cpp:33
ripple::Ticket_test::testTicketInsufficientReserve
void testTicketInsufficientReserve()
Definition: Ticket_test.cpp:562
ripple::sfFinalFields
const SField sfFinalFields
ripple::sfTicketSequence
const SF_UINT32 sfTicketSequence
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:133
ripple::sfDeletedNode
const SField sfDeletedNode
ripple::Ticket_test::testTicketNotEnabled
void testTicketNotEnabled()
Definition: Ticket_test.cpp:381
std::sort
T sort(T... args)
ripple::error_code_i
error_code_i
Definition: ErrorCodes.h:40
ripple::ttPAYMENT
@ ttPAYMENT
Definition: TxFormats.h:36
std::vector::push_back
T push_back(T... args)
ripple::Ticket_test::checkTicketConsumeMeta
void checkTicketConsumeMeta(test::jtx::Env &env)
Validate metadata for a ticket using transaction.
Definition: Ticket_test.cpp:254
ripple::base_uint< 256 >
ripple::sfTransactionType
const SF_UINT16 sfTransactionType
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:106
ripple::test::jtx::Env::meta
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:364
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
ripple::Ticket_test::run
void run() override
Definition: Ticket_test.cpp:831
ripple::tecNO_ALTERNATIVE_KEY
@ tecNO_ALTERNATIVE_KEY
Definition: TER.h:257
ripple::sfNewFields
const SField sfNewFields
ripple::JsonOptions::none
@ none
ripple::sfAffectedNodes
const SField sfAffectedNodes
ripple::Ticket_test::testTicketCreatePreflightFail
void testTicketCreatePreflightFail()
Definition: Ticket_test.cpp:423
std::to_string
T to_string(T... args)
ripple::tfFullyCanonicalSig
const std::uint32_t tfFullyCanonicalSig
Definition: TxFlags.h:48
ripple::sfTicketCount
const SF_UINT32 sfTicketCount
ripple::sfModifiedNode
const SField sfModifiedNode
ripple::Ticket_test::testUsingTickets
void testUsingTickets()
Definition: Ticket_test.cpp:622
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::ttTICKET_CREATE
@ ttTICKET_CREATE
Definition: TxFormats.h:46
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:87
ripple::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:248
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:192
ripple::sfPreviousFields
const SField sfPreviousFields
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple::asfDisableMaster
const std::uint32_t asfDisableMaster
Definition: TxFlags.h:68
ripple::TxType
TxType
Transaction type identifiers.
Definition: TxFormats.h:33
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Transaction::load
static std::variant< std::pair< std::shared_ptr< Transaction >, std::shared_ptr< TxMeta > >, TxSearched > load(uint256 const &id, Application &app, error_code_i &ec)
Definition: Transaction.cpp:111
ripple::tfSell
const std::uint32_t tfSell
Definition: TxFlags.h:79
ripple::sfTransactionResult
const SF_UINT8 sfTransactionResult
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:109
ripple::ttACCOUNT_SET
@ ttACCOUNT_SET
Definition: TxFormats.h:39
std::vector::begin
T begin(T... args)
ripple::Ticket_test
Definition: Ticket_test.cpp:27
ripple::sfCreatedNode
const SField sfCreatedNode
std::adjacent_find
T adjacent_find(T... args)
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:268
ripple::featureTicketBatch
const uint256 featureTicketBatch
Definition: Feature.cpp:189
ripple::sfAccount
const SF_ACCOUNT sfAccount
std::vector::end
T end(T... args)
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:82
ripple::temINVALID_COUNT
@ temINVALID_COUNT
Definition: TER.h:116
ripple::Ticket_test::testTicketCreatePreclaimFail
void testTicketCreatePreclaimFail()
Definition: Ticket_test.cpp:470
ripple::ttDEPOSIT_PREAUTH
@ ttDEPOSIT_PREAUTH
Definition: TxFormats.h:55
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
ripple::temINVALID
@ temINVALID
Definition: TER.h:105
std::vector::rbegin
T rbegin(T... args)
Json::Value
Represents a JSON value.
Definition: json_value.h:145
std::variant
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469