21#include <xrpld/app/misc/Transaction.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/jss.h>
35 using namespace std::string_literals;
40 tx[sfTransactionType.jsonName].asString();
43 txType == jss::TicketCreate,
44 "Unexpected TransactionType: "s + txType))
48 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
54 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
55 std::string const account = tx[sfAccount.jsonName].asString();
59 metadata.
isMember(sfTransactionResult.jsonName) &&
60 metadata[sfTransactionResult.jsonName].
asString() ==
62 "Not metadata for successful TicketCreate."))
65 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
66 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
68 bool directoryChanged =
false;
72 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
74 if (node.isMember(sfModifiedNode.jsonName))
76 Json::Value const& modified = node[sfModifiedNode.jsonName];
78 modified[sfLedgerEntryType.jsonName].
asString();
79 if (entryType == jss::AccountRoot)
81 auto const& previousFields =
82 modified[sfPreviousFields.jsonName];
83 auto const& finalFields = modified[sfFinalFields.jsonName];
87 previousFields[sfSequence.jsonName].asUInt();
90 finalFields[sfSequence.jsonName].asUInt();
95 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
100 BEAST_EXPECT(prevSeq == txSeq);
102 acctRootFinalSeq == prevSeq + count + 1);
107 txSeq == 0u ? 1u : 0u};
116 bool const unreportedPrevTicketCount = {
117 count == 1 && txSeq == 0};
120 if (unreportedPrevTicketCount)
125 !previousFields.isMember(sfOwnerCount.jsonName));
131 previousFields[sfOwnerCount.jsonName].asUInt()};
134 finalFields[sfOwnerCount.jsonName].asUInt()};
137 prevCount + count - consumedTickets == finalCount);
141 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
143 if (unreportedPrevTicketCount)
148 !previousFields.isMember(sfTicketCount.jsonName));
155 previousFields.isMember(sfTicketCount.jsonName)
156 ? previousFields[sfTicketCount.jsonName]
162 previousFields.isMember(sfTicketCount.jsonName));
165 startCount + count - consumedTickets ==
166 finalFields[sfTicketCount.jsonName]);
169 else if (entryType == jss::DirectoryNode)
171 directoryChanged =
true;
176 "Unexpected modified node: "s + entryType,
181 else if (node.isMember(sfCreatedNode.jsonName))
183 Json::Value const& created = node[sfCreatedNode.jsonName];
185 created[sfLedgerEntryType.jsonName].
asString();
186 if (entryType == jss::Ticket)
188 auto const& newFields = created[sfNewFields.jsonName];
191 newFields[sfAccount.jsonName].asString() == account);
193 newFields[sfTicketSequence.jsonName].asUInt());
195 else if (entryType == jss::DirectoryNode)
197 directoryChanged =
true;
202 "Unexpected created node: "s + entryType,
207 else if (node.isMember(sfDeletedNode.jsonName))
209 Json::Value const& deleted = node[sfDeletedNode.jsonName];
211 deleted[sfLedgerEntryType.jsonName].
asString();
213 if (entryType == jss::Ticket)
216 BEAST_EXPECT(txSeq == 0);
219 auto const& finalFields = deleted[sfFinalFields.jsonName];
221 finalFields[sfAccount.jsonName].asString() == account);
225 finalFields[sfTicketSequence.jsonName].asUInt() ==
226 tx[sfTicketSequence.jsonName].asUInt());
232 "Unexpected node type in TicketCreate metadata.",
237 BEAST_EXPECT(directoryChanged);
240 BEAST_EXPECT(ticketSeqs.
size() == count);
245 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
280 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
281 std::string const account{tx[sfAccount.jsonName].asString()};
283 tx.isMember(sfTicketSequence.jsonName),
284 "Not metadata for a ticket consuming transaction."))
287 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
291 metadata.isMember(sfTransactionResult.jsonName),
292 "Metadata is missing TransactionResult."))
297 metadata[sfTransactionResult.jsonName].asString()};
299 transactionResult ==
"tesSUCCESS" ||
300 transactionResult.compare(0, 3,
"tec") == 0,
301 transactionResult +
" neither tesSUCCESS nor tec"))
305 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
306 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
308 bool acctRootFound{
false};
310 int ticketsRemoved{0};
311 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
313 if (node.isMember(sfModifiedNode.jsonName))
315 Json::Value const& modified{node[sfModifiedNode.jsonName]};
317 modified[sfLedgerEntryType.jsonName].asString();
318 if (entryType ==
"AccountRoot" &&
319 modified[sfFinalFields.jsonName][sfAccount.jsonName]
320 .asString() == account)
322 acctRootFound =
true;
324 auto const& previousFields =
325 modified[sfPreviousFields.jsonName];
326 auto const& finalFields = modified[sfFinalFields.jsonName];
328 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
334 previousFields.isMember(sfTicketCount.jsonName),
335 "AccountRoot previous is missing TicketCount"))
339 previousFields[sfTicketCount.jsonName].asUInt();
341 BEAST_EXPECT(prevTicketCount > 0);
342 if (prevTicketCount == 1)
344 !finalFields.isMember(sfTicketCount.jsonName));
347 finalFields.isMember(sfTicketCount.jsonName) &&
348 finalFields[sfTicketCount.jsonName].asUInt() ==
349 prevTicketCount - 1);
352 else if (node.isMember(sfDeletedNode.jsonName))
354 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
356 deleted[sfLedgerEntryType.jsonName].asString()};
358 if (entryType == jss::Ticket)
362 deleted[sfFinalFields.jsonName][sfAccount.jsonName]
363 .asString() == account);
367 deleted[sfFinalFields.jsonName]
368 [sfTicketSequence.jsonName]
369 .asUInt() == ticketSeq);
375 BEAST_EXPECT(acctRootFound);
376 BEAST_EXPECT(ticketsRemoved == 1);
377 BEAST_EXPECT(ticketSeq < acctRootSeq);
385 using namespace test::jtx;
386 Env env{*
this, supported_amendments() - featureTicketBatch};
388 env(ticket::create(env.master, 1), ter(
temDISABLED));
390 env.require(owners(env.master, 0), tickets(env.master, 0));
392 env(noop(env.master), ticket::use(1), ter(
temMALFORMED));
393 env(noop(env.master),
395 seq(env.seq(env.master)),
400 for (
int i = 0; i < 8; ++i)
403 env.enableFeature(featureTicketBatch);
405 env.require(owners(env.master, 0), tickets(env.master, 0));
408 env(ticket::create(env.master, 2));
411 env.require(owners(env.master, 2), tickets(env.master, 2));
413 env(noop(env.master), ticket::use(ticketSeq++));
416 env.require(owners(env.master, 1), tickets(env.master, 1));
419 ticket::use(ticketSeq++),
423 env.require(owners(env.master, 0), tickets(env.master, 0));
429 testcase(
"Create Tickets that fail Preflight");
431 using namespace test::jtx;
434 Account
const master{env.master};
442 env(ticket::create(master, 1), fee(XRP(10)));
445 env.require(owners(master, 1), tickets(master, 1));
447 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
454 env.require(owners(master, 2), tickets(master, 2));
458 env.require(owners(master, 2), tickets(master, 2));
462 env(noop(master), ticket::use(ticketSeq_A));
465 env.require(owners(master, 1), tickets(master, 1));
467 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
470 env.require(owners(master, 250), tickets(master, 250));
476 testcase(
"Create Tickets that fail Preclaim");
478 using namespace test::jtx;
482 Account alice{
"alice"};
485 env(ticket::create(alice, 1),
486 json(jss::Sequence, 1),
493 Account alice{
"alice"};
495 env.fund(XRP(100000), alice);
498 env(ticket::create(alice, 250));
501 env.require(owners(alice, 250), tickets(alice, 250));
505 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
508 env.require(owners(alice, 250), tickets(alice, 250));
511 env(ticket::create(alice, 2),
512 ticket::use(ticketSeq + 1),
515 env.require(owners(alice, 249), tickets(alice, 249));
518 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
521 env.require(owners(alice, 250), tickets(alice, 250));
527 env.require(owners(alice, 250), tickets(alice, 250));
532 Account alice{
"alice"};
534 env.fund(XRP(100000), alice);
538 env(ticket::create(alice, 2));
541 env.require(owners(alice, 2), tickets(alice, 2));
545 env(ticket::create(alice, 250),
546 ticket::use(ticketSeq_AB + 0),
549 env.require(owners(alice, 1), tickets(alice, 1));
555 env.require(owners(alice, 1), tickets(alice, 1));
558 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
561 env.require(owners(alice, 250), tickets(alice, 250));
568 testcase(
"Create Ticket Insufficient Reserve");
570 using namespace test::jtx;
572 Account alice{
"alice"};
575 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
580 env.require(owners(alice, 0), tickets(alice, 0));
586 env.current()->fees().accountReserve(1) - env.balance(alice)));
589 env(ticket::create(alice, 1));
592 env.require(owners(alice, 1), tickets(alice, 1));
599 env.current()->fees().accountReserve(250) - drops(1) -
600 env.balance(alice)));
607 env.require(owners(alice, 1), tickets(alice, 1));
614 env.current()->fees().accountReserve(250) - env.balance(alice)));
618 env(ticket::create(alice, 249));
621 env.require(owners(alice, 250), tickets(alice, 250));
622 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
630 using namespace test::jtx;
632 Account alice{
"alice"};
634 env.fund(XRP(10000), alice);
639 env(ticket::create(alice, 2));
642 env.require(owners(alice, 2), tickets(alice, 2));
643 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
647 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
650 env.require(owners(alice, 2), tickets(alice, 2));
651 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
655 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
658 env.require(owners(alice, 3), tickets(alice, 3));
659 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
662 env(noop(alice), ticket::use(ticketSeq_DE + 0));
665 env.require(owners(alice, 2), tickets(alice, 2));
666 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
668 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
671 env.require(owners(alice, 1), tickets(alice, 1));
672 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
674 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
677 env.require(owners(alice, 1), tickets(alice, 0));
678 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
681 env(noop(alice), ticket::use(ticketSeq_C), ter(
tefNO_TICKET));
686 env(noop(alice), ticket::use(ticketSeq_F), ter(
terPRE_TICKET));
690 env(ticket::create(alice, 1));
693 env.require(owners(alice, 1), tickets(alice, 0));
694 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
699 env(ticket::create(alice, 1));
704 ticket::use(ticketSeq_G),
705 json(R
"({"AccountTxnID": "0"})"),
708 env.require(owners(alice, 2), tickets(alice, 1));
724 testcase(
"Transaction Database With Tickets");
726 using namespace test::jtx;
728 Account alice{
"alice"};
730 env.fund(XRP(10000), alice);
734 auto getTxID = [&env,
this]() ->
uint256 {
736 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
737 Throw<std::invalid_argument>(
"Invalid transaction ID");
739 return tx->getTransactionID();
753 env(ticket::create(alice, ticketCount));
754 uint256 const txHash_1{getTxID()};
757 ticketSeq += ticketCount;
758 env(noop(alice), ticket::use(--ticketSeq));
759 uint256 const txHash_2{getTxID()};
761 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
762 uint256 const txHash_3{getTxID()};
764 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
765 uint256 const txHash_4{getTxID()};
771 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
772 uint256 const txHash_5{getTxID()};
774 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
775 uint256 const txHash_6{getTxID()};
777 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
778 uint256 const txHash_7{getTxID()};
780 env(noop(alice), ticket::use(--ticketSeq));
781 uint256 const txHash_8{getTxID()};
791 auto checkTxFromDB = [&env,
this](
805 if (
auto txPtr = std::get_if<TxPair>(&maybeTx))
808 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
810 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
812 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
813 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
817 fail(
"Expected transaction was not found");
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);
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);
840 testcase(
"Sign with TicketSequence");
842 using namespace test::jtx;
844 Account alice{
"alice"};
846 env.fund(XRP(10000), alice);
851 env(ticket::create(alice, 2));
854 env.require(owners(alice, 2), tickets(alice, 2));
855 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
864 tx[jss::tx_json] = noop(alice);
865 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
869 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
876 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
877 sfSequence.jsonName)))
880 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
885 env.require(owners(alice, 2), tickets(alice, 2));
888 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
890 env.require(owners(alice, 1), tickets(alice, 1));
899 tx[jss::tx_json] = noop(alice);
900 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
904 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
911 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
912 sfSequence.jsonName)))
915 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
920 env.require(owners(alice, 0), tickets(alice, 0));
929 testcase(
"Fix both Seq and Ticket");
932 using namespace test::jtx;
934 Env env{*
this, supported_amendments() - featureTicketBatch};
935 Account alice{
"alice"};
937 env.fund(XRP(10000), alice);
944 env.require(owners(alice, 0), tickets(alice, 0));
945 BEAST_EXPECT(ticketSeq == env.seq(alice) + 1);
951 ticket::use(ticketSeq),
958 Env env{*
this, supported_amendments()};
959 Account alice{
"alice"};
961 env.fund(XRP(10000), alice);
966 env(ticket::create(alice, 1));
968 env.require(owners(alice, 1), tickets(alice, 1));
969 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
974 ticket::use(ticketSeq),
981 env.require(owners(alice, 1), tickets(alice, 1));
982 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
T adjacent_find(T... args)
std::string asString() const
Returns the unquoted string value.
bool isMember(const char *key) const
Return true if the object has a member named key.
testcase_t testcase
Memberspace for declaring test cases.
void fail(String const &reason, char const *file, int line)
Record a failure.
void testFixBothSeqAndTicket()
void testTicketInsufficientReserve()
void testTransactionDatabaseWithTickets()
void testSignWithTicketSequence()
void checkTicketCreateMeta(test::jtx::Env &env)
Validate metadata for a successful CreateTicket transaction.
void testTicketCreatePreclaimFail()
void testTicketCreatePreflightFail()
void testTicketNotEnabled()
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.
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
@ objectValue
object value (collection of name/value pairs).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
TxType
Transaction type identifiers.
constexpr std::uint32_t asfDisableMaster
@ tecINSUFFICIENT_RESERVE
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
constexpr std::uint32_t tfSell
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.