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