1#include <xrpld/app/ledger/OpenLedger.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/TxQ.h>
4#include <xrpld/app/tx/apply.h>
6#include <xrpl/basics/mulDiv.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/jss.h>
9#include <xrpl/protocol/st.h>
22 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
28 XRPAmount const mod = [&view, &tx, baseFee]() {
32 return def.signum() == 0 ?
XRPAmount{1} : def;
34 return std::pair{baseFee + mod, feePaid + mod};
37 XRPL_ASSERT(baseFee.signum() > 0,
"ripple::getFeeLevelPaid : positive fee");
38 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
58 return mulDiv(level, 100 + increasePercent, 100)
72 auto const txBegin = view.
txs.
begin();
73 auto const txEnd = view.
txs.
end();
81 size == feeLevels.
size(),
82 "ripple::TxQ::FeeMetrics::update : fee levels size");
85 <<
"Ledger " << view.
info().
seq <<
" has " << size <<
" transactions. "
86 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
114 auto const next = [&] {
142 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
193 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
197static_assert(sumOfFirstSquares(1).first ==
true);
198static_assert(sumOfFirstSquares(1).second == 1);
200static_assert(sumOfFirstSquares(2).first ==
true);
201static_assert(sumOfFirstSquares(2).second == 5);
203static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
204static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
206static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
208 sumOfFirstSquares(0x200000).second ==
229 auto const last =
current + seriesSize - 1;
236 "ripple::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
251 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
252 auto const totalFeeLevel =
mulDiv(
253 multiplier, sumNlast.second - sumNcurrent.second, target * target);
255 return {totalFeeLevel.has_value(), *totalFeeLevel};
267 , feeLevel(feeLevel_)
269 , account(txn_->getAccountID(sfAccount))
271 , seqProxy(txn_->getSeqProxy())
272 , retriesRemaining(retriesAllowed)
274 , pfresult(pfresult_)
283 pfresult,
"ripple::TxQ::MaybeTx::apply : preflight result is set");
286 if (pfresult->rules != view.
rules() || pfresult->flags != flags)
288 JLOG(j.
debug()) <<
"Queued transaction " << txID
289 <<
" rules or flags have changed. Flags from "
290 << pfresult->flags <<
" to " << flags;
296 auto pcresult =
preclaim(*pfresult, app, view);
298 return doApply(pcresult, app, view);
310TxQ::TxQAccount::TxMap::const_iterator
315 auto sameOrPrevIter = transactions.lower_bound(seqProx);
316 if (sameOrPrevIter != transactions.begin())
318 return sameOrPrevIter;
326 auto result = transactions.emplace(seqProx, std::move(txn));
328 result.second,
"ripple::TxQ::TxQAccount::add : emplace succeeded");
330 &result.first->second != &txn,
331 "ripple::TxQ::TxQAccount::add : transaction moved");
333 return result.first->second;
339 return transactions.erase(seqProx) != 0;
354template <
size_t fillPercentage>
359 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
369 AccountMap::iterator
const& accountIter,
400 TxQAccount const& txQAcct = accountIter->second;
407 if (txSeqProx.isTicket())
414 if (txSeqProx != nextQueuable)
430TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
431 -> FeeMultiSet::iterator_type
433 auto& txQAccount = byAccount_.at(candidateIter->account);
434 auto const seqProx = candidateIter->seqProxy;
435 auto const newCandidateIter = byFee_.erase(candidateIter);
439 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
440 XRPL_ASSERT(found,
"ripple::TxQ::erase : account removed");
442 return newCandidateIter;
447 -> FeeMultiSet::iterator_type
449 auto& txQAccount = byAccount_.at(candidateIter->account);
450 auto const accountIter =
451 txQAccount.transactions.find(candidateIter->seqProxy);
453 accountIter != txQAccount.transactions.end(),
454 "ripple::TxQ::eraseAndAdvance : account found");
460 candidateIter->seqProxy.isTicket() ||
461 accountIter == txQAccount.transactions.begin(),
462 "ripple::TxQ::eraseAndAdvance : ticket or sequence");
464 byFee_.iterator_to(accountIter->second) == candidateIter,
465 "ripple::TxQ::eraseAndAdvance : found in byFee");
466 auto const accountNextIter =
std::next(accountIter);
470 auto const feeNextIter =
std::next(candidateIter);
471 bool const useAccountNext =
472 accountNextIter != txQAccount.transactions.end() &&
473 accountNextIter->first > candidateIter->seqProxy &&
474 (feeNextIter == byFee_.end() ||
475 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
477 auto const candidateNextIter = byFee_.erase(candidateIter);
478 txQAccount.transactions.erase(accountIter);
480 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
487 TxQ::TxQAccount::TxMap::const_iterator begin,
488 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
490 for (
auto it = begin; it != end; ++it)
492 byFee_.erase(byFee_.iterator_to(it->second));
494 return txQAccount.transactions.erase(begin, end);
502 TxQ::AccountMap::iterator
const& accountIter,
503 TxQAccount::TxMap::iterator beginTxIter,
513 beginTxIter != accountIter->second.transactions.end(),
514 "ripple::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
518 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
522 metricsSnapshot, view, txExtraCount, dist + 1);
526 if (!requiredTotalFeeLevel.first)
533 [](
auto const& total,
auto const& txn) {
534 return total + txn.second.feeLevel;
538 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
543 for (
auto it = beginTxIter; it != endTxIter; ++it)
545 auto txResult = it->second.apply(app, view, j);
550 --it->second.retriesRemaining;
551 it->second.lastResult = txResult.ter;
572 if (!txResult.applied)
575 return {txResult.ter,
false};
580 auto const txResult =
doApply(
preclaim(pfresult, app, view), app, view);
582 if (txResult.applied)
586 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
588 if (endTxIter != accountIter->second.transactions.end() &&
589 endTxIter->first == tSeqProx)
722 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
724 return {pfresult.ter,
false};
729 return *directApplied;
739 auto const account = (*tx)[sfAccount];
741 auto const sleAccount = view.
read(accountKey);
747 SeqProxy const txSeqProx = tx->getSeqProxy();
765 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
778 TxQAccount::TxMap::iterator first_,
779 TxQAccount::TxMap::iterator end_)
780 : first(first_), end(end_)
784 TxQAccount::TxMap::iterator first;
785 TxQAccount::TxMap::iterator end;
792 if (!accountIsInQueue)
797 TxQAccount::TxMap::iterator
const firstIter =
800 if (firstIter == acctTxs.
end())
805 return {TxIter{firstIter, acctTxs.
end()}};
808 auto const acctTxCount{
816 if (pfresult.consequences.isBlocker())
824 <<
". Account has other queued transactions.";
827 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
832 <<
". Blocker does not replace lone queued transaction.";
839 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
841 if (accountIsInQueue)
855 auto const requiredFeeLevel =
867 if (acctTxCount == 1 &&
868 txIter->first->second.consequences().isBlocker() &&
869 (txIter->first->first != txSeqProx))
883 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
887 <<
"Found transaction in queue for account " << account
888 <<
" with " << txSeqProx <<
" new txn fee level is "
889 << feeLevelPaid <<
", old txn fee level is "
890 << existingIter->second.feeLevel
891 <<
", new txn needs fee level of " << requiredRetryLevel;
892 if (feeLevelPaid > requiredRetryLevel)
897 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
898 << existingIter->second.txID <<
" in favor of "
906 <<
" in favor of queued " << existingIter->second.txID;
918 : applyView(&view, flags), openView(&applyView)
925 if (acctTxCount == 0)
930 if (txSeqProx.
isSeq())
932 if (acctSeqProx > txSeqProx)
934 if (acctSeqProx < txSeqProx)
943 TxQAccount const& txQAcct = accountIter->second;
945 if (acctSeqProx > txSeqProx)
954 bool requiresMultiTxn =
false;
955 if (acctTxCount > 1 || !replacedTxIter)
970 requiresMultiTxn =
true;
973 if (requiresMultiTxn)
987 TxQAccount::TxMap::const_iterator
const prevIter =
997 prevIter != txIter->end,
"ripple::TxQ::apply : not end");
998 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1002 if (txSeqProx.
isSeq())
1004 if (txSeqProx < acctSeqProx)
1006 else if (txSeqProx > acctSeqProx)
1010 else if (!replacedTxIter)
1017 if (txSeqProx.
isSeq() &&
1027 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1032 if (iter->first != txSeqProx)
1034 totalFee += iter->second.consequences().fee();
1036 iter->second.consequences().potentialSpend();
1038 else if (
std::next(iter) != txIter->end)
1043 totalFee += pfresult.consequences.fee();
1044 potentialSpend += pfresult.consequences.potentialSpend();
1077 auto const balance = (*sleAccount)[sfBalance].xrp();
1096 auto const base = view.
fees().
base;
1097 if (totalFee >= balance ||
1098 (reserve > 10 * base && totalFee >= reserve))
1102 <<
". Total fees in flight too high.";
1107 multiTxn.
emplace(view, flags);
1109 auto const sleBump = multiTxn->applyView.peek(accountKey);
1116 auto const potentialTotalSpend = totalFee +
1121 multiTxn->applyView.fees().base == 0),
1122 "ripple::TxQ::apply : total spend check");
1123 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1129 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1146 auto const pcresult =
1147 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1148 if (!pcresult.likelyToClaimFee)
1149 return {pcresult.
ter,
false};
1152 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"ripple::TxQ::apply : minimum fee");
1155 << account <<
" has fee level of " << feeLevelPaid
1156 <<
" needs at least " << requiredFeeLevel
1157 <<
" to get in the open ledger, which has "
1158 << view.
txCount() <<
" entries.";
1179 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1197 sandbox.
apply(view);
1209 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1215 return {ter,
false};
1222 if (!replacedTxIter &&
isFull())
1224 auto lastRIter =
byFee_.rbegin();
1225 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1229 if (lastRIter ==
byFee_.rend())
1239 <<
" would kick a transaction from the same account ("
1240 << account <<
") out of the queue.";
1243 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1244 auto endEffectiveFeeLevel = [&]() {
1248 if (lastRIter->feeLevel > feeLevelPaid ||
1249 endAccount.transactions.size() == 1)
1250 return lastRIter->feeLevel;
1254 endAccount.transactions.begin(),
1255 endAccount.transactions.end(),
1257 [&](
auto const& total,
1261 txn.second.feeLevel / endAccount.transactions.size();
1263 txn.second.feeLevel % endAccount.transactions.size();
1264 if (total.first >= max - next || total.second >= max - mod)
1265 return {max, FeeLevel64{0}};
1267 return {total.first + next, total.second + mod};
1269 return endTotal.first +
1270 endTotal.second / endAccount.transactions.size();
1272 if (feeLevelPaid > endEffectiveFeeLevel)
1276 auto dropRIter = endAccount.transactions.rbegin();
1278 dropRIter->second.account == lastRIter->account,
1279 "ripple::TxQ::apply : cheapest transaction found");
1281 <<
"Removing last item of account " << lastRIter->account
1282 <<
" from queue with average fee of " << endEffectiveFeeLevel
1291 <<
" fee is lower than end item's account average fee";
1299 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1302 if (!accountIsInQueue)
1305 [[maybe_unused]]
bool created =
false;
1307 byAccount_.emplace(account, TxQAccount(tx));
1308 XRPL_ASSERT(created,
"ripple::TxQ::apply : account created");
1318 auto& candidate = accountIter->second.add(
1322 byFee_.insert(candidate);
1323 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1324 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1325 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1326 << candidate.account <<
" to queue."
1327 <<
" Flags: " << flags;
1349 feeMetrics_.update(app, view, timeLeap, setup_);
1350 auto const& snapshot = feeMetrics_.getSnapshot();
1352 auto ledgerSeq = view.
info().
seq;
1356 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1359 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1361 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1363 byAccount_.at(candidateIter->account).dropPenalty =
true;
1364 candidateIter =
erase(candidateIter);
1374 for (
auto txQAccountIter = byAccount_.begin();
1375 txQAccountIter != byAccount_.end();)
1377 if (txQAccountIter->second.empty())
1378 txQAccountIter = byAccount_.erase(txQAccountIter);
1422 auto ledgerChanged =
false;
1426 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1428 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1430 auto& account = byAccount_.at(candidateIter->account);
1431 auto const beginIter = account.transactions.begin();
1432 if (candidateIter->seqProxy.isSeq() &&
1433 candidateIter->seqProxy > beginIter->first)
1439 <<
"Skipping queued transaction " << candidateIter->txID
1440 <<
" from account " << candidateIter->account
1441 <<
" as it is not the first.";
1445 auto const requiredFeeLevel =
1446 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1447 auto const feeLevelPaid = candidateIter->feeLevel;
1448 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1449 <<
" from account " << candidateIter->account
1450 <<
" has fee level of " << feeLevelPaid
1451 <<
" needs at least " << requiredFeeLevel;
1452 if (feeLevelPaid >= requiredFeeLevel)
1454 JLOG(j_.
trace()) <<
"Applying queued transaction "
1455 << candidateIter->txID <<
" to open ledger.";
1457 auto const [txnResult, didApply, _metadata] =
1458 candidateIter->apply(app, view, j_);
1464 <<
"Queued transaction " << candidateIter->txID
1465 <<
" applied successfully with " <<
transToken(txnResult)
1466 <<
". Remove from queue.";
1468 candidateIter = eraseAndAdvance(candidateIter);
1469 ledgerChanged =
true;
1473 candidateIter->retriesRemaining <= 0)
1475 if (candidateIter->retriesRemaining <= 0)
1476 account.retryPenalty =
true;
1478 account.dropPenalty =
true;
1479 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1481 <<
". Remove from queue.";
1482 candidateIter = eraseAndAdvance(candidateIter);
1486 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1488 <<
". Leave in queue."
1489 <<
" Applied: " << didApply
1490 <<
". Flags: " << candidateIter->flags;
1491 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1492 candidateIter->retriesRemaining = 1;
1494 --candidateIter->retriesRemaining;
1495 candidateIter->lastResult = txnResult;
1496 if (account.dropPenalty && account.transactions.size() > 1 &&
1502 if (candidateIter->seqProxy.isTicket())
1507 <<
"Queue is nearly full, and transaction "
1508 << candidateIter->txID <<
" failed with "
1510 <<
". Removing ticketed tx from account "
1512 candidateIter = eraseAndAdvance(candidateIter);
1520 auto dropRIter = account.transactions.rbegin();
1522 dropRIter->second.account == candidateIter->account,
1523 "ripple::TxQ::accept : account check");
1526 <<
"Queue is nearly full, and transaction "
1527 << candidateIter->txID <<
" failed with "
1529 <<
". Removing last item from account "
1531 auto endIter = byFee_.iterator_to(dropRIter->second);
1532 if (endIter != candidateIter)
1552 if (parentHash == parentHash_)
1553 JLOG(j_.
warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1555 parentHash_ = parentHash;
1557 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1568 MaybeTx::parentHashComp = parentHash;
1570 for (
auto& [_, account] : byAccount_)
1572 for (
auto& [_, candidate] : account.transactions)
1574 byFee_.insert(candidate);
1578 byFee_.size() == startingSize,
1579 "ripple::TxQ::accept : byFee size match");
1581 return ledgerChanged;
1591 return nextQueuableSeqImpl(sleAccount, lock);
1601TxQ::nextQueuableSeqImpl(
1607 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1608 return SeqProxy::sequence(0);
1610 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1613 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1614 if (accountIter == byAccount_.end() ||
1615 accountIter->second.transactions.empty())
1623 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1625 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1626 txIter->first != acctSeqProx)
1636 SeqProxy attempt = txIter->second.consequences().followingSeq();
1637 while (++txIter != acctTxs.
cend())
1639 if (attempt < txIter->first)
1642 attempt = txIter->second.consequences().followingSeq();
1648TxQ::getRequiredFeeLevel(
1654 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1665 auto const account = (*tx)[sfAccount];
1666 auto const sleAccount = view.
read(keylet::account(account));
1672 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1673 SeqProxy const txSeqProx = tx->getSeqProxy();
1677 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1680 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1682 return getRequiredFeeLevel(
1683 view, flags, feeMetrics_.getSnapshot(), lock);
1690 if (feeLevelPaid >= requiredFeeLevel)
1695 <<
" to open ledger.";
1697 auto const [txnResult, didApply, metadata] =
1701 << (didApply ?
" applied successfully with "
1711 AccountMap::iterator accountIter = byAccount_.find(account);
1712 if (accountIter != byAccount_.end())
1715 if (
auto const existingIter =
1719 removeFromByFee(existingIter, tx);
1723 return ApplyResult{txnResult, didApply, metadata};
1729TxQ::removeFromByFee(
1733 if (replacedTxIter && tx)
1737 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1739 deleteIter != byFee_.end(),
1740 "ripple::TxQ::removeFromByFee : found in byFee");
1742 &(*replacedTxIter)->second == &*deleteIter,
1743 "ripple::TxQ::removeFromByFee : matching transaction");
1745 deleteIter->seqProxy == tx->getSeqProxy(),
1746 "ripple::TxQ::removeFromByFee : matching sequence");
1748 deleteIter->account == (*tx)[sfAccount],
1749 "ripple::TxQ::removeFromByFee : matching account");
1763 auto const snapshot = feeMetrics_.getSnapshot();
1765 result.
txCount = byFee_.size();
1771 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1772 result.
medFeeLevel = snapshot.escalationMultiplier;
1779TxQ::getTxRequiredFeeAndSeq(
1783 auto const account = (*tx)[sfAccount];
1787 auto const snapshot = feeMetrics_.getSnapshot();
1789 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1791 auto const sle = view.
read(keylet::account(account));
1793 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1794 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1796 mulDiv(fee, baseFee, baseLevel)
1809 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1811 if (accountIter == byAccount_.end() ||
1812 accountIter->second.transactions.empty())
1815 result.
reserve(accountIter->second.transactions.size());
1816 for (
auto const& tx : accountIter->second.transactions)
1830 result.
reserve(byFee_.size());
1832 for (
auto const& tx : byFee_)
1844 BOOST_ASSERT(
false);
1848 auto const metrics = getMetrics(*view);
1854 ret[jss::ledger_current_index] = view->info().seq;
1855 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1856 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1858 if (metrics.txQMaxSize)
1861 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1862 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1863 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1864 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1866 auto const baseFee = view->fees().base;
1870 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1871 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1877 drops[jss::base_fee] = to_string(baseFee);
1878 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1879 drops[jss::minimum_fee] = to_string(
toDrops(
1880 metrics.minProcessingFeeLevel,
1881 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1882 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1883 if (effectiveBaseFee &&
1884 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1886 drops[jss::open_ledger_fee] = to_string(openFee);
1897 auto const& section = config.
section(
"transaction_queue");
1902 "minimum_escalation_multiplier",
1906 "minimum_txn_in_ledger_standalone",
1910 if (
set(max,
"maximum_txn_in_ledger", section))
1914 Throw<std::runtime_error>(
1915 "The minimum number of low-fee transactions allowed "
1916 "per ledger (minimum_txn_in_ledger) exceeds "
1917 "the maximum number of low-fee transactions allowed per "
1918 "ledger (maximum_txn_in_ledger).");
1922 Throw<std::runtime_error>(
1923 "The minimum number of low-fee transactions allowed "
1924 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1925 "the maximum number of low-fee transactions allowed per "
1926 "ledger (maximum_txn_in_ledger).");
1939 "normal_consensus_increase_percent",
1949 "slow_consensus_decrease_percent",
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
virtual OpenLedger & openLedger()=0
Editable, discardable view that can build metadata for one tx.
Section & section(std::string const &name)
Returns the section with the given name.
RAII class to set and restore the Number switchover.
std::shared_ptr< OpenView const > current() const
Returns a view to the current open ledger.
Writable ledger view that accumulates state and tx changes.
std::size_t txCount() const
Return the number of tx inserted since creation.
LedgerInfo const & info() const override
Returns information about the ledger.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Fees const & fees() const override
Returns the fees for the base ledger.
void apply(TxsRawView &to) const
Apply changes.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
std::uint32_t getFieldU32(SField const &field) const
bool isFieldPresent(SField const &field) const
SeqProxy getSeqProxy() const
A type that represents either a sequence value or a ticket value.
static constexpr SeqProxy sequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
constexpr bool isSeq() const
constexpr std::uint32_t value() const
constexpr bool isTicket() const
std::size_t txnsExpected_
Number of transactions expected per ledger.
beast::Journal const j_
Journal.
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...
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
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.
Snapshot getSnapshot() const
Get the current Snapshot.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
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.
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Represents a transaction in the queue which may be applied later to the open ledger.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfresult)
Constructor.
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
static LedgerHash parentHashComp
The hash of the parent ledger.
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
TxMap transactions
Sequence number will be used as the key.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level)
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
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.
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
ApplyResult tryClearAccountQueueUpThruTx(Application &app, OpenView &view, STTx const &tx, AccountMap::iterator const &accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const &pfresult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, beast::Journal j)
All-or-nothing attempt to try to apply the queued txs for accountIter up to and including tx.
bool isFull() const
Is the queue at least fillPercentage full?
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...
FeeMetrics feeMetrics_
Tracks the current state of the queue.
virtual ~TxQ()
Destructor.
FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::lock_guard< std::mutex > const &lock) const
TxQ(Setup const &setup, beast::Journal j)
Constructor.
static constexpr FeeLevel64 baseLevel
Fee level for single-signed reference transaction.
Setup const setup_
Setup parameters used to control the behavior of the queue.
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
ApplyResult apply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Add a new transaction to the open ledger, hold it in the queue, or reject it.
constexpr int signum() const noexcept
Return the sign of the amount.
static constexpr std::size_t size()
T emplace_back(T... args)
@ objectValue
object value (collection of name/value pairs).
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
Keylet account(AccountID const &id) noexcept
AccountID root.
static ticket_t const ticket
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr struct ripple::open_ledger_t open_ledger
TxQ::Setup setup_TxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
ApplyResult doApply(PreclaimResult const &preclaimResult, Application &app, OpenView &view)
Apply a prechecked transaction to an OpenView.
auto constexpr muldiv_max
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
PreclaimResult preclaim(PreflightResult const &preflightResult, Application &app, OpenView const &view)
Gate a transaction based on static ledger information.
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
bool isTefFailure(TER x) noexcept
std::string transToken(TER code)
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
FeeLevel< std::uint64_t > FeeLevel64
bool isTesSuccess(TER x) noexcept
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
bool isTemMalformed(TER x) noexcept
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
@ transactionID
transaction plus signature to give transaction ID
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
A pair of SHAMap key and LedgerEntryType.
TER const ter
Intermediate transaction result.
Describes the results of the preflight check.
Snapshot of the externally relevant FeeMetrics fields at any given time.
std::size_t const txnsExpected
FeeLevel64 const escalationMultiplier
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txPerLedger
Number of transactions expected per ledger.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
std::size_t txInLedger
Number of transactions currently in the open ledger.
std::size_t txCount
Number of transactions in the queue.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
Structure used to customize TxQ behavior.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
std::size_t queueSizeMin
The smallest limit the queue is allowed.
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "works towards".
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
bool standAlone
Use standalone mode behavior.
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...