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