xrpld
Loading...
Searching...
No Matches
TxQ.cpp
1#include <xrpld/app/misc/TxQ.h>
2
3#include <xrpld/app/ledger/OpenLedger.h>
4#include <xrpld/app/main/Application.h>
5
6#include <xrpl/basics/Log.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/basics/mulDiv.h>
9#include <xrpl/beast/utility/Zero.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/config/BasicConfig.h>
12#include <xrpl/config/Constants.h>
13#include <xrpl/json/json_value.h>
14#include <xrpl/ledger/ApplyView.h>
15#include <xrpl/ledger/ApplyViewImpl.h>
16#include <xrpl/ledger/OpenView.h>
17#include <xrpl/ledger/ReadView.h>
18#include <xrpl/ledger/helpers/SponsorHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/Indexes.h>
21#include <xrpl/protocol/Keylet.h>
22#include <xrpl/protocol/LedgerFormats.h>
23#include <xrpl/protocol/Protocol.h>
24#include <xrpl/protocol/RippleLedgerHash.h>
25#include <xrpl/protocol/SField.h>
26#include <xrpl/protocol/STTx.h>
27#include <xrpl/protocol/SeqProxy.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFormats.h>
30#include <xrpl/protocol/Units.h>
31#include <xrpl/protocol/XRPAmount.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/tx/apply.h>
34#include <xrpl/tx/applySteps.h>
35
36#include <boost/function/function_base.hpp>
37
38#include <algorithm>
39#include <cstddef>
40#include <cstdint>
41#include <iterator>
42#include <limits>
43#include <memory>
44#include <mutex>
45#include <numeric>
46#include <optional>
47#include <stdexcept>
48#include <string>
49#include <tuple>
50#include <utility>
51#include <vector>
52
53namespace xrpl {
54
56
57static FeeLevel64
58getFeeLevelPaid(ReadView const& view, STTx const& tx)
59{
60 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
61 XRPAmount const baseFee = calculateBaseFee(view, tx);
62 XRPAmount const feePaid = tx[sfFee].xrp();
63
64 // If baseFee is 0 then the cost of a basic transaction is free, but we
65 // need the effective fee level to be non-zero.
66 XRPAmount const mod = [&view, &tx, baseFee]() {
67 if (baseFee.signum() > 0)
68 return XRPAmount{0};
69 auto def = calculateDefaultBaseFee(view, tx);
70 return def.signum() == 0 ? XRPAmount{1} : def;
71 }();
72 return std::pair{baseFee + mod, feePaid + mod};
73 }();
74
75 XRPL_ASSERT(baseFee.signum() > 0, "xrpl::getFeeLevelPaid : positive fee");
76 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
77 {
78 return FeeLevel64(0);
79 }
80
81 return mulDiv(effectiveFeePaid, TxQ::kBaseLevel, baseFee)
83}
84
87{
88 if (!tx.isFieldPresent(sfLastLedgerSequence))
89 return std::nullopt;
90 return tx.getFieldU32(sfLastLedgerSequence);
91}
92
93static FeeLevel64
94increase(FeeLevel64 level, std::uint32_t increasePercent)
95{
96 return mulDiv(level, 100 + increasePercent, 100)
97 .value_or(static_cast<FeeLevel64>(xrpl::kMuldivMax));
98}
99
101
104 Application& app,
105 ReadView const& view,
106 bool timeLeap,
107 TxQ::Setup const& setup)
108{
109 std::vector<FeeLevel64> feeLevels;
110 auto const txBegin = view.txs.begin();
111 auto const txEnd = view.txs.end();
112 auto const size = std::distance(txBegin, txEnd);
113 feeLevels.reserve(size);
114 std::for_each(txBegin, txEnd, [&](auto const& tx) {
115 feeLevels.push_back(getFeeLevelPaid(view, *tx.first));
116 });
117 std::ranges::sort(feeLevels);
118 XRPL_ASSERT(size == feeLevels.size(), "xrpl::TxQ::FeeMetrics::update : fee levels size");
119
120 JLOG((timeLeap ? j_.warn() : j_.debug()))
121 << "Ledger " << view.header().seq << " has " << size << " transactions. "
122 << "Ledgers are processing " << (timeLeap ? "slowly" : "as expected")
123 << ". Expected transactions is currently " << txnsExpected_ << " and multiplier is "
125
126 if (timeLeap)
127 {
128 // Ledgers are taking to long to process,
129 // so clamp down on limits.
130 auto const cutPct = 100 - setup.slowConsensusDecreasePercent;
131 // upperLimit must be >= minimumTxnCount_ or std::clamp can give
132 // unexpected results
133 auto const upperLimit = std::max<std::uint64_t>(
134 mulDiv(txnsExpected_, cutPct, 100).value_or(xrpl::kMuldivMax), minimumTxnCount_);
136 mulDiv(size, cutPct, 100).value_or(xrpl::kMuldivMax), minimumTxnCount_, upperLimit);
137 recentTxnCounts_.clear();
138 }
139 else if (size > txnsExpected_ || size > targetTxnCount_)
140 {
141 recentTxnCounts_.push_back(mulDiv(size, 100 + setup.normalConsensusIncreasePercent, 100)
144 BOOST_ASSERT(iter != recentTxnCounts_.end());
145 auto const next = [&] {
146 // Grow quickly: If the max_element is >= the
147 // current size limit, use it.
148 if (*iter >= txnsExpected_)
149 return *iter;
150 // Shrink slowly: If the max_element is < the
151 // current size limit, use a limit that is
152 // 90% of the way from max_element to the
153 // current size limit.
154 return ((txnsExpected_ * 9) + *iter) / 10;
155 }();
156 // Ledgers are processing in a timely manner,
157 // so keep the limit high, but don't let it
158 // grow without bound.
159 txnsExpected_ = std::min(next, maximumTxnCount_.value_or(next));
160 }
161
162 if (size == 0)
163 {
165 }
166 else
167 {
168 // In the case of an odd number of elements, this
169 // evaluates to the middle element; for an even
170 // number of elements, it will add the two elements
171 // on either side of the "middle" and average them.
173 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] + FeeLevel64{1}) / 2;
175 }
176 JLOG(j_.debug()) << "Expected transactions updated to " << txnsExpected_
177 << " and multiplier updated to " << escalationMultiplier_;
178
179 return size;
180}
181
184{
185 // Transactions in the open ledger so far
186 auto const current = view.txCount();
187
188 auto const target = snapshot.txnsExpected;
189 auto const multiplier = snapshot.escalationMultiplier;
190
191 // Once the open ledger bypasses the target,
192 // escalate the fee quickly.
193 if (current > target)
194 {
195 // Compute escalated fee level
196 // Don't care about the overflow flag
197 return mulDiv(multiplier, current * current, target * target)
198 .value_or(static_cast<FeeLevel64>(xrpl::kMuldivMax));
199 }
200
201 return kBaseLevel;
202}
203
204namespace detail {
205
206static constexpr std::pair<bool, std::uint64_t>
208{
209 // sum(n = 1->x) : n * n = x(x + 1)(2x + 1) / 6
210
211 // We expect that size_t == std::uint64_t but, just in case, guarantee
212 // we lose no bits.
213 std::uint64_t const x{xIn};
214
215 // If x is anywhere on the order of 2^^21, it's going
216 // to completely dominate the computation and is likely
217 // enough to overflow that we're just going to assume
218 // it does. If we have anywhere near 2^^21 transactions
219 // in a ledger, this is the least of our problems.
220 if (x >= (1 << 21))
222 return {true, (x * (x + 1) * ((2 * x) + 1)) / 6};
223}
224
225// Unit tests for sumOfSquares()
226static_assert(sumOfFirstSquares(1).first);
227static_assert(sumOfFirstSquares(1).second == 1);
228
229static_assert(sumOfFirstSquares(2).first);
230static_assert(sumOfFirstSquares(2).second == 5);
231
232static_assert(sumOfFirstSquares(0x1FFFFF).first);
233static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul);
234
235static_assert(!sumOfFirstSquares(0x200000).first);
236static_assert(sumOfFirstSquares(0x200000).second == std::numeric_limits<std::uint64_t>::max());
237
238} // namespace detail
239
242 Snapshot const& snapshot,
243 OpenView const& view,
244 std::size_t extraCount,
245 std::size_t seriesSize)
246{
247 /* Transactions in the open ledger so far.
248 AKA Transactions that will be in the open ledger when
249 the first tx in the series is attempted.
250 */
251 auto const current = view.txCount() + extraCount;
252 /* Transactions that will be in the open ledger when
253 the last tx in the series is attempted.
254 */
255 auto const last = current + seriesSize - 1;
256
257 auto const target = snapshot.txnsExpected;
258 auto const multiplier = snapshot.escalationMultiplier;
259
260 XRPL_ASSERT(
261 current > target,
262 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
263 "target");
264
265 /* Calculate (apologies for the terrible notation)
266 sum(n = current -> last) : multiplier * n * n / (target * target)
267 multiplier / (target * target) * (sum(n = current -> last) : n * n)
268 multiplier / (target * target) * ((sum(n = 1 -> last) : n * n) -
269 (sum(n = 1 -> current - 1) : n * n))
270 */
271 auto const sumNlast = detail::sumOfFirstSquares(last);
272 auto const sumNcurrent = detail::sumOfFirstSquares(current - 1);
273 // because `last` is bigger, if either sum overflowed, then
274 // `sumNlast` definitely overflowed. Also the odds of this
275 // are nearly nil.
276 if (!sumNlast.first)
277 return {sumNlast.first, FeeLevel64{sumNlast.second}};
278 auto const totalFeeLevel =
279 mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target);
280
281 return {
282 totalFeeLevel.has_value(), *totalFeeLevel}; // NOLINT(bugprone-unchecked-optional-access)
283}
284
286
289 TxID const& txId,
291 ApplyFlags const flags,
293 : txn(txn)
295 , txID(txId)
296 , account(txn->getAccountID(sfAccount))
298 , seqProxy(txn->getSeqProxy())
299 , flags(flags)
301{
302}
303
306{
307 // If the rules or flags change, preflight again
308 XRPL_ASSERT(pfResult, "xrpl::TxQ::MaybeTx::apply : preflight result is set");
309
310 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
311 if (pfResult->rules != view.rules() || pfResult->flags != flags)
312 {
313 JLOG(j.debug()) << "Queued transaction " << txID
314 << " rules or flags have changed. Flags from " << pfResult->flags << " to "
315 << flags;
316
317 pfResult.emplace(preflight(app, view.rules(), pfResult->tx, flags, pfResult->j));
318 }
319
320 auto pcresult = preclaim(*pfResult, app, view);
321 // NOLINTEND(bugprone-unchecked-optional-access)
322
323 return doApply(pcresult, app, view);
324}
325
327 : TxQAccount(txn->getAccountID(sfAccount))
328{
329}
330
334
335TxQ::TxQAccount::TxMap::const_iterator
337{
338 // Find the entry that is greater than or equal to the new transaction,
339 // then decrement the iterator.
340 auto sameOrPrevIter = transactions.lower_bound(seqProx);
341 if (sameOrPrevIter != transactions.begin())
342 --sameOrPrevIter;
343 return sameOrPrevIter;
344}
345
348{
349 auto const seqProx = txn.seqProxy;
350 [[maybe_unused]] auto const* txnPtr = &txn;
351
352 auto result = transactions.emplace(seqProx, std::move(txn));
353 XRPL_ASSERT(result.second, "xrpl::TxQ::TxQAccount::add : emplace succeeded");
354 XRPL_ASSERT(&result.first->second != txnPtr, "xrpl::TxQ::TxQAccount::add : transaction moved");
355
356 return result.first->second;
357}
358
359bool
361{
362 return transactions.erase(seqProx) != 0;
363}
364
366
368 : setup_(setup), j_(j), feeMetrics_(setup, j), maxSize_(std::nullopt)
369{
370}
371
373{
374 byFee_.clear();
375}
376
377template <size_t FillPercentage>
378bool
380{
381 static_assert(FillPercentage > 0 && FillPercentage <= 100, "Invalid fill percentage");
382 return maxSize_ && byFee_.size() >= (*maxSize_ * FillPercentage / 100);
383}
384
385TER
387 STTx const& tx,
388 ApplyFlags const flags,
389 OpenView const& view,
390 SLE::const_ref sleAccount,
391 AccountMap::iterator const& accountIter,
392 std::optional<TxQAccount::TxMap::iterator> const& replacementIter,
394{
395 // A Batch is never queued: its inner transactions can change the sequence
396 // numbers of multiple accounts, which the TxQ's per-account model cannot
397 // forecast. It must apply straight to the open ledger or not at all.
398 if (tx.getTxnType() == ttBATCH)
399 return telCAN_NOT_QUEUE;
400
401 // PreviousTxnID is deprecated and should never be used.
402 // AccountTxnID is not supported by the transaction
403 // queue yet, but should be added in the future.
404 // TapFailHard transactions are never held
405 if (tx.isFieldPresent(sfPreviousTxnID) || tx.isFieldPresent(sfAccountTxnID) ||
406 ((flags & TapFailHard) != 0u))
407 return telCAN_NOT_QUEUE;
408
409 // Disallow delegated transactions from being queued.
410 if (tx.isFieldPresent(sfDelegate))
411 return telCAN_NOT_QUEUE;
412 // Disallow fee-sponsored transactions from being queued.
413 if (isFeeSponsored(tx))
414 return telCAN_NOT_QUEUE;
415
416 {
417 // To be queued and relayed, the transaction needs to
418 // promise to stick around for long enough that it has
419 // a realistic chance of getting into a ledger.
420 auto const lastValid = getLastLedgerSequence(tx);
421 if (lastValid && *lastValid < view.header().seq + setup_.minimumLastLedgerBuffer)
422 return telCAN_NOT_QUEUE;
423 }
424
425 // Allow if the account is not in the queue at all.
426 if (accountIter == byAccount_.end())
427 return tesSUCCESS;
428
429 // Allow this tx to replace another one.
430 if (replacementIter)
431 return tesSUCCESS;
432
433 // Allow if there are fewer than the limit.
434 TxQAccount const& txQAcct = accountIter->second;
435 if (txQAcct.getTxnCount() < setup_.maximumTxnPerAccount)
436 return tesSUCCESS;
437
438 // If we get here the queue limit is exceeded. Only allow if this
439 // transaction fills the _first_ sequence hole for the account.
440 auto const txSeqProx = tx.getSeqProxy();
441 if (txSeqProx.isTicket())
442 {
443 // Tickets always follow sequence-based transactions, so a ticket
444 // cannot unblock a sequence-based transaction.
446 }
447
448 // This is the next queuable sequence-based SeqProxy for the account.
449 SeqProxy const nextQueuable = nextQueuableSeqImpl(sleAccount, lock);
450 if (txSeqProx != nextQueuable)
451 {
452 // The provided transaction does not fill the next open sequence gap.
454 }
455
456 // Make sure they are not just topping off the account's queued
457 // sequence-based transactions.
458 if (auto const nextTxIter = txQAcct.transactions.upper_bound(nextQueuable);
459 nextTxIter != txQAcct.transactions.end() && nextTxIter->first.isSeq())
460 {
461 // There is a next transaction and it is sequence based. They are
462 // filling a real gap. Allow it.
463 return tesSUCCESS;
464 }
465
467}
468
469auto
470TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter) -> FeeMultiSet::iterator_type
471{
472 auto& txQAccount = byAccount_.at(candidateIter->account);
473 auto const seqProx = candidateIter->seqProxy;
474 auto const newCandidateIter = byFee_.erase(candidateIter);
475 // Now that the candidate has been removed from the
476 // intrusive list remove it from the TxQAccount
477 // so the memory can be freed.
478 [[maybe_unused]] auto const found = txQAccount.remove(seqProx);
479 XRPL_ASSERT(found, "xrpl::TxQ::erase : account removed");
480
481 return newCandidateIter;
482}
483
484auto
485TxQ::eraseAndAdvance(TxQ::FeeMultiSet::const_iterator_type candidateIter)
486 -> FeeMultiSet::iterator_type
487{
488 auto& txQAccount = byAccount_.at(candidateIter->account);
489 auto const accountIter = txQAccount.transactions.find(candidateIter->seqProxy);
490 XRPL_ASSERT(
491 accountIter != txQAccount.transactions.end(), "xrpl::TxQ::eraseAndAdvance : account found");
492
493 // Note that sequence-based transactions must be applied in sequence order
494 // from smallest to largest. But ticket-based transactions can be
495 // applied in any order.
496 XRPL_ASSERT(
497 candidateIter->seqProxy.isTicket() || accountIter == txQAccount.transactions.begin(),
498 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
499 XRPL_ASSERT(
500 byFee_.iterator_to(accountIter->second) == candidateIter,
501 "xrpl::TxQ::eraseAndAdvance : found in byFee");
502 auto const accountNextIter = std::next(accountIter);
503
504 // Check if the next transaction for this account is earlier in the queue,
505 // which means we skipped it earlier, and need to try it again.
506 auto const feeNextIter = std::next(candidateIter);
507 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
508 accountNextIter->first > candidateIter->seqProxy &&
509 (feeNextIter == byFee_.end() || byFee_.value_comp()(accountNextIter->second, *feeNextIter));
510
511 auto const candidateNextIter = byFee_.erase(candidateIter);
512 txQAccount.transactions.erase(accountIter);
513
514 return useAccountNext ? byFee_.iterator_to(accountNextIter->second) : candidateNextIter;
515}
516
517auto
519 TxQ::TxQAccount& txQAccount,
520 TxQ::TxQAccount::TxMap::const_iterator begin,
521 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
522{
523 for (auto it = begin; it != end; ++it)
524 {
525 byFee_.erase(byFee_.iterator_to(it->second));
526 }
527 return txQAccount.transactions.erase(begin, end);
528}
529
532 Application& app,
533 OpenView& view,
534 STTx const& tx,
535 TxQ::AccountMap::iterator const& accountIter,
536 TxQAccount::TxMap::iterator beginTxIter,
537 FeeLevel64 feeLevelPaid,
538 PreflightResult const& pfResult,
539 std::size_t const txExtraCount,
540 ApplyFlags flags,
541 FeeMetrics::Snapshot const& metricsSnapshot,
543{
544 SeqProxy const tSeqProx{tx.getSeqProxy()};
545 XRPL_ASSERT(
546 beginTxIter != accountIter->second.transactions.end(),
547 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
548
549 // This check is only concerned with the range from
550 // [aSeqProxy, tSeqProxy)
551 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
552 auto const dist = std::distance(beginTxIter, endTxIter);
553
554 auto const requiredTotalFeeLevel =
555 FeeMetrics::escalatedSeriesFeeLevel(metricsSnapshot, view, txExtraCount, dist + 1);
556 // If the computation for the total manages to overflow (however extremely
557 // unlikely), then there's no way we can confidently verify if the queue
558 // can be cleared.
559 if (!requiredTotalFeeLevel.first)
560 return {telINSUF_FEE_P, false};
561
562 auto const totalFeeLevelPaid = std::accumulate(
563 beginTxIter, endTxIter, feeLevelPaid, [](auto const& total, auto const& txn) {
564 return total + txn.second.feeLevel;
565 });
566
567 // This transaction did not pay enough, so fall back to the normal process.
568 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
569 return {telINSUF_FEE_P, false};
570
571 // This transaction paid enough to clear out the queue.
572 // Attempt to apply the queued transactions.
573 for (auto it = beginTxIter; it != endTxIter; ++it)
574 {
575 auto txResult = it->second.apply(app, view, j);
576 // Succeed or fail, use up a retry, because if the overall
577 // process fails, we want the attempt to count. If it all
578 // succeeds, the MaybeTx will be destructed, so it'll be
579 // moot.
580 --it->second.retriesRemaining;
581 it->second.lastResult = txResult.ter;
582
583 // In TxQ::apply we note that it's possible for a transaction with
584 // a ticket to both be in the queue and in the ledger. And, while
585 // we're in TxQ::apply, it's too expensive to filter those out.
586 //
587 // So here in tryClearAccountQueueUpThruTx we just received a batch of
588 // queued transactions. And occasionally one of those is a ticketed
589 // transaction that is both in the queue and in the ledger. When
590 // that happens the queued transaction returns tefNO_TICKET.
591 //
592 // The transaction that returned tefNO_TICKET can never succeed
593 // and we'd like to get it out of the queue as soon as possible.
594 // The easiest way to do that from here is to treat the transaction
595 // as though it succeeded and attempt to clear the remaining
596 // transactions in the account queue. Then, if clearing the account
597 // is successful, we will have removed any ticketed transactions
598 // that can never succeed.
599 if (txResult.ter == tefNO_TICKET)
600 continue;
601
602 if (!txResult.applied)
603 {
604 // Transaction failed to apply. Fall back to the normal process.
605 return {txResult.ter, false};
606 }
607 }
608 // Apply the current tx. Because the state of the view has been changed
609 // by the queued txs, we also need to preclaim again.
610 auto const txResult = doApply(preclaim(pfResult, app, view), app, view);
611
612 if (txResult.applied)
613 {
614 // All of the queued transactions applied, so remove them from the
615 // queue.
616 endTxIter = erase(accountIter->second, beginTxIter, endTxIter);
617 // If `tx` is replacing a queued tx, delete that one, too.
618 if (endTxIter != accountIter->second.transactions.end() && endTxIter->first == tSeqProx)
619 erase(accountIter->second, endTxIter, std::next(endTxIter));
620 }
621
622 return txResult;
623}
624
625// Overview of considerations for when a transaction is accepted into the TxQ:
626//
627// These rules apply to the transactions in the queue owned by a single
628// account. Briefly, the primary considerations are:
629//
630// 1. Is the new transaction blocking?
631// 2. Is there an expiration gap in the account's sequence-based transactions?
632// 3. Does the new transaction replace one that is already in the TxQ?
633// 4. Is the transaction's sequence or ticket value acceptable for this account?
634// 5. Is the transaction likely to claim a fee?
635// 6. Is the queue full?
636//
637// Here are more details.
638//
639// 1. A blocking transaction is one that would change the validity of following
640// transactions for the issuing account. Examples of blocking transactions
641// include SetRegularKey and SignerListSet.
642//
643// A blocking transaction can only be added to the queue for an account if:
644//
645// a. The queue for that account is empty, or
646//
647// b. The blocking transaction replaces the only transaction in the
648// account's queue.
649//
650// While a blocker is in the account's queue no additional transactions
651// can be added to the queue.
652//
653// As a consequence, any blocker is always alone in the account's queue.
654//
655// 2. Transactions are given unique identifiers using either Sequence numbers
656// or Tickets. In general, sequence numbers in the queue are expected to
657// start with the account root sequence and increment from there. There
658// are two exceptions:
659//
660// a. Sequence holes left by ticket creation. If a transaction creates
661// more than one ticket, then the account sequence number will jump
662// by the number of tickets created. These holes are fine.
663//
664// b. Sequence gaps left by transaction expiration. If transactions stay
665// in the queue long enough they may expire. If that happens it leaves
666// gaps in the sequence numbers held by the queue. These gaps are
667// important because, if left in place, they will block any later
668// sequence-based transactions in the queue from working. Remember,
669// for any given account sequence numbers must be used consecutively
670// (with the exception of ticket-induced holes).
671//
672// 3. Transactions in the queue may be replaced. If a transaction in the
673// queue has the same SeqProxy as the incoming transaction, then the
674// transaction in the queue will be replaced if the following conditions
675// are met:
676//
677// a. The replacement must provide a fee that is at least 1.25 times the
678// fee of the transaction it is replacing.
679//
680// b. If the transaction being replaced has a sequence number, then
681// the transaction may not be after any expiration-based sequence
682// gaps in the account's queue.
683//
684// c. A replacement that is a blocker is only allowed if the transaction
685// it replaces is the only transaction in the account's queue.
686//
687// 4. The transaction that is not a replacement must have an acceptable
688// sequence or ticket ID:
689//
690// Sequence: For a given account's queue configuration there is at most
691// one sequence number that is acceptable to the queue for that account.
692// The rules are:
693//
694// a. If there are no sequence-based transactions in the queue and the
695// candidate transaction has a sequence number, that value must match
696// the account root's sequence.
697//
698// b. If there are sequence-based transactions in the queue for that
699// account and there are no expiration-based gaps, then the candidate's
700// sequence number must belong at the end of the list of sequences.
701//
702// c. If there are expiration-based gaps in the sequence-based
703// transactions in the account's queue, then the candidate's sequence
704// value must go precisely at the front of the first gap.
705//
706// Ticket: If there are no blockers or sequence gaps in the account's
707// queue, then there are many tickets that are acceptable to the queue
708// for that account. The rules are:
709//
710// a. If there are no blockers in the account's queue and the ticket
711// required by the transaction is in the ledger then the transaction
712// may be added to the account's queue.
713//
714// b. If there is a ticket-based blocker in the account's queue then
715// that blocker can be replaced.
716//
717// Note that it is not sufficient for the transaction that would create
718// the necessary ticket to be in the account's queue. The required ticket
719// must already be in the ledger. This avoids problems that can occur if
720// a ticket-creating transaction enters the queue but expires out of the
721// queue before its tickets are created.
722//
723// 5. The transaction must be likely to claim a fee. In general that is
724// checked by having preclaim return a tes or tec code.
725//
726// Extra work is done here to account for funds that other transactions
727// in the queue remove from the account.
728//
729// 6. The queue must not be full.
730//
731// a. Each account can queue up to a maximum of 10 transactions. Beyond
732// that transactions are rejected. There is an exception for this case
733// when filling expiration-based sequence gaps.
734//
735// b. The entire queue also has a (dynamic) maximum size. Transactions
736// beyond that limit are rejected.
737//
740 Application& app,
741 OpenView& view,
743 ApplyFlags flags,
745{
746 // See if the transaction is valid, properly formed,
747 // etc. before doing potentially expensive queue
748 // replace and multi-transaction operations.
749 auto const pfResult = preflight(app, view.rules(), *tx, flags, j);
750 if (!isTesSuccess(pfResult.ter))
751 return {pfResult.ter, false};
752
753 // See if the transaction paid a high enough fee that it can go straight
754 // into the ledger.
755 if (auto directApplied = tryDirectApply(app, view, tx, flags, j))
756 return *directApplied;
757
758 if ((flags & TapDryRun) != 0u)
759 return {telCAN_NOT_QUEUE, false};
760
761 // If we get past tryDirectApply() without returning then we expect
762 // one of the following to occur:
763 //
764 // o We will decide the transaction is unlikely to claim a fee.
765 // o The transaction paid a high enough fee that fee averaging will apply.
766 // o The transaction will be queued.
767
768 // If the account is not currently in the ledger, don't queue its tx.
769 auto const account = (*tx)[sfAccount];
770 Keylet const accountKey{keylet::account(account)};
771 auto const sleAccount = view.read(accountKey);
772 if (!sleAccount)
773 return {terNO_ACCOUNT, false};
774
775 // If the transaction needs a Ticket is that Ticket in the ledger?
776 SeqProxy const acctSeqProx = SeqProxy::rawSequence((*sleAccount)[sfSequence]);
777 SeqProxy const txSeqProx = tx->getSeqProxy();
778 if (txSeqProx.isTicket() && !view.exists(keylet::ticket(account, txSeqProx)))
779 {
780 if (txSeqProx.value() < acctSeqProx.value())
781 {
782 // The ticket number is low enough that it should already be
783 // in the ledger if it were ever going to exist.
784 return {tefNO_TICKET, false};
785 }
786
787 // We don't queue transactions that use Tickets unless
788 // we can find the Ticket in the ledger.
789 return {terPRE_TICKET, false};
790 }
791
792 std::scoped_lock const lock(mutex_);
793
794 // accountIter is not const because it may be updated further down.
795 auto accountIter = byAccount_.find(account);
796 bool const accountIsInQueue = accountIter != byAccount_.end();
797
798 // _If_ the account is in the queue, then ignore any sequence-based
799 // queued transactions that slipped into the ledger while we were not
800 // watching. This does actually happen in the wild, but it's uncommon.
801 //
802 // Note that we _don't_ ignore queued ticket-based transactions that
803 // slipped into the ledger while we were not watching. It would be
804 // desirable to do so, but the measured cost was too high since we have
805 // to individually check each queued ticket against the ledger.
806 struct TxIter
807 {
808 TxIter(TxQAccount::TxMap::iterator first, TxQAccount::TxMap::iterator end)
809 : first(first), end(end)
810 {
811 }
812
813 TxQAccount::TxMap::iterator first;
814 TxQAccount::TxMap::iterator end;
815 };
816
817 std::optional<TxIter> const txIter =
818 [accountIter, accountIsInQueue, acctSeqProx]() -> std::optional<TxIter> {
819 if (!accountIsInQueue)
820 return {};
821
822 // Find the first transaction in the queue that we might apply.
823 TxQAccount::TxMap& acctTxs = accountIter->second.transactions;
824 auto const firstIter = acctTxs.lower_bound(acctSeqProx);
825
826 if (firstIter == acctTxs.end())
827 {
828 // Even though there may be transactions in the queue, there are
829 // none that we should pay attention to.
830 return {};
831 }
832
833 return {TxIter{firstIter, acctTxs.end()}};
834 }();
835
836 auto const acctTxCount{!txIter ? 0 : std::distance(txIter->first, txIter->end)};
837
838 // Is tx a blocker? If so there are very limited conditions when it
839 // is allowed in the TxQ:
840 // 1. If the account's queue is empty or
841 // 2. If the blocker replaces the only entry in the account's queue.
842 auto const transactionID = tx->getTransactionID();
843 if (pfResult.consequences.isBlocker())
844 {
845 if (acctTxCount > 1)
846 {
847 // A blocker may not be co-resident with other transactions in
848 // the account's queue.
849 JLOG(j_.trace()) << "Rejecting blocker transaction " << transactionID
850 << ". Account has other queued transactions.";
851 return {telCAN_NOT_QUEUE_BLOCKS, false};
852 }
853 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) acctTxCount == 1 implies txIter is set
854 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
855 {
856 // The blocker is not replacing the lone queued transaction.
857 JLOG(j_.trace()) << "Rejecting blocker transaction " << transactionID
858 << ". Blocker does not replace lone queued transaction.";
859 return {telCAN_NOT_QUEUE_BLOCKS, false};
860 }
861 }
862
863 // If the transaction is intending to replace a transaction in the queue
864 // identify the one that might be replaced.
865 auto replacedTxIter = [accountIsInQueue,
866 &accountIter,
868 if (accountIsInQueue)
869 {
870 TxQAccount& txQAcct = accountIter->second;
871 if (auto const existingIter = txQAcct.transactions.find(txSeqProx);
872 existingIter != txQAcct.transactions.end())
873 return existingIter;
874 }
875 return {};
876 }();
877
878 // We may need the base fee for multiple transactions or transaction
879 // replacement, so just pull it up now.
880 auto const metricsSnapshot = feeMetrics_.getSnapshot();
881 auto const feeLevelPaid = getFeeLevelPaid(view, *tx);
882 auto const requiredFeeLevel = getRequiredFeeLevel(view, flags, metricsSnapshot, lock);
883
884 // Is there a blocker already in the account's queue? If so, don't
885 // allow additional transactions in the queue.
886 if (acctTxCount > 0)
887 {
888 // Allow tx to replace a blocker. Otherwise, if there's a
889 // blocker, we can't queue tx.
890 //
891 // We only need to check if txIter->first is a blocker because we
892 // require that a blocker be alone in the account's queue.
893 // NOLINTBEGIN(bugprone-unchecked-optional-access) acctTxCount == 1 implies txIter is set
894 if (acctTxCount == 1 && txIter->first->second.consequences().isBlocker() &&
895 (txIter->first->first != txSeqProx))
896 // NOLINTEND(bugprone-unchecked-optional-access)
897 {
898 return {telCAN_NOT_QUEUE_BLOCKED, false};
899 }
900
901 // Is there a transaction for the same account with the same
902 // SeqProxy already in the queue? If so we may replace the
903 // existing entry with this new transaction.
904 if (replacedTxIter)
905 {
906 // We are attempting to replace a transaction in the queue.
907 //
908 // Is the current transaction's fee higher than
909 // the queued transaction's fee + a percentage
910 TxQAccount::TxMap::iterator const& existingIter = *replacedTxIter;
911 auto requiredRetryLevel =
912 increase(existingIter->second.feeLevel, setup_.retrySequencePercent);
913 JLOG(j_.trace()) << "Found transaction in queue for account " << account << " with "
914 << txSeqProx << " new txn fee level is " << feeLevelPaid
915 << ", old txn fee level is " << existingIter->second.feeLevel
916 << ", new txn needs fee level of " << requiredRetryLevel;
917 if (feeLevelPaid > requiredRetryLevel)
918 {
919 // Continue, leaving the queued transaction marked for removal.
920 // DO NOT REMOVE if the new tx fails, because there may
921 // be other txs dependent on it in the queue.
922 JLOG(j_.trace()) << "Removing transaction from queue " << existingIter->second.txID
923 << " in favor of " << transactionID;
924 }
925 else
926 {
927 // Drop the current transaction
928 JLOG(j_.trace()) << "Ignoring transaction " << transactionID
929 << " in favor of queued " << existingIter->second.txID;
930 return {telCAN_NOT_QUEUE_FEE, false};
931 }
932 }
933 }
934
935 struct MultiTxn
936 {
937 ApplyViewImpl applyView;
938 OpenView openView;
939
940 MultiTxn(OpenView& view, ApplyFlags flags) : applyView(&view, flags), openView(&applyView)
941 {
942 }
943 };
944
946
947 if (acctTxCount == 0)
948 {
949 // There are no queued transactions for this account. If the
950 // transaction has a sequence make sure it's valid (tickets
951 // are checked elsewhere).
952 if (txSeqProx.isSeq())
953 {
954 if (acctSeqProx > txSeqProx)
955 return {tefPAST_SEQ, false};
956 if (acctSeqProx < txSeqProx)
957 return {terPRE_SEQ, false};
958 }
959 }
960 else
961 {
962 // There are probably other transactions in the queue for this
963 // account. Make sure the new transaction can work with the others
964 // in the queue.
965 TxQAccount const& txQAcct = accountIter->second;
966
967 if (acctSeqProx > txSeqProx)
968 return {tefPAST_SEQ, false};
969
970 // Determine if we need a multiTxn object. Assuming the account
971 // is in the queue, there are two situations where we need to
972 // build multiTx:
973 // 1. If there are two or more transactions in the account's queue, or
974 // 2. If the account has a single queue entry, we may still need
975 // multiTxn, but only if that lone entry will not be replaced by tx.
976 bool requiresMultiTxn = false;
977 if (acctTxCount > 1 || !replacedTxIter)
978 {
979 // If the transaction is queueable, create the multiTxn
980 // object to hold the info we need to adjust for prior txns.
981 TER const ter{
982 canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
983 if (!isTesSuccess(ter))
984 return {ter, false};
985
986 requiresMultiTxn = true;
987 }
988
989 if (requiresMultiTxn)
990 {
991 // See if adding this entry to the queue makes sense.
992 //
993 // o Transactions with sequences should start with the
994 // account's Sequence.
995 //
996 // o Additional transactions with Sequences should
997 // follow preceding sequence-based transactions with no
998 // gaps (except for those required by TicketCreate
999 // transactions).
1000
1001 // Find the entry in the queue that precedes the new
1002 // transaction, if one does.
1003 auto const prevIter = txQAcct.getPrevTx(txSeqProx);
1004
1005 // Does the new transaction go to the front of the queue?
1006 // This can happen if:
1007 // o A transaction in the queue with a Sequence expired, or
1008 // o The current first thing in the queue has a Ticket and
1009 // * The tx has a Ticket that precedes it or
1010 // * txSeqProx == acctSeqProx.
1011 // NOLINTBEGIN(bugprone-unchecked-optional-access) acctTxCount > 0 in else branch
1012 // implies txIter is set
1013 XRPL_ASSERT(prevIter != txIter->end, "xrpl::TxQ::apply : not end");
1014 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1015 {
1016 // The first Sequence number in the queue must be the
1017 // account's sequence.
1018 if (txSeqProx.isSeq())
1019 {
1020 if (txSeqProx < acctSeqProx)
1021 {
1022 return {tefPAST_SEQ, false};
1023 }
1024 if (txSeqProx > acctSeqProx)
1025 {
1026 return {terPRE_SEQ, false};
1027 }
1028 }
1029 }
1030 else if (!replacedTxIter)
1031 {
1032 // The current transaction is not replacing a transaction
1033 // in the queue. So apparently there's a transaction in
1034 // front of this one in the queue. Make sure the current
1035 // transaction fits in proper sequence order with the
1036 // previous transaction or is a ticket.
1037 if (txSeqProx.isSeq() && nextQueuableSeqImpl(sleAccount, lock) != txSeqProx)
1038 return {telCAN_NOT_QUEUE, false};
1039 }
1040
1041 // Sum fees and spending for all of the queued transactions
1042 // so we know how much to remove from the account balance
1043 // for the trial preclaim.
1044 XRPAmount potentialSpend = beast::kZero;
1045 XRPAmount totalFee = beast::kZero;
1046 for (auto iter = txIter->first; iter != txIter->end; ++iter)
1047 {
1048 // If we're replacing this transaction don't include
1049 // the replaced transaction's XRP spend. Otherwise add
1050 // it to potentialSpend.
1051 if (iter->first != txSeqProx)
1052 {
1053 totalFee += iter->second.consequences().fee();
1054 potentialSpend += iter->second.consequences().potentialSpend();
1055 }
1056 else if (std::next(iter) != txIter->end)
1057 {
1058 // The fee for the candidate transaction _should_ be
1059 // counted if it's replacing a transaction in the middle
1060 // of the queue.
1061 totalFee += pfResult.consequences.fee();
1062 potentialSpend += pfResult.consequences.potentialSpend();
1063 }
1064 }
1065 // NOLINTEND(bugprone-unchecked-optional-access)
1066
1067 /* Check if the total fees in flight are greater
1068 than the account's current balance, or the
1069 minimum reserve. If it is, then there's a risk
1070 that the fees won't get paid, so drop this
1071 transaction with a telCAN_NOT_QUEUE_BALANCE result.
1072 Assume: Minimum account reserve is 20 XRP.
1073 Example 1: If I have 1,000,000 XRP, I can queue
1074 a transaction with a 1,000,000 XRP fee. In
1075 the meantime, some other transaction may
1076 lower my balance (eg. taking an offer). When
1077 the transaction executes, I will either
1078 spend the 1,000,000 XRP, or the transaction
1079 will get stuck in the queue with a
1080 `terINSUF_FEE_B`.
1081 Example 2: If I have 1,000,000 XRP, and I queue
1082 10 transactions with 0.1 XRP fee, I have 1 XRP
1083 in flight. I can now queue another tx with a
1084 999,999 XRP fee. When the first 10 execute,
1085 they're guaranteed to pay their fee, because
1086 nothing can eat into my reserve. The last
1087 transaction, again, will either spend the
1088 999,999 XRP, or get stuck in the queue.
1089 Example 3: If I have 1,000,000 XRP, and I queue
1090 7 transactions with 3 XRP fee, I have 21 XRP
1091 in flight. I can not queue any more transactions,
1092 no matter how small or large the fee.
1093 Transactions stuck in the queue are mitigated by
1094 LastLedgerSeq and MaybeTx::retriesRemaining.
1095 */
1096 auto const balance = (*sleAccount)[sfBalance].xrp();
1097 /* Get the minimum possible account reserve. If it
1098 is at least 10 * the base fee, and fees exceed
1099 this amount, the transaction can't be queued.
1100
1101 Currently typical fees are several orders
1102 of magnitude smaller than any current or expected
1103 future reserve. This calculation is simpler than
1104 trying to figure out the potential changes to
1105 the ownerCount that may occur to the account
1106 as a result of these transactions, and removes
1107 any need to account for other transactions that
1108 may affect the owner count while these are queued.
1109
1110 However, in case the account reserve is on a
1111 comparable scale to the base fee, ignore the
1112 reserve. Only check the account balance.
1113 */
1114 auto const reserve = view.fees().reserve;
1115 auto const base = view.fees().base;
1116 if (totalFee >= balance || (reserve > 10 * base && totalFee >= reserve))
1117 {
1118 // Drop the current transaction
1119 JLOG(j_.trace()) << "Ignoring transaction " << transactionID
1120 << ". Total fees in flight too high.";
1121 return {telCAN_NOT_QUEUE_BALANCE, false};
1122 }
1123
1124 // Create the test view from the current view.
1125 multiTxn.emplace(view, flags);
1126
1127 auto const sleBump = multiTxn->applyView.peek(accountKey);
1128 if (!sleBump)
1129 return {tefINTERNAL, false};
1130
1131 // Subtract the fees and XRP spend from all of the other
1132 // transactions in the queue. That prevents a transaction
1133 // inserted in the middle from fouling up later transactions.
1134 auto const potentialTotalSpend =
1135 totalFee + std::min(balance - std::min(balance, reserve), potentialSpend);
1136 XRPL_ASSERT(
1137 potentialTotalSpend > XRPAmount{0} ||
1138 (potentialTotalSpend == XRPAmount{0} && multiTxn->applyView.fees().base == 0),
1139 "xrpl::TxQ::apply : total spend check");
1140 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1141 // The transaction's sequence/ticket will be valid when the other
1142 // transactions in the queue have been processed. If the tx has a
1143 // sequence, set the account to match it. If it has a ticket, use
1144 // the next queueable sequence, which is the closest approximation
1145 // to the most successful case.
1146 sleBump->at(sfSequence) = txSeqProx.isSeq()
1147 ? txSeqProx.value()
1148 : nextQueuableSeqImpl(sleAccount, lock).value();
1149 }
1150 }
1151
1152 // See if the transaction is likely to claim a fee.
1153 //
1154 // We assume that if the transaction survives preclaim(), then it
1155 // is likely to claim a fee. However we can't allow preclaim to
1156 // check the sequence/ticket. Transactions in the queue may be
1157 // responsible for increasing the sequence, and mocking those up
1158 // is non-trivially expensive.
1159 //
1160 // Note that earlier code has already verified that the sequence/ticket
1161 // is valid. So we use a special entry point that runs all of the
1162 // preclaim checks with the exception of the sequence check.
1163 auto const pcresult = preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1164 if (!pcresult.likelyToClaimFee)
1165 return {pcresult.ter, false};
1166
1167 // Too low of a fee should get caught by preclaim
1168 XRPL_ASSERT(feeLevelPaid >= kBaseLevel, "xrpl::TxQ::apply : minimum fee");
1169
1170 JLOG(j_.trace()) << "Transaction " << transactionID << " from account " << account
1171 << " has fee level of " << feeLevelPaid << " needs at least "
1172 << requiredFeeLevel << " to get in the open ledger, which has "
1173 << view.txCount() << " entries.";
1174
1175 /* Quick heuristic check to see if it's worth checking that this tx has
1176 a high enough fee to clear all the txs in front of it in the queue.
1177 1) Transaction is trying to get into the open ledger.
1178 2) Transaction must be Sequence-based.
1179 3) Must be an account already in the queue.
1180 4) Must be have passed the multiTxn checks (tx is not the next
1181 account seq, the skipped seqs are in the queue, the reserve
1182 doesn't get exhausted, etc).
1183 5) The next transaction must not have previously tried and failed
1184 to apply to an open ledger.
1185 6) Tx must be paying more than just the required fee level to
1186 get itself into the queue.
1187 7) Fee level must be escalated above the default (if it's not,
1188 then the first tx _must_ have failed to process in `accept`
1189 for some other reason. Tx is allowed to queue in case
1190 conditions change, but don't waste the effort to clear).
1191 */
1192 if (txSeqProx.isSeq() && txIter && multiTxn.has_value() &&
1193 txIter->first->second.retriesRemaining == MaybeTx::kRetriesAllowed &&
1194 feeLevelPaid > requiredFeeLevel && requiredFeeLevel > kBaseLevel)
1195 {
1196 OpenView sandbox(kOpenLedger, &view, view.rules());
1197
1198 auto result = tryClearAccountQueueUpThruTx(
1199 app,
1200 sandbox,
1201 *tx,
1202 accountIter,
1203 txIter->first,
1204 feeLevelPaid,
1205 pfResult,
1206 view.txCount(),
1207 flags,
1208 metricsSnapshot,
1209 j);
1210 if (result.applied)
1211 {
1212 sandbox.apply(view);
1213 /* Can't erase (*replacedTxIter) here because success
1214 implies that it has already been deleted.
1215 */
1216 return result;
1217 }
1218 }
1219
1220 // If `multiTxn` has a value, then `canBeHeld` has already been verified
1221 if (!multiTxn)
1222 {
1223 TER const ter{canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1224 if (!isTesSuccess(ter))
1225 {
1226 // Bail, transaction cannot be held
1227 JLOG(j_.trace()) << "Transaction " << transactionID << " cannot be held";
1228 return {ter, false};
1229 }
1230 }
1231
1232 // If the queue is full, decide whether to drop the current
1233 // transaction or the last transaction for the account with
1234 // the lowest fee.
1235 if (!replacedTxIter && isFull())
1236 {
1237 auto lastRIter = byFee_.rbegin();
1238 while (lastRIter != byFee_.rend() && lastRIter->account == account)
1239 {
1240 ++lastRIter;
1241 }
1242 if (lastRIter == byFee_.rend())
1243 {
1244 // The only way this condition can happen is if the entire
1245 // queue is filled with transactions from this account. This
1246 // is impossible with default settings - minimum queue size
1247 // is 2000, and an account can only have 10 transactions
1248 // queued. However, it can occur if settings are changed,
1249 // and there is unit test coverage.
1250 JLOG(j_.info()) << "Queue is full, and transaction " << transactionID
1251 << " would kick a transaction from the same account (" << account
1252 << ") out of the queue.";
1253 return {telCAN_NOT_QUEUE_FULL, false};
1254 }
1255 auto const& endAccount = byAccount_.at(lastRIter->account);
1256 auto endEffectiveFeeLevel = [&]() {
1257 // Compute the average of all the txs for the endAccount,
1258 // but only if the last tx in the queue has a lower fee
1259 // level than this candidate tx.
1260 if (lastRIter->feeLevel > feeLevelPaid || endAccount.transactions.size() == 1)
1261 return lastRIter->feeLevel;
1262
1264 auto endTotal = std::accumulate(
1265 endAccount.transactions.begin(),
1266 endAccount.transactions.end(),
1268 [&](auto const& total, auto const& txn) -> std::pair<FeeLevel64, FeeLevel64> {
1269 // Check for overflow.
1270 auto next = txn.second.feeLevel / endAccount.transactions.size();
1271 auto mod = txn.second.feeLevel % endAccount.transactions.size();
1272 if (total.first >= kMax - next || total.second >= kMax - mod)
1273 return {kMax, FeeLevel64{0}};
1274
1275 return {total.first + next, total.second + mod};
1276 });
1277 return endTotal.first + endTotal.second / endAccount.transactions.size();
1278 }();
1279 if (feeLevelPaid > endEffectiveFeeLevel)
1280 {
1281 // The queue is full, and this transaction is more
1282 // valuable, so kick out the cheapest transaction.
1283 auto dropRIter = endAccount.transactions.rbegin();
1284 XRPL_ASSERT(
1285 dropRIter->second.account == lastRIter->account,
1286 "xrpl::TxQ::apply : cheapest transaction found");
1287 JLOG(j_.info()) << "Removing last item of account " << lastRIter->account
1288 << " from queue with average fee of " << endEffectiveFeeLevel
1289 << " in favor of " << transactionID << " with fee of " << feeLevelPaid;
1290 erase(byFee_.iterator_to(dropRIter->second));
1291 }
1292 else
1293 {
1294 JLOG(j_.info()) << "Queue is full, and transaction " << transactionID
1295 << " fee is lower than end item's account average fee";
1296 return {telCAN_NOT_QUEUE_FULL, false};
1297 }
1298 }
1299
1300 // Hold the transaction in the queue.
1301 if (replacedTxIter)
1302 {
1303 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1304 }
1305
1306 if (!accountIsInQueue)
1307 {
1308 // Create a new TxQAccount object and add the byAccount lookup.
1309 [[maybe_unused]] bool created = false;
1310 std::tie(accountIter, created) = byAccount_.emplace(account, TxQAccount(tx));
1311 XRPL_ASSERT(created, "xrpl::TxQ::apply : account created");
1312 }
1313 // Modify the flags for use when coming out of the queue.
1314 // These changes _may_ cause an extra `preflight`, but as long as
1315 // the `HashRouter` still knows about the transaction, the signature
1316 // will not be checked again, so the cost should be minimal.
1317
1318 // Don't allow soft failures, which can lead to retries
1319 flags &= ~TapRetry;
1320
1321 auto& candidate = accountIter->second.add({tx, transactionID, feeLevelPaid, flags, pfResult});
1322
1323 // Then index it into the byFee lookup.
1324 byFee_.insert(candidate);
1325 JLOG(j_.debug()) << "Added transaction " << candidate.txID << " with result "
1326 << transToken(pfResult.ter) << " from "
1327 << (accountIsInQueue ? "existing" : "new") << " account " << candidate.account
1328 << " to queue."
1329 << " Flags: " << flags;
1330
1331 return {terQUEUED, false};
1332}
1333
1334/*
1335 1. Update the fee metrics based on the fee levels of the
1336 txs in the validated ledger and whether consensus is
1337 slow.
1338 2. Adjust the maximum queue size to be enough to hold
1339 `ledgersInQueue` ledgers.
1340 3. Remove any transactions from the queue for which the
1341 `LastLedgerSequence` has passed.
1342 4. Remove any account objects that have no candidates
1343 under them.
1344
1345*/
1346void
1347TxQ::processClosedLedger(Application& app, ReadView const& view, bool timeLeap)
1348{
1349 std::scoped_lock const lock(mutex_);
1350
1351 feeMetrics_.update(app, view, timeLeap, setup_);
1352 auto const& snapshot = feeMetrics_.getSnapshot();
1353
1354 auto ledgerSeq = view.header().seq;
1355
1356 if (!timeLeap)
1357 maxSize_ = std::max(snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1358
1359 // Remove any queued candidates whose LastLedgerSequence has gone by.
1360 for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1361 {
1362 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1363 {
1364 byAccount_.at(candidateIter->account).dropPenalty = true;
1365 candidateIter = erase(candidateIter);
1366 }
1367 else
1368 {
1369 ++candidateIter;
1370 }
1371 }
1372
1373 // Remove any TxQAccounts that don't have candidates
1374 // under them
1375 for (auto txQAccountIter = byAccount_.begin(); txQAccountIter != byAccount_.end();)
1376 {
1377 if (txQAccountIter->second.empty())
1378 {
1379 txQAccountIter = byAccount_.erase(txQAccountIter);
1380 }
1381 else
1382 {
1383 ++txQAccountIter;
1384 }
1385 }
1386}
1387
1388/*
1389 How the txs are moved from the queue to the new open ledger.
1390
1391 1. Iterate over the txs from highest fee level to lowest.
1392 For each tx:
1393 a) Is this the first tx in the queue for this account?
1394 No: Skip this tx. We'll come back to it later.
1395 Yes: Continue to the next sub-step.
1396 b) Is the tx fee level less than the current required
1397 fee level?
1398 Yes: Stop iterating. Continue to the next step.
1399 No: Try to apply the transaction. Did it apply?
1400 Yes: Take it out of the queue. Continue with
1401 the next appropriate candidate (see below).
1402 No: Did it get a tef, tem, or tel, or has it
1403 retried `MaybeTx::retriesAllowed`
1404 times already?
1405 Yes: Take it out of the queue. Continue
1406 with the next appropriate candidate
1407 (see below).
1408 No: Leave it in the queue, track the retries,
1409 and continue iterating.
1410 2. Return indicator of whether the open ledger was modified.
1411
1412 "Appropriate candidate" is defined as the tx that has the
1413 highest fee level of:
1414 * the tx for the current account with the next sequence.
1415 * the next tx in the queue, simply ordered by fee.
1416*/
1417bool
1419{
1420 /* Move transactions from the queue from largest fee level to smallest.
1421 As we add more transactions, the required fee level will increase.
1422 Stop when the transaction fee level gets lower than the required fee
1423 level.
1424 */
1425
1426 auto ledgerChanged = false;
1427
1428 std::scoped_lock const lock(mutex_);
1429
1430 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1431
1432 for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1433 {
1434 auto& account = byAccount_.at(candidateIter->account);
1435 auto const beginIter = account.transactions.begin();
1436 if (candidateIter->seqProxy.isSeq() && candidateIter->seqProxy > beginIter->first)
1437 {
1438 // There is a sequence transaction at the front of the queue and
1439 // candidate has a later sequence, so skip this candidate. We
1440 // need to process sequence-based transactions in sequence order.
1441 JLOG(j_.trace()) << "Skipping queued transaction " << candidateIter->txID
1442 << " from account " << candidateIter->account
1443 << " as it is not the first.";
1444 candidateIter++;
1445 continue;
1446 }
1447 auto const requiredFeeLevel = getRequiredFeeLevel(view, TapNone, metricsSnapshot, lock);
1448 auto const feeLevelPaid = candidateIter->feeLevel;
1449 JLOG(j_.trace()) << "Queued transaction " << candidateIter->txID << " from account "
1450 << candidateIter->account << " has fee level of " << feeLevelPaid
1451 << " needs at least " << requiredFeeLevel;
1452 if (feeLevelPaid >= requiredFeeLevel)
1453 {
1454 JLOG(j_.trace()) << "Applying queued transaction " << candidateIter->txID
1455 << " to open ledger.";
1456
1457 auto const [txnResult, didApply, _metadata] = candidateIter->apply(app, view, j_);
1458
1459 if (didApply)
1460 {
1461 // Remove the candidate from the queue
1462 JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
1463 << " applied successfully with " << transToken(txnResult)
1464 << ". Remove from queue.";
1465
1466 candidateIter = eraseAndAdvance(candidateIter);
1467 ledgerChanged = true;
1468 }
1469 else if (
1470 isTefFailure(txnResult) || isTemMalformed(txnResult) ||
1471 candidateIter->retriesRemaining <= 0)
1472 {
1473 if (candidateIter->retriesRemaining <= 0)
1474 {
1475 account.retryPenalty = true;
1476 }
1477 else
1478 {
1479 account.dropPenalty = true;
1480 }
1481 JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID << " failed with "
1482 << transToken(txnResult) << ". Remove from queue.";
1483 candidateIter = eraseAndAdvance(candidateIter);
1484 }
1485 else
1486 {
1487 JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID << " failed with "
1488 << transToken(txnResult) << ". Leave in queue."
1489 << " Applied: " << didApply << ". Flags: " << candidateIter->flags;
1490 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1491 {
1492 candidateIter->retriesRemaining = 1;
1493 }
1494 else
1495 {
1496 --candidateIter->retriesRemaining;
1497 }
1498 candidateIter->lastResult = txnResult;
1499 if (account.dropPenalty && account.transactions.size() > 1 && isFull<95>())
1500 {
1501 // The queue is close to full, this account has multiple
1502 // txs queued, and this account has had a transaction
1503 // fail.
1504 if (candidateIter->seqProxy.isTicket())
1505 {
1506 // Since the failed transaction has a ticket, order
1507 // doesn't matter. Drop this one.
1508 JLOG(j_.info())
1509 << "Queue is nearly full, and transaction " << candidateIter->txID
1510 << " failed with " << transToken(txnResult)
1511 << ". Removing ticketed tx from account " << account.account;
1512 candidateIter = eraseAndAdvance(candidateIter);
1513 }
1514 else
1515 {
1516 // Even though we're giving this transaction another
1517 // chance, chances are it won't recover. To avoid
1518 // making things worse, drop the _last_ transaction for
1519 // this account.
1520 auto dropRIter = account.transactions.rbegin();
1521 XRPL_ASSERT(
1522 dropRIter->second.account == candidateIter->account,
1523 "xrpl::TxQ::accept : account check");
1524
1525 JLOG(j_.info())
1526 << "Queue is nearly full, and transaction " << candidateIter->txID
1527 << " failed with " << transToken(txnResult)
1528 << ". Removing last item from account " << account.account;
1529 auto endIter = byFee_.iterator_to(dropRIter->second);
1530 if (endIter != candidateIter)
1531 erase(endIter);
1532 ++candidateIter;
1533 }
1534 }
1535 else
1536 {
1537 ++candidateIter;
1538 }
1539 }
1540 }
1541 else
1542 {
1543 break;
1544 }
1545 }
1546
1547 // All transactions that can be moved out of the queue into the open
1548 // ledger have been. Rebuild the queue using the open ledger's
1549 // parent hash, so that transactions paying the same fee are
1550 // reordered.
1551 LedgerHash const& parentHash = view.header().parentHash;
1552 if (parentHash == parentHash_)
1553 {
1554 JLOG(j_.warn()) << "Parent ledger hash unchanged from " << parentHash;
1555 }
1556 else
1557 {
1558 parentHash_ = parentHash;
1559 }
1560
1561 [[maybe_unused]] auto const startingSize = byFee_.size();
1562 // byFee_ doesn't "own" the candidate objects inside it, so it's
1563 // perfectly safe to wipe it and start over, repopulating from
1564 // byAccount_.
1565 //
1566 // In the absence of a "re-sort the list in place" function, this
1567 // was the fastest method tried to repopulate the list.
1568 // Other methods included: create a new list and moving items over one at a
1569 // time, create a new list and merge the old list into it.
1570 byFee_.clear();
1571
1572 MaybeTx::parentHashComp = parentHash;
1573
1574 for (auto& [_, account] : byAccount_)
1575 {
1576 for (auto& [_, candidate] : account.transactions)
1577 {
1578 byFee_.insert(candidate);
1579 }
1580 }
1581 XRPL_ASSERT(byFee_.size() == startingSize, "xrpl::TxQ::accept : byFee size match");
1582
1583 return ledgerChanged;
1584}
1585
1586// Public entry point for nextQueuableSeq().
1587//
1588// Acquires a lock and calls the implementation.
1591{
1592 std::scoped_lock const lock(mutex_);
1593 return nextQueuableSeqImpl(sleAccount, lock);
1594}
1595
1596// The goal is to return a SeqProxy for a sequence that will fill the next
1597// available hole in the queue for the passed in account.
1598//
1599// If there are queued transactions for the account then the first viable
1600// sequence number, that is not used by a transaction in the queue, must
1601// be found and returned.
1604{
1605 // If the account is not in the ledger or a non-account was passed
1606 // then return zero. We have no idea.
1607 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1608 return SeqProxy::rawSequence(0);
1609
1610 SeqProxy const acctSeqProx = SeqProxy::rawSequence((*sleAccount)[sfSequence]);
1611
1612 // If the account is not in the queue then acctSeqProx is good enough.
1613 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1614 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1615 return acctSeqProx;
1616
1617 TxQAccount::TxMap const& acctTxs = accountIter->second.transactions;
1618
1619 // Ignore any sequence-based queued transactions that slipped into the
1620 // ledger while we were not watching. This does actually happen in the
1621 // wild, but it's uncommon.
1622 auto txIter = acctTxs.lower_bound(acctSeqProx);
1623
1624 if (txIter == acctTxs.end() || !txIter->first.isSeq() || txIter->first != acctSeqProx)
1625 {
1626 // Either...
1627 // o There are no queued sequence-based transactions equal to or
1628 // following acctSeqProx or
1629 // o acctSeqProx is not currently in the queue.
1630 // So acctSeqProx is as good as it gets.
1631 return acctSeqProx;
1632 }
1633
1634 // There are sequence-based transactions queued that follow acctSeqProx.
1635 // Locate the first opening to put a transaction into.
1636 SeqProxy attempt = txIter->second.consequences().followingSeq();
1637 while (++txIter != acctTxs.cend())
1638 {
1639 if (attempt < txIter->first)
1640 break;
1641
1642 attempt = txIter->second.consequences().followingSeq();
1643 }
1644 return attempt;
1645}
1646
1649 OpenView& view,
1650 ApplyFlags flags,
1651 FeeMetrics::Snapshot const& metricsSnapshot,
1652 std::scoped_lock<std::mutex> const& lock)
1653{
1654 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1655}
1656
1659 Application& app,
1660 OpenView& view,
1662 ApplyFlags flags,
1664{
1665 auto const account = (*tx)[sfAccount];
1666 auto const sleAccount = view.read(keylet::account(account));
1667
1668 // Don't attempt to direct apply if the account is not in the ledger.
1669 if (!sleAccount)
1670 return {};
1671
1672 SeqProxy const acctSeqProx = SeqProxy::rawSequence((*sleAccount)[sfSequence]);
1673 SeqProxy const txSeqProx = tx->getSeqProxy();
1674
1675 // Can only directly apply if the transaction sequence matches the account
1676 // sequence or if the transaction uses a ticket.
1677 if (txSeqProx.isSeq() && txSeqProx != acctSeqProx)
1678 return {};
1679
1680 FeeLevel64 const requiredFeeLevel = [this, &view, flags]() {
1681 std::scoped_lock const lock(mutex_);
1682 return getRequiredFeeLevel(view, flags, feeMetrics_.getSnapshot(), lock);
1683 }();
1684
1685 // If the transaction's fee is high enough we may be able to put the
1686 // transaction straight into the ledger.
1687 FeeLevel64 const feeLevelPaid = getFeeLevelPaid(view, *tx);
1688
1689 if (feeLevelPaid >= requiredFeeLevel)
1690 {
1691 // Attempt to apply the transaction directly.
1692 auto const transactionID = tx->getTransactionID();
1693 JLOG(j_.trace()) << "Applying transaction " << transactionID << " to open ledger.";
1694
1695 auto const [txnResult, didApply, metadata] = xrpl::apply(app, view, *tx, flags, j);
1696
1697 JLOG(j_.trace()) << "New transaction " << transactionID
1698 << (didApply ? " applied successfully with " : " failed with ")
1699 << transToken(txnResult);
1700
1701 if (didApply)
1702 {
1703 // If the applied transaction replaced a transaction in the
1704 // queue then remove the replaced transaction.
1705 std::scoped_lock const lock(mutex_);
1706
1707 auto const accountIter = byAccount_.find(account);
1708 if (accountIter != byAccount_.end())
1709 {
1710 TxQAccount& txQAcct = accountIter->second;
1711 if (auto const existingIter = txQAcct.transactions.find(txSeqProx);
1712 existingIter != txQAcct.transactions.end())
1713 {
1714 removeFromByFee(existingIter, tx);
1715 }
1716 }
1717 }
1718 return ApplyResult{txnResult, didApply, metadata};
1719 }
1720 return {};
1721}
1722
1725 std::optional<TxQAccount::TxMap::iterator> const& replacedTxIter,
1727{
1728 if (replacedTxIter && tx)
1729 {
1730 // If the transaction we're holding replaces a transaction in the
1731 // queue, remove the transaction that is being replaced.
1732 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1733 XRPL_ASSERT(deleteIter != byFee_.end(), "xrpl::TxQ::removeFromByFee : found in byFee");
1734 XRPL_ASSERT(
1735 &(*replacedTxIter)->second == &*deleteIter,
1736 "xrpl::TxQ::removeFromByFee : matching transaction");
1737 XRPL_ASSERT(
1738 deleteIter->seqProxy == tx->getSeqProxy(),
1739 "xrpl::TxQ::removeFromByFee : matching sequence");
1740 XRPL_ASSERT(
1741 deleteIter->account == (*tx)[sfAccount],
1742 "xrpl::TxQ::removeFromByFee : matching account");
1743
1744 erase(deleteIter);
1745 }
1746 return std::nullopt;
1747}
1748
1750TxQ::getMetrics(OpenView const& view) const
1751{
1752 Metrics result;
1753
1754 std::scoped_lock const lock(mutex_);
1755
1756 auto const snapshot = feeMetrics_.getSnapshot();
1757
1758 result.txCount = byFee_.size();
1759 result.txQMaxSize = maxSize_;
1760 result.txInLedger = view.txCount();
1761 result.txPerLedger = snapshot.txnsExpected;
1763 result.minProcessingFeeLevel =
1764 isFull() ? byFee_.rbegin()->feeLevel + FeeLevel64{1} : kBaseLevel;
1765 result.medFeeLevel = snapshot.escalationMultiplier;
1766 result.openLedgerFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view);
1767
1768 return result;
1769}
1770
1773{
1774 auto const account = (*tx)[sfAccount];
1775
1776 std::scoped_lock const lock(mutex_);
1777
1778 auto const snapshot = feeMetrics_.getSnapshot();
1779 auto const baseFee = calculateBaseFee(view, *tx);
1780 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1781
1782 auto const sle = view.read(keylet::account(account));
1783
1784 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1785 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1786 return {
1787 .fee = mulDiv(fee, baseFee, kBaseLevel)
1789 .accountSeq = accountSeq,
1790 .availableSeq = availableSeq};
1791}
1792
1794TxQ::getAccountTxs(AccountID const& account) const
1795{
1797
1798 std::scoped_lock const lock(mutex_);
1799
1800 AccountMap::const_iterator const accountIter{byAccount_.find(account)};
1801
1802 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1803 return result;
1804
1805 result.reserve(accountIter->second.transactions.size());
1806 for (auto const& tx : accountIter->second.transactions)
1807 {
1808 result.emplace_back(tx.second.getTxDetails());
1809 }
1810 return result;
1811}
1812
1815{
1817
1818 std::scoped_lock const lock(mutex_);
1819
1820 result.reserve(byFee_.size());
1821
1822 for (auto const& tx : byFee_)
1823 result.emplace_back(tx.getTxDetails());
1824
1825 return result;
1826}
1827
1830{
1831 auto const view = app.getOpenLedger().current();
1832 if (!view)
1833 {
1834 BOOST_ASSERT(false);
1835 return {};
1836 }
1837
1838 auto const metrics = getMetrics(*view);
1839
1841
1842 auto& levels = ret[jss::levels] = json::ValueType::Object;
1843
1844 ret[jss::ledger_current_index] = view->header().seq;
1845 ret[jss::expected_ledger_size] = std::to_string(metrics.txPerLedger);
1846 ret[jss::current_ledger_size] = std::to_string(metrics.txInLedger);
1847 ret[jss::current_queue_size] = std::to_string(metrics.txCount);
1848 if (metrics.txQMaxSize)
1849 ret[jss::max_queue_size] = std::to_string(*metrics.txQMaxSize);
1850
1851 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1852 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1853 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1854 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1855
1856 auto const baseFee = view->fees().base;
1857 // If the base fee is 0 drops, but escalation has kicked in, treat the
1858 // base fee as if it is 1 drop, which makes the rest of the math
1859 // work.
1860 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1861 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1862 return XRPAmount{1};
1863 return baseFee;
1864 }();
1865 auto& drops = ret[jss::drops] = json::Value();
1866
1867 drops[jss::base_fee] = to_string(baseFee);
1868 drops[jss::median_fee] = to_string(toDrops(metrics.medFeeLevel, baseFee));
1869 drops[jss::minimum_fee] = to_string(toDrops(
1870 metrics.minProcessingFeeLevel,
1871 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1872 auto openFee = toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1873 if (effectiveBaseFee && toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1874 openFee += 1;
1875 drops[jss::open_ledger_fee] = to_string(openFee);
1876
1877 return ret;
1878}
1879
1881
1883setupTxQ(Config const& config)
1884{
1885 TxQ::Setup setup;
1886 auto const& section = config.section(Sections::kTransactionQueue);
1887 set(setup.ledgersInQueue, Keys::kLedgersInQueue, section);
1888 set(setup.queueSizeMin, Keys::kMinimumQueueSize, section);
1894 std::uint32_t max = 0;
1895 if (set(max, Keys::kMaximumTxnInLedger, section))
1896 {
1897 if (max < setup.minimumTxnInLedger)
1898 {
1900 "The minimum number of low-fee transactions allowed "
1901 "per ledger (minimum_txn_in_ledger) exceeds "
1902 "the maximum number of low-fee transactions allowed per "
1903 "ledger (maximum_txn_in_ledger).");
1904 }
1905 if (max < setup.minimumTxnInLedgerSA)
1906 {
1908 "The minimum number of low-fee transactions allowed "
1909 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1910 "the maximum number of low-fee transactions allowed per "
1911 "ledger (maximum_txn_in_ledger).");
1912 }
1913
1914 setup.maximumTxnInLedger.emplace(max);
1915 }
1916
1917 /* The math works as expected for any value up to and including
1918 MAXINT, but put a reasonable limit on this percentage so that
1919 the factor can't be configured to render escalation effectively
1920 moot. (There are other ways to do that, including
1921 minimum_txn_in_ledger_.)
1922 */
1926
1927 /* If this percentage is outside of the 0-100 range, the results
1928 are nonsensical (uint overflows happen, so the limit grows
1929 instead of shrinking). 0 is not recommended.
1930 */
1933
1936
1937 setup.standAlone = config.standalone();
1938 return setup;
1939}
1940
1941} // namespace xrpl
T accumulate(T... args)
T clamp(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Represents a JSON value.
Definition json_value.h:117
Editable, discardable view that can build metadata for one tx.
Section & section(std::string const &name)
Returns the section with the given name.
bool standalone() const
std::shared_ptr< OpenView const > current() const
Returns a view to the current open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
std::size_t txCount() const
Return the number of tx inserted since creation.
Definition OpenView.cpp:120
Fees const & fees() const override
Returns the fees for the base ledger.
Definition OpenView.cpp:142
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:167
LedgerHeader const & header() const override
Returns information about the ledger.
Definition OpenView.cpp:136
void apply(TxsRawView &to) const
Apply changes.
Definition OpenView.cpp:126
Rules const & rules() const override
Returns the tx processing rules.
Definition OpenView.cpp:148
bool exists(Keylet const &k) const override
Determine if a state item exists.
Definition OpenView.cpp:154
A view into a ledger.
Definition ReadView.h:41
TxsType txs
Definition ReadView.h:270
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
std::shared_ptr< STLedgerEntry const > const & const_ref
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:601
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
SeqProxy getSeqProxy() const
Definition STTx.cpp:199
TxType getTxnType() const
Definition STTx.h:226
A type that represents either a sequence value or a ticket value.
Definition SeqProxy.h:37
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
constexpr bool isTicket() const
Definition SeqProxy.h:92
constexpr std::uint32_t value() const
Definition SeqProxy.h:80
constexpr bool isSeq() const
Definition SeqProxy.h:86
virtual OpenLedger & getOpenLedger()=0
std::size_t txnsExpected_
Number of transactions expected per ledger.
Definition TxQ.h:458
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "workstowards".
Definition TxQ.h:448
static FeeLevel64 scaleFeeLevel(Snapshot const &snapshot, OpenView const &view)
Use the number of transactions in the current open ledger to compute the fee level a transaction must...
Definition TxQ.cpp:183
beast::Journal const j_
Journal.
Definition TxQ.h:472
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
Definition TxQ.h:452
std::size_t update(Application &app, ReadView const &view, bool timeLeap, TxQ::Setup const &setup)
Updates fee metrics based on the transactions in the ReadView for use in fee escalation calculations.
Definition TxQ.cpp:103
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
Definition TxQ.h:443
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
Definition TxQ.h:463
static std::pair< bool, FeeLevel64 > escalatedSeriesFeeLevel(Snapshot const &snapshot, OpenView const &view, std::size_t extraCount, std::size_t seriesSize)
Computes the total fee level for all transactions in a series.
Definition TxQ.cpp:241
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Definition TxQ.h:468
Represents a transaction in the queue which may be applied later to the open ledger.
Definition TxQ.h:589
static LedgerHash parentHashComp
The hash of the parent ledger.
Definition TxQ.h:685
std::optional< LedgerIndex > const lastValid
Expiration ledger for the transaction (sfLastLedgerSequence field).
Definition TxQ.h:619
TxID const txID
Transaction ID.
Definition TxQ.h:610
FeeLevel64 const feeLevel
Computed fee level that the transaction will pay.
Definition TxQ.h:606
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfResult)
Constructor.
Definition TxQ.cpp:287
ApplyFlags const flags
Flags provided to apply.
Definition TxQ.h:639
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
Definition TxQ.cpp:305
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
Definition TxQ.h:624
std::shared_ptr< STTx const > txn
The complete transaction.
Definition TxQ.h:601
static constexpr int kRetriesAllowed
Starting retry count for newly queued transactions.
Definition TxQ.h:674
AccountID const account
Account submitting the transaction.
Definition TxQ.h:614
std::optional< PreflightResult const > pfResult
Cached result of the preflight operation.
Definition TxQ.h:657
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
Definition TxQ.h:775
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
Definition TxQ.cpp:336
TxMap transactions
Sequence number will be used as the key.
Definition TxQ.h:786
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
Definition TxQ.cpp:347
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
Definition TxQ.h:816
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
Definition TxQ.cpp:326
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
Definition TxQ.cpp:360
AccountID const account
The account.
Definition TxQ.h:782
std::map< SeqProxy, MaybeTx > TxMap
Definition TxQ.h:777
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition TxQ.cpp:1750
json::Value doRPC(Application &app) const
Summarize current fee metrics for the fee RPC command.
Definition TxQ.cpp:1829
TxQ(Setup const &setup, beast::Journal j)
Constructor.
Definition TxQ.cpp:367
SeqProxy nextQueuableSeq(SLE::const_ref sleAccount) const
Return the next sequence that would go in the TxQ for an account.
Definition TxQ.cpp:1590
FeeMetrics feeMetrics_
Tracks the current state of the queue.
Definition TxQ.h:898
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
Definition TxQ.h:921
void processClosedLedger(Application &app, ReadView const &view, bool timeLeap)
Update fee metrics and clean up the queue in preparation for the next ledger.
Definition TxQ.cpp:1347
std::vector< TxDetails > getAccountTxs(AccountID const &account) const
Returns information about the transactions currently in the queue for the account.
Definition TxQ.cpp:1794
SeqProxy nextQueuableSeqImpl(SLE::const_ref sleAccount, std::scoped_lock< std::mutex > const &) const
Definition TxQ.cpp:1603
bool isFull() const
Is the queue at least fillPercentage full?
Definition TxQ.cpp:379
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type)
Erase and return the next entry for the account (if fee level is higher), or next entry in byFee_ (lo...
Definition TxQ.cpp:485
ApplyResult tryClearAccountQueueUpThruTx(Application &app, OpenView &view, STTx const &tx, AccountMap::iterator const &accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const &pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, beast::Journal j)
All-or-nothing attempt to try to apply the queued txs for accountIter up to and including tx.
Definition TxQ.cpp:531
static FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::scoped_lock< std::mutex > const &lock)
Definition TxQ.cpp:1648
ApplyResult apply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Add a new transaction to the open ledger, hold it in the queue, or reject it.
Definition TxQ.cpp:739
FeeAndSeq getTxRequiredFeeAndSeq(OpenView const &view, std::shared_ptr< STTx const > const &tx) const
Returns minimum required fee for tx and two sequences: first valid sequence for this account in curre...
Definition TxQ.cpp:1772
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Definition TxQ.cpp:1658
virtual ~TxQ()
Destructor.
Definition TxQ.cpp:372
bool accept(Application &app, OpenView &view)
Fill the new open ledger with transactions from the queue.
Definition TxQ.cpp:1418
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
Definition TxQ.h:932
std::vector< TxDetails > getTxs() const
Returns information about all transactions currently in the queue.
Definition TxQ.cpp:1814
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
Definition TxQ.h:905
beast::Journal const j_
Journal.
Definition TxQ.h:891
std::optional< TxQAccount::TxMap::iterator > removeFromByFee(std::optional< TxQAccount::TxMap::iterator > const &replacedTxIter, std::shared_ptr< STTx const > const &tx)
Definition TxQ.cpp:1724
LedgerHash parentHash_
parentHash_ used for logging only
Definition TxQ.h:926
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level).
static constexpr FeeLevel64 kBaseLevel
Fee level for single-signed reference transaction.
Definition TxQ.h:63
TER canBeHeld(STTx const &, ApplyFlags const, OpenView const &, SLE::const_ref sleAccount, AccountMap::iterator const &, std::optional< TxQAccount::TxMap::iterator > const &, std::scoped_lock< std::mutex > const &lock)
Checks if the indicated transaction fits the conditions for being stored in the queue.
Definition TxQ.cpp:386
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
Definition TxQ.h:913
Setup const setup_
Setup parameters used to control the behavior of the queue.
Definition TxQ.h:887
constexpr int signum() const noexcept
Return the sign of the amount.
Definition XRPAmount.h:159
T distance(T... args)
T emplace_back(T... args)
T emplace(T... args)
T end(T... args)
T find(T... args)
T for_each(T... args)
T lower_bound(T... args)
T max_element(T... args)
T max(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
Definition TxQ.cpp:207
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:310
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telCAN_NOT_QUEUE_FULL
Definition TER.h:50
@ telCAN_NOT_QUEUE_FEE
Definition TER.h:49
@ telCAN_NOT_QUEUE_BLOCKED
Definition TER.h:48
@ telINSUF_FEE_P
Definition TER.h:43
@ telCAN_NOT_QUEUE
Definition TER.h:45
@ telCAN_NOT_QUEUE_BALANCE
Definition TER.h:46
@ telCAN_NOT_QUEUE_BLOCKS
Definition TER.h:47
@ terPRE_SEQ
Definition TER.h:217
@ terNO_ACCOUNT
Definition TER.h:213
@ terPRE_TICKET
Definition TER.h:222
@ terQUEUED
Definition TER.h:221
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
Definition TxQ.cpp:86
PreflightResult preflight(ServiceRegistry &registry, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
PreclaimResult preclaim(PreflightResult const &preflightResult, ServiceRegistry &registry, OpenView const &view)
Gate a transaction based on static ledger information.
ApplyResult apply(ServiceRegistry &registry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:122
@ tefNO_TICKET
Definition TER.h:177
@ tefINTERNAL
Definition TER.h:165
@ tefPAST_SEQ
Definition TER.h:167
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition TxQ.h:1003
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
Definition TxQ.cpp:94
std::string transToken(TER code)
Definition TER.cpp:251
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
Definition TxQ.cpp:58
bool isTefFailure(TER x) noexcept
Definition TER.h:664
uint256 LedgerHash
bool isFeeSponsored(STTx const &tx)
Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set).
FeeLevel< std::uint64_t > FeeLevel64
Definition Units.h:443
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
Definition TxQ.h:1009
constexpr auto kMuldivMax
Definition mulDiv.h:8
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
ApplyFlags
Definition ApplyView.h:27
@ TapDryRun
Definition ApplyView.h:46
@ TapFailHard
Definition ApplyView.h:32
@ TapNone
Definition ApplyView.h:28
@ TapRetry
Definition ApplyView.h:36
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
constexpr struct xrpl::OpenLedgerT kOpenLedger
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry &registry, OpenView &view)
Apply a prechecked transaction to an OpenView.
TxQ::Setup setupTxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
Definition TxQ.cpp:1883
bool isTemMalformed(TER x) noexcept
Definition TER.h:658
uint256 TxID
A transaction identifier.
Definition Protocol.h:391
@ tesSUCCESS
Definition TER.h:245
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T next(T... args)
T has_value(T... args)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
XRPAmount reserve
Minimum XRP an account must hold to exist on the ledger.
XRPAmount base
Cost of a reference transaction in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
static constexpr auto kRetrySequencePercent
Definition Constants.h:151
static constexpr auto kMinimumEscalationMultiplier
Definition Constants.h:129
static constexpr auto kMaximumTxnInLedger
Definition Constants.h:125
static constexpr auto kSlowConsensusDecreasePercent
Definition Constants.h:159
static constexpr auto kMinimumLastLedgerBuffer
Definition Constants.h:130
static constexpr auto kMaximumTxnPerAccount
Definition Constants.h:126
static constexpr auto kTargetTxnInLedger
Definition Constants.h:167
static constexpr auto kMinimumQueueSize
Definition Constants.h:131
static constexpr auto kMinimumTxnInLedger
Definition Constants.h:132
static constexpr auto kNormalConsensusIncreasePercent
Definition Constants.h:134
static constexpr auto kLedgersInQueue
Definition Constants.h:117
static constexpr auto kMinimumTxnInLedgerStandalone
Definition Constants.h:133
Describes the results of the preflight check.
Definition applySteps.h:200
Iterator end() const
Definition ReadView.cpp:55
Iterator begin() const
Definition ReadView.cpp:49
static constexpr auto kTransactionQueue
Definition Constants.h:66
Snapshot of the externally relevant FeeMetrics fields at any given time.
Definition TxQ.h:515
std::size_t const txnsExpected
Definition TxQ.h:519
FeeLevel64 const escalationMultiplier
Definition TxQ.h:522
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
Definition TxQ.h:185
std::size_t txCount
Number of transactions in the queue.
Definition TxQ.h:194
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
Definition TxQ.h:198
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
Definition TxQ.h:224
std::size_t txInLedger
Number of transactions currently in the open ledger.
Definition TxQ.h:202
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
Definition TxQ.h:215
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
Definition TxQ.h:210
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
Definition TxQ.h:219
std::size_t txPerLedger
Number of transactions expected per ledger.
Definition TxQ.h:206
Structure used to customize TxQ behavior.
Definition TxQ.h:69
bool standAlone
Use standalone mode behavior.
Definition TxQ.h:177
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
Definition TxQ.h:165
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
Definition TxQ.h:106
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
Definition TxQ.h:133
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "workstowards".
Definition TxQ.h:121
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
Definition TxQ.h:173
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
Definition TxQ.h:83
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
Definition TxQ.h:101
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
Definition TxQ.h:116
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
Definition TxQ.h:161
std::size_t queueSizeMin
The smallest limit the queue is allowed.
Definition TxQ.h:90
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
Definition TxQ.h:111
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...
Definition TxQ.h:146
T tie(T... args)
T to_string(T... args)
T upper_bound(T... args)
T value_or(T... args)