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