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