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