rippled
Transaction_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2017 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/app/rdb/backend/SQLiteDatabase.h>
21 #include <ripple/protocol/ErrorCodes.h>
22 #include <ripple/protocol/STBase.h>
23 #include <ripple/protocol/jss.h>
24 #include <ripple/rpc/CTID.h>
25 #include <optional>
26 #include <test/jtx.h>
27 #include <test/jtx/Env.h>
28 #include <test/jtx/envconfig.h>
29 #include <tuple>
30 
31 namespace ripple {
32 
33 class Transaction_test : public beast::unit_test::suite
34 {
36  makeNetworkConfig(uint32_t networkID)
37  {
38  using namespace test::jtx;
39  return envconfig([&](std::unique_ptr<Config> cfg) {
40  cfg->NETWORK_ID = networkID;
41  return cfg;
42  });
43  }
44 
45  void
47  {
48  testcase("Test Range Request");
49 
50  using namespace test::jtx;
51  using std::to_string;
52 
53  const char* COMMAND = jss::tx.c_str();
54  const char* BINARY = jss::binary.c_str();
55  const char* NOT_FOUND = RPC::get_error_info(rpcTXN_NOT_FOUND).token;
57  const char* EXCESSIVE =
59 
60  Env env{*this, features};
61  auto const alice = Account("alice");
62  env.fund(XRP(1000), alice);
63  env.close();
64 
67  auto const startLegSeq = env.current()->info().seq;
68  for (int i = 0; i < 750; ++i)
69  {
70  env(noop(alice));
71  txns.emplace_back(env.tx());
72  env.close();
73  metas.emplace_back(
74  env.closed()->txRead(env.tx()->getTransactionID()).second);
75  }
76  auto const endLegSeq = env.closed()->info().seq;
77 
78  // Find the existing transactions
79  for (size_t i = 0; i < txns.size(); ++i)
80  {
81  auto const& tx = txns[i];
82  auto const& meta = metas[i];
83  auto const result = env.rpc(
84  COMMAND,
85  to_string(tx->getTransactionID()),
86  BINARY,
87  to_string(startLegSeq),
88  to_string(endLegSeq));
89 
90  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
91  BEAST_EXPECT(
92  result[jss::result][jss::tx] ==
93  strHex(tx->getSerializer().getData()));
94  BEAST_EXPECT(
95  result[jss::result][jss::meta] ==
96  strHex(meta->getSerializer().getData()));
97  }
98 
99  auto const tx = env.jt(noop(alice), seq(env.seq(alice))).stx;
100  for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
101  {
102  auto const result = env.rpc(
103  COMMAND,
104  to_string(tx->getTransactionID()),
105  BINARY,
106  to_string(startLegSeq),
107  to_string(endLegSeq + deltaEndSeq));
108 
109  BEAST_EXPECT(
110  result[jss::result][jss::status] == jss::error &&
111  result[jss::result][jss::error] == NOT_FOUND);
112 
113  if (deltaEndSeq)
114  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
115  else
116  BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
117  }
118 
119  // Find transactions outside of provided range.
120  for (auto&& tx : txns)
121  {
122  auto const result = env.rpc(
123  COMMAND,
124  to_string(tx->getTransactionID()),
125  BINARY,
126  to_string(endLegSeq + 1),
127  to_string(endLegSeq + 100));
128 
129  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
130  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
131  }
132 
133  const auto deletedLedger = (startLegSeq + endLegSeq) / 2;
134  {
135  // Remove one of the ledgers from the database directly
136  dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
137  ->deleteTransactionByLedgerSeq(deletedLedger);
138  }
139 
140  for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
141  {
142  auto const result = env.rpc(
143  COMMAND,
144  to_string(tx->getTransactionID()),
145  BINARY,
146  to_string(startLegSeq),
147  to_string(endLegSeq + deltaEndSeq));
148 
149  BEAST_EXPECT(
150  result[jss::result][jss::status] == jss::error &&
151  result[jss::result][jss::error] == NOT_FOUND);
152  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
153  }
154 
155  // Provide range without providing the `binary`
156  // field. (Tests parameter parsing)
157  {
158  auto const result = env.rpc(
159  COMMAND,
160  to_string(tx->getTransactionID()),
161  to_string(startLegSeq),
162  to_string(endLegSeq));
163 
164  BEAST_EXPECT(
165  result[jss::result][jss::status] == jss::error &&
166  result[jss::result][jss::error] == NOT_FOUND);
167 
168  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
169  }
170 
171  // Provide range without providing the `binary`
172  // field. (Tests parameter parsing)
173  {
174  auto const result = env.rpc(
175  COMMAND,
176  to_string(tx->getTransactionID()),
177  to_string(startLegSeq),
178  to_string(deletedLedger - 1));
179 
180  BEAST_EXPECT(
181  result[jss::result][jss::status] == jss::error &&
182  result[jss::result][jss::error] == NOT_FOUND);
183 
184  BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
185  }
186 
187  // Provide range without providing the `binary`
188  // field. (Tests parameter parsing)
189  {
190  auto const result = env.rpc(
191  COMMAND,
192  to_string(txns[0]->getTransactionID()),
193  to_string(startLegSeq),
194  to_string(deletedLedger - 1));
195 
196  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
197  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
198  }
199 
200  // Provide an invalid range: (min > max)
201  {
202  auto const result = env.rpc(
203  COMMAND,
204  to_string(tx->getTransactionID()),
205  BINARY,
206  to_string(deletedLedger - 1),
207  to_string(startLegSeq));
208 
209  BEAST_EXPECT(
210  result[jss::result][jss::status] == jss::error &&
211  result[jss::result][jss::error] == INVALID);
212 
213  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
214  }
215 
216  // Provide an invalid range: (min < 0)
217  {
218  auto const result = env.rpc(
219  COMMAND,
220  to_string(tx->getTransactionID()),
221  BINARY,
222  to_string(-1),
223  to_string(deletedLedger - 1));
224 
225  BEAST_EXPECT(
226  result[jss::result][jss::status] == jss::error &&
227  result[jss::result][jss::error] == INVALID);
228 
229  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
230  }
231 
232  // Provide an invalid range: (min < 0, max < 0)
233  {
234  auto const result = env.rpc(
235  COMMAND,
236  to_string(tx->getTransactionID()),
237  BINARY,
238  to_string(-20),
239  to_string(-10));
240 
241  BEAST_EXPECT(
242  result[jss::result][jss::status] == jss::error &&
243  result[jss::result][jss::error] == INVALID);
244 
245  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
246  }
247 
248  // Provide an invalid range: (only one value)
249  {
250  auto const result = env.rpc(
251  COMMAND,
252  to_string(tx->getTransactionID()),
253  BINARY,
254  to_string(20));
255 
256  BEAST_EXPECT(
257  result[jss::result][jss::status] == jss::error &&
258  result[jss::result][jss::error] == INVALID);
259 
260  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
261  }
262 
263  // Provide an invalid range: (only one value)
264  {
265  auto const result = env.rpc(
266  COMMAND, to_string(tx->getTransactionID()), to_string(20));
267 
268  // Since we only provided one value for the range,
269  // the interface parses it as a false binary flag,
270  // as single-value ranges are not accepted. Since
271  // the error this causes differs depending on the platform
272  // we don't call out a specific error here.
273  BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
274 
275  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
276  }
277 
278  // Provide an invalid range: (max - min > 1000)
279  {
280  auto const result = env.rpc(
281  COMMAND,
282  to_string(tx->getTransactionID()),
283  BINARY,
284  to_string(startLegSeq),
285  to_string(startLegSeq + 1001));
286 
287  BEAST_EXPECT(
288  result[jss::result][jss::status] == jss::error &&
289  result[jss::result][jss::error] == EXCESSIVE);
290 
291  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
292  }
293  }
294 
295  void
297  {
298  testcase("ctid_range");
299 
300  using namespace test::jtx;
301  using std::to_string;
302 
303  const char* COMMAND = jss::tx.c_str();
304  const char* BINARY = jss::binary.c_str();
305  const char* NOT_FOUND = RPC::get_error_info(rpcTXN_NOT_FOUND).token;
307  const char* EXCESSIVE =
309 
310  Env env{*this, makeNetworkConfig(11111)};
311  uint32_t netID = env.app().config().NETWORK_ID;
312 
313  auto const alice = Account("alice");
314  env.fund(XRP(1000), alice);
315  env.close();
316 
319  auto const startLegSeq = env.current()->info().seq;
320  for (int i = 0; i < 750; ++i)
321  {
322  env(noop(alice));
323  txns.emplace_back(env.tx());
324  env.close();
325  metas.emplace_back(
326  env.closed()->txRead(env.tx()->getTransactionID()).second);
327  }
328  auto const endLegSeq = env.closed()->info().seq;
329 
330  // Find the existing transactions
331  for (size_t i = 0; i < txns.size(); ++i)
332  {
333  auto const& tx = txns[i];
334  auto const& meta = metas[i];
335  uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
336  auto const result = env.rpc(
337  COMMAND,
338  *RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
339  BINARY,
340  to_string(startLegSeq),
341  to_string(endLegSeq));
342 
343  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
344  BEAST_EXPECT(
345  result[jss::result][jss::tx] ==
346  strHex(tx->getSerializer().getData()));
347  BEAST_EXPECT(
348  result[jss::result][jss::meta] ==
349  strHex(meta->getSerializer().getData()));
350  }
351 
352  auto const tx = env.jt(noop(alice), seq(env.seq(alice))).stx;
353  auto const ctid =
354  *RPC::encodeCTID(endLegSeq, tx->getSeqProxy().value(), netID);
355  for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
356  {
357  auto const result = env.rpc(
358  COMMAND,
359  ctid,
360  BINARY,
361  to_string(startLegSeq),
362  to_string(endLegSeq + deltaEndSeq));
363 
364  BEAST_EXPECT(
365  result[jss::result][jss::status] == jss::error &&
366  result[jss::result][jss::error] == NOT_FOUND);
367 
368  if (deltaEndSeq)
369  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
370  else
371  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
372  }
373 
374  // Find transactions outside of provided range.
375  for (size_t i = 0; i < txns.size(); ++i)
376  {
377  // auto const& tx = txns[i];
378  auto const& meta = metas[i];
379  uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
380  auto const result = env.rpc(
381  COMMAND,
382  *RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
383  BINARY,
384  to_string(endLegSeq + 1),
385  to_string(endLegSeq + 100));
386 
387  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
388  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
389  }
390 
391  const auto deletedLedger = (startLegSeq + endLegSeq) / 2;
392  {
393  // Remove one of the ledgers from the database directly
394  dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
395  ->deleteTransactionByLedgerSeq(deletedLedger);
396  }
397 
398  for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
399  {
400  auto const result = env.rpc(
401  COMMAND,
402  ctid,
403  BINARY,
404  to_string(startLegSeq),
405  to_string(endLegSeq + deltaEndSeq));
406 
407  BEAST_EXPECT(
408  result[jss::result][jss::status] == jss::error &&
409  result[jss::result][jss::error] == NOT_FOUND);
410  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
411  }
412 
413  // Provide range without providing the `binary`
414  // field. (Tests parameter parsing)
415  {
416  auto const result = env.rpc(
417  COMMAND, ctid, to_string(startLegSeq), to_string(endLegSeq));
418 
419  BEAST_EXPECT(
420  result[jss::result][jss::status] == jss::error &&
421  result[jss::result][jss::error] == NOT_FOUND);
422 
423  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
424  }
425 
426  // Provide range without providing the `binary`
427  // field. (Tests parameter parsing)
428  {
429  auto const result = env.rpc(
430  COMMAND,
431  ctid,
432  to_string(startLegSeq),
433  to_string(deletedLedger - 1));
434 
435  BEAST_EXPECT(
436  result[jss::result][jss::status] == jss::error &&
437  result[jss::result][jss::error] == NOT_FOUND);
438 
439  BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
440  }
441 
442  // Provide range without providing the `binary`
443  // field. (Tests parameter parsing)
444  {
445  auto const& meta = metas[0];
446  uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
447  auto const result = env.rpc(
448  COMMAND,
449  *RPC::encodeCTID(endLegSeq, txnIdx, netID),
450  to_string(startLegSeq),
451  to_string(deletedLedger - 1));
452 
453  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
454  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
455  }
456 
457  // Provide an invalid range: (min > max)
458  {
459  auto const result = env.rpc(
460  COMMAND,
461  ctid,
462  BINARY,
463  to_string(deletedLedger - 1),
464  to_string(startLegSeq));
465 
466  BEAST_EXPECT(
467  result[jss::result][jss::status] == jss::error &&
468  result[jss::result][jss::error] == INVALID);
469 
470  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
471  }
472 
473  // Provide an invalid range: (min < 0)
474  {
475  auto const result = env.rpc(
476  COMMAND,
477  ctid,
478  BINARY,
479  to_string(-1),
480  to_string(deletedLedger - 1));
481 
482  BEAST_EXPECT(
483  result[jss::result][jss::status] == jss::error &&
484  result[jss::result][jss::error] == INVALID);
485 
486  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
487  }
488 
489  // Provide an invalid range: (min < 0, max < 0)
490  {
491  auto const result =
492  env.rpc(COMMAND, ctid, BINARY, to_string(-20), to_string(-10));
493 
494  BEAST_EXPECT(
495  result[jss::result][jss::status] == jss::error &&
496  result[jss::result][jss::error] == INVALID);
497 
498  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
499  }
500 
501  // Provide an invalid range: (only one value)
502  {
503  auto const result = env.rpc(COMMAND, ctid, BINARY, to_string(20));
504 
505  BEAST_EXPECT(
506  result[jss::result][jss::status] == jss::error &&
507  result[jss::result][jss::error] == INVALID);
508 
509  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
510  }
511 
512  // Provide an invalid range: (only one value)
513  {
514  auto const result = env.rpc(COMMAND, ctid, to_string(20));
515 
516  // Since we only provided one value for the range,
517  // the interface parses it as a false binary flag,
518  // as single-value ranges are not accepted. Since
519  // the error this causes differs depending on the platform
520  // we don't call out a specific error here.
521  BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
522 
523  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
524  }
525 
526  // Provide an invalid range: (max - min > 1000)
527  {
528  auto const result = env.rpc(
529  COMMAND,
530  ctid,
531  BINARY,
532  to_string(startLegSeq),
533  to_string(startLegSeq + 1001));
534 
535  BEAST_EXPECT(
536  result[jss::result][jss::status] == jss::error &&
537  result[jss::result][jss::error] == EXCESSIVE);
538 
539  BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
540  }
541  }
542 
543  void
545  {
546  testcase("ctid_validation");
547 
548  using namespace test::jtx;
549  using std::to_string;
550 
551  Env env{*this, makeNetworkConfig(11111)};
552 
553  // Test case 1: Valid input values
554  auto const expected11 = std::optional<std::string>("CFFFFFFFFFFFFFFF");
555  BEAST_EXPECT(
556  RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU) == expected11);
557  auto const expected12 = std::optional<std::string>("C000000000000000");
558  BEAST_EXPECT(RPC::encodeCTID(0, 0, 0) == expected12);
559  auto const expected13 = std::optional<std::string>("C000000100020003");
560  BEAST_EXPECT(RPC::encodeCTID(1U, 2U, 3U) == expected13);
561  auto const expected14 = std::optional<std::string>("C0CA2AA7326FFFFF");
562  BEAST_EXPECT(RPC::encodeCTID(13249191UL, 12911U, 65535U) == expected14);
563 
564  // Test case 2: ledger_seq greater than 0xFFFFFFF
565  BEAST_EXPECT(!RPC::encodeCTID(0x1000'0000UL, 0xFFFFU, 0xFFFFU));
566 
567  // Test case 3: txn_index greater than 0xFFFF
568  // this test case is impossible in c++ due to the type, left in for
569  // completeness
570  auto const expected3 = std::optional<std::string>("CFFFFFFF0000FFFF");
571  BEAST_EXPECT(
572  RPC::encodeCTID(0x0FFF'FFFF, (uint16_t)0x10000, 0xFFFF) ==
573  expected3);
574 
575  // Test case 4: network_id greater than 0xFFFF
576  // this test case is impossible in c++ due to the type, left in for
577  // completeness
578  auto const expected4 = std::optional<std::string>("CFFFFFFFFFFF0000");
579  BEAST_EXPECT(
580  RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, (uint16_t)0x1000'0U) ==
581  expected4);
582 
583  // Test case 5: Valid input values
584  auto const expected51 =
585  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
586  std::make_tuple(0, 0, 0));
587  BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51);
588  auto const expected52 =
589  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
590  std::make_tuple(1U, 2U, 3U));
591  BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52);
592  auto const expected53 =
593  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
594  std::make_tuple(13249191UL, 12911U, 49221U));
595  BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53);
596 
597  // Test case 6: ctid not a string or big int
598  BEAST_EXPECT(!RPC::decodeCTID(0xCFF));
599 
600  // Test case 7: ctid not a hexadecimal string
601  BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG"));
602 
603  // Test case 8: ctid not exactly 16 nibbles
604  BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF"));
605 
606  // Test case 9: ctid too large to be a valid CTID value
607  BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF"));
608 
609  // Test case 10: ctid doesn't start with a C nibble
610  BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF"));
611 
612  // Test case 11: Valid input values
613  BEAST_EXPECT(
614  (RPC::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) ==
615  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
616  std::make_tuple(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU))));
617  BEAST_EXPECT(
618  (RPC::decodeCTID(0xC000'0000'0000'0000ULL) ==
619  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
620  std::make_tuple(0, 0, 0))));
621  BEAST_EXPECT(
622  (RPC::decodeCTID(0xC000'0001'0002'0003ULL) ==
624  std::make_tuple(1U, 2U, 3U))));
625  BEAST_EXPECT(
626  (RPC::decodeCTID(0xC0CA'2AA7'326F'C045ULL) ==
627  std::optional<std::tuple<int32_t, uint16_t, uint16_t>>(
628  std::make_tuple(1324'9191UL, 12911U, 49221U))));
629 
630  // Test case 12: ctid not exactly 16 nibbles
631  BEAST_EXPECT(!RPC::decodeCTID(0xC003'FFFF'FFFF'FFF));
632 
633  // Test case 13: ctid too large to be a valid CTID value
634  // this test case is not possible in c++ because it would overflow the
635  // type, left in for completeness
636  // BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
637 
638  // Test case 14: ctid doesn't start with a C nibble
639  BEAST_EXPECT(!RPC::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL));
640  }
641 
642  void
643  testCTIDRPC(FeatureBitset features)
644  {
645  testcase("ctid_rpc");
646 
647  using namespace test::jtx;
648 
649  // test that the ctid AND the hash are in the response
650  {
651  Env env{*this, makeNetworkConfig(11111)};
652  uint32_t netID = env.app().config().NETWORK_ID;
653 
654  auto const alice = Account("alice");
655  auto const bob = Account("bob");
656 
657  auto const startLegSeq = env.current()->info().seq;
658  env.fund(XRP(10000), alice, bob);
659  env(pay(alice, bob, XRP(10)));
660  env.close();
661 
662  auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
663  Json::Value jsonTx;
664  jsonTx[jss::binary] = false;
665  jsonTx[jss::ctid] = ctid;
666  jsonTx[jss::id] = 1;
667  auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
668  BEAST_EXPECT(jrr[jss::ctid] == ctid);
669  BEAST_EXPECT(jrr[jss::hash]);
670  }
671 
672  // test that if the network is 65535 the ctid is not in the response
673  {
674  Env env{*this, makeNetworkConfig(65535)};
675  uint32_t netID = env.app().config().NETWORK_ID;
676 
677  auto const alice = Account("alice");
678  auto const bob = Account("bob");
679 
680  auto const startLegSeq = env.current()->info().seq;
681  env.fund(XRP(10000), alice, bob);
682  env(pay(alice, bob, XRP(10)));
683  env.close();
684 
685  auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
686  Json::Value jsonTx;
687  jsonTx[jss::binary] = false;
688  jsonTx[jss::ctid] = ctid;
689  jsonTx[jss::id] = 1;
690  auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
691  BEAST_EXPECT(!jrr[jss::ctid]);
692  BEAST_EXPECT(jrr[jss::hash]);
693  }
694  }
695 
696  void
697  testRequest(FeatureBitset features, unsigned apiVersion)
698  {
699  testcase("Test Request API version " + std::to_string(apiVersion));
700 
701  using namespace test::jtx;
702  using std::to_string;
703 
704  Env env{*this};
705  Account const alice{"alice"};
706  Account const alie{"alie"};
707  Account const gw{"gw"};
708  auto const USD{gw["USD"]};
709 
710  env.fund(XRP(1000000), alice, gw);
711  env.close();
712 
713  // AccountSet
714  env(noop(alice));
715 
716  // Payment
717  env(pay(alice, gw, XRP(100)));
718 
719  std::shared_ptr<STTx const> txn = env.tx();
720  env.close();
721  std::shared_ptr<STObject const> meta =
722  env.closed()->txRead(env.tx()->getTransactionID()).second;
723 
724  Json::Value expected = txn->getJson(JsonOptions::none);
725  expected[jss::DeliverMax] = expected[jss::Amount];
726  if (apiVersion > 1)
727  {
728  expected.removeMember(jss::hash);
729  expected.removeMember(jss::Amount);
730  }
731 
732  Json::Value const result = {[&env, txn, apiVersion]() {
733  Json::Value params{Json::objectValue};
734  params[jss::transaction] = to_string(txn->getTransactionID());
735  params[jss::binary] = false;
736  params[jss::api_version] = apiVersion;
737  return env.client().invoke("tx", params);
738  }()};
739 
740  BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
741  if (apiVersion > 1)
742  {
743  BEAST_EXPECT(
744  result[jss::result][jss::close_time_iso] ==
745  "2000-01-01T00:00:20Z");
746  BEAST_EXPECT(
747  result[jss::result][jss::hash] ==
748  to_string(txn->getTransactionID()));
749  BEAST_EXPECT(result[jss::result][jss::validated] == true);
750  BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4);
751  BEAST_EXPECT(
752  result[jss::result][jss::ledger_hash] ==
753  "B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
754  "D2");
755  }
756 
757  for (auto memberIt = expected.begin(); memberIt != expected.end();
758  memberIt++)
759  {
760  std::string const name = memberIt.memberName();
761  auto const& result_transaction =
762  (apiVersion > 1 ? result[jss::result][jss::tx_json]
763  : result[jss::result]);
764  if (BEAST_EXPECT(result_transaction.isMember(name)))
765  {
766  auto const received = result_transaction[name];
767  BEAST_EXPECTS(
768  received == *memberIt,
769  "Transaction contains \n\"" + name + "\": " //
770  + to_string(received) //
771  + " but expected " //
772  + to_string(expected));
773  }
774  }
775  }
776 
777 public:
778  void
779  run() override
780  {
781  using namespace test::jtx;
782  FeatureBitset const all{supported_amendments()};
783  testWithFeats(all);
784  }
785 
786  void
787  testWithFeats(FeatureBitset features)
788  {
789  testRangeRequest(features);
790  testRangeCTIDRequest(features);
791  testCTIDValidation(features);
792  testCTIDRPC(features);
793  test::jtx::forAllApiVersions(
794  std::bind_front(&Transaction_test::testRequest, this, features));
795  }
796 };
797 
798 BEAST_DEFINE_TESTSUITE(Transaction, rpc, ripple);
799 
800 } // namespace ripple
ripple::SQLiteDatabase
Definition: SQLiteDatabase.h:27
std::make_tuple
T make_tuple(T... args)
ripple::RPC::get_error_info
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:172
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::rpcEXCESSIVE_LGR_RANGE
@ rpcEXCESSIVE_LGR_RANGE
Definition: ErrorCodes.h:135
tuple
ripple::Transaction_test::testCTIDValidation
void testCTIDValidation(FeatureBitset features)
Definition: Transaction_test.cpp:544
ripple::RPC::decodeCTID
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(const T ctid) noexcept
Definition: CTID.h:54
ripple::Transaction_test
Definition: Transaction_test.cpp:33
ripple::Transaction_test::testRangeCTIDRequest
void testRangeCTIDRequest(FeatureBitset features)
Definition: Transaction_test.cpp:296
std::to_string
T to_string(T... args)
ripple::rpcTXN_NOT_FOUND
@ rpcTXN_NOT_FOUND
Definition: ErrorCodes.h:80
ripple::RPC::encodeCTID
std::optional< std::string > encodeCTID(uint32_t ledger_seq, uint16_t txn_index, uint16_t network_id) noexcept
Definition: CTID.h:34
ripple::sfTransactionIndex
const SF_UINT32 sfTransactionIndex
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Transaction_test::testRangeRequest
void testRangeRequest(FeatureBitset features)
Definition: Transaction_test.cpp:46
ripple::INVALID
@ INVALID
Definition: Transaction.h:48
ripple::FeatureBitset
Definition: Feature.h:113
optional
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
ripple::rpcINVALID_LGR_RANGE
@ rpcINVALID_LGR_RANGE
Definition: ErrorCodes.h:136
std::unique_ptr
STL class.
ripple::RPC::ErrorInfo::token
Json::StaticString token
Definition: ErrorCodes.h:201
ripple::Transaction_test::makeNetworkConfig
std::unique_ptr< Config > makeNetworkConfig(uint32_t networkID)
Definition: Transaction_test.cpp:36