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