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