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