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