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