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