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