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