rippled
Loading...
Searching...
No Matches
TxQ_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 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/TestSuite.h>
22#include <test/jtx/WSClient.h>
23#include <test/jtx/envconfig.h>
24#include <test/jtx/ticket.h>
25
26#include <xrpld/app/main/Application.h>
27#include <xrpld/app/misc/LoadFeeTrack.h>
28#include <xrpld/app/misc/TxQ.h>
29#include <xrpld/app/tx/apply.h>
30
31#include <xrpl/protocol/ErrorCodes.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/protocol/st.h>
34
35namespace ripple {
36
37namespace test {
38
40{
41 // Same as corresponding values from TxQ.h
42 static constexpr FeeLevel64 baseFeeLevel{256};
44
45 void
47 int line,
48 jtx::Env& env,
49 std::size_t expectedCount,
50 std::optional<std::size_t> expectedMaxCount,
51 std::size_t expectedInLedger,
52 std::size_t expectedPerLedger,
53 std::uint64_t expectedMinFeeLevel = baseFeeLevel.fee(),
54 std::uint64_t expectedMedFeeLevel = minEscalationFeeLevel.fee())
55 {
56 FeeLevel64 const expectedMin{expectedMinFeeLevel};
57 FeeLevel64 const expectedMed{expectedMedFeeLevel};
58 auto const metrics = env.app().getTxQ().getMetrics(*env.current());
59 using namespace std::string_literals;
60
62 ? pass()
63 : fail(
64 "reference: "s +
65 std::to_string(metrics.referenceFeeLevel.value()) + "/" +
67 __FILE__,
68 line);
69
70 metrics.txCount == expectedCount
71 ? pass()
72 : fail(
73 "txCount: "s + std::to_string(metrics.txCount) + "/" +
74 std::to_string(expectedCount),
75 __FILE__,
76 line);
77
78 metrics.txQMaxSize == expectedMaxCount
79 ? pass()
80 : fail(
81 "txQMaxSize: "s +
82 std::to_string(metrics.txQMaxSize.value_or(0)) + "/" +
83 std::to_string(expectedMaxCount.value_or(0)),
84 __FILE__,
85 line);
86
87 metrics.txInLedger == expectedInLedger
88 ? pass()
89 : fail(
90 "txInLedger: "s + std::to_string(metrics.txInLedger) + "/" +
91 std::to_string(expectedInLedger),
92 __FILE__,
93 line);
94
95 metrics.txPerLedger == expectedPerLedger
96 ? pass()
97 : fail(
98 "txPerLedger: "s + std::to_string(metrics.txPerLedger) + "/" +
99 std::to_string(expectedPerLedger),
100 __FILE__,
101 line);
102
103 metrics.minProcessingFeeLevel == expectedMin
104 ? pass()
105 : fail(
106 "minProcessingFeeLevel: "s +
107 std::to_string(metrics.minProcessingFeeLevel.value()) +
108 "/" + std::to_string(expectedMin.value()),
109 __FILE__,
110 line);
111
112 metrics.medFeeLevel == expectedMed
113 ? pass()
114 : fail(
115 "medFeeLevel: "s +
116 std::to_string(metrics.medFeeLevel.value()) + "/" +
117 std::to_string(expectedMed.value()),
118 __FILE__,
119 line);
120
121 auto const expectedCurFeeLevel = expectedInLedger > expectedPerLedger
122 ? expectedMed * expectedInLedger * expectedInLedger /
123 (expectedPerLedger * expectedPerLedger)
124 : metrics.referenceFeeLevel;
125
126 metrics.openLedgerFeeLevel == expectedCurFeeLevel
127 ? pass()
128 : fail(
129 "openLedgerFeeLevel: "s +
130 std::to_string(metrics.openLedgerFeeLevel.value()) + "/" +
131 std::to_string(expectedCurFeeLevel.value()),
132 __FILE__,
133 line);
134 }
135
136 void
137 fillQueue(jtx::Env& env, jtx::Account const& account)
138 {
139 auto metrics = env.app().getTxQ().getMetrics(*env.current());
140 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
141 env(noop(account));
142 }
143
144 auto
146 {
147 using namespace jtx;
148
149 auto const& view = *env.current();
150 auto const base = [&view]() {
151 auto base = view.fees().base;
152 if (!base)
153 base += 1;
154 return base;
155 }();
156
157 // Don't care about the overflow flag
158 auto const& metrics = env.app().getTxQ().getMetrics(view);
159 return toDrops(metrics.openLedgerFeeLevel, base) + 1;
160 }
161
162 // Get a fee level of a transaction made by an account
163 // This fee level is used to ensure we can place transaction into TxQ
164 auto
166 {
167 using namespace jtx;
168
169 auto const& txq = env.app().getTxQ();
170 auto const& txs = txq.getAccountTxs(account.id());
171
172 return txs.begin()->feeLevel.fee();
173 }
174
175 // Calculating expected median fee level based on known fee levels of median
176 // transaction levels.
177 auto
178 calcMedFeeLevel(FeeLevel64 const feeLevel1, FeeLevel64 const feeLevel2)
179 {
180 FeeLevel64 const expectedMedFeeLevel =
181 (feeLevel1 + feeLevel2 + FeeLevel64{1}) / 2;
182
183 return std::max(expectedMedFeeLevel, minEscalationFeeLevel).fee();
184 }
185
186 auto
188 {
189 return calcMedFeeLevel(feeLevel, feeLevel);
190 }
191
195 std::map<std::string, std::string> extraVoting = {})
196 {
197 auto p = test::jtx::envconfig();
198 auto& section = p->section("transaction_queue");
199 section.set("ledgers_in_queue", "2");
200 section.set("minimum_queue_size", "2");
201 section.set("min_ledgers_to_compute_size_limit", "3");
202 section.set("max_ledger_counts_to_store", "100");
203 section.set("retry_sequence_percent", "25");
204 section.set("normal_consensus_increase_percent", "0");
205
206 for (auto const& [k, v] : extraTxQ)
207 section.set(k, v);
208
209 // Some tests specify different fee settings that are enabled by
210 // a FeeVote
211 if (!extraVoting.empty())
212 {
213 auto& votingSection = p->section("voting");
214 for (auto const& [k, v] : extraVoting)
215 {
216 votingSection.set(k, v);
217 }
218
219 // In order for the vote to occur, we must run as a validator
220 p->section("validation_seed")
221 .legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH");
222 }
223 return p;
224 }
225
228 jtx::Env& env,
229 std::size_t expectedPerLedger,
230 std::size_t ledgersInQueue,
231 std::uint32_t base,
233 std::uint32_t increment)
234 {
235 // Run past the flag ledger so that a Fee change vote occurs and
236 // lowers the reserve fee. (It also activates all supported
237 // amendments.) This will allow creating accounts with lower
238 // reserves and balances.
239 for (auto i = env.current()->seq(); i <= 257; ++i)
240 env.close();
241 // The ledger after the flag ledger creates all the
242 // fee (1) and amendment (numUpVotedAmendments())
243 // pseudotransactions. The queue treats the fees on these
244 // transactions as though they are ordinary transactions.
245 auto const flagPerLedger = 1 + ripple::detail::numUpVotedAmendments();
246 auto const flagMaxQueue = ledgersInQueue * flagPerLedger;
247 checkMetrics(__LINE__, env, 0, flagMaxQueue, 0, flagPerLedger);
248
249 // Pad a couple of txs with normal fees so the median comes
250 // back down to normal
251 env(noop(env.master));
252 env(noop(env.master));
253
254 // Close the ledger with a delay, which causes all the TxQ
255 // metrics to reset to defaults, EXCEPT the maxQueue size.
256 using namespace std::chrono_literals;
257 env.close(env.now() + 5s, 10000ms);
258 checkMetrics(__LINE__, env, 0, flagMaxQueue, 0, expectedPerLedger);
259 auto const fees = env.current()->fees();
260 BEAST_EXPECT(fees.base == XRPAmount{base});
261 BEAST_EXPECT(fees.reserve == XRPAmount{reserve});
262 BEAST_EXPECT(fees.increment == XRPAmount{increment});
263
264 return flagMaxQueue;
265 }
266
267public:
268 void
270 {
271 using namespace jtx;
272 using namespace std::chrono;
273 testcase("queue sequence");
274
275 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
276
277 auto alice = Account("alice");
278 auto bob = Account("bob");
279 auto charlie = Account("charlie");
280 auto daria = Account("daria");
281 auto elmo = Account("elmo");
282 auto fred = Account("fred");
283 auto gwen = Account("gwen");
284 auto hank = Account("hank");
285 auto iris = Account("iris");
286
287 auto queued = ter(terQUEUED);
288 auto const baseFee = env.current()->fees().base.drops();
289
290 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
291
292 // Create several accounts while the fee is cheap so they all apply.
293 env.fund(XRP(50000), noripple(alice, bob, charlie, daria));
294 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
295
296 // Alice - price starts exploding: held
297 env(noop(alice), queued);
298 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 3);
299
300 // Bob with really high fee - applies
301 env(noop(bob), fee(openLedgerCost(env)));
302 checkMetrics(__LINE__, env, 1, std::nullopt, 5, 3);
303
304 // Daria with low fee: hold
305 env(noop(daria), fee(baseFee * 100), queued);
306 checkMetrics(__LINE__, env, 2, std::nullopt, 5, 3);
307
308 env.close();
309 // Verify that the held transactions got applied
310 checkMetrics(__LINE__, env, 0, 10, 2, 5);
311
313
314 // Make some more accounts. We'll need them later to abuse the queue.
315 env.fund(XRP(50000), noripple(elmo, fred, gwen, hank));
316 checkMetrics(__LINE__, env, 0, 10, 6, 5);
317
318 // Now get a bunch of transactions held.
319 env(noop(alice), fee(baseFee * 1.2), queued);
320 checkMetrics(__LINE__, env, 1, 10, 6, 5);
321
322 env(noop(bob), fee(baseFee), queued); // won't clear the queue
323 env(noop(charlie), fee(baseFee * 2), queued);
324 env(noop(daria), fee(baseFee * 1.5), queued);
325 env(noop(elmo), fee(baseFee * 1.1), queued);
326 env(noop(fred), fee(baseFee * 1.9), queued);
327 env(noop(gwen), fee(baseFee * 1.6), queued);
328 env(noop(hank), fee(baseFee * 1.8), queued);
329 checkMetrics(__LINE__, env, 8, 10, 6, 5);
330
331 env.close();
332 // Verify that the held transactions got applied
333 checkMetrics(__LINE__, env, 1, 12, 7, 6);
334
335 // Bob's transaction is still stuck in the queue.
336
338
339 // Hank sends another txn
340 env(noop(hank), fee(baseFee), queued);
341 // But he's not going to leave it in the queue
342 checkMetrics(__LINE__, env, 2, 12, 7, 6);
343
344 // Hank sees his txn got held and bumps the fee,
345 // but doesn't even bump it enough to requeue
346 env(noop(hank), fee(baseFee * 1.1), ter(telCAN_NOT_QUEUE_FEE));
347 checkMetrics(__LINE__, env, 2, 12, 7, 6);
348
349 // Hank sees his txn got held and bumps the fee,
350 // enough to requeue, but doesn't bump it enough to
351 // apply to the ledger
352 env(noop(hank), fee(baseFee * 600), queued);
353 // But he's not going to leave it in the queue
354 checkMetrics(__LINE__, env, 2, 12, 7, 6);
355
356 // Hank sees his txn got held and bumps the fee,
357 // high enough to get into the open ledger, because
358 // he doesn't want to wait.
359 env(noop(hank), fee(openLedgerCost(env)));
360 checkMetrics(__LINE__, env, 1, 12, 8, 6);
361
362 // Hank then sends another, less important txn
363 // (In addition to the metrics, this will verify that
364 // the original txn got removed.)
365 env(noop(hank), fee(baseFee * 2), queued);
366 checkMetrics(__LINE__, env, 2, 12, 8, 6);
367
368 env.close();
369
370 // Verify that bob and hank's txns were applied
371 checkMetrics(__LINE__, env, 0, 16, 2, 8);
372
373 // Close again with a simulated time leap to
374 // reset the escalation limit down to minimum
375 env.close(env.now() + 5s, 10000ms);
376 checkMetrics(__LINE__, env, 0, 16, 0, 3);
377 // Then close once more without the time leap
378 // to reset the queue maxsize down to minimum
379 env.close();
380 checkMetrics(__LINE__, env, 0, 6, 0, 3);
381
383
384 constexpr auto largeFeeMultiplier = 700;
385 auto const largeFee = baseFee * largeFeeMultiplier;
386
387 // Stuff the ledger and queue so we can verify that
388 // stuff gets kicked out.
389 env(noop(hank), fee(largeFee));
390 env(noop(gwen), fee(largeFee));
391 env(noop(fred), fee(largeFee));
392 env(noop(elmo), fee(largeFee));
393 checkMetrics(__LINE__, env, 0, 6, 4, 3);
394
395 // Use explicit fees so we can control which txn
396 // will get dropped
397 // This one gets into the queue, but gets dropped when the
398 // higher fee one is added later.
399 env(noop(daria), fee(baseFee * 1.5), queued);
400 // These stay in the queue.
401 env(noop(elmo), fee(baseFee * 1.6), queued);
402 env(noop(fred), fee(baseFee * 1.7), queued);
403 env(noop(gwen), fee(baseFee * 1.8), queued);
404 env(noop(hank), fee(baseFee * 1.9), queued);
405 env(noop(alice), fee(baseFee * 2.0), queued);
406
407 // Queue is full now.
408 // clang-format off
409 checkMetrics(__LINE__, env, 6, 6, 4, 3, txFeeLevelByAccount(env, daria) + 1);
410 // clang-format on
411 // Try to add another transaction with the default (low) fee,
412 // it should fail because the queue is full.
413 env(noop(charlie), ter(telCAN_NOT_QUEUE_FULL));
414
415 // Add another transaction, with a higher fee,
416 // Not high enough to get into the ledger, but high
417 // enough to get into the queue (and kick somebody out)
418 env(noop(charlie), fee(baseFee * 10), queued);
419
420 // Queue is still full, of course, but the min fee has gone up
421 // clang-format off
422 checkMetrics(__LINE__, env, 6, 6, 4, 3, txFeeLevelByAccount(env, elmo) + 1);
423 // clang-format on
424
425 // Close out the ledger, the transactions are accepted, the
426 // queue is cleared, then the localTxs are retried. At this
427 // point, daria's transaction that was dropped from the queue
428 // is put back in. Neat.
429 env.close();
430 // clang-format off
431 checkMetrics(__LINE__, env, 2, 8, 5, 4, baseFeeLevel.fee(), calcMedFeeLevel(FeeLevel64{baseFeeLevel.fee() * largeFeeMultiplier}));
432 // clang-format on
433
434 env.close();
435 checkMetrics(__LINE__, env, 0, 10, 2, 5);
436
438
439 // Attempt to put a transaction in the queue for an account
440 // that is not yet funded.
441 env.memoize(iris);
442
443 env(noop(alice));
444 env(noop(bob));
445 env(noop(charlie));
446 env(noop(daria));
447 env(pay(alice, iris, XRP(1000)), queued);
448 env(noop(iris), seq(1), fee(baseFee * 2), ter(terNO_ACCOUNT));
449 checkMetrics(__LINE__, env, 1, 10, 6, 5);
450
451 env.close();
452 checkMetrics(__LINE__, env, 0, 12, 1, 6);
453
454 env.require(balance(iris, XRP(1000)));
455 BEAST_EXPECT(env.seq(iris) == 11);
456
458 // Cleanup:
459
460 // Create a few more transactions, so that
461 // we can be sure that there's one in the queue when the
462 // test ends and the TxQ is destructed.
463
464 auto metrics = env.app().getTxQ().getMetrics(*env.current());
465 BEAST_EXPECT(metrics.txCount == 0);
466
467 // Stuff the ledger.
468 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
469 {
470 env(noop(env.master));
471 }
472
473 // Queue one straightforward transaction
474 env(noop(env.master), fee(baseFee * 2), queued);
475 ++metrics.txCount;
476
478 __LINE__,
479 env,
480 metrics.txCount,
481 metrics.txQMaxSize,
482 metrics.txPerLedger + 1,
483 metrics.txPerLedger);
484 }
485
486 void
488 {
489 using namespace jtx;
490 testcase("queue ticket");
491
492 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
493
494 auto alice = Account("alice");
495
496 auto queued = ter(terQUEUED);
497 auto const baseFee = env.current()->fees().base.drops();
498
499 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
500
501 // Fund alice and then fill the ledger.
502 env.fund(XRP(50000), noripple(alice));
503 env(noop(alice));
504 env(noop(alice));
505 env(noop(alice));
506 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
507
509
510 // Alice requests tickets, but that transaction is queued. So
511 // Alice can't queue ticketed transactions yet.
512 std::uint32_t const tkt1{env.seq(alice) + 1};
513 env(ticket::create(alice, 250), seq(tkt1 - 1), queued);
514
515 env(noop(alice), ticket::use(tkt1 - 2), ter(tefNO_TICKET));
516 env(noop(alice), ticket::use(tkt1 - 1), ter(terPRE_TICKET));
517 env.require(owners(alice, 0), tickets(alice, 0));
518 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 3);
519
520 env.close();
521 env.require(owners(alice, 250), tickets(alice, 250));
522 checkMetrics(__LINE__, env, 0, 8, 1, 4);
523 BEAST_EXPECT(env.seq(alice) == tkt1 + 250);
524
526
527 // Unlike queued sequence-based transactions, ticket-based
528 // transactions _do_ move out of the queue largest fee first,
529 // even within one account, since they can be applied in any order.
530 // Demonstrate that.
531
532 // Fill the ledger so we can start queuing things.
533 env(noop(alice), ticket::use(tkt1 + 1), fee(baseFee * 1.1));
534 env(noop(alice), ticket::use(tkt1 + 2), fee(baseFee * 1.2));
535 env(noop(alice), ticket::use(tkt1 + 3), fee(baseFee * 1.3));
536 env(noop(alice), ticket::use(tkt1 + 4), fee(baseFee * 1.4));
537 env(noop(alice), ticket::use(tkt1 + 5), fee(baseFee * 1.5), queued);
538 auto const expectedMinFeeLevel = txFeeLevelByAccount(env, alice) + 1;
539 env(noop(alice), ticket::use(tkt1 + 6), fee(baseFee * 1.6), queued);
540 env(noop(alice), ticket::use(tkt1 + 7), fee(baseFee * 1.7), queued);
541 env(noop(alice), ticket::use(tkt1 + 8), fee(baseFee * 1.8), queued);
542 env(noop(alice), ticket::use(tkt1 + 9), fee(baseFee * 1.9), queued);
543 env(noop(alice), ticket::use(tkt1 + 10), fee(baseFee * 2.0), queued);
544 env(noop(alice), ticket::use(tkt1 + 11), fee(baseFee * 2.1), queued);
545 env(noop(alice), ticket::use(tkt1 + 12), fee(baseFee * 2.2), queued);
546 env(noop(alice),
547 ticket::use(tkt1 + 13),
548 fee(baseFee * 2.3),
550 checkMetrics(__LINE__, env, 8, 8, 5, 4, expectedMinFeeLevel);
551
552 // Check which of the queued transactions got into the ledger by
553 // attempting to replace them.
554 // o Get tefNO_TICKET if the ticket has already been used.
555 // o Get telCAN_NOT_QUEUE_FEE if the transaction is still in the queue.
556 env.close();
557 env.require(owners(alice, 240), tickets(alice, 240));
558
559 // These 4 went straight to the ledger:
560 env(noop(alice), ticket::use(tkt1 + 1), ter(tefNO_TICKET));
561 env(noop(alice), ticket::use(tkt1 + 2), ter(tefNO_TICKET));
562 env(noop(alice), ticket::use(tkt1 + 3), ter(tefNO_TICKET));
563 env(noop(alice), ticket::use(tkt1 + 4), ter(tefNO_TICKET));
564
565 // These two are still in the TxQ:
566 env(noop(alice), ticket::use(tkt1 + 5), ter(telCAN_NOT_QUEUE_FEE));
567 env(noop(alice), ticket::use(tkt1 + 6), ter(telCAN_NOT_QUEUE_FEE));
568
569 // These six were moved from the queue into the open ledger
570 // since those with the highest fees go first.
571 env(noop(alice), ticket::use(tkt1 + 7), ter(tefNO_TICKET));
572 env(noop(alice), ticket::use(tkt1 + 8), ter(tefNO_TICKET));
573 env(noop(alice), ticket::use(tkt1 + 9), ter(tefNO_TICKET));
574 env(noop(alice), ticket::use(tkt1 + 10), ter(tefNO_TICKET));
575 env(noop(alice), ticket::use(tkt1 + 11), ter(tefNO_TICKET));
576 env(noop(alice), ticket::use(tkt1 + 12), ter(tefNO_TICKET));
577
578 // This last one was moved from the local transactions into
579 // the queue.
580 env(noop(alice), ticket::use(tkt1 + 13), ter(telCAN_NOT_QUEUE_FEE));
581
582 checkMetrics(__LINE__, env, 3, 10, 6, 5);
583
585
586 // Do some experiments with putting sequence-based transactions
587 // into the queue while there are ticket-based transactions
588 // already in the queue.
589
590 // Alice still has three ticket-based transactions in the queue.
591 // The fee is escalated so unless we pay a sufficient fee
592 // transactions will go straight to the queue.
593 std::uint32_t const nextSeq{env.seq(alice)};
594 env(noop(alice), seq(nextSeq + 1), ter(terPRE_SEQ));
595 env(noop(alice), seq(nextSeq - 1), ter(tefPAST_SEQ));
596 env(noop(alice), seq(nextSeq + 0), queued);
597
598 // Now that nextSeq is in the queue, we should be able to queue
599 // nextSeq + 1.
600 env(noop(alice), seq(nextSeq + 1), queued);
601
602 // Fill the queue with sequence-based transactions. When the
603 // ledger closes we should find the three ticket-based
604 // transactions gone from the queue (because they had the
605 // highest fee). Then the earliest of the sequence-based
606 // transactions should also be gone from the queue.
607 env(noop(alice), seq(nextSeq + 2), queued);
608 env(noop(alice), seq(nextSeq + 3), queued);
609 env(noop(alice), seq(nextSeq + 4), queued);
610 env(noop(alice), seq(nextSeq + 5), queued);
611 env(noop(alice), seq(nextSeq + 6), queued);
612 env(noop(alice), seq(nextSeq + 7), ter(telCAN_NOT_QUEUE_FULL));
613 checkMetrics(__LINE__, env, 10, 10, 6, 5, 257);
614
615 // Check which of the queued transactions got into the ledger by
616 // attempting to replace them.
617 // o Get tefNo_TICKET if the ticket has already been used.
618 // o Get tefPAST_SEQ if the sequence moved out of the queue.
619 // o Get telCAN_NOT_QUEUE_FEE if the transaction is still in
620 // the queue.
621 env.close();
622 env.require(owners(alice, 237), tickets(alice, 237));
623
624 // The four ticket-based transactions went out first, since
625 // they paid the highest fee.
626 env(noop(alice), ticket::use(tkt1 + 4), ter(tefNO_TICKET));
627 env(noop(alice), ticket::use(tkt1 + 5), ter(tefNO_TICKET));
628 env(noop(alice), ticket::use(tkt1 + 12), ter(tefNO_TICKET));
629 env(noop(alice), ticket::use(tkt1 + 13), ter(tefNO_TICKET));
630
631 // Three of the sequence-based transactions also moved out of
632 // the queue.
633 env(noop(alice), seq(nextSeq + 1), ter(tefPAST_SEQ));
634 env(noop(alice), seq(nextSeq + 2), ter(tefPAST_SEQ));
635 env(noop(alice), seq(nextSeq + 3), ter(tefPAST_SEQ));
636 env(noop(alice), seq(nextSeq + 4), ter(telCAN_NOT_QUEUE_FEE));
637 env(noop(alice), seq(nextSeq + 5), ter(telCAN_NOT_QUEUE_FEE));
638 env(noop(alice), seq(nextSeq + 6), ter(telCAN_NOT_QUEUE_FEE));
639 env(noop(alice), seq(nextSeq + 7), ter(telCAN_NOT_QUEUE_FEE));
640
641 checkMetrics(__LINE__, env, 4, 12, 7, 6);
642 BEAST_EXPECT(env.seq(alice) == nextSeq + 4);
643
645
646 // We haven't yet shown that ticket-based transactions can be added
647 // to the queue in any order. We should do that...
648 std::uint32_t tkt250 = tkt1 + 249;
649 env(noop(alice), ticket::use(tkt250 - 0), fee(baseFee * 3.0), queued);
650 env(noop(alice), ticket::use(tkt1 + 14), fee(baseFee * 2.9), queued);
651 env(noop(alice), ticket::use(tkt250 - 1), fee(baseFee * 2.8), queued);
652 env(noop(alice), ticket::use(tkt1 + 15), fee(baseFee * 2.7), queued);
653 env(noop(alice), ticket::use(tkt250 - 2), fee(baseFee * 2.6), queued);
654 env(noop(alice), ticket::use(tkt1 + 16), fee(baseFee * 2.5), queued);
655 env(noop(alice),
656 ticket::use(tkt250 - 3),
657 fee(baseFee * 2.4),
659 env(noop(alice),
660 ticket::use(tkt1 + 17),
661 fee(baseFee * 2.3),
663 env(noop(alice),
664 ticket::use(tkt250 - 4),
665 fee(baseFee * 2.2),
667 env(noop(alice),
668 ticket::use(tkt1 + 18),
669 fee(baseFee * 2.1),
671
672 checkMetrics(__LINE__, env, 10, 12, 7, 6);
673
674 env.close();
675 env.require(owners(alice, 231), tickets(alice, 231));
676
677 // These three ticket-based transactions escaped the queue.
678 env(noop(alice), ticket::use(tkt1 + 14), ter(tefNO_TICKET));
679 env(noop(alice), ticket::use(tkt1 + 15), ter(tefNO_TICKET));
680 env(noop(alice), ticket::use(tkt1 + 16), ter(tefNO_TICKET));
681
682 // But these four ticket-based transactions are in the queue
683 // now; they moved into the TxQ from local transactions.
684 env(noop(alice), ticket::use(tkt250 - 3), ter(telCAN_NOT_QUEUE_FEE));
685 env(noop(alice), ticket::use(tkt1 + 17), ter(telCAN_NOT_QUEUE_FEE));
686 env(noop(alice), ticket::use(tkt250 - 4), ter(telCAN_NOT_QUEUE_FEE));
687 env(noop(alice), ticket::use(tkt1 + 18), ter(telCAN_NOT_QUEUE_FEE));
688
689 // These three ticket-based transactions also escaped the queue.
690 env(noop(alice), ticket::use(tkt250 - 2), ter(tefNO_TICKET));
691 env(noop(alice), ticket::use(tkt250 - 1), ter(tefNO_TICKET));
692 env(noop(alice), ticket::use(tkt250 - 0), ter(tefNO_TICKET));
693
694 // These sequence-based transactions escaped the queue.
695 env(noop(alice), seq(nextSeq + 4), ter(tefPAST_SEQ));
696 env(noop(alice), seq(nextSeq + 5), ter(tefPAST_SEQ));
697
698 // But these sequence-based transactions are still stuck in the queue.
699 env(noop(alice), seq(nextSeq + 6), ter(telCAN_NOT_QUEUE_FEE));
700 env(noop(alice), seq(nextSeq + 7), ter(telCAN_NOT_QUEUE_FEE));
701
702 BEAST_EXPECT(env.seq(alice) == nextSeq + 6);
703 checkMetrics(__LINE__, env, 6, 14, 8, 7);
704
706
707 // Since we still have two ticket-based transactions in the queue
708 // let's try replacing them.
709
710 // The lowest fee ticket is baseFee * 2.1, trying to replace it
711 env(noop(alice),
712 ticket::use(tkt1 + 18),
713 fee(baseFee * 2.1 * 1.25 - 1),
715 env(noop(alice),
716 ticket::use(tkt1 + 18),
717 fee(baseFee * 2.1 * 1.25 + 1),
718 queued);
719
720 // New lowest fee ticket is baseFee * 2.2
721 env(noop(alice),
722 ticket::use(tkt250 - 4),
723 fee(baseFee * 2.2 * 1.25 - 1),
725 env(noop(alice),
726 ticket::use(tkt250 - 4),
727 fee(baseFee * 2.2 * 1.25 + 1),
728 queued);
729
730 env.close();
731 env.require(owners(alice, 227), tickets(alice, 227));
732
733 // Verify that all remaining transactions made it out of the TxQ.
734 env(noop(alice), ticket::use(tkt1 + 18), ter(tefNO_TICKET));
735 env(noop(alice), ticket::use(tkt250 - 4), ter(tefNO_TICKET));
736 env(noop(alice), seq(nextSeq + 4), ter(tefPAST_SEQ));
737 env(noop(alice), seq(nextSeq + 5), ter(tefPAST_SEQ));
738 env(noop(alice), seq(nextSeq + 6), ter(tefPAST_SEQ));
739 env(noop(alice), seq(nextSeq + 7), ter(tefPAST_SEQ));
740
741 BEAST_EXPECT(env.seq(alice) == nextSeq + 8);
742 checkMetrics(__LINE__, env, 0, 16, 6, 8);
743 }
744
745 void
747 {
748 using namespace jtx;
749 testcase("queue tec");
750
751 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}}));
752
753 auto alice = Account("alice");
754 auto gw = Account("gw");
755 auto USD = gw["USD"];
756
757 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
758
759 // Create accounts
760 env.fund(XRP(50000), noripple(alice, gw));
761 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 2);
762 env.close();
763 checkMetrics(__LINE__, env, 0, 4, 0, 2);
764
765 // Alice creates an unfunded offer while the ledger is not full
766 env(offer(alice, XRP(1000), USD(1000)), ter(tecUNFUNDED_OFFER));
767 checkMetrics(__LINE__, env, 0, 4, 1, 2);
768
769 fillQueue(env, alice);
770 checkMetrics(__LINE__, env, 0, 4, 3, 2);
771
772 // Alice creates an unfunded offer that goes in the queue
773 env(offer(alice, XRP(1000), USD(1000)), ter(terQUEUED));
774 checkMetrics(__LINE__, env, 1, 4, 3, 2);
775
776 // The offer comes out of the queue
777 env.close();
778 checkMetrics(__LINE__, env, 0, 6, 1, 3);
779 }
780
781 void
783 {
784 using namespace jtx;
785 using namespace std::chrono;
786 testcase("local tx retry");
787
788 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}}));
789
790 auto alice = Account("alice");
791 auto bob = Account("bob");
792 auto charlie = Account("charlie");
793
794 auto queued = ter(terQUEUED);
795 auto const baseFee = env.current()->fees().base.drops();
796
797 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
798
799 // Create several accounts while the fee is cheap so they all apply.
800 env.fund(XRP(50000), noripple(alice, bob, charlie));
801 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 2);
802
803 // Future transaction for Alice - fails
804 env(noop(alice),
805 fee(openLedgerCost(env)),
806 seq(env.seq(alice) + 1),
807 ter(terPRE_SEQ));
808 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 2);
809
810 // Current transaction for Alice: held
811 env(noop(alice), queued);
812 checkMetrics(__LINE__, env, 1, std::nullopt, 3, 2);
813
814 // Alice - sequence is too far ahead, so won't queue.
815 env(noop(alice), seq(env.seq(alice) + 2), ter(telCAN_NOT_QUEUE));
816 checkMetrics(__LINE__, env, 1, std::nullopt, 3, 2);
817
818 // Bob with really high fee - applies
819 env(noop(bob), fee(openLedgerCost(env)));
820 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 2);
821
822 // Daria with low fee: hold
823 env(noop(charlie), fee(baseFee * 100), queued);
824 checkMetrics(__LINE__, env, 2, std::nullopt, 4, 2);
825
826 // Alice with normal fee: hold
827 env(noop(alice), seq(env.seq(alice) + 1), queued);
828 checkMetrics(__LINE__, env, 3, std::nullopt, 4, 2);
829
830 env.close();
831 // Verify that the held transactions got applied
832 // Alice's bad transaction applied from the
833 // Local Txs.
834 checkMetrics(__LINE__, env, 0, 8, 4, 4);
835 }
836
837 void
839 {
840 using namespace jtx;
841 using namespace std::chrono;
842 testcase("last ledger sequence");
843
844 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}}));
845
846 auto alice = Account("alice");
847 auto bob = Account("bob");
848 auto charlie = Account("charlie");
849 auto daria = Account("daria");
850 auto edgar = Account("edgar");
851 auto felicia = Account("felicia");
852
853 auto queued = ter(terQUEUED);
854 auto const baseFee = env.current()->fees().base.drops();
855
856 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
857
858 // Fund across several ledgers so the TxQ metrics stay restricted.
859 env.fund(XRP(1000), noripple(alice, bob));
860 env.close(env.now() + 5s, 10000ms);
861 env.fund(XRP(1000), noripple(charlie, daria));
862 env.close(env.now() + 5s, 10000ms);
863 env.fund(XRP(1000), noripple(edgar, felicia));
864 env.close(env.now() + 5s, 10000ms);
865
866 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
867 env(noop(bob));
868 env(noop(charlie));
869 env(noop(daria));
870 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 2);
871
872 BEAST_EXPECT(env.current()->info().seq == 6);
873 // Fail to queue an item with a low LastLedgerSeq
874 env(noop(alice),
875 json(R"({"LastLedgerSequence":7})"),
877 // Queue an item with a sufficient LastLedgerSeq.
878 env(noop(alice), json(R"({"LastLedgerSequence":8})"), queued);
879
880 constexpr auto largeFeeMultiplier = 700;
881 auto const largeFee = baseFee * largeFeeMultiplier;
882
883 // Queue items with higher fees to force the previous
884 // txn to wait.
885 env(noop(bob), fee(largeFee), queued);
886 env(noop(charlie), fee(largeFee), queued);
887 env(noop(daria), fee(largeFee), queued);
888 env(noop(edgar), fee(largeFee), queued);
889 checkMetrics(__LINE__, env, 5, std::nullopt, 3, 2);
890 {
891 auto& txQ = env.app().getTxQ();
892 auto aliceStat = txQ.getAccountTxs(alice.id());
893 BEAST_EXPECT(aliceStat.size() == 1);
894 BEAST_EXPECT(aliceStat.begin()->feeLevel == baseFeeLevel);
896 aliceStat.begin()->lastValid &&
897 *aliceStat.begin()->lastValid == 8);
898 BEAST_EXPECT(!aliceStat.begin()->consequences.isBlocker());
899
900 auto bobStat = txQ.getAccountTxs(bob.id());
901 BEAST_EXPECT(bobStat.size() == 1);
903 bobStat.begin()->feeLevel ==
904 FeeLevel64{baseFeeLevel.fee() * largeFeeMultiplier});
905 BEAST_EXPECT(!bobStat.begin()->lastValid);
906 BEAST_EXPECT(!bobStat.begin()->consequences.isBlocker());
907
908 auto noStat = txQ.getAccountTxs(Account::master.id());
909 BEAST_EXPECT(noStat.empty());
910 }
911
912 env.close();
913 checkMetrics(__LINE__, env, 1, 6, 4, 3);
914
915 // Keep alice's transaction waiting.
916 env(noop(bob), fee(largeFee), queued);
917 env(noop(charlie), fee(largeFee), queued);
918 env(noop(daria), fee(largeFee), queued);
919 env(noop(edgar), fee(largeFee), queued);
920 env(noop(felicia), fee(largeFee - 1), queued);
921 checkMetrics(__LINE__, env, 6, 6, 4, 3, 257);
922
923 env.close();
924 // alice's transaction is still hanging around
925 // clang-format off
926 checkMetrics(__LINE__, env, 1, 8, 5, 4, baseFeeLevel.fee(), baseFeeLevel.fee() * largeFeeMultiplier);
927 // clang-format on
928 BEAST_EXPECT(env.seq(alice) == 3);
929
930 constexpr auto anotherLargeFeeMultiplier = 800;
931 auto const anotherLargeFee = baseFee * anotherLargeFeeMultiplier;
932 // Keep alice's transaction waiting.
933 // clang-format off
934 env(noop(bob), fee(anotherLargeFee), queued);
935 env(noop(charlie), fee(anotherLargeFee), queued);
936 env(noop(daria), fee(anotherLargeFee), queued);
937 env(noop(daria), fee(anotherLargeFee), seq(env.seq(daria) + 1), queued);
938 env(noop(edgar), fee(anotherLargeFee), queued);
939 env(noop(felicia), fee(anotherLargeFee - 1), queued);
940 env(noop(felicia), fee(anotherLargeFee - 1), seq(env.seq(felicia) + 1), queued);
941 checkMetrics(__LINE__, env, 8, 8, 5, 4, baseFeeLevel.fee() + 1, baseFeeLevel.fee() * largeFeeMultiplier);
942 // clang-format on
943
944 env.close();
945 // alice's transaction expired without getting
946 // into the ledger, so her transaction is gone,
947 // though one of felicia's is still in the queue.
948 // clang-format off
949 checkMetrics(__LINE__, env, 1, 10, 6, 5, baseFeeLevel.fee(), baseFeeLevel.fee() * largeFeeMultiplier);
950 // clang-format on
951 BEAST_EXPECT(env.seq(alice) == 3);
952 BEAST_EXPECT(env.seq(felicia) == 7);
953
954 env.close();
955 // And now the queue is empty
956 // clang-format off
957 checkMetrics(__LINE__, env, 0, 12, 1, 6, baseFeeLevel.fee(), baseFeeLevel.fee() * anotherLargeFeeMultiplier);
958 // clang-format on
959 BEAST_EXPECT(env.seq(alice) == 3);
960 BEAST_EXPECT(env.seq(felicia) == 8);
961 }
962
963 void
965 {
966 using namespace jtx;
967 using namespace std::chrono;
968 testcase("zero transaction fee");
969
970 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}}));
971
972 auto alice = Account("alice");
973 auto bob = Account("bob");
974 auto carol = Account("carol");
975
976 auto queued = ter(terQUEUED);
977 auto const baseFee = env.current()->fees().base.drops();
978
979 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
980
981 // Fund across several ledgers so the TxQ metrics stay restricted.
982 env.fund(XRP(1000), noripple(alice, bob));
983 env.close(env.now() + 5s, 10000ms);
984 env.fund(XRP(1000), noripple(carol));
985 env.close(env.now() + 5s, 10000ms);
986
987 // Fill the ledger
988 env(noop(alice));
989 env(noop(alice));
990 env(noop(alice));
991 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 2);
992
993 env(noop(bob), queued);
994 checkMetrics(__LINE__, env, 1, std::nullopt, 3, 2);
995
996 // Since Alice's queue is empty this blocker can go into her queue.
997 env(regkey(alice, bob), fee(0), queued);
998 checkMetrics(__LINE__, env, 2, std::nullopt, 3, 2);
999
1000 // Close out this ledger so we can get a maxsize
1001 env.close();
1002 checkMetrics(__LINE__, env, 0, 6, 2, 3);
1003
1004 fillQueue(env, alice);
1005 checkMetrics(__LINE__, env, 0, 6, 4, 3);
1006
1007 constexpr auto aliceFeeMultiplier = 3;
1008 auto feeAlice = baseFee * aliceFeeMultiplier;
1009 auto seqAlice = env.seq(alice);
1010 for (int i = 0; i < 4; ++i)
1011 {
1012 env(noop(alice), fee(feeAlice), seq(seqAlice), queued);
1013 feeAlice = (feeAlice + 1) * 125 / 100;
1014 ++seqAlice;
1015 }
1016 checkMetrics(__LINE__, env, 4, 6, 4, 3);
1017
1018 // Bob adds a zero fee blocker to his queue.
1019 auto const seqBob = env.seq(bob);
1020 env(regkey(bob, alice), fee(0), queued);
1021 checkMetrics(__LINE__, env, 5, 6, 4, 3);
1022
1023 // Carol fills the queue.
1024 auto feeCarol = feeAlice;
1025 auto seqCarol = env.seq(carol);
1026 for (int i = 0; i < 4; ++i)
1027 {
1028 env(noop(carol), fee(feeCarol), seq(seqCarol), queued);
1029 feeCarol = (feeCarol + 1) * 125 / 100;
1030 ++seqCarol;
1031 }
1032 // clang-format off
1033 checkMetrics( __LINE__, env, 6, 6, 4, 3, baseFeeLevel.fee() * aliceFeeMultiplier + 1);
1034 // clang-format on
1035
1036 // Carol submits high enough to beat Bob's average fee which kicks
1037 // out Bob's queued transaction. However Bob's transaction stays
1038 // in the localTx queue, so it will return to the TxQ next time
1039 // around.
1040 env(noop(carol), fee(feeCarol), seq(seqCarol), ter(terQUEUED));
1041
1042 env.close();
1043 // Some of Alice's transactions stay in the queue. Bob's
1044 // transaction returns to the TxQ.
1045 checkMetrics(__LINE__, env, 5, 8, 5, 4);
1046 BEAST_EXPECT(env.seq(alice) == seqAlice - 4);
1047 BEAST_EXPECT(env.seq(bob) == seqBob);
1048 BEAST_EXPECT(env.seq(carol) == seqCarol + 1);
1049
1050 env.close();
1051 // The remaining queued transactions flush through to the ledger.
1052 checkMetrics(__LINE__, env, 0, 10, 5, 5);
1053 BEAST_EXPECT(env.seq(alice) == seqAlice);
1054 BEAST_EXPECT(env.seq(bob) == seqBob + 1);
1055 BEAST_EXPECT(env.seq(carol) == seqCarol + 1);
1056
1057 env.close();
1058 checkMetrics(__LINE__, env, 0, 10, 0, 5);
1059 BEAST_EXPECT(env.seq(alice) == seqAlice);
1060 BEAST_EXPECT(env.seq(bob) == seqBob + 1);
1061 BEAST_EXPECT(env.seq(carol) == seqCarol + 1);
1062 }
1063
1064 void
1066 {
1067 using namespace jtx;
1068
1069 Env env(*this, makeConfig());
1070 testcase("fail in preclaim");
1071
1072 auto alice = Account("alice");
1073 auto bob = Account("bob");
1074
1075 env.fund(XRP(1000), noripple(alice));
1076
1077 // These types of checks are tested elsewhere, but
1078 // this verifies that TxQ handles the failures as
1079 // expected.
1080
1081 // Fail in preflight
1082 env(pay(alice, bob, XRP(-1000)), ter(temBAD_AMOUNT));
1083
1084 // Fail in preflight
1085 env(pay(alice, alice, XRP(100)), ter(temREDUNDANT));
1086
1087 // Fail in preclaim
1088 env(noop(alice), fee(XRP(100000)), ter(terINSUF_FEE_B));
1089 }
1090
1091 void
1093 {
1094 using namespace jtx;
1095 testcase("queued tx fails");
1096
1097 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}}));
1098
1099 auto alice = Account("alice");
1100 auto bob = Account("bob");
1101
1102 auto queued = ter(terQUEUED);
1103
1104 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
1105
1106 env.fund(XRP(1000), noripple(alice, bob));
1107
1108 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 2);
1109
1110 // Fill the ledger
1111 env(noop(alice));
1112 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 2);
1113
1114 // Put a transaction in the queue
1115 env(noop(alice), queued);
1116 checkMetrics(__LINE__, env, 1, std::nullopt, 3, 2);
1117
1118 // Now cheat, and bypass the queue.
1119 {
1120 auto const& jt = env.jt(noop(alice));
1121 BEAST_EXPECT(jt.stx);
1122
1123 Env::ParsedResult parsed;
1124
1125 env.app().openLedger().modify(
1126 [&](OpenView& view, beast::Journal j) {
1127 auto const result = ripple::apply(
1128 env.app(), view, *jt.stx, tapNONE, env.journal);
1129 parsed.ter = result.ter;
1130 return result.applied;
1131 });
1132 env.postconditions(jt, parsed);
1133 }
1134 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 2);
1135
1136 env.close();
1137 // Alice's queued transaction failed in TxQ::accept
1138 // with tefPAST_SEQ
1139 checkMetrics(__LINE__, env, 0, 8, 0, 4);
1140 }
1141
1142 void
1144 {
1145 using namespace jtx;
1146 testcase("multi tx per account");
1147
1148 Env env(
1149 *this,
1150 makeConfig(
1151 {{"minimum_txn_in_ledger_standalone", "3"}},
1152 {{"account_reserve", "200"}, {"owner_reserve", "50"}}));
1153
1154 auto alice = Account("alice");
1155 auto bob = Account("bob");
1156 auto charlie = Account("charlie");
1157 auto daria = Account("daria");
1158
1159 auto queued = ter(terQUEUED);
1160
1161 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
1162
1163 // ledgers in queue is 2 because of makeConfig
1164 auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
1165
1166 // Create several accounts while the fee is cheap so they all apply.
1167 env.fund(drops(2000), noripple(alice));
1168 env.fund(XRP(500000), noripple(bob, charlie, daria));
1169 checkMetrics(__LINE__, env, 0, initQueueMax, 4, 3);
1170
1171 // Alice - price starts exploding: held
1172 env(noop(alice), fee(11), queued);
1173 checkMetrics(__LINE__, env, 1, initQueueMax, 4, 3);
1174
1175 auto aliceSeq = env.seq(alice);
1176 auto bobSeq = env.seq(bob);
1177 auto charlieSeq = env.seq(charlie);
1178
1179 // Alice - try to queue a second transaction, but leave a gap
1180 env(noop(alice), seq(aliceSeq + 2), fee(100), ter(telCAN_NOT_QUEUE));
1181 checkMetrics(__LINE__, env, 1, initQueueMax, 4, 3);
1182
1183 // Alice - queue a second transaction. Yay!
1184 env(noop(alice), seq(aliceSeq + 1), fee(13), queued);
1185 checkMetrics(__LINE__, env, 2, initQueueMax, 4, 3);
1186
1187 // Alice - queue a third transaction. Yay.
1188 env(noop(alice), seq(aliceSeq + 2), fee(17), queued);
1189 checkMetrics(__LINE__, env, 3, initQueueMax, 4, 3);
1190
1191 // Bob - queue a transaction
1192 env(noop(bob), queued);
1193 checkMetrics(__LINE__, env, 4, initQueueMax, 4, 3);
1194
1195 // Bob - queue a second transaction
1196 env(noop(bob), seq(bobSeq + 1), fee(50), queued);
1197 checkMetrics(__LINE__, env, 5, initQueueMax, 4, 3);
1198
1199 // Charlie - queue a transaction, with a higher fee
1200 // than default
1201 env(noop(charlie), fee(15), queued);
1202 checkMetrics(__LINE__, env, 6, initQueueMax, 4, 3);
1203
1204 BEAST_EXPECT(env.seq(alice) == aliceSeq);
1205 BEAST_EXPECT(env.seq(bob) == bobSeq);
1206 BEAST_EXPECT(env.seq(charlie) == charlieSeq);
1207
1208 env.close();
1209 // Verify that all of but one of the queued transactions
1210 // got applied.
1211 checkMetrics(__LINE__, env, 1, 8, 5, 4);
1212
1213 // Verify that the stuck transaction is Bob's second.
1214 // Even though it had a higher fee than Alice's and
1215 // Charlie's, it didn't get attempted until the fee escalated.
1216 BEAST_EXPECT(env.seq(alice) == aliceSeq + 3);
1217 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
1218 BEAST_EXPECT(env.seq(charlie) == charlieSeq + 1);
1219
1220 // Alice - fill up the queue
1221 std::int64_t aliceFee = 27;
1222 aliceSeq = env.seq(alice);
1223 auto lastLedgerSeq = env.current()->info().seq + 2;
1224 for (auto i = 0; i < 7; i++)
1225 {
1226 env(noop(alice),
1227 seq(aliceSeq),
1228 json(jss::LastLedgerSequence, lastLedgerSeq + i),
1229 fee(--aliceFee),
1230 queued);
1231 ++aliceSeq;
1232 }
1233 checkMetrics(__LINE__, env, 8, 8, 5, 4, 513);
1234 {
1235 auto& txQ = env.app().getTxQ();
1236 auto aliceStat = txQ.getAccountTxs(alice.id());
1237 aliceFee = 27;
1238 auto const& baseFee = env.current()->fees().base;
1239 auto seq = env.seq(alice);
1240 BEAST_EXPECT(aliceStat.size() == 7);
1241 for (auto const& tx : aliceStat)
1242 {
1243 BEAST_EXPECT(tx.seqProxy.isSeq() && tx.seqProxy.value() == seq);
1245 tx.feeLevel == toFeeLevel(XRPAmount(--aliceFee), baseFee));
1246 BEAST_EXPECT(tx.lastValid);
1248 (tx.consequences.fee() == drops(aliceFee) &&
1249 tx.consequences.potentialSpend() == drops(0) &&
1250 !tx.consequences.isBlocker()) ||
1251 tx.seqProxy.value() == env.seq(alice) + 6);
1252 ++seq;
1253 }
1254 }
1255
1256 // Alice attempts to add another item to the queue,
1257 // but you can't force your own earlier txn off the
1258 // queue.
1259 env(noop(alice),
1260 seq(aliceSeq),
1261 json(jss::LastLedgerSequence, lastLedgerSeq + 7),
1262 fee(aliceFee),
1264 checkMetrics(__LINE__, env, 8, 8, 5, 4, 513);
1265
1266 // Charlie - try to add another item to the queue,
1267 // which fails because fee is lower than Alice's
1268 // queued average.
1269 env(noop(charlie), fee(19), ter(telCAN_NOT_QUEUE_FULL));
1270 checkMetrics(__LINE__, env, 8, 8, 5, 4, 513);
1271
1272 // Charlie - add another item to the queue, which
1273 // causes Alice's last txn to drop
1274 env(noop(charlie), fee(30), queued);
1275 checkMetrics(__LINE__, env, 8, 8, 5, 4, 538);
1276
1277 // Alice - now attempt to add one more to the queue,
1278 // which fails because the last tx was dropped, so
1279 // there is no complete chain.
1280 env(noop(alice), seq(aliceSeq), fee(aliceFee), ter(telCAN_NOT_QUEUE));
1281 checkMetrics(__LINE__, env, 8, 8, 5, 4, 538);
1282
1283 // Alice wants this tx more than the dropped tx,
1284 // so resubmits with higher fee, but the queue
1285 // is full, and her account is the cheapest.
1286 env(noop(alice),
1287 seq(aliceSeq - 1),
1288 fee(aliceFee),
1290 checkMetrics(__LINE__, env, 8, 8, 5, 4, 538);
1291
1292 // Try to replace a middle item in the queue
1293 // without enough fee.
1294 aliceSeq = env.seq(alice) + 2;
1295 aliceFee = 29;
1296 env(noop(alice),
1297 seq(aliceSeq),
1298 fee(aliceFee),
1300 checkMetrics(__LINE__, env, 8, 8, 5, 4, 538);
1301
1302 // Replace a middle item from the queue successfully
1303 ++aliceFee;
1304 env(noop(alice), seq(aliceSeq), fee(aliceFee), queued);
1305 checkMetrics(__LINE__, env, 8, 8, 5, 4, 538);
1306
1307 env.close();
1308 // Alice's transactions processed, along with
1309 // Charlie's, and the lost one is replayed and
1310 // added back to the queue.
1311 checkMetrics(__LINE__, env, 4, 10, 6, 5);
1312
1313 aliceSeq = env.seq(alice) + 1;
1314
1315 // Try to replace that item with a transaction that will
1316 // bankrupt Alice. Fails, because an account can't have
1317 // more than the minimum reserve in flight before the
1318 // last queued transaction
1319 aliceFee =
1320 env.le(alice)->getFieldAmount(sfBalance).xrp().drops() - (62);
1321 env(noop(alice),
1322 seq(aliceSeq),
1323 fee(aliceFee),
1325 checkMetrics(__LINE__, env, 4, 10, 6, 5);
1326
1327 // Try to spend more than Alice can afford with all the other txs.
1328 aliceSeq += 2;
1329 env(noop(alice), seq(aliceSeq), fee(aliceFee), ter(terINSUF_FEE_B));
1330 checkMetrics(__LINE__, env, 4, 10, 6, 5);
1331
1332 // Replace the last queued item with a transaction that will
1333 // bankrupt Alice
1334 --aliceFee;
1335 env(noop(alice), seq(aliceSeq), fee(aliceFee), queued);
1336 checkMetrics(__LINE__, env, 4, 10, 6, 5);
1337
1338 // Alice - Attempt to queue a last transaction, but it
1339 // fails because the fee in flight is too high, before
1340 // the fee is checked against the balance
1341 aliceFee /= 5;
1342 ++aliceSeq;
1343 env(noop(alice),
1344 seq(aliceSeq),
1345 fee(aliceFee),
1347 checkMetrics(__LINE__, env, 4, 10, 6, 5);
1348
1349 env.close();
1350 // All of Alice's transactions applied.
1351 checkMetrics(__LINE__, env, 0, 12, 4, 6);
1352
1353 env.close();
1354 checkMetrics(__LINE__, env, 0, 12, 0, 6);
1355
1356 // Alice is broke
1357 env.require(balance(alice, XRP(0)));
1358 env(noop(alice), ter(terINSUF_FEE_B));
1359
1360 // Bob tries to queue up more than the single
1361 // account limit (10) txs.
1362 fillQueue(env, bob);
1363 bobSeq = env.seq(bob);
1364 checkMetrics(__LINE__, env, 0, 12, 7, 6);
1365 for (int i = 0; i < 10; ++i)
1366 env(noop(bob), seq(bobSeq + i), queued);
1367 checkMetrics(__LINE__, env, 10, 12, 7, 6);
1368 // Bob hit the single account limit
1369 env(noop(bob), seq(bobSeq + 10), ter(telCAN_NOT_QUEUE_FULL));
1370 checkMetrics(__LINE__, env, 10, 12, 7, 6);
1371 // Bob can replace one of the earlier txs regardless
1372 // of the limit
1373 env(noop(bob), seq(bobSeq + 5), fee(20), queued);
1374 checkMetrics(__LINE__, env, 10, 12, 7, 6);
1375
1376 // Try to replace a middle item in the queue
1377 // with enough fee to bankrupt bob and make the
1378 // later transactions unable to pay their fees
1379 std::int64_t bobFee =
1380 env.le(bob)->getFieldAmount(sfBalance).xrp().drops() - (9 * 10 - 1);
1381 env(noop(bob),
1382 seq(bobSeq + 5),
1383 fee(bobFee),
1385 checkMetrics(__LINE__, env, 10, 12, 7, 6);
1386
1387 // Attempt to replace a middle item in the queue with enough fee
1388 // to bankrupt bob, and also to use fee averaging to clear out the
1389 // first six transactions.
1390 //
1391 // The attempt fails because the sum of bob's fees now exceeds the
1392 // (artificially lowered to 200 drops) account reserve.
1393 bobFee =
1394 env.le(bob)->getFieldAmount(sfBalance).xrp().drops() - (9 * 10);
1395 env(noop(bob),
1396 seq(bobSeq + 5),
1397 fee(bobFee),
1399 checkMetrics(__LINE__, env, 10, 12, 7, 6);
1400
1401 // Close the ledger and verify that the queued transactions succeed
1402 // and bob has the right ending balance.
1403 env.close();
1404 checkMetrics(__LINE__, env, 3, 14, 8, 7);
1405 env.close();
1406 checkMetrics(__LINE__, env, 0, 16, 3, 8);
1407 env.require(balance(bob, drops(499'999'999'750)));
1408 }
1409
1410 void
1412 {
1413 using namespace jtx;
1414 using namespace std::chrono;
1415 testcase("tie breaking");
1416
1417 auto cfg = makeConfig({{"minimum_txn_in_ledger_standalone", "4"}});
1418 cfg->FEES.reference_fee = 10;
1419 Env env(*this, std::move(cfg));
1420
1421 auto alice = Account("alice");
1422 auto bob = Account("bob");
1423 auto charlie = Account("charlie");
1424 auto daria = Account("daria");
1425 auto elmo = Account("elmo");
1426 auto fred = Account("fred");
1427 auto gwen = Account("gwen");
1428 auto hank = Account("hank");
1429
1430 auto queued = ter(terQUEUED);
1431
1432 BEAST_EXPECT(env.current()->fees().base == 10);
1433
1434 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 4);
1435
1436 // Create several accounts while the fee is cheap so they all apply.
1437 env.fund(XRP(50000), noripple(alice, bob, charlie, daria));
1438 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 4);
1439
1440 env.close();
1441 checkMetrics(__LINE__, env, 0, 8, 0, 4);
1442
1443 env.fund(XRP(50000), noripple(elmo, fred, gwen, hank));
1444 checkMetrics(__LINE__, env, 0, 8, 4, 4);
1445
1446 env.close();
1447 checkMetrics(__LINE__, env, 0, 8, 0, 4);
1448
1450
1451 // Stuff the ledger and queue so we can verify that
1452 // stuff gets kicked out.
1453 env(noop(gwen));
1454 env(noop(hank));
1455 env(noop(gwen));
1456 env(noop(fred));
1457 env(noop(elmo));
1458 checkMetrics(__LINE__, env, 0, 8, 5, 4);
1459
1460 auto aliceSeq = env.seq(alice);
1461 auto bobSeq = env.seq(bob);
1462 auto charlieSeq = env.seq(charlie);
1463 auto dariaSeq = env.seq(daria);
1464 auto elmoSeq = env.seq(elmo);
1465 auto fredSeq = env.seq(fred);
1466 auto gwenSeq = env.seq(gwen);
1467 auto hankSeq = env.seq(hank);
1468
1469 // This time, use identical fees.
1470
1471 // All of these get into the queue, but one gets dropped when the
1472 // higher fee one is added later. Which one depends on ordering.
1473 env(noop(alice), fee(15), queued);
1474 env(noop(bob), fee(15), queued);
1475 env(noop(charlie), fee(15), queued);
1476 env(noop(daria), fee(15), queued);
1477 env(noop(elmo), fee(15), queued);
1478 env(noop(fred), fee(15), queued);
1479 env(noop(gwen), fee(15), queued);
1480 env(noop(hank), fee(15), queued);
1481
1482 // Queue is full now. Minimum fee now reflects the
1483 // lowest fee in the queue.
1484 auto minFeeLevel = txFeeLevelByAccount(env, alice);
1485 checkMetrics(__LINE__, env, 8, 8, 5, 4, minFeeLevel + 1);
1486
1487 // Try to add another transaction with the default (low) fee,
1488 // it should fail because it can't replace the one already
1489 // there.
1490 env(noop(charlie), ter(telCAN_NOT_QUEUE_FEE));
1491
1492 // Add another transaction, with a higher fee,
1493 // Not high enough to get into the ledger, but high
1494 // enough to get into the queue (and kick somebody out)
1495 env(noop(charlie), fee(100), seq(charlieSeq + 1), queued);
1496
1497 // Queue is still full.
1498 checkMetrics(__LINE__, env, 8, 8, 5, 4, minFeeLevel + 1);
1499
1500 // Six txs are processed out of the queue into the ledger,
1501 // leaving two txs. The dropped tx is retried from localTxs, and
1502 // put back into the queue.
1503 env.close();
1504 checkMetrics(__LINE__, env, 3, 10, 6, 5);
1505
1506 // This next test should remain unchanged regardless of
1507 // transaction ordering
1509 aliceSeq + bobSeq + charlieSeq + dariaSeq + elmoSeq + fredSeq +
1510 gwenSeq + hankSeq + 6 ==
1511 env.seq(alice) + env.seq(bob) + env.seq(charlie) + env.seq(daria) +
1512 env.seq(elmo) + env.seq(fred) + env.seq(gwen) + env.seq(hank));
1513 // These tests may change if TxQ ordering is changed
1514 using namespace std::string_literals;
1515 BEAST_EXPECTS(
1516 aliceSeq == env.seq(alice),
1517 "alice: "s + std::to_string(aliceSeq) + ", " +
1518 std::to_string(env.seq(alice)));
1519 BEAST_EXPECTS(
1520 bobSeq + 1 == env.seq(bob),
1521 "bob: "s + std::to_string(bobSeq) + ", " +
1522 std::to_string(env.seq(bob)));
1523 BEAST_EXPECTS(
1524 charlieSeq + 2 == env.seq(charlie),
1525 "charlie: "s + std::to_string(charlieSeq) + ", " +
1526 std::to_string(env.seq(charlie)));
1527 BEAST_EXPECTS(
1528 dariaSeq + 1 == env.seq(daria),
1529 "daria: "s + std::to_string(dariaSeq) + ", " +
1530 std::to_string(env.seq(daria)));
1531 BEAST_EXPECTS(
1532 elmoSeq + 1 == env.seq(elmo),
1533 "elmo: "s + std::to_string(elmoSeq) + ", " +
1534 std::to_string(env.seq(elmo)));
1535 BEAST_EXPECTS(
1536 fredSeq == env.seq(fred),
1537 "fred: "s + std::to_string(fredSeq) + ", " +
1538 std::to_string(env.seq(fred)));
1539 BEAST_EXPECTS(
1540 gwenSeq == env.seq(gwen),
1541 "gwen: "s + std::to_string(gwenSeq) + ", " +
1542 std::to_string(env.seq(gwen)));
1543 BEAST_EXPECTS(
1544 hankSeq + 1 == env.seq(hank),
1545 "hank: "s + std::to_string(hankSeq) + ", " +
1546 std::to_string(env.seq(hank)));
1547
1548 // Which sequences get incremented may change if TxQ ordering is
1549 // changed
1550 //++aliceSeq;
1551 ++bobSeq;
1552 ++(++charlieSeq);
1553 ++dariaSeq;
1554 ++elmoSeq;
1555 // ++fredSeq;
1556 //++gwenSeq;
1557 ++hankSeq;
1558
1559 auto getTxsQueued = [&]() {
1560 auto const txs = env.app().getTxQ().getTxs();
1562 for (auto const& tx : txs)
1563 {
1564 ++result[tx.txn->at(sfAccount)];
1565 }
1566 return result;
1567 };
1568 auto qTxCount1 = getTxsQueued();
1569 BEAST_EXPECT(qTxCount1.size() <= 3);
1570
1571 // Fill up the queue again
1572 env(noop(alice),
1573 seq(aliceSeq + qTxCount1[alice.id()]++),
1574 fee(15),
1575 queued);
1576 env(noop(bob), seq(bobSeq + qTxCount1[bob.id()]++), fee(15), queued);
1577 env(noop(charlie),
1578 seq(charlieSeq + qTxCount1[charlie.id()]++),
1579 fee(15),
1580 queued);
1581 env(noop(daria),
1582 seq(dariaSeq + qTxCount1[daria.id()]++),
1583 fee(15),
1584 queued);
1585 env(noop(elmo), seq(elmoSeq + qTxCount1[elmo.id()]++), fee(15), queued);
1586 env(noop(fred), seq(fredSeq + qTxCount1[fred.id()]++), fee(15), queued);
1587 env(noop(gwen), seq(gwenSeq + qTxCount1[gwen.id()]++), fee(15), queued);
1588
1589 minFeeLevel = txFeeLevelByAccount(env, gwen) + 1;
1590 checkMetrics(__LINE__, env, 10, 10, 6, 5, minFeeLevel);
1591
1592 // Add another transaction, with a higher fee,
1593 // Not high enough to get into the ledger, but high
1594 // enough to get into the queue (and kick somebody out)
1595 env(noop(alice),
1596 fee(100),
1597 seq(aliceSeq + qTxCount1[alice.id()]++),
1598 queued);
1599
1600 checkMetrics(__LINE__, env, 10, 10, 6, 5, minFeeLevel);
1601
1602 // Seven txs are processed out of the queue, leaving 3. One
1603 // dropped tx is retried from localTxs, and put back into the
1604 // queue.
1605 env.close();
1606 checkMetrics(__LINE__, env, 4, 12, 7, 6);
1607
1608 // Refresh the queue counts
1609 auto qTxCount2 = getTxsQueued();
1610 BEAST_EXPECT(qTxCount2.size() <= 4);
1611
1612 // This next test should remain unchanged regardless of
1613 // transaction ordering
1615 aliceSeq + bobSeq + charlieSeq + dariaSeq + elmoSeq + fredSeq +
1616 gwenSeq + hankSeq + 7 ==
1617 env.seq(alice) + env.seq(bob) + env.seq(charlie) + env.seq(daria) +
1618 env.seq(elmo) + env.seq(fred) + env.seq(gwen) + env.seq(hank));
1619 // These tests may change if TxQ ordering is changed
1620 BEAST_EXPECTS(
1621 aliceSeq + qTxCount1[alice.id()] - qTxCount2[alice.id()] ==
1622 env.seq(alice),
1623 "alice: "s + std::to_string(aliceSeq) + ", " +
1624 std::to_string(env.seq(alice)));
1625 BEAST_EXPECTS(
1626 bobSeq + qTxCount1[bob.id()] - qTxCount2[bob.id()] == env.seq(bob),
1627 "bob: "s + std::to_string(bobSeq) + ", " +
1628 std::to_string(env.seq(bob)));
1629 BEAST_EXPECTS(
1630 charlieSeq + qTxCount1[charlie.id()] - qTxCount2[charlie.id()] ==
1631 env.seq(charlie),
1632 "charlie: "s + std::to_string(charlieSeq) + ", " +
1633 std::to_string(env.seq(charlie)));
1634 BEAST_EXPECTS(
1635 dariaSeq + qTxCount1[daria.id()] - qTxCount2[daria.id()] ==
1636 env.seq(daria),
1637 "daria: "s + std::to_string(dariaSeq) + ", " +
1638 std::to_string(env.seq(daria)));
1639 BEAST_EXPECTS(
1640 elmoSeq + qTxCount1[elmo.id()] - qTxCount2[elmo.id()] ==
1641 env.seq(elmo),
1642 "elmo: "s + std::to_string(elmoSeq) + ", " +
1643 std::to_string(env.seq(elmo)));
1644 BEAST_EXPECTS(
1645 fredSeq + qTxCount1[fred.id()] - qTxCount2[fred.id()] ==
1646 env.seq(fred),
1647 "fred: "s + std::to_string(fredSeq) + ", " +
1648 std::to_string(env.seq(fred)));
1649 BEAST_EXPECTS(
1650 gwenSeq + qTxCount1[gwen.id()] - qTxCount2[gwen.id()] ==
1651 env.seq(gwen),
1652 "gwen: "s + std::to_string(gwenSeq) + ", " +
1653 std::to_string(env.seq(gwen)));
1654 BEAST_EXPECTS(
1655 hankSeq + qTxCount1[hank.id()] - qTxCount2[hank.id()] ==
1656 env.seq(hank),
1657 "hank: "s + std::to_string(hankSeq) + ", " +
1658 std::to_string(env.seq(hank)));
1659 }
1660
1661 void
1663 {
1664 using namespace jtx;
1665 testcase("acct tx id");
1666
1667 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "1"}}));
1668
1669 auto alice = Account("alice");
1670
1671 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 1);
1672
1673 env.fund(XRP(50000), noripple(alice));
1674 checkMetrics(__LINE__, env, 0, std::nullopt, 1, 1);
1675
1676 env(fset(alice, asfAccountTxnID));
1677 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 1);
1678
1679 // Immediately after the fset, the sfAccountTxnID field
1680 // is still uninitialized, so preflight succeeds here,
1681 // and this txn fails because it can't be stored in the queue.
1682 env(noop(alice),
1683 json(R"({"AccountTxnID": "0"})"),
1685
1686 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 1);
1687 env.close();
1688 // The failed transaction is retried from LocalTx
1689 // and succeeds.
1690 checkMetrics(__LINE__, env, 0, 4, 1, 2);
1691
1692 env(noop(alice));
1693 checkMetrics(__LINE__, env, 0, 4, 2, 2);
1694
1695 env(noop(alice), json(R"({"AccountTxnID": "0"})"), ter(tefWRONG_PRIOR));
1696 }
1697
1698 void
1700 {
1701 using namespace jtx;
1702 using namespace std::string_literals;
1703 testcase("maximum tx");
1704
1705 {
1706 Env env(
1707 *this,
1708 makeConfig(
1709 {{"minimum_txn_in_ledger_standalone", "2"},
1710 {"minimum_txn_in_ledger", "5"},
1711 {"target_txn_in_ledger", "4"},
1712 {"maximum_txn_in_ledger", "5"}}));
1713 auto const baseFee = env.current()->fees().base.drops();
1714
1715 auto alice = Account("alice");
1716
1717 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 2);
1718
1719 env.fund(XRP(50000), noripple(alice));
1720 checkMetrics(__LINE__, env, 0, std::nullopt, 1, 2);
1721
1722 FeeLevel64 medFeeLevel;
1723 for (int i = 0; i < 10; ++i)
1724 {
1725 auto const cost = openLedgerCost(env);
1726 // We know there's going to be 11 transactions in ledger
1727 // Need to fetch 5th transaction cost here to be able to
1728 // calculate median fee level.
1729 if (i == 4)
1730 {
1731 double const feeMultiplier =
1732 static_cast<double>(cost.drops()) / baseFee;
1733 medFeeLevel = FeeLevel64{static_cast<uint64_t>(
1734 feeMultiplier * baseFeeLevel.fee())};
1735 }
1736
1737 env(noop(alice), fee(cost));
1738 }
1739
1740 checkMetrics(__LINE__, env, 0, std::nullopt, 11, 2);
1741
1742 env.close();
1743 // If not for the maximum, the per ledger would be 11.
1744 // clang-format off
1745 checkMetrics(__LINE__, env, 0, 10, 0, 5, baseFeeLevel.fee(), calcMedFeeLevel(medFeeLevel));
1746 // clang-format on
1747 }
1748
1749 try
1750 {
1751 Env env(
1752 *this,
1753 makeConfig(
1754 {{"minimum_txn_in_ledger", "200"},
1755 {"minimum_txn_in_ledger_standalone", "200"},
1756 {"target_txn_in_ledger", "4"},
1757 {"maximum_txn_in_ledger", "5"}}));
1758 // should throw
1759 fail();
1760 }
1761 catch (std::runtime_error const& e)
1762 {
1764 e.what() ==
1765 "The minimum number of low-fee transactions allowed "
1766 "per ledger (minimum_txn_in_ledger) exceeds "
1767 "the maximum number of low-fee transactions allowed per "
1768 "ledger (maximum_txn_in_ledger)."s);
1769 }
1770 try
1771 {
1772 Env env(
1773 *this,
1774 makeConfig(
1775 {{"minimum_txn_in_ledger", "200"},
1776 {"minimum_txn_in_ledger_standalone", "2"},
1777 {"target_txn_in_ledger", "4"},
1778 {"maximum_txn_in_ledger", "5"}}));
1779 // should throw
1780 fail();
1781 }
1782 catch (std::runtime_error const& e)
1783 {
1785 e.what() ==
1786 "The minimum number of low-fee transactions allowed "
1787 "per ledger (minimum_txn_in_ledger) exceeds "
1788 "the maximum number of low-fee transactions allowed per "
1789 "ledger (maximum_txn_in_ledger)."s);
1790 }
1791 try
1792 {
1793 Env env(
1794 *this,
1795 makeConfig(
1796 {{"minimum_txn_in_ledger", "2"},
1797 {"minimum_txn_in_ledger_standalone", "200"},
1798 {"target_txn_in_ledger", "4"},
1799 {"maximum_txn_in_ledger", "5"}}));
1800 // should throw
1801 fail();
1802 }
1803 catch (std::runtime_error const& e)
1804 {
1806 e.what() ==
1807 "The minimum number of low-fee transactions allowed "
1808 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1809 "the maximum number of low-fee transactions allowed per "
1810 "ledger (maximum_txn_in_ledger)."s);
1811 }
1812 }
1813
1814 void
1816 {
1817 using namespace jtx;
1818 testcase("unexpected balance change");
1819
1820 Env env(
1821 *this,
1822 makeConfig(
1823 {{"minimum_txn_in_ledger_standalone", "3"}},
1824 {{"account_reserve", "200"}, {"owner_reserve", "50"}}));
1825
1826 auto alice = Account("alice");
1827 auto bob = Account("bob");
1828
1829 auto queued = ter(terQUEUED);
1830
1831 // ledgers in queue is 2 because of makeConfig
1832 auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
1833
1834 checkMetrics(__LINE__, env, 0, initQueueMax, 0, 3);
1835
1836 env.fund(drops(5000), noripple(alice));
1837 env.fund(XRP(50000), noripple(bob));
1838 checkMetrics(__LINE__, env, 0, initQueueMax, 2, 3);
1839 auto USD = bob["USD"];
1840
1841 env(offer(alice, USD(5000), drops(5000)), require(owners(alice, 1)));
1842 checkMetrics(__LINE__, env, 0, initQueueMax, 3, 3);
1843
1844 env.close();
1845 checkMetrics(__LINE__, env, 0, 6, 0, 3);
1846
1847 // Fill up the ledger
1848 fillQueue(env, alice);
1849 checkMetrics(__LINE__, env, 0, 6, 4, 3);
1850
1851 // Queue up a couple of transactions, plus one
1852 // more expensive one.
1853 auto aliceSeq = env.seq(alice);
1854 env(noop(alice), seq(aliceSeq++), queued);
1855 env(noop(alice), seq(aliceSeq++), queued);
1856 env(noop(alice), seq(aliceSeq++), queued);
1857 env(noop(alice), fee(drops(1000)), seq(aliceSeq), queued);
1858 checkMetrics(__LINE__, env, 4, 6, 4, 3);
1859
1860 // This offer should take Alice's offer
1861 // up to Alice's reserve.
1862 env(offer(bob, drops(5000), USD(5000)),
1863 fee(openLedgerCost(env)),
1864 require(
1865 balance(alice, drops(250)), owners(alice, 1), lines(alice, 1)));
1866 checkMetrics(__LINE__, env, 4, 6, 5, 3);
1867
1868 // Try adding a new transaction.
1869 // Too many fees in flight.
1870 env(noop(alice),
1871 fee(drops(200)),
1872 seq(aliceSeq + 1),
1874 checkMetrics(__LINE__, env, 4, 6, 5, 3);
1875
1876 // Close the ledger. All of Alice's transactions
1877 // take a fee, except the last one.
1878 env.close();
1879 checkMetrics(__LINE__, env, 1, 10, 3, 5);
1880 env.require(balance(alice, drops(250 - 30)));
1881
1882 // Still can't add a new transaction for Alice,
1883 // no matter the fee.
1884 env(noop(alice),
1885 fee(drops(200)),
1886 seq(aliceSeq + 1),
1888 checkMetrics(__LINE__, env, 1, 10, 3, 5);
1889
1890 /* At this point, Alice's transaction is indefinitely
1891 stuck in the queue. Eventually it will either
1892 expire, get forced off the end by more valuable
1893 transactions, get replaced by Alice, or Alice
1894 will get more XRP, and it'll process.
1895 */
1896
1897 for (int i = 0; i < 9; ++i)
1898 {
1899 env.close();
1900 checkMetrics(__LINE__, env, 1, 10, 0, 5);
1901 }
1902
1903 // And Alice's transaction expires (via the retry limit,
1904 // not LastLedgerSequence).
1905 env.close();
1906 checkMetrics(__LINE__, env, 0, 10, 0, 5);
1907 }
1908
1909 void
1911 {
1912 using namespace jtx;
1913 testcase("blockers sequence");
1914
1915 auto alice = Account("alice");
1916 auto bob = Account("bob");
1917 auto charlie = Account("charlie");
1918 auto daria = Account("daria");
1919
1920 auto queued = ter(terQUEUED);
1921
1922 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
1923 auto const baseFee = env.current()->fees().base.drops();
1924
1925 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
1926
1927 env.fund(XRP(50000), noripple(alice, bob));
1928 env.memoize(charlie);
1929 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 3);
1930 {
1931 // Cannot put a blocker in an account's queue if that queue
1932 // already holds two or more (non-blocker) entries.
1933
1934 // Fill up the open ledger
1935 env(noop(alice));
1936 // Set a regular key just to clear the password spent flag
1937 env(regkey(alice, charlie));
1938 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
1939
1940 // Put two "normal" txs in the queue
1941 auto const aliceSeq = env.seq(alice);
1942 env(noop(alice), seq(aliceSeq + 0), queued);
1943 env(noop(alice), seq(aliceSeq + 1), queued);
1944
1945 // Can't replace either queued transaction with a blocker
1946 env(fset(alice, asfAccountTxnID),
1947 seq(aliceSeq + 0),
1948 fee(baseFee * 2),
1950
1951 env(regkey(alice, bob),
1952 seq(aliceSeq + 1),
1953 fee(baseFee * 2),
1955
1956 // Can't append a blocker to the queue.
1957 env(signers(alice, 2, {{bob}, {charlie}, {daria}}),
1958 seq(aliceSeq + 2),
1959 fee(baseFee * 2),
1961
1962 // Other accounts are not affected
1963 env(noop(bob), queued);
1964 checkMetrics(__LINE__, env, 3, std::nullopt, 4, 3);
1965
1966 // Drain the queue.
1967 env.close();
1968 checkMetrics(__LINE__, env, 0, 8, 4, 4);
1969 }
1970 {
1971 // Replace a lone non-blocking tx with a blocker.
1972
1973 // Fill up the open ledger and put just one entry in the TxQ.
1974 env(noop(alice));
1975
1976 auto const aliceSeq = env.seq(alice);
1977 env(noop(alice), seq(aliceSeq + 0), queued);
1978
1979 // Since there's only one entry in the queue we can replace
1980 // that entry with a blocker.
1981 env(regkey(alice, bob),
1982 seq(aliceSeq + 0),
1983 fee(baseFee * 2),
1984 queued);
1985
1986 // Now that there's a blocker in the queue we can't append to
1987 // the queue.
1988 env(noop(alice), seq(aliceSeq + 1), ter(telCAN_NOT_QUEUE_BLOCKED));
1989
1990 // Other accounts are unaffected.
1991 env(noop(bob), queued);
1992
1993 // We can replace the blocker with a different blocker.
1994 env(signers(alice, 2, {{bob}, {charlie}, {daria}}),
1995 seq(aliceSeq + 0),
1996 fee(baseFee * 2.6),
1997 queued);
1998
1999 // Prove that the queue is still blocked.
2000 env(noop(alice), seq(aliceSeq + 1), ter(telCAN_NOT_QUEUE_BLOCKED));
2001
2002 // We can replace the blocker with a non-blocker. Then we can
2003 // successfully append to the queue.
2004 env(noop(alice), seq(aliceSeq + 0), fee(baseFee * 3.3), queued);
2005 env(noop(alice), seq(aliceSeq + 1), queued);
2006
2007 // Drain the queue.
2008 env.close();
2009 checkMetrics(__LINE__, env, 0, 10, 3, 5);
2010 }
2011 {
2012 // Put a blocker in an empty queue.
2013
2014 // Fill up the open ledger and put a blocker as Alice's first
2015 // entry in the (empty) TxQ.
2016 env(noop(alice));
2017 env(noop(alice));
2018 env(noop(alice));
2019
2020 auto const aliceSeq = env.seq(alice);
2021 env(fset(alice, asfAccountTxnID), seq(aliceSeq + 0), queued);
2022
2023 // Since there's a blocker in the queue we can't append to
2024 // the queue.
2025 env(noop(alice), seq(aliceSeq + 1), ter(telCAN_NOT_QUEUE_BLOCKED));
2026
2027 // Other accounts are unaffected.
2028 env(noop(bob), queued);
2029
2030 // We can replace the blocker with a non-blocker. Then we can
2031 // successfully append to the queue.
2032 env(noop(alice), seq(aliceSeq + 0), fee(baseFee * 2), queued);
2033 env(noop(alice), seq(aliceSeq + 1), queued);
2034
2035 // Drain the queue.
2036 env.close();
2037 checkMetrics(__LINE__, env, 0, 12, 3, 6);
2038 }
2039 }
2040
2041 void
2043 {
2044 using namespace jtx;
2045 testcase("blockers ticket");
2046
2047 auto alice = Account("alice");
2048 auto bob = Account("bob");
2049 auto charlie = Account("charlie");
2050 auto daria = Account("daria");
2051
2052 auto queued = ter(terQUEUED);
2053
2054 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
2055 auto const baseFee = env.current()->fees().base.drops();
2056
2057 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
2058
2059 env.fund(XRP(50000), noripple(alice, bob));
2060 env.memoize(charlie);
2061
2062 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 3);
2063
2064 std::uint32_t tkt{env.seq(alice) + 1};
2065 {
2066 // Cannot put a blocker in an account's queue if that queue
2067 // already holds two or more (non-blocker) entries.
2068
2069 // Fill up the open ledger
2070 env(ticket::create(alice, 250), seq(tkt - 1));
2071 // Set a regular key just to clear the password spent flag
2072 env(regkey(alice, charlie));
2073 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
2074
2075 // Put two "normal" txs in the queue
2076 auto const aliceSeq = env.seq(alice);
2077 env(noop(alice), ticket::use(tkt + 2), queued);
2078 env(noop(alice), ticket::use(tkt + 1), queued);
2079
2080 // Can't replace either queued transaction with a blocker
2081 env(fset(alice, asfAccountTxnID),
2082 ticket::use(tkt + 1),
2083 fee(baseFee * 2),
2085
2086 env(regkey(alice, bob),
2087 ticket::use(tkt + 2),
2088 fee(baseFee * 2),
2090
2091 // Can't append a blocker to the queue.
2092 env(signers(alice, 2, {{bob}, {charlie}, {daria}}),
2093 fee(baseFee * 2),
2095
2096 env(signers(alice, 2, {{bob}, {charlie}, {daria}}),
2097 ticket::use(tkt + 0),
2098 fee(baseFee * 2),
2100
2101 // Other accounts are not affected
2102 env(noop(bob), queued);
2103 checkMetrics(__LINE__, env, 3, std::nullopt, 4, 3);
2104
2105 // Drain the queue and local transactions.
2106 env.close();
2107 checkMetrics(__LINE__, env, 0, 8, 5, 4);
2108
2109 // Show that the local transactions have flushed through as well.
2110 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2111 env(noop(alice), ticket::use(tkt + 0), ter(tefNO_TICKET));
2112 env(noop(alice), ticket::use(tkt + 1), ter(tefNO_TICKET));
2113 env(noop(alice), ticket::use(tkt + 2), ter(tefNO_TICKET));
2114 tkt += 3;
2115 }
2116 {
2117 // Replace a lone non-blocking tx with a blocker.
2118
2119 // Put just one entry in the TxQ.
2120 auto const aliceSeq = env.seq(alice);
2121 env(noop(alice), ticket::use(tkt + 0), queued);
2122
2123 // Since there's an entry in the queue we cannot append a
2124 // blocker to the account's queue.
2125 env(regkey(alice, bob),
2126 fee(baseFee * 2),
2128 env(regkey(alice, bob),
2129 ticket::use(tkt + 1),
2130 fee(baseFee * 2),
2132
2133 // However we can _replace_ that lone entry with a blocker.
2134 env(regkey(alice, bob),
2135 ticket::use(tkt + 0),
2136 fee(baseFee * 2),
2137 queued);
2138
2139 // Now that there's a blocker in the queue we can't append to
2140 // the queue.
2141 env(noop(alice), ter(telCAN_NOT_QUEUE_BLOCKED));
2142 env(noop(alice),
2143 ticket::use(tkt + 1),
2145
2146 // Other accounts are unaffected.
2147 env(noop(bob), queued);
2148
2149 // We can replace the blocker with a different blocker.
2150 env(signers(alice, 2, {{bob}, {charlie}, {daria}}),
2151 ticket::use(tkt + 0),
2152 fee(baseFee * 2.6),
2153 queued);
2154
2155 // Prove that the queue is still blocked.
2156 env(noop(alice), ter(telCAN_NOT_QUEUE_BLOCKED));
2157 env(noop(alice),
2158 ticket::use(tkt + 1),
2160
2161 // We can replace the blocker with a non-blocker. Then we can
2162 // successfully append to the queue.
2163 env(noop(alice), ticket::use(tkt + 0), fee(baseFee * 3.3), queued);
2164 env(noop(alice), ticket::use(tkt + 1), queued);
2165 env(noop(alice), seq(aliceSeq), queued);
2166
2167 // Drain the queue.
2168 env.close();
2169 checkMetrics(__LINE__, env, 0, 10, 4, 5);
2170
2171 // Show that the local transactions have flushed through as well.
2172 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2173 env(noop(alice), ticket::use(tkt + 0), ter(tefNO_TICKET));
2174 env(noop(alice), ticket::use(tkt + 1), ter(tefNO_TICKET));
2175 tkt += 2;
2176 }
2177 {
2178 // Put a blocker in an empty queue.
2179
2180 // Fill up the open ledger and put a blocker as Alice's first
2181 // entry in the (empty) TxQ.
2182 env(noop(alice));
2183 env(noop(alice));
2184
2185 env(fset(alice, asfAccountTxnID), ticket::use(tkt + 2), queued);
2186
2187 // Since there's a blocker in the queue we can't append to
2188 // the queue.
2189 env(noop(alice),
2190 ticket::use(tkt + 1),
2192
2193 // Other accounts are unaffected.
2194 env(noop(bob), queued);
2195
2196 // We can replace the blocker with a non-blocker. Then we can
2197 // successfully append to the queue.
2198 env(noop(alice), ticket::use(tkt + 2), fee(baseFee * 2), queued);
2199 env(noop(alice), ticket::use(tkt + 1), queued);
2200
2201 // Drain the queue.
2202 env.close();
2203 checkMetrics(__LINE__, env, 0, 12, 3, 6);
2204 }
2205 }
2206
2207 void
2209 {
2210 using namespace jtx;
2211 testcase("In-flight balance checks");
2212
2213 Env env(
2214 *this,
2215 makeConfig(
2216 {{"minimum_txn_in_ledger_standalone", "3"}},
2217 {{"account_reserve", "200"}, {"owner_reserve", "50"}}));
2218
2219 auto alice = Account("alice");
2220 auto charlie = Account("charlie");
2221 auto gw = Account("gw");
2222
2223 auto queued = ter(terQUEUED);
2224
2225 // Set the fee reserves _really_ low so transactions with fees
2226 // in the ballpark of the reserves can be queued. With default
2227 // reserves, a couple hundred transactions would have to be
2228 // queued before the open ledger fee approached the reserve,
2229 // which would unnecessarily slow down this test.
2230 // ledgers in queue is 2 because of makeConfig
2231 auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
2232
2233 auto limit = 3;
2234
2235 checkMetrics(__LINE__, env, 0, initQueueMax, 0, limit);
2236
2237 env.fund(XRP(50000), noripple(alice, charlie), gw);
2238 checkMetrics(__LINE__, env, 0, initQueueMax, limit + 1, limit);
2239
2240 auto USD = gw["USD"];
2241 auto BUX = gw["BUX"];
2242
2244 // Offer with high XRP out and low fee doesn't block
2245 auto aliceSeq = env.seq(alice);
2246 auto aliceBal = env.balance(alice);
2247
2248 env.require(balance(alice, XRP(50000)), owners(alice, 0));
2249
2250 // If this offer crosses, all of alice's
2251 // XRP will be taken (except the reserve).
2252 env(offer(alice, BUX(5000), XRP(50000)), queued);
2253 checkMetrics(__LINE__, env, 1, initQueueMax, limit + 1, limit);
2254
2255 // But because the reserve is protected, another
2256 // transaction will be allowed to queue
2257 env(noop(alice), seq(aliceSeq + 1), queued);
2258 checkMetrics(__LINE__, env, 2, initQueueMax, limit + 1, limit);
2259
2260 env.close();
2261 ++limit;
2262 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2263
2264 // But once we close the ledger, we find alice
2265 // has plenty of XRP, because the offer didn't
2266 // cross (of course).
2267 env.require(balance(alice, aliceBal - drops(20)), owners(alice, 1));
2268 // cancel the offer
2269 env(offer_cancel(alice, aliceSeq));
2270
2272 // Offer with high XRP out and high total fee blocks later txs
2273 fillQueue(env, alice);
2274 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2275 aliceSeq = env.seq(alice);
2276 aliceBal = env.balance(alice);
2277
2278 env.require(owners(alice, 0));
2279
2280 // Alice creates an offer with a fee of half the reserve
2281 env(offer(alice, BUX(5000), XRP(50000)), fee(drops(100)), queued);
2282 checkMetrics(__LINE__, env, 1, limit * 2, limit + 1, limit);
2283
2284 // Alice creates another offer with a fee
2285 // that brings the total to just shy of the reserve
2286 env(noop(alice), fee(drops(99)), seq(aliceSeq + 1), queued);
2287 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2288
2289 // So even a noop will look like alice
2290 // doesn't have the balance to pay the fee
2291 env(noop(alice),
2292 fee(drops(51)),
2293 seq(aliceSeq + 2),
2295 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2296
2297 env.close();
2298 ++limit;
2299 checkMetrics(__LINE__, env, 0, limit * 2, 3, limit);
2300
2301 // But once we close the ledger, we find alice
2302 // has plenty of XRP, because the offer didn't
2303 // cross (of course).
2304 env.require(balance(alice, aliceBal - drops(250)), owners(alice, 1));
2305 // cancel the offer
2306 env(offer_cancel(alice, aliceSeq));
2307
2309 // Offer with high XRP out and super high fee blocks later txs
2310 fillQueue(env, alice);
2311 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2312 aliceSeq = env.seq(alice);
2313 aliceBal = env.balance(alice);
2314
2315 env.require(owners(alice, 0));
2316
2317 // Alice creates an offer with a fee larger than the reserve
2318 // This one can queue because it's the first in the queue for alice
2319 env(offer(alice, BUX(5000), XRP(50000)), fee(drops(300)), queued);
2320 checkMetrics(__LINE__, env, 1, limit * 2, limit + 1, limit);
2321
2322 // So even a noop will look like alice
2323 // doesn't have the balance to pay the fee
2324 env(noop(alice),
2325 fee(drops(51)),
2326 seq(aliceSeq + 1),
2328 checkMetrics(__LINE__, env, 1, limit * 2, limit + 1, limit);
2329
2330 env.close();
2331 ++limit;
2332 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2333
2334 // But once we close the ledger, we find alice
2335 // has plenty of XRP, because the offer didn't
2336 // cross (of course).
2337 env.require(balance(alice, aliceBal - drops(351)), owners(alice, 1));
2338 // cancel the offer
2339 env(offer_cancel(alice, aliceSeq));
2340
2342 // Offer with low XRP out allows later txs
2343 fillQueue(env, alice);
2344 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2345 aliceSeq = env.seq(alice);
2346 aliceBal = env.balance(alice);
2347
2348 // If this offer crosses, just a bit
2349 // of alice's XRP will be taken.
2350 env(offer(alice, BUX(50), XRP(500)), queued);
2351
2352 // And later transactions are just fine
2353 env(noop(alice), seq(aliceSeq + 1), queued);
2354 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2355
2356 env.close();
2357 ++limit;
2358 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2359
2360 // But once we close the ledger, we find alice
2361 // has plenty of XRP, because the offer didn't
2362 // cross (of course).
2363 env.require(balance(alice, aliceBal - drops(20)), owners(alice, 1));
2364 // cancel the offer
2365 env(offer_cancel(alice, aliceSeq));
2366
2368 // Large XRP payment doesn't block later txs
2369 fillQueue(env, alice);
2370 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2371
2372 aliceSeq = env.seq(alice);
2373 aliceBal = env.balance(alice);
2374
2375 // If this payment succeeds, alice will
2376 // send her entire balance to charlie
2377 // (minus the reserve).
2378 env(pay(alice, charlie, XRP(50000)), queued);
2379
2380 // But because the reserve is protected, another
2381 // transaction will be allowed to queue
2382 env(noop(alice), seq(aliceSeq + 1), queued);
2383 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2384
2385 env.close();
2386 ++limit;
2387 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2388
2389 // But once we close the ledger, we find alice
2390 // still has most of her balance, because the
2391 // payment was unfunded!
2392 env.require(balance(alice, aliceBal - drops(20)), owners(alice, 0));
2393
2395 // Small XRP payment allows later txs
2396 fillQueue(env, alice);
2397 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2398
2399 aliceSeq = env.seq(alice);
2400 aliceBal = env.balance(alice);
2401
2402 // If this payment succeeds, alice will
2403 // send just a bit of balance to charlie
2404 env(pay(alice, charlie, XRP(500)), queued);
2405
2406 // And later transactions are just fine
2407 env(noop(alice), seq(aliceSeq + 1), queued);
2408 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2409
2410 env.close();
2411 ++limit;
2412 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2413
2414 // The payment succeeds
2415 env.require(
2416 balance(alice, aliceBal - XRP(500) - drops(20)), owners(alice, 0));
2417
2419 // Large IOU payment allows later txs
2420 auto const amount = USD(500000);
2421 env(trust(alice, USD(50000000)));
2422 env(trust(charlie, USD(50000000)));
2423 checkMetrics(__LINE__, env, 0, limit * 2, 4, limit);
2424 // Close so we don't have to deal
2425 // with tx ordering in consensus.
2426 env.close();
2427
2428 env(pay(gw, alice, amount));
2429 checkMetrics(__LINE__, env, 0, limit * 2, 1, limit);
2430 // Close so we don't have to deal
2431 // with tx ordering in consensus.
2432 env.close();
2433
2434 fillQueue(env, alice);
2435 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2436
2437 aliceSeq = env.seq(alice);
2438 aliceBal = env.balance(alice);
2439 auto aliceUSD = env.balance(alice, USD);
2440
2441 // If this payment succeeds, alice will
2442 // send her entire USD balance to charlie.
2443 env(pay(alice, charlie, amount), queued);
2444
2445 // But that's fine, because it doesn't affect
2446 // alice's XRP balance (other than the fee, of course).
2447 env(noop(alice), seq(aliceSeq + 1), queued);
2448 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2449
2450 env.close();
2451 ++limit;
2452 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2453
2454 // So once we close the ledger, alice has her
2455 // XRP balance, but her USD balance went to charlie.
2456 env.require(
2457 balance(alice, aliceBal - drops(20)),
2458 balance(alice, USD(0)),
2459 balance(charlie, aliceUSD),
2460 owners(alice, 1),
2461 owners(charlie, 1));
2462
2464 // Large XRP to IOU payment doesn't block later txs.
2465
2466 env(offer(gw, XRP(500000), USD(50000)));
2467 // Close so we don't have to deal
2468 // with tx ordering in consensus.
2469 env.close();
2470
2471 fillQueue(env, charlie);
2472 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2473
2474 aliceSeq = env.seq(alice);
2475 aliceBal = env.balance(alice);
2476 auto charlieUSD = env.balance(charlie, USD);
2477
2478 // If this payment succeeds, and uses the
2479 // entire sendMax, alice will send her
2480 // entire XRP balance to charlie in the
2481 // form of USD.
2482 BEAST_EXPECT(XRP(60000) > aliceBal);
2483 env(pay(alice, charlie, USD(1000)), sendmax(XRP(60000)), queued);
2484
2485 // But because the reserve is protected, another
2486 // transaction will be allowed to queue
2487 env(noop(alice), seq(aliceSeq + 1), queued);
2488 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2489
2490 env.close();
2491 ++limit;
2492 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2493
2494 // So once we close the ledger, alice sent a payment
2495 // to charlie using only a portion of her XRP balance
2496 env.require(
2497 balance(alice, aliceBal - XRP(10000) - drops(20)),
2498 balance(alice, USD(0)),
2499 balance(charlie, charlieUSD + USD(1000)),
2500 owners(alice, 1),
2501 owners(charlie, 1));
2502
2504 // Small XRP to IOU payment allows later txs.
2505
2506 fillQueue(env, charlie);
2507 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2508
2509 aliceSeq = env.seq(alice);
2510 aliceBal = env.balance(alice);
2511 charlieUSD = env.balance(charlie, USD);
2512
2513 // If this payment succeeds, and uses the
2514 // entire sendMax, alice will only send
2515 // a portion of her XRP balance to charlie
2516 // in the form of USD.
2517 BEAST_EXPECT(aliceBal > XRP(6001));
2518 env(pay(alice, charlie, USD(500)), sendmax(XRP(6000)), queued);
2519
2520 // And later transactions are just fine
2521 env(noop(alice), seq(aliceSeq + 1), queued);
2522 checkMetrics(__LINE__, env, 2, limit * 2, limit + 1, limit);
2523
2524 env.close();
2525 ++limit;
2526 checkMetrics(__LINE__, env, 0, limit * 2, 2, limit);
2527
2528 // So once we close the ledger, alice sent a payment
2529 // to charlie using only a portion of her XRP balance
2530 env.require(
2531 balance(alice, aliceBal - XRP(5000) - drops(20)),
2532 balance(alice, USD(0)),
2533 balance(charlie, charlieUSD + USD(500)),
2534 owners(alice, 1),
2535 owners(charlie, 1));
2536
2538 // Edge case: what happens if the balance is below the reserve?
2539 env(noop(alice), fee(env.balance(alice) - drops(30)));
2540 env.close();
2541
2542 fillQueue(env, charlie);
2543 checkMetrics(__LINE__, env, 0, limit * 2, limit + 1, limit);
2544
2545 aliceSeq = env.seq(alice);
2546 aliceBal = env.balance(alice);
2547 BEAST_EXPECT(aliceBal == drops(30));
2548
2549 env(noop(alice), fee(drops(25)), queued);
2550 env(noop(alice), seq(aliceSeq + 1), ter(terINSUF_FEE_B));
2551 BEAST_EXPECT(env.balance(alice) == drops(30));
2552
2553 checkMetrics(__LINE__, env, 1, limit * 2, limit + 1, limit);
2554
2555 env.close();
2556 ++limit;
2557 checkMetrics(__LINE__, env, 0, limit * 2, 1, limit);
2558 BEAST_EXPECT(env.balance(alice) == drops(5));
2559 }
2560
2561 void
2563 {
2564 using namespace jtx;
2565 using namespace std::chrono;
2566 testcase("consequences");
2567
2568 Env env(*this);
2569 auto const alice = Account("alice");
2570 env.memoize(alice);
2571 env.memoize("bob");
2572 env.memoize("carol");
2573 {
2574 auto const jtx = env.jt(offer_cancel(alice, 3), seq(5), fee(10));
2575 auto const pf = preflight(
2576 env.app(),
2577 env.current()->rules(),
2578 *jtx.stx,
2579 tapNONE,
2580 env.journal);
2581 BEAST_EXPECT(pf.ter == tesSUCCESS);
2582 BEAST_EXPECT(!pf.consequences.isBlocker());
2583 BEAST_EXPECT(pf.consequences.fee() == drops(10));
2584 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(0));
2585 }
2586
2587 {
2588 auto USD = alice["USD"];
2589
2590 auto const jtx =
2591 env.jt(trust("carol", USD(50000000)), seq(1), fee(10));
2592 auto const pf = preflight(
2593 env.app(),
2594 env.current()->rules(),
2595 *jtx.stx,
2596 tapNONE,
2597 env.journal);
2598 BEAST_EXPECT(pf.ter == tesSUCCESS);
2599 BEAST_EXPECT(!pf.consequences.isBlocker());
2600 BEAST_EXPECT(pf.consequences.fee() == drops(10));
2601 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(0));
2602 }
2603
2604 {
2605 auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10));
2606 auto const pf = preflight(
2607 env.app(),
2608 env.current()->rules(),
2609 *jtx.stx,
2610 tapNONE,
2611 env.journal);
2612 BEAST_EXPECT(pf.ter == tesSUCCESS);
2613 BEAST_EXPECT(!pf.consequences.isBlocker());
2614 BEAST_EXPECT(pf.consequences.fee() == drops(10));
2615 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(0));
2616 }
2617 }
2618
2619 void
2621 {
2622 // It is possible for an account to be present in the queue but have
2623 // no queued transactions. This has been the source of at least one
2624 // bug where an insufficiently informed developer assumed that if an
2625 // account was present in the queue then it also had at least one
2626 // queued transaction.
2627 //
2628 // This test does touch testing to verify that, at least, that bug
2629 // is addressed.
2630 using namespace jtx;
2631 testcase("acct in queue but empty");
2632
2633 auto alice = Account("alice");
2634 auto bob = Account("bob");
2635 auto charlie = Account("charlie");
2636
2637 auto queued = ter(terQUEUED);
2638
2639 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
2640 auto const baseFee = env.current()->fees().base.drops();
2641
2642 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
2643
2644 // Fund accounts while the fee is cheap so they all apply.
2645 env.fund(XRP(50000), noripple(alice, bob, charlie));
2646 checkMetrics(__LINE__, env, 0, std::nullopt, 3, 3);
2647
2648 // Alice - no fee change yet
2649 env(noop(alice));
2650 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
2651
2652 // Bob with really high fee - applies
2653 env(noop(bob), fee(openLedgerCost(env)));
2654 checkMetrics(__LINE__, env, 0, std::nullopt, 5, 3);
2655
2656 // Charlie with low fee: queued
2657 env(noop(charlie), fee(baseFee * 100), queued);
2658 checkMetrics(__LINE__, env, 1, std::nullopt, 5, 3);
2659
2660 env.close();
2661 // Verify that the queued transaction was applied
2662 checkMetrics(__LINE__, env, 0, 10, 1, 5);
2663
2665
2666 // Stuff the ledger and queue so we can verify that
2667 // stuff gets kicked out.
2668 env(noop(bob), fee(baseFee * 100));
2669 env(noop(bob), fee(baseFee * 100));
2670 env(noop(bob), fee(baseFee * 100));
2671 env(noop(bob), fee(baseFee * 100));
2672 env(noop(bob), fee(baseFee * 100));
2673 checkMetrics(__LINE__, env, 0, 10, 6, 5);
2674
2675 // Use explicit fees so we can control which txn
2676 // will get dropped
2677 // This one gets into the queue, but gets dropped when the
2678 // higher fee one is added later.
2679 std::uint32_t const charlieSeq{env.seq(charlie)};
2680 env(noop(charlie), fee(baseFee * 1.5), seq(charlieSeq), queued);
2681 auto const expectedFeeLevel = txFeeLevelByAccount(env, charlie);
2682
2683 // These stay in the queue.
2684 std::uint32_t aliceSeq{env.seq(alice)};
2685 std::uint32_t bobSeq{env.seq(bob)};
2686
2687 env(noop(alice), fee(baseFee * 1.6), seq(aliceSeq++), queued);
2688 env(noop(bob), fee(baseFee * 1.6), seq(bobSeq++), queued);
2689 env(noop(alice), fee(baseFee * 1.7), seq(aliceSeq++), queued);
2690 env(noop(bob), fee(baseFee * 1.7), seq(bobSeq++), queued);
2691 env(noop(alice), fee(baseFee * 1.8), seq(aliceSeq++), queued);
2692 env(noop(bob), fee(baseFee * 1.9), seq(bobSeq++), queued);
2693 env(noop(alice), fee(baseFee * 2), seq(aliceSeq++), queued);
2694 env(noop(bob), fee(baseFee * 2), seq(bobSeq++), queued);
2695 env(noop(alice), fee(baseFee * 2.1), seq(aliceSeq++), queued);
2696
2697 // Queue is full now.
2698 checkMetrics(__LINE__, env, 10, 10, 6, 5, expectedFeeLevel + 1);
2699
2700 // Try to add another transaction with the default (low) fee,
2701 // it should fail because the queue is full.
2702 env(noop(alice), seq(aliceSeq++), ter(telCAN_NOT_QUEUE_FULL));
2703
2704 // Add another transaction, with a higher fee,
2705 // not high enough to get into the ledger, but high
2706 // enough to get into the queue (and kick Charlie's out)
2707 env(noop(bob), fee(baseFee * 2.2), seq(bobSeq++), queued);
2708
2710
2711 // That was the setup for the actual test :-). Now make
2712 // sure we get the right results if we try to add a
2713 // transaction for Charlie (who's in the queue, but has no queued
2714 // transactions) with the wrong sequence numbers.
2715 //
2716 // Charlie is paying a high enough fee to go straight into the
2717 // ledger in order to get into the vicinity of an assert which
2718 // should no longer fire :-).
2719 env(noop(charlie),
2720 fee(baseFee * 800),
2721 seq(charlieSeq - 1),
2722 ter(tefPAST_SEQ));
2723 env(noop(charlie),
2724 fee(baseFee * 800),
2725 seq(charlieSeq + 1),
2726 ter(terPRE_SEQ));
2727 env(noop(charlie),
2728 fee(baseFee * 800),
2729 seq(charlieSeq),
2730 ter(tesSUCCESS));
2731 }
2732
2733 void
2735 {
2736 using namespace jtx;
2737 testcase("rpc");
2738
2739 Env env(*this);
2740
2741 auto fee = env.rpc("fee");
2742
2743 if (BEAST_EXPECT(fee.isMember(jss::result)) &&
2744 BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
2745 {
2746 auto const& result = fee[jss::result];
2748 result.isMember(jss::ledger_current_index) &&
2749 result[jss::ledger_current_index] == 3);
2750 BEAST_EXPECT(result.isMember(jss::current_ledger_size));
2751 BEAST_EXPECT(result.isMember(jss::current_queue_size));
2752 BEAST_EXPECT(result.isMember(jss::expected_ledger_size));
2753 BEAST_EXPECT(!result.isMember(jss::max_queue_size));
2754 BEAST_EXPECT(result.isMember(jss::drops));
2755 auto const& drops = result[jss::drops];
2756 BEAST_EXPECT(drops.isMember(jss::base_fee));
2757 BEAST_EXPECT(drops.isMember(jss::median_fee));
2758 BEAST_EXPECT(drops.isMember(jss::minimum_fee));
2759 BEAST_EXPECT(drops.isMember(jss::open_ledger_fee));
2760 BEAST_EXPECT(result.isMember(jss::levels));
2761 auto const& levels = result[jss::levels];
2762 BEAST_EXPECT(levels.isMember(jss::median_level));
2763 BEAST_EXPECT(levels.isMember(jss::minimum_level));
2764 BEAST_EXPECT(levels.isMember(jss::open_ledger_level));
2765 BEAST_EXPECT(levels.isMember(jss::reference_level));
2766 }
2767
2768 env.close();
2769
2770 fee = env.rpc("fee");
2771
2772 if (BEAST_EXPECT(fee.isMember(jss::result)) &&
2773 BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
2774 {
2775 auto const& result = fee[jss::result];
2777 result.isMember(jss::ledger_current_index) &&
2778 result[jss::ledger_current_index] == 4);
2779 BEAST_EXPECT(result.isMember(jss::current_ledger_size));
2780 BEAST_EXPECT(result.isMember(jss::current_queue_size));
2781 BEAST_EXPECT(result.isMember(jss::expected_ledger_size));
2782 BEAST_EXPECT(result.isMember(jss::max_queue_size));
2783 auto const& drops = result[jss::drops];
2784 BEAST_EXPECT(drops.isMember(jss::base_fee));
2785 BEAST_EXPECT(drops.isMember(jss::median_fee));
2786 BEAST_EXPECT(drops.isMember(jss::minimum_fee));
2787 BEAST_EXPECT(drops.isMember(jss::open_ledger_fee));
2788 BEAST_EXPECT(result.isMember(jss::levels));
2789 auto const& levels = result[jss::levels];
2790 BEAST_EXPECT(levels.isMember(jss::median_level));
2791 BEAST_EXPECT(levels.isMember(jss::minimum_level));
2792 BEAST_EXPECT(levels.isMember(jss::open_ledger_level));
2793 BEAST_EXPECT(levels.isMember(jss::reference_level));
2794 }
2795 }
2796
2797 void
2799 {
2800 /* This test is based on a reported regression where a
2801 replacement candidate transaction found the tx it was trying
2802 to replace did not have `consequences` set
2803
2804 Hypothesis: The queue had '22 through '25. At some point(s),
2805 both the original '22 and '23 expired and were removed from
2806 the queue. A second '22 was submitted, and the multi-tx logic
2807 did not kick in, because it matched the account's sequence
2808 number (a_seq == t_seq). The third '22 was submitted and found
2809 the '22 in the queue did not have consequences.
2810 */
2811 using namespace jtx;
2812 testcase("expiration replacement");
2813
2814 Env env(
2815 *this,
2816 makeConfig(
2817 {{"minimum_txn_in_ledger_standalone", "1"},
2818 {"ledgers_in_queue", "10"},
2819 {"maximum_txn_per_account", "20"}}));
2820
2821 auto const baseFee = env.current()->fees().base.drops();
2822
2823 // Alice will recreate the scenario. Bob will block.
2824 auto const alice = Account("alice");
2825 auto const bob = Account("bob");
2826
2827 env.fund(XRP(500000), noripple(alice, bob));
2828 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 1);
2829
2830 auto const aliceSeq = env.seq(alice);
2831 BEAST_EXPECT(env.current()->info().seq == 3);
2832 env(noop(alice),
2833 seq(aliceSeq),
2834 json(R"({"LastLedgerSequence":5})"),
2835 ter(terQUEUED));
2836 env(noop(alice),
2837 seq(aliceSeq + 1),
2838 json(R"({"LastLedgerSequence":5})"),
2839 ter(terQUEUED));
2840 env(noop(alice),
2841 seq(aliceSeq + 2),
2842 json(R"({"LastLedgerSequence":10})"),
2843 ter(terQUEUED));
2844 env(noop(alice),
2845 seq(aliceSeq + 3),
2846 json(R"({"LastLedgerSequence":11})"),
2847 ter(terQUEUED));
2848 checkMetrics(__LINE__, env, 4, std::nullopt, 2, 1);
2849 auto const bobSeq = env.seq(bob);
2850 // Ledger 4 gets 3,
2851 // Ledger 5 gets 4,
2852 // Ledger 6 gets 5.
2853 for (int i = 0; i < 3 + 4 + 5; ++i)
2854 {
2855 env(noop(bob), seq(bobSeq + i), fee(baseFee * 20), ter(terQUEUED));
2856 }
2857 checkMetrics(__LINE__, env, 4 + 3 + 4 + 5, std::nullopt, 2, 1);
2858 // Close ledger 3
2859 env.close();
2860 checkMetrics(__LINE__, env, 4 + 4 + 5, 20, 3, 2);
2861 // Close ledger 4
2862 env.close();
2863 checkMetrics(__LINE__, env, 4 + 5, 30, 4, 3);
2864 // Close ledger 5
2865 env.close();
2866 // Alice's first two txs expired.
2867 checkMetrics(__LINE__, env, 2, 40, 5, 4);
2868
2869 // Because aliceSeq is missing, aliceSeq + 1 fails
2870 env(noop(alice), seq(aliceSeq + 1), ter(terPRE_SEQ));
2871
2872 // Cannot fill the gap with a blocker since Alice's queue is not empty.
2873 env(fset(alice, asfAccountTxnID),
2874 seq(aliceSeq),
2876 checkMetrics(__LINE__, env, 2, 40, 5, 4);
2877
2878 // However we can fill the gap with a non-blocker.
2879 env(noop(alice), seq(aliceSeq), fee(baseFee * 2), ter(terQUEUED));
2880 checkMetrics(__LINE__, env, 3, 40, 5, 4);
2881
2882 // Attempt to queue up a new aliceSeq + 1 tx that's a blocker.
2883 env(fset(alice, asfAccountTxnID),
2884 seq(aliceSeq + 1),
2886 checkMetrics(__LINE__, env, 3, 40, 5, 4);
2887
2888 // Queue up a non-blocker replacement for aliceSeq + 1.
2889 env(noop(alice), seq(aliceSeq + 1), fee(baseFee * 2), ter(terQUEUED));
2890 checkMetrics(__LINE__, env, 4, 40, 5, 4);
2891
2892 // Close ledger 6
2893 env.close();
2894 // We expect that all of alice's queued tx's got into
2895 // the open ledger.
2896 checkMetrics(__LINE__, env, 0, 50, 4, 5);
2897 BEAST_EXPECT(env.seq(alice) == aliceSeq + 4);
2898 }
2899
2900 void
2902 {
2903 // This test focuses on which gaps in queued transactions are
2904 // allowed to be filled even when the account's queue is full.
2905
2906 // NOTE: This test is fragile and dependent on ordering of
2907 // transactions, which is affected by the closed/validated
2908 // ledger hash. This test may need to be edited if changes
2909 // are made that impact the ledger hash.
2910 // TODO: future-proof this test.
2911 using namespace jtx;
2912 testcase("full queue gap handling");
2913
2914 auto cfg = makeConfig(
2915 {{"minimum_txn_in_ledger_standalone", "1"},
2916 {"ledgers_in_queue", "10"},
2917 {"maximum_txn_per_account", "11"}});
2918 cfg->FEES.reference_fee = 10;
2919 Env env(*this, std::move(cfg));
2920
2921 auto const baseFee = env.current()->fees().base.drops();
2922
2923 // Alice will have the gaps. Bob will keep the queue busy with
2924 // high fee transactions so alice's transactions can expire to leave
2925 // gaps.
2926 auto const alice = Account("alice");
2927 auto const bob = Account("bob");
2928
2929 env.fund(XRP(500000), noripple(alice, bob));
2930 checkMetrics(__LINE__, env, 0, std::nullopt, 2, 1);
2931
2932 auto const aliceSeq = env.seq(alice);
2933 BEAST_EXPECT(env.current()->info().seq == 3);
2934
2935 // Start by procuring tickets for alice to use to keep her queue full
2936 // without affecting the sequence gap that will appear later.
2937 env(ticket::create(alice, 11),
2938 seq(aliceSeq + 0),
2939 fee(baseFee * 20 + 1),
2940 ter(terQUEUED));
2941 env(noop(alice),
2942 seq(aliceSeq + 11),
2943 json(R"({"LastLedgerSequence":11})"),
2944 ter(terQUEUED));
2945 env(noop(alice),
2946 seq(aliceSeq + 12),
2947 json(R"({"LastLedgerSequence":11})"),
2948 ter(terQUEUED));
2949 env(noop(alice),
2950 seq(aliceSeq + 13),
2951 json(R"({"LastLedgerSequence":11})"),
2952 ter(terQUEUED));
2953 env(noop(alice),
2954 seq(aliceSeq + 14),
2955 json(R"({"LastLedgerSequence":11})"),
2956 ter(terQUEUED));
2957 env(noop(alice),
2958 seq(aliceSeq + 15),
2959 json(R"({"LastLedgerSequence":11})"),
2960 ter(terQUEUED));
2961 env(noop(alice),
2962 seq(aliceSeq + 16),
2963 json(R"({"LastLedgerSequence": 5})"),
2964 ter(terQUEUED));
2965 env(noop(alice),
2966 seq(aliceSeq + 17),
2967 json(R"({"LastLedgerSequence": 5})"),
2968 ter(terQUEUED));
2969 env(noop(alice),
2970 seq(aliceSeq + 18),
2971 json(R"({"LastLedgerSequence": 5})"),
2972 ter(terQUEUED));
2973 env(noop(alice),
2974 seq(aliceSeq + 19),
2975 json(R"({"LastLedgerSequence":11})"),
2976 ter(terQUEUED));
2977 checkMetrics(__LINE__, env, 10, std::nullopt, 2, 1);
2978
2979 auto const bobSeq = env.seq(bob);
2980 // Ledger 4 gets 2 from bob and 1 from alice,
2981 // Ledger 5 gets 4 from bob,
2982 // Ledger 6 gets 5 from bob.
2983 for (int i = 0; i < 2 + 4 + 5; ++i)
2984 {
2985 env(noop(bob), seq(bobSeq + i), fee(baseFee * 20), ter(terQUEUED));
2986 }
2987 checkMetrics(__LINE__, env, 10 + 2 + 4 + 5, std::nullopt, 2, 1);
2988 // Close ledger 3
2989 env.close();
2990 checkMetrics(__LINE__, env, 9 + 4 + 5, 20, 3, 2);
2991 BEAST_EXPECT(env.seq(alice) == aliceSeq + 12);
2992
2993 // Close ledger 4
2994 env.close();
2995 checkMetrics(__LINE__, env, 9 + 5, 30, 4, 3);
2996 BEAST_EXPECT(env.seq(alice) == aliceSeq + 12);
2997
2998 // Close ledger 5
2999 env.close();
3000 // Three of Alice's txs expired.
3001 checkMetrics(__LINE__, env, 6, 40, 5, 4);
3002 BEAST_EXPECT(env.seq(alice) == aliceSeq + 12);
3003
3004 // Top off Alice's queue again using Tickets so the sequence gap is
3005 // unaffected.
3006 env(noop(alice), ticket::use(aliceSeq + 1), ter(terQUEUED));
3007 env(noop(alice), ticket::use(aliceSeq + 2), ter(terQUEUED));
3008 env(noop(alice), ticket::use(aliceSeq + 3), ter(terQUEUED));
3009 env(noop(alice), ticket::use(aliceSeq + 4), ter(terQUEUED));
3010 env(noop(alice), ticket::use(aliceSeq + 5), ter(terQUEUED));
3011 env(noop(alice), ticket::use(aliceSeq + 6), ter(telCAN_NOT_QUEUE_FULL));
3012 checkMetrics(__LINE__, env, 11, 40, 5, 4);
3013
3014 // Even though alice's queue is full we can still slide in a couple
3015 // more transactions because she has a sequence gap. But we
3016 // can only install a transaction that fills the bottom of the gap.
3017 // Explore that...
3018
3019 // Verify that we can't queue a sequence-based transaction that
3020 // follows a gap.
3021 env(noop(alice), seq(aliceSeq + 20), ter(telCAN_NOT_QUEUE_FULL));
3022
3023 // Verify that the transaction in front of the gap is still present
3024 // by attempting to replace it without a sufficient fee.
3025 env(noop(alice), seq(aliceSeq + 15), ter(telCAN_NOT_QUEUE_FEE));
3026
3027 // We can't queue a transaction into the middle of the gap. It must
3028 // go at the front.
3029 env(noop(alice), seq(aliceSeq + 18), ter(telCAN_NOT_QUEUE_FULL));
3030 env(noop(alice), seq(aliceSeq + 17), ter(telCAN_NOT_QUEUE_FULL));
3031
3032 // Successfully put this transaction into the front of the gap.
3033 env(noop(alice), seq(aliceSeq + 16), ter(terQUEUED));
3034
3035 // Still can't put a sequence-based transaction at the end of the gap.
3036 env(noop(alice), seq(aliceSeq + 18), ter(telCAN_NOT_QUEUE_FULL));
3037
3038 // But we can still fill the gap from the front.
3039 env(noop(alice), seq(aliceSeq + 17), ter(terQUEUED));
3040
3041 // Finally we can fill in the entire gap.
3042 env(noop(alice), seq(aliceSeq + 18), ter(terQUEUED));
3043 checkMetrics(__LINE__, env, 14, 40, 5, 4);
3044
3045 // Verify that nothing can be added now that the gap is filled.
3046 env(noop(alice), seq(aliceSeq + 20), ter(telCAN_NOT_QUEUE_FULL));
3047
3048 // Close ledger 6. That removes some of alice's transactions,
3049 // but alice adds some more transaction(s) so expectedCount
3050 // may not reduce to 8.
3051 env.close();
3052 checkMetrics(__LINE__, env, 9, 50, 6, 5);
3053 BEAST_EXPECT(env.seq(alice) == aliceSeq + 15);
3054
3055 // Close ledger 7. That should remove 4 more of alice's transactions.
3056 env.close();
3057 checkMetrics(__LINE__, env, 2, 60, 7, 6);
3058 BEAST_EXPECT(env.seq(alice) == aliceSeq + 19);
3059
3060 // Close one last ledger to see all of alice's transactions moved
3061 // into the ledger, including the tickets
3062 env.close();
3063 checkMetrics(__LINE__, env, 0, 70, 2, 7);
3064 BEAST_EXPECT(env.seq(alice) == aliceSeq + 21);
3065 }
3066
3067 void
3069 {
3070 testcase("Autofilled sequence should account for TxQ");
3071 using namespace jtx;
3072 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "6"}}));
3073 auto const baseFee = env.current()->fees().base.drops();
3074 Env_ss envs(env);
3075 auto const& txQ = env.app().getTxQ();
3076
3077 auto const alice = Account("alice");
3078 auto const bob = Account("bob");
3079 env.fund(XRP(100000), alice, bob);
3080
3081 fillQueue(env, alice);
3082 checkMetrics(__LINE__, env, 0, std::nullopt, 7, 6);
3083
3084 // Queue up several transactions for alice sign-and-submit
3085 auto const aliceSeq = env.seq(alice);
3086 auto const lastLedgerSeq = env.current()->info().seq + 2;
3087
3089 for (int i = 0; i < 5; ++i)
3090 {
3091 if (i == 2)
3092 envs(
3093 noop(alice),
3094 fee(baseFee * 100),
3095 seq(none),
3096 json(jss::LastLedgerSequence, lastLedgerSeq),
3098 else
3099 envs(
3100 noop(alice), fee(baseFee * 100), seq(none), ter(terQUEUED))(
3101 submitParams);
3102 }
3103 checkMetrics(__LINE__, env, 5, std::nullopt, 7, 6);
3104 {
3105 auto aliceStat = txQ.getAccountTxs(alice.id());
3106 SeqProxy seq = SeqProxy::sequence(aliceSeq);
3107 BEAST_EXPECT(aliceStat.size() == 5);
3108 for (auto const& tx : aliceStat)
3109 {
3110 BEAST_EXPECT(tx.seqProxy == seq);
3112 tx.feeLevel == FeeLevel64{baseFeeLevel.fee() * 100});
3113 if (seq.value() == aliceSeq + 2)
3114 {
3116 tx.lastValid && *tx.lastValid == lastLedgerSeq);
3117 }
3118 else
3119 {
3120 BEAST_EXPECT(!tx.lastValid);
3121 }
3122 seq.advanceBy(1);
3123 }
3124 }
3125 // Put some txs in the queue for bob.
3126 // Give them a higher fee so they'll beat alice's.
3127 for (int i = 0; i < 8; ++i)
3128 envs(noop(bob), fee(baseFee * 200), seq(none), ter(terQUEUED))();
3129 checkMetrics(__LINE__, env, 13, std::nullopt, 7, 6);
3130
3131 env.close();
3132 checkMetrics(__LINE__, env, 5, 14, 8, 7);
3133 // Put some more txs in the queue for bob.
3134 // Give them a higher fee so they'll beat alice's.
3135 fillQueue(env, bob);
3136 for (int i = 0; i < 9; ++i)
3137 envs(noop(bob), fee(baseFee * 200), seq(none), ter(terQUEUED))();
3138 checkMetrics(__LINE__, env, 14, 14, 8, 7, 25601);
3139 env.close();
3140 // Put some more txs in the queue for bob.
3141 // Give them a higher fee so they'll beat alice's.
3142 fillQueue(env, bob);
3143 for (int i = 0; i < 10; ++i)
3144 envs(noop(bob), fee(baseFee * 200), seq(none), ter(terQUEUED))();
3145 checkMetrics(__LINE__, env, 15, 16, 9, 8);
3146 env.close();
3147 checkMetrics(__LINE__, env, 4, 18, 10, 9);
3148 {
3149 // Bob has nothing left in the queue.
3150 auto bobStat = txQ.getAccountTxs(bob.id());
3151 BEAST_EXPECT(bobStat.empty());
3152 }
3153 // Verify alice's tx got dropped as we BEAST_EXPECT, and that there's
3154 // a gap in her queued txs.
3155 {
3156 auto aliceStat = txQ.getAccountTxs(alice.id());
3157 auto seq = aliceSeq;
3158 BEAST_EXPECT(aliceStat.size() == 4);
3159 for (auto const& tx : aliceStat)
3160 {
3161 // Skip over the missing one.
3162 if (seq == aliceSeq + 2)
3163 ++seq;
3164
3165 BEAST_EXPECT(tx.seqProxy.isSeq() && tx.seqProxy.value() == seq);
3167 tx.feeLevel == FeeLevel64{baseFeeLevel.fee() * 100});
3168 BEAST_EXPECT(!tx.lastValid);
3169 ++seq;
3170 }
3171 }
3172 // Now, fill the gap.
3173 envs(noop(alice), fee(baseFee * 100), seq(none), ter(terQUEUED))(
3174 submitParams);
3175 checkMetrics(__LINE__, env, 5, 18, 10, 9);
3176 {
3177 auto aliceStat = txQ.getAccountTxs(alice.id());
3178 auto seq = aliceSeq;
3179 BEAST_EXPECT(aliceStat.size() == 5);
3180 for (auto const& tx : aliceStat)
3181 {
3182 BEAST_EXPECT(tx.seqProxy.isSeq() && tx.seqProxy.value() == seq);
3183 BEAST_EXPECT(tx.feeLevel == FeeLevel64{baseFeeLevel * 100});
3184 BEAST_EXPECT(!tx.lastValid);
3185 ++seq;
3186 }
3187 }
3188
3189 env.close();
3190 checkMetrics(__LINE__, env, 0, 20, 5, 10);
3191 {
3192 // Bob's data has been cleaned up.
3193 auto bobStat = txQ.getAccountTxs(bob.id());
3194 BEAST_EXPECT(bobStat.empty());
3195 }
3196 {
3197 auto aliceStat = txQ.getAccountTxs(alice.id());
3198 BEAST_EXPECT(aliceStat.empty());
3199 }
3200 }
3201
3202 void
3204 {
3205 using namespace jtx;
3206 testcase("account info");
3207
3208 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
3209 auto const baseFee = env.current()->fees().base.drops();
3210 Env_ss envs(env);
3211
3212 Account const alice{"alice"};
3213 env.fund(XRP(1000000), alice);
3214 env.close();
3215
3216 auto const withQueue =
3217 R"({ "account": ")" + alice.human() + R"(", "queue": true })";
3218 auto const withoutQueue = R"({ "account": ")" + alice.human() + R"("})";
3219 auto const prevLedgerWithQueue = R"({ "account": ")" + alice.human() +
3220 R"(", "queue": true, "ledger_index": 3 })";
3221 BEAST_EXPECT(env.current()->info().seq > 3);
3222
3223 {
3224 // account_info without the "queue" argument.
3225 auto const info = env.rpc("json", "account_info", withoutQueue);
3227 info.isMember(jss::result) &&
3228 info[jss::result].isMember(jss::account_data));
3229 BEAST_EXPECT(!info[jss::result].isMember(jss::queue_data));
3230 }
3231 {
3232 // account_info with the "queue" argument.
3233 auto const info = env.rpc("json", "account_info", withQueue);
3235 info.isMember(jss::result) &&
3236 info[jss::result].isMember(jss::account_data));
3237 auto const& result = info[jss::result];
3238 BEAST_EXPECT(result.isMember(jss::queue_data));
3239 auto const& queue_data = result[jss::queue_data];
3241 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3242 BEAST_EXPECT(queue_data[jss::txn_count] == 0);
3243 BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
3244 BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
3245 BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
3246 BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
3247 BEAST_EXPECT(!queue_data.isMember(jss::transactions));
3248 }
3249 checkMetrics(__LINE__, env, 0, 6, 0, 3);
3250
3251 fillQueue(env, alice);
3252 checkMetrics(__LINE__, env, 0, 6, 4, 3);
3253
3254 {
3255 auto const info = env.rpc("json", "account_info", withQueue);
3257 info.isMember(jss::result) &&
3258 info[jss::result].isMember(jss::account_data));
3259 auto const& result = info[jss::result];
3260 BEAST_EXPECT(result.isMember(jss::queue_data));
3261 auto const& queue_data = result[jss::queue_data];
3263 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3264 BEAST_EXPECT(queue_data[jss::txn_count] == 0);
3265 BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
3266 BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
3267 BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
3268 BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
3269 BEAST_EXPECT(!queue_data.isMember(jss::transactions));
3270 }
3271
3273 envs(noop(alice), fee(baseFee * 10), seq(none), ter(terQUEUED))(
3274 submitParams);
3275 envs(noop(alice), fee(baseFee * 10), seq(none), ter(terQUEUED))(
3276 submitParams);
3277 envs(noop(alice), fee(baseFee * 10), seq(none), ter(terQUEUED))(
3278 submitParams);
3279 envs(noop(alice), fee(baseFee * 10), seq(none), ter(terQUEUED))(
3280 submitParams);
3281 checkMetrics(__LINE__, env, 4, 6, 4, 3);
3282
3283 {
3284 auto const info = env.rpc("json", "account_info", withQueue);
3286 info.isMember(jss::result) &&
3287 info[jss::result].isMember(jss::account_data));
3288 auto const& result = info[jss::result];
3289 auto const& data = result[jss::account_data];
3290 BEAST_EXPECT(result.isMember(jss::queue_data));
3291 auto const& queue_data = result[jss::queue_data];
3293 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3294 BEAST_EXPECT(queue_data[jss::txn_count] == 4);
3295 BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
3297 queue_data[jss::lowest_sequence] == data[jss::Sequence]);
3298 BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
3300 queue_data[jss::highest_sequence] ==
3301 data[jss::Sequence].asUInt() +
3302 queue_data[jss::txn_count].asUInt() - 1);
3303 BEAST_EXPECT(queue_data.isMember(jss::auth_change_queued));
3304 BEAST_EXPECT(queue_data[jss::auth_change_queued] == false);
3305 BEAST_EXPECT(queue_data.isMember(jss::max_spend_drops_total));
3307 queue_data[jss::max_spend_drops_total] ==
3308 std::to_string(baseFee * 40));
3309 BEAST_EXPECT(queue_data.isMember(jss::transactions));
3310 auto const& queued = queue_data[jss::transactions];
3311 BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
3312 for (unsigned i = 0; i < queued.size(); ++i)
3313 {
3314 auto const& item = queued[i];
3315 BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
3317 item[jss::fee_level] ==
3319 BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
3320
3321 BEAST_EXPECT(item.isMember(jss::fee));
3322 BEAST_EXPECT(item[jss::fee] == std::to_string(baseFee * 10));
3323 BEAST_EXPECT(item.isMember(jss::max_spend_drops));
3325 item[jss::max_spend_drops] == std::to_string(baseFee * 10));
3326 BEAST_EXPECT(item.isMember(jss::auth_change));
3327 BEAST_EXPECT(item[jss::auth_change].asBool() == false);
3328 }
3329 }
3330
3331 // Drain the queue so we can queue up a blocker.
3332 env.close();
3333 checkMetrics(__LINE__, env, 0, 8, 4, 4);
3334
3335 // Fill the ledger and then queue up a blocker.
3337
3339 fset(alice, asfAccountTxnID),
3340 fee(baseFee * 10),
3341 seq(none),
3342 json(jss::LastLedgerSequence, 10),
3344 checkMetrics(__LINE__, env, 1, 8, 5, 4);
3345
3346 {
3347 auto const info = env.rpc("json", "account_info", withQueue);
3349 info.isMember(jss::result) &&
3350 info[jss::result].isMember(jss::account_data));
3351 auto const& result = info[jss::result];
3352 auto const& data = result[jss::account_data];
3353 BEAST_EXPECT(result.isMember(jss::queue_data));
3354 auto const& queue_data = result[jss::queue_data];
3356 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3357 BEAST_EXPECT(queue_data[jss::txn_count] == 1);
3358 BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
3360 queue_data[jss::lowest_sequence] == data[jss::Sequence]);
3361 BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
3363 queue_data[jss::highest_sequence] ==
3364 data[jss::Sequence].asUInt() +
3365 queue_data[jss::txn_count].asUInt() - 1);
3366 BEAST_EXPECT(queue_data.isMember(jss::auth_change_queued));
3367 BEAST_EXPECT(queue_data[jss::auth_change_queued] == true);
3368 BEAST_EXPECT(queue_data.isMember(jss::max_spend_drops_total));
3370 queue_data[jss::max_spend_drops_total] ==
3371 std::to_string(baseFee * 10));
3372 BEAST_EXPECT(queue_data.isMember(jss::transactions));
3373 auto const& queued = queue_data[jss::transactions];
3374 BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
3375 for (unsigned i = 0; i < queued.size(); ++i)
3376 {
3377 auto const& item = queued[i];
3378 BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
3380 item[jss::fee_level] ==
3382 BEAST_EXPECT(item.isMember(jss::fee));
3383 BEAST_EXPECT(item[jss::fee] == std::to_string(baseFee * 10));
3384 BEAST_EXPECT(item.isMember(jss::max_spend_drops));
3386 item[jss::max_spend_drops] == std::to_string(baseFee * 10));
3387 BEAST_EXPECT(item.isMember(jss::auth_change));
3388
3389 if (i == queued.size() - 1)
3390 {
3391 BEAST_EXPECT(item[jss::auth_change].asBool() == true);
3392 BEAST_EXPECT(item.isMember(jss::LastLedgerSequence));
3393 BEAST_EXPECT(item[jss::LastLedgerSequence] == 10);
3394 }
3395 else
3396 {
3397 BEAST_EXPECT(item[jss::auth_change].asBool() == false);
3398 BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
3399 }
3400 }
3401 }
3402
3404 noop(alice),
3405 fee(baseFee * 10),
3406 seq(none),
3408 checkMetrics(__LINE__, env, 1, 8, 5, 4);
3409
3410 {
3411 auto const info = env.rpc("json", "account_info", withQueue);
3413 info.isMember(jss::result) &&
3414 info[jss::result].isMember(jss::account_data));
3415 auto const& result = info[jss::result];
3416 auto const& data = result[jss::account_data];
3417 BEAST_EXPECT(result.isMember(jss::queue_data));
3418 auto const& queue_data = result[jss::queue_data];
3420 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3421 BEAST_EXPECT(queue_data[jss::txn_count] == 1);
3422 BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence));
3424 queue_data[jss::lowest_sequence] == data[jss::Sequence]);
3425 BEAST_EXPECT(queue_data.isMember(jss::highest_sequence));
3427 queue_data[jss::highest_sequence] ==
3428 data[jss::Sequence].asUInt() +
3429 queue_data[jss::txn_count].asUInt() - 1);
3430 BEAST_EXPECT(queue_data.isMember(jss::auth_change_queued));
3431 BEAST_EXPECT(queue_data[jss::auth_change_queued].asBool());
3432 BEAST_EXPECT(queue_data.isMember(jss::max_spend_drops_total));
3434 queue_data[jss::max_spend_drops_total] ==
3435 std::to_string(baseFee * 10));
3436 BEAST_EXPECT(queue_data.isMember(jss::transactions));
3437 auto const& queued = queue_data[jss::transactions];
3438 BEAST_EXPECT(queued.size() == queue_data[jss::txn_count]);
3439 for (unsigned i = 0; i < queued.size(); ++i)
3440 {
3441 auto const& item = queued[i];
3442 BEAST_EXPECT(item[jss::seq] == data[jss::Sequence].asInt() + i);
3444 item[jss::fee_level] ==
3446
3447 if (i == queued.size() - 1)
3448 {
3449 BEAST_EXPECT(item.isMember(jss::fee));
3451 item[jss::fee] == std::to_string(baseFee * 10));
3452 BEAST_EXPECT(item.isMember(jss::max_spend_drops));
3454 item[jss::max_spend_drops] ==
3455 std::to_string(baseFee * 10));
3456 BEAST_EXPECT(item.isMember(jss::auth_change));
3457 BEAST_EXPECT(item[jss::auth_change].asBool());
3458 BEAST_EXPECT(item.isMember(jss::LastLedgerSequence));
3459 BEAST_EXPECT(item[jss::LastLedgerSequence] == 10);
3460 }
3461 else
3462 {
3463 BEAST_EXPECT(item.isMember(jss::fee));
3465 item[jss::fee] == std::to_string(baseFee * 10));
3466 BEAST_EXPECT(item.isMember(jss::max_spend_drops));
3468 item[jss::max_spend_drops] ==
3469 std::to_string(baseFee * 10));
3470 BEAST_EXPECT(item.isMember(jss::auth_change));
3471 BEAST_EXPECT(!item[jss::auth_change].asBool());
3472 BEAST_EXPECT(!item.isMember(jss::LastLedgerSequence));
3473 }
3474 }
3475 }
3476
3477 {
3478 auto const info =
3479 env.rpc("json", "account_info", prevLedgerWithQueue);
3481 info.isMember(jss::result) &&
3482 RPC::contains_error(info[jss::result]));
3483 }
3484
3485 env.close();
3486 checkMetrics(__LINE__, env, 0, 10, 2, 5);
3487 env.close();
3488 checkMetrics(__LINE__, env, 0, 10, 0, 5);
3489
3490 {
3491 auto const info = env.rpc("json", "account_info", withQueue);
3493 info.isMember(jss::result) &&
3494 info[jss::result].isMember(jss::account_data));
3495 auto const& result = info[jss::result];
3496 BEAST_EXPECT(result.isMember(jss::queue_data));
3497 auto const& queue_data = result[jss::queue_data];
3499 BEAST_EXPECT(queue_data.isMember(jss::txn_count));
3500 BEAST_EXPECT(queue_data[jss::txn_count] == 0);
3501 BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence));
3502 BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence));
3503 BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued));
3504 BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total));
3505 BEAST_EXPECT(!queue_data.isMember(jss::transactions));
3506 }
3507 }
3508
3509 void
3511 {
3512 using namespace jtx;
3513 testcase("server info");
3514
3515 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
3516 auto const baseFee = env.current()->fees().base.drops();
3517 Env_ss envs(env);
3518
3519 Account const alice{"alice"};
3520 env.fund(XRP(1000000), alice);
3521 env.close();
3522
3523 {
3524 auto const server_info = env.rpc("server_info");
3526 server_info.isMember(jss::result) &&
3527 server_info[jss::result].isMember(jss::info));
3528 auto const& info = server_info[jss::result][jss::info];
3530 info.isMember(jss::load_factor) && info[jss::load_factor] == 1);
3531 BEAST_EXPECT(!info.isMember(jss::load_factor_server));
3532 BEAST_EXPECT(!info.isMember(jss::load_factor_local));
3533 BEAST_EXPECT(!info.isMember(jss::load_factor_net));
3534 BEAST_EXPECT(!info.isMember(jss::load_factor_fee_escalation));
3535 }
3536 {
3537 auto const server_state = env.rpc("server_state");
3538 auto const& state = server_state[jss::result][jss::state];
3540 state.isMember(jss::load_factor) &&
3541 state[jss::load_factor] == 256);
3543 state.isMember(jss::load_base) && state[jss::load_base] == 256);
3545 state.isMember(jss::load_factor_server) &&
3546 state[jss::load_factor_server] == 256);
3548 state.isMember(jss::load_factor_fee_escalation) &&
3549 state[jss::load_factor_fee_escalation] == 256);
3551 state.isMember(jss::load_factor_fee_queue) &&
3552 state[jss::load_factor_fee_queue] == 256);
3554 state.isMember(jss::load_factor_fee_reference) &&
3555 state[jss::load_factor_fee_reference] == 256);
3556 }
3557
3558 checkMetrics(__LINE__, env, 0, 6, 0, 3);
3559
3560 fillQueue(env, alice);
3561 checkMetrics(__LINE__, env, 0, 6, 4, 3);
3562
3563 auto aliceSeq = env.seq(alice);
3565 for (auto i = 0; i < 4; ++i)
3566 envs(
3567 noop(alice),
3568 fee(baseFee * 10),
3569 seq(aliceSeq + i),
3571 checkMetrics(__LINE__, env, 4, 6, 4, 3);
3572
3573 {
3574 auto const server_info = env.rpc("server_info");
3576 server_info.isMember(jss::result) &&
3577 server_info[jss::result].isMember(jss::info));
3578 auto const& info = server_info[jss::result][jss::info];
3579 // Avoid double rounding issues by comparing to a range.
3581 info.isMember(jss::load_factor) &&
3582 info[jss::load_factor] > 888.88 &&
3583 info[jss::load_factor] < 888.89);
3585 info.isMember(jss::load_factor_server) &&
3586 info[jss::load_factor_server] == 1);
3587 BEAST_EXPECT(!info.isMember(jss::load_factor_local));
3588 BEAST_EXPECT(!info.isMember(jss::load_factor_net));
3590 info.isMember(jss::load_factor_fee_escalation) &&
3591 info[jss::load_factor_fee_escalation] > 888.88 &&
3592 info[jss::load_factor_fee_escalation] < 888.89);
3593 }
3594 {
3595 auto const server_state = env.rpc("server_state");
3596 auto const& state = server_state[jss::result][jss::state];
3598 state.isMember(jss::load_factor) &&
3599 state[jss::load_factor] == 227555);
3601 state.isMember(jss::load_base) && state[jss::load_base] == 256);
3603 state.isMember(jss::load_factor_server) &&
3604 state[jss::load_factor_server] == 256);
3606 state.isMember(jss::load_factor_fee_escalation) &&
3607 state[jss::load_factor_fee_escalation] == 227555);
3609 state.isMember(jss::load_factor_fee_queue) &&
3610 state[jss::load_factor_fee_queue] == 256);
3612 state.isMember(jss::load_factor_fee_reference) &&
3613 state[jss::load_factor_fee_reference] == 256);
3614 }
3615
3616 env.app().getFeeTrack().setRemoteFee(256000);
3617
3618 {
3619 auto const server_info = env.rpc("server_info");
3621 server_info.isMember(jss::result) &&
3622 server_info[jss::result].isMember(jss::info));
3623 auto const& info = server_info[jss::result][jss::info];
3624 // Avoid double rounding issues by comparing to a range.
3626 info.isMember(jss::load_factor) &&
3627 info[jss::load_factor] == 1000);
3628 BEAST_EXPECT(!info.isMember(jss::load_factor_server));
3629 BEAST_EXPECT(!info.isMember(jss::load_factor_local));
3631 info.isMember(jss::load_factor_net) &&
3632 info[jss::load_factor_net] == 1000);
3634 info.isMember(jss::load_factor_fee_escalation) &&
3635 info[jss::load_factor_fee_escalation] > 888.88 &&
3636 info[jss::load_factor_fee_escalation] < 888.89);
3637 }
3638 {
3639 auto const server_state = env.rpc("server_state");
3640 auto const& state = server_state[jss::result][jss::state];
3642 state.isMember(jss::load_factor) &&
3643 state[jss::load_factor] == 256000);
3645 state.isMember(jss::load_base) && state[jss::load_base] == 256);
3647 state.isMember(jss::load_factor_server) &&
3648 state[jss::load_factor_server] == 256000);
3650 state.isMember(jss::load_factor_fee_escalation) &&
3651 state[jss::load_factor_fee_escalation] == 227555);
3653 state.isMember(jss::load_factor_fee_queue) &&
3654 state[jss::load_factor_fee_queue] == 256);
3656 state.isMember(jss::load_factor_fee_reference) &&
3657 state[jss::load_factor_fee_reference] == 256);
3658 }
3659
3660 env.app().getFeeTrack().setRemoteFee(256);
3661
3662 // Increase the server load
3663 for (int i = 0; i < 5; ++i)
3664 env.app().getFeeTrack().raiseLocalFee();
3665 BEAST_EXPECT(env.app().getFeeTrack().getLoadFactor() == 625);
3666
3667 {
3668 auto const server_info = env.rpc("server_info");
3670 server_info.isMember(jss::result) &&
3671 server_info[jss::result].isMember(jss::info));
3672 auto const& info = server_info[jss::result][jss::info];
3673 // Avoid double rounding issues by comparing to a range.
3675 info.isMember(jss::load_factor) &&
3676 info[jss::load_factor] > 888.88 &&
3677 info[jss::load_factor] < 888.89);
3678 // There can be a race between LoadManager lowering the fee,
3679 // and the call to server_info, so check a wide range.
3680 // The important thing is that it's not 1.
3682 info.isMember(jss::load_factor_server) &&
3683 info[jss::load_factor_server] > 1.245 &&
3684 info[jss::load_factor_server] < 2.4415);
3686 info.isMember(jss::load_factor_local) &&
3687 info[jss::load_factor_local] > 1.245 &&
3688 info[jss::load_factor_local] < 2.4415);
3689 BEAST_EXPECT(!info.isMember(jss::load_factor_net));
3691 info.isMember(jss::load_factor_fee_escalation) &&
3692 info[jss::load_factor_fee_escalation] > 888.88 &&
3693 info[jss::load_factor_fee_escalation] < 888.89);
3694 }
3695 {
3696 auto const server_state = env.rpc("server_state");
3697 auto const& state = server_state[jss::result][jss::state];
3699 state.isMember(jss::load_factor) &&
3700 state[jss::load_factor] == 227555);
3702 state.isMember(jss::load_base) && state[jss::load_base] == 256);
3703 // There can be a race between LoadManager lowering the fee,
3704 // and the call to server_info, so check a wide range.
3705 // The important thing is that it's not 256.
3707 state.isMember(jss::load_factor_server) &&
3708 state[jss::load_factor_server] >= 320 &&
3709 state[jss::load_factor_server] <= 625);
3711 state.isMember(jss::load_factor_fee_escalation) &&
3712 state[jss::load_factor_fee_escalation] == 227555);
3714 state.isMember(jss::load_factor_fee_queue) &&
3715 state[jss::load_factor_fee_queue] == 256);
3717 state.isMember(jss::load_factor_fee_reference) &&
3718 state[jss::load_factor_fee_reference] == 256);
3719 }
3720
3721 env.close();
3722
3723 {
3724 auto const server_info = env.rpc("server_info");
3726 server_info.isMember(jss::result) &&
3727 server_info[jss::result].isMember(jss::info));
3728 auto const& info = server_info[jss::result][jss::info];
3729 // Avoid double rounding issues by comparing to a range.
3730
3731 // There can be a race between LoadManager lowering the fee,
3732 // and the call to server_info, so check a wide range.
3733 // The important thing is that it's not 1.
3735 info.isMember(jss::load_factor) &&
3736 info[jss::load_factor] > 1.245 &&
3737 info[jss::load_factor] < 2.4415);
3738 BEAST_EXPECT(!info.isMember(jss::load_factor_server));
3740 info.isMember(jss::load_factor_local) &&
3741 info[jss::load_factor_local] > 1.245 &&
3742 info[jss::load_factor_local] < 2.4415);
3743 BEAST_EXPECT(!info.isMember(jss::load_factor_net));
3744 BEAST_EXPECT(!info.isMember(jss::load_factor_fee_escalation));
3745 }
3746 {
3747 auto const server_state = env.rpc("server_state");
3748 auto const& state = server_state[jss::result][jss::state];
3750 state.isMember(jss::load_factor) &&
3751 state[jss::load_factor] >= 320 &&
3752 state[jss::load_factor] <= 625);
3754 state.isMember(jss::load_base) && state[jss::load_base] == 256);
3755 // There can be a race between LoadManager lowering the fee,
3756 // and the call to server_info, so check a wide range.
3757 // The important thing is that it's not 256.
3759 state.isMember(jss::load_factor_server) &&
3760 state[jss::load_factor_server] >= 320 &&
3761 state[jss::load_factor_server] <= 625);
3763 state.isMember(jss::load_factor_fee_escalation) &&
3764 state[jss::load_factor_fee_escalation] == 256);
3766 state.isMember(jss::load_factor_fee_queue) &&
3767 state[jss::load_factor_fee_queue] == 256);
3769 state.isMember(jss::load_factor_fee_reference) &&
3770 state[jss::load_factor_fee_reference] == 256);
3771 }
3772 }
3773
3774 void
3776 {
3777 using namespace jtx;
3778 testcase("server subscribe");
3779
3780 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
3781 auto const baseFee = env.current()->fees().base.drops();
3782
3783 Json::Value stream;
3784 stream[jss::streams] = Json::arrayValue;
3785 stream[jss::streams].append("server");
3786 auto wsc = makeWSClient(env.app().config());
3787 {
3788 auto jv = wsc->invoke("subscribe", stream);
3789 BEAST_EXPECT(jv[jss::status] == "success");
3790 }
3791
3792 Account a{"a"}, b{"b"}, c{"c"}, d{"d"}, e{"e"}, f{"f"}, g{"g"}, h{"h"},
3793 i{"i"};
3794
3795 // Fund the first few accounts at non escalated fee
3796 env.fund(XRP(50000), noripple(a, b, c, d));
3797 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
3798
3799 // First transaction establishes the messaging
3800 using namespace std::chrono_literals;
3801 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3802 return jv[jss::type] == "serverStatus" &&
3803 jv.isMember(jss::load_factor) && jv[jss::load_factor] == 256 &&
3804 jv.isMember(jss::load_base) && jv[jss::load_base] == 256 &&
3805 jv.isMember(jss::load_factor_server) &&
3806 jv[jss::load_factor_server] == 256 &&
3807 jv.isMember(jss::load_factor_fee_escalation) &&
3808 jv[jss::load_factor_fee_escalation] == 256 &&
3809 jv.isMember(jss::load_factor_fee_queue) &&
3810 jv[jss::load_factor_fee_queue] == 256 &&
3811 jv.isMember(jss::load_factor_fee_reference) &&
3812 jv[jss::load_factor_fee_reference] == 256;
3813 }));
3814 // Last transaction escalates the fee
3815 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3816 return jv[jss::type] == "serverStatus" &&
3817 jv.isMember(jss::load_factor) &&
3818 jv[jss::load_factor] == 227555 && jv.isMember(jss::load_base) &&
3819 jv[jss::load_base] == 256 &&
3820 jv.isMember(jss::load_factor_server) &&
3821 jv[jss::load_factor_server] == 256 &&
3822 jv.isMember(jss::load_factor_fee_escalation) &&
3823 jv[jss::load_factor_fee_escalation] == 227555 &&
3824 jv.isMember(jss::load_factor_fee_queue) &&
3825 jv[jss::load_factor_fee_queue] == 256 &&
3826 jv.isMember(jss::load_factor_fee_reference) &&
3827 jv[jss::load_factor_fee_reference] == 256;
3828 }));
3829
3830 env.close();
3831
3832 // Closing ledger should publish a status update
3833 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3834 return jv[jss::type] == "serverStatus" &&
3835 jv.isMember(jss::load_factor) && jv[jss::load_factor] == 256 &&
3836 jv.isMember(jss::load_base) && jv[jss::load_base] == 256 &&
3837 jv.isMember(jss::load_factor_server) &&
3838 jv[jss::load_factor_server] == 256 &&
3839 jv.isMember(jss::load_factor_fee_escalation) &&
3840 jv[jss::load_factor_fee_escalation] == 256 &&
3841 jv.isMember(jss::load_factor_fee_queue) &&
3842 jv[jss::load_factor_fee_queue] == 256 &&
3843 jv.isMember(jss::load_factor_fee_reference) &&
3844 jv[jss::load_factor_fee_reference] == 256;
3845 }));
3846
3847 checkMetrics(__LINE__, env, 0, 8, 0, 4);
3848
3849 // Fund then next few accounts at non escalated fee
3850 env.fund(XRP(50000), noripple(e, f, g, h, i));
3851
3852 // Extra transactions with low fee are queued
3853 auto queued = ter(terQUEUED);
3854 env(noop(a), fee(baseFee), queued);
3855 env(noop(b), fee(baseFee), queued);
3856 env(noop(c), fee(baseFee), queued);
3857 env(noop(d), fee(baseFee), queued);
3858 env(noop(e), fee(baseFee), queued);
3859 env(noop(f), fee(baseFee), queued);
3860 env(noop(g), fee(baseFee), queued);
3861 checkMetrics(__LINE__, env, 7, 8, 5, 4);
3862
3863 // Last transaction escalates the fee
3864 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3865 return jv[jss::type] == "serverStatus" &&
3866 jv.isMember(jss::load_factor) &&
3867 jv[jss::load_factor] == 200000 && jv.isMember(jss::load_base) &&
3868 jv[jss::load_base] == 256 &&
3869 jv.isMember(jss::load_factor_server) &&
3870 jv[jss::load_factor_server] == 256 &&
3871 jv.isMember(jss::load_factor_fee_escalation) &&
3872 jv[jss::load_factor_fee_escalation] == 200000 &&
3873 jv.isMember(jss::load_factor_fee_queue) &&
3874 jv[jss::load_factor_fee_queue] == 256 &&
3875 jv.isMember(jss::load_factor_fee_reference) &&
3876 jv[jss::load_factor_fee_reference] == 256;
3877 }));
3878
3879 env.close();
3880 // Ledger close publishes with escalated fees for queued transactions
3881 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3882 return jv[jss::type] == "serverStatus" &&
3883 jv.isMember(jss::load_factor) &&
3884 jv[jss::load_factor] == 184320 && jv.isMember(jss::load_base) &&
3885 jv[jss::load_base] == 256 &&
3886 jv.isMember(jss::load_factor_server) &&
3887 jv[jss::load_factor_server] == 256 &&
3888 jv.isMember(jss::load_factor_fee_escalation) &&
3889 jv[jss::load_factor_fee_escalation] == 184320 &&
3890 jv.isMember(jss::load_factor_fee_queue) &&
3891 jv[jss::load_factor_fee_queue] == 256 &&
3892 jv.isMember(jss::load_factor_fee_reference) &&
3893 jv[jss::load_factor_fee_reference] == 256;
3894 }));
3895
3896 env.close();
3897 // ledger close clears queue so fee is back to normal
3898 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
3899 return jv[jss::type] == "serverStatus" &&
3900 jv.isMember(jss::load_factor) && jv[jss::load_factor] == 256 &&
3901 jv.isMember(jss::load_base) && jv[jss::load_base] == 256 &&
3902 jv.isMember(jss::load_factor_server) &&
3903 jv[jss::load_factor_server] == 256 &&
3904 jv.isMember(jss::load_factor_fee_escalation) &&
3905 jv[jss::load_factor_fee_escalation] == 256 &&
3906 jv.isMember(jss::load_factor_fee_queue) &&
3907 jv[jss::load_factor_fee_queue] == 256 &&
3908 jv.isMember(jss::load_factor_fee_reference) &&
3909 jv[jss::load_factor_fee_reference] == 256;
3910 }));
3911
3912 BEAST_EXPECT(!wsc->findMsg(1s, [&](auto const& jv) {
3913 return jv[jss::type] == "serverStatus";
3914 }));
3915
3916 auto jv = wsc->invoke("unsubscribe", stream);
3917 BEAST_EXPECT(jv[jss::status] == "success");
3918 }
3919
3920 void
3922 {
3923 using namespace jtx;
3924 testcase("clear queued acct txs");
3925
3926 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
3927 auto const baseFee = env.current()->fees().base.drops();
3928 auto alice = Account("alice");
3929 auto bob = Account("bob");
3930
3931 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
3932 env.fund(XRP(50000000), alice, bob);
3933
3934 fillQueue(env, alice);
3935
3936 auto calcTotalFee = [&](std::int64_t alreadyPaid,
3937 std::optional<std::size_t> numToClear =
3938 std::nullopt) -> std::uint64_t {
3939 auto totalFactor = 0;
3940 auto const metrics = env.app().getTxQ().getMetrics(*env.current());
3941 if (!numToClear)
3942 numToClear.emplace(metrics.txCount + 1);
3943 for (int i = 0; i < *numToClear; ++i)
3944 {
3945 auto inLedger = metrics.txInLedger + i;
3946 totalFactor += inLedger * inLedger;
3947 }
3948
3949 auto const den = (metrics.txPerLedger * metrics.txPerLedger);
3950 FeeLevel64 feeLevel =
3951 (metrics.medFeeLevel * totalFactor + FeeLevel64{den - 1}) / den;
3952
3953 auto result = toDrops(feeLevel, env.current()->fees().base).drops();
3954
3955 // Subtract the fees already paid
3956 result -= alreadyPaid;
3957 // round up
3958 ++result;
3959 return result;
3960 };
3961
3962 testcase("straightfoward positive case");
3963 {
3964 // Queue up some transactions at a too-low fee.
3965 auto aliceSeq = env.seq(alice);
3966 std::uint64_t totalPaid = 0;
3967 for (int i = 0; i < 2; ++i)
3968 {
3969 env(noop(alice),
3970 fee(baseFee * 10),
3971 seq(aliceSeq++),
3972 ter(terQUEUED));
3973 totalPaid += baseFee * 10;
3974 }
3975
3976 // Queue up a transaction paying the open ledger fee
3977 // This will be the first tx to call the operative function,
3978 // but it won't succeed.
3979 totalPaid += openLedgerCost(env).drops();
3980 env(noop(alice),
3981 fee(openLedgerCost(env)),
3982 seq(aliceSeq++),
3983 ter(terQUEUED));
3984
3985 checkMetrics(__LINE__, env, 3, std::nullopt, 4, 3);
3986
3987 // Figure out how much it would cost to cover all the
3988 // queued txs + itself
3989 std::uint64_t totalFee = calcTotalFee(totalPaid);
3990 --totalFee;
3991
3992 // Submit a transaction with that fee. It will get queued
3993 // because the fee level calculation rounds down. This is
3994 // the edge case test.
3995 env(noop(alice), fee(totalFee), seq(aliceSeq++), ter(terQUEUED));
3996
3997 checkMetrics(__LINE__, env, 4, std::nullopt, 4, 3);
3998
3999 // Now repeat the process including the new tx
4000 // and avoiding the rounding error
4001 totalPaid += totalFee;
4002 totalFee = calcTotalFee(totalPaid);
4003
4004 // Submit a transaction with that fee. It will succeed.
4005 env(noop(alice), fee(totalFee), seq(aliceSeq++));
4006
4007 checkMetrics(__LINE__, env, 0, std::nullopt, 9, 3);
4008 }
4009
4010 testcase("replace last tx with enough to clear queue");
4011 {
4012 // Queue up some transactions at a too-low fee.
4013 auto aliceSeq = env.seq(alice);
4014 uint64_t totalPaid = 0;
4015 for (int i = 0; i < 2; ++i)
4016 {
4017 env(noop(alice),
4018 fee(baseFee * 10),
4019 seq(aliceSeq++),
4020 ter(terQUEUED));
4021 totalPaid += baseFee * 10;
4022 }
4023
4024 // Queue up a transaction paying the open ledger fee
4025 // This will be the first tx to call the operative function,
4026 // but it won't succeed.
4027 env(noop(alice),
4028 fee(openLedgerCost(env)),
4029 seq(aliceSeq++),
4030 ter(terQUEUED));
4031
4032 checkMetrics(__LINE__, env, 3, std::nullopt, 9, 3);
4033
4034 // Figure out how much it would cost to cover all the
4035 // queued txs + itself
4036 auto const metrics = env.app().getTxQ().getMetrics(*env.current());
4037 std::uint64_t const totalFee =
4038 calcTotalFee(totalPaid, metrics.txCount);
4039 // Replacing the last tx with the large fee succeeds.
4040 --aliceSeq;
4041 env(noop(alice), fee(totalFee), seq(aliceSeq++));
4042
4043 // The queue is clear
4044 checkMetrics(__LINE__, env, 0, std::nullopt, 12, 3);
4045
4046 env.close();
4047 checkMetrics(__LINE__, env, 0, 24, 0, 12);
4048 }
4049
4050 testcase("replace middle tx with enough to clear queue");
4051 {
4052 fillQueue(env, alice);
4053 // Queue up some transactions at a too-low fee.
4054 auto aliceSeq = env.seq(alice);
4055 for (int i = 0; i < 5; ++i)
4056 {
4057 env(noop(alice),
4058 fee(baseFee * 10),
4059 seq(aliceSeq++),
4060 ter(terQUEUED));
4061 }
4062
4063 checkMetrics(__LINE__, env, 5, 24, 13, 12);
4064
4065 // Figure out how much it would cost to cover 3 txns
4066 uint64_t const totalFee = calcTotalFee(baseFee * 10 * 2, 3);
4067 // Replacing the last tx with the large fee succeeds.
4068 aliceSeq -= 3;
4069 env(noop(alice), fee(totalFee), seq(aliceSeq++));
4070
4071 checkMetrics(__LINE__, env, 2, 24, 16, 12);
4072 auto const aliceQueue =
4073 env.app().getTxQ().getAccountTxs(alice.id());
4074 BEAST_EXPECT(aliceQueue.size() == 2);
4075 SeqProxy seq = SeqProxy::sequence(aliceSeq);
4076 for (auto const& tx : aliceQueue)
4077 {
4078 BEAST_EXPECT(tx.seqProxy == seq);
4080 tx.feeLevel == FeeLevel64{baseFeeLevel.fee() * 10});
4081 seq.advanceBy(1);
4082 }
4083
4084 // Close the ledger to clear the queue
4085 env.close();
4086 checkMetrics(__LINE__, env, 0, 32, 2, 16);
4087 }
4088
4089 testcase("clear queue failure (load)");
4090 {
4091 fillQueue(env, alice);
4092 // Queue up some transactions at a too-low fee.
4093 auto aliceSeq = env.seq(alice);
4094 uint64_t totalPaid = 0;
4095 for (int i = 0; i < 2; ++i)
4096 {
4097 env(noop(alice),
4098 fee(baseFee * 20),
4099 seq(aliceSeq++),
4100 ter(terQUEUED));
4101 totalPaid += baseFee * 20;
4102 }
4103 for (int i = 0; i < 2; ++i)
4104 {
4105 env(noop(alice),
4106 fee(baseFee * 2.2),
4107 seq(aliceSeq++),
4108 ter(terQUEUED));
4109 totalPaid += baseFee * 2.2;
4110 }
4111
4112 checkMetrics(__LINE__, env, 4, 32, 17, 16);
4113
4114 // Figure out how much it would cost to cover all the txns
4115 // + 1
4116 std::uint64_t const totalFee = calcTotalFee(totalPaid);
4117 // This fee should be enough, but oh no! Server load went up!
4118 auto& feeTrack = env.app().getFeeTrack();
4119 auto const origFee = feeTrack.getRemoteFee();
4120 feeTrack.setRemoteFee(origFee * 5);
4121 // Instead the tx gets queued, and all of the queued
4122 // txs stay in the queue.
4123 env(noop(alice), fee(totalFee), seq(aliceSeq++), ter(terQUEUED));
4124
4125 // The original last transaction is still in the queue
4126 checkMetrics(__LINE__, env, 5, 32, 17, 16);
4127
4128 // With high load, some of the txs stay in the queue
4129 env.close();
4130 checkMetrics(__LINE__, env, 3, 34, 2, 17);
4131
4132 // Load drops back down
4133 feeTrack.setRemoteFee(origFee);
4134
4135 // Because of the earlier failure, alice can not clear the queue,
4136 // no matter how high the fee
4137 fillQueue(env, bob);
4138 checkMetrics(__LINE__, env, 3, 34, 18, 17);
4139
4140 env(noop(alice), fee(XRP(1)), seq(aliceSeq++), ter(terQUEUED));
4141 checkMetrics(__LINE__, env, 4, 34, 18, 17);
4142
4143 // With normal load, those txs get into the ledger
4144 env.close();
4145 checkMetrics(__LINE__, env, 0, 36, 4, 18);
4146 }
4147 }
4148
4149 void
4151 {
4152 using namespace jtx;
4153 using namespace std::chrono_literals;
4154 testcase("scaling");
4155
4156 {
4157 Env env(
4158 *this,
4159 makeConfig(
4160 {{"minimum_txn_in_ledger_standalone", "3"},
4161 {"normal_consensus_increase_percent", "25"},
4162 {"slow_consensus_decrease_percent", "50"},
4163 {"target_txn_in_ledger", "10"},
4164 {"maximum_txn_per_account", "200"}}));
4165 auto alice = Account("alice");
4166
4167 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
4168 env.fund(XRP(50000000), alice);
4169
4170 fillQueue(env, alice);
4171 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
4172 auto seqAlice = env.seq(alice);
4173 auto txCount = 140;
4174 for (int i = 0; i < txCount; ++i)
4175 env(noop(alice), seq(seqAlice++), ter(terQUEUED));
4176 checkMetrics(__LINE__, env, txCount, std::nullopt, 4, 3);
4177
4178 // Close a few ledgers successfully, so the limit grows
4179
4180 env.close();
4181 // 4 + 25% = 5
4182 txCount -= 6;
4183 checkMetrics(__LINE__, env, txCount, 10, 6, 5, 257);
4184
4185 env.close();
4186 // 6 + 25% = 7
4187 txCount -= 8;
4188 checkMetrics(__LINE__, env, txCount, 14, 8, 7, 257);
4189
4190 env.close();
4191 // 8 + 25% = 10
4192 txCount -= 11;
4193 checkMetrics(__LINE__, env, txCount, 20, 11, 10, 257);
4194
4195 env.close();
4196 // 11 + 25% = 13
4197 txCount -= 14;
4198 checkMetrics(__LINE__, env, txCount, 26, 14, 13, 257);
4199
4200 env.close();
4201 // 14 + 25% = 17
4202 txCount -= 18;
4203 checkMetrics(__LINE__, env, txCount, 34, 18, 17, 257);
4204
4205 env.close();
4206 // 18 + 25% = 22
4207 txCount -= 23;
4208 checkMetrics(__LINE__, env, txCount, 44, 23, 22, 257);
4209
4210 env.close();
4211 // 23 + 25% = 28
4212 txCount -= 29;
4213 checkMetrics(__LINE__, env, txCount, 56, 29, 28);
4214
4215 // From 3 expected to 28 in 7 "fast" ledgers.
4216
4217 // Close the ledger with a delay.
4218 env.close(env.now() + 5s, 10000ms);
4219 txCount -= 15;
4220 checkMetrics(__LINE__, env, txCount, 56, 15, 14);
4221
4222 // Close the ledger with a delay.
4223 env.close(env.now() + 5s, 10000ms);
4224 txCount -= 8;
4225 checkMetrics(__LINE__, env, txCount, 56, 8, 7);
4226
4227 // Close the ledger with a delay.
4228 env.close(env.now() + 5s, 10000ms);
4229 txCount -= 4;
4230 checkMetrics(__LINE__, env, txCount, 56, 4, 3);
4231
4232 // From 28 expected back down to 3 in 3 "slow" ledgers.
4233
4234 // Confirm the minimum sticks
4235 env.close(env.now() + 5s, 10000ms);
4236 txCount -= 4;
4237 checkMetrics(__LINE__, env, txCount, 56, 4, 3);
4238
4239 BEAST_EXPECT(!txCount);
4240 }
4241
4242 {
4243 Env env(
4244 *this,
4245 makeConfig(
4246 {{"minimum_txn_in_ledger_standalone", "3"},
4247 {"normal_consensus_increase_percent", "150"},
4248 {"slow_consensus_decrease_percent", "150"},
4249 {"target_txn_in_ledger", "10"},
4250 {"maximum_txn_per_account", "200"}}));
4251 auto alice = Account("alice");
4252
4253 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
4254 env.fund(XRP(50000000), alice);
4255
4256 fillQueue(env, alice);
4257 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
4258 auto seqAlice = env.seq(alice);
4259 auto txCount = 43;
4260 for (int i = 0; i < txCount; ++i)
4261 env(noop(alice), seq(seqAlice++), ter(terQUEUED));
4262 checkMetrics(__LINE__, env, txCount, std::nullopt, 4, 3);
4263
4264 // Close a few ledgers successfully, so the limit grows
4265
4266 env.close();
4267 // 4 + 150% = 10
4268 txCount -= 11;
4269 checkMetrics(__LINE__, env, txCount, 20, 11, 10, 257);
4270
4271 env.close();
4272 // 11 + 150% = 27
4273 txCount -= 28;
4274 checkMetrics(__LINE__, env, txCount, 54, 28, 27);
4275
4276 // From 3 expected to 28 in 7 "fast" ledgers.
4277
4278 // Close the ledger with a delay.
4279 env.close(env.now() + 5s, 10000ms);
4280 txCount -= 4;
4281 checkMetrics(__LINE__, env, txCount, 54, 4, 3);
4282
4283 // From 28 expected back down to 3 in 3 "slow" ledgers.
4284
4285 BEAST_EXPECT(!txCount);
4286 }
4287 }
4288
4289 void
4291 {
4292 // Test the situation where a transaction with an account and
4293 // sequence that's in the queue also appears in the ledger.
4294 //
4295 // Normally this situation can only happen on a network
4296 // when a transaction gets validated by most of the network,
4297 // but one or more nodes have that transaction (or a different
4298 // transaction with the same sequence) queued. And, yes, this
4299 // situation has been observed (rarely) in the wild.
4300 testcase("Sequence in queue and open ledger");
4301 using namespace jtx;
4302
4303 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
4304
4305 auto const alice = Account("alice");
4306
4307 auto const queued = ter(terQUEUED);
4308
4309 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
4310
4311 // Create account
4312 env.fund(XRP(50000), noripple(alice));
4313 checkMetrics(__LINE__, env, 0, std::nullopt, 1, 3);
4314
4315 fillQueue(env, alice);
4316 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
4317
4318 // Queue a transaction
4319 auto const aliceSeq = env.seq(alice);
4320 env(noop(alice), queued);
4321 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 3);
4322
4323 // Now, apply a (different) transaction directly
4324 // to the open ledger, bypassing the queue
4325 // (This requires calling directly into the open ledger,
4326 // which won't work if unit tests are separated to only
4327 // be callable via RPC.)
4328 env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
4329 auto const tx =
4330 env.jt(noop(alice), seq(aliceSeq), fee(openLedgerCost(env)));
4331 auto const result =
4332 ripple::apply(env.app(), view, *tx.stx, tapUNLIMITED, j);
4333 BEAST_EXPECT(result.ter == tesSUCCESS && result.applied);
4334 return result.applied;
4335 });
4336 // the queued transaction is still there
4337 checkMetrics(__LINE__, env, 1, std::nullopt, 5, 3);
4338
4339 // The next transaction should be able to go into the open
4340 // ledger, even though aliceSeq is queued. In earlier incarnations
4341 // of the TxQ this would cause an assert.
4342 env(noop(alice), seq(aliceSeq + 1), fee(openLedgerCost(env)));
4343 checkMetrics(__LINE__, env, 1, std::nullopt, 6, 3);
4344 // Now queue a couple more transactions to make sure
4345 // they succeed despite aliceSeq being queued
4346 env(noop(alice), seq(aliceSeq + 2), queued);
4347 env(noop(alice), seq(aliceSeq + 3), queued);
4348 checkMetrics(__LINE__, env, 3, std::nullopt, 6, 3);
4349
4350 // Now close the ledger. One of the queued transactions
4351 // (aliceSeq) should be dropped.
4352 env.close();
4353 checkMetrics(__LINE__, env, 0, 12, 2, 6);
4354 }
4355
4356 void
4358 {
4359 // Test the situation where a transaction with an account and
4360 // ticket that's in the queue also appears in the ledger.
4361 //
4362 // Although this situation has not (yet) been observed in the wild,
4363 // it is a direct analogy to the previous sequence based test. So
4364 // there is no reason to not expect to see it in the wild.
4365 testcase("Ticket in queue and open ledger");
4366 using namespace jtx;
4367
4368 Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}}));
4369
4370 auto alice = Account("alice");
4371
4372 auto queued = ter(terQUEUED);
4373
4374 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
4375
4376 // Create account
4377 env.fund(XRP(50000), noripple(alice));
4378 checkMetrics(__LINE__, env, 0, std::nullopt, 1, 3);
4379
4380 // Create tickets
4381 std::uint32_t const tktSeq0{env.seq(alice) + 1};
4382 env(ticket::create(alice, 4));
4383
4384 // Fill the queue so the next transaction will be queued.
4385 fillQueue(env, alice);
4386 checkMetrics(__LINE__, env, 0, std::nullopt, 4, 3);
4387
4388 // Queue a transaction with a ticket. Leave an unused ticket
4389 // on either side.
4390 env(noop(alice), ticket::use(tktSeq0 + 1), queued);
4391 checkMetrics(__LINE__, env, 1, std::nullopt, 4, 3);
4392
4393 // Now, apply a (different) transaction directly
4394 // to the open ledger, bypassing the queue
4395 // (This requires calling directly into the open ledger,
4396 // which won't work if unit tests are separated to only
4397 // be callable via RPC.)
4398 env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
4399 auto const tx = env.jt(
4400 noop(alice),
4401 ticket::use(tktSeq0 + 1),
4402 fee(openLedgerCost(env)));
4403 auto const result =
4404 ripple::apply(env.app(), view, *tx.stx, tapUNLIMITED, j);
4405 BEAST_EXPECT(result.ter == tesSUCCESS && result.applied);
4406 return result.applied;
4407 });
4408 // the queued transaction is still there
4409 checkMetrics(__LINE__, env, 1, std::nullopt, 5, 3);
4410
4411 // The next (sequence-based) transaction should be able to go into
4412 // the open ledger, even though tktSeq0 is queued. Note that this
4413 // sequence-based transaction goes in front of the queued
4414 // transaction, so the queued transaction is left in the queue.
4415 env(noop(alice), fee(openLedgerCost(env)));
4416 checkMetrics(__LINE__, env, 1, std::nullopt, 6, 3);
4417
4418 // We should be able to do the same thing with a ticket that goes
4419 // if front of the queued transaction. This one too will leave
4420 // the queued transaction in place.
4421 env(noop(alice), ticket::use(tktSeq0 + 0), fee(openLedgerCost(env)));
4422 checkMetrics(__LINE__, env, 1, std::nullopt, 7, 3);
4423
4424 // We have one ticketed transaction in the queue. We should able
4425 // to add another to the queue.
4426 env(noop(alice), ticket::use(tktSeq0 + 2), queued);
4427 checkMetrics(__LINE__, env, 2, std::nullopt, 7, 3);
4428
4429 // Here we try to force the queued transactions into the ledger by
4430 // adding one more queued (ticketed) transaction that pays enough
4431 // so fee averaging kicks in. It doesn't work. It only succeeds in
4432 // forcing just the one ticketed transaction into the ledger.
4433 //
4434 // The fee averaging functionality makes sense for sequence-based
4435 // transactions because if there are several sequence-based
4436 // transactions queued, the transactions in front must go into the
4437 // ledger before the later ones can go in.
4438 //
4439 // Fee averaging does not make sense with tickets. Every ticketed
4440 // transaction is equally capable of going into the ledger independent
4441 // of all other ticket- or sequence-based transactions.
4442 env(noop(alice), ticket::use(tktSeq0 + 3), fee(XRP(10)));
4443 checkMetrics(__LINE__, env, 2, std::nullopt, 8, 3);
4444
4445 // Now close the ledger. One of the queued transactions
4446 // (the one with tktSeq0 + 1) should be dropped.
4447 env.close();
4448 checkMetrics(__LINE__, env, 0, 16, 1, 8);
4449 }
4450
4451 void
4453 {
4454 // The TxQ caches preflight results. But there are situations where
4455 // that cache must become invalidated, like if amendments change.
4456 //
4457 // This test puts transactions into the TxQ and then enables an
4458 // amendment. We won't really see much interesting here in the unit
4459 // test, but the code that checks for cache invalidation should be
4460 // exercised. You can see that in improved code coverage,
4461 testcase("Re-execute preflight");
4462 using namespace jtx;
4463
4464 Account const alice("alice");
4465 Account const bob("bob");
4466 Account const carol("carol");
4467 Account const daria("daria");
4468 Account const ellie("ellie");
4469 Account const fiona("fiona");
4470
4471 auto cfg = makeConfig(
4472 {{"minimum_txn_in_ledger_standalone", "1"},
4473 {"ledgers_in_queue", "5"},
4474 {"maximum_txn_per_account", "10"}},
4475 {{"account_reserve", "1000"}, {"owner_reserve", "50"}});
4476
4477 auto& votingSection = cfg->section("voting");
4478 votingSection.set(
4479 "account_reserve",
4480 std::to_string(cfg->FEES.reference_fee.drops() * 100));
4481
4482 votingSection.set(
4483 "reference_fee", std::to_string(cfg->FEES.reference_fee.drops()));
4484
4485 Env env(*this, std::move(cfg));
4486
4487 env.fund(XRP(10000), alice);
4488 env.close();
4489 env.fund(XRP(10000), bob);
4490 env.close();
4491 env.fund(XRP(10000), carol);
4492 env.close();
4493 env.fund(XRP(10000), daria);
4494 env.close();
4495 env.fund(XRP(10000), ellie);
4496 env.close();
4497 env.fund(XRP(10000), fiona);
4498 env.close();
4499 checkMetrics(__LINE__, env, 0, 10, 0, 2);
4500
4501 // Close ledgers until the amendments show up.
4502 int i = 0;
4503 for (i = 0; i <= 257; ++i)
4504 {
4505 env.close();
4506 if (!getMajorityAmendments(*env.closed()).empty())
4507 break;
4508 }
4509 auto expectedPerLedger = ripple::detail::numUpVotedAmendments() + 1;
4511 __LINE__, env, 0, 5 * expectedPerLedger, 0, expectedPerLedger);
4512
4513 // Now wait 2 weeks modulo 256 ledgers for the amendments to be
4514 // enabled. Speed the process by closing ledgers every 80 minutes,
4515 // which should get us to just past 2 weeks after 256 ledgers.
4516 using namespace std::chrono_literals;
4517 auto closeDuration = 80min;
4518 for (i = 0; i <= 255; ++i)
4519 {
4520 env.close(closeDuration);
4521 }
4522
4523 auto const baseFee = env.current()->fees().base.drops();
4524 // We're very close to the flag ledger. Fill the ledger.
4525 fillQueue(env, alice);
4527 __LINE__,
4528 env,
4529 0,
4530 5 * expectedPerLedger,
4531 expectedPerLedger + 1,
4532 expectedPerLedger);
4533
4534 // Fill everyone's queues.
4535 auto seqAlice = env.seq(alice);
4536 auto seqBob = env.seq(bob);
4537 auto seqCarol = env.seq(carol);
4538 auto seqDaria = env.seq(daria);
4539 auto seqEllie = env.seq(ellie);
4540 auto seqFiona = env.seq(fiona);
4541
4542 // Use fees to guarantee order
4543 int txFee{static_cast<int>(baseFee * 9)};
4544 auto prepareFee = [&](uint64_t multiplier) {
4545 return fee(txFee - multiplier * baseFee / 10);
4546 };
4547
4548 uint64_t multiplier = 0;
4549 for (int i = 0; i < 10; ++i)
4550 {
4551 env(noop(alice),
4552 seq(seqAlice++),
4553 prepareFee(++multiplier),
4554 ter(terQUEUED));
4555 env(noop(bob),
4556 seq(seqBob++),
4557 prepareFee(++multiplier),
4558 ter(terQUEUED));
4559 env(noop(carol),
4560 seq(seqCarol++),
4561 prepareFee(++multiplier),
4562 ter(terQUEUED));
4563 env(noop(daria),
4564 seq(seqDaria++),
4565 prepareFee(++multiplier),
4566 ter(terQUEUED));
4567 env(noop(ellie),
4568 seq(seqEllie++),
4569 prepareFee(++multiplier),
4570 ter(terQUEUED));
4571 env(noop(fiona),
4572 seq(seqFiona++),
4573 prepareFee(++multiplier),
4574 ter(terQUEUED));
4575 }
4576 std::size_t expectedInQueue = 60;
4578 __LINE__,
4579 env,
4580 expectedInQueue,
4581 5 * expectedPerLedger,
4582 expectedPerLedger + 1,
4583 expectedPerLedger);
4584
4585 // The next close should cause the in-ledger amendments to change.
4586 // Alice's queued transactions have a cached PreflightResult
4587 // that resulted from running against the Rules in the previous
4588 // ledger. Since the amendments change in this newest ledger
4589 // The TxQ must re-run preflight using the new rules.
4590 //
4591 // These particular amendments don't impact any of the queued
4592 // transactions, so we won't see any change in the transaction
4593 // outcomes. But code coverage is affected.
4594 do
4595 {
4596 env.close(closeDuration);
4597 auto expectedInLedger = expectedInQueue;
4598 expectedInQueue =
4599 (expectedInQueue > expectedPerLedger + 2
4600 ? expectedInQueue - (expectedPerLedger + 2)
4601 : 0);
4602 expectedInLedger -= expectedInQueue;
4603 ++expectedPerLedger;
4605 __LINE__,
4606 env,
4607 expectedInQueue,
4608 5 * expectedPerLedger,
4609 expectedInLedger,
4610 expectedPerLedger);
4611 {
4612 auto const expectedPerAccount = expectedInQueue / 6;
4613 auto const expectedRemainder = expectedInQueue % 6;
4614 BEAST_EXPECT(env.seq(alice) == seqAlice - expectedPerAccount);
4616 env.seq(bob) ==
4617 seqBob - expectedPerAccount -
4618 (expectedRemainder > 4 ? 1 : 0));
4620 env.seq(carol) ==
4621 seqCarol - expectedPerAccount -
4622 (expectedRemainder > 3 ? 1 : 0));
4624 env.seq(daria) ==
4625 seqDaria - expectedPerAccount -
4626 (expectedRemainder > 2 ? 1 : 0));
4628 env.seq(ellie) ==
4629 seqEllie - expectedPerAccount -
4630 (expectedRemainder > 1 ? 1 : 0));
4632 env.seq(fiona) ==
4633 seqFiona - expectedPerAccount -
4634 (expectedRemainder > 0 ? 1 : 0));
4635 }
4636 } while (expectedInQueue > 0);
4637 }
4638
4639 void
4641 {
4642 // If...
4643 // o The queue is close to full,
4644 // o An account has multiple txs queued, and
4645 // o That same account has a transaction fail
4646 // Then drop the last transaction for the account if possible.
4647 //
4648 // Verify that happens.
4649 testcase("Queue full drop penalty");
4650 using namespace jtx;
4651
4652 // Because we're looking at a phenomenon that occurs when the TxQ
4653 // is at 95% capacity or greater, we need to have lots of entries
4654 // in the queue. You can't even see 95% capacity unless there are
4655 // 20 entries in the queue.
4656 Account const alice("alice");
4657 Account const bob("bob");
4658 Account const carol("carol");
4659 Account const daria("daria");
4660 Account const ellie("ellie");
4661 Account const fiona("fiona");
4662
4663 auto cfg = makeConfig(
4664 {{"minimum_txn_in_ledger_standalone", "5"},
4665 {"ledgers_in_queue", "5"},
4666 {"maximum_txn_per_account", "30"},
4667 {"minimum_queue_size", "50"}});
4668
4669 Env env(*this, std::move(cfg));
4670 auto const baseFee = env.current()->fees().base.drops();
4671
4672 // We'll be using fees to control which entries leave the queue in
4673 // which order. There's no "lowFee" -- that's the default fee from
4674 // the unit test.
4675 int const medFee = baseFee * 10;
4676 int const hiFee = baseFee * 100;
4677
4678 // The noripple is to reduce the number of transactions required to
4679 // fund the accounts. There is no rippling in this test.
4680 env.fund(XRP(10000), noripple(alice, bob, carol, daria, ellie, fiona));
4681 env.close();
4682
4683 // Get bob some tickets.
4684 std::uint32_t const bobTicketSeq = env.seq(bob) + 1;
4685 env(ticket::create(bob, 10));
4686 env.close();
4687
4688 // Get the dropPenalty flag set on alice and bob by having one
4689 // of their transactions expire out of the queue. To start out
4690 // alice fills the ledger.
4691 fillQueue(env, alice);
4692 checkMetrics(__LINE__, env, 0, 50, 7, 6);
4693
4694 // Now put a few transactions into alice's queue, including one that
4695 // will expire out soon.
4696 auto seqAlice = env.seq(alice);
4697 auto const seqSaveAlice = seqAlice;
4698 int feeDrops = baseFee * 4;
4699 env(noop(alice),
4700 seq(seqAlice++),
4701 fee(--feeDrops),
4702 json(R"({"LastLedgerSequence": 7})"),
4703 ter(terQUEUED));
4704 env(noop(alice), seq(seqAlice++), fee(--feeDrops), ter(terQUEUED));
4705 env(noop(alice), seq(seqAlice++), fee(--feeDrops), ter(terQUEUED));
4706 BEAST_EXPECT(env.seq(alice) == seqSaveAlice);
4707
4708 // Similarly for bob, but bob uses tickets in his transactions.
4709 // The drop penalty works a little differently with tickets.
4710 env(noop(bob),
4711 ticket::use(bobTicketSeq + 0),
4712 json(R"({"LastLedgerSequence": 7})"),
4713 ter(terQUEUED));
4714 env(noop(bob),
4715 ticket::use(bobTicketSeq + 1),
4716 fee(--feeDrops),
4717 ter(terQUEUED));
4718 env(noop(bob),
4719 ticket::use(bobTicketSeq + 2),
4720 fee(--feeDrops),
4721 ter(terQUEUED));
4722
4723 // Fill the queue with higher fee transactions so alice's and
4724 // bob's transactions are stuck in the queue.
4725 auto seqCarol = env.seq(carol);
4726 auto seqDaria = env.seq(daria);
4727 auto seqEllie = env.seq(ellie);
4728 auto seqFiona = env.seq(fiona);
4729 feeDrops = medFee;
4730 for (int i = 0; i < 7; ++i)
4731 {
4732 env(noop(carol), seq(seqCarol++), fee(--feeDrops), ter(terQUEUED));
4733 env(noop(daria), seq(seqDaria++), fee(--feeDrops), ter(terQUEUED));
4734 env(noop(ellie), seq(seqEllie++), fee(--feeDrops), ter(terQUEUED));
4735 env(noop(fiona), seq(seqFiona++), fee(--feeDrops), ter(terQUEUED));
4736 }
4737
4738 checkMetrics(__LINE__, env, 34, 50, 7, 6);
4739 env.close();
4740 checkMetrics(__LINE__, env, 26, 50, 8, 7);
4741
4742 // Re-fill the queue so alice and bob stay stuck.
4743 feeDrops = medFee;
4744 for (int i = 0; i < 3; ++i)
4745 {
4746 env(noop(carol), seq(seqCarol++), fee(--feeDrops), ter(terQUEUED));
4747 env(noop(daria), seq(seqDaria++), fee(--feeDrops), ter(terQUEUED));
4748 env(noop(ellie), seq(seqEllie++), fee(--feeDrops), ter(terQUEUED));
4749 env(noop(fiona), seq(seqFiona++), fee(--feeDrops), ter(terQUEUED));
4750 }
4751 checkMetrics(__LINE__, env, 38, 50, 8, 7);
4752 env.close();
4753 checkMetrics(__LINE__, env, 29, 50, 9, 8);
4754
4755 // One more time...
4756 feeDrops = medFee;
4757 for (int i = 0; i < 3; ++i)
4758 {
4759 env(noop(carol), seq(seqCarol++), fee(--feeDrops), ter(terQUEUED));
4760 env(noop(daria), seq(seqDaria++), fee(--feeDrops), ter(terQUEUED));
4761 env(noop(ellie), seq(seqEllie++), fee(--feeDrops), ter(terQUEUED));
4762 env(noop(fiona), seq(seqFiona++), fee(--feeDrops), ter(terQUEUED));
4763 }
4764 checkMetrics(__LINE__, env, 41, 50, 9, 8);
4765 env.close();
4766 checkMetrics(__LINE__, env, 29, 50, 10, 9);
4767
4768 // Finally the stage is set. alice's and bob's transactions expired
4769 // out of the queue which caused the dropPenalty flag to be set on
4770 // their accounts.
4771 //
4772 // This also means that alice has a sequence gap in her transactions,
4773 // and thus can't queue any more.
4774 env(noop(alice), seq(seqAlice), fee(hiFee), ter(telCAN_NOT_QUEUE));
4775
4776 // Once again, fill the queue almost to the brim.
4777 feeDrops = medFee;
4778 for (int i = 0; i < 4; ++i)
4779 {
4780 env(noop(carol), seq(seqCarol++), fee(--feeDrops), ter(terQUEUED));
4781 env(noop(daria), seq(seqDaria++), fee(--feeDrops), ter(terQUEUED));
4782 env(noop(ellie), seq(seqEllie++), fee(--feeDrops), ter(terQUEUED));
4783 env(noop(fiona), seq(seqFiona++), fee(--feeDrops), ter(terQUEUED));
4784 }
4785 env(noop(carol), seq(seqCarol++), fee(--feeDrops), ter(terQUEUED));
4786 env(noop(daria), seq(seqDaria++), fee(--feeDrops), ter(terQUEUED));
4787 env(noop(ellie), seq(seqEllie++), fee(--feeDrops), ter(terQUEUED));
4788 checkMetrics(__LINE__, env, 48, 50, 10, 9);
4789
4790 // Now induce a fee jump which should cause all the transactions
4791 // in the queue to fail with telINSUF_FEE_P.
4792 //
4793 // *NOTE* raiseLocalFee() is tricky to use since the local fee is
4794 // asynchronously lowered by LoadManager. Here we're just
4795 // pushing the local fee up really high and then hoping that we
4796 // outrace LoadManager undoing our work.
4797 for (int i = 0; i < 30; ++i)
4798 env.app().getFeeTrack().raiseLocalFee();
4799
4800 // Now close the ledger, which will attempt to process alice's
4801 // and bob's queued transactions.
4802 // o The _last_ transaction should be dropped from alice's queue.
4803 // o The first failing transaction should be dropped from bob's queue.
4804 env.close();
4805 checkMetrics(__LINE__, env, 46, 50, 0, 10);
4806
4807 // Run the local fee back down.
4808 while (env.app().getFeeTrack().lowerLocalFee())
4809 ;
4810
4811 // bob fills the ledger so it's easier to probe the TxQ.
4812 fillQueue(env, bob);
4813 checkMetrics(__LINE__, env, 46, 50, 11, 10);
4814
4815 // Before the close() alice had two transactions in her queue.
4816 // We now expect her to have one. Here's the state of alice's queue.
4817 //
4818 // 0. The transaction that used to be first in her queue expired
4819 // out two env.close() calls back. That left a gap in alice's
4820 // queue which has not been filled yet.
4821 //
4822 // 1. The first transaction in the queue failed to apply because
4823 // of the sequence gap. But it is retained in the queue.
4824 //
4825 // 2. The last (second) transaction in alice's queue was removed
4826 // as "punishment"...
4827 // a) For already having a transaction expire out of her queue, and
4828 // b) For just now having a queued transaction fail on apply()
4829 // because of the sequence gap.
4830 //
4831 // Verify that none of alice's queued transactions actually applied to
4832 // her account.
4833 BEAST_EXPECT(env.seq(alice) == seqSaveAlice);
4834 seqAlice = seqSaveAlice;
4835
4836 // Verify that there's a gap at the front of alice's queue by
4837 // queuing another low fee transaction into that spot.
4838 env(noop(alice), seq(seqAlice++), fee(baseFee * 1.1), ter(terQUEUED));
4839
4840 // Verify that the first entry in alice's queue is still there
4841 // by trying to replace it and having that fail.
4842 env(noop(alice), seq(seqAlice++), ter(telCAN_NOT_QUEUE_FEE));
4843
4844 // Verify that the last transaction in alice's queue was removed by
4845 // appending to her queue with a very low fee.
4846 env(noop(alice), seq(seqAlice++), ter(terQUEUED));
4847
4848 // Before the close() bob had two transactions in his queue.
4849 // We now expect him to have one. Here's the state of bob's queue.
4850 //
4851 // 0. The transaction that used to be first in his queue expired out
4852 // two env.close() calls back. That is how the dropPenalty flag
4853 // got set on bob's queue.
4854 //
4855 // 1. Since bob's remaining transactions all have the same fee, the
4856 // TxQ attempted to apply bob's second transaction to the ledger,
4857 // but the fee was too low. So the TxQ threw that transaction
4858 // (not bob's last transaction) out of the queue.
4859 //
4860 // 2. The last of bob's transactions remains in the TxQ.
4861
4862 // Verify that bob's first transaction was removed from the queue
4863 // by queueing another low fee transaction into that spot.
4864 env(noop(bob),
4865 ticket::use(bobTicketSeq + 0),
4866 fee(baseFee * 1.2),
4867 ter(terQUEUED));
4868
4869 // Verify that bob's second transaction was removed from the queue
4870 // by queueing another low fee transaction into that spot.
4871 env(noop(bob),
4872 ticket::use(bobTicketSeq + 1),
4873 fee(baseFee * 1.1),
4874 ter(terQUEUED));
4875
4876 // Verify that the last entry in bob's queue is still there
4877 // by trying to replace it and having that fail.
4878 env(noop(bob),
4879 ticket::use(bobTicketSeq + 2),
4881 }
4882
4883 void
4885 {
4886 testcase("Cancel queued offers");
4887 using namespace jtx;
4888
4889 Account const alice("alice");
4890 auto gw = Account("gw");
4891 auto USD = gw["USD"];
4892
4893 auto cfg = makeConfig(
4894 {{"minimum_txn_in_ledger_standalone", "5"},
4895 {"ledgers_in_queue", "5"},
4896 {"maximum_txn_per_account", "30"},
4897 {"minimum_queue_size", "50"}});
4898
4899 Env env(*this, std::move(cfg));
4900
4901 // The noripple is to reduce the number of transactions required to
4902 // fund the accounts. There is no rippling in this test.
4903 env.fund(XRP(100000), noripple(alice));
4904 env.close();
4905
4906 {
4907 // ------- Sequence-based transactions -------
4908 fillQueue(env, alice);
4909
4910 // Alice creates a couple offers
4911 auto const aliceSeq = env.seq(alice);
4912 env(offer(alice, USD(1000), XRP(1000)), ter(terQUEUED));
4913
4914 env(offer(alice, USD(1000), XRP(1001)),
4915 seq(aliceSeq + 1),
4916 ter(terQUEUED));
4917
4918 // Alice creates transactions that cancel the first set of
4919 // offers, one through another offer, and one cancel
4920 env(offer(alice, USD(1000), XRP(1002)),
4921 seq(aliceSeq + 2),
4922 json(jss::OfferSequence, aliceSeq),
4923 ter(terQUEUED));
4924
4925 env(offer_cancel(alice, aliceSeq + 1),
4926 seq(aliceSeq + 3),
4927 ter(terQUEUED));
4928
4929 env.close();
4930
4931 checkMetrics(__LINE__, env, 0, 50, 4, 6);
4932 }
4933
4934 {
4935 // ------- Ticket-based transactions -------
4936
4937 // Alice creates some tickets
4938 auto const aliceTkt = env.seq(alice);
4939 env(ticket::create(alice, 6));
4940 env.close();
4941
4942 fillQueue(env, alice);
4943
4944 // Alice creates a couple offers using tickets, consuming the
4945 // tickets in reverse order
4946 auto const aliceSeq = env.seq(alice);
4947 env(offer(alice, USD(1000), XRP(1000)),
4948 ticket::use(aliceTkt + 4),
4949 ter(terQUEUED));
4950
4951 env(offer(alice, USD(1000), XRP(1001)),
4952 ticket::use(aliceTkt + 3),
4953 ter(terQUEUED));
4954
4955 // Alice creates a couple more transactions that cancel the first
4956 // set of offers, also in reverse order. This allows Alice to submit
4957 // a tx with a lower ticket value than the offer it's cancelling.
4958 // These transactions succeed because Ticket ordering is arbitrary
4959 // and it's up to the user to ensure they don't step on their own
4960 // feet.
4961 env(offer(alice, USD(1000), XRP(1002)),
4962 ticket::use(aliceTkt + 2),
4963 json(jss::OfferSequence, aliceTkt + 4),
4964 ter(terQUEUED));
4965
4966 env(offer_cancel(alice, aliceTkt + 3),
4967 ticket::use(aliceTkt + 1),
4968 ter(terQUEUED));
4969
4970 // Create a couple more offers using sequences
4971 env(offer(alice, USD(1000), XRP(1000)), ter(terQUEUED));
4972
4973 env(offer(alice, USD(1000), XRP(1001)),
4974 seq(aliceSeq + 1),
4975 ter(terQUEUED));
4976
4977 // And try to cancel those using tickets
4978 env(offer(alice, USD(1000), XRP(1002)),
4979 ticket::use(aliceTkt + 5),
4980 json(jss::OfferSequence, aliceSeq),
4981 ter(terQUEUED));
4982
4983 env(offer_cancel(alice, aliceSeq + 1),
4984 ticket::use(aliceTkt + 6),
4985 ter(terQUEUED));
4986
4987 env.close();
4988
4989 // The ticket transactions that didn't succeed or get queued succeed
4990 // this time because the tickets got consumed when the offers came
4991 // out of the queue
4992 checkMetrics(__LINE__, env, 0, 50, 8, 7);
4993 }
4994 }
4995
4996 void
4998 {
4999 testcase("Zero reference fee");
5000 using namespace jtx;
5001
5002 Account const alice("alice");
5003 auto const queued = ter(terQUEUED);
5004
5005 Env env(
5006 *this,
5007 makeConfig(
5008 {{"minimum_txn_in_ledger_standalone", "3"}},
5009 {{"reference_fee", "0"},
5010 {"account_reserve", "0"},
5011 {"owner_reserve", "0"}}));
5012
5013 checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3);
5014
5015 // ledgers in queue is 2 because of makeConfig
5016 auto const initQueueMax = initFee(env, 3, 2, 0, 0, 0);
5017
5018 BEAST_EXPECT(env.current()->fees().base == 0);
5019
5020 {
5021 auto const fee = env.rpc("fee");
5022
5023 if (BEAST_EXPECT(fee.isMember(jss::result)) &&
5024 BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
5025 {
5026 auto const& result = fee[jss::result];
5027
5028 BEAST_EXPECT(result.isMember(jss::levels));
5029 auto const& levels = result[jss::levels];
5031 levels.isMember(jss::median_level) &&
5032 levels[jss::median_level] == "128000");
5034 levels.isMember(jss::minimum_level) &&
5035 levels[jss::minimum_level] == "256");
5037 levels.isMember(jss::open_ledger_level) &&
5038 levels[jss::open_ledger_level] == "256");
5040 levels.isMember(jss::reference_level) &&
5041 levels[jss::reference_level] == "256");
5042
5043 auto const& drops = result[jss::drops];
5045 drops.isMember(jss::base_fee) &&
5046 drops[jss::base_fee] == "0");
5048 drops.isMember(jss::median_fee) &&
5049 drops[jss::median_fee] == "0");
5051 drops.isMember(jss::minimum_fee) &&
5052 drops[jss::minimum_fee] == "0");
5054 drops.isMember(jss::open_ledger_fee) &&
5055 drops[jss::open_ledger_fee] == "0");
5056 }
5057 }
5058
5059 checkMetrics(__LINE__, env, 0, initQueueMax, 0, 3);
5060
5061 // The noripple is to reduce the number of transactions required to
5062 // fund the accounts. There is no rippling in this test.
5063 env.fund(XRP(100000), noripple(alice));
5064
5065 checkMetrics(__LINE__, env, 0, initQueueMax, 1, 3);
5066
5067 env.close();
5068
5069 checkMetrics(__LINE__, env, 0, 6, 0, 3);
5070
5071 fillQueue(env, alice);
5072
5073 checkMetrics(__LINE__, env, 0, 6, 4, 3);
5074
5075 env(noop(alice), fee(openLedgerCost(env)));
5076
5077 checkMetrics(__LINE__, env, 0, 6, 5, 3);
5078
5079 auto aliceSeq = env.seq(alice);
5080 env(noop(alice), queued);
5081
5082 checkMetrics(__LINE__, env, 1, 6, 5, 3);
5083
5084 env(noop(alice), seq(aliceSeq + 1), fee(10), queued);
5085
5086 checkMetrics(__LINE__, env, 2, 6, 5, 3);
5087
5088 {
5089 auto const fee = env.rpc("fee");
5090
5091 if (BEAST_EXPECT(fee.isMember(jss::result)) &&
5092 BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
5093 {
5094 auto const& result = fee[jss::result];
5095
5096 BEAST_EXPECT(result.isMember(jss::levels));
5097 auto const& levels = result[jss::levels];
5099 levels.isMember(jss::median_level) &&
5100 levels[jss::median_level] == "128000");
5102 levels.isMember(jss::minimum_level) &&
5103 levels[jss::minimum_level] == "256");
5105 levels.isMember(jss::open_ledger_level) &&
5106 levels[jss::open_ledger_level] == "355555");
5108 levels.isMember(jss::reference_level) &&
5109 levels[jss::reference_level] == "256");
5110
5111 auto const& drops = result[jss::drops];
5113 drops.isMember(jss::base_fee) &&
5114 drops[jss::base_fee] == "0");
5116 drops.isMember(jss::median_fee) &&
5117 drops[jss::median_fee] == "0");
5119 drops.isMember(jss::minimum_fee) &&
5120 drops[jss::minimum_fee] == "0");
5122 drops.isMember(jss::open_ledger_fee) &&
5123 drops[jss::open_ledger_fee] == "1389");
5124 }
5125 }
5126
5127 env.close();
5128
5129 checkMetrics(__LINE__, env, 0, 10, 2, 5);
5130 }
5131
5132 void
5133 run() override
5134 {
5135 testQueueSeq();
5137 testTecResult();
5145 testAcctTxnID();
5146 testMaximum();
5152 }
5153
5154 void
5156 {
5158 testRPC();
5166 testScaling();
5173 }
5174};
5175
5177{
5178 void
5179 run() override
5180 {
5181 runMetaInfo();
5182 }
5183};
5184
5185BEAST_DEFINE_TESTSUITE_PRIO(TxQPosNegFlows, app, ripple, 1);
5186BEAST_DEFINE_TESTSUITE_PRIO(TxQMetaInfo, app, ripple, 1);
5187
5188} // namespace test
5189} // 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
void pass()
Record a successful test condition.
Definition: suite.h:511
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
virtual Config & config()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual OpenLedger & openLedger()=0
virtual TxQ & getTxQ()=0
void setRemoteFee(std::uint32_t f)
Definition: LoadFeeTrack.h:60
std::uint32_t getRemoteFee() const
Definition: LoadFeeTrack.h:68
std::uint32_t getLoadFactor() const
Definition: LoadFeeTrack.h:95
bool modify(modify_type const &f)
Modify the open ledger.
Definition: OpenLedger.cpp:56
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:57
A type that represents either a sequence value or a ticket value.
Definition: SeqProxy.h:56
static constexpr SeqProxy sequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition: SeqProxy.h:76
std::vector< TxDetails > getTxs() const
Returns information about all transactions currently in the queue.
Definition: TxQ.cpp:1845
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition: TxQ.cpp:1778
std::vector< TxDetails > getAccountTxs(AccountID const &account) const
Returns information about the transactions currently in the queue for the account.
Definition: TxQ.cpp:1824
constexpr value_type drops() const
Returns the number of drops.
Definition: XRPAmount.h:177
constexpr value_type value() const
Returns the underlying value.
Definition: FeeUnits.h:323
constexpr value_type fee() const
Returns the number of drops.
Definition: FeeUnits.h:277
void run() override
Runs the suite.
Definition: TxQ_test.cpp:5179
BEAST_EXPECT(queue_data[jss::highest_sequence]==data[jss::Sequence].asUInt()+queue_data[jss::txn_count].asUInt() - 1)
auto calcMedFeeLevel(FeeLevel64 const feeLevel)
Definition: TxQ_test.cpp:187
BEAST_EXPECT(queue_data[jss::txn_count]==0)
checkMetrics(__LINE__, env, 0, 10, 2, 5)
BEAST_EXPECT(queue_data.isObject())
BEAST_EXPECT(!queue_data.isMember(jss::transactions))
static std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
Definition: TxQ_test.cpp:193
envs(noop(alice), fee(baseFee *10), seq(none), ter(terQUEUED))(submitParams)
void checkMetrics(int line, 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())
Definition: TxQ_test.cpp:46
auto calcMedFeeLevel(FeeLevel64 const feeLevel1, FeeLevel64 const feeLevel2)
Definition: TxQ_test.cpp:178
BEAST_EXPECT(info.isMember(jss::result) &&RPC::contains_error(info[jss::result]))
checkMetrics(__LINE__, env, 1, 8, 5, 4)
BEAST_EXPECT(queue_data[jss::auth_change_queued]==true)
envs(noop(alice), fee(baseFee *10), seq(none), ter(telCAN_NOT_QUEUE_BLOCKED))(submitParams)
BEAST_EXPECT(queue_data.isMember(jss::transactions))
static constexpr FeeLevel64 baseFeeLevel
Definition: TxQ_test.cpp:42
checkMetrics(__LINE__, env, 0, 6, 4, 3)
BEAST_EXPECT(queue_data[jss::max_spend_drops_total]==std::to_string(baseFee *10))
BEAST_EXPECT(info.isMember(jss::result) &&info[jss::result].isMember(jss::account_data))
checkMetrics(__LINE__, env, 4, 6, 4, 3)
checkMetrics(__LINE__, env, 0, 10, 0, 5)
BEAST_EXPECT(queue_data[jss::txn_count]==4)
BEAST_EXPECT(queue_data[jss::lowest_sequence]==data[jss::Sequence])
BEAST_EXPECT(env.current() ->info().seq > 3)
BEAST_EXPECT(result.isMember(jss::queue_data))
envs(noop(alice), seq(none))(submitParams)
BEAST_EXPECT(!info[jss::result].isMember(jss::queue_data))
void fillQueue(jtx::Env &env, jtx::Account const &account)
Definition: TxQ_test.cpp:137
BEAST_EXPECT(queue_data.isMember(jss::max_spend_drops_total))
BEAST_EXPECT(queue_data[jss::auth_change_queued]==false)
static constexpr FeeLevel64 minEscalationFeeLevel
Definition: TxQ_test.cpp:43
checkMetrics(__LINE__, env, 0, 8, 4, 4)
BEAST_EXPECT(!queue_data.isMember(jss::max_spend_drops_total))
BEAST_EXPECT(queue_data[jss::auth_change_queued].asBool())
std::size_t initFee(jtx::Env &env, std::size_t expectedPerLedger, std::size_t ledgersInQueue, std::uint32_t base, std::uint32_t reserve, std::uint32_t increment)
Definition: TxQ_test.cpp:227
BEAST_EXPECT(queued.size()==queue_data[jss::txn_count])
auto openLedgerCost(jtx::Env &env)
Definition: TxQ_test.cpp:145
auto txFeeLevelByAccount(jtx::Env &env, jtx::Account const &account)
Definition: TxQ_test.cpp:165
BEAST_EXPECT(!queue_data.isMember(jss::auth_change_queued))
BEAST_EXPECT(!queue_data.isMember(jss::highest_sequence))
envs(fset(alice, asfAccountTxnID), fee(baseFee *10), seq(none), json(jss::LastLedgerSequence, 10), ter(terQUEUED))(submitParams)
BEAST_EXPECT(queue_data[jss::max_spend_drops_total]==std::to_string(baseFee *40))
BEAST_EXPECT(!queue_data.isMember(jss::lowest_sequence))
BEAST_EXPECT(queue_data.isMember(jss::lowest_sequence))
checkMetrics(__LINE__, env, 0, 6, 0, 3)
void run() override
Runs the suite.
Definition: TxQ_test.cpp:5133
BEAST_EXPECT(queue_data.isMember(jss::highest_sequence))
BEAST_EXPECT(queue_data.isMember(jss::txn_count))
BEAST_EXPECT(queue_data.isMember(jss::auth_change_queued))
BEAST_EXPECT(queue_data[jss::txn_count]==1)
Immutable cryptographic account descriptor.
Definition: Account.h:39
static Account const master
The master account.
Definition: Account.h:48
A transaction testing environment wrapper.
Definition: Env_ss.h:34
A transaction testing environment.
Definition: Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:535
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
void postconditions(JTx const &jt, ParsedResult const &parsed, Json::Value const &jr=Json::Value())
Check expected postconditions of JTx submission.
Definition: Env.cpp:387
Account const & master
Definition: Env.h:125
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:496
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
Application & app()
Definition: Env.h:261
beast::Journal const journal
Definition: Env.h:162
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:770
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:221
A balance matches.
Definition: balance.h:39
Set the fee on a JTx.
Definition: fee.h:37
Inject raw JSON.
Definition: jtx_json.h:33
Match the number of items in the account's owner directory.
Definition: owners.h:73
Check a set of conditions.
Definition: require.h:65
Sets the SendMax on a JTx.
Definition: sendmax.h:33
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
T empty(T... args)
T max(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
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:203
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:374
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
owner_count< ltRIPPLE_STATE > lines
Match the number of trust lines in the account's owner directory.
Definition: owners.h:89
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
static none_t const none
Definition: tags.h:34
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
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
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
owner_count< ltTICKET > tickets
Match the number of tickets on the account.
Definition: ticket.h:64
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition: offer.cpp:46
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:302
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
Definition: applySteps.cpp:309
@ telCAN_NOT_QUEUE_BLOCKED
Definition: TER.h:62
@ telCAN_NOT_QUEUE_FULL
Definition: TER.h:64
@ telCAN_NOT_QUEUE
Definition: TER.h:59
@ telCAN_NOT_QUEUE_BALANCE
Definition: TER.h:60
@ telCAN_NOT_QUEUE_FEE
Definition: TER.h:63
@ telCAN_NOT_QUEUE_BLOCKS
Definition: TER.h:61
@ tefPAST_SEQ
Definition: TER.h:175
@ tefNO_TICKET
Definition: TER.h:185
@ tefWRONG_PRIOR
Definition: TER.h:176
constexpr std::uint32_t asfAccountTxnID
Definition: TxFlags.h:80
@ tecUNFUNDED_OFFER
Definition: TER.h:284
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
Definition: TxQ.h:870
@ tesSUCCESS
Definition: TER.h:244
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition: apply.cpp:110
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition: View.cpp:925
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition: TxQ.h:863
@ tapUNLIMITED
Definition: ApplyView.h:43
@ tapNONE
Definition: ApplyView.h:32
@ terINSUF_FEE_B
Definition: TER.h:216
@ terNO_ACCOUNT
Definition: TER.h:217
@ terPRE_SEQ
Definition: TER.h:221
@ terQUEUED
Definition: TER.h:225
@ terPRE_TICKET
Definition: TER.h:226
@ temBAD_AMOUNT
Definition: TER.h:89
@ temREDUNDANT
Definition: TER.h:112
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
Definition: TxQ.h:178
std::size_t txInLedger
Number of transactions currently in the open ledger.
Definition: TxQ.h:174
Used by parseResult() and postConditions()
Definition: Env.h:129
std::optional< TER > ter
Definition: Env.h:130
Set the sequence number on a JTx.
Definition: seq.h:34
T to_string(T... args)
T value_or(T... args)
T what(T... args)