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");
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 // this test case is impossible in c++ due to the type, left in for
574 // completeness
575 auto const expected3 = std::optional<std::string>("CFFFFFFF0000FFFF");
576 BEAST_EXPECT(
577 RPC::encodeCTID(0x0FFF'FFFF, (uint16_t)0x10000, 0xFFFF) ==
578 expected3);
579
580 // Test case 4: network_id greater than 0xFFFF
581 // this test case is impossible in c++ due to the type, left in for
582 // completeness
583 auto const expected4 = std::optional<std::string>("CFFFFFFFFFFF0000");
584 BEAST_EXPECT(
585 RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, (uint16_t)0x1000'0U) ==
586 expected4);
587
588 // Test case 5: Valid input values
589 auto const expected51 =
591 std::make_tuple(0, 0, 0));
592 BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51);
593 auto const expected52 =
595 std::make_tuple(1U, 2U, 3U));
596 BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52);
597 auto const expected53 =
599 std::make_tuple(13249191UL, 12911U, 49221U));
600 BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53);
601
602 // Test case 6: ctid not a string or big int
603 BEAST_EXPECT(!RPC::decodeCTID(0xCFF));
604
605 // Test case 7: ctid not a hexadecimal string
606 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG"));
607
608 // Test case 8: ctid not exactly 16 nibbles
609 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF"));
610
611 // Test case 9: ctid too large to be a valid CTID value
612 BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF"));
613
614 // Test case 10: ctid doesn't start with a C nibble
615 BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF"));
616
617 // Test case 11: Valid input values
618 BEAST_EXPECT(
619 (RPC::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) ==
621 std::make_tuple(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU))));
622 BEAST_EXPECT(
623 (RPC::decodeCTID(0xC000'0000'0000'0000ULL) ==
625 std::make_tuple(0, 0, 0))));
626 BEAST_EXPECT(
627 (RPC::decodeCTID(0xC000'0001'0002'0003ULL) ==
629 std::make_tuple(1U, 2U, 3U))));
630 BEAST_EXPECT(
631 (RPC::decodeCTID(0xC0CA'2AA7'326F'C045ULL) ==
633 std::make_tuple(1324'9191UL, 12911U, 49221U))));
634
635 // Test case 12: ctid not exactly 16 nibbles
636 BEAST_EXPECT(!RPC::decodeCTID(0xC003'FFFF'FFFF'FFF));
637
638 // Test case 13: ctid too large to be a valid CTID value
639 // this test case is not possible in c++ because it would overflow the
640 // type, left in for completeness
641 // BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
642
643 // Test case 14: ctid doesn't start with a C nibble
644 BEAST_EXPECT(!RPC::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL));
645 }
646
647 void
649 {
650 testcase("ctid_rpc");
651
652 using namespace test::jtx;
653
654 // test that the ctid AND the hash are in the response
655 {
656 Env env{*this, makeNetworkConfig(11111)};
657 uint32_t netID = env.app().config().NETWORK_ID;
658
659 auto const alice = Account("alice");
660 auto const bob = Account("bob");
661
662 auto const startLegSeq = env.current()->info().seq;
663 env.fund(XRP(10000), alice, bob);
664 env(pay(alice, bob, XRP(10)));
665 env.close();
666
667 auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
668 Json::Value jsonTx;
669 jsonTx[jss::binary] = false;
670 jsonTx[jss::ctid] = ctid;
671 jsonTx[jss::id] = 1;
672 auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
673 BEAST_EXPECT(jrr[jss::ctid] == ctid);
674 BEAST_EXPECT(jrr[jss::hash]);
675 }
676
677 // test querying with mixed case ctid
678 {
679 Env env{*this, makeNetworkConfig(11111)};
680 std::uint32_t const netID = env.app().config().NETWORK_ID;
681
682 Account const alice = Account("alice");
683 Account const bob = Account("bob");
684
685 std::uint32_t const startLegSeq = env.current()->info().seq;
686 env.fund(XRP(10000), alice, bob);
687 env(pay(alice, bob, XRP(10)));
688 env.close();
689
690 std::string const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
691 auto isUpper = [](char c) { return std::isupper(c) != 0; };
692
693 // Verify that there are at least two upper case letters in ctid and
694 // test a mixed case
695 if (BEAST_EXPECT(
696 std::count_if(ctid.begin(), ctid.end(), isUpper) > 1))
697 {
698 // Change the first upper case letter to lower case.
699 std::string mixedCase = ctid;
700 {
701 auto const iter = std::find_if(
702 mixedCase.begin(), mixedCase.end(), isUpper);
703 *iter = std::tolower(*iter);
704 }
705 BEAST_EXPECT(ctid != mixedCase);
706
707 Json::Value jsonTx;
708 jsonTx[jss::binary] = false;
709 jsonTx[jss::ctid] = mixedCase;
710 jsonTx[jss::id] = 1;
711 Json::Value const jrr =
712 env.rpc("json", "tx", to_string(jsonTx))[jss::result];
713 BEAST_EXPECT(jrr[jss::ctid] == ctid);
714 BEAST_EXPECT(jrr[jss::hash]);
715 }
716 }
717
718 // test that if the network is 65535 the ctid is not in the response
719 {
720 Env env{*this, makeNetworkConfig(65535)};
721 uint32_t netID = env.app().config().NETWORK_ID;
722
723 auto const alice = Account("alice");
724 auto const bob = Account("bob");
725
726 auto const startLegSeq = env.current()->info().seq;
727 env.fund(XRP(10000), alice, bob);
728 env(pay(alice, bob, XRP(10)));
729 env.close();
730
731 auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
732 Json::Value jsonTx;
733 jsonTx[jss::binary] = false;
734 jsonTx[jss::ctid] = ctid;
735 jsonTx[jss::id] = 1;
736 auto jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
737 BEAST_EXPECT(!jrr[jss::ctid]);
738 BEAST_EXPECT(jrr[jss::hash]);
739 }
740 }
741
742 void
743 testRequest(FeatureBitset features, unsigned apiVersion)
744 {
745 testcase("Test Request API version " + std::to_string(apiVersion));
746
747 using namespace test::jtx;
748 using std::to_string;
749
750 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
751 cfg->FEES.reference_fee = 10;
752 return cfg;
753 })};
754 Account const alice{"alice"};
755 Account const alie{"alie"};
756 Account const gw{"gw"};
757 auto const USD{gw["USD"]};
758
759 env.fund(XRP(1000000), alice, gw);
760 env.close();
761
762 // AccountSet
763 env(noop(alice));
764
765 // Payment
766 env(pay(alice, gw, XRP(100)));
767
768 std::shared_ptr<STTx const> txn = env.tx();
769 env.close();
771 env.closed()->txRead(env.tx()->getTransactionID()).second;
772
773 Json::Value expected = txn->getJson(JsonOptions::none);
774 expected[jss::DeliverMax] = expected[jss::Amount];
775 if (apiVersion > 1)
776 {
777 expected.removeMember(jss::hash);
778 expected.removeMember(jss::Amount);
779 }
780
781 Json::Value const result = {[&env, txn, apiVersion]() {
783 params[jss::transaction] = to_string(txn->getTransactionID());
784 params[jss::binary] = false;
785 params[jss::api_version] = apiVersion;
786 return env.client().invoke("tx", params);
787 }()};
788
789 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
790 if (apiVersion > 1)
791 {
792 BEAST_EXPECT(
793 result[jss::result][jss::close_time_iso] ==
794 "2000-01-01T00:00:20Z");
795 BEAST_EXPECT(
796 result[jss::result][jss::hash] ==
797 to_string(txn->getTransactionID()));
798 BEAST_EXPECT(result[jss::result][jss::validated] == true);
799 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4);
800 BEAST_EXPECT(
801 result[jss::result][jss::ledger_hash] ==
802 "B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
803 "D2");
804 }
805
806 for (auto memberIt = expected.begin(); memberIt != expected.end();
807 memberIt++)
808 {
809 std::string const name = memberIt.memberName();
810 auto const& result_transaction =
811 (apiVersion > 1 ? result[jss::result][jss::tx_json]
812 : result[jss::result]);
813 if (BEAST_EXPECT(result_transaction.isMember(name)))
814 {
815 auto const received = result_transaction[name];
816 BEAST_EXPECTS(
817 received == *memberIt,
818 "Transaction contains \n\"" + name + "\": " //
819 + to_string(received) //
820 + " but expected " //
821 + to_string(expected));
822 }
823 }
824 }
825
826 void
827 testBinaryRequest(unsigned apiVersion)
828 {
829 testcase(
830 "Test binary request API version " + std::to_string(apiVersion));
831
832 using namespace test::jtx;
833 using std::to_string;
834
835 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
836 cfg->FEES.reference_fee = 10;
837 return cfg;
838 })};
839 Account const alice{"alice"};
840 Account const gw{"gw"};
841 auto const USD{gw["USD"]};
842
843 env.fund(XRP(1000000), alice, gw);
844 std::shared_ptr<STTx const> const txn = env.tx();
845 BEAST_EXPECT(
846 to_string(txn->getTransactionID()) ==
847 "3F8BDE5A5F82C4F4708E5E9255B713E303E6E1A371FD5C7A704AFD1387C23981");
848 env.close();
850 env.closed()->txRead(txn->getTransactionID()).second;
851
852 std::string const expected_tx_blob = serializeHex(*txn);
853 std::string const expected_meta_blob = serializeHex(*meta);
854
855 Json::Value const result = [&env, txn, apiVersion]() {
857 params[jss::transaction] = to_string(txn->getTransactionID());
858 params[jss::binary] = true;
859 params[jss::api_version] = apiVersion;
860 return env.client().invoke("tx", params);
861 }();
862
863 if (BEAST_EXPECT(result[jss::status] == "success"))
864 {
865 BEAST_EXPECT(result[jss::result][jss::status] == "success");
866 BEAST_EXPECT(result[jss::result][jss::validated] == true);
867 BEAST_EXPECT(
868 result[jss::result][jss::hash] ==
869 to_string(txn->getTransactionID()));
870 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 3);
871 BEAST_EXPECT(result[jss::result][jss::ctid] == "C000000300030000");
872
873 if (apiVersion > 1)
874 {
875 BEAST_EXPECT(
876 result[jss::result][jss::tx_blob] == expected_tx_blob);
877 BEAST_EXPECT(
878 result[jss::result][jss::meta_blob] == expected_meta_blob);
879 BEAST_EXPECT(
880 result[jss::result][jss::ledger_hash] ==
881 "2D5150E5A5AA436736A732291E437ABF01BC9E206C2DF3C77C4F856915"
882 "7905AA");
883 BEAST_EXPECT(
884 result[jss::result][jss::close_time_iso] ==
885 "2000-01-01T00:00:10Z");
886 }
887 else
888 {
889 BEAST_EXPECT(result[jss::result][jss::tx] == expected_tx_blob);
890 BEAST_EXPECT(
891 result[jss::result][jss::meta] == expected_meta_blob);
892 BEAST_EXPECT(result[jss::result][jss::date] == 10);
893 }
894 }
895 }
896
897public:
898 void
899 run() override
900 {
901 using namespace test::jtx;
904
905 FeatureBitset const all{supported_amendments()};
907 }
908
909 void
911 {
912 testRangeRequest(features);
913 testRangeCTIDRequest(features);
914 testCTIDValidation(features);
915 testCTIDRPC(features);
918 }
919};
920
921BEAST_DEFINE_TESTSUITE(Transaction, rpc, ripple);
922
923} // 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:54
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:177
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:630
T size(T... args)
Json::StaticString token
Definition: ErrorCodes.h:210
T to_string(T... args)