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 char const* COMMAND = jss::tx.c_str();
59 char const* BINARY = jss::binary.c_str();
60 char const* NOT_FOUND = RPC::get_error_info(rpcTXN_NOT_FOUND).token;
62 char const* 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 auto const 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 char const* COMMAND = jss::tx.c_str();
309 char const* BINARY = jss::binary.c_str();
310 char const* NOT_FOUND = RPC::get_error_info(rpcTXN_NOT_FOUND).token;
312 char const* 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 = *RPC::encodeCTID(endLegSeq, tx->getSeqValue(), netID);
359 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
360 {
361 auto const result = env.rpc(
362 COMMAND,
363 ctid,
364 BINARY,
365 to_string(startLegSeq),
366 to_string(endLegSeq + deltaEndSeq));
367
368 BEAST_EXPECT(
369 result[jss::result][jss::status] == jss::error &&
370 result[jss::result][jss::error] == NOT_FOUND);
371
372 if (deltaEndSeq)
373 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
374 else
375 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
376 }
377
378 // Find transactions outside of provided range.
379 for (size_t i = 0; i < txns.size(); ++i)
380 {
381 // auto const& tx = txns[i];
382 auto const& meta = metas[i];
383 uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
384 auto const result = env.rpc(
385 COMMAND,
386 *RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
387 BINARY,
388 to_string(endLegSeq + 1),
389 to_string(endLegSeq + 100));
390
391 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
392 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
393 }
394
395 auto const deletedLedger = (startLegSeq + endLegSeq) / 2;
396 {
397 // Remove one of the ledgers from the database directly
398 dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
399 ->deleteTransactionByLedgerSeq(deletedLedger);
400 }
401
402 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
403 {
404 auto const result = env.rpc(
405 COMMAND,
406 ctid,
407 BINARY,
408 to_string(startLegSeq),
409 to_string(endLegSeq + deltaEndSeq));
410
411 BEAST_EXPECT(
412 result[jss::result][jss::status] == jss::error &&
413 result[jss::result][jss::error] == NOT_FOUND);
414 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
415 }
416
417 // Provide range without providing the `binary`
418 // field. (Tests parameter parsing)
419 {
420 auto const result = env.rpc(
421 COMMAND, ctid, to_string(startLegSeq), to_string(endLegSeq));
422
423 BEAST_EXPECT(
424 result[jss::result][jss::status] == jss::error &&
425 result[jss::result][jss::error] == NOT_FOUND);
426
427 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
428 }
429
430 // Provide range without providing the `binary`
431 // field. (Tests parameter parsing)
432 {
433 auto const result = env.rpc(
434 COMMAND,
435 ctid,
436 to_string(startLegSeq),
437 to_string(deletedLedger - 1));
438
439 BEAST_EXPECT(
440 result[jss::result][jss::status] == jss::error &&
441 result[jss::result][jss::error] == NOT_FOUND);
442
443 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
444 }
445
446 // Provide range without providing the `binary`
447 // field. (Tests parameter parsing)
448 {
449 auto const& meta = metas[0];
450 uint32_t txnIdx = meta->getFieldU32(sfTransactionIndex);
451 auto const result = env.rpc(
452 COMMAND,
453 *RPC::encodeCTID(endLegSeq, txnIdx, netID),
454 to_string(startLegSeq),
455 to_string(deletedLedger - 1));
456
457 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
458 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
459 }
460
461 // Provide an invalid range: (min > max)
462 {
463 auto const result = env.rpc(
464 COMMAND,
465 ctid,
466 BINARY,
467 to_string(deletedLedger - 1),
468 to_string(startLegSeq));
469
470 BEAST_EXPECT(
471 result[jss::result][jss::status] == jss::error &&
472 result[jss::result][jss::error] == INVALID);
473
474 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
475 }
476
477 // Provide an invalid range: (min < 0)
478 {
479 auto const result = env.rpc(
480 COMMAND,
481 ctid,
482 BINARY,
483 to_string(-1),
484 to_string(deletedLedger - 1));
485
486 BEAST_EXPECT(
487 result[jss::result][jss::status] == jss::error &&
488 result[jss::result][jss::error] == INVALID);
489
490 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
491 }
492
493 // Provide an invalid range: (min < 0, max < 0)
494 {
495 auto const result =
496 env.rpc(COMMAND, ctid, BINARY, to_string(-20), to_string(-10));
497
498 BEAST_EXPECT(
499 result[jss::result][jss::status] == jss::error &&
500 result[jss::result][jss::error] == INVALID);
501
502 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
503 }
504
505 // Provide an invalid range: (only one value)
506 {
507 auto const result = env.rpc(COMMAND, ctid, BINARY, to_string(20));
508
509 BEAST_EXPECT(
510 result[jss::result][jss::status] == jss::error &&
511 result[jss::result][jss::error] == INVALID);
512
513 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
514 }
515
516 // Provide an invalid range: (only one value)
517 {
518 auto const result = env.rpc(COMMAND, ctid, to_string(20));
519
520 // Since we only provided one value for the range,
521 // the interface parses it as a false binary flag,
522 // as single-value ranges are not accepted. Since
523 // the error this causes differs depending on the platform
524 // we don't call out a specific error here.
525 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
526
527 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
528 }
529
530 // Provide an invalid range: (max - min > 1000)
531 {
532 auto const result = env.rpc(
533 COMMAND,
534 ctid,
535 BINARY,
536 to_string(startLegSeq),
537 to_string(startLegSeq + 1001));
538
539 BEAST_EXPECT(
540 result[jss::result][jss::status] == jss::error &&
541 result[jss::result][jss::error] == EXCESSIVE);
542
543 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
544 }
545 }
546
547 void
549 {
550 testcase("CTID Validation");
551
552 using namespace test::jtx;
553 using std::to_string;
554
555 Env env{*this, makeNetworkConfig(11111)};
556
557 // Test case 1: Valid input values
558 auto const expected11 = std::optional<std::string>("CFFFFFFFFFFFFFFF");
559 BEAST_EXPECT(
560 RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU) == expected11);
561 auto const expected12 = std::optional<std::string>("C000000000000000");
562 BEAST_EXPECT(RPC::encodeCTID(0, 0, 0) == expected12);
563 auto const expected13 = std::optional<std::string>("C000000100020003");
564 BEAST_EXPECT(RPC::encodeCTID(1U, 2U, 3U) == expected13);
565 auto const expected14 = std::optional<std::string>("C0CA2AA7326FFFFF");
566 BEAST_EXPECT(RPC::encodeCTID(13249191UL, 12911U, 65535U) == expected14);
567
568 // Test case 2: ledger_seq greater than 0xFFFFFFF
569 BEAST_EXPECT(!RPC::encodeCTID(0x1000'0000UL, 0xFFFFU, 0xFFFFU));
570
571 // Test case 3: txn_index greater than 0xFFFF
572 BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFF, 0x1'0000, 0xFFFF));
573
574 // Test case 4: network_id greater than 0xFFFF
575 BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0x1'0000U));
576
577 // Test case 5: Valid input values
578 auto const expected51 =
580 std::make_tuple(0, 0, 0));
581 BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51);
582 auto const expected52 =
584 std::make_tuple(1U, 2U, 3U));
585 BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52);
586 auto const expected53 =
588 std::make_tuple(13249191UL, 12911U, 49221U));
589 BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53);
590
591 // Test case 6: ctid not a string or big int
592 BEAST_EXPECT(!RPC::decodeCTID(0xCFF));
593
594 // Test case 7: ctid not a hexadecimal string
595 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG"));
596
597 // Test case 8: ctid not exactly 16 nibbles
598 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF"));
599
600 // Test case 9: ctid too large to be a valid CTID value
601 BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF"));
602
603 // Test case 10: ctid doesn't start with a C nibble
604 BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF"));
605
606 // Test case 11: Valid input values
607 BEAST_EXPECT(
608 (RPC::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) ==
610 std::make_tuple(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU))));
611 BEAST_EXPECT(
612 (RPC::decodeCTID(0xC000'0000'0000'0000ULL) ==
614 std::make_tuple(0, 0, 0))));
615 BEAST_EXPECT(
616 (RPC::decodeCTID(0xC000'0001'0002'0003ULL) ==
618 std::make_tuple(1U, 2U, 3U))));
619 BEAST_EXPECT(
620 (RPC::decodeCTID(0xC0CA'2AA7'326F'C045ULL) ==
622 std::make_tuple(1324'9191UL, 12911U, 49221U))));
623
624 // Test case 12: ctid not exactly 16 nibbles
625 BEAST_EXPECT(!RPC::decodeCTID(0xC003'FFFF'FFFF'FFF));
626
627 // Test case 13: ctid too large to be a valid CTID value
628 // this test case is not possible in c++ because it would overflow the
629 // type, left in for completeness
630 // BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
631
632 // Test case 14: ctid doesn't start with a C nibble
633 BEAST_EXPECT(!RPC::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL));
634 }
635
636 void
638 {
639 testcase("CTID RPC");
640
641 using namespace test::jtx;
642
643 // Use a Concise Transaction Identifier to request a transaction.
644 for (uint32_t netID : {11111, 65535, 65536})
645 {
646 Env env{*this, makeNetworkConfig(netID)};
647 BEAST_EXPECT(netID == env.app().config().NETWORK_ID);
648
649 auto const alice = Account("alice");
650 auto const bob = Account("bob");
651
652 auto const startLegSeq = env.current()->info().seq;
653 env.fund(XRP(10000), alice, bob);
654 env(pay(alice, bob, XRP(10)));
655 env.close();
656
657 auto const ctid = RPC::encodeCTID(startLegSeq, 0, netID);
658 if (netID > 0xFFFF)
659 {
660 // Concise transaction IDs do not support a network ID > 0xFFFF.
661 BEAST_EXPECT(ctid == std::nullopt);
662 continue;
663 }
664
665 Json::Value jsonTx;
666 jsonTx[jss::binary] = false;
667 jsonTx[jss::ctid] = *ctid;
668 jsonTx[jss::id] = 1;
669 auto const jrr =
670 env.rpc("json", "tx", to_string(jsonTx))[jss::result];
671 BEAST_EXPECT(jrr[jss::ctid] == ctid);
672 BEAST_EXPECT(jrr.isMember(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 // Using a hash to request the transaction, test the network ID
718 // boundary where the CTID is (not) in the response.
719 for (uint32_t netID : {2, 1024, 65535, 65536})
720 {
721 Env env{*this, makeNetworkConfig(netID)};
722 BEAST_EXPECT(netID == env.app().config().NETWORK_ID);
723
724 auto const alice = Account("alice");
725 auto const bob = Account("bob");
726
727 env.fund(XRP(10000), alice, bob);
728 env(pay(alice, bob, XRP(10)));
729 env.close();
730
731 auto const ledgerSeq = env.current()->info().seq;
732
733 env(noop(alice), ter(tesSUCCESS));
734 env.close();
735
736 Json::Value params;
737 params[jss::id] = 1;
738 auto const hash = env.tx()->getJson(JsonOptions::none)[jss::hash];
739 params[jss::transaction] = hash;
740 auto const jrr =
741 env.rpc("json", "tx", to_string(params))[jss::result];
742 BEAST_EXPECT(jrr[jss::hash] == hash);
743
744 BEAST_EXPECT(jrr.isMember(jss::ctid) == (netID <= 0xFFFF));
745 if (jrr.isMember(jss::ctid))
746 {
747 auto const ctid = RPC::encodeCTID(ledgerSeq, 0, netID);
748 BEAST_EXPECT(jrr[jss::ctid] == *ctid);
749 }
750 }
751
752 // test the wrong network ID was submitted
753 {
754 Env env{*this, makeNetworkConfig(21337)};
755 uint32_t netID = env.app().config().NETWORK_ID;
756
757 auto const alice = Account("alice");
758 auto const bob = Account("bob");
759
760 auto const startLegSeq = env.current()->info().seq;
761 env.fund(XRP(10000), alice, bob);
762 env(pay(alice, bob, XRP(10)));
763 env.close();
764
765 auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID + 1);
766 Json::Value jsonTx;
767 jsonTx[jss::binary] = false;
768 jsonTx[jss::ctid] = ctid;
769 jsonTx[jss::id] = 1;
770 auto const jrr =
771 env.rpc("json", "tx", to_string(jsonTx))[jss::result];
772 BEAST_EXPECT(jrr[jss::error] == "wrongNetwork");
773 BEAST_EXPECT(jrr[jss::error_code] == rpcWRONG_NETWORK);
774 BEAST_EXPECT(
775 jrr[jss::error_message] ==
776 "Wrong network. You should submit this request to a node "
777 "running on NetworkID: 21338");
778 }
779 }
780
781 void
782 testRequest(FeatureBitset features, unsigned apiVersion)
783 {
784 testcase("Test Request API version " + std::to_string(apiVersion));
785
786 using namespace test::jtx;
787 using std::to_string;
788
789 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
790 cfg->FEES.reference_fee = 10;
791 return cfg;
792 })};
793 Account const alice{"alice"};
794 Account const alie{"alie"};
795 Account const gw{"gw"};
796 auto const USD{gw["USD"]};
797
798 env.fund(XRP(1000000), alice, gw);
799 env.close();
800
801 // AccountSet
802 env(noop(alice));
803
804 // Payment
805 env(pay(alice, gw, XRP(100)));
806
807 std::shared_ptr<STTx const> txn = env.tx();
808 env.close();
810 env.closed()->txRead(env.tx()->getTransactionID()).second;
811
812 Json::Value expected = txn->getJson(JsonOptions::none);
813 expected[jss::DeliverMax] = expected[jss::Amount];
814 if (apiVersion > 1)
815 {
816 expected.removeMember(jss::hash);
817 expected.removeMember(jss::Amount);
818 }
819
820 Json::Value const result = {[&env, txn, apiVersion]() {
822 params[jss::transaction] = to_string(txn->getTransactionID());
823 params[jss::binary] = false;
824 params[jss::api_version] = apiVersion;
825 return env.client().invoke("tx", params);
826 }()};
827
828 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
829 if (apiVersion > 1)
830 {
831 BEAST_EXPECT(
832 result[jss::result][jss::close_time_iso] ==
833 "2000-01-01T00:00:20Z");
834 BEAST_EXPECT(
835 result[jss::result][jss::hash] ==
836 to_string(txn->getTransactionID()));
837 BEAST_EXPECT(result[jss::result][jss::validated] == true);
838 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4);
839 BEAST_EXPECT(
840 result[jss::result][jss::ledger_hash] ==
841 "B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
842 "D2");
843 }
844
845 for (auto memberIt = expected.begin(); memberIt != expected.end();
846 memberIt++)
847 {
848 std::string const name = memberIt.memberName();
849 auto const& result_transaction =
850 (apiVersion > 1 ? result[jss::result][jss::tx_json]
851 : result[jss::result]);
852 if (BEAST_EXPECT(result_transaction.isMember(name)))
853 {
854 auto const received = result_transaction[name];
855 BEAST_EXPECTS(
856 received == *memberIt,
857 "Transaction contains \n\"" + name + "\": " //
858 + to_string(received) //
859 + " but expected " //
860 + to_string(expected));
861 }
862 }
863 }
864
865 void
866 testBinaryRequest(unsigned apiVersion)
867 {
868 testcase(
869 "Test binary request API version " + std::to_string(apiVersion));
870
871 using namespace test::jtx;
872 using std::to_string;
873
874 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
875 cfg->FEES.reference_fee = 10;
876 return cfg;
877 })};
878 Account const alice{"alice"};
879 Account const gw{"gw"};
880 auto const USD{gw["USD"]};
881
882 env.fund(XRP(1000000), alice, gw);
883 std::shared_ptr<STTx const> const txn = env.tx();
884 BEAST_EXPECT(
885 to_string(txn->getTransactionID()) ==
886 "3F8BDE5A5F82C4F4708E5E9255B713E303E6E1A371FD5C7A704AFD1387C23981");
887 env.close();
889 env.closed()->txRead(txn->getTransactionID()).second;
890
891 std::string const expected_tx_blob = serializeHex(*txn);
892 std::string const expected_meta_blob = serializeHex(*meta);
893
894 Json::Value const result = [&env, txn, apiVersion]() {
896 params[jss::transaction] = to_string(txn->getTransactionID());
897 params[jss::binary] = true;
898 params[jss::api_version] = apiVersion;
899 return env.client().invoke("tx", params);
900 }();
901
902 if (BEAST_EXPECT(result[jss::status] == "success"))
903 {
904 BEAST_EXPECT(result[jss::result][jss::status] == "success");
905 BEAST_EXPECT(result[jss::result][jss::validated] == true);
906 BEAST_EXPECT(
907 result[jss::result][jss::hash] ==
908 to_string(txn->getTransactionID()));
909 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 3);
910 BEAST_EXPECT(result[jss::result][jss::ctid] == "C000000300030000");
911
912 if (apiVersion > 1)
913 {
914 BEAST_EXPECT(
915 result[jss::result][jss::tx_blob] == expected_tx_blob);
916 BEAST_EXPECT(
917 result[jss::result][jss::meta_blob] == expected_meta_blob);
918 BEAST_EXPECT(
919 result[jss::result][jss::ledger_hash] ==
920 "2D5150E5A5AA436736A732291E437ABF01BC9E206C2DF3C77C4F856915"
921 "7905AA");
922 BEAST_EXPECT(
923 result[jss::result][jss::close_time_iso] ==
924 "2000-01-01T00:00:10Z");
925 }
926 else
927 {
928 BEAST_EXPECT(result[jss::result][jss::tx] == expected_tx_blob);
929 BEAST_EXPECT(
930 result[jss::result][jss::meta] == expected_meta_blob);
931 BEAST_EXPECT(result[jss::result][jss::date] == 10);
932 }
933 }
934 }
935
936public:
937 void
938 run() override
939 {
940 using namespace test::jtx;
943
944 FeatureBitset const all{supported_amendments()};
946 }
947
948 void
950 {
951 testRangeRequest(features);
952 testRangeCTIDRequest(features);
953 testCTIDValidation(features);
954 testCTIDRPC(features);
957 }
958};
959
960BEAST_DEFINE_TESTSUITE(Transaction, rpc, ripple);
961
962} // namespace ripple
T begin(T... args)
T bind_front(T... args)
Represents a JSON value.
Definition: json_value.h:150
const_iterator begin() const
const_iterator end() const
Value removeMember(char const *key)
Remove and return the named member.
Definition: json_value.cpp:935
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:46
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:180
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(T const ctid) noexcept
Definition: CTID.h:60
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:244
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:213
T to_string(T... args)