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