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/TxQ.h>
23#include <xrpld/app/tx/apply.h>
24
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
30#include <algorithm>
31#include <limits>
32#include <numeric>
33
34namespace ripple {
35
37
38static FeeLevel64
39getFeeLevelPaid(ReadView const& view, STTx const& tx)
40{
41 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
42 XRPAmount baseFee = calculateBaseFee(view, tx);
43 XRPAmount feePaid = tx[sfFee].xrp();
44
45 // If baseFee is 0 then the cost of a basic transaction is free, but we
46 // need the effective fee level to be non-zero.
47 XRPAmount const mod = [&view, &tx, baseFee]() {
48 if (baseFee.signum() > 0)
49 return XRPAmount{0};
50 auto def = calculateDefaultBaseFee(view, tx);
51 return def.signum() == 0 ? XRPAmount{1} : def;
52 }();
53 return std::pair{baseFee + mod, feePaid + mod};
54 }();
55
56 XRPL_ASSERT(baseFee.signum() > 0, "ripple::getFeeLevelPaid : positive fee");
57 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
58 {
59 return FeeLevel64(0);
60 }
61
62 return mulDiv(effectiveFeePaid, TxQ::baseLevel, baseFee)
64}
65
68{
69 if (!tx.isFieldPresent(sfLastLedgerSequence))
70 return std::nullopt;
71 return tx.getFieldU32(sfLastLedgerSequence);
72}
73
74static FeeLevel64
75increase(FeeLevel64 level, std::uint32_t increasePercent)
76{
77 return mulDiv(level, 100 + increasePercent, 100)
79}
80
82
85 Application& app,
86 ReadView const& view,
87 bool timeLeap,
88 TxQ::Setup const& setup)
89{
91 auto const txBegin = view.txs.begin();
92 auto const txEnd = view.txs.end();
93 auto const size = std::distance(txBegin, txEnd);
94 feeLevels.reserve(size);
95 std::for_each(txBegin, txEnd, [&](auto const& tx) {
96 feeLevels.push_back(getFeeLevelPaid(view, *tx.first));
97 });
98 std::sort(feeLevels.begin(), feeLevels.end());
99 XRPL_ASSERT(
100 size == feeLevels.size(),
101 "ripple::TxQ::FeeMetrics::update : fee levels size");
102
103 JLOG((timeLeap ? j_.warn() : j_.debug()))
104 << "Ledger " << view.info().seq << " has " << size << " transactions. "
105 << "Ledgers are processing " << (timeLeap ? "slowly" : "as expected")
106 << ". Expected transactions is currently " << txnsExpected_
107 << " and multiplier is " << escalationMultiplier_;
108
109 if (timeLeap)
110 {
111 // Ledgers are taking to long to process,
112 // so clamp down on limits.
113 auto const cutPct = 100 - setup.slowConsensusDecreasePercent;
114 // upperLimit must be >= minimumTxnCount_ or std::clamp can give
115 // unexpected results
116 auto const upperLimit = std::max<std::uint64_t>(
117 mulDiv(txnsExpected_, cutPct, 100).value_or(ripple::muldiv_max),
120 mulDiv(size, cutPct, 100).value_or(ripple::muldiv_max),
122 upperLimit);
123 recentTxnCounts_.clear();
124 }
125 else if (size > txnsExpected_ || size > targetTxnCount_)
126 {
127 recentTxnCounts_.push_back(
128 mulDiv(size, 100 + setup.normalConsensusIncreasePercent, 100)
130 auto const iter =
132 BOOST_ASSERT(iter != recentTxnCounts_.end());
133 auto const next = [&] {
134 // Grow quickly: If the max_element is >= the
135 // current size limit, use it.
136 if (*iter >= txnsExpected_)
137 return *iter;
138 // Shrink slowly: If the max_element is < the
139 // current size limit, use a limit that is
140 // 90% of the way from max_element to the
141 // current size limit.
142 return (txnsExpected_ * 9 + *iter) / 10;
143 }();
144 // Ledgers are processing in a timely manner,
145 // so keep the limit high, but don't let it
146 // grow without bound.
148 }
149
150 if (!size)
151 {
153 }
154 else
155 {
156 // In the case of an odd number of elements, this
157 // evaluates to the middle element; for an even
158 // number of elements, it will add the two elements
159 // on either side of the "middle" and average them.
161 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] + FeeLevel64{1}) /
162 2;
165 }
166 JLOG(j_.debug()) << "Expected transactions updated to " << txnsExpected_
167 << " and multiplier updated to " << escalationMultiplier_;
168
169 return size;
170}
171
174{
175 // Transactions in the open ledger so far
176 auto const current = view.txCount();
177
178 auto const target = snapshot.txnsExpected;
179 auto const multiplier = snapshot.escalationMultiplier;
180
181 // Once the open ledger bypasses the target,
182 // escalate the fee quickly.
183 if (current > target)
184 {
185 // Compute escalated fee level
186 // Don't care about the overflow flag
187 return mulDiv(multiplier, current * current, target * target)
188 .value_or(static_cast<FeeLevel64>(ripple::muldiv_max));
189 }
190
191 return baseLevel;
192}
193
194namespace detail {
195
196constexpr static std::pair<bool, std::uint64_t>
198{
199 // sum(n = 1->x) : n * n = x(x + 1)(2x + 1) / 6
200
201 // We expect that size_t == std::uint64_t but, just in case, guarantee
202 // we lose no bits.
203 std::uint64_t x{xIn};
204
205 // If x is anywhere on the order of 2^^21, it's going
206 // to completely dominate the computation and is likely
207 // enough to overflow that we're just going to assume
208 // it does. If we have anywhere near 2^^21 transactions
209 // in a ledger, this is the least of our problems.
210 if (x >= (1 << 21))
212 return {true, (x * (x + 1) * (2 * x + 1)) / 6};
213}
214
215// Unit tests for sumOfSquares()
216static_assert(sumOfFirstSquares(1).first == true);
217static_assert(sumOfFirstSquares(1).second == 1);
218
219static_assert(sumOfFirstSquares(2).first == true);
220static_assert(sumOfFirstSquares(2).second == 5);
221
222static_assert(sumOfFirstSquares(0x1FFFFF).first == true, "");
223static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul, "");
224
225static_assert(sumOfFirstSquares(0x200000).first == false, "");
226static_assert(
227 sumOfFirstSquares(0x200000).second ==
229 "");
230
231} // namespace detail
232
235 Snapshot const& snapshot,
236 OpenView const& view,
237 std::size_t extraCount,
238 std::size_t seriesSize)
239{
240 /* Transactions in the open ledger so far.
241 AKA Transactions that will be in the open ledger when
242 the first tx in the series is attempted.
243 */
244 auto const current = view.txCount() + extraCount;
245 /* Transactions that will be in the open ledger when
246 the last tx in the series is attempted.
247 */
248 auto const last = current + seriesSize - 1;
249
250 auto const target = snapshot.txnsExpected;
251 auto const multiplier = snapshot.escalationMultiplier;
252
253 XRPL_ASSERT(
254 current > target,
255 "ripple::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
256 "target");
257
258 /* Calculate (apologies for the terrible notation)
259 sum(n = current -> last) : multiplier * n * n / (target * target)
260 multiplier / (target * target) * (sum(n = current -> last) : n * n)
261 multiplier / (target * target) * ((sum(n = 1 -> last) : n * n) -
262 (sum(n = 1 -> current - 1) : n * n))
263 */
264 auto const sumNlast = detail::sumOfFirstSquares(last);
265 auto const sumNcurrent = detail::sumOfFirstSquares(current - 1);
266 // because `last` is bigger, if either sum overflowed, then
267 // `sumNlast` definitely overflowed. Also the odds of this
268 // are nearly nil.
269 if (!sumNlast.first)
270 return {sumNlast.first, FeeLevel64{sumNlast.second}};
271 auto const totalFeeLevel = mulDiv(
272 multiplier, sumNlast.second - sumNcurrent.second, target * target);
273
274 return {totalFeeLevel.has_value(), *totalFeeLevel};
275}
276
278
280 std::shared_ptr<STTx const> const& txn_,
281 TxID const& txID_,
282 FeeLevel64 feeLevel_,
283 ApplyFlags const flags_,
284 PreflightResult const& pfresult_)
285 : txn(txn_)
286 , feeLevel(feeLevel_)
287 , txID(txID_)
288 , account(txn_->getAccountID(sfAccount))
289 , lastValid(getLastLedgerSequence(*txn_))
290 , seqProxy(txn_->getSeqProxy())
291 , retriesRemaining(retriesAllowed)
292 , flags(flags_)
293 , pfresult(pfresult_)
294{
295}
296
299{
300 // If the rules or flags change, preflight again
301 XRPL_ASSERT(
302 pfresult, "ripple::TxQ::MaybeTx::apply : preflight result is set");
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(AccountID const& 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 NumberSO stNumberSO{view.rules().enabled(fixUniversalNumber)};
737
738 // See if the transaction is valid, properly formed,
739 // etc. before doing potentially expensive queue
740 // replace and multi-transaction operations.
741 auto const pfresult = preflight(app, view.rules(), *tx, flags, j);
742 if (pfresult.ter != tesSUCCESS)
743 return {pfresult.ter, false};
744
745 // See if the transaction paid a high enough fee that it can go straight
746 // into the ledger.
747 if (auto directApplied = tryDirectApply(app, view, tx, flags, j))
748 return *directApplied;
749
750 // If we get past tryDirectApply() without returning then we expect
751 // one of the following to occur:
752 //
753 // o We will decide the transaction is unlikely to claim a fee.
754 // o The transaction paid a high enough fee that fee averaging will apply.
755 // o The transaction will be queued.
756
757 // If the account is not currently in the ledger, don't queue its tx.
758 auto const account = (*tx)[sfAccount];
759 Keylet const accountKey{keylet::account(account)};
760 auto const sleAccount = view.read(accountKey);
761 if (!sleAccount)
762 return {terNO_ACCOUNT, false};
763
764 // If the transaction needs a Ticket is that Ticket in the ledger?
765 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
766 SeqProxy const txSeqProx = tx->getSeqProxy();
767 if (txSeqProx.isTicket() &&
768 !view.exists(keylet::ticket(account, txSeqProx)))
769 {
770 if (txSeqProx.value() < acctSeqProx.value())
771 // The ticket number is low enough that it should already be
772 // in the ledger if it were ever going to exist.
773 return {tefNO_TICKET, false};
774
775 // We don't queue transactions that use Tickets unless
776 // we can find the Ticket in the ledger.
777 return {terPRE_TICKET, false};
778 }
779
781
782 // accountIter is not const because it may be updated further down.
783 AccountMap::iterator accountIter = byAccount_.find(account);
784 bool const accountIsInQueue = accountIter != byAccount_.end();
785
786 // _If_ the account is in the queue, then ignore any sequence-based
787 // queued transactions that slipped into the ledger while we were not
788 // watching. This does actually happen in the wild, but it's uncommon.
789 //
790 // Note that we _don't_ ignore queued ticket-based transactions that
791 // slipped into the ledger while we were not watching. It would be
792 // desirable to do so, but the measured cost was too high since we have
793 // to individually check each queued ticket against the ledger.
794 struct TxIter
795 {
796 TxIter(
797 TxQAccount::TxMap::iterator first_,
798 TxQAccount::TxMap::iterator end_)
799 : first(first_), end(end_)
800 {
801 }
802
803 TxQAccount::TxMap::iterator first;
804 TxQAccount::TxMap::iterator end;
805 };
806
807 std::optional<TxIter> const txIter =
808 [accountIter,
809 accountIsInQueue,
810 acctSeqProx]() -> std::optional<TxIter> {
811 if (!accountIsInQueue)
812 return {};
813
814 // Find the first transaction in the queue that we might apply.
815 TxQAccount::TxMap& acctTxs = accountIter->second.transactions;
816 TxQAccount::TxMap::iterator const firstIter =
817 acctTxs.lower_bound(acctSeqProx);
818
819 if (firstIter == acctTxs.end())
820 // Even though there may be transactions in the queue, there are
821 // none that we should pay attention to.
822 return {};
823
824 return {TxIter{firstIter, acctTxs.end()}};
825 }();
826
827 auto const acctTxCount{
828 !txIter ? 0 : std::distance(txIter->first, txIter->end)};
829
830 // Is tx a blocker? If so there are very limited conditions when it
831 // is allowed in the TxQ:
832 // 1. If the account's queue is empty or
833 // 2. If the blocker replaces the only entry in the account's queue.
834 auto const transactionID = tx->getTransactionID();
835 if (pfresult.consequences.isBlocker())
836 {
837 if (acctTxCount > 1)
838 {
839 // A blocker may not be co-resident with other transactions in
840 // the account's queue.
841 JLOG(j_.trace())
842 << "Rejecting blocker transaction " << transactionID
843 << ". Account has other queued transactions.";
844 return {telCAN_NOT_QUEUE_BLOCKS, false};
845 }
846 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
847 {
848 // The blocker is not replacing the lone queued transaction.
849 JLOG(j_.trace())
850 << "Rejecting blocker transaction " << transactionID
851 << ". Blocker does not replace lone queued transaction.";
852 return {telCAN_NOT_QUEUE_BLOCKS, false};
853 }
854 }
855
856 // If the transaction is intending to replace a transaction in the queue
857 // identify the one that might be replaced.
858 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
860 if (accountIsInQueue)
861 {
862 TxQAccount& txQAcct = accountIter->second;
863 if (auto const existingIter = txQAcct.transactions.find(txSeqProx);
864 existingIter != txQAcct.transactions.end())
865 return existingIter;
866 }
867 return {};
868 }();
869
870 // We may need the base fee for multiple transactions or transaction
871 // replacement, so just pull it up now.
872 auto const metricsSnapshot = feeMetrics_.getSnapshot();
873 auto const feeLevelPaid = getFeeLevelPaid(view, *tx);
874 auto const requiredFeeLevel =
875 getRequiredFeeLevel(view, flags, metricsSnapshot, lock);
876
877 // Is there a blocker already in the account's queue? If so, don't
878 // allow additional transactions in the queue.
879 if (acctTxCount > 0)
880 {
881 // Allow tx to replace a blocker. Otherwise, if there's a
882 // blocker, we can't queue tx.
883 //
884 // We only need to check if txIter->first is a blocker because we
885 // require that a blocker be alone in the account's queue.
886 if (acctTxCount == 1 &&
887 txIter->first->second.consequences().isBlocker() &&
888 (txIter->first->first != txSeqProx))
889 {
890 return {telCAN_NOT_QUEUE_BLOCKED, false};
891 }
892
893 // Is there a transaction for the same account with the same
894 // SeqProxy already in the queue? If so we may replace the
895 // existing entry with this new transaction.
896 if (replacedTxIter)
897 {
898 // We are attempting to replace a transaction in the queue.
899 //
900 // Is the current transaction's fee higher than
901 // the queued transaction's fee + a percentage
902 TxQAccount::TxMap::iterator const& existingIter = *replacedTxIter;
903 auto requiredRetryLevel = increase(
904 existingIter->second.feeLevel, setup_.retrySequencePercent);
905 JLOG(j_.trace())
906 << "Found transaction in queue for account " << account
907 << " with " << txSeqProx << " new txn fee level is "
908 << feeLevelPaid << ", old txn fee level is "
909 << existingIter->second.feeLevel
910 << ", new txn needs fee level of " << requiredRetryLevel;
911 if (feeLevelPaid > requiredRetryLevel)
912 {
913 // Continue, leaving the queued transaction marked for removal.
914 // DO NOT REMOVE if the new tx fails, because there may
915 // be other txs dependent on it in the queue.
916 JLOG(j_.trace()) << "Removing transaction from queue "
917 << existingIter->second.txID << " in favor of "
918 << transactionID;
919 }
920 else
921 {
922 // Drop the current transaction
923 JLOG(j_.trace())
924 << "Ignoring transaction " << transactionID
925 << " in favor of queued " << existingIter->second.txID;
926 return {telCAN_NOT_QUEUE_FEE, false};
927 }
928 }
929 }
930
931 struct MultiTxn
932 {
933 ApplyViewImpl applyView;
934 OpenView openView;
935
936 MultiTxn(OpenView& view, ApplyFlags flags)
937 : applyView(&view, flags), openView(&applyView)
938 {
939 }
940 };
941
943
944 if (acctTxCount == 0)
945 {
946 // There are no queued transactions for this account. If the
947 // transaction has a sequence make sure it's valid (tickets
948 // are checked elsewhere).
949 if (txSeqProx.isSeq())
950 {
951 if (acctSeqProx > txSeqProx)
952 return {tefPAST_SEQ, false};
953 if (acctSeqProx < txSeqProx)
954 return {terPRE_SEQ, false};
955 }
956 }
957 else
958 {
959 // There are probably other transactions in the queue for this
960 // account. Make sure the new transaction can work with the others
961 // in the queue.
962 TxQAccount const& txQAcct = accountIter->second;
963
964 if (acctSeqProx > txSeqProx)
965 return {tefPAST_SEQ, false};
966
967 // Determine if we need a multiTxn object. Assuming the account
968 // is in the queue, there are two situations where we need to
969 // build multiTx:
970 // 1. If there are two or more transactions in the account's queue, or
971 // 2. If the account has a single queue entry, we may still need
972 // multiTxn, but only if that lone entry will not be replaced by tx.
973 bool requiresMultiTxn = false;
974 if (acctTxCount > 1 || !replacedTxIter)
975 {
976 // If the transaction is queueable, create the multiTxn
977 // object to hold the info we need to adjust for prior txns.
978 TER const ter{canBeHeld(
979 *tx,
980 flags,
981 view,
982 sleAccount,
983 accountIter,
984 replacedTxIter,
985 lock)};
986 if (!isTesSuccess(ter))
987 return {ter, false};
988
989 requiresMultiTxn = true;
990 }
991
992 if (requiresMultiTxn)
993 {
994 // See if adding this entry to the queue makes sense.
995 //
996 // o Transactions with sequences should start with the
997 // account's Sequence.
998 //
999 // o Additional transactions with Sequences should
1000 // follow preceding sequence-based transactions with no
1001 // gaps (except for those required by CreateTicket
1002 // transactions).
1003
1004 // Find the entry in the queue that precedes the new
1005 // transaction, if one does.
1006 TxQAccount::TxMap::const_iterator const prevIter =
1007 txQAcct.getPrevTx(txSeqProx);
1008
1009 // Does the new transaction go to the front of the queue?
1010 // This can happen if:
1011 // o A transaction in the queue with a Sequence expired, or
1012 // o The current first thing in the queue has a Ticket and
1013 // * The tx has a Ticket that precedes it or
1014 // * txSeqProx == acctSeqProx.
1015 XRPL_ASSERT(
1016 prevIter != txIter->end, "ripple::TxQ::apply : not end");
1017 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1018 {
1019 // The first Sequence number in the queue must be the
1020 // account's sequence.
1021 if (txSeqProx.isSeq())
1022 {
1023 if (txSeqProx < acctSeqProx)
1024 return {tefPAST_SEQ, false};
1025 else if (txSeqProx > acctSeqProx)
1026 return {terPRE_SEQ, false};
1027 }
1028 }
1029 else if (!replacedTxIter)
1030 {
1031 // The current transaction is not replacing a transaction
1032 // in the queue. So apparently there's a transaction in
1033 // front of this one in the queue. Make sure the current
1034 // transaction fits in proper sequence order with the
1035 // previous transaction or is a ticket.
1036 if (txSeqProx.isSeq() &&
1037 nextQueuableSeqImpl(sleAccount, lock) != txSeqProx)
1038 return {telCAN_NOT_QUEUE, false};
1039 }
1040
1041 // Sum fees and spending for all of the queued transactions
1042 // so we know how much to remove from the account balance
1043 // for the trial preclaim.
1044 XRPAmount potentialSpend = beast::zero;
1045 XRPAmount totalFee = beast::zero;
1046 for (auto iter = txIter->first; iter != txIter->end; ++iter)
1047 {
1048 // If we're replacing this transaction don't include
1049 // the replaced transaction's XRP spend. Otherwise add
1050 // it to potentialSpend.
1051 if (iter->first != txSeqProx)
1052 {
1053 totalFee += iter->second.consequences().fee();
1054 potentialSpend +=
1055 iter->second.consequences().potentialSpend();
1056 }
1057 else if (std::next(iter) != txIter->end)
1058 {
1059 // The fee for the candidate transaction _should_ be
1060 // counted if it's replacing a transaction in the middle
1061 // of the queue.
1062 totalFee += pfresult.consequences.fee();
1063 potentialSpend += pfresult.consequences.potentialSpend();
1064 }
1065 }
1066
1067 /* Check if the total fees in flight are greater
1068 than the account's current balance, or the
1069 minimum reserve. If it is, then there's a risk
1070 that the fees won't get paid, so drop this
1071 transaction with a telCAN_NOT_QUEUE_BALANCE result.
1072 Assume: Minimum account reserve is 20 XRP.
1073 Example 1: If I have 1,000,000 XRP, I can queue
1074 a transaction with a 1,000,000 XRP fee. In
1075 the meantime, some other transaction may
1076 lower my balance (eg. taking an offer). When
1077 the transaction executes, I will either
1078 spend the 1,000,000 XRP, or the transaction
1079 will get stuck in the queue with a
1080 `terINSUF_FEE_B`.
1081 Example 2: If I have 1,000,000 XRP, and I queue
1082 10 transactions with 0.1 XRP fee, I have 1 XRP
1083 in flight. I can now queue another tx with a
1084 999,999 XRP fee. When the first 10 execute,
1085 they're guaranteed to pay their fee, because
1086 nothing can eat into my reserve. The last
1087 transaction, again, will either spend the
1088 999,999 XRP, or get stuck in the queue.
1089 Example 3: If I have 1,000,000 XRP, and I queue
1090 7 transactions with 3 XRP fee, I have 21 XRP
1091 in flight. I can not queue any more transactions,
1092 no matter how small or large the fee.
1093 Transactions stuck in the queue are mitigated by
1094 LastLedgerSeq and MaybeTx::retriesRemaining.
1095 */
1096 auto const balance = (*sleAccount)[sfBalance].xrp();
1097 /* Get the minimum possible account reserve. If it
1098 is at least 10 * the base fee, and fees exceed
1099 this amount, the transaction can't be queued.
1100
1101 Currently typical fees are several orders
1102 of magnitude smaller than any current or expected
1103 future reserve. This calculation is simpler than
1104 trying to figure out the potential changes to
1105 the ownerCount that may occur to the account
1106 as a result of these transactions, and removes
1107 any need to account for other transactions that
1108 may affect the owner count while these are queued.
1109
1110 However, in case the account reserve is on a
1111 comparable scale to the base fee, ignore the
1112 reserve. Only check the account balance.
1113 */
1114 auto const reserve = view.fees().reserve;
1115 auto const base = view.fees().base;
1116 if (totalFee >= balance ||
1117 (reserve > 10 * base && totalFee >= reserve))
1118 {
1119 // Drop the current transaction
1120 JLOG(j_.trace()) << "Ignoring transaction " << transactionID
1121 << ". Total fees in flight too high.";
1122 return {telCAN_NOT_QUEUE_BALANCE, false};
1123 }
1124
1125 // Create the test view from the current view.
1126 multiTxn.emplace(view, flags);
1127
1128 auto const sleBump = multiTxn->applyView.peek(accountKey);
1129 if (!sleBump)
1130 return {tefINTERNAL, false};
1131
1132 // Subtract the fees and XRP spend from all of the other
1133 // transactions in the queue. That prevents a transaction
1134 // inserted in the middle from fouling up later transactions.
1135 auto const potentialTotalSpend = totalFee +
1136 std::min(balance - std::min(balance, reserve), potentialSpend);
1137 XRPL_ASSERT(
1138 potentialTotalSpend > XRPAmount{0} ||
1139 (potentialTotalSpend == XRPAmount{0} &&
1140 multiTxn->applyView.fees().base == 0),
1141 "ripple::TxQ::apply : total spend check");
1142 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1143 // The transaction's sequence/ticket will be valid when the other
1144 // transactions in the queue have been processed. If the tx has a
1145 // sequence, set the account to match it. If it has a ticket, use
1146 // the next queueable sequence, which is the closest approximation
1147 // to the most successful case.
1148 sleBump->at(sfSequence) = txSeqProx.isSeq()
1149 ? txSeqProx.value()
1150 : nextQueuableSeqImpl(sleAccount, lock).value();
1151 }
1152 }
1153
1154 // See if the transaction is likely to claim a fee.
1155 //
1156 // We assume that if the transaction survives preclaim(), then it
1157 // is likely to claim a fee. However we can't allow preclaim to
1158 // check the sequence/ticket. Transactions in the queue may be
1159 // responsible for increasing the sequence, and mocking those up
1160 // is non-trivially expensive.
1161 //
1162 // Note that earlier code has already verified that the sequence/ticket
1163 // is valid. So we use a special entry point that runs all of the
1164 // preclaim checks with the exception of the sequence check.
1165 auto const pcresult =
1166 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1167 if (!pcresult.likelyToClaimFee)
1168 return {pcresult.ter, false};
1169
1170 // Too low of a fee should get caught by preclaim
1171 XRPL_ASSERT(feeLevelPaid >= baseLevel, "ripple::TxQ::apply : minimum fee");
1172
1173 JLOG(j_.trace()) << "Transaction " << transactionID << " from account "
1174 << account << " has fee level of " << feeLevelPaid
1175 << " needs at least " << requiredFeeLevel
1176 << " to get in the open ledger, which has "
1177 << view.txCount() << " entries.";
1178
1179 /* Quick heuristic check to see if it's worth checking that this tx has
1180 a high enough fee to clear all the txs in front of it in the queue.
1181 1) Transaction is trying to get into the open ledger.
1182 2) Transaction must be Sequence-based.
1183 3) Must be an account already in the queue.
1184 4) Must be have passed the multiTxn checks (tx is not the next
1185 account seq, the skipped seqs are in the queue, the reserve
1186 doesn't get exhausted, etc).
1187 5) The next transaction must not have previously tried and failed
1188 to apply to an open ledger.
1189 6) Tx must be paying more than just the required fee level to
1190 get itself into the queue.
1191 7) Fee level must be escalated above the default (if it's not,
1192 then the first tx _must_ have failed to process in `accept`
1193 for some other reason. Tx is allowed to queue in case
1194 conditions change, but don't waste the effort to clear).
1195 */
1196 if (txSeqProx.isSeq() && txIter && multiTxn.has_value() &&
1197 txIter->first->second.retriesRemaining == MaybeTx::retriesAllowed &&
1198 feeLevelPaid > requiredFeeLevel && requiredFeeLevel > baseLevel)
1199 {
1200 OpenView sandbox(open_ledger, &view, view.rules());
1201
1202 auto result = tryClearAccountQueueUpThruTx(
1203 app,
1204 sandbox,
1205 *tx,
1206 accountIter,
1207 txIter->first,
1208 feeLevelPaid,
1209 pfresult,
1210 view.txCount(),
1211 flags,
1212 metricsSnapshot,
1213 j);
1214 if (result.applied)
1215 {
1216 sandbox.apply(view);
1217 /* Can't erase (*replacedTxIter) here because success
1218 implies that it has already been deleted.
1219 */
1220 return result;
1221 }
1222 }
1223
1224 // If `multiTxn` has a value, then `canBeHeld` has already been verified
1225 if (!multiTxn)
1226 {
1227 TER const ter{canBeHeld(
1228 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1229 if (!isTesSuccess(ter))
1230 {
1231 // Bail, transaction cannot be held
1232 JLOG(j_.trace())
1233 << "Transaction " << transactionID << " cannot be held";
1234 return {ter, false};
1235 }
1236 }
1237
1238 // If the queue is full, decide whether to drop the current
1239 // transaction or the last transaction for the account with
1240 // the lowest fee.
1241 if (!replacedTxIter && isFull())
1242 {
1243 auto lastRIter = byFee_.rbegin();
1244 while (lastRIter != byFee_.rend() && lastRIter->account == account)
1245 {
1246 ++lastRIter;
1247 }
1248 if (lastRIter == byFee_.rend())
1249 {
1250 // The only way this condition can happen is if the entire
1251 // queue is filled with transactions from this account. This
1252 // is impossible with default settings - minimum queue size
1253 // is 2000, and an account can only have 10 transactions
1254 // queued. However, it can occur if settings are changed,
1255 // and there is unit test coverage.
1256 JLOG(j_.info())
1257 << "Queue is full, and transaction " << transactionID
1258 << " would kick a transaction from the same account ("
1259 << account << ") out of the queue.";
1260 return {telCAN_NOT_QUEUE_FULL, false};
1261 }
1262 auto const& endAccount = byAccount_.at(lastRIter->account);
1263 auto endEffectiveFeeLevel = [&]() {
1264 // Compute the average of all the txs for the endAccount,
1265 // but only if the last tx in the queue has a lower fee
1266 // level than this candidate tx.
1267 if (lastRIter->feeLevel > feeLevelPaid ||
1268 endAccount.transactions.size() == 1)
1269 return lastRIter->feeLevel;
1270
1272 auto endTotal = std::accumulate(
1273 endAccount.transactions.begin(),
1274 endAccount.transactions.end(),
1276 [&](auto const& total,
1277 auto const& txn) -> std::pair<FeeLevel64, FeeLevel64> {
1278 // Check for overflow.
1279 auto next =
1280 txn.second.feeLevel / endAccount.transactions.size();
1281 auto mod =
1282 txn.second.feeLevel % endAccount.transactions.size();
1283 if (total.first >= max - next || total.second >= max - mod)
1284 return {max, FeeLevel64{0}};
1285
1286 return {total.first + next, total.second + mod};
1287 });
1288 return endTotal.first +
1289 endTotal.second / endAccount.transactions.size();
1290 }();
1291 if (feeLevelPaid > endEffectiveFeeLevel)
1292 {
1293 // The queue is full, and this transaction is more
1294 // valuable, so kick out the cheapest transaction.
1295 auto dropRIter = endAccount.transactions.rbegin();
1296 XRPL_ASSERT(
1297 dropRIter->second.account == lastRIter->account,
1298 "ripple::TxQ::apply : cheapest transaction found");
1299 JLOG(j_.info())
1300 << "Removing last item of account " << lastRIter->account
1301 << " from queue with average fee of " << endEffectiveFeeLevel
1302 << " in favor of " << transactionID << " with fee of "
1303 << feeLevelPaid;
1304 erase(byFee_.iterator_to(dropRIter->second));
1305 }
1306 else
1307 {
1308 JLOG(j_.info())
1309 << "Queue is full, and transaction " << transactionID
1310 << " fee is lower than end item's account average fee";
1311 return {telCAN_NOT_QUEUE_FULL, false};
1312 }
1313 }
1314
1315 // Hold the transaction in the queue.
1316 if (replacedTxIter)
1317 {
1318 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1319 }
1320
1321 if (!accountIsInQueue)
1322 {
1323 // Create a new TxQAccount object and add the byAccount lookup.
1324 [[maybe_unused]] bool created = false;
1325 std::tie(accountIter, created) =
1326 byAccount_.emplace(account, TxQAccount(tx));
1327 XRPL_ASSERT(created, "ripple::TxQ::apply : account created");
1328 }
1329 // Modify the flags for use when coming out of the queue.
1330 // These changes _may_ cause an extra `preflight`, but as long as
1331 // the `HashRouter` still knows about the transaction, the signature
1332 // will not be checked again, so the cost should be minimal.
1333
1334 // Don't allow soft failures, which can lead to retries
1335 flags &= ~tapRETRY;
1336
1337 auto& candidate = accountIter->second.add(
1338 {tx, transactionID, feeLevelPaid, flags, pfresult});
1339
1340 // Then index it into the byFee lookup.
1341 byFee_.insert(candidate);
1342 JLOG(j_.debug()) << "Added transaction " << candidate.txID
1343 << " with result " << transToken(pfresult.ter) << " from "
1344 << (accountIsInQueue ? "existing" : "new") << " account "
1345 << candidate.account << " to queue."
1346 << " Flags: " << flags;
1347
1348 return {terQUEUED, false};
1349}
1350
1351/*
1352 1. Update the fee metrics based on the fee levels of the
1353 txs in the validated ledger and whether consensus is
1354 slow.
1355 2. Adjust the maximum queue size to be enough to hold
1356 `ledgersInQueue` ledgers.
1357 3. Remove any transactions from the queue for which the
1358 `LastLedgerSequence` has passed.
1359 4. Remove any account objects that have no candidates
1360 under them.
1361
1362*/
1363void
1364TxQ::processClosedLedger(Application& app, ReadView const& view, bool timeLeap)
1365{
1366 std::lock_guard lock(mutex_);
1367
1368 feeMetrics_.update(app, view, timeLeap, setup_);
1369 auto const& snapshot = feeMetrics_.getSnapshot();
1370
1371 auto ledgerSeq = view.info().seq;
1372
1373 if (!timeLeap)
1374 maxSize_ = std::max(
1375 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1376
1377 // Remove any queued candidates whose LastLedgerSequence has gone by.
1378 for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1379 {
1380 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1381 {
1382 byAccount_.at(candidateIter->account).dropPenalty = true;
1383 candidateIter = erase(candidateIter);
1384 }
1385 else
1386 {
1387 ++candidateIter;
1388 }
1389 }
1390
1391 // Remove any TxQAccounts that don't have candidates
1392 // under them
1393 for (auto txQAccountIter = byAccount_.begin();
1394 txQAccountIter != byAccount_.end();)
1395 {
1396 if (txQAccountIter->second.empty())
1397 txQAccountIter = byAccount_.erase(txQAccountIter);
1398 else
1399 ++txQAccountIter;
1400 }
1401}
1402
1403/*
1404 How the txs are moved from the queue to the new open ledger.
1405
1406 1. Iterate over the txs from highest fee level to lowest.
1407 For each tx:
1408 a) Is this the first tx in the queue for this account?
1409 No: Skip this tx. We'll come back to it later.
1410 Yes: Continue to the next sub-step.
1411 b) Is the tx fee level less than the current required
1412 fee level?
1413 Yes: Stop iterating. Continue to the next step.
1414 No: Try to apply the transaction. Did it apply?
1415 Yes: Take it out of the queue. Continue with
1416 the next appropriate candidate (see below).
1417 No: Did it get a tef, tem, or tel, or has it
1418 retried `MaybeTx::retriesAllowed`
1419 times already?
1420 Yes: Take it out of the queue. Continue
1421 with the next appropriate candidate
1422 (see below).
1423 No: Leave it in the queue, track the retries,
1424 and continue iterating.
1425 2. Return indicator of whether the open ledger was modified.
1426
1427 "Appropriate candidate" is defined as the tx that has the
1428 highest fee level of:
1429 * the tx for the current account with the next sequence.
1430 * the next tx in the queue, simply ordered by fee.
1431*/
1432bool
1433TxQ::accept(Application& app, OpenView& view)
1434{
1435 /* Move transactions from the queue from largest fee level to smallest.
1436 As we add more transactions, the required fee level will increase.
1437 Stop when the transaction fee level gets lower than the required fee
1438 level.
1439 */
1440
1441 auto ledgerChanged = false;
1442
1443 std::lock_guard lock(mutex_);
1444
1445 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1446
1447 for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1448 {
1449 auto& account = byAccount_.at(candidateIter->account);
1450 auto const beginIter = account.transactions.begin();
1451 if (candidateIter->seqProxy.isSeq() &&
1452 candidateIter->seqProxy > beginIter->first)
1453 {
1454 // There is a sequence transaction at the front of the queue and
1455 // candidate has a later sequence, so skip this candidate. We
1456 // need to process sequence-based transactions in sequence order.
1457 JLOG(j_.trace())
1458 << "Skipping queued transaction " << candidateIter->txID
1459 << " from account " << candidateIter->account
1460 << " as it is not the first.";
1461 candidateIter++;
1462 continue;
1463 }
1464 auto const requiredFeeLevel =
1465 getRequiredFeeLevel(view, tapNONE, metricsSnapshot, lock);
1466 auto const feeLevelPaid = candidateIter->feeLevel;
1467 JLOG(j_.trace()) << "Queued transaction " << candidateIter->txID
1468 << " from account " << candidateIter->account
1469 << " has fee level of " << feeLevelPaid
1470 << " needs at least " << requiredFeeLevel;
1471 if (feeLevelPaid >= requiredFeeLevel)
1472 {
1473 JLOG(j_.trace()) << "Applying queued transaction "
1474 << candidateIter->txID << " to open ledger.";
1475
1476 auto const [txnResult, didApply, _metadata] =
1477 candidateIter->apply(app, view, j_);
1478
1479 if (didApply)
1480 {
1481 // Remove the candidate from the queue
1482 JLOG(j_.debug())
1483 << "Queued transaction " << candidateIter->txID
1484 << " applied successfully with " << transToken(txnResult)
1485 << ". Remove from queue.";
1486
1487 candidateIter = eraseAndAdvance(candidateIter);
1488 ledgerChanged = true;
1489 }
1490 else if (
1491 isTefFailure(txnResult) || isTemMalformed(txnResult) ||
1492 candidateIter->retriesRemaining <= 0)
1493 {
1494 if (candidateIter->retriesRemaining <= 0)
1495 account.retryPenalty = true;
1496 else
1497 account.dropPenalty = true;
1498 JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
1499 << " failed with " << transToken(txnResult)
1500 << ". Remove from queue.";
1501 candidateIter = eraseAndAdvance(candidateIter);
1502 }
1503 else
1504 {
1505 JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
1506 << " failed with " << transToken(txnResult)
1507 << ". Leave in queue."
1508 << " Applied: " << didApply
1509 << ". Flags: " << candidateIter->flags;
1510 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1511 candidateIter->retriesRemaining = 1;
1512 else
1513 --candidateIter->retriesRemaining;
1514 candidateIter->lastResult = txnResult;
1515 if (account.dropPenalty && account.transactions.size() > 1 &&
1516 isFull<95>())
1517 {
1518 // The queue is close to full, this account has multiple
1519 // txs queued, and this account has had a transaction
1520 // fail.
1521 if (candidateIter->seqProxy.isTicket())
1522 {
1523 // Since the failed transaction has a ticket, order
1524 // doesn't matter. Drop this one.
1525 JLOG(j_.info())
1526 << "Queue is nearly full, and transaction "
1527 << candidateIter->txID << " failed with "
1528 << transToken(txnResult)
1529 << ". Removing ticketed tx from account "
1530 << account.account;
1531 candidateIter = eraseAndAdvance(candidateIter);
1532 }
1533 else
1534 {
1535 // Even though we're giving this transaction another
1536 // chance, chances are it won't recover. To avoid
1537 // making things worse, drop the _last_ transaction for
1538 // this account.
1539 auto dropRIter = account.transactions.rbegin();
1540 XRPL_ASSERT(
1541 dropRIter->second.account == candidateIter->account,
1542 "ripple::TxQ::accept : account check");
1543
1544 JLOG(j_.info())
1545 << "Queue is nearly full, and transaction "
1546 << candidateIter->txID << " failed with "
1547 << transToken(txnResult)
1548 << ". Removing last item from account "
1549 << account.account;
1550 auto endIter = byFee_.iterator_to(dropRIter->second);
1551 if (endIter != candidateIter)
1552 erase(endIter);
1553 ++candidateIter;
1554 }
1555 }
1556 else
1557 ++candidateIter;
1558 }
1559 }
1560 else
1561 {
1562 break;
1563 }
1564 }
1565
1566 // All transactions that can be moved out of the queue into the open
1567 // ledger have been. Rebuild the queue using the open ledger's
1568 // parent hash, so that transactions paying the same fee are
1569 // reordered.
1570 LedgerHash const& parentHash = view.info().parentHash;
1571 if (parentHash == parentHash_)
1572 JLOG(j_.warn()) << "Parent ledger hash unchanged from " << parentHash;
1573 else
1574 parentHash_ = parentHash;
1575
1576 [[maybe_unused]] auto const startingSize = byFee_.size();
1577 // byFee_ doesn't "own" the candidate objects inside it, so it's
1578 // perfectly safe to wipe it and start over, repopulating from
1579 // byAccount_.
1580 //
1581 // In the absence of a "re-sort the list in place" function, this
1582 // was the fastest method tried to repopulate the list.
1583 // Other methods included: create a new list and moving items over one at a
1584 // time, create a new list and merge the old list into it.
1585 byFee_.clear();
1586
1587 MaybeTx::parentHashComp = parentHash;
1588
1589 for (auto& [_, account] : byAccount_)
1590 {
1591 for (auto& [_, candidate] : account.transactions)
1592 {
1593 byFee_.insert(candidate);
1594 }
1595 }
1596 XRPL_ASSERT(
1597 byFee_.size() == startingSize,
1598 "ripple::TxQ::accept : byFee size match");
1599
1600 return ledgerChanged;
1601}
1602
1603// Public entry point for nextQueuableSeq().
1604//
1605// Acquires a lock and calls the implementation.
1607TxQ::nextQueuableSeq(std::shared_ptr<SLE const> const& sleAccount) const
1608{
1609 std::lock_guard<std::mutex> lock(mutex_);
1610 return nextQueuableSeqImpl(sleAccount, lock);
1611}
1612
1613// The goal is to return a SeqProxy for a sequence that will fill the next
1614// available hole in the queue for the passed in account.
1615//
1616// If there are queued transactions for the account then the first viable
1617// sequence number, that is not used by a transaction in the queue, must
1618// be found and returned.
1620TxQ::nextQueuableSeqImpl(
1621 std::shared_ptr<SLE const> const& sleAccount,
1622 std::lock_guard<std::mutex> const&) const
1623{
1624 // If the account is not in the ledger or a non-account was passed
1625 // then return zero. We have no idea.
1626 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1627 return SeqProxy::sequence(0);
1628
1629 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1630
1631 // If the account is not in the queue then acctSeqProx is good enough.
1632 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1633 if (accountIter == byAccount_.end() ||
1634 accountIter->second.transactions.empty())
1635 return acctSeqProx;
1636
1637 TxQAccount::TxMap const& acctTxs = accountIter->second.transactions;
1638
1639 // Ignore any sequence-based queued transactions that slipped into the
1640 // ledger while we were not watching. This does actually happen in the
1641 // wild, but it's uncommon.
1642 TxQAccount::TxMap::const_iterator txIter = acctTxs.lower_bound(acctSeqProx);
1643
1644 if (txIter == acctTxs.end() || !txIter->first.isSeq() ||
1645 txIter->first != acctSeqProx)
1646 // Either...
1647 // o There are no queued sequence-based transactions equal to or
1648 // following acctSeqProx or
1649 // o acctSeqProx is not currently in the queue.
1650 // So acctSeqProx is as good as it gets.
1651 return acctSeqProx;
1652
1653 // There are sequence-based transactions queued that follow acctSeqProx.
1654 // Locate the first opening to put a transaction into.
1655 SeqProxy attempt = txIter->second.consequences().followingSeq();
1656 while (++txIter != acctTxs.cend())
1657 {
1658 if (attempt < txIter->first)
1659 break;
1660
1661 attempt = txIter->second.consequences().followingSeq();
1662 }
1663 return attempt;
1664}
1665
1667TxQ::getRequiredFeeLevel(
1668 OpenView& view,
1669 ApplyFlags flags,
1670 FeeMetrics::Snapshot const& metricsSnapshot,
1671 std::lock_guard<std::mutex> const& lock) const
1672{
1673 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1674}
1675
1677TxQ::tryDirectApply(
1678 Application& app,
1679 OpenView& view,
1681 ApplyFlags flags,
1683{
1684 auto const account = (*tx)[sfAccount];
1685 auto const sleAccount = view.read(keylet::account(account));
1686
1687 // Don't attempt to direct apply if the account is not in the ledger.
1688 if (!sleAccount)
1689 return {};
1690
1691 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1692 SeqProxy const txSeqProx = tx->getSeqProxy();
1693
1694 // Can only directly apply if the transaction sequence matches the account
1695 // sequence or if the transaction uses a ticket.
1696 if (txSeqProx.isSeq() && txSeqProx != acctSeqProx)
1697 return {};
1698
1699 FeeLevel64 const requiredFeeLevel = [this, &view, flags]() {
1700 std::lock_guard lock(mutex_);
1701 return getRequiredFeeLevel(
1702 view, flags, feeMetrics_.getSnapshot(), lock);
1703 }();
1704
1705 // If the transaction's fee is high enough we may be able to put the
1706 // transaction straight into the ledger.
1707 FeeLevel64 const feeLevelPaid = getFeeLevelPaid(view, *tx);
1708
1709 if (feeLevelPaid >= requiredFeeLevel)
1710 {
1711 // Attempt to apply the transaction directly.
1712 auto const transactionID = tx->getTransactionID();
1713 JLOG(j_.trace()) << "Applying transaction " << transactionID
1714 << " to open ledger.";
1715
1716 auto const [txnResult, didApply, metadata] =
1717 ripple::apply(app, view, *tx, flags, j);
1718
1719 JLOG(j_.trace()) << "New transaction " << transactionID
1720 << (didApply ? " applied successfully with "
1721 : " failed with ")
1722 << transToken(txnResult);
1723
1724 if (didApply)
1725 {
1726 // If the applied transaction replaced a transaction in the
1727 // queue then remove the replaced transaction.
1728 std::lock_guard lock(mutex_);
1729
1730 AccountMap::iterator accountIter = byAccount_.find(account);
1731 if (accountIter != byAccount_.end())
1732 {
1733 TxQAccount& txQAcct = accountIter->second;
1734 if (auto const existingIter =
1735 txQAcct.transactions.find(txSeqProx);
1736 existingIter != txQAcct.transactions.end())
1737 {
1738 removeFromByFee(existingIter, tx);
1739 }
1740 }
1741 }
1742 return ApplyResult{txnResult, didApply, metadata};
1743 }
1744 return {};
1745}
1746
1748TxQ::removeFromByFee(
1749 std::optional<TxQAccount::TxMap::iterator> const& replacedTxIter,
1751{
1752 if (replacedTxIter && tx)
1753 {
1754 // If the transaction we're holding replaces a transaction in the
1755 // queue, remove the transaction that is being replaced.
1756 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1757 XRPL_ASSERT(
1758 deleteIter != byFee_.end(),
1759 "ripple::TxQ::removeFromByFee : found in byFee");
1760 XRPL_ASSERT(
1761 &(*replacedTxIter)->second == &*deleteIter,
1762 "ripple::TxQ::removeFromByFee : matching transaction");
1763 XRPL_ASSERT(
1764 deleteIter->seqProxy == tx->getSeqProxy(),
1765 "ripple::TxQ::removeFromByFee : matching sequence");
1766 XRPL_ASSERT(
1767 deleteIter->account == (*tx)[sfAccount],
1768 "ripple::TxQ::removeFromByFee : matching account");
1769
1770 erase(deleteIter);
1771 }
1772 return std::nullopt;
1773}
1774
1776TxQ::getMetrics(OpenView const& view) const
1777{
1778 Metrics result;
1779
1780 std::lock_guard lock(mutex_);
1781
1782 auto const snapshot = feeMetrics_.getSnapshot();
1783
1784 result.txCount = byFee_.size();
1785 result.txQMaxSize = maxSize_;
1786 result.txInLedger = view.txCount();
1787 result.txPerLedger = snapshot.txnsExpected;
1788 result.referenceFeeLevel = baseLevel;
1789 result.minProcessingFeeLevel =
1790 isFull() ? byFee_.rbegin()->feeLevel + FeeLevel64{1} : baseLevel;
1791 result.medFeeLevel = snapshot.escalationMultiplier;
1792 result.openLedgerFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view);
1793
1794 return result;
1795}
1796
1798TxQ::getTxRequiredFeeAndSeq(
1799 OpenView const& view,
1800 std::shared_ptr<STTx const> const& tx) const
1801{
1802 auto const account = (*tx)[sfAccount];
1803
1804 std::lock_guard lock(mutex_);
1805
1806 auto const snapshot = feeMetrics_.getSnapshot();
1807 auto const baseFee = calculateBaseFee(view, *tx);
1808 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1809
1810 auto const sle = view.read(keylet::account(account));
1811
1812 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1813 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1814 return {
1815 mulDiv(fee, baseFee, baseLevel)
1817 accountSeq,
1818 availableSeq};
1819}
1820
1822TxQ::getAccountTxs(AccountID const& account) const
1823{
1825
1826 std::lock_guard lock(mutex_);
1827
1828 AccountMap::const_iterator const accountIter{byAccount_.find(account)};
1829
1830 if (accountIter == byAccount_.end() ||
1831 accountIter->second.transactions.empty())
1832 return result;
1833
1834 result.reserve(accountIter->second.transactions.size());
1835 for (auto const& tx : accountIter->second.transactions)
1836 {
1837 result.emplace_back(tx.second.getTxDetails());
1838 }
1839 return result;
1840}
1841
1843TxQ::getTxs() const
1844{
1846
1847 std::lock_guard lock(mutex_);
1848
1849 result.reserve(byFee_.size());
1850
1851 for (auto const& tx : byFee_)
1852 result.emplace_back(tx.getTxDetails());
1853
1854 return result;
1855}
1856
1858TxQ::doRPC(Application& app) const
1859{
1860 auto const view = app.openLedger().current();
1861 if (!view)
1862 {
1863 BOOST_ASSERT(false);
1864 return {};
1865 }
1866
1867 auto const metrics = getMetrics(*view);
1868
1870
1871 auto& levels = ret[jss::levels] = Json::objectValue;
1872
1873 ret[jss::ledger_current_index] = view->info().seq;
1874 ret[jss::expected_ledger_size] = std::to_string(metrics.txPerLedger);
1875 ret[jss::current_ledger_size] = std::to_string(metrics.txInLedger);
1876 ret[jss::current_queue_size] = std::to_string(metrics.txCount);
1877 if (metrics.txQMaxSize)
1878 ret[jss::max_queue_size] = std::to_string(*metrics.txQMaxSize);
1879
1880 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1881 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1882 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1883 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1884
1885 auto const baseFee = view->fees().base;
1886 // If the base fee is 0 drops, but escalation has kicked in, treat the
1887 // base fee as if it is 1 drop, which makes the rest of the math
1888 // work.
1889 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1890 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1891 return XRPAmount{1};
1892 return baseFee;
1893 }();
1894 auto& drops = ret[jss::drops] = Json::Value();
1895
1896 drops[jss::base_fee] = to_string(baseFee);
1897 drops[jss::median_fee] = to_string(toDrops(metrics.medFeeLevel, baseFee));
1898 drops[jss::minimum_fee] = to_string(toDrops(
1899 metrics.minProcessingFeeLevel,
1900 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1901 auto openFee = toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1902 if (effectiveBaseFee &&
1903 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1904 openFee += 1;
1905 drops[jss::open_ledger_fee] = to_string(openFee);
1906
1907 return ret;
1908}
1909
1911
1913setup_TxQ(Config const& config)
1914{
1915 TxQ::Setup setup;
1916 auto const& section = config.section("transaction_queue");
1917 set(setup.ledgersInQueue, "ledgers_in_queue", section);
1918 set(setup.queueSizeMin, "minimum_queue_size", section);
1919 set(setup.retrySequencePercent, "retry_sequence_percent", section);
1921 "minimum_escalation_multiplier",
1922 section);
1923 set(setup.minimumTxnInLedger, "minimum_txn_in_ledger", section);
1925 "minimum_txn_in_ledger_standalone",
1926 section);
1927 set(setup.targetTxnInLedger, "target_txn_in_ledger", section);
1928 std::uint32_t max;
1929 if (set(max, "maximum_txn_in_ledger", section))
1930 {
1931 if (max < setup.minimumTxnInLedger)
1932 {
1933 Throw<std::runtime_error>(
1934 "The minimum number of low-fee transactions allowed "
1935 "per ledger (minimum_txn_in_ledger) exceeds "
1936 "the maximum number of low-fee transactions allowed per "
1937 "ledger (maximum_txn_in_ledger).");
1938 }
1939 if (max < setup.minimumTxnInLedgerSA)
1940 {
1941 Throw<std::runtime_error>(
1942 "The minimum number of low-fee transactions allowed "
1943 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1944 "the maximum number of low-fee transactions allowed per "
1945 "ledger (maximum_txn_in_ledger).");
1946 }
1947
1948 setup.maximumTxnInLedger.emplace(max);
1949 }
1950
1951 /* The math works as expected for any value up to and including
1952 MAXINT, but put a reasonable limit on this percentage so that
1953 the factor can't be configured to render escalation effectively
1954 moot. (There are other ways to do that, including
1955 minimum_txn_in_ledger.)
1956 */
1958 "normal_consensus_increase_percent",
1959 section);
1962
1963 /* If this percentage is outside of the 0-100 range, the results
1964 are nonsensical (uint overflows happen, so the limit grows
1965 instead of shrinking). 0 is not recommended.
1966 */
1968 "slow_consensus_decrease_percent",
1969 section);
1972
1973 set(setup.maximumTxnPerAccount, "maximum_txn_per_account", section);
1974 set(setup.minimumLastLedgerBuffer, "minimum_last_ledger_buffer", section);
1975
1976 setup.standAlone = config.standalone();
1977 return setup;
1978}
1979
1980} // 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:149
A generic endpoint for log messages.
Definition Journal.h:60
Stream debug() const
Definition Journal.h:328
Stream info() const
Definition Journal.h:334
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Stream warn() const
Definition Journal.h:340
virtual OpenLedger & openLedger()=0
Editable, discardable view that can build metadata for one tx.
Section & section(std::string const &name)
Returns the section with the given name.
bool standalone() const
Definition Config.h:336
RAII class to set and restore the Number switchover.
Definition IOUAmount.h:211
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:65
std::size_t txCount() const
Return the number of tx inserted since creation.
Definition OpenView.cpp:122
LedgerInfo const & info() const override
Returns information about the ledger.
Definition OpenView.cpp:138
bool exists(Keylet const &k) const override
Determine if a state item exists.
Definition OpenView.cpp:156
Rules const & rules() const override
Returns the tx processing rules.
Definition OpenView.cpp:150
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:169
Fees const & fees() const override
Returns the fees for the base ledger.
Definition OpenView.cpp:144
void apply(TxsRawView &to) const
Apply changes.
Definition OpenView.cpp:128
A view into a ledger.
Definition ReadView.h:51
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:130
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:615
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
SeqProxy getSeqProxy() const
Definition STTx.cpp:216
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:390
beast::Journal const j_
Journal.
Definition TxQ.h:398
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:173
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
Definition TxQ.h:381
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:234
Snapshot getSnapshot() const
Get the current Snapshot.
Definition TxQ.h:455
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
Definition TxQ.h:386
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
Definition TxQ.h:384
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
Definition TxQ.h:393
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:84
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Definition TxQ.h:396
Represents a transaction in the queue which may be applied later to the open ledger.
Definition TxQ.h:514
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
Definition TxQ.h:535
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
Definition TxQ.cpp:298
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfresult)
Constructor.
Definition TxQ.cpp:279
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
Definition TxQ.h:580
static LedgerHash parentHashComp
The hash of the parent ledger.
Definition TxQ.h:590
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
Definition TxQ.h:666
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:673
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
Definition TxQ.h:697
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:789
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:775
beast::Journal const j_
Journal.
Definition TxQ.h:763
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:782
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
Definition TxQ.cpp:1620
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:769
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:1667
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:64
Setup const setup_
Setup parameters used to control the behavior of the queue.
Definition TxQ.h:761
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Definition TxQ.cpp:1677
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:526
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:45
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
Definition TxQ.cpp:197
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
static ticket_t const ticket
Definition Indexes.h:171
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr struct ripple::open_ledger_t open_ledger
TxQ::Setup setup_TxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
Definition TxQ.cpp:1913
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
Definition TxQ.cpp:75
@ 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.
auto constexpr muldiv_max
Definition mulDiv.h:28
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
PreclaimResult preclaim(PreflightResult const &preflightResult, Application &app, OpenView const &view)
Gate a transaction based on static ledger information.
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,...
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
Definition TxQ.cpp:39
@ tefPAST_SEQ
Definition TER.h:175
@ tefNO_TICKET
Definition TER.h:185
@ tefINTERNAL
Definition TER.h:173
bool isTefFailure(TER x) noexcept
Definition TER.h:666
std::string transToken(TER code)
Definition TER.cpp:264
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:172
FeeLevel< std::uint64_t > FeeLevel64
Definition Units.h:451
@ tesSUCCESS
Definition TER.h:245
bool isTesSuccess(TER x) noexcept
Definition TER.h:678
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:146
bool isTemMalformed(TER x) noexcept
Definition TER.h:660
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.
@ tapFAIL_HARD
Definition ApplyView.h:35
@ tapNONE
Definition ApplyView.h:31
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
Definition TxQ.cpp:67
@ transactionID
transaction plus signature to give transaction ID
@ terNO_ACCOUNT
Definition TER.h:217
@ terPRE_SEQ
Definition TER.h:221
@ terQUEUED
Definition TER.h:225
@ terPRE_TICKET
Definition TER.h:226
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
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
XRPAmount reserve
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
TER const ter
Intermediate transaction result.
Definition applySteps.h:223
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:443
std::size_t const txnsExpected
Definition TxQ.h:447
FeeLevel64 const escalationMultiplier
Definition TxQ.h:450
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
Definition TxQ.h:165
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
Definition TxQ.h:181
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
Definition TxQ.h:186
std::size_t txPerLedger
Number of transactions expected per ledger.
Definition TxQ.h:176
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
Definition TxQ.h:172
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
Definition TxQ.h:178
std::size_t txInLedger
Number of transactions currently in the open ledger.
Definition TxQ.h:174
std::size_t txCount
Number of transactions in the queue.
Definition TxQ.h:170
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
Definition TxQ.h:183
Structure used to customize TxQ behavior.
Definition TxQ.h:70
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
Definition TxQ.h:146
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
Definition TxQ.h:103
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
Definition TxQ.h:148
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
Definition TxQ.h:100
std::size_t queueSizeMin
The smallest limit the queue is allowed.
Definition TxQ.h:87
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
Definition TxQ.h:120
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "works towards".
Definition TxQ.h:109
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
Definition TxQ.h:97
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
Definition TxQ.h:155
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
Definition TxQ.h:106
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
Definition TxQ.h:81
bool standAlone
Use standalone mode behavior.
Definition TxQ.h:157
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...
Definition TxQ.h:132
T tie(T... args)
T to_string(T... args)
T upper_bound(T... args)
T value_or(T... args)