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