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 <ripple/protocol/Feature.h>
21 #include <ripple/protocol/jss.h>
22 #include <test/jtx.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)
64  expected_nodes;
65 
66  if (is_cancel && other_target)
67  {
68  expected_nodes = {
69  {0, sfModifiedNode.fieldName, jss::AccountRoot},
70  {expiration ? 2 : 1,
72  jss::AccountRoot},
73  {expiration ? 1 : 2, sfDeletedNode.fieldName, jss::Ticket},
74  {3, sfDeletedNode.fieldName, jss::DirectoryNode}};
75  }
76  else
77  {
78  expected_nodes = {
79  {0, sfModifiedNode.fieldName, jss::AccountRoot},
80  {1,
82  jss::Ticket},
83  {2,
85  jss::DirectoryNode}};
86  }
87 
88  BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
89  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
90  BEAST_EXPECT(
91  jvm[sfAffectedNodes.fieldName].size() == expected_nodes.size());
92 
93  // verify the actual metadata against the expected
94  for (auto const& it : expected_nodes)
95  {
96  auto const& idx = std::get<0>(it);
97  auto const& field = std::get<1>(it);
98  auto const& type = std::get<2>(it);
99  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][idx].isMember(field));
100  retval[idx] = jvm[sfAffectedNodes.fieldName][idx][field];
101  BEAST_EXPECT(retval[idx][sfLedgerEntryType.fieldName] == type);
102  }
103 
104  return retval;
105  }
106 
107  void
109  {
110  testcase("Feature Not Enabled");
111 
112  using namespace test::jtx;
113  Env env{*this, FeatureBitset{}};
114 
115  env(ticket::create(env.master), ter(temDISABLED));
116  env(ticket::cancel(env.master, idOne), ter(temDISABLED));
117  }
118 
119  void
121  {
122  testcase("Cancel Nonexistent");
123 
124  using namespace test::jtx;
125  Env env{*this, supported_amendments().set(featureTickets)};
126  env(ticket::cancel(env.master, idOne), ter(tecNO_ENTRY));
127  }
128 
129  void
131  {
132  testcase("Create/Cancel Ticket with Bad Fee, Fail Preflight");
133 
134  using namespace test::jtx;
135  Env env{*this, supported_amendments().set(featureTickets)};
136 
137  env(ticket::create(env.master), fee(XRP(-1)), ter(temBAD_FEE));
138  env(ticket::cancel(env.master, idOne), fee(XRP(-1)), ter(temBAD_FEE));
139  }
140 
141  void
143  {
144  testcase("Create Tickets with Nonexistent Accounts");
145 
146  using namespace test::jtx;
147  Env env{*this, supported_amendments().set(featureTickets)};
148  Account alice{"alice"};
149  env.memoize(alice);
150 
151  env(ticket::create(env.master, alice), ter(tecNO_TARGET));
152 
153  env(ticket::create(alice, env.master),
154  json(jss::Sequence, 1),
155  ter(terNO_ACCOUNT));
156  }
157 
158  void
160  {
161  testcase("Create Tickets with Same Account and Target");
162 
163  using namespace test::jtx;
164  Env env{*this, supported_amendments().set(featureTickets)};
165 
166  env(ticket::create(env.master, env.master));
167  auto cr = checkTicketMeta(env);
168  auto const& jticket = cr[1];
169 
170  BEAST_EXPECT(
171  jticket[sfLedgerIndex.fieldName] ==
172  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
173  BEAST_EXPECT(
174  jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
175  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 1);
176  // verify that there's no `Target` saved in the ticket
177  BEAST_EXPECT(
178  !jticket[sfNewFields.fieldName].isMember(sfTarget.fieldName));
179  }
180 
181  void
183  {
184  testcase("Create Ticket and Then Cancel by Creator");
185 
186  using namespace test::jtx;
187  Env env{*this, supported_amendments().set(featureTickets)};
188 
189  // create and verify
190  env(ticket::create(env.master));
191  auto cr = checkTicketMeta(env);
192  auto const& jacct = cr[0];
193  auto const& jticket = cr[1];
194  BEAST_EXPECT(
196  BEAST_EXPECT(
198  BEAST_EXPECT(
199  jticket[sfNewFields.fieldName][jss::Sequence] ==
200  jacct[sfPreviousFields.fieldName][jss::Sequence]);
201  BEAST_EXPECT(
202  jticket[sfLedgerIndex.fieldName] ==
203  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
204  BEAST_EXPECT(
205  jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
206 
207  // cancel
208  env(ticket::cancel(
209  env.master, jticket[sfLedgerIndex.fieldName].asString()));
210  auto crd = checkTicketMeta(env);
211  auto const& jacctd = crd[0];
212  BEAST_EXPECT(jacctd[sfFinalFields.fieldName][jss::Sequence] == 3);
213  BEAST_EXPECT(
215  }
216 
217  void
219  {
220  testcase("Create Ticket Insufficient Reserve");
221 
222  using namespace test::jtx;
223  Env env{*this, supported_amendments().set(featureTickets)};
224  Account alice{"alice"};
225 
226  env.fund(env.current()->fees().accountReserve(0), alice);
227  env.close();
228 
229  env(ticket::create(alice), ter(tecINSUFFICIENT_RESERVE));
230  }
231 
232  void
234  {
235  testcase("Create Ticket and Then Cancel by Target");
236 
237  using namespace test::jtx;
238  Env env{*this, supported_amendments().set(featureTickets)};
239  Account alice{"alice"};
240 
241  env.fund(XRP(10000), alice);
242  env.close();
243 
244  // create and verify
245  env(ticket::create(env.master, alice));
246  auto cr = checkTicketMeta(env, true);
247  auto const& jacct = cr[0];
248  auto const& jticket = cr[1];
249  BEAST_EXPECT(
251  BEAST_EXPECT(jticket[sfLedgerEntryType.fieldName] == jss::Ticket);
252  BEAST_EXPECT(
253  jticket[sfLedgerIndex.fieldName] ==
254  "C231BA31A0E13A4D524A75F990CE0D6890B800FF1AE75E51A2D33559547AC1A2");
255  BEAST_EXPECT(
256  jticket[sfNewFields.fieldName][jss::Account] == env.master.human());
257  BEAST_EXPECT(
259  alice.human());
260  BEAST_EXPECT(jticket[sfNewFields.fieldName][jss::Sequence] == 2);
261 
262  // cancel using the target account
263  env(ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString()));
264  auto crd = checkTicketMeta(env, true);
265  auto const& jacctd = crd[0];
266  auto const& jdir = crd[2];
267  BEAST_EXPECT(
269  BEAST_EXPECT(
270  jdir[sfLedgerIndex.fieldName] == jticket[sfLedgerIndex.fieldName]);
271  BEAST_EXPECT(
272  jdir[sfFinalFields.fieldName][jss::Account] == env.master.human());
273  BEAST_EXPECT(
274  jdir[sfFinalFields.fieldName][sfTarget.fieldName] == alice.human());
275  BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Flags] == 0);
276  BEAST_EXPECT(
278  "0000000000000000");
279  BEAST_EXPECT(jdir[sfFinalFields.fieldName][jss::Sequence] == 2);
280  }
281 
282  void
284  {
285  testcase("Create Ticket with Future Expiration");
286 
287  using namespace test::jtx;
288  Env env{*this, supported_amendments().set(featureTickets)};
289 
290  // create and verify
291  using namespace std::chrono_literals;
292  uint32_t expire =
293  (env.timeKeeper().closeTime() + 60s).time_since_epoch().count();
294  env(ticket::create(env.master, expire));
295  auto cr = checkTicketMeta(env);
296  auto const& jacct = cr[0];
297  auto const& jticket = cr[1];
298  BEAST_EXPECT(
300  BEAST_EXPECT(
302  BEAST_EXPECT(
303  jticket[sfNewFields.fieldName][jss::Sequence] ==
304  jacct[sfPreviousFields.fieldName][jss::Sequence]);
305  BEAST_EXPECT(
306  jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
307  }
308 
309  void
311  {
312  testcase("Create Ticket with Zero Expiration");
313 
314  using namespace test::jtx;
315  Env env{*this, supported_amendments().set(featureTickets)};
316 
317  // create and verify
318  env(ticket::create(env.master, 0u), ter(temBAD_EXPIRATION));
319  }
320 
321  void
323  {
324  testcase("Create Ticket with Past Expiration");
325 
326  using namespace test::jtx;
327  Env env{*this, supported_amendments().set(featureTickets)};
328 
329  env.timeKeeper().adjustCloseTime(days{2});
330  env.close();
331 
332  // create and verify
333  uint32_t expire = 60;
334  env(ticket::create(env.master, expire));
335  // in the case of past expiration, we only get
336  // one meta node entry returned
337  auto const& jvm = env.meta()->getJson(JsonOptions::none);
338  BEAST_EXPECT(jvm.isMember(sfAffectedNodes.fieldName));
339  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].isArray());
340  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName].size() == 1);
341  BEAST_EXPECT(jvm[sfAffectedNodes.fieldName][0u].isMember(
343  auto const& jacct =
345  BEAST_EXPECT(jacct[sfLedgerEntryType.fieldName] == jss::AccountRoot);
346  BEAST_EXPECT(
347  jacct[sfFinalFields.fieldName][jss::Account] == env.master.human());
348  }
349 
350  void
352  {
353  testcase("Create Ticket and Allow to Expire");
354 
355  using namespace test::jtx;
356  Env env{*this, supported_amendments().set(featureTickets)};
357 
358  // create and verify
359  uint32_t expire = (env.timeKeeper().closeTime() + std::chrono::hours{3})
360  .time_since_epoch()
361  .count();
362  env(ticket::create(env.master, expire));
363  auto cr = checkTicketMeta(env);
364  auto const& jacct = cr[0];
365  auto const& jticket = cr[1];
366  BEAST_EXPECT(
368  BEAST_EXPECT(
370  BEAST_EXPECT(
371  jticket[sfNewFields.fieldName][sfExpiration.fieldName] == expire);
372  BEAST_EXPECT(
373  jticket[sfLedgerIndex.fieldName] ==
374  "7F58A0AE17775BA3404D55D406DD1C2E91EADD7AF3F03A26877BCE764CCB75E3");
375 
376  Account alice{"alice"};
377  env.fund(XRP(10000), alice);
378  env.close();
379 
380  // now try to cancel with alice account, which should not work
381  auto jv =
382  ticket::cancel(alice, jticket[sfLedgerIndex.fieldName].asString());
383  env(jv, ter(tecNO_PERMISSION));
384 
385  // advance the ledger time to as to trigger expiration
386  env.timeKeeper().adjustCloseTime(days{3});
387  env.close();
388 
389  // now try again - the cancel succeeds because ticket has expired
390  env(jv);
391  auto crd = checkTicketMeta(env, true, true);
392  auto const& jticketd = crd[1];
393  BEAST_EXPECT(
395  expire);
396  }
397 
398 public:
399  void
400  run() override
401  {
414  }
415 };
416 
417 BEAST_DEFINE_TESTSUITE(Ticket, tx, ripple);
418 
419 } // namespace ripple
ripple::Ticket_test::testTicketWithPastExpiration
void testTicketWithPastExpiration()
Definition: Ticket_test.cpp:322
ripple::sfPreviousFields
const SField sfPreviousFields(access, STI_OBJECT, 6, "PreviousFields")
Definition: SField.h:499
ripple::tecNO_TARGET
@ tecNO_TARGET
Definition: TER.h:262
ripple::Ticket_test::testTicketCancelByTarget
void testTicketCancelByTarget()
Definition: Ticket_test.cpp:233
ripple::sfTarget
const SF_Account sfTarget(access, STI_ACCOUNT, 7, "Target")
Definition: SField.h:482
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::sfLedgerIndex
const SF_U256 sfLedgerIndex(access, STI_HASH256, 6, "LedgerIndex")
Definition: SField.h:421
ripple::sfLedgerEntryType
const SF_U16 sfLedgerEntryType(access, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never)
Definition: SField.h:345
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:360
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:129
std::chrono::duration
ripple::Ticket_test::testTicketInsufficientReserve
void testTicketInsufficientReserve()
Definition: Ticket_test.cpp:218
ripple::sfNewFields
const SField sfNewFields(access, STI_OBJECT, 8, "NewFields")
Definition: SField.h:501
ripple::Ticket_test::testTicketCreateNonexistent
void testTicketCreateNonexistent()
Definition: Ticket_test.cpp:142
ripple::sfOwnerNode
const SF_U64 sfOwnerNode(access, STI_UINT64, 4, "OwnerNode")
Definition: SField.h:397
ripple::Ticket_test::testTicketCancelNonexistent
void testTicketCancelNonexistent()
Definition: Ticket_test.cpp:120
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:364
ripple::sfFinalFields
const SField sfFinalFields(access, STI_OBJECT, 7, "FinalFields")
Definition: SField.h:500
ripple::sfModifiedNode
const SField sfModifiedNode(access, STI_OBJECT, 5, "ModifiedNode")
Definition: SField.h:498
ripple::Ticket_test::testTicketNotEnabled
void testTicketNotEnabled()
Definition: Ticket_test.cpp:108
ripple::test::jtx::Env::meta
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:352
ripple::Ticket_test::run
void run() override
Definition: Ticket_test.cpp:400
ripple::JsonOptions::none
@ none
ripple::Ticket_test::testTicketCreatePreflightFail
void testTicketCreatePreflightFail()
Definition: Ticket_test.cpp:130
ripple::Ticket_test::testTicketCancelByCreator
void testTicketCancelByCreator()
Definition: Ticket_test.cpp:182
std::array
STL class.
ripple::Ticket_test::testTicketWithExpiration
void testTicketWithExpiration()
Definition: Ticket_test.cpp:283
ripple::featureTickets
const uint256 featureTickets
Definition: Feature.cpp:160
ripple::sfCreatedNode
const SField sfCreatedNode(access, STI_OBJECT, 3, "CreatedNode")
Definition: SField.h:496
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:87
ripple::sfExpiration
const SF_U32 sfExpiration(access, STI_UINT32, 10, "Expiration")
Definition: SField.h:361
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:190
ripple::Ticket_test::testTicketToSelf
void testTicketToSelf()
Definition: Ticket_test.cpp:159
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:109
ripple::Ticket_test::testTicketAllowExpiration
void testTicketAllowExpiration()
Definition: Ticket_test.cpp:351
ripple::sfAffectedNodes
const SField sfAffectedNodes(access, STI_ARRAY, 8, "AffectedNodes")
Definition: SField.h:516
ripple::Ticket_test
Definition: Ticket_test.cpp:26
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:263
ripple::FeatureBitset::set
FeatureBitset & set(uint256 const &f, bool value=true)
Definition: Feature.h:219
ripple::FeatureBitset
Definition: Feature.h:154
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:265
ripple::Ticket_test::testTicketZeroExpiration
void testTicketZeroExpiration()
Definition: Ticket_test.cpp:310
ripple::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:264
ripple::Ticket_test::idOne
static constexpr auto idOne
Definition: Ticket_test.cpp:28
ripple::temBAD_EXPIRATION
@ temBAD_EXPIRATION
Definition: TER.h:86
ripple::sfDeletedNode
const SField sfDeletedNode(access, STI_OBJECT, 4, "DeletedNode")
Definition: SField.h:497
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:114