rippled
Loading...
Searching...
No Matches
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 <test/jtx.h>
21#include <xrpld/app/misc/Transaction.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/jss.h>
24
25namespace ripple {
26
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(
59 metadata.isMember(sfTransactionResult.jsonName) &&
60 metadata[sfTransactionResult.jsonName].asString() ==
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};
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 =
78 modified[sfLedgerEntryType.jsonName].asString();
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 =
185 created[sfLedgerEntryType.jsonName].asString();
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 =
211 deleted[sfLedgerEntryType.jsonName].asString();
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" &&
319 modified[sfFinalFields.jsonName][sfAccount.jsonName]
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(
362 deleted[sfFinalFields.jsonName][sfAccount.jsonName]
363 .asString() == account);
364
365 // Verify the deleted ticket has the right TicketSequence.
366 BEAST_EXPECT(
367 deleted[sfFinalFields.jsonName]
368 [sfTicketSequence.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++),
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(
877 sfSequence.jsonName)))
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(
912 sfSequence.jsonName)))
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
986public:
987 void
988 run() override
989 {
998 }
999};
1000
1001BEAST_DEFINE_TESTSUITE(Ticket, tx, ripple);
1002
1003} // namespace ripple
T adjacent_find(T... args)
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:148
bool isArray() const
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
void testFixBothSeqAndTicket()
void testTicketInsufficientReserve()
void testTransactionDatabaseWithTickets()
void testSignWithTicketSequence()
void checkTicketCreateMeta(test::jtx::Env &env)
Validate metadata for a successful CreateTicket transaction.
Definition: Ticket_test.cpp:33
void testTicketCreatePreclaimFail()
void testTicketCreatePreflightFail()
void checkTicketConsumeMeta(test::jtx::Env &env)
Validate metadata for a ticket using transaction.
void run() override
Runs the suite.
static std::variant< std::pair< std::shared_ptr< Transaction >, std::shared_ptr< TxMeta > >, TxSearched > load(uint256 const &id, Application &app, error_code_i &ec)
A transaction testing environment.
Definition: Env.h:118
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:453
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:445
T end(T... args)
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
TxType
Transaction type identifiers.
Definition: TxFormats.h:57
error_code_i
Definition: ErrorCodes.h:40
@ rpcSUCCESS
Definition: ErrorCodes.h:44
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:79
@ tefNO_TICKET
Definition: TER.h:185
@ tecDIR_FULL
Definition: TER.h:274
@ tecNO_ALTERNATIVE_KEY
Definition: TER.h:283
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition: TxFlags.h:60
constexpr std::uint32_t tfSell
Definition: TxFlags.h:99
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:76
@ terNO_ACCOUNT
Definition: TER.h:217
@ terPRE_TICKET
Definition: TER.h:226
@ temBAD_FEE
Definition: TER.h:92
@ temSEQ_AND_TICKET
Definition: TER.h:126
@ temINVALID_COUNT
Definition: TER.h:121
@ temMALFORMED
Definition: TER.h:87
@ temINVALID
Definition: TER.h:110
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
T push_back(T... args)
T rbegin(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
T to_string(T... args)