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