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