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