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