rippled
Ticket_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2016 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <test/jtx.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/jss.h>
23 
24 namespace ripple {
25 
26 class Ticket_test : public beast::unit_test::suite
27 {
28  static auto constexpr idOne =
29  "00000000000000000000000000000000"
30  "00000000000000000000000000000001";
31 
47  auto
49  test::jtx::Env& env,
50  bool other_target = false,
51  bool expiration = false)
52  {
53  using namespace std::string_literals;
54  auto const& tx = env.tx ()->getJson (JsonOptions::none);
55  bool is_cancel = tx[jss::TransactionType] == jss::TicketCancel;
56 
57  auto const& jvm = env.meta ()->getJson (JsonOptions::none);
59 
60  // these are the affected nodes that we expect for
61  // a few different scenarios.
62  // tuple is index, field name, and label (LedgerEntryType)
65  > expected_nodes;
66 
67  if (is_cancel && other_target)
68  {
69  expected_nodes = {
70  {0, sfModifiedNode.fieldName, jss::AccountRoot},
71  {expiration ? 2: 1, sfModifiedNode.fieldName, jss::AccountRoot},
72  {expiration ? 1: 2, sfDeletedNode.fieldName, jss::Ticket},
73  {3, sfDeletedNode.fieldName, jss::DirectoryNode}
74  };
75  }
76  else
77  {
78  expected_nodes = {
79  {0, sfModifiedNode.fieldName, jss::AccountRoot},
80  {1, is_cancel ? sfDeletedNode.fieldName :
81  sfCreatedNode.fieldName, jss::Ticket},
82  {2, is_cancel ? sfDeletedNode.fieldName :
83  sfCreatedNode.fieldName, jss::DirectoryNode}
84  };
85  }
86 
87  BEAST_EXPECT(jvm.isMember (sfAffectedNodes.fieldName));
88  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
89  BEAST_EXPECT(
90  jvm[sfAffectedNodes.fieldName].size() == expected_nodes.size());
91 
92  // verify the actual metadata against the expected
93  for (auto const& it : expected_nodes)
94  {
95  auto const& idx = std::get<0>(it);
96  auto const& field = std::get<1>(it);
97  auto const& type = std::get<2>(it);
98  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][idx].isMember(field));
99  retval[idx] = jvm[sfAffectedNodes.fieldName][idx][field];
100  BEAST_EXPECT(retval[idx][sfLedgerEntryType.fieldName] == type);
101  }
102 
103  return retval;
104  }
105 
107  {
108  testcase ("Feature Not Enabled");
109 
110  using namespace test::jtx;
111  Env env {*this, FeatureBitset{}};
112 
113  env (ticket::create (env.master), ter(temDISABLED));
114  env (ticket::cancel (env.master, idOne), ter (temDISABLED));
115  }
116 
118  {
119  testcase ("Cancel Nonexistent");
120 
121  using namespace test::jtx;
122  Env env {*this, supported_amendments().set(featureTickets)};
123  env (ticket::cancel (env.master, idOne), ter (tecNO_ENTRY));
124  }
125 
127  {
128  testcase ("Create/Cancel Ticket with Bad Fee, Fail Preflight");
129 
130  using namespace test::jtx;
131  Env env {*this, supported_amendments().set(featureTickets)};
132 
133  env (ticket::create (env.master), fee (XRP (-1)), ter (temBAD_FEE));
134  env (ticket::cancel (env.master, idOne), fee (XRP (-1)), ter (temBAD_FEE));
135  }
136 
138  {
139  testcase ("Create Tickets with Nonexistent Accounts");
140 
141  using namespace test::jtx;
142  Env env {*this, supported_amendments().set(featureTickets)};
143  Account alice {"alice"};
144  env.memoize (alice);
145 
146  env (ticket::create (env.master, alice), ter(tecNO_TARGET));
147 
148  env (ticket::create (alice, env.master),
149  json (jss::Sequence, 1),
150  ter (terNO_ACCOUNT));
151  }
152 
154  {
155  testcase ("Create Tickets with Same Account and Target");
156 
157  using namespace test::jtx;
158  Env env {*this, supported_amendments().set(featureTickets)};
159 
160  env (ticket::create (env.master, env.master));
161  auto cr = checkTicketMeta (env);
162  auto const& jticket = cr[1];
163 
164  BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
165  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
166  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
167  env.master.human());
168  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 1);
169  //verify that there's no `Target` saved in the ticket
170  BEAST_EXPECT(! jticket[sfNewFields.fieldName].
171  isMember(sfTarget.fieldName));
172  }
173 
175  {
176  testcase ("Create Ticket and Then Cancel by Creator");
177 
178  using namespace test::jtx;
179  Env env {*this, supported_amendments().set(featureTickets)};
180 
181  // create and verify
182  env (ticket::create (env.master));
183  auto cr = checkTicketMeta (env);
184  auto const& jacct = cr[0];
185  auto const& jticket = cr[1];
186  BEAST_EXPECT(
188  BEAST_EXPECT(
190  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] ==
191  jacct[sfPreviousFields.fieldName][jss::Sequence]);
192  BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
193  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
194  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
195  env.master.human());
196 
197  // cancel
198  env (ticket::cancel(env.master, jticket[sfLedgerIndex.fieldName].asString()));
199  auto crd = checkTicketMeta (env);
200  auto const& jacctd = crd[0];
201  BEAST_EXPECT(jacctd[sfFinalFields.fieldName][jss::Sequence] == 3);
202  BEAST_EXPECT(
204  }
205 
207  {
208  testcase ("Create Ticket Insufficient Reserve");
209 
210  using namespace test::jtx;
211  Env env {*this, supported_amendments().set(featureTickets)};
212  Account alice {"alice"};
213 
214  env.fund (env.current ()->fees ().accountReserve (0), alice);
215  env.close ();
216 
217  env (ticket::create (alice), ter (tecINSUFFICIENT_RESERVE));
218  }
219 
221  {
222  testcase ("Create Ticket and Then Cancel by Target");
223 
224  using namespace test::jtx;
225  Env env {*this, supported_amendments().set(featureTickets)};
226  Account alice {"alice"};
227 
228  env.fund (XRP (10000), alice);
229  env.close ();
230 
231  // create and verify
232  env (ticket::create (env.master, alice));
233  auto cr = checkTicketMeta (env, true);
234  auto const& jacct = cr[0];
235  auto const& jticket = cr[1];
236  BEAST_EXPECT(
238  BEAST_EXPECT(jticket[sfLedgerEntryType.fieldName] == jss::Ticket);
239  BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
240  "C231BA31A0E13A4D524A75F990CE0D6890B800FF1AE75E51A2D33559547AC1A2");
241  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Account] ==
242  env.master.human());
243  BEAST_EXPECT(jticket[sfNewFields.fieldName][sfTarget.fieldName] ==
244  alice.human());
245  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 2);
246 
247  // cancel using the target account
248  env (ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString()));
249  auto crd = checkTicketMeta (env, true);
250  auto const& jacctd = crd[0];
251  auto const& jdir = crd[2];
252  BEAST_EXPECT(
254  BEAST_EXPECT(jdir[sfLedgerIndex.fieldName] ==
255  jticket[sfLedgerIndex.fieldName]);
256  BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Account] ==
257  env.master.human());
258  BEAST_EXPECT(jdir[sfFinalFields.fieldName][sfTarget.fieldName] ==
259  alice.human());
260  BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Flags] == 0);
261  BEAST_EXPECT(jdir[sfFinalFields.fieldName][sfOwnerNode.fieldName] ==
262  "0000000000000000");
263  BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Sequence] == 2);
264  }
265 
267  {
268  testcase ("Create Ticket with Future Expiration");
269 
270  using namespace test::jtx;
271  Env env {*this, supported_amendments().set(featureTickets)};
272 
273  // create and verify
274  using namespace std::chrono_literals;
275  uint32_t expire =
276  (env.timeKeeper ().closeTime () + 60s)
277  .time_since_epoch ().count ();
278  env (ticket::create (env.master, expire));
279  auto cr = checkTicketMeta (env);
280  auto const& jacct = cr[0];
281  auto const& jticket = cr[1];
282  BEAST_EXPECT(
284  BEAST_EXPECT(
286  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] ==
287  jacct[sfPreviousFields.fieldName][jss::Sequence]);
288  BEAST_EXPECT(
289  jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
290  }
291 
293  {
294  testcase ("Create Ticket with Zero Expiration");
295 
296  using namespace test::jtx;
297  Env env {*this, supported_amendments().set(featureTickets)};
298 
299  // create and verify
300  env (ticket::create (env.master, 0u), ter (temBAD_EXPIRATION));
301  }
302 
304  {
305  testcase ("Create Ticket with Past Expiration");
306 
307  using namespace test::jtx;
308  Env env {*this, supported_amendments().set(featureTickets)};
309 
310  env.timeKeeper ().adjustCloseTime (days {2});
311  env.close ();
312 
313  // create and verify
314  uint32_t expire = 60;
315  env (ticket::create (env.master, expire));
316  // in the case of past expiration, we only get
317  // one meta node entry returned
318  auto const& jvm = env.meta ()->getJson (JsonOptions::none);
319  BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
320  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
321  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].size() == 1);
322  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][0u].
323  isMember(sfModifiedNode.fieldName));
324  auto const& jacct =
326  BEAST_EXPECT(
327  jacct[sfLedgerEntryType.fieldName] == jss::AccountRoot);
328  BEAST_EXPECT(jacct[sfFinalFields.fieldName][jss::Account] ==
329  env.master.human());
330  }
331 
333  {
334  testcase ("Create Ticket and Allow to Expire");
335 
336  using namespace test::jtx;
337  Env env {*this, supported_amendments().set(featureTickets)};
338 
339  // create and verify
340  uint32_t expire =
341  (env.timeKeeper ().closeTime () + std::chrono::hours {3})
342  .time_since_epoch().count();
343  env (ticket::create (env.master, expire));
344  auto cr = checkTicketMeta (env);
345  auto const& jacct = cr[0];
346  auto const& jticket = cr[1];
347  BEAST_EXPECT(
349  BEAST_EXPECT(
351  BEAST_EXPECT(
352  jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
353  BEAST_EXPECT(jticket[sfLedgerIndex.fieldName] ==
354  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
355 
356  Account alice {"alice"};
357  env.fund (XRP (10000), alice);
358  env.close ();
359 
360  // now try to cancel with alice account, which should not work
361  auto jv = ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString());
362  env (jv, ter (tecNO_PERMISSION));
363 
364  // advance the ledger time to as to trigger expiration
365  env.timeKeeper ().adjustCloseTime (days {3});
366  env.close ();
367 
368  // now try again - the cancel succeeds because ticket has expired
369  env (jv);
370  auto crd = checkTicketMeta (env, true, true);
371  auto const& jticketd = crd[1];
372  BEAST_EXPECT(
373  jticketd[sfFinalFields.fieldName][sfExpiration.fieldName] == expire);
374  }
375 
376 public:
377  void run () override
378  {
383  testTicketToSelf ();
391  }
392 };
393 
394 BEAST_DEFINE_TESTSUITE (Ticket, tx, ripple);
395 
396 } // ripple
397 
ripple::Ticket_test::testTicketWithPastExpiration
void testTicketWithPastExpiration()
Definition: Ticket_test.cpp:303
ripple::sfPreviousFields
const SField sfPreviousFields(access, STI_OBJECT, 6, "PreviousFields")
Definition: SField.h:483
ripple::tecNO_TARGET
@ tecNO_TARGET
Definition: TER.h:269
ripple::Ticket_test::testTicketCancelByTarget
void testTicketCancelByTarget()
Definition: Ticket_test.cpp:220
ripple::sfTarget
const SF_Account sfTarget(access, STI_ACCOUNT, 7, "Target")
Definition: SField.h:466
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::sfLedgerIndex
const SF_U256 sfLedgerIndex(access, STI_HASH256, 6, "LedgerIndex")
Definition: SField.h:406
ripple::sfLedgerEntryType
const SF_U16 sfLedgerEntryType(access, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never)
Definition: SField.h:330
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:370
std::vector
STL class.
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:136
std::chrono::duration
ripple::Ticket_test::testTicketInsufficientReserve
void testTicketInsufficientReserve()
Definition: Ticket_test.cpp:206
ripple::sfNewFields
const SField sfNewFields(access, STI_OBJECT, 8, "NewFields")
Definition: SField.h:485
ripple::Ticket_test::testTicketCreateNonexistent
void testTicketCreateNonexistent()
Definition: Ticket_test.cpp:137
std::tuple
ripple::sfOwnerNode
const SF_U64 sfOwnerNode(access, STI_UINT64, 4, "OwnerNode")
Definition: SField.h:382
ripple::Ticket_test::testTicketCancelNonexistent
void testTicketCancelNonexistent()
Definition: Ticket_test.cpp:117
ripple::Ticket_test::checkTicketMeta
auto checkTicketMeta(test::jtx::Env &env, bool other_target=false, bool expiration=false)
validate metadata for a create/cancel ticket transaction and return the 3 or 4 nodes that make-up the...
Definition: Ticket_test.cpp:48
ripple::sfOwnerCount
const SF_U32 sfOwnerCount(access, STI_UINT32, 13, "OwnerCount")
Definition: SField.h:349
ripple::sfFinalFields
const SField sfFinalFields(access, STI_OBJECT, 7, "FinalFields")
Definition: SField.h:484
ripple::sfModifiedNode
const SField sfModifiedNode(access, STI_OBJECT, 5, "ModifiedNode")
Definition: SField.h:482
ripple::Ticket_test::testTicketNotEnabled
void testTicketNotEnabled()
Definition: Ticket_test.cpp:106
ripple::test::jtx::Env::meta
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:362
ripple::Ticket_test::run
void run() override
Definition: Ticket_test.cpp:377
ripple::JsonOptions::none
@ none
ripple::Ticket_test::testTicketCreatePreflightFail
void testTicketCreatePreflightFail()
Definition: Ticket_test.cpp:126
ripple::Ticket_test::testTicketCancelByCreator
void testTicketCancelByCreator()
Definition: Ticket_test.cpp:174
std::array
STL class.
ripple::Ticket_test::testTicketWithExpiration
void testTicketWithExpiration()
Definition: Ticket_test.cpp:266
ripple::featureTickets
const uint256 featureTickets
Definition: Feature.cpp:157
ripple::sfCreatedNode
const SField sfCreatedNode(access, STI_OBJECT, 3, "CreatedNode")
Definition: SField.h:480
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:90
ripple::sfExpiration
const SF_U32 sfExpiration(access, STI_UINT32, 10, "Expiration")
Definition: SField.h:346
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:195
ripple::Ticket_test::testTicketToSelf
void testTicketToSelf()
Definition: Ticket_test.cpp:153
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
ripple::Ticket_test::testTicketAllowExpiration
void testTicketAllowExpiration()
Definition: Ticket_test.cpp:332
ripple::sfAffectedNodes
const SField sfAffectedNodes(access, STI_ARRAY, 8, "AffectedNodes")
Definition: SField.h:500
ripple::Ticket_test
Definition: Ticket_test.cpp:26
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:270
ripple::FeatureBitset::set
FeatureBitset & set(uint256 const &f, bool value=true)
Definition: Feature.h:219
ripple::FeatureBitset
Definition: Feature.h:153
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:272
ripple::Ticket_test::testTicketZeroExpiration
void testTicketZeroExpiration()
Definition: Ticket_test.cpp:292
ripple::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:271
ripple::Ticket_test::idOne
static constexpr auto idOne
Definition: Ticket_test.cpp:28
ripple::temBAD_EXPIRATION
@ temBAD_EXPIRATION
Definition: TER.h:89
ripple::sfDeletedNode
const SField sfDeletedNode(access, STI_OBJECT, 4, "DeletedNode")
Definition: SField.h:481
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117