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  env(noop(env.master),
394  ticket::use(1),
395  seq(env.seq(env.master)),
396  ter(temMALFORMED));
397 
398  // Close enough ledgers that the previous transactions are no
399  // longer retried.
400  for (int i = 0; i < 8; ++i)
401  env.close();
402 
403  env.enableFeature(featureTicketBatch);
404  env.close();
405  env.require(owners(env.master, 0), tickets(env.master, 0));
406 
407  std::uint32_t ticketSeq{env.seq(env.master) + 1};
408  env(ticket::create(env.master, 2));
410  env.close();
411  env.require(owners(env.master, 2), tickets(env.master, 2));
412 
413  env(noop(env.master), ticket::use(ticketSeq++));
415  env.close();
416  env.require(owners(env.master, 1), tickets(env.master, 1));
417 
418  env(fset(env.master, asfDisableMaster),
419  ticket::use(ticketSeq++),
420  ter(tecNO_ALTERNATIVE_KEY));
422  env.close();
423  env.require(owners(env.master, 0), tickets(env.master, 0));
424  }
425 
426  void
428  {
429  testcase("Create Tickets that fail Preflight");
430 
431  using namespace test::jtx;
432  Env env{*this};
433 
434  Account const master{env.master};
435 
436  // Exercise boundaries on count.
437  env(ticket::create(master, 0), ter(temINVALID_COUNT));
438  env(ticket::create(master, 251), ter(temINVALID_COUNT));
439 
440  // Exercise fees.
441  std::uint32_t const ticketSeq_A{env.seq(master) + 1};
442  env(ticket::create(master, 1), fee(XRP(10)));
444  env.close();
445  env.require(owners(master, 1), tickets(master, 1));
446 
447  env(ticket::create(master, 1), fee(XRP(-1)), ter(temBAD_FEE));
448 
449  // Exercise flags.
450  std::uint32_t const ticketSeq_B{env.seq(master) + 1};
451  env(ticket::create(master, 1), txflags(tfFullyCanonicalSig));
453  env.close();
454  env.require(owners(master, 2), tickets(master, 2));
455 
456  env(ticket::create(master, 1), txflags(tfSell), ter(temINVALID_FLAG));
457  env.close();
458  env.require(owners(master, 2), tickets(master, 2));
459 
460  // We successfully created 1 ticket earlier. Verify that we can
461  // create 250 tickets in one shot. We must consume one ticket first.
462  env(noop(master), ticket::use(ticketSeq_A));
464  env.close();
465  env.require(owners(master, 1), tickets(master, 1));
466 
467  env(ticket::create(master, 250), ticket::use(ticketSeq_B));
469  env.close();
470  env.require(owners(master, 250), tickets(master, 250));
471  }
472 
473  void
475  {
476  testcase("Create Tickets that fail Preclaim");
477 
478  using namespace test::jtx;
479  {
480  // Create tickets on a non-existent account.
481  Env env{*this};
482  Account alice{"alice"};
483  env.memoize(alice);
484 
485  env(ticket::create(alice, 1),
486  json(jss::Sequence, 1),
487  ter(terNO_ACCOUNT));
488  }
489  {
490  // Exceed the threshold where tickets can no longer be
491  // added to an account.
492  Env env{*this};
493  Account alice{"alice"};
494 
495  env.fund(XRP(100000), alice);
496 
497  std::uint32_t ticketSeq{env.seq(alice) + 1};
498  env(ticket::create(alice, 250));
500  env.close();
501  env.require(owners(alice, 250), tickets(alice, 250));
502 
503  // Note that we can add one more ticket while consuming a ticket
504  // because the final result is still 250 tickets.
505  env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
507  env.close();
508  env.require(owners(alice, 250), tickets(alice, 250));
509 
510  // Adding one more ticket will exceed the threshold.
511  env(ticket::create(alice, 2),
512  ticket::use(ticketSeq + 1),
513  ter(tecDIR_FULL));
514  env.close();
515  env.require(owners(alice, 249), tickets(alice, 249));
516 
517  // Now we can successfully add one more ticket.
518  env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
520  env.close();
521  env.require(owners(alice, 250), tickets(alice, 250));
522 
523  // Since we're at 250, we can't add another ticket using a
524  // sequence.
525  env(ticket::create(alice, 1), ter(tecDIR_FULL));
526  env.close();
527  env.require(owners(alice, 250), tickets(alice, 250));
528  }
529  {
530  // Explore exceeding the ticket threshold from another angle.
531  Env env{*this};
532  Account alice{"alice"};
533 
534  env.fund(XRP(100000), alice);
535  env.close();
536 
537  std::uint32_t ticketSeq_AB{env.seq(alice) + 1};
538  env(ticket::create(alice, 2));
540  env.close();
541  env.require(owners(alice, 2), tickets(alice, 2));
542 
543  // Adding 250 tickets (while consuming one) will exceed the
544  // threshold.
545  env(ticket::create(alice, 250),
546  ticket::use(ticketSeq_AB + 0),
547  ter(tecDIR_FULL));
548  env.close();
549  env.require(owners(alice, 1), tickets(alice, 1));
550 
551  // Adding 250 tickets (without consuming one) will exceed the
552  // threshold.
553  env(ticket::create(alice, 250), ter(tecDIR_FULL));
554  env.close();
555  env.require(owners(alice, 1), tickets(alice, 1));
556 
557  // Alice can now add 250 tickets while consuming one.
558  env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
560  env.close();
561  env.require(owners(alice, 250), tickets(alice, 250));
562  }
563  }
564 
565  void
567  {
568  testcase("Create Ticket Insufficient Reserve");
569 
570  using namespace test::jtx;
571  Env env{*this};
572  Account alice{"alice"};
573 
574  // Fund alice not quite enough to make the reserve for a Ticket.
575  env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
576  env.close();
577 
578  env(ticket::create(alice, 1), ter(tecINSUFFICIENT_RESERVE));
579  env.close();
580  env.require(owners(alice, 0), tickets(alice, 0));
581 
582  // Give alice enough to exactly meet the reserve for one Ticket.
583  env(
584  pay(env.master,
585  alice,
586  env.current()->fees().accountReserve(1) - env.balance(alice)));
587  env.close();
588 
589  env(ticket::create(alice, 1));
591  env.close();
592  env.require(owners(alice, 1), tickets(alice, 1));
593 
594  // Give alice not quite enough to make the reserve for a total of
595  // 250 Tickets.
596  env(
597  pay(env.master,
598  alice,
599  env.current()->fees().accountReserve(250) - drops(1) -
600  env.balance(alice)));
601  env.close();
602 
603  // alice doesn't quite have the reserve for a total of 250
604  // Tickets, so the transaction fails.
605  env(ticket::create(alice, 249), ter(tecINSUFFICIENT_RESERVE));
606  env.close();
607  env.require(owners(alice, 1), tickets(alice, 1));
608 
609  // Give alice enough so she can make the reserve for all 250
610  // Tickets.
611  env(pay(
612  env.master,
613  alice,
614  env.current()->fees().accountReserve(250) - env.balance(alice)));
615  env.close();
616 
617  std::uint32_t const ticketSeq{env.seq(alice) + 1};
618  env(ticket::create(alice, 249));
620  env.close();
621  env.require(owners(alice, 250), tickets(alice, 250));
622  BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
623  }
624 
625  void
627  {
628  testcase("Using Tickets");
629 
630  using namespace test::jtx;
631  Env env{*this};
632  Account alice{"alice"};
633 
634  env.fund(XRP(10000), alice);
635  env.close();
636 
637  // Successfully create tickets (using a sequence)
638  std::uint32_t const ticketSeq_AB{env.seq(alice) + 1};
639  env(ticket::create(alice, 2));
641  env.close();
642  env.require(owners(alice, 2), tickets(alice, 2));
643  BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
644 
645  // You can use a ticket to create one ticket ...
646  std::uint32_t const ticketSeq_C{env.seq(alice)};
647  env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
649  env.close();
650  env.require(owners(alice, 2), tickets(alice, 2));
651  BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
652 
653  // ... you can use a ticket to create multiple tickets ...
654  std::uint32_t const ticketSeq_DE{env.seq(alice)};
655  env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
657  env.close();
658  env.require(owners(alice, 3), tickets(alice, 3));
659  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
660 
661  // ... and you can use a ticket for other things.
662  env(noop(alice), ticket::use(ticketSeq_DE + 0));
664  env.close();
665  env.require(owners(alice, 2), tickets(alice, 2));
666  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
667 
668  env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
670  env.close();
671  env.require(owners(alice, 1), tickets(alice, 1));
672  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
673 
674  env(trust(alice, env.master["USD"](20)), ticket::use(ticketSeq_C));
676  env.close();
677  env.require(owners(alice, 1), tickets(alice, 0));
678  BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
679 
680  // Attempt to use a ticket that has already been used.
681  env(noop(alice), ticket::use(ticketSeq_C), ter(tefNO_TICKET));
682  env.close();
683 
684  // Attempt to use a ticket from the future.
685  std::uint32_t const ticketSeq_F{env.seq(alice) + 1};
686  env(noop(alice), ticket::use(ticketSeq_F), ter(terPRE_TICKET));
687  env.close();
688 
689  // Now create the ticket. The retry will consume the new ticket.
690  env(ticket::create(alice, 1));
692  env.close();
693  env.require(owners(alice, 1), tickets(alice, 0));
694  BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
695 
696  // Try a transaction that combines consuming a ticket with
697  // AccountTxnID.
698  std::uint32_t const ticketSeq_G{env.seq(alice) + 1};
699  env(ticket::create(alice, 1));
701  env.close();
702 
703  env(noop(alice),
704  ticket::use(ticketSeq_G),
705  json(R"({"AccountTxnID": "0"})"),
706  ter(temINVALID));
707  env.close();
708  env.require(owners(alice, 2), tickets(alice, 1));
709  }
710 
711  void
713  {
714  // The Transaction database keeps each transaction's sequence number
715  // in an entry (called "FromSeq"). Until the introduction of tickets
716  // each sequence stored for a given account would always be unique.
717  // With the advent of tickets there could be lots of entries
718  // with zero.
719  //
720  // We really don't expect those zeros to cause any problems since
721  // there are no indexes that use "FromSeq". But it still seems
722  // prudent to exercise this a bit to see if tickets cause any obvious
723  // harm.
724  testcase("Transaction Database With Tickets");
725 
726  using namespace test::jtx;
727  Env env{*this};
728  Account alice{"alice"};
729 
730  env.fund(XRP(10000), alice);
731  env.close();
732 
733  // Lambda that returns the hash of the most recent transaction.
734  auto getTxID = [&env, this]() -> uint256 {
735  std::shared_ptr<STTx const> tx{env.tx()};
736  if (!BEAST_EXPECTS(tx, "Transaction not found"))
737  Throw<std::invalid_argument>("Invalid transaction ID");
738 
739  return tx->getTransactionID();
740  };
741 
742  // A note about the metadata created by these transactions.
743  //
744  // We _could_ check the metadata on these transactions. However
745  // checking the metadata has the side effect of advancing the ledger.
746  // So if we check the metadata we don't get to look at several
747  // transactions in the same ledger. Therefore a specific choice was
748  // made to not check the metadata on these transactions.
749 
750  // Successfully create several tickets (using a sequence).
751  std::uint32_t ticketSeq{env.seq(alice)};
752  static constexpr std::uint32_t ticketCount{10};
753  env(ticket::create(alice, ticketCount));
754  uint256 const txHash_1{getTxID()};
755 
756  // Just for grins use the tickets in reverse from largest to smallest.
757  ticketSeq += ticketCount;
758  env(noop(alice), ticket::use(--ticketSeq));
759  uint256 const txHash_2{getTxID()};
760 
761  env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
762  uint256 const txHash_3{getTxID()};
763 
764  env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
765  uint256 const txHash_4{getTxID()};
766 
767  // Close the ledger so we look at transactions from a couple of
768  // different ledgers.
769  env.close();
770 
771  env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
772  uint256 const txHash_5{getTxID()};
773 
774  env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
775  uint256 const txHash_6{getTxID()};
776 
777  env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
778  uint256 const txHash_7{getTxID()};
779 
780  env(noop(alice), ticket::use(--ticketSeq));
781  uint256 const txHash_8{getTxID()};
782 
783  env.close();
784 
785  // Checkout what's in the Transaction database. We go straight
786  // to the database. Most of our interfaces cache transactions
787  // in memory. So if we use normal interfaces we would get the
788  // transactions from memory rather than from the database.
789 
790  // Lambda to verify a transaction pulled from the Transaction database.
791  auto checkTxFromDB = [&env, this](
792  uint256 const& txID,
793  std::uint32_t ledgerSeq,
794  std::uint32_t txSeq,
796  TxType txType) {
797  error_code_i txErrCode{rpcSUCCESS};
798 
799  using TxPair = std::
800  pair<std::shared_ptr<Transaction>, std::shared_ptr<TxMeta>>;
802  Transaction::load(txID, env.app(), txErrCode);
803 
804  BEAST_EXPECT(txErrCode == rpcSUCCESS);
805  if (auto txPtr = std::get_if<TxPair>(&maybeTx))
806  {
807  std::shared_ptr<Transaction>& tx = txPtr->first;
808  BEAST_EXPECT(tx->getLedger() == ledgerSeq);
809  std::shared_ptr<STTx const> const& sttx = tx->getSTransaction();
810  BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
811  if (ticketSeq)
812  BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
813  BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
814  }
815  else
816  {
817  fail("Expected transaction was not found");
818  }
819  };
820 
821  // txID ledgerSeq txSeq ticketSeq txType
822  checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
823  checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
824  checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
825  checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
826 
827  checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
828  checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
829  checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
830  checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
831  }
832 
833  void
835  {
836  // The sign and the submit RPC commands automatically fill in the
837  // Sequence field of a transaction if none is provided. If a
838  // TicketSequence is provided in the transaction, then the
839  // auto-filled Sequence should be zero.
840  testcase("Sign with TicketSequence");
841 
842  using namespace test::jtx;
843  Env env{*this};
844  Account alice{"alice"};
845 
846  env.fund(XRP(10000), alice);
847  env.close();
848 
849  // Successfully create tickets (using a sequence)
850  std::uint32_t const ticketSeq = env.seq(alice) + 1;
851  env(ticket::create(alice, 2));
853  env.close();
854  env.require(owners(alice, 2), tickets(alice, 2));
855  BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
856 
857  {
858  // Test that the "sign" RPC command fills in a "Sequence": 0 field
859  // if none is provided.
860 
861  // Create a noop transaction using a TicketSequence but don't fill
862  // in the Sequence field.
864  tx[jss::tx_json] = noop(alice);
865  tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
866  tx[jss::secret] = toBase58(generateSeed("alice"));
867 
868  // Verify that there is no "Sequence" field.
869  BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
870 
871  // Call the "sign" RPC command and see the "Sequence": 0 field
872  // filled in.
873  Json::Value jr = env.rpc("json", "sign", to_string(tx));
874 
875  // Verify that "sign" inserted a "Sequence": 0 field.
876  if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
878  {
879  BEAST_EXPECT(
880  jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
881  }
882 
883  // "sign" should not have consumed any of alice's tickets.
884  env.close();
885  env.require(owners(alice, 2), tickets(alice, 2));
886 
887  // "submit" the signed blob and see one of alice's tickets consumed.
888  env.rpc("submit", jr[jss::result][jss::tx_blob].asString());
889  env.close();
890  env.require(owners(alice, 1), tickets(alice, 1));
891  }
892  {
893  // Test that the "submit" RPC command fills in a "Sequence": 0
894  // field if none is provided.
895 
896  // Create a noop transaction using a TicketSequence but don't fill
897  // in the Sequence field.
899  tx[jss::tx_json] = noop(alice);
900  tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
901  tx[jss::secret] = toBase58(generateSeed("alice"));
902 
903  // Verify that there is no "Sequence" field.
904  BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
905 
906  // Call the "submit" RPC command and see the "Sequence": 0 field
907  // filled in.
908  Json::Value jr = env.rpc("json", "submit", to_string(tx));
909 
910  // Verify that "submit" inserted a "Sequence": 0 field.
911  if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
913  {
914  BEAST_EXPECT(
915  jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
916  }
917 
918  // "submit" should have consumed the last of alice's tickets.
919  env.close();
920  env.require(owners(alice, 0), tickets(alice, 0));
921  }
922  }
923 
924  void
926  {
927  // It is an error if a transaction contains a non-zero Sequence field
928  // and a TicketSequence field. Verify that the error is detected.
929  testcase("Fix both Seq and Ticket");
930 
931  // Try the test without featureTicketBatch enabled.
932  using namespace test::jtx;
933  {
934  Env env{*this, supported_amendments() - featureTicketBatch};
935  Account alice{"alice"};
936 
937  env.fund(XRP(10000), alice);
938  env.close();
939 
940  // Fail to create a ticket.
941  std::uint32_t const ticketSeq = env.seq(alice) + 1;
942  env(ticket::create(alice, 1), ter(temDISABLED));
943  env.close();
944  env.require(owners(alice, 0), tickets(alice, 0));
945  BEAST_EXPECT(ticketSeq == env.seq(alice) + 1);
946 
947  // Create a transaction that includes both a ticket and a non-zero
948  // sequence number. Since a ticket is used and tickets are not yet
949  // enabled the transaction should be malformed.
950  env(noop(alice),
951  ticket::use(ticketSeq),
952  seq(env.seq(alice)),
953  ter(temMALFORMED));
954  env.close();
955  }
956  // Try the test with featureTicketBatch enabled.
957  {
958  Env env{*this, supported_amendments()};
959  Account alice{"alice"};
960 
961  env.fund(XRP(10000), alice);
962  env.close();
963 
964  // Create a ticket.
965  std::uint32_t const ticketSeq = env.seq(alice) + 1;
966  env(ticket::create(alice, 1));
967  env.close();
968  env.require(owners(alice, 1), tickets(alice, 1));
969  BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
970 
971  // Create a transaction that includes both a ticket and a non-zero
972  // sequence number. The transaction fails with temSEQ_AND_TICKET.
973  env(noop(alice),
974  ticket::use(ticketSeq),
975  seq(env.seq(alice)),
976  ter(temSEQ_AND_TICKET));
977  env.close();
978 
979  // Verify that the transaction failed by looking at alice's
980  // sequence number and tickets.
981  env.require(owners(alice, 1), tickets(alice, 1));
982  BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
983  }
984  }
985 
986 public:
987  void
988  run() override
989  {
998  }
999 };
1000 
1001 BEAST_DEFINE_TESTSUITE(Ticket, tx, ripple);
1002 
1003 } // namespace ripple
ripple::sfOwnerCount
const SF_UINT32 sfOwnerCount
ripple::tefNO_TICKET
@ tefNO_TICKET
Definition: TER.h:167
ripple::terPRE_TICKET
@ terPRE_TICKET
Definition: TER.h:207
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:382
ripple::Ticket_test::testTransactionDatabaseWithTickets
void testTransactionDatabaseWithTickets()
Definition: Ticket_test.cpp:712
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:566
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
ripple::tfFullyCanonicalSig
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition: TxFlags.h:58
ripple::TxType
TxType
Transaction type identifiers.
Definition: TxFormats.h:56
ripple::sfFinalFields
const SField sfFinalFields
ripple::sfTicketSequence
const SF_UINT32 sfTicketSequence
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:136
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
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::ttPAYMENT
@ ttPAYMENT
This transaction type executes a payment.
Definition: TxFormats.h:59
ripple::base_uint< 256 >
ripple::sfTransactionType
const SF_UINT16 sfTransactionType
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
ripple::test::jtx::Env::meta
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:374
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
ripple::Ticket_test::run
void run() override
Definition: Ticket_test.cpp:988
ripple::asfDisableMaster
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:77
ripple::tecNO_ALTERNATIVE_KEY
@ tecNO_ALTERNATIVE_KEY
Definition: TER.h:263
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::sfNewFields
const SField sfNewFields
ripple::JsonOptions::none
@ none
ripple::sfAffectedNodes
const SField sfAffectedNodes
ripple::Ticket_test::testSignWithTicketSequence
void testSignWithTicketSequence()
Definition: Ticket_test.cpp:834
ripple::Ticket_test::testTicketCreatePreflightFail
void testTicketCreatePreflightFail()
Definition: Ticket_test.cpp:427
std::to_string
T to_string(T... args)
ripple::sfTicketCount
const SF_UINT32 sfTicketCount
ripple::sfModifiedNode
const SField sfModifiedNode
ripple::Ticket_test::testUsingTickets
void testUsingTickets()
Definition: Ticket_test.cpp:626
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::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:90
ripple::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:254
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:198
ripple::sfPreviousFields
const SField sfPreviousFields
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple::ttACCOUNT_SET
@ ttACCOUNT_SET
This transaction type adjusts various account settings.
Definition: TxFormats.h:68
ripple::generateSeed
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:69
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:114
ripple::tfSell
constexpr std::uint32_t tfSell
Definition: TxFlags.h:96
ripple::sfTransactionResult
const SF_UINT8 sfTransactionResult
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
ripple::temSEQ_AND_TICKET
@ temSEQ_AND_TICKET
Definition: TER.h:124
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
ripple::ttDEPOSIT_PREAUTH
@ ttDEPOSIT_PREAUTH
This transaction type grants or revokes authorization to transfer funds.
Definition: TxFormats.h:116
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:274
ripple::featureTicketBatch
const uint256 featureTicketBatch
std::optional< std::uint32_t >
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::sfAccount
const SF_ACCOUNT sfAccount
std::vector::end
T end(T... args)
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:85
ripple::temINVALID_COUNT
@ temINVALID_COUNT
Definition: TER.h:119
ripple::Ticket_test::testTicketCreatePreclaimFail
void testTicketCreatePreclaimFail()
Definition: Ticket_test.cpp:474
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::Ticket_test::testFixBothSeqAndTicket
void testFixBothSeqAndTicket()
Definition: Ticket_test.cpp:925
ripple::ttTICKET_CREATE
@ ttTICKET_CREATE
This transaction type creates a new set of tickets.
Definition: TxFormats.h:89
ripple::temINVALID
@ temINVALID
Definition: TER.h:108
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