rippled
Loading...
Searching...
No Matches
Batch_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 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/TestHelpers.h>
22#include <test/jtx/utility.h>
23
24#include <xrpld/app/misc/HashRouter.h>
25#include <xrpld/app/misc/Transaction.h>
26#include <xrpld/app/tx/apply.h>
27#include <xrpld/app/tx/detail/Batch.h>
28
29#include <xrpl/protocol/Batch.h>
30#include <xrpl/protocol/Feature.h>
31#include <xrpl/protocol/STParsedJSON.h>
32#include <xrpl/protocol/Sign.h>
33#include <xrpl/protocol/TxFlags.h>
34#include <xrpl/protocol/jss.h>
35
36namespace ripple {
37namespace test {
38
40{
42 {
43 int index;
48 };
49
51 {
54 };
55
57 getTxByIndex(Json::Value const& jrr, int const index)
58 {
59 for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions])
60 {
61 if (txn[jss::metaData][sfTransactionIndex.jsonName] == index)
62 return txn;
63 }
64 return {};
65 }
66
69 {
70 Json::Value params;
71 params[jss::ledger_index] = env.closed()->seq();
72 params[jss::transactions] = true;
73 params[jss::expand] = true;
74 return env.rpc("json", "ledger", to_string(params));
75 }
76
77 void
79 jtx::Env& env,
80 std::string const& batchID,
81 TestLedgerData const& ledgerResult)
82 {
83 Json::Value const jrr = env.rpc("tx", ledgerResult.txHash)[jss::result];
84 BEAST_EXPECT(jrr[sfTransactionType.jsonName] == ledgerResult.txType);
85 BEAST_EXPECT(
86 jrr[jss::meta][sfTransactionResult.jsonName] ==
87 ledgerResult.result);
88 BEAST_EXPECT(jrr[jss::meta][sfParentBatchID.jsonName] == batchID);
89 }
90
91 void
93 jtx::Env& env,
94 std::vector<TestLedgerData> const& ledgerResults)
95 {
96 auto const jrr = getLastLedger(env);
97 auto const transactions =
98 jrr[jss::result][jss::ledger][jss::transactions];
99 BEAST_EXPECT(transactions.size() == ledgerResults.size());
100 for (TestLedgerData const& ledgerResult : ledgerResults)
101 {
102 auto const txn = getTxByIndex(jrr, ledgerResult.index);
103 BEAST_EXPECT(txn[jss::hash].asString() == ledgerResult.txHash);
104 BEAST_EXPECT(txn.isMember(jss::metaData));
105 Json::Value const meta = txn[jss::metaData];
106 BEAST_EXPECT(
107 txn[sfTransactionType.jsonName] == ledgerResult.txType);
108 BEAST_EXPECT(
109 meta[sfTransactionResult.jsonName] == ledgerResult.result);
110 if (ledgerResult.batchID)
111 validateInnerTxn(env, *ledgerResult.batchID, ledgerResult);
112 }
113 }
114
115 template <typename... Args>
117 submitBatch(jtx::Env& env, TER const& result, Args&&... args)
118 {
119 auto batchTxn = env.jt(std::forward<Args>(args)...);
120 env(batchTxn, jtx::ter(result));
121
122 auto const ids = batchTxn.stx->getBatchTransactionIDs();
124 for (auto const& id : ids)
125 txIDs.push_back(strHex(id));
126 TxID const batchID = batchTxn.stx->getTransactionID();
127 return std::make_pair(txIDs, strHex(batchID));
128 }
129
130 static uint256
131 getCheckIndex(AccountID const& account, std::uint32_t uSequence)
132 {
133 return keylet::check(account, uSequence).key;
134 }
135
139 std::map<std::string, std::string> extraVoting = {})
140 {
141 auto p = test::jtx::envconfig();
142 auto& section = p->section("transaction_queue");
143 section.set("ledgers_in_queue", "2");
144 section.set("minimum_queue_size", "2");
145 section.set("min_ledgers_to_compute_size_limit", "3");
146 section.set("max_ledger_counts_to_store", "100");
147 section.set("retry_sequence_percent", "25");
148 section.set("normal_consensus_increase_percent", "0");
149
150 for (auto const& [k, v] : extraTxQ)
151 section.set(k, v);
152
153 return p;
154 }
155
156 auto
157 openLedgerFee(jtx::Env& env, XRPAmount const& batchFee)
158 {
159 using namespace jtx;
160
161 auto const& view = *env.current();
162 auto metrics = env.app().getTxQ().getMetrics(view);
163 return toDrops(metrics.openLedgerFeeLevel, batchFee) + 1;
164 }
165
166 void
168 {
169 testcase("enabled");
170
171 using namespace test::jtx;
172 using namespace std::literals;
173
174 for (bool const withBatch : {true, false})
175 {
176 auto const amend = withBatch ? features : features - featureBatch;
177 test::jtx::Env env{*this, envconfig(), amend};
178
179 auto const alice = Account("alice");
180 auto const bob = Account("bob");
181 auto const carol = Account("carol");
182 env.fund(XRP(10000), alice, bob, carol);
183 env.close();
184
185 // ttBatch
186 {
187 auto const seq = env.seq(alice);
188 auto const batchFee = batch::calcBatchFee(env, 0, 2);
189 auto const txResult =
190 withBatch ? ter(tesSUCCESS) : ter(temDISABLED);
191 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
192 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
193 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
194 txResult);
195 env.close();
196 }
197
198 // tfInnerBatchTxn
199 // If the feature is disabled, the transaction fails with
200 // temINVALID_FLAG If the feature is enabled, the transaction fails
201 // early in checkValidity()
202 {
203 auto const txResult =
205 env(pay(alice, bob, XRP(1)),
207 txResult);
208 env.close();
209 }
210
211 env.close();
212 }
213 }
214
215 void
217 {
218 testcase("preflight");
219
220 using namespace test::jtx;
221 using namespace std::literals;
222
223 //----------------------------------------------------------------------
224 // preflight
225
226 test::jtx::Env env{*this, envconfig()};
227
228 auto const alice = Account("alice");
229 auto const bob = Account("bob");
230 auto const carol = Account("carol");
231 env.fund(XRP(10000), alice, bob, carol);
232 env.close();
233
234 // temBAD_FEE: preflight1
235 {
236 env(batch::outer(alice, env.seq(alice), XRP(-1), tfAllOrNothing),
237 ter(temBAD_FEE));
238 env.close();
239 }
240
241 // DEFENSIVE: temINVALID_FLAG: Batch: inner batch flag.
242 // ACTUAL: telENV_RPC_FAILED: checkValidity()
243 {
244 auto const seq = env.seq(alice);
245 auto const batchFee = batch::calcBatchFee(env, 0, 0);
246 env(batch::outer(alice, seq, batchFee, tfInnerBatchTxn),
248 env.close();
249 }
250
251 // temINVALID_FLAG: Batch: invalid flags.
252 {
253 auto const seq = env.seq(alice);
254 auto const batchFee = batch::calcBatchFee(env, 0, 0);
255 env(batch::outer(alice, seq, batchFee, tfDisallowXRP),
257 env.close();
258 }
259
260 // temINVALID_FLAG: Batch: too many flags.
261 {
262 auto const seq = env.seq(alice);
263 auto const batchFee = batch::calcBatchFee(env, 0, 0);
264 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
267 env.close();
268 }
269
270 // temARRAY_EMPTY: Batch: txns array must have at least 2 entries.
271 {
272 auto const seq = env.seq(alice);
273 auto const batchFee = batch::calcBatchFee(env, 0, 0);
274 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
276 env.close();
277 }
278
279 // temARRAY_EMPTY: Batch: txns array must have at least 2 entries.
280 {
281 auto const seq = env.seq(alice);
282 auto const batchFee = batch::calcBatchFee(env, 0, 0);
283 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
284 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
286 env.close();
287 }
288
289 // DEFENSIVE: temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries.
290 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
291 {
292 auto const seq = env.seq(alice);
293 auto const batchFee = batch::calcBatchFee(env, 0, 9);
294 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
295 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
296 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
297 batch::inner(pay(alice, bob, XRP(1)), seq + 3),
298 batch::inner(pay(alice, bob, XRP(1)), seq + 4),
299 batch::inner(pay(alice, bob, XRP(1)), seq + 5),
300 batch::inner(pay(alice, bob, XRP(1)), seq + 6),
301 batch::inner(pay(alice, bob, XRP(1)), seq + 7),
302 batch::inner(pay(alice, bob, XRP(1)), seq + 8),
303 batch::inner(pay(alice, bob, XRP(1)), seq + 9),
305 env.close();
306 }
307
308 // temREDUNDANT: Batch: duplicate Txn found.
309 {
310 auto const batchFee = batch::calcBatchFee(env, 1, 2);
311 auto const seq = env.seq(alice);
312 auto jt = env.jtnofill(
313 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
314 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
315 batch::inner(pay(alice, bob, XRP(10)), seq + 1));
316
317 env(jt.jv, batch::sig(bob), ter(temREDUNDANT));
318 env.close();
319 }
320
321 // DEFENSIVE: temINVALID: Batch: batch cannot have inner batch txn.
322 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
323 {
324 auto const seq = env.seq(alice);
325 auto const batchFee = batch::calcBatchFee(env, 0, 2);
326 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
328 batch::outer(alice, seq, batchFee, tfAllOrNothing), seq),
329 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
331 env.close();
332 }
333
334 // temINVALID_FLAG: Batch: inner txn must have the
335 // tfInnerBatchTxn flag.
336 {
337 auto const batchFee = batch::calcBatchFee(env, 1, 2);
338 auto const seq = env.seq(alice);
339 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
340 tx1[jss::Flags] = 0;
341 auto jt = env.jtnofill(
342 batch::outer(alice, seq, batchFee, tfAllOrNothing),
343 tx1,
344 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
345
346 env(jt.jv, batch::sig(bob), ter(temINVALID_FLAG));
347 env.close();
348 }
349
350 // temBAD_SIGNATURE: Batch: inner txn cannot include TxnSignature.
351 {
352 auto const seq = env.seq(alice);
353 auto const batchFee = batch::calcBatchFee(env, 0, 2);
354 auto jt = env.jt(pay(alice, bob, XRP(1)));
355 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
356 batch::inner(jt.jv, seq + 1),
357 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
359 env.close();
360 }
361
362 // temBAD_SIGNER: Batch: inner txn cannot include Signers.
363 {
364 auto const seq = env.seq(alice);
365 auto const batchFee = batch::calcBatchFee(env, 0, 2);
366 auto tx1 = pay(alice, bob, XRP(1));
367 tx1[sfSigners.jsonName] = Json::arrayValue;
368 tx1[sfSigners.jsonName][0U][sfSigner.jsonName] = Json::objectValue;
369 tx1[sfSigners.jsonName][0U][sfSigner.jsonName][sfAccount.jsonName] =
370 alice.human();
371 tx1[sfSigners.jsonName][0U][sfSigner.jsonName]
372 [sfSigningPubKey.jsonName] = strHex(alice.pk());
373 tx1[sfSigners.jsonName][0U][sfSigner.jsonName]
374 [sfTxnSignature.jsonName] = "DEADBEEF";
375 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
376 batch::inner(tx1, seq + 1),
377 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
379 env.close();
380 }
381
382 // temBAD_REGKEY: Batch: inner txn must include empty
383 // SigningPubKey.
384 {
385 auto const seq = env.seq(alice);
386 auto const batchFee = batch::calcBatchFee(env, 0, 2);
387 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
388 tx1[jss::SigningPubKey] = strHex(alice.pk());
389 auto jt = env.jtnofill(
390 batch::outer(alice, seq, batchFee, tfAllOrNothing),
391 tx1,
392 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
393
394 env(jt.jv, ter(temBAD_REGKEY));
395 env.close();
396 }
397
398 // temINVALID_INNER_BATCH: Batch: inner txn preflight failed.
399 {
400 auto const seq = env.seq(alice);
401 auto const batchFee = batch::calcBatchFee(env, 0, 2);
402 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
403 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
404 // amount can't be negative
405 batch::inner(pay(alice, bob, XRP(-1)), seq + 2),
407 env.close();
408 }
409
410 // temBAD_FEE: Batch: inner txn must have a fee of 0.
411 {
412 auto const seq = env.seq(alice);
413 auto const batchFee = batch::calcBatchFee(env, 0, 2);
414 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
415 tx1[jss::Fee] = to_string(env.current()->fees().base);
416 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
417 tx1,
418 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
419 ter(temBAD_FEE));
420 env.close();
421 }
422
423 // temSEQ_AND_TICKET: Batch: inner txn cannot have both Sequence
424 // and TicketSequence.
425 {
426 auto const seq = env.seq(alice);
427 auto const batchFee = batch::calcBatchFee(env, 0, 2);
428 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), 0, 1);
429 tx1[jss::Sequence] = seq + 1;
430 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
431 tx1,
432 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
434 env.close();
435 }
436
437 // temSEQ_AND_TICKET: Batch: inner txn must have either Sequence or
438 // TicketSequence.
439 {
440 auto const seq = env.seq(alice);
441 auto const batchFee = batch::calcBatchFee(env, 0, 2);
442 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
443 batch::inner(pay(alice, bob, XRP(1)), 0),
444 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
446 env.close();
447 }
448
449 // temREDUNDANT: Batch: duplicate sequence found:
450 {
451 auto const seq = env.seq(alice);
452 auto const batchFee = batch::calcBatchFee(env, 0, 2);
453 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
454 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
455 batch::inner(pay(alice, bob, XRP(2)), seq + 1),
457 env.close();
458 }
459
460 // temREDUNDANT: Batch: duplicate ticket found:
461 {
462 auto const seq = env.seq(alice);
463 auto const batchFee = batch::calcBatchFee(env, 0, 2);
464 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
465 batch::inner(pay(alice, bob, XRP(1)), 0, seq + 1),
466 batch::inner(pay(alice, bob, XRP(2)), 0, seq + 1),
468 env.close();
469 }
470
471 // temREDUNDANT: Batch: duplicate ticket & sequence found:
472 {
473 auto const seq = env.seq(alice);
474 auto const batchFee = batch::calcBatchFee(env, 0, 2);
475 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
476 batch::inner(pay(alice, bob, XRP(1)), 0, seq + 1),
477 batch::inner(pay(alice, bob, XRP(2)), seq + 1),
479 env.close();
480 }
481
482 // DEFENSIVE: temARRAY_TOO_LARGE: Batch: signers array exceeds 8
483 // entries.
484 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
485 {
486 auto const seq = env.seq(alice);
487 auto const batchFee = batch::calcBatchFee(env, 9, 2);
488 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
489 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
490 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
492 bob,
493 carol,
494 alice,
495 bob,
496 carol,
497 alice,
498 bob,
499 carol,
500 alice,
501 alice),
503 env.close();
504 }
505
506 // temBAD_SIGNER: Batch: signer cannot be the outer account
507 {
508 auto const seq = env.seq(alice);
509 auto const batchFee = batch::calcBatchFee(env, 2, 2);
510 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
511 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
512 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
513 batch::sig(alice, bob),
515 env.close();
516 }
517
518 // temREDUNDANT: Batch: duplicate signer found
519 {
520 auto const seq = env.seq(alice);
521 auto const batchFee = batch::calcBatchFee(env, 2, 2);
522 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
523 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
524 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
525 batch::sig(bob, bob),
527 env.close();
528 }
529
530 // temBAD_SIGNER: Batch: no account signature for inner txn.
531 // Note: Extra signature by bob
532 {
533 auto const seq = env.seq(alice);
534 auto const batchFee = batch::calcBatchFee(env, 1, 2);
535 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
536 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
537 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
538 batch::sig(bob),
540 env.close();
541 }
542
543 // temBAD_SIGNER: Batch: no account signature for inner txn.
544 {
545 auto const seq = env.seq(alice);
546 auto const batchFee = batch::calcBatchFee(env, 1, 2);
547 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
548 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
549 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
550 batch::sig(carol),
552 env.close();
553 }
554
555 // temBAD_SIGNATURE: Batch: invalid batch txn signature.
556 {
557 auto const seq = env.seq(alice);
558 auto const bobSeq = env.seq(bob);
559 auto const batchFee = batch::calcBatchFee(env, 1, 2);
560 auto jt = env.jtnofill(
561 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
562 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
563 batch::inner(pay(bob, alice, XRP(5)), bobSeq));
564
565 Serializer msg;
567 msg, tfAllOrNothing, jt.stx->getBatchTransactionIDs());
568 auto const sig = ripple::sign(bob.pk(), bob.sk(), msg.slice());
569 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
570 [sfAccount.jsonName] = bob.human();
571 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
572 [sfSigningPubKey.jsonName] = strHex(alice.pk());
573 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
574 [sfTxnSignature.jsonName] =
575 strHex(Slice{sig.data(), sig.size()});
576
577 env(jt.jv, ter(temBAD_SIGNATURE));
578 env.close();
579 }
580
581 // temBAD_SIGNER: Batch: invalid batch signers.
582 {
583 auto const seq = env.seq(alice);
584 auto const batchFee = batch::calcBatchFee(env, 2, 2);
585 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
586 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
587 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
588 batch::inner(pay(carol, alice, XRP(5)), env.seq(carol)),
589 batch::sig(bob),
591 env.close();
592 }
593 }
594
595 void
597 {
598 testcase("preclaim");
599
600 using namespace test::jtx;
601 using namespace std::literals;
602
603 //----------------------------------------------------------------------
604 // preclaim
605
606 test::jtx::Env env{*this, envconfig()};
607
608 auto const alice = Account("alice");
609 auto const bob = Account("bob");
610 auto const carol = Account("carol");
611 auto const dave = Account("dave");
612 auto const elsa = Account("elsa");
613 auto const frank = Account("frank");
614 auto const phantom = Account("phantom");
615 env.memoize(phantom);
616
617 env.fund(XRP(10000), alice, bob, carol, dave, elsa, frank);
618 env.close();
619
620 //----------------------------------------------------------------------
621 // checkSign.checkSingleSign
622
623 // tefBAD_AUTH: Bob is not authorized to sign for Alice
624 {
625 auto const seq = env.seq(alice);
626 auto const batchFee = batch::calcBatchFee(env, 3, 2);
627 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
628 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
629 batch::inner(pay(alice, bob, XRP(20)), seq + 2),
630 sig(bob),
632 env.close();
633 }
634
635 //----------------------------------------------------------------------
636 // checkBatchSign.checkMultiSign
637
638 // tefNOT_MULTI_SIGNING: SignersList not enabled
639 {
640 auto const seq = env.seq(alice);
641 auto const batchFee = batch::calcBatchFee(env, 3, 2);
642 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
643 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
644 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
645 batch::msig(bob, {dave, carol}),
647 env.close();
648 }
649
650 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
651 env.close();
652
653 env(signers(bob, 2, {{carol, 1}, {dave, 1}, {elsa, 1}}));
654 env.close();
655
656 // tefBAD_SIGNATURE: Account not in SignersList
657 {
658 auto const seq = env.seq(alice);
659 auto const batchFee = batch::calcBatchFee(env, 3, 2);
660 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
661 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
662 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
663 batch::msig(bob, {carol, frank}),
665 env.close();
666 }
667
668 // tefBAD_SIGNATURE: Wrong publicKey type
669 {
670 auto const seq = env.seq(alice);
671 auto const batchFee = batch::calcBatchFee(env, 3, 2);
672 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
673 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
674 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
675 batch::msig(bob, {carol, Account("dave", KeyType::ed25519)}),
677 env.close();
678 }
679
680 // tefMASTER_DISABLED: Master key disabled
681 {
682 env(regkey(elsa, frank));
683 env(fset(elsa, asfDisableMaster), sig(elsa));
684 auto const seq = env.seq(alice);
685 auto const batchFee = batch::calcBatchFee(env, 3, 2);
686 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
687 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
688 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
689 batch::msig(bob, {carol, elsa}),
691 env.close();
692 }
693
694 // tefBAD_SIGNATURE: Signer does not exist
695 {
696 auto const seq = env.seq(alice);
697 auto const batchFee = batch::calcBatchFee(env, 3, 2);
698 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
699 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
700 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
701 batch::msig(bob, {carol, phantom}),
703 env.close();
704 }
705
706 // tefBAD_SIGNATURE: Signer has not enabled RegularKey
707 {
708 auto const seq = env.seq(alice);
709 auto const batchFee = batch::calcBatchFee(env, 3, 2);
710 Account const davo{"davo", KeyType::ed25519};
711 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
712 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
713 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
714 batch::msig(bob, {carol, Reg{dave, davo}}),
716 env.close();
717 }
718
719 // tefBAD_SIGNATURE: Wrong RegularKey Set
720 {
721 env(regkey(dave, frank));
722 auto const seq = env.seq(alice);
723 auto const batchFee = batch::calcBatchFee(env, 3, 2);
724 Account const davo{"davo", KeyType::ed25519};
725 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
726 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
727 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
728 batch::msig(bob, {carol, Reg{dave, davo}}),
729 ter(tefBAD_SIGNATURE));
730 env.close();
731 }
732
733 // tefBAD_QUORUM
734 {
735 auto const seq = env.seq(alice);
736 auto const batchFee = batch::calcBatchFee(env, 2, 2);
737 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
738 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
739 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
740 batch::msig(bob, {carol}),
741 ter(tefBAD_QUORUM));
742 env.close();
743 }
744
745 // tesSUCCESS: BatchSigners.Signers
746 {
747 auto const seq = env.seq(alice);
748 auto const batchFee = batch::calcBatchFee(env, 3, 2);
749 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
750 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
751 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
752 batch::msig(bob, {carol, dave}),
753 ter(tesSUCCESS));
754 env.close();
755 }
756
757 // tesSUCCESS: Multisign + BatchSigners.Signers
758 {
759 auto const seq = env.seq(alice);
760 auto const batchFee = batch::calcBatchFee(env, 4, 2);
761 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
762 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
763 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
764 batch::msig(bob, {carol, dave}),
765 msig(bob, carol),
766 ter(tesSUCCESS));
767 env.close();
768 }
769
770 //----------------------------------------------------------------------
771 // checkBatchSign.checkSingleSign
772
773 // tefBAD_AUTH: Inner Account is not signer
774 {
775 auto const ledSeq = env.current()->seq();
776 auto const seq = env.seq(alice);
777 auto const batchFee = batch::calcBatchFee(env, 1, 2);
778 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
779 batch::inner(pay(alice, phantom, XRP(1000)), seq + 1),
780 batch::inner(noop(phantom), ledSeq),
781 batch::sig(Reg{phantom, carol}),
782 ter(tefBAD_AUTH));
783 env.close();
784 }
785
786 // tefBAD_AUTH: Account is not signer
787 {
788 auto const ledSeq = env.current()->seq();
789 auto const seq = env.seq(alice);
790 auto const batchFee = batch::calcBatchFee(env, 1, 2);
791 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
792 batch::inner(pay(alice, bob, XRP(1000)), seq + 1),
793 batch::inner(noop(bob), ledSeq),
794 batch::sig(Reg{bob, carol}),
795 ter(tefBAD_AUTH));
796 env.close();
797 }
798
799 // tesSUCCESS: Signed With Regular Key
800 {
801 env(regkey(bob, carol));
802 auto const seq = env.seq(alice);
803 auto const batchFee = batch::calcBatchFee(env, 1, 2);
804 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
805 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
806 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
807 batch::sig(Reg{bob, carol}),
808 ter(tesSUCCESS));
809 env.close();
810 }
811
812 // tesSUCCESS: Signed With Master Key
813 {
814 auto const seq = env.seq(alice);
815 auto const batchFee = batch::calcBatchFee(env, 1, 2);
816 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
817 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
818 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
819 batch::sig(bob),
820 ter(tesSUCCESS));
821 env.close();
822 }
823
824 // tefMASTER_DISABLED: Signed With Master Key Disabled
825 {
826 env(regkey(bob, carol));
827 env(fset(bob, asfDisableMaster), sig(bob));
828 auto const seq = env.seq(alice);
829 auto const batchFee = batch::calcBatchFee(env, 1, 2);
830 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
831 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
832 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
833 batch::sig(bob),
834 ter(tefMASTER_DISABLED));
835 env.close();
836 }
837 }
838
839 void
841 {
842 testcase("bad raw txn");
843
844 using namespace test::jtx;
845 using namespace std::literals;
846
847 test::jtx::Env env{*this, envconfig()};
848
849 auto const alice = Account("alice");
850 auto const bob = Account("bob");
851
852 env.fund(XRP(10000), alice, bob);
853
854 // Invalid: sfTransactionType
855 {
856 auto const batchFee = batch::calcBatchFee(env, 1, 2);
857 auto const seq = env.seq(alice);
858 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
859 tx1.removeMember(jss::TransactionType);
860 auto jt = env.jtnofill(
861 batch::outer(alice, seq, batchFee, tfAllOrNothing),
862 tx1,
863 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
864
865 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
866 env.close();
867 }
868
869 // Invalid: sfAccount
870 {
871 auto const batchFee = batch::calcBatchFee(env, 1, 2);
872 auto const seq = env.seq(alice);
873 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
874 tx1.removeMember(jss::Account);
875 auto jt = env.jtnofill(
876 batch::outer(alice, seq, batchFee, tfAllOrNothing),
877 tx1,
878 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
879
880 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
881 env.close();
882 }
883
884 // Invalid: sfSequence
885 {
886 auto const batchFee = batch::calcBatchFee(env, 1, 2);
887 auto const seq = env.seq(alice);
888 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
889 tx1.removeMember(jss::Sequence);
890 auto jt = env.jtnofill(
891 batch::outer(alice, seq, batchFee, tfAllOrNothing),
892 tx1,
893 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
894
895 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
896 env.close();
897 }
898
899 // Invalid: sfFee
900 {
901 auto const batchFee = batch::calcBatchFee(env, 1, 2);
902 auto const seq = env.seq(alice);
903 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
904 tx1.removeMember(jss::Fee);
905 auto jt = env.jtnofill(
906 batch::outer(alice, seq, batchFee, tfAllOrNothing),
907 tx1,
908 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
909
910 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
911 env.close();
912 }
913
914 // Invalid: sfSigningPubKey
915 {
916 auto const batchFee = batch::calcBatchFee(env, 1, 2);
917 auto const seq = env.seq(alice);
918 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
919 tx1.removeMember(jss::SigningPubKey);
920 auto jt = env.jtnofill(
921 batch::outer(alice, seq, batchFee, tfAllOrNothing),
922 tx1,
923 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
924
925 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
926 env.close();
927 }
928 }
929
930 void
932 {
933 testcase("bad sequence");
934
935 using namespace test::jtx;
936 using namespace std::literals;
937
938 test::jtx::Env env{*this, envconfig()};
939
940 auto const alice = Account("alice");
941 auto const bob = Account("bob");
942 auto const gw = Account("gw");
943 auto const USD = gw["USD"];
944
945 env.fund(XRP(10000), alice, bob, gw);
946 env.close();
947 env.trust(USD(1000), alice, bob);
948 env(pay(gw, alice, USD(100)));
949 env(pay(gw, bob, USD(100)));
950 env.close();
951
952 env(noop(bob), ter(tesSUCCESS));
953 env.close();
954
955 // Invalid: Alice Sequence is a past sequence
956 {
957 auto const preAliceSeq = env.seq(alice);
958 auto const preAlice = env.balance(alice);
959 auto const preAliceUSD = env.balance(alice, USD.issue());
960 auto const preBobSeq = env.seq(bob);
961 auto const preBob = env.balance(bob);
962 auto const preBobUSD = env.balance(bob, USD.issue());
963
964 auto const batchFee = batch::calcBatchFee(env, 1, 2);
965 auto const [txIDs, batchID] = submitBatch(
966 env,
968 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
969 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq - 10),
970 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
971 batch::sig(bob));
972
973 env.close();
974 {
975 std::vector<TestLedgerData> testCases = {
976 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
977 };
978 validateClosedLedger(env, testCases);
979 }
980
981 env.close();
982 {
983 // next ledger is empty
984 std::vector<TestLedgerData> testCases = {};
985 validateClosedLedger(env, testCases);
986 }
987
988 // Alice pays fee & Bob should not be affected.
989 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
990 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
991 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
992 BEAST_EXPECT(env.seq(bob) == preBobSeq);
993 BEAST_EXPECT(env.balance(bob) == preBob);
994 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
995 }
996
997 // Invalid: Alice Sequence is a future sequence
998 {
999 auto const preAliceSeq = env.seq(alice);
1000 auto const preAlice = env.balance(alice);
1001 auto const preAliceUSD = env.balance(alice, USD.issue());
1002 auto const preBobSeq = env.seq(bob);
1003 auto const preBob = env.balance(bob);
1004 auto const preBobUSD = env.balance(bob, USD.issue());
1005
1006 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1007 auto const [txIDs, batchID] = submitBatch(
1008 env,
1009 tesSUCCESS,
1010 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1011 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 10),
1012 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
1013 batch::sig(bob));
1014
1015 env.close();
1016 {
1017 std::vector<TestLedgerData> testCases = {
1018 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1019 };
1020 validateClosedLedger(env, testCases);
1021 }
1022
1023 env.close();
1024 {
1025 // next ledger is empty
1026 std::vector<TestLedgerData> testCases = {};
1027 validateClosedLedger(env, testCases);
1028 }
1029
1030 // Alice pays fee & Bob should not be affected.
1031 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1032 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1033 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1034 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1035 BEAST_EXPECT(env.balance(bob) == preBob);
1036 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1037 }
1038
1039 // Invalid: Bob Sequence is a past sequence
1040 {
1041 auto const preAliceSeq = env.seq(alice);
1042 auto const preAlice = env.balance(alice);
1043 auto const preAliceUSD = env.balance(alice, USD.issue());
1044 auto const preBobSeq = env.seq(bob);
1045 auto const preBob = env.balance(bob);
1046 auto const preBobUSD = env.balance(bob, USD.issue());
1047
1048 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1049 auto const [txIDs, batchID] = submitBatch(
1050 env,
1051 tesSUCCESS,
1052 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1053 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 1),
1054 batch::inner(pay(bob, alice, XRP(5)), preBobSeq - 10),
1055 batch::sig(bob));
1056
1057 env.close();
1058 {
1059 std::vector<TestLedgerData> testCases = {
1060 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1061 };
1062 validateClosedLedger(env, testCases);
1063 }
1064
1065 env.close();
1066 {
1067 // next ledger is empty
1068 std::vector<TestLedgerData> testCases = {};
1069 validateClosedLedger(env, testCases);
1070 }
1071
1072 // Alice pays fee & Bob should not be affected.
1073 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1074 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1075 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1076 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1077 BEAST_EXPECT(env.balance(bob) == preBob);
1078 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1079 }
1080
1081 // Invalid: Bob Sequence is a future sequence
1082 {
1083 auto const preAliceSeq = env.seq(alice);
1084 auto const preAlice = env.balance(alice);
1085 auto const preAliceUSD = env.balance(alice, USD.issue());
1086 auto const preBobSeq = env.seq(bob);
1087 auto const preBob = env.balance(bob);
1088 auto const preBobUSD = env.balance(bob, USD.issue());
1089
1090 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1091 auto const [txIDs, batchID] = submitBatch(
1092 env,
1093 tesSUCCESS,
1094 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1095 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 1),
1096 batch::inner(pay(bob, alice, XRP(5)), preBobSeq + 10),
1097 batch::sig(bob));
1098
1099 env.close();
1100 {
1101 std::vector<TestLedgerData> testCases = {
1102 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1103 };
1104 validateClosedLedger(env, testCases);
1105 }
1106
1107 env.close();
1108 {
1109 // next ledger is empty
1110 std::vector<TestLedgerData> testCases = {};
1111 validateClosedLedger(env, testCases);
1112 }
1113
1114 // Alice pays fee & Bob should not be affected.
1115 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1116 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1117 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1118 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1119 BEAST_EXPECT(env.balance(bob) == preBob);
1120 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1121 }
1122
1123 // Invalid: Outer and Inner Sequence are the same
1124 {
1125 auto const preAliceSeq = env.seq(alice);
1126 auto const preAlice = env.balance(alice);
1127 auto const preAliceUSD = env.balance(alice, USD.issue());
1128 auto const preBobSeq = env.seq(bob);
1129 auto const preBob = env.balance(bob);
1130 auto const preBobUSD = env.balance(bob, USD.issue());
1131
1132 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1133 auto const [txIDs, batchID] = submitBatch(
1134 env,
1135 tesSUCCESS,
1136 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1137 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq),
1138 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
1139 batch::sig(bob));
1140
1141 env.close();
1142 {
1143 std::vector<TestLedgerData> testCases = {
1144 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1145 };
1146 validateClosedLedger(env, testCases);
1147 }
1148
1149 env.close();
1150 {
1151 // next ledger is empty
1152 std::vector<TestLedgerData> testCases = {};
1153 validateClosedLedger(env, testCases);
1154 }
1155
1156 // Alice pays fee & Bob should not be affected.
1157 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1158 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1159 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1160 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1161 BEAST_EXPECT(env.balance(bob) == preBob);
1162 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1163 }
1164 }
1165
1166 void
1168 {
1169 testcase("bad outer fee");
1170
1171 using namespace test::jtx;
1172 using namespace std::literals;
1173
1174 // Bad Fee Without Signer
1175 {
1176 test::jtx::Env env{*this, envconfig()};
1177
1178 auto const alice = Account("alice");
1179 auto const bob = Account("bob");
1180 env.fund(XRP(10000), alice, bob);
1181 env.close();
1182
1183 env(noop(bob), ter(tesSUCCESS));
1184 env.close();
1185
1186 // Bad Fee: Should be batch::calcBatchFee(env, 0, 2)
1187 auto const batchFee = batch::calcBatchFee(env, 0, 1);
1188 auto const aliceSeq = env.seq(alice);
1189 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1190 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1191 batch::inner(pay(alice, bob, XRP(15)), aliceSeq + 2),
1193 env.close();
1194 }
1195
1196 // Bad Fee With MultiSign
1197 {
1198 test::jtx::Env env{*this, envconfig()};
1199
1200 auto const alice = Account("alice");
1201 auto const bob = Account("bob");
1202 auto const carol = Account("carol");
1203 env.fund(XRP(10000), alice, bob, carol);
1204 env.close();
1205
1206 env(noop(bob), ter(tesSUCCESS));
1207 env.close();
1208
1209 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1210 env.close();
1211
1212 // Bad Fee: Should be batch::calcBatchFee(env, 2, 2)
1213 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1214 auto const aliceSeq = env.seq(alice);
1215 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1216 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1217 batch::inner(pay(alice, bob, XRP(15)), aliceSeq + 2),
1218 msig(bob, carol),
1220 env.close();
1221 }
1222
1223 // Bad Fee With MultiSign + BatchSigners
1224 {
1225 test::jtx::Env env{*this, envconfig()};
1226
1227 auto const alice = Account("alice");
1228 auto const bob = Account("bob");
1229 auto const carol = Account("carol");
1230 env.fund(XRP(10000), alice, bob, carol);
1231 env.close();
1232
1233 env(noop(bob), ter(tesSUCCESS));
1234 env.close();
1235
1236 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1237 env.close();
1238
1239 // Bad Fee: Should be batch::calcBatchFee(env, 3, 2)
1240 auto const batchFee = batch::calcBatchFee(env, 2, 2);
1241 auto const aliceSeq = env.seq(alice);
1242 auto const bobSeq = env.seq(bob);
1243 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1244 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1245 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1246 batch::sig(bob),
1247 msig(bob, carol),
1249 env.close();
1250 }
1251
1252 // Bad Fee With MultiSign + BatchSigners.Signers
1253 {
1254 test::jtx::Env env{*this, envconfig()};
1255
1256 auto const alice = Account("alice");
1257 auto const bob = Account("bob");
1258 auto const carol = Account("carol");
1259 env.fund(XRP(10000), alice, bob, carol);
1260 env.close();
1261
1262 env(noop(bob), ter(tesSUCCESS));
1263 env.close();
1264
1265 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1266 env.close();
1267
1268 env(signers(bob, 2, {{alice, 1}, {carol, 1}}));
1269 env.close();
1270
1271 // Bad Fee: Should be batch::calcBatchFee(env, 4, 2)
1272 auto const batchFee = batch::calcBatchFee(env, 3, 2);
1273 auto const aliceSeq = env.seq(alice);
1274 auto const bobSeq = env.seq(bob);
1275 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1276 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1277 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1278 batch::msig(bob, {alice, carol}),
1279 msig(bob, carol),
1281 env.close();
1282 }
1283
1284 // Bad Fee With BatchSigners
1285 {
1286 test::jtx::Env env{*this, envconfig()};
1287
1288 auto const alice = Account("alice");
1289 auto const bob = Account("bob");
1290 env.fund(XRP(10000), alice, bob);
1291 env.close();
1292
1293 env(noop(bob), ter(tesSUCCESS));
1294 env.close();
1295
1296 // Bad Fee: Should be batch::calcBatchFee(env, 1, 2)
1297 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1298 auto const aliceSeq = env.seq(alice);
1299 auto const bobSeq = env.seq(bob);
1300 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1301 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1302 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1303 batch::sig(bob),
1305 env.close();
1306 }
1307
1308 // Bad Fee Dynamic Fee Calculation
1309 {
1310 test::jtx::Env env{*this, envconfig()};
1311
1312 auto const alice = Account("alice");
1313 auto const bob = Account("bob");
1314 auto const gw = Account("gw");
1315 auto const USD = gw["USD"];
1316
1317 env.fund(XRP(10000), alice, bob, gw);
1318 env.close();
1319 auto const ammCreate =
1320 [&alice](STAmount const& amount, STAmount const& amount2) {
1321 Json::Value jv;
1322 jv[jss::Account] = alice.human();
1323 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1324 jv[jss::Amount2] = amount2.getJson(JsonOptions::none);
1325 jv[jss::TradingFee] = 0;
1326 jv[jss::TransactionType] = jss::AMMCreate;
1327 return jv;
1328 };
1329
1330 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1331 auto const seq = env.seq(alice);
1332 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
1333 batch::inner(ammCreate(XRP(10), USD(10)), seq + 1),
1334 batch::inner(pay(alice, bob, XRP(10)), seq + 2),
1336 env.close();
1337 }
1338 }
1339
1340 void
1342 {
1343 testcase("calculate base fee");
1344
1345 using namespace test::jtx;
1346 using namespace std::literals;
1347
1348 // telENV_RPC_FAILED: Batch: txns array exceeds 8 entries.
1349 {
1350 test::jtx::Env env{*this, envconfig()};
1351
1352 auto const alice = Account("alice");
1353 auto const bob = Account("bob");
1354 env.fund(XRP(10000), alice, bob);
1355 env.close();
1356
1357 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1358 auto const aliceSeq = env.seq(alice);
1359 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1360 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1361 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1362 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1363 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1364 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1365 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1366 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1367 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1368 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1370 env.close();
1371 }
1372
1373 // temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries.
1374 {
1375 test::jtx::Env env{*this, envconfig()};
1376
1377 auto const alice = Account("alice");
1378 auto const bob = Account("bob");
1379 env.fund(XRP(10000), alice, bob);
1380 env.close();
1381
1382 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1383 auto const aliceSeq = env.seq(alice);
1384 auto jt = env.jtnofill(
1385 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1386 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1387 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1388 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1389 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1390 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1391 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1392 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1393 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1394 batch::inner(pay(alice, bob, XRP(1)), aliceSeq));
1395
1396 env.app().openLedger().modify(
1397 [&](OpenView& view, beast::Journal j) {
1398 auto const result =
1399 ripple::apply(env.app(), view, *jt.stx, tapNONE, j);
1400 BEAST_EXPECT(
1401 !result.applied && result.ter == temARRAY_TOO_LARGE);
1402 return result.applied;
1403 });
1404 }
1405
1406 // telENV_RPC_FAILED: Batch: signers array exceeds 8 entries.
1407 {
1408 test::jtx::Env env{*this, envconfig()};
1409
1410 auto const alice = Account("alice");
1411 auto const bob = Account("bob");
1412 env.fund(XRP(10000), alice, bob);
1413 env.close();
1414
1415 auto const aliceSeq = env.seq(alice);
1416 auto const batchFee = batch::calcBatchFee(env, 9, 2);
1417 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1418 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1419 batch::inner(pay(alice, bob, XRP(5)), aliceSeq + 2),
1420 batch::sig(bob, bob, bob, bob, bob, bob, bob, bob, bob, bob),
1422 env.close();
1423 }
1424
1425 // temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries.
1426 {
1427 test::jtx::Env env{*this, envconfig()};
1428
1429 auto const alice = Account("alice");
1430 auto const bob = Account("bob");
1431 env.fund(XRP(10000), alice, bob);
1432 env.close();
1433
1434 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1435 auto const aliceSeq = env.seq(alice);
1436 auto jt = env.jtnofill(
1437 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1438 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1439 batch::inner(pay(alice, bob, XRP(5)), aliceSeq + 2),
1440 batch::sig(bob, bob, bob, bob, bob, bob, bob, bob, bob, bob));
1441
1442 env.app().openLedger().modify(
1443 [&](OpenView& view, beast::Journal j) {
1444 auto const result =
1445 ripple::apply(env.app(), view, *jt.stx, tapNONE, j);
1446 BEAST_EXPECT(
1447 !result.applied && result.ter == temARRAY_TOO_LARGE);
1448 return result.applied;
1449 });
1450 }
1451 }
1452
1453 void
1455 {
1456 testcase("all or nothing");
1457
1458 using namespace test::jtx;
1459 using namespace std::literals;
1460
1461 test::jtx::Env env{*this, envconfig()};
1462
1463 auto const alice = Account("alice");
1464 auto const bob = Account("bob");
1465 auto const gw = Account("gw");
1466 auto const USD = gw["USD"];
1467 env.fund(XRP(10000), alice, bob, gw);
1468 env.close();
1469
1470 // all
1471 {
1472 auto const preAlice = env.balance(alice);
1473 auto const preBob = env.balance(bob);
1474
1475 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1476 auto const seq = env.seq(alice);
1477 auto const [txIDs, batchID] = submitBatch(
1478 env,
1479 tesSUCCESS,
1480 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1481 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1482 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
1483 env.close();
1484
1485 std::vector<TestLedgerData> testCases = {
1486 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1487 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1488 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1489 };
1490 validateClosedLedger(env, testCases);
1491
1492 // Alice consumes sequences (# of txns)
1493 BEAST_EXPECT(env.seq(alice) == seq + 3);
1494
1495 // Alice pays XRP & Fee; Bob receives XRP
1496 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1497 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1498 }
1499
1500 // tec failure
1501 {
1502 auto const preAlice = env.balance(alice);
1503 auto const preBob = env.balance(bob);
1504
1505 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1506 auto const seq = env.seq(alice);
1507
1508 auto const [txIDs, batchID] = submitBatch(
1509 env,
1510 tesSUCCESS,
1511 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1512 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1513 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1514 batch::inner(pay(alice, bob, XRP(9999)), seq + 2));
1515 env.close();
1516
1517 std::vector<TestLedgerData> testCases = {
1518 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1519 };
1520 validateClosedLedger(env, testCases);
1521
1522 // Alice consumes sequence
1523 BEAST_EXPECT(env.seq(alice) == seq + 1);
1524
1525 // Alice pays Fee; Bob should not be affected
1526 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1527 BEAST_EXPECT(env.balance(bob) == preBob);
1528 }
1529
1530 // tef failure
1531 {
1532 auto const preAlice = env.balance(alice);
1533 auto const preBob = env.balance(bob);
1534
1535 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1536 auto const seq = env.seq(alice);
1537 auto const [txIDs, batchID] = submitBatch(
1538 env,
1539 tesSUCCESS,
1540 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1541 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1542 // tefNO_AUTH_REQUIRED: trustline auth is not required
1543 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 2));
1544 env.close();
1545
1546 std::vector<TestLedgerData> testCases = {
1547 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1548 };
1549 validateClosedLedger(env, testCases);
1550
1551 // Alice consumes sequence
1552 BEAST_EXPECT(env.seq(alice) == seq + 1);
1553
1554 // Alice pays Fee; Bob should not be affected
1555 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1556 BEAST_EXPECT(env.balance(bob) == preBob);
1557 }
1558
1559 // ter failure
1560 {
1561 auto const preAlice = env.balance(alice);
1562 auto const preBob = env.balance(bob);
1563
1564 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1565 auto const seq = env.seq(alice);
1566 auto const [txIDs, batchID] = submitBatch(
1567 env,
1568 tesSUCCESS,
1569 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1570 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1571 // terPRE_TICKET: ticket does not exist
1572 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 2));
1573 env.close();
1574
1575 std::vector<TestLedgerData> testCases = {
1576 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1577 };
1578 validateClosedLedger(env, testCases);
1579
1580 // Alice consumes sequence
1581 BEAST_EXPECT(env.seq(alice) == seq + 1);
1582
1583 // Alice pays Fee; Bob should not be affected
1584 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1585 BEAST_EXPECT(env.balance(bob) == preBob);
1586 }
1587 }
1588
1589 void
1591 {
1592 testcase("only one");
1593
1594 using namespace test::jtx;
1595 using namespace std::literals;
1596
1597 test::jtx::Env env{*this, envconfig()};
1598
1599 auto const alice = Account("alice");
1600 auto const bob = Account("bob");
1601 auto const carol = Account("carol");
1602 auto const dave = Account("dave");
1603 auto const gw = Account("gw");
1604 auto const USD = gw["USD"];
1605 env.fund(XRP(10000), alice, bob, carol, dave, gw);
1606 env.close();
1607
1608 // all transactions fail
1609 {
1610 auto const preAlice = env.balance(alice);
1611 auto const preBob = env.balance(bob);
1612
1613 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1614 auto const seq = env.seq(alice);
1615 auto const [txIDs, batchID] = submitBatch(
1616 env,
1617 tesSUCCESS,
1618 batch::outer(alice, seq, batchFee, tfOnlyOne),
1619 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1620 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1621 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1622 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
1623 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1624 batch::inner(pay(alice, bob, XRP(9999)), seq + 3));
1625 env.close();
1626
1627 std::vector<TestLedgerData> testCases = {
1628 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1629 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1630 {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID},
1631 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
1632 };
1633 validateClosedLedger(env, testCases);
1634
1635 // Alice consumes sequences (# of txns)
1636 BEAST_EXPECT(env.seq(alice) == seq + 4);
1637
1638 // Alice pays XRP & Fee; Bob receives XRP
1639 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1640 BEAST_EXPECT(env.balance(bob) == preBob);
1641 }
1642
1643 // first transaction fails
1644 {
1645 auto const preAlice = env.balance(alice);
1646 auto const preBob = env.balance(bob);
1647
1648 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1649 auto const seq = env.seq(alice);
1650 auto const [txIDs, batchID] = submitBatch(
1651 env,
1652 tesSUCCESS,
1653 batch::outer(alice, seq, batchFee, tfOnlyOne),
1654 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1655 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1656 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
1657 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1658 env.close();
1659
1660 std::vector<TestLedgerData> testCases = {
1661 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1662 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1663 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1664 };
1665 validateClosedLedger(env, testCases);
1666
1667 // Alice consumes sequences (# of txns)
1668 BEAST_EXPECT(env.seq(alice) == seq + 3);
1669
1670 // Alice pays XRP & Fee; Bob receives XRP
1671 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
1672 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1673 }
1674
1675 // tec failure
1676 {
1677 auto const preAlice = env.balance(alice);
1678 auto const preBob = env.balance(bob);
1679
1680 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1681 auto const seq = env.seq(alice);
1682 auto const [txIDs, batchID] = submitBatch(
1683 env,
1684 tesSUCCESS,
1685 batch::outer(alice, seq, batchFee, tfOnlyOne),
1686 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1687 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1688 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
1689 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1690 env.close();
1691
1692 std::vector<TestLedgerData> testCases = {
1693 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1694 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1695 };
1696 validateClosedLedger(env, testCases);
1697
1698 // Alice consumes sequences (# of txns)
1699 BEAST_EXPECT(env.seq(alice) == seq + 2);
1700
1701 // Alice pays XRP & Fee; Bob receives XRP
1702 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
1703 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1704 }
1705
1706 // tef failure
1707 {
1708 auto const preAlice = env.balance(alice);
1709 auto const preBob = env.balance(bob);
1710
1711 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1712 auto const seq = env.seq(alice);
1713 auto const [txIDs, batchID] = submitBatch(
1714 env,
1715 tesSUCCESS,
1716 batch::outer(alice, seq, batchFee, tfOnlyOne),
1717 // tefNO_AUTH_REQUIRED: trustline auth is not required
1718 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 1),
1719 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1720 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1721 env.close();
1722
1723 std::vector<TestLedgerData> testCases = {
1724 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1725 {1, "Payment", "tesSUCCESS", txIDs[1], batchID},
1726 };
1727 validateClosedLedger(env, testCases);
1728
1729 // Alice consumes sequences (# of txns)
1730 BEAST_EXPECT(env.seq(alice) == seq + 2);
1731
1732 // Alice pays XRP & Fee; Bob receives XRP
1733 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(1));
1734 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1735 }
1736
1737 // ter failure
1738 {
1739 auto const preAlice = env.balance(alice);
1740 auto const preBob = env.balance(bob);
1741
1742 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1743 auto const seq = env.seq(alice);
1744 auto const [txIDs, batchID] = submitBatch(
1745 env,
1746 tesSUCCESS,
1747 batch::outer(alice, seq, batchFee, tfOnlyOne),
1748 // terPRE_TICKET: ticket does not exist
1749 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 1),
1750 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1751 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1752 env.close();
1753
1754 std::vector<TestLedgerData> testCases = {
1755 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1756 {1, "Payment", "tesSUCCESS", txIDs[1], batchID},
1757 };
1758 validateClosedLedger(env, testCases);
1759
1760 // Alice consumes sequences (# of txns)
1761 BEAST_EXPECT(env.seq(alice) == seq + 2);
1762
1763 // Alice pays XRP & Fee; Bob receives XRP
1764 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(1));
1765 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1766 }
1767
1768 // tec (tecKILLED) error
1769 {
1770 auto const preAlice = env.balance(alice);
1771 auto const preBob = env.balance(bob);
1772 auto const preCarol = env.balance(carol);
1773 auto const seq = env.seq(alice);
1774 auto const batchFee = batch::calcBatchFee(env, 0, 6);
1775
1776 auto const [txIDs, batchID] = submitBatch(
1777 env,
1778 tesSUCCESS,
1779 batch::outer(alice, seq, batchFee, tfOnlyOne),
1781 offer(
1782 alice,
1783 alice["USD"](100),
1784 XRP(100),
1786 seq + 1),
1788 offer(
1789 alice,
1790 alice["USD"](100),
1791 XRP(100),
1793 seq + 2),
1795 offer(
1796 alice,
1797 alice["USD"](100),
1798 XRP(100),
1800 seq + 3),
1801 batch::inner(pay(alice, bob, XRP(100)), seq + 4),
1802 batch::inner(pay(alice, carol, XRP(100)), seq + 5),
1803 batch::inner(pay(alice, dave, XRP(100)), seq + 6));
1804 env.close();
1805
1806 std::vector<TestLedgerData> testCases = {
1807 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1808 {1, "OfferCreate", "tecKILLED", txIDs[0], batchID},
1809 {2, "OfferCreate", "tecKILLED", txIDs[1], batchID},
1810 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
1811 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
1812 };
1813 validateClosedLedger(env, testCases);
1814
1815 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(100) - batchFee);
1816 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
1817 BEAST_EXPECT(env.balance(carol) == preCarol);
1818 }
1819 }
1820
1821 void
1823 {
1824 testcase("until failure");
1825
1826 using namespace test::jtx;
1827 using namespace std::literals;
1828
1829 test::jtx::Env env{*this, envconfig()};
1830
1831 auto const alice = Account("alice");
1832 auto const bob = Account("bob");
1833 auto const carol = Account("carol");
1834 auto const dave = Account("dave");
1835 auto const gw = Account("gw");
1836 auto const USD = gw["USD"];
1837 env.fund(XRP(10000), alice, bob, carol, dave, gw);
1838 env.close();
1839
1840 // first transaction fails
1841 {
1842 auto const preAlice = env.balance(alice);
1843 auto const preBob = env.balance(bob);
1844
1845 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1846 auto const seq = env.seq(alice);
1847 auto const [txIDs, batchID] = submitBatch(
1848 env,
1849 tesSUCCESS,
1850 batch::outer(alice, seq, batchFee, tfUntilFailure),
1851 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1852 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1853 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
1854 batch::inner(pay(alice, bob, XRP(2)), seq + 3),
1855 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1856 env.close();
1857
1858 std::vector<TestLedgerData> testCases = {
1859 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1860 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1861 };
1862 validateClosedLedger(env, testCases);
1863
1864 // Alice consumes sequences (# of txns)
1865 BEAST_EXPECT(env.seq(alice) == seq + 2);
1866
1867 // Alice pays XRP & Fee; Bob receives XRP
1868 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1869 BEAST_EXPECT(env.balance(bob) == preBob);
1870 }
1871
1872 // all transactions succeed
1873 {
1874 auto const preAlice = env.balance(alice);
1875 auto const preBob = env.balance(bob);
1876
1877 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1878 auto const seq = env.seq(alice);
1879 auto const [txIDs, batchID] = submitBatch(
1880 env,
1881 tesSUCCESS,
1882 batch::outer(alice, seq, batchFee, tfUntilFailure),
1883 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1884 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1885 batch::inner(pay(alice, bob, XRP(3)), seq + 3),
1886 batch::inner(pay(alice, bob, XRP(4)), seq + 4));
1887 env.close();
1888
1889 std::vector<TestLedgerData> testCases = {
1890 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1891 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1892 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1893 {3, "Payment", "tesSUCCESS", txIDs[2], batchID},
1894 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
1895 };
1896 validateClosedLedger(env, testCases);
1897
1898 // Alice consumes sequences (# of txns)
1899 BEAST_EXPECT(env.seq(alice) == seq + 5);
1900
1901 // Alice pays XRP & Fee; Bob receives XRP
1902 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(10) - batchFee);
1903 BEAST_EXPECT(env.balance(bob) == preBob + XRP(10));
1904 }
1905
1906 // tec error
1907 {
1908 auto const preAlice = env.balance(alice);
1909 auto const preBob = env.balance(bob);
1910
1911 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1912 auto const seq = env.seq(alice);
1913 auto const [txIDs, batchID] = submitBatch(
1914 env,
1915 tesSUCCESS,
1916 batch::outer(alice, seq, batchFee, tfUntilFailure),
1917 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1918 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1919 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1920 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
1921 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1922 env.close();
1923
1924 std::vector<TestLedgerData> testCases = {
1925 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1926 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1927 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1928 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
1929 };
1930 validateClosedLedger(env, testCases);
1931
1932 // Alice consumes sequences (# of txns)
1933 BEAST_EXPECT(env.seq(alice) == seq + 4);
1934
1935 // Alice pays XRP & Fee; Bob receives XRP
1936 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1937 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1938 }
1939
1940 // tef error
1941 {
1942 auto const preAlice = env.balance(alice);
1943 auto const preBob = env.balance(bob);
1944
1945 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1946 auto const seq = env.seq(alice);
1947 auto const [txIDs, batchID] = submitBatch(
1948 env,
1949 tesSUCCESS,
1950 batch::outer(alice, seq, batchFee, tfUntilFailure),
1951 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1952 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1953 // tefNO_AUTH_REQUIRED: trustline auth is not required
1954 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 3),
1955 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1956 env.close();
1957
1958 std::vector<TestLedgerData> testCases = {
1959 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1960 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1961 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1962 };
1963 validateClosedLedger(env, testCases);
1964
1965 // Alice consumes sequences (# of txns)
1966 BEAST_EXPECT(env.seq(alice) == seq + 3);
1967
1968 // Alice pays XRP & Fee; Bob receives XRP
1969 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1970 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1971 }
1972
1973 // ter error
1974 {
1975 auto const preAlice = env.balance(alice);
1976 auto const preBob = env.balance(bob);
1977
1978 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1979 auto const seq = env.seq(alice);
1980 auto const [txIDs, batchID] = submitBatch(
1981 env,
1982 tesSUCCESS,
1983 batch::outer(alice, seq, batchFee, tfUntilFailure),
1984 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1985 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1986 // terPRE_TICKET: ticket does not exist
1987 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 3),
1988 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1989 env.close();
1990
1991 std::vector<TestLedgerData> testCases = {
1992 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1993 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1994 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1995 };
1996 validateClosedLedger(env, testCases);
1997
1998 // Alice consumes sequences (# of txns)
1999 BEAST_EXPECT(env.seq(alice) == seq + 3);
2000
2001 // Alice pays XRP & Fee; Bob receives XRP
2002 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2003 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2004 }
2005
2006 // tec (tecKILLED) error
2007 {
2008 auto const preAlice = env.balance(alice);
2009 auto const preBob = env.balance(bob);
2010 auto const preCarol = env.balance(carol);
2011 auto const seq = env.seq(alice);
2012 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2013 auto const [txIDs, batchID] = submitBatch(
2014 env,
2015 tesSUCCESS,
2016 batch::outer(alice, seq, batchFee, tfUntilFailure),
2017 batch::inner(pay(alice, bob, XRP(100)), seq + 1),
2018 batch::inner(pay(alice, carol, XRP(100)), seq + 2),
2020 offer(
2021 alice,
2022 alice["USD"](100),
2023 XRP(100),
2025 seq + 3),
2026 batch::inner(pay(alice, dave, XRP(100)), seq + 4));
2027 env.close();
2028
2029 std::vector<TestLedgerData> testCases = {
2030 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2031 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2032 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2033 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
2034 };
2035 validateClosedLedger(env, testCases);
2036
2037 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(200) - batchFee);
2038 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
2039 BEAST_EXPECT(env.balance(carol) == preCarol + XRP(100));
2040 }
2041 }
2042
2043 void
2045 {
2046 testcase("independent");
2047
2048 using namespace test::jtx;
2049 using namespace std::literals;
2050
2051 test::jtx::Env env{*this, envconfig()};
2052
2053 auto const alice = Account("alice");
2054 auto const bob = Account("bob");
2055 auto const carol = Account("carol");
2056 auto const gw = Account("gw");
2057 auto const USD = gw["USD"];
2058 env.fund(XRP(10000), alice, bob, carol, gw);
2059 env.close();
2060
2061 // multiple transactions fail
2062 {
2063 auto const preAlice = env.balance(alice);
2064 auto const preBob = env.balance(bob);
2065
2066 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2067 auto const seq = env.seq(alice);
2068 auto const [txIDs, batchID] = submitBatch(
2069 env,
2070 tesSUCCESS,
2071 batch::outer(alice, seq, batchFee, tfIndependent),
2072 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2073 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2074 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
2075 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2076 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
2077 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
2078 env.close();
2079
2080 std::vector<TestLedgerData> testCases = {
2081 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2082 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2083 {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID},
2084 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
2085 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
2086 };
2087 validateClosedLedger(env, testCases);
2088
2089 // Alice consumes sequences (# of txns)
2090 BEAST_EXPECT(env.seq(alice) == seq + 5);
2091
2092 // Alice pays XRP & Fee; Bob receives XRP
2093 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(4) - batchFee);
2094 BEAST_EXPECT(env.balance(bob) == preBob + XRP(4));
2095 }
2096
2097 // tec error
2098 {
2099 auto const preAlice = env.balance(alice);
2100 auto const preBob = env.balance(bob);
2101
2102 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2103 auto const seq = env.seq(alice);
2104 auto const [txIDs, batchID] = submitBatch(
2105 env,
2106 tesSUCCESS,
2107 batch::outer(alice, seq, batchFee, tfIndependent),
2108 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2109 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2110 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2111 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
2112 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
2113 env.close();
2114
2115 std::vector<TestLedgerData> testCases = {
2116 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2117 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2118 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2119 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
2120 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
2121 };
2122 validateClosedLedger(env, testCases);
2123
2124 // Alice consumes sequences (# of txns)
2125 BEAST_EXPECT(env.seq(alice) == seq + 5);
2126
2127 // Alice pays XRP & Fee; Bob receives XRP
2128 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(6) - batchFee);
2129 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2130 }
2131
2132 // tef error
2133 {
2134 auto const preAlice = env.balance(alice);
2135 auto const preBob = env.balance(bob);
2136
2137 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2138 auto const seq = env.seq(alice);
2139 auto const [txIDs, batchID] = submitBatch(
2140 env,
2141 tesSUCCESS,
2142 batch::outer(alice, seq, batchFee, tfIndependent),
2143 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2144 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2145 // tefNO_AUTH_REQUIRED: trustline auth is not required
2146 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 3),
2147 batch::inner(pay(alice, bob, XRP(3)), seq + 3));
2148 env.close();
2149
2150 std::vector<TestLedgerData> testCases = {
2151 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2152 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2153 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2154 {3, "Payment", "tesSUCCESS", txIDs[3], batchID},
2155 };
2156 validateClosedLedger(env, testCases);
2157
2158 // Alice consumes sequences (# of txns)
2159 BEAST_EXPECT(env.seq(alice) == seq + 4);
2160
2161 // Alice pays XRP & Fee; Bob receives XRP
2162 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(6));
2163 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2164 }
2165
2166 // ter error
2167 {
2168 auto const preAlice = env.balance(alice);
2169 auto const preBob = env.balance(bob);
2170
2171 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2172 auto const seq = env.seq(alice);
2173 auto const [txIDs, batchID] = submitBatch(
2174 env,
2175 tesSUCCESS,
2176 batch::outer(alice, seq, batchFee, tfIndependent),
2177 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2178 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2179 // terPRE_TICKET: ticket does not exist
2180 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 3),
2181 batch::inner(pay(alice, bob, XRP(3)), seq + 3));
2182 env.close();
2183
2184 std::vector<TestLedgerData> testCases = {
2185 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2186 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2187 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2188 {3, "Payment", "tesSUCCESS", txIDs[3], batchID},
2189 };
2190 validateClosedLedger(env, testCases);
2191
2192 // Alice consumes sequences (# of txns)
2193 BEAST_EXPECT(env.seq(alice) == seq + 4);
2194
2195 // Alice pays XRP & Fee; Bob receives XRP
2196 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(6));
2197 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2198 }
2199
2200 // tec (tecKILLED) error
2201 {
2202 auto const preAlice = env.balance(alice);
2203 auto const preBob = env.balance(bob);
2204 auto const preCarol = env.balance(carol);
2205 auto const seq = env.seq(alice);
2206 auto const batchFee = batch::calcBatchFee(env, 0, 3);
2207 auto const [txIDs, batchID] = submitBatch(
2208 env,
2209 tesSUCCESS,
2210 batch::outer(alice, seq, batchFee, tfIndependent),
2211 batch::inner(pay(alice, bob, XRP(100)), seq + 1),
2212 batch::inner(pay(alice, carol, XRP(100)), seq + 2),
2214 offer(
2215 alice,
2216 alice["USD"](100),
2217 XRP(100),
2219 seq + 3));
2220 env.close();
2221
2222 std::vector<TestLedgerData> testCases = {
2223 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2224 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2225 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2226 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
2227 };
2228 validateClosedLedger(env, testCases);
2229
2230 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(200) - batchFee);
2231 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
2232 BEAST_EXPECT(env.balance(carol) == preCarol + XRP(100));
2233 }
2234 }
2235
2236 void
2238 {
2239 testcase("inner submit rpc");
2240
2241 using namespace test::jtx;
2242 using namespace std::literals;
2243
2244 test::jtx::Env env{*this, envconfig()};
2245
2246 auto const alice = Account("alice");
2247 auto const bob = Account("bob");
2248
2249 env.fund(XRP(10000), alice, bob);
2250 env.close();
2251
2252 auto submitAndValidate = [&](Slice const& slice) {
2253 auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
2254 BEAST_EXPECT(
2255 jrr[jss::status] == "error" &&
2256 jrr[jss::error] == "invalidTransaction" &&
2257 jrr[jss::error_exception] ==
2258 "fails local checks: Malformed: Invalid inner batch "
2259 "transaction.");
2260 env.close();
2261 };
2262
2263 // Invalid RPC Submission: TxnSignature
2264 // - has `TxnSignature` field
2265 // - has no `SigningPubKey` field
2266 // - has no `Signers` field
2267 // - has `tfInnerBatchTxn` flag
2268 {
2269 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2270 txn[sfTxnSignature] = "DEADBEEF";
2271 STParsedJSONObject parsed("test", txn.getTxn());
2272 Serializer s;
2273 parsed.object->add(s);
2274 submitAndValidate(s.slice());
2275 }
2276
2277 // Invalid RPC Submission: SigningPubKey
2278 // - has no `TxnSignature` field
2279 // - has `SigningPubKey` field
2280 // - has no `Signers` field
2281 // - has `tfInnerBatchTxn` flag
2282 {
2283 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2284 txn[sfSigningPubKey] = strHex(alice.pk());
2285 STParsedJSONObject parsed("test", txn.getTxn());
2286 Serializer s;
2287 parsed.object->add(s);
2288 submitAndValidate(s.slice());
2289 }
2290
2291 // Invalid RPC Submission: Signers
2292 // - has no `TxnSignature` field
2293 // - has empty `SigningPubKey` field
2294 // - has `Signers` field
2295 // - has `tfInnerBatchTxn` flag
2296 {
2297 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2298 txn[sfSigners] = Json::arrayValue;
2299 STParsedJSONObject parsed("test", txn.getTxn());
2300 Serializer s;
2301 parsed.object->add(s);
2302 submitAndValidate(s.slice());
2303 }
2304
2305 // Invalid RPC Submission: tfInnerBatchTxn
2306 // - has no `TxnSignature` field
2307 // - has empty `SigningPubKey` field
2308 // - has no `Signers` field
2309 // - has `tfInnerBatchTxn` flag
2310 {
2311 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2312 STParsedJSONObject parsed("test", txn.getTxn());
2313 Serializer s;
2314 parsed.object->add(s);
2315 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
2316 BEAST_EXPECT(
2317 jrr[jss::status] == "success" &&
2318 jrr[jss::engine_result] == "temINVALID_FLAG");
2319
2320 env.close();
2321 }
2322 }
2323
2324 void
2326 {
2327 testcase("account activation");
2328
2329 using namespace test::jtx;
2330 using namespace std::literals;
2331
2332 test::jtx::Env env{*this, envconfig()};
2333
2334 auto const alice = Account("alice");
2335 auto const bob = Account("bob");
2336 env.fund(XRP(10000), alice);
2337 env.close();
2338 env.memoize(bob);
2339
2340 auto const preAlice = env.balance(alice);
2341 auto const ledSeq = env.current()->seq();
2342 auto const seq = env.seq(alice);
2343 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2344 auto const [txIDs, batchID] = submitBatch(
2345 env,
2346 tesSUCCESS,
2347 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2348 batch::inner(pay(alice, bob, XRP(1000)), seq + 1),
2350 batch::sig(bob));
2351 env.close();
2352
2353 std::vector<TestLedgerData> testCases = {
2354 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2355 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2356 {2, "AccountSet", "tesSUCCESS", txIDs[1], batchID},
2357 };
2358 validateClosedLedger(env, testCases);
2359
2360 // Alice consumes sequences (# of txns)
2361 BEAST_EXPECT(env.seq(alice) == seq + 2);
2362
2363 // Bob consumes sequences (# of txns)
2364 BEAST_EXPECT(env.seq(bob) == ledSeq + 1);
2365
2366 // Alice pays XRP & Fee; Bob receives XRP
2367 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - batchFee);
2368 BEAST_EXPECT(env.balance(bob) == XRP(1000));
2369 }
2370
2371 void
2373 {
2374 testcase("account set");
2375
2376 using namespace test::jtx;
2377 using namespace std::literals;
2378
2379 test::jtx::Env env{*this, envconfig()};
2380
2381 auto const alice = Account("alice");
2382 auto const bob = Account("bob");
2383 env.fund(XRP(10000), alice, bob);
2384 env.close();
2385
2386 auto const preAlice = env.balance(alice);
2387 auto const preBob = env.balance(bob);
2388
2389 auto const seq = env.seq(alice);
2390 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2391 auto tx1 = batch::inner(noop(alice), seq + 1);
2392 std::string domain = "example.com";
2393 tx1[sfDomain] = strHex(domain);
2394 auto const [txIDs, batchID] = submitBatch(
2395 env,
2396 tesSUCCESS,
2397 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2398 tx1,
2399 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
2400 env.close();
2401
2402 std::vector<TestLedgerData> testCases = {
2403 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2404 {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID},
2405 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2406 };
2407 validateClosedLedger(env, testCases);
2408
2409 auto const sle = env.le(keylet::account(alice));
2410 BEAST_EXPECT(sle);
2411 BEAST_EXPECT(
2412 sle->getFieldVL(sfDomain) == Blob(domain.begin(), domain.end()));
2413
2414 // Alice consumes sequences (# of txns)
2415 BEAST_EXPECT(env.seq(alice) == seq + 3);
2416
2417 // Alice pays XRP & Fee; Bob receives XRP
2418 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
2419 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
2420 }
2421
2422 void
2424 {
2425 testcase("account delete");
2426
2427 using namespace test::jtx;
2428 using namespace std::literals;
2429
2430 // tfIndependent: account delete success
2431 {
2432 test::jtx::Env env{*this, envconfig()};
2433
2434 auto const alice = Account("alice");
2435 auto const bob = Account("bob");
2436 env.fund(XRP(10000), alice, bob);
2437 env.close();
2438
2439 incLgrSeqForAccDel(env, alice);
2440 for (int i = 0; i < 5; ++i)
2441 env.close();
2442
2443 auto const preAlice = env.balance(alice);
2444 auto const preBob = env.balance(bob);
2445
2446 auto const seq = env.seq(alice);
2447 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2448 env.current()->fees().increment;
2449 auto const [txIDs, batchID] = submitBatch(
2450 env,
2451 tesSUCCESS,
2452 batch::outer(alice, seq, batchFee, tfIndependent),
2453 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2454 batch::inner(acctdelete(alice, bob), seq + 2),
2455 // terNO_ACCOUNT: alice does not exist
2456 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2457 env.close();
2458
2459 std::vector<TestLedgerData> testCases = {
2460 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2461 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2462 {2, "AccountDelete", "tesSUCCESS", txIDs[1], batchID},
2463 };
2464 validateClosedLedger(env, testCases);
2465
2466 // Alice does not exist; Bob receives Alice's XRP
2467 BEAST_EXPECT(!env.le(keylet::account(alice)));
2468 BEAST_EXPECT(env.balance(bob) == preBob + (preAlice - batchFee));
2469 }
2470
2471 // tfIndependent: account delete fails
2472 {
2473 test::jtx::Env env{*this, envconfig()};
2474
2475 auto const alice = Account("alice");
2476 auto const bob = Account("bob");
2477 env.fund(XRP(10000), alice, bob);
2478 env.close();
2479
2480 incLgrSeqForAccDel(env, alice);
2481 for (int i = 0; i < 5; ++i)
2482 env.close();
2483
2484 auto const preAlice = env.balance(alice);
2485 auto const preBob = env.balance(bob);
2486
2487 env.trust(bob["USD"](1000), alice);
2488 env.close();
2489
2490 auto const seq = env.seq(alice);
2491 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2492 env.current()->fees().increment;
2493 auto const [txIDs, batchID] = submitBatch(
2494 env,
2495 tesSUCCESS,
2496 batch::outer(alice, seq, batchFee, tfIndependent),
2497 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2498 // tecHAS_OBLIGATIONS: alice has obligations
2499 batch::inner(acctdelete(alice, bob), seq + 2),
2500 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2501 env.close();
2502
2503 std::vector<TestLedgerData> testCases = {
2504 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2505 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2506 {2, "AccountDelete", "tecHAS_OBLIGATIONS", txIDs[1], batchID},
2507 {3, "Payment", "tesSUCCESS", txIDs[2], batchID},
2508 };
2509 validateClosedLedger(env, testCases);
2510
2511 // Alice does not exist; Bob receives XRP
2512 BEAST_EXPECT(env.le(keylet::account(alice)));
2513 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2514 }
2515
2516 // tfAllOrNothing: account delete fails
2517 {
2518 test::jtx::Env env{*this, envconfig()};
2519
2520 auto const alice = Account("alice");
2521 auto const bob = Account("bob");
2522 env.fund(XRP(10000), alice, bob);
2523 env.close();
2524
2525 incLgrSeqForAccDel(env, alice);
2526 for (int i = 0; i < 5; ++i)
2527 env.close();
2528
2529 auto const preAlice = env.balance(alice);
2530 auto const preBob = env.balance(bob);
2531
2532 auto const seq = env.seq(alice);
2533 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2534 env.current()->fees().increment;
2535 auto const [txIDs, batchID] = submitBatch(
2536 env,
2537 tesSUCCESS,
2538 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2539 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2540 batch::inner(acctdelete(alice, bob), seq + 2),
2541 // terNO_ACCOUNT: alice does not exist
2542 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2543 env.close();
2544
2545 std::vector<TestLedgerData> testCases = {
2546 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2547 };
2548 validateClosedLedger(env, testCases);
2549
2550 // Alice still exists; Bob is unchanged
2551 BEAST_EXPECT(env.le(keylet::account(alice)));
2552 BEAST_EXPECT(env.balance(bob) == preBob);
2553 }
2554 }
2555
2556 void
2558 {
2559 testcase("object create w/ sequence");
2560
2561 using namespace test::jtx;
2562 using namespace std::literals;
2563
2564 test::jtx::Env env{*this, envconfig()};
2565
2566 auto const alice = Account("alice");
2567 auto const bob = Account("bob");
2568 auto const gw = Account("gw");
2569 auto const USD = gw["USD"];
2570
2571 env.fund(XRP(10000), alice, bob, gw);
2572 env.close();
2573
2574 env.trust(USD(1000), alice, bob);
2575 env(pay(gw, alice, USD(100)));
2576 env(pay(gw, bob, USD(100)));
2577 env.close();
2578
2579 // success
2580 {
2581 auto const aliceSeq = env.seq(alice);
2582 auto const bobSeq = env.seq(bob);
2583 auto const preAlice = env.balance(alice);
2584 auto const preBob = env.balance(bob);
2585 auto const preAliceUSD = env.balance(alice, USD.issue());
2586 auto const preBobUSD = env.balance(bob, USD.issue());
2587
2588 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2589 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2590 auto const [txIDs, batchID] = submitBatch(
2591 env,
2592 tesSUCCESS,
2593 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2594 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2595 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2596 batch::sig(bob));
2597 env.close();
2598
2599 std::vector<TestLedgerData> testCases = {
2600 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2601 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
2602 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
2603 };
2604 validateClosedLedger(env, testCases);
2605
2606 // Alice consumes sequences (# of txns)
2607 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2608
2609 // Alice consumes sequences (# of txns)
2610 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2611
2612 // Alice pays Fee; Bob XRP Unchanged
2613 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2614 BEAST_EXPECT(env.balance(bob) == preBob);
2615
2616 // Alice pays USD & Bob receives USD
2617 BEAST_EXPECT(
2618 env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2619 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2620 }
2621
2622 // failure
2623 {
2624 env(fset(alice, asfRequireDest));
2625 env.close();
2626
2627 auto const aliceSeq = env.seq(alice);
2628 auto const bobSeq = env.seq(bob);
2629 auto const preAlice = env.balance(alice);
2630 auto const preBob = env.balance(bob);
2631 auto const preAliceUSD = env.balance(alice, USD.issue());
2632 auto const preBobUSD = env.balance(bob, USD.issue());
2633
2634 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2635 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2636 auto const [txIDs, batchID] = submitBatch(
2637 env,
2638 tesSUCCESS,
2639 batch::outer(alice, aliceSeq, batchFee, tfIndependent),
2640 // tecDST_TAG_NEEDED - alice has enabled asfRequireDest
2641 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2642 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2643 batch::sig(bob));
2644 env.close();
2645
2646 std::vector<TestLedgerData> testCases = {
2647 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2648 {1, "CheckCreate", "tecDST_TAG_NEEDED", txIDs[0], batchID},
2649 {2, "CheckCash", "tecNO_ENTRY", txIDs[1], batchID},
2650 };
2651 validateClosedLedger(env, testCases);
2652
2653 // Alice consumes sequences (# of txns)
2654 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2655
2656 // Bob consumes sequences (# of txns)
2657 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2658
2659 // Alice pays Fee; Bob XRP Unchanged
2660 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2661 BEAST_EXPECT(env.balance(bob) == preBob);
2662
2663 // Alice pays USD & Bob receives USD
2664 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
2665 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
2666 }
2667 }
2668
2669 void
2671 {
2672 testcase("object create w/ ticket");
2673
2674 using namespace test::jtx;
2675 using namespace std::literals;
2676
2677 test::jtx::Env env{*this, envconfig()};
2678
2679 auto const alice = Account("alice");
2680 auto const bob = Account("bob");
2681 auto const gw = Account("gw");
2682 auto const USD = gw["USD"];
2683
2684 env.fund(XRP(10000), alice, bob, gw);
2685 env.close();
2686
2687 env.trust(USD(1000), alice, bob);
2688 env(pay(gw, alice, USD(100)));
2689 env(pay(gw, bob, USD(100)));
2690 env.close();
2691
2692 auto const aliceSeq = env.seq(alice);
2693 auto const bobSeq = env.seq(bob);
2694 auto const preAlice = env.balance(alice);
2695 auto const preBob = env.balance(bob);
2696 auto const preAliceUSD = env.balance(alice, USD.issue());
2697 auto const preBobUSD = env.balance(bob, USD.issue());
2698
2699 auto const batchFee = batch::calcBatchFee(env, 1, 3);
2700 uint256 const chkID{getCheckIndex(bob, bobSeq + 1)};
2701 auto const [txIDs, batchID] = submitBatch(
2702 env,
2703 tesSUCCESS,
2704 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2705 batch::inner(ticket::create(bob, 10), bobSeq),
2706 batch::inner(check::create(bob, alice, USD(10)), 0, bobSeq + 1),
2707 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2708 batch::sig(bob));
2709 env.close();
2710
2711 std::vector<TestLedgerData> testCases = {
2712 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2713 {1, "TicketCreate", "tesSUCCESS", txIDs[0], batchID},
2714 {2, "CheckCreate", "tesSUCCESS", txIDs[1], batchID},
2715 {3, "CheckCash", "tesSUCCESS", txIDs[2], batchID},
2716 };
2717 validateClosedLedger(env, testCases);
2718
2719 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2720 BEAST_EXPECT(env.seq(bob) == bobSeq + 10 + 1);
2721 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2722 BEAST_EXPECT(env.balance(bob) == preBob);
2723 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2724 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2725 }
2726
2727 void
2729 {
2730 testcase("object create w/ 3rd party");
2731
2732 using namespace test::jtx;
2733 using namespace std::literals;
2734
2735 test::jtx::Env env{*this, envconfig()};
2736
2737 auto const alice = Account("alice");
2738 auto const bob = Account("bob");
2739 auto const carol = Account("carol");
2740 auto const gw = Account("gw");
2741 auto const USD = gw["USD"];
2742
2743 env.fund(XRP(10000), alice, bob, carol, gw);
2744 env.close();
2745
2746 env.trust(USD(1000), alice, bob);
2747 env(pay(gw, alice, USD(100)));
2748 env(pay(gw, bob, USD(100)));
2749 env.close();
2750
2751 auto const aliceSeq = env.seq(alice);
2752 auto const bobSeq = env.seq(bob);
2753 auto const carolSeq = env.seq(carol);
2754 auto const preAlice = env.balance(alice);
2755 auto const preBob = env.balance(bob);
2756 auto const preCarol = env.balance(carol);
2757 auto const preAliceUSD = env.balance(alice, USD.issue());
2758 auto const preBobUSD = env.balance(bob, USD.issue());
2759
2760 auto const batchFee = batch::calcBatchFee(env, 2, 2);
2761 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2762 auto const [txIDs, batchID] = submitBatch(
2763 env,
2764 tesSUCCESS,
2765 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
2766 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2767 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq),
2768 batch::sig(alice, bob));
2769 env.close();
2770
2771 std::vector<TestLedgerData> testCases = {
2772 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2773 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
2774 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
2775 };
2776 validateClosedLedger(env, testCases);
2777
2778 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2779 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2780 BEAST_EXPECT(env.seq(carol) == carolSeq + 1);
2781 BEAST_EXPECT(env.balance(alice) == preAlice);
2782 BEAST_EXPECT(env.balance(bob) == preBob);
2783 BEAST_EXPECT(env.balance(carol) == preCarol - batchFee);
2784 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2785 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2786 }
2787
2788 void
2790 {
2791 {
2792 testcase("tickets outer");
2793
2794 using namespace test::jtx;
2795 using namespace std::literals;
2796
2797 test::jtx::Env env{*this, envconfig()};
2798
2799 auto const alice = Account("alice");
2800 auto const bob = Account("bob");
2801
2802 env.fund(XRP(10000), alice, bob);
2803 env.close();
2804
2805 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2806 env(ticket::create(alice, 10));
2807 env.close();
2808
2809 auto const aliceSeq = env.seq(alice);
2810 auto const preAlice = env.balance(alice);
2811 auto const preBob = env.balance(bob);
2812
2813 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2814 auto const [txIDs, batchID] = submitBatch(
2815 env,
2816 tesSUCCESS,
2817 batch::outer(alice, 0, batchFee, tfAllOrNothing),
2818 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 0),
2819 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
2820 ticket::use(aliceTicketSeq));
2821 env.close();
2822
2823 std::vector<TestLedgerData> testCases = {
2824 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2825 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2826 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2827 };
2828 validateClosedLedger(env, testCases);
2829
2830 auto const sle = env.le(keylet::account(alice));
2831 BEAST_EXPECT(sle);
2832 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 9);
2833 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 9);
2834
2835 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2836 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2837 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2838 }
2839
2840 {
2841 testcase("tickets inner");
2842
2843 using namespace test::jtx;
2844 using namespace std::literals;
2845
2846 test::jtx::Env env{*this, envconfig()};
2847
2848 auto const alice = Account("alice");
2849 auto const bob = Account("bob");
2850
2851 env.fund(XRP(10000), alice, bob);
2852 env.close();
2853
2854 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2855 env(ticket::create(alice, 10));
2856 env.close();
2857
2858 auto const aliceSeq = env.seq(alice);
2859 auto const preAlice = env.balance(alice);
2860 auto const preBob = env.balance(bob);
2861
2862 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2863 auto const [txIDs, batchID] = submitBatch(
2864 env,
2865 tesSUCCESS,
2866 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2867 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq),
2868 batch::inner(pay(alice, bob, XRP(2)), 0, aliceTicketSeq + 1));
2869 env.close();
2870
2871 std::vector<TestLedgerData> testCases = {
2872 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2873 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2874 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2875 };
2876 validateClosedLedger(env, testCases);
2877
2878 auto const sle = env.le(keylet::account(alice));
2879 BEAST_EXPECT(sle);
2880 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
2881 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
2882
2883 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2884 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2885 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2886 }
2887
2888 {
2889 testcase("tickets outer inner");
2890
2891 using namespace test::jtx;
2892 using namespace std::literals;
2893
2894 test::jtx::Env env{*this, envconfig()};
2895
2896 auto const alice = Account("alice");
2897 auto const bob = Account("bob");
2898
2899 env.fund(XRP(10000), alice, bob);
2900 env.close();
2901
2902 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2903 env(ticket::create(alice, 10));
2904 env.close();
2905
2906 auto const aliceSeq = env.seq(alice);
2907 auto const preAlice = env.balance(alice);
2908 auto const preBob = env.balance(bob);
2909
2910 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2911 auto const [txIDs, batchID] = submitBatch(
2912 env,
2913 tesSUCCESS,
2914 batch::outer(alice, 0, batchFee, tfAllOrNothing),
2915 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
2916 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
2917 ticket::use(aliceTicketSeq));
2918 env.close();
2919
2920 std::vector<TestLedgerData> testCases = {
2921 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2922 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2923 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2924 };
2925 validateClosedLedger(env, testCases);
2926
2927 auto const sle = env.le(keylet::account(alice));
2928 BEAST_EXPECT(sle);
2929 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
2930 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
2931
2932 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2933 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2934 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2935 }
2936 }
2937
2938 void
2940 {
2941 testcase("sequence open ledger");
2942
2943 using namespace test::jtx;
2944 using namespace std::literals;
2945
2946 auto const alice = Account("alice");
2947 auto const bob = Account("bob");
2948 auto const carol = Account("carol");
2949
2950 // Before Batch Txn w/ retry following ledger
2951 {
2952 // IMPORTANT: The batch txn is applied first, then the noop txn.
2953 // Because of this ordering, the noop txn is not applied and is
2954 // overwritten by the payment in the batch transaction. Because the
2955 // terPRE_SEQ is outside of the batch this noop transaction will ge
2956 // reapplied in the following ledger
2957 test::jtx::Env env{*this, envconfig()};
2958 env.fund(XRP(10000), alice, bob, carol);
2959 env.close();
2960
2961 auto const aliceSeq = env.seq(alice);
2962 auto const carolSeq = env.seq(carol);
2963
2964 // AccountSet Txn
2965 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 2));
2966 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
2967 env(noopTxn, ter(terPRE_SEQ));
2968
2969 // Batch Txn
2970 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2971 auto const [txIDs, batchID] = submitBatch(
2972 env,
2973 tesSUCCESS,
2974 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
2975 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
2976 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
2977 batch::sig(alice));
2978 env.close();
2979
2980 {
2981 std::vector<TestLedgerData> testCases = {
2982 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2983 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2984 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2985 };
2986 validateClosedLedger(env, testCases);
2987 }
2988
2989 env.close();
2990 {
2991 // next ledger contains noop txn
2992 std::vector<TestLedgerData> testCases = {
2993 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
2994 };
2995 validateClosedLedger(env, testCases);
2996 }
2997 }
2998
2999 // Before Batch Txn w/ same sequence
3000 {
3001 // IMPORTANT: The batch txn is applied first, then the noop txn.
3002 // Because of this ordering, the noop txn is not applied and is
3003 // overwritten by the payment in the batch transaction.
3004 test::jtx::Env env{*this, envconfig()};
3005 env.fund(XRP(10000), alice, bob);
3006 env.close();
3007
3008 auto const aliceSeq = env.seq(alice);
3009
3010 // AccountSet Txn
3011 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
3012 env(noopTxn, ter(terPRE_SEQ));
3013
3014 // Batch Txn
3015 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3016 auto const [txIDs, batchID] = submitBatch(
3017 env,
3018 tesSUCCESS,
3019 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3020 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3021 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3022 env.close();
3023
3024 {
3025 std::vector<TestLedgerData> testCases = {
3026 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3027 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3028 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3029 };
3030 validateClosedLedger(env, testCases);
3031 }
3032
3033 env.close();
3034 {
3035 // next ledger is empty
3036 std::vector<TestLedgerData> testCases = {};
3037 validateClosedLedger(env, testCases);
3038 }
3039 }
3040
3041 // After Batch Txn w/ same sequence
3042 {
3043 // IMPORTANT: The batch txn is applied first, then the noop txn.
3044 // Because of this ordering, the noop txn is not applied and is
3045 // overwritten by the payment in the batch transaction.
3046 test::jtx::Env env{*this, envconfig()};
3047 env.fund(XRP(10000), alice, bob);
3048 env.close();
3049
3050 auto const aliceSeq = env.seq(alice);
3051 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3052 auto const [txIDs, batchID] = submitBatch(
3053 env,
3054 tesSUCCESS,
3055 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3056 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3057 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3058
3059 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
3060 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3061 env(noopTxn, ter(tesSUCCESS));
3062 env.close();
3063
3064 {
3065 std::vector<TestLedgerData> testCases = {
3066 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3067 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3068 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3069 };
3070 validateClosedLedger(env, testCases);
3071 }
3072
3073 env.close();
3074 {
3075 // next ledger is empty
3076 std::vector<TestLedgerData> testCases = {};
3077 validateClosedLedger(env, testCases);
3078 }
3079 }
3080
3081 // Outer Batch terPRE_SEQ
3082 {
3083 test::jtx::Env env{*this, envconfig()};
3084 env.fund(XRP(10000), alice, bob, carol);
3085 env.close();
3086
3087 auto const aliceSeq = env.seq(alice);
3088 auto const carolSeq = env.seq(carol);
3089
3090 // Batch Txn
3091 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3092 auto const [txIDs, batchID] = submitBatch(
3093 env,
3094 terPRE_SEQ,
3095 batch::outer(carol, carolSeq + 1, batchFee, tfAllOrNothing),
3096 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
3097 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3098 batch::sig(alice));
3099
3100 // AccountSet Txn
3101 auto const noopTxn = env.jt(noop(carol), seq(carolSeq));
3102 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3103 env(noopTxn, ter(tesSUCCESS));
3104 env.close();
3105
3106 {
3107 std::vector<TestLedgerData> testCases = {
3108 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
3109 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3110 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3111 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3112 };
3113 validateClosedLedger(env, testCases);
3114 }
3115
3116 env.close();
3117 {
3118 // next ledger contains no transactions
3119 std::vector<TestLedgerData> testCases = {};
3120 validateClosedLedger(env, testCases);
3121 }
3122 }
3123 }
3124
3125 void
3127 {
3128 testcase("tickets open ledger");
3129
3130 using namespace test::jtx;
3131 using namespace std::literals;
3132
3133 auto const alice = Account("alice");
3134 auto const bob = Account("bob");
3135
3136 // Before Batch Txn w/ same ticket
3137 {
3138 // IMPORTANT: The batch txn is applied first, then the noop txn.
3139 // Because of this ordering, the noop txn is not applied and is
3140 // overwritten by the payment in the batch transaction.
3141 test::jtx::Env env{*this, envconfig()};
3142 env.fund(XRP(10000), alice, bob);
3143 env.close();
3144
3145 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3146 env(ticket::create(alice, 10));
3147 env.close();
3148
3149 auto const aliceSeq = env.seq(alice);
3150
3151 // AccountSet Txn
3152 auto const noopTxn =
3153 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3154 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3155 env(noopTxn, ter(tesSUCCESS));
3156
3157 // Batch Txn
3158 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3159 auto const [txIDs, batchID] = submitBatch(
3160 env,
3161 tesSUCCESS,
3162 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3163 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3164 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3165 ticket::use(aliceTicketSeq));
3166 env.close();
3167
3168 {
3169 std::vector<TestLedgerData> testCases = {
3170 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3171 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3172 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3173 };
3174 validateClosedLedger(env, testCases);
3175 }
3176
3177 env.close();
3178 {
3179 // next ledger is empty
3180 std::vector<TestLedgerData> testCases = {};
3181 validateClosedLedger(env, testCases);
3182 }
3183 }
3184
3185 // After Batch Txn w/ same ticket
3186 {
3187 // IMPORTANT: The batch txn is applied first, then the noop txn.
3188 // Because of this ordering, the noop txn is not applied and is
3189 // overwritten by the payment in the batch transaction.
3190 test::jtx::Env env{*this, envconfig()};
3191 env.fund(XRP(10000), alice, bob);
3192 env.close();
3193
3194 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3195 env(ticket::create(alice, 10));
3196 env.close();
3197
3198 auto const aliceSeq = env.seq(alice);
3199
3200 // Batch Txn
3201 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3202 auto const [txIDs, batchID] = submitBatch(
3203 env,
3204 tesSUCCESS,
3205 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3206 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3207 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3208 ticket::use(aliceTicketSeq));
3209
3210 // AccountSet Txn
3211 auto const noopTxn =
3212 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3213 env(noopTxn);
3214
3215 env.close();
3216 {
3217 std::vector<TestLedgerData> testCases = {
3218 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3219 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3220 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3221 };
3222 validateClosedLedger(env, testCases);
3223 }
3224
3225 env.close();
3226 {
3227 // next ledger is empty
3228 std::vector<TestLedgerData> testCases = {};
3229 validateClosedLedger(env, testCases);
3230 }
3231 }
3232 }
3233
3234 void
3236 {
3237 testcase("objects open ledger");
3238
3239 using namespace test::jtx;
3240 using namespace std::literals;
3241
3242 auto const alice = Account("alice");
3243 auto const bob = Account("bob");
3244
3245 // Consume Object Before Batch Txn
3246 {
3247 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3248 // because the create transaction has not been applied because the
3249 // batch will run in the close ledger process. The batch will be
3250 // allied and then retry this transaction in the current ledger.
3251
3252 test::jtx::Env env{*this, envconfig()};
3253 env.fund(XRP(10000), alice, bob);
3254 env.close();
3255
3256 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3257 env(ticket::create(alice, 10));
3258 env.close();
3259
3260 auto const aliceSeq = env.seq(alice);
3261
3262 // CheckCash Txn
3263 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3264 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3265 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3266 env(objTxn, ter(tecNO_ENTRY));
3267
3268 // Batch Txn
3269 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3270 auto const [txIDs, batchID] = submitBatch(
3271 env,
3272 tesSUCCESS,
3273 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3274 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3275 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3276 ticket::use(aliceTicketSeq));
3277
3278 env.close();
3279 {
3280 std::vector<TestLedgerData> testCases = {
3281 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3282 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3283 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3284 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3285 };
3286 validateClosedLedger(env, testCases);
3287 }
3288
3289 env.close();
3290 {
3291 // next ledger is empty
3292 std::vector<TestLedgerData> testCases = {};
3293 validateClosedLedger(env, testCases);
3294 }
3295 }
3296
3297 // Create Object Before Batch Txn
3298 {
3299 test::jtx::Env env{*this, envconfig()};
3300 env.fund(XRP(10000), alice, bob);
3301 env.close();
3302
3303 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3304 env(ticket::create(alice, 10));
3305 env.close();
3306
3307 auto const aliceSeq = env.seq(alice);
3308 auto const bobSeq = env.seq(bob);
3309
3310 // CheckCreate Txn
3311 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3312 auto const objTxn = env.jt(check::create(alice, bob, XRP(10)));
3313 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3314 env(objTxn, ter(tesSUCCESS));
3315
3316 // Batch Txn
3317 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3318 auto const [txIDs, batchID] = submitBatch(
3319 env,
3320 tesSUCCESS,
3321 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3322 batch::inner(check::cash(bob, chkID, XRP(10)), bobSeq),
3323 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3324 ticket::use(aliceTicketSeq),
3325 batch::sig(bob));
3326
3327 env.close();
3328 {
3329 std::vector<TestLedgerData> testCases = {
3330 {0, "CheckCreate", "tesSUCCESS", objTxnID, std::nullopt},
3331 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3332 {2, "CheckCash", "tesSUCCESS", txIDs[0], batchID},
3333 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3334 };
3335 validateClosedLedger(env, testCases);
3336 }
3337 }
3338
3339 // After Batch Txn
3340 {
3341 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3342 // because the create transaction has not been applied because the
3343 // batch will run in the close ledger process. The batch will be
3344 // applied and then retry this transaction in the current ledger.
3345
3346 test::jtx::Env env{*this, envconfig()};
3347 env.fund(XRP(10000), alice, bob);
3348 env.close();
3349
3350 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3351 env(ticket::create(alice, 10));
3352 env.close();
3353
3354 auto const aliceSeq = env.seq(alice);
3355
3356 // Batch Txn
3357 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3358 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3359 auto const [txIDs, batchID] = submitBatch(
3360 env,
3361 tesSUCCESS,
3362 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3363 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3364 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3365 ticket::use(aliceTicketSeq));
3366
3367 // CheckCash Txn
3368 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3369 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3370 env(objTxn, ter(tecNO_ENTRY));
3371
3372 env.close();
3373 {
3374 std::vector<TestLedgerData> testCases = {
3375 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3376 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3377 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3378 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3379 };
3380 validateClosedLedger(env, testCases);
3381 }
3382 }
3383 }
3384
3385 void
3387 {
3388 testcase("pseudo txn with tfInnerBatchTxn");
3389
3390 using namespace test::jtx;
3391 using namespace std::literals;
3392
3393 test::jtx::Env env{*this, envconfig()};
3394
3395 auto const alice = Account("alice");
3396 auto const bob = Account("bob");
3397 env.fund(XRP(10000), alice, bob);
3398 env.close();
3399
3400 STTx const stx = STTx(ttAMENDMENT, [&](auto& obj) {
3401 obj.setAccountID(sfAccount, AccountID());
3402 obj.setFieldH256(sfAmendment, uint256(2));
3403 obj.setFieldU32(sfLedgerSequence, env.seq(alice));
3404 obj.setFieldU32(sfFlags, tfInnerBatchTxn);
3405 });
3406
3407 std::string reason;
3408 BEAST_EXPECT(isPseudoTx(stx));
3409 BEAST_EXPECT(!passesLocalChecks(stx, reason));
3410 BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
3411 env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
3412 auto const result = ripple::apply(env.app(), view, stx, tapNONE, j);
3413 BEAST_EXPECT(!result.applied && result.ter == temINVALID_FLAG);
3414 return result.applied;
3415 });
3416 }
3417
3418 void
3420 {
3421 testcase("batch open ledger");
3422 // IMPORTANT: When a transaction is submitted outside of a batch and
3423 // another transaction is part of the batch, the batch might fail
3424 // because the sequence is out of order. This is because the canonical
3425 // order of transactions is determined by the account first. So in this
3426 // case, alice's batch comes after bobs self submitted transaction even
3427 // though the payment was submitted after the batch.
3428
3429 using namespace test::jtx;
3430 using namespace std::literals;
3431
3432 test::jtx::Env env{*this, envconfig()};
3433 XRPAmount const baseFee = env.current()->fees().base;
3434
3435 auto const alice = Account("alice");
3436 auto const bob = Account("bob");
3437
3438 env.fund(XRP(10000), alice, bob);
3439 env.close();
3440
3441 env(noop(bob), ter(tesSUCCESS));
3442 env.close();
3443
3444 auto const aliceSeq = env.seq(alice);
3445 auto const preAlice = env.balance(alice);
3446 auto const preBob = env.balance(bob);
3447 auto const bobSeq = env.seq(bob);
3448
3449 // Alice Pays Bob (Open Ledger)
3450 auto const payTxn1 = env.jt(pay(alice, bob, XRP(10)), seq(aliceSeq));
3451 auto const payTxn1ID = to_string(payTxn1.stx->getTransactionID());
3452 env(payTxn1, ter(tesSUCCESS));
3453
3454 // Alice & Bob Atomic Batch
3455 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3456 auto const [txIDs, batchID] = submitBatch(
3457 env,
3458 tesSUCCESS,
3459 batch::outer(alice, aliceSeq + 1, batchFee, tfAllOrNothing),
3460 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 2),
3461 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3462 batch::sig(bob));
3463
3464 // Bob pays Alice (Open Ledger)
3465 auto const payTxn2 = env.jt(pay(bob, alice, XRP(5)), seq(bobSeq + 1));
3466 auto const payTxn2ID = to_string(payTxn2.stx->getTransactionID());
3467 env(payTxn2, ter(terPRE_SEQ));
3468 env.close();
3469
3470 std::vector<TestLedgerData> testCases = {
3471 {0, "Payment", "tesSUCCESS", payTxn1ID, std::nullopt},
3472 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3473 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3474 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3475 };
3476 validateClosedLedger(env, testCases);
3477
3478 env.close();
3479 {
3480 // next ledger includes the payment txn
3481 std::vector<TestLedgerData> testCases = {
3482 {0, "Payment", "tesSUCCESS", payTxn2ID, std::nullopt},
3483 };
3484 validateClosedLedger(env, testCases);
3485 }
3486
3487 // Alice consumes sequences (# of txns)
3488 BEAST_EXPECT(env.seq(alice) == aliceSeq + 3);
3489
3490 // Alice consumes sequences (# of txns)
3491 BEAST_EXPECT(env.seq(bob) == bobSeq + 2);
3492
3493 // Alice pays XRP & Fee; Bob receives XRP & pays Fee
3494 BEAST_EXPECT(
3495 env.balance(alice) == preAlice - XRP(10) - batchFee - baseFee);
3496 BEAST_EXPECT(env.balance(bob) == preBob + XRP(10) - baseFee);
3497 }
3498
3499 void
3501 {
3502 testcase("batch tx queue");
3503
3504 using namespace test::jtx;
3505 using namespace std::literals;
3506
3507 // only outer batch transactions are counter towards the queue size
3508 {
3509 test::jtx::Env env{
3510 *this,
3511 makeSmallQueueConfig(
3512 {{"minimum_txn_in_ledger_standalone", "2"}}),
3513 nullptr,
3515
3516 auto alice = Account("alice");
3517 auto bob = Account("bob");
3518 auto carol = Account("carol");
3519
3520 // Fund across several ledgers so the TxQ metrics stay restricted.
3521 env.fund(XRP(10000), noripple(alice, bob));
3522 env.close(env.now() + 5s, 10000ms);
3523 env.fund(XRP(10000), noripple(carol));
3524 env.close(env.now() + 5s, 10000ms);
3525
3526 // Fill the ledger
3527 env(noop(alice));
3528 env(noop(alice));
3529 env(noop(alice));
3530 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3531
3532 env(noop(carol), ter(terQUEUED));
3533 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3534
3535 auto const aliceSeq = env.seq(alice);
3536 auto const bobSeq = env.seq(bob);
3537 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3538
3539 // Queue Batch
3540 {
3541 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3542 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3543 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3544 batch::sig(bob),
3545 ter(terQUEUED));
3546 }
3547
3548 checkMetrics(*this, env, 2, std::nullopt, 3, 2);
3549
3550 // Replace Queued Batch
3551 {
3552 env(batch::outer(
3553 alice,
3554 aliceSeq,
3555 openLedgerFee(env, batchFee),
3557 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3558 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3559 batch::sig(bob),
3560 ter(tesSUCCESS));
3561 env.close();
3562 }
3563
3564 checkMetrics(*this, env, 0, 12, 1, 6);
3565 }
3566
3567 // inner batch transactions are counter towards the ledger tx count
3568 {
3569 test::jtx::Env env{
3570 *this,
3571 makeSmallQueueConfig(
3572 {{"minimum_txn_in_ledger_standalone", "2"}}),
3573 nullptr,
3575
3576 auto alice = Account("alice");
3577 auto bob = Account("bob");
3578 auto carol = Account("carol");
3579
3580 // Fund across several ledgers so the TxQ metrics stay restricted.
3581 env.fund(XRP(10000), noripple(alice, bob));
3582 env.close(env.now() + 5s, 10000ms);
3583 env.fund(XRP(10000), noripple(carol));
3584 env.close(env.now() + 5s, 10000ms);
3585
3586 // Fill the ledger leaving room for 1 queued transaction
3587 env(noop(alice));
3588 env(noop(alice));
3589 checkMetrics(*this, env, 0, std::nullopt, 2, 2);
3590
3591 auto const aliceSeq = env.seq(alice);
3592 auto const bobSeq = env.seq(bob);
3593 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3594
3595 // Batch Successful
3596 {
3597 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3598 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3599 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3600 batch::sig(bob),
3601 ter(tesSUCCESS));
3602 }
3603
3604 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3605
3606 env(noop(carol), ter(terQUEUED));
3607 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3608 }
3609 }
3610
3611 void
3613 {
3614 testcase("batch network ops");
3615
3616 using namespace test::jtx;
3617 using namespace std::literals;
3618
3619 Env env(
3620 *this,
3621 envconfig(),
3622 features,
3623 nullptr,
3625
3626 auto alice = Account("alice");
3627 auto bob = Account("bob");
3628 env.fund(XRP(10000), alice, bob);
3629 env.close();
3630
3631 auto submitTx = [&](std::uint32_t flags) -> uint256 {
3632 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3633 Serializer s;
3634 jt.stx->add(s);
3635 env.app().getOPs().submitTransaction(jt.stx);
3636 return jt.stx->getTransactionID();
3637 };
3638
3639 auto processTxn = [&](std::uint32_t flags) -> uint256 {
3640 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3641 Serializer s;
3642 jt.stx->add(s);
3643 std::string reason;
3644 auto transaction =
3645 std::make_shared<Transaction>(jt.stx, reason, env.app());
3647 transaction, false, true, NetworkOPs::FailHard::yes);
3648 return transaction->getID();
3649 };
3650
3651 // Validate: NetworkOPs::submitTransaction()
3652 {
3653 // Submit a tx with tfInnerBatchTxn
3654 uint256 const txBad = submitTx(tfInnerBatchTxn);
3655 BEAST_EXPECT(env.app().getHashRouter().getFlags(txBad) == 0);
3656 }
3657
3658 // Validate: NetworkOPs::processTransaction()
3659 {
3660 uint256 const txid = processTxn(tfInnerBatchTxn);
3661 // HashRouter::getFlags() should return SF_BAD
3662 BEAST_EXPECT(env.app().getHashRouter().getFlags(txid) == SF_BAD);
3663 }
3664 }
3665
3666 void
3668 {
3669 testcase("batch delegate");
3670
3671 using namespace test::jtx;
3672 using namespace std::literals;
3673
3674 // delegated non atomic inner
3675 {
3676 test::jtx::Env env{*this, envconfig()};
3677
3678 auto const alice = Account("alice");
3679 auto const bob = Account("bob");
3680 auto const gw = Account("gw");
3681 auto const USD = gw["USD"];
3682 env.fund(XRP(10000), alice, bob, gw);
3683 env.close();
3684
3685 env(delegate::set(alice, bob, {"Payment"}));
3686 env.close();
3687
3688 auto const preAlice = env.balance(alice);
3689 auto const preBob = env.balance(bob);
3690
3691 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3692 auto const seq = env.seq(alice);
3693
3694 auto tx = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
3695 tx[jss::Delegate] = bob.human();
3696 auto const [txIDs, batchID] = submitBatch(
3697 env,
3698 tesSUCCESS,
3699 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3700 tx,
3701 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
3702 env.close();
3703
3704 std::vector<TestLedgerData> testCases = {
3705 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3706 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3707 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3708 };
3709 validateClosedLedger(env, testCases);
3710
3711 // Alice consumes sequences (# of txns)
3712 BEAST_EXPECT(env.seq(alice) == seq + 3);
3713
3714 // Alice pays XRP & Fee; Bob receives XRP
3715 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
3716 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
3717 }
3718
3719 // delegated atomic inner
3720 {
3721 test::jtx::Env env{*this, envconfig()};
3722
3723 auto const alice = Account("alice");
3724 auto const bob = Account("bob");
3725 auto const carol = Account("carol");
3726 auto const gw = Account("gw");
3727 auto const USD = gw["USD"];
3728 env.fund(XRP(10000), alice, bob, carol, gw);
3729 env.close();
3730
3731 env(delegate::set(bob, carol, {"Payment"}));
3732 env.close();
3733
3734 auto const preAlice = env.balance(alice);
3735 auto const preBob = env.balance(bob);
3736 auto const preCarol = env.balance(carol);
3737
3738 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3739 auto const aliceSeq = env.seq(alice);
3740 auto const bobSeq = env.seq(bob);
3741
3742 auto tx = batch::inner(pay(bob, alice, XRP(1)), bobSeq);
3743 tx[jss::Delegate] = carol.human();
3744 auto const [txIDs, batchID] = submitBatch(
3745 env,
3746 tesSUCCESS,
3747 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3748 tx,
3749 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3750 batch::sig(bob));
3751 env.close();
3752
3753 std::vector<TestLedgerData> testCases = {
3754 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3755 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3756 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3757 };
3758 validateClosedLedger(env, testCases);
3759
3760 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
3761 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
3762 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
3763 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
3764 // NOTE: Carol would normally pay the fee for delegated txns, but
3765 // because the batch is atomic, the fee is paid by the batch
3766 BEAST_EXPECT(env.balance(carol) == preCarol);
3767 }
3768
3769 // delegated non atomic inner (AccountSet)
3770 // this also makes sure tfInnerBatchTxn won't block delegated AccountSet
3771 // with granular permission
3772 {
3773 test::jtx::Env env{*this, envconfig()};
3774
3775 auto const alice = Account("alice");
3776 auto const bob = Account("bob");
3777 auto const gw = Account("gw");
3778 auto const USD = gw["USD"];
3779 env.fund(XRP(10000), alice, bob, gw);
3780 env.close();
3781
3782 env(delegate::set(alice, bob, {"AccountDomainSet"}));
3783 env.close();
3784
3785 auto const preAlice = env.balance(alice);
3786 auto const preBob = env.balance(bob);
3787
3788 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3789 auto const seq = env.seq(alice);
3790
3791 auto tx = batch::inner(noop(alice), seq + 1);
3792 std::string const domain = "example.com";
3793 tx[sfDomain.jsonName] = strHex(domain);
3794 tx[jss::Delegate] = bob.human();
3795 auto const [txIDs, batchID] = submitBatch(
3796 env,
3797 tesSUCCESS,
3798 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3799 tx,
3800 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
3801 env.close();
3802
3803 std::vector<TestLedgerData> testCases = {
3804 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3805 {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID},
3806 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3807 };
3808 validateClosedLedger(env, testCases);
3809
3810 // Alice consumes sequences (# of txns)
3811 BEAST_EXPECT(env.seq(alice) == seq + 3);
3812
3813 // Alice pays XRP & Fee; Bob receives XRP
3814 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee);
3815 BEAST_EXPECT(env.balance(bob) == preBob + XRP(2));
3816 }
3817
3818 // delegated non atomic inner (MPTokenIssuanceSet)
3819 // this also makes sure tfInnerBatchTxn won't block delegated
3820 // MPTokenIssuanceSet with granular permission
3821 {
3822 test::jtx::Env env{*this, envconfig()};
3823 Account alice{"alice"};
3824 Account bob{"bob"};
3825 env.fund(XRP(100000), alice, bob);
3826 env.close();
3827
3828 auto const mptID = makeMptID(env.seq(alice), alice);
3829 MPTTester mpt(env, alice, {.fund = false});
3830 env.close();
3831 mpt.create({.flags = tfMPTCanLock});
3832 env.close();
3833
3834 // alice gives granular permission to bob of MPTokenIssuanceLock
3835 env(delegate::set(
3836 alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
3837 env.close();
3838
3839 auto const seq = env.seq(alice);
3840 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3841
3842 Json::Value jv1;
3843 jv1[sfTransactionType] = jss::MPTokenIssuanceSet;
3844 jv1[sfAccount] = alice.human();
3845 jv1[sfDelegate] = bob.human();
3846 jv1[sfSequence] = seq + 1;
3847 jv1[sfMPTokenIssuanceID] = to_string(mptID);
3848 jv1[sfFlags] = tfMPTLock;
3849
3850 Json::Value jv2;
3851 jv2[sfTransactionType] = jss::MPTokenIssuanceSet;
3852 jv2[sfAccount] = alice.human();
3853 jv2[sfDelegate] = bob.human();
3854 jv2[sfSequence] = seq + 2;
3855 jv2[sfMPTokenIssuanceID] = to_string(mptID);
3856 jv2[sfFlags] = tfMPTUnlock;
3857
3858 auto const [txIDs, batchID] = submitBatch(
3859 env,
3860 tesSUCCESS,
3861 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3862 batch::inner(jv1, seq + 1),
3863 batch::inner(jv2, seq + 2));
3864 env.close();
3865
3866 std::vector<TestLedgerData> testCases = {
3867 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3868 {1, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[0], batchID},
3869 {2, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[1], batchID},
3870 };
3871 validateClosedLedger(env, testCases);
3872 }
3873
3874 // delegated non atomic inner (TrustSet)
3875 // this also makes sure tfInnerBatchTxn won't block delegated TrustSet
3876 // with granular permission
3877 {
3878 test::jtx::Env env{*this, envconfig()};
3879 Account gw{"gw"};
3880 Account alice{"alice"};
3881 Account bob{"bob"};
3882 env.fund(XRP(10000), gw, alice, bob);
3883 env(fset(gw, asfRequireAuth));
3884 env.close();
3885 env(trust(alice, gw["USD"](50)));
3886 env.close();
3887
3888 env(delegate::set(
3889 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
3890 env.close();
3891
3892 auto const seq = env.seq(gw);
3893 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3894
3895 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetfAuth);
3896 jv1[sfDelegate] = bob.human();
3897 auto jv2 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
3898 jv2[sfDelegate] = bob.human();
3899
3900 auto const [txIDs, batchID] = submitBatch(
3901 env,
3902 tesSUCCESS,
3903 batch::outer(gw, seq, batchFee, tfAllOrNothing),
3904 batch::inner(jv1, seq + 1),
3905 batch::inner(jv2, seq + 2));
3906 env.close();
3907
3908 std::vector<TestLedgerData> testCases = {
3909 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3910 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
3911 {2, "TrustSet", "tesSUCCESS", txIDs[1], batchID},
3912 };
3913 validateClosedLedger(env, testCases);
3914 }
3915
3916 // inner transaction not authorized by the delegating account.
3917 {
3918 test::jtx::Env env{*this, envconfig()};
3919 Account gw{"gw"};
3920 Account alice{"alice"};
3921 Account bob{"bob"};
3922 env.fund(XRP(10000), gw, alice, bob);
3923 env(fset(gw, asfRequireAuth));
3924 env.close();
3925 env(trust(alice, gw["USD"](50)));
3926 env.close();
3927
3928 env(delegate::set(
3929 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
3930 env.close();
3931
3932 auto const seq = env.seq(gw);
3933 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3934
3935 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
3936 jv1[sfDelegate] = bob.human();
3937 auto jv2 = trust(gw, gw["USD"](0), alice, tfClearFreeze);
3938 jv2[sfDelegate] = bob.human();
3939
3940 auto const [txIDs, batchID] = submitBatch(
3941 env,
3942 tesSUCCESS,
3943 batch::outer(gw, seq, batchFee, tfIndependent),
3944 batch::inner(jv1, seq + 1),
3945 // tecNO_DELEGATE_PERMISSION: not authorized to clear freeze
3946 batch::inner(jv2, seq + 2));
3947 env.close();
3948
3949 std::vector<TestLedgerData> testCases = {
3950 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3951 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
3952 {2, "TrustSet", "tecNO_DELEGATE_PERMISSION", txIDs[1], batchID},
3953 };
3954 validateClosedLedger(env, testCases);
3955 }
3956 }
3957
3958 void
3960 {
3961 // Verifying that the RPC response from submit includes
3962 // the account_sequence_available, account_sequence_next,
3963 // open_ledger_cost and validated_ledger_index fields.
3964 testcase("Validate RPC response");
3965
3966 using namespace jtx;
3967 Env env(*this);
3968 Account const alice("alice");
3969 Account const bob("bob");
3970 env.fund(XRP(10000), alice, bob);
3971 env.close();
3972
3973 // tes
3974 {
3975 auto const baseFee = env.current()->fees().base;
3976 auto const aliceSeq = env.seq(alice);
3977 auto jtx = env.jt(pay(alice, bob, XRP(1)));
3978
3979 Serializer s;
3980 jtx.stx->add(s);
3981 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
3982 env.close();
3983
3984 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
3985 BEAST_EXPECT(
3986 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
3987 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
3988 BEAST_EXPECT(
3989 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
3990 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
3991 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
3992 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
3993 }
3994
3995 // tec failure
3996 {
3997 auto const baseFee = env.current()->fees().base;
3998 auto const aliceSeq = env.seq(alice);
3999 env(fset(bob, asfRequireDest));
4000 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq));
4001
4002 Serializer s;
4003 jtx.stx->add(s);
4004 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4005 env.close();
4006
4007 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4008 BEAST_EXPECT(
4009 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
4010 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4011 BEAST_EXPECT(
4012 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
4013 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4014 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4015 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4016 }
4017
4018 // tem failure
4019 {
4020 auto const baseFee = env.current()->fees().base;
4021 auto const aliceSeq = env.seq(alice);
4022 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq + 1));
4023
4024 Serializer s;
4025 jtx.stx->add(s);
4026 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4027 env.close();
4028
4029 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4030 BEAST_EXPECT(
4031 jr[jss::account_sequence_available].asUInt() == aliceSeq);
4032 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4033 BEAST_EXPECT(jr[jss::account_sequence_next].asUInt() == aliceSeq);
4034 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4035 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4036 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4037 }
4038 }
4039
4040 void
4042 {
4043 using namespace jtx;
4044 Env env(*this);
4045 Account const alice("alice");
4046 Account const bob("bob");
4047 Account const carol("carol");
4048 env.fund(XRP(10000), alice, bob, carol);
4049 env.close();
4050
4051 auto getBaseFee = [&](JTx const& jtx) -> XRPAmount {
4052 Serializer s;
4053 jtx.stx->add(s);
4054 return Batch::calculateBaseFee(*env.current(), *jtx.stx);
4055 };
4056
4057 // bad: Inner Batch transaction found
4058 {
4059 auto const seq = env.seq(alice);
4060 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4061 auto jtx = env.jt(
4062 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4064 batch::outer(alice, seq, batchFee, tfAllOrNothing), seq),
4065 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
4066 XRPAmount const txBaseFee = getBaseFee(jtx);
4067 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4068 }
4069
4070 // bad: Raw Transactions array exceeds max entries.
4071 {
4072 auto const seq = env.seq(alice);
4073 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4074
4075 auto jtx = env.jt(
4076 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4077 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4078 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
4079 batch::inner(pay(alice, bob, XRP(1)), seq + 3),
4080 batch::inner(pay(alice, bob, XRP(1)), seq + 4),
4081 batch::inner(pay(alice, bob, XRP(1)), seq + 5),
4082 batch::inner(pay(alice, bob, XRP(1)), seq + 6),
4083 batch::inner(pay(alice, bob, XRP(1)), seq + 7),
4084 batch::inner(pay(alice, bob, XRP(1)), seq + 8),
4085 batch::inner(pay(alice, bob, XRP(1)), seq + 9));
4086
4087 XRPAmount const txBaseFee = getBaseFee(jtx);
4088 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4089 }
4090
4091 // bad: Signers array exceeds max entries.
4092 {
4093 auto const seq = env.seq(alice);
4094 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4095
4096 auto jtx = env.jt(
4097 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4098 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
4099 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
4100 batch::sig(
4101 bob,
4102 carol,
4103 alice,
4104 bob,
4105 carol,
4106 alice,
4107 bob,
4108 carol,
4109 alice,
4110 alice));
4111 XRPAmount const txBaseFee = getBaseFee(jtx);
4112 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4113 }
4114
4115 // good:
4116 {
4117 auto const seq = env.seq(alice);
4118 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4119 auto jtx = env.jt(
4120 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4121 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4122 batch::inner(pay(bob, alice, XRP(2)), seq + 2));
4123 XRPAmount const txBaseFee = getBaseFee(jtx);
4124 BEAST_EXPECT(txBaseFee == batchFee);
4125 }
4126 }
4127
4128 void
4130 {
4131 testEnable(features);
4132 testPreflight(features);
4133 testPreclaim(features);
4134 testBadRawTxn(features);
4135 testBadSequence(features);
4136 testBadOuterFee(features);
4137 testCalculateBaseFee(features);
4138 testAllOrNothing(features);
4139 testOnlyOne(features);
4140 testUntilFailure(features);
4141 testIndependent(features);
4142 testInnerSubmitRPC(features);
4143 testAccountActivation(features);
4144 testAccountSet(features);
4145 testAccountDelete(features);
4146 testObjectCreateSequence(features);
4147 testObjectCreateTicket(features);
4148 testObjectCreate3rdParty(features);
4149 testTickets(features);
4150 testSequenceOpenLedger(features);
4151 testTicketsOpenLedger(features);
4152 testObjectsOpenLedger(features);
4153 testPseudoTxn(features);
4154 testOpenLedger(features);
4155 testBatchTxQueue(features);
4156 testBatchNetworkOps(features);
4157 testBatchDelegate(features);
4158 testValidateRPCResponse(features);
4159 testBatchCalculateBaseFee(features);
4160 }
4161
4162public:
4163 void
4164 run() override
4165 {
4166 using namespace test::jtx;
4167 auto const sa = supported_amendments();
4168 testWithFeats(sa);
4169 }
4170};
4171
4172BEAST_DEFINE_TESTSUITE(Batch, app, ripple);
4173
4174} // namespace test
4175} // namespace ripple
Represents a JSON value.
Definition: json_value.h:150
A generic endpoint for log messages.
Definition: Journal.h:60
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
virtual NetworkOPs & getOPs()=0
virtual TxQ & getTxQ()=0
virtual HashRouter & getHashRouter()=0
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Calculates the total base fee for a batch transaction.
Definition: Batch.cpp:54
int getFlags(uint256 const &key)
Definition: HashRouter.cpp:95
virtual void submitTransaction(std::shared_ptr< STTx const > const &)=0
virtual void processTransaction(std::shared_ptr< Transaction > &transaction, bool bUnlimited, bool bLocal, FailHard failType)=0
Process transactions as they arrive from the network or which are submitted by clients.
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:66
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition: STAmount.cpp:795
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:33
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:51
Slice slice() const noexcept
Definition: Serializer.h:67
An immutable linear range of bytes.
Definition: Slice.h:46
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition: TxQ.cpp:1778
void testBatchNetworkOps(FeatureBitset features)
void validateInnerTxn(jtx::Env &env, std::string const &batchID, TestLedgerData const &ledgerResult)
Definition: Batch_test.cpp:78
void testAccountSet(FeatureBitset features)
void testTickets(FeatureBitset features)
void run() override
Runs the suite.
void testAllOrNothing(FeatureBitset features)
void testBatchCalculateBaseFee(FeatureBitset features)
void testObjectCreate3rdParty(FeatureBitset features)
void testAccountActivation(FeatureBitset features)
Json::Value getLastLedger(jtx::Env &env)
Definition: Batch_test.cpp:68
void testValidateRPCResponse(FeatureBitset features)
void testObjectCreateTicket(FeatureBitset features)
void testBadRawTxn(FeatureBitset features)
Definition: Batch_test.cpp:840
void testPreclaim(FeatureBitset features)
Definition: Batch_test.cpp:596
std::pair< std::vector< std::string >, std::string > submitBatch(jtx::Env &env, TER const &result, Args &&... args)
Definition: Batch_test.cpp:117
void testBatchTxQueue(FeatureBitset features)
void testBadSequence(FeatureBitset features)
Definition: Batch_test.cpp:931
static uint256 getCheckIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Batch_test.cpp:131
Json::Value getTxByIndex(Json::Value const &jrr, int const index)
Definition: Batch_test.cpp:57
void validateClosedLedger(jtx::Env &env, std::vector< TestLedgerData > const &ledgerResults)
Definition: Batch_test.cpp:92
void testObjectCreateSequence(FeatureBitset features)
void testBatchDelegate(FeatureBitset features)
void testOpenLedger(FeatureBitset features)
auto openLedgerFee(jtx::Env &env, XRPAmount const &batchFee)
Definition: Batch_test.cpp:157
void testPreflight(FeatureBitset features)
Definition: Batch_test.cpp:216
void testUntilFailure(FeatureBitset features)
void testWithFeats(FeatureBitset features)
void testSequenceOpenLedger(FeatureBitset features)
void testTicketsOpenLedger(FeatureBitset features)
void testIndependent(FeatureBitset features)
static std::unique_ptr< Config > makeSmallQueueConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
Definition: Batch_test.cpp:137
void testOnlyOne(FeatureBitset features)
void testPseudoTxn(FeatureBitset features)
void testInnerSubmitRPC(FeatureBitset features)
void testEnable(FeatureBitset features)
Definition: Batch_test.cpp:167
void testBadOuterFee(FeatureBitset features)
void testObjectsOpenLedger(FeatureBitset features)
void testCalculateBaseFee(FeatureBitset features)
void testAccountDelete(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition: Account.h:39
A transaction testing environment.
Definition: Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:254
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:505
Application & app()
Definition: Env.h:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
void create(MPTCreate const &arg=MPTCreate{})
Definition: mpt.cpp:86
Adds a new Batch Txn on a JTx and autofills.
Definition: batch.h:61
Set a batch nested multi-signature on a JTx.
Definition: batch.h:135
Set a batch signature on a JTx.
Definition: batch.h:112
Set the domain on a JTx.
Definition: domain.h:30
Match set account flags.
Definition: flags.h:128
Set a multisignature on a JTx.
Definition: multisign.h:67
Set the regular signature on a JTx.
Definition: sig.h:35
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
Set a ticket sequence on a JTx.
Definition: ticket.h:48
Set the flags on a JTx.
Definition: txflags.h:31
T make_pair(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:336
Json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Batch.
Definition: batch.cpp:49
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate Batch Fee.
Definition: batch.cpp:38
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition: Env.h:68
void checkMetrics(Suite &test, jtx::Env &env, std::size_t expectedCount, std::optional< std::size_t > expectedMaxCount, std::size_t expectedInLedger, std::size_t expectedPerLedger, std::uint64_t expectedMinFeeLevel=baseFeeLevel.fee(), std::uint64_t expectedMedFeeLevel=minEscalationFeeLevel.fee(), source_location const location=source_location::current())
Definition: TestHelpers.h:356
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
Definition: acctdelete.cpp:44
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:31
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
FeatureBitset supported_amendments()
Definition: Env.h:74
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition: AccountID.h:49
constexpr std::uint32_t tfAllOrNothing
Definition: TxFlags.h:240
@ telINSUF_FEE_P
Definition: TER.h:57
@ telENV_RPC_FAILED
Definition: TER.h:68
constexpr std::uint32_t tfOnlyOne
Definition: TxFlags.h:241
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition: STTx.cpp:820
constexpr std::uint32_t asfRequireDest
Definition: TxFlags.h:77
base_uint< 256 > uint256
Definition: base_uint.h:558
constexpr std::uint32_t tfIndependent
Definition: TxFlags.h:243
void serializeBatch(Serializer &msg, std::uint32_t const &flags, std::vector< uint256 > const &txids)
constexpr std::uint32_t const tfMPTUnlock
Definition: TxFlags.h:160
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:99
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:80
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
Definition: SecretKey.cpp:256
@ tefNOT_MULTI_SIGNING
Definition: TER.h:181
@ tefBAD_AUTH
Definition: TER.h:169
@ tefBAD_QUORUM
Definition: TER.h:180
@ tefBAD_SIGNATURE
Definition: TER.h:179
@ tefMASTER_DISABLED
Definition: TER.h:177
constexpr std::uint32_t tfUntilFailure
Definition: TxFlags.h:242
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:115
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:119
@ tecNO_ENTRY
Definition: TER.h:306
constexpr std::uint32_t const tfMPTLock
Definition: TxFlags.h:159
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfDisallowXRP
Definition: TxFlags.h:70
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition: apply.cpp:143
bool passesLocalChecks(STObject const &st, std::string &)
Definition: STTx.cpp:781
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t asfAllowTrustLineClawback
Definition: TxFlags.h:94
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition: TxQ.h:863
@ tapNONE
Definition: ApplyView.h:32
constexpr std::uint32_t asfRequireAuth
Definition: TxFlags.h:78
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition: Indexes.cpp:170
@ terPRE_SEQ
Definition: TER.h:221
@ terQUEUED
Definition: TER.h:225
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:118
constexpr std::uint32_t const tfMPTCanLock
Definition: TxFlags.h:145
constexpr std::uint32_t tfInnerBatchTxn
Definition: TxFlags.h:61
@ temREDUNDANT
Definition: TER.h:112
@ temBAD_FEE
Definition: TER.h:92
@ temBAD_SIGNER
Definition: TER.h:115
@ temSEQ_AND_TICKET
Definition: TER.h:126
@ temINVALID_INNER_BATCH
Definition: TER.h:143
@ temBAD_REGKEY
Definition: TER.h:98
@ temINVALID_FLAG
Definition: TER.h:111
@ temARRAY_EMPTY
Definition: TER.h:140
@ temARRAY_TOO_LARGE
Definition: TER.h:141
@ temDISABLED
Definition: TER.h:114
@ temBAD_SIGNATURE
Definition: TER.h:105
T push_back(T... args)
T size(T... args)
uint256 key
Definition: Keylet.h:40
std::optional< std::string > batchID
Definition: Batch_test.cpp:47
Execution context for applying a JSON transaction.
Definition: JTx.h:45
Set the sequence number on a JTx.
Definition: seq.h:34
seq(autofill_t)
Definition: seq.h:40