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,
"xrpl::getFeeLevelPaid : positive fee");
38 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
67 auto const txBegin = view.
txs.
begin();
68 auto const txEnd = view.
txs.
end();
73 XRPL_ASSERT(size == feeLevels.
size(),
"xrpl::TxQ::FeeMetrics::update : fee levels size");
76 <<
"Ledger " << view.
header().
seq <<
" has " << size <<
" transactions. "
77 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected") <<
". Expected transactions is currently "
87 auto const upperLimit =
99 auto const next = [&] {
129 JLOG(
j_.
debug()) <<
"Expected transactions updated to " <<
txnsExpected_ <<
" and multiplier updated to "
175 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
179static_assert(sumOfFirstSquares(1).first ==
true);
180static_assert(sumOfFirstSquares(1).second == 1);
182static_assert(sumOfFirstSquares(2).first ==
true);
183static_assert(sumOfFirstSquares(2).second == 5);
185static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
186static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
188static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
208 auto const last =
current + seriesSize - 1;
215 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
230 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
231 auto const totalFeeLevel =
mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target);
233 return {totalFeeLevel.has_value(), *totalFeeLevel};
245 , feeLevel(feeLevel_)
247 , account(txn_->getAccountID(sfAccount))
249 , seqProxy(txn_->getSeqProxy())
250 , retriesRemaining(retriesAllowed)
252 , pfResult(pfResult_)
260 XRPL_ASSERT(pfResult,
"xrpl::TxQ::MaybeTx::apply : preflight result is set");
263 if (pfResult->rules != view.
rules() || pfResult->flags != flags)
265 JLOG(j.
debug()) <<
"Queued transaction " << txID <<
" rules or flags have changed. Flags from "
266 << pfResult->flags <<
" to " << flags;
268 pfResult.emplace(
preflight(app, view.
rules(), pfResult->tx, flags, pfResult->j));
271 auto pcresult =
preclaim(*pfResult, app, view);
273 return doApply(pcresult, app, view);
284TxQ::TxQAccount::TxMap::const_iterator
289 auto sameOrPrevIter = transactions.lower_bound(seqProx);
290 if (sameOrPrevIter != transactions.begin())
292 return sameOrPrevIter;
300 auto result = transactions.emplace(seqProx, std::move(txn));
301 XRPL_ASSERT(result.second,
"xrpl::TxQ::TxQAccount::add : emplace succeeded");
302 XRPL_ASSERT(&result.first->second != &txn,
"xrpl::TxQ::TxQAccount::add : transaction moved");
304 return result.first->second;
310 return transactions.erase(seqProx) != 0;
324template <
size_t fillPercentage>
328 static_assert(fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
338 AccountMap::iterator
const& accountIter,
367 TxQAccount const& txQAcct = accountIter->second;
374 if (txSeqProx.isTicket())
381 if (txSeqProx != nextQueuable)
397TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter) -> FeeMultiSet::iterator_type
399 auto& txQAccount = byAccount_.at(candidateIter->account);
400 auto const seqProx = candidateIter->seqProxy;
401 auto const newCandidateIter = byFee_.erase(candidateIter);
405 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
406 XRPL_ASSERT(found,
"xrpl::TxQ::erase : account removed");
408 return newCandidateIter;
414 auto& txQAccount = byAccount_.at(candidateIter->account);
415 auto const accountIter = txQAccount.transactions.find(candidateIter->seqProxy);
416 XRPL_ASSERT(accountIter != txQAccount.transactions.end(),
"xrpl::TxQ::eraseAndAdvance : account found");
422 candidateIter->seqProxy.isTicket() || accountIter == txQAccount.transactions.begin(),
423 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
425 byFee_.iterator_to(accountIter->second) == candidateIter,
"xrpl::TxQ::eraseAndAdvance : found in byFee");
426 auto const accountNextIter =
std::next(accountIter);
430 auto const feeNextIter =
std::next(candidateIter);
431 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
432 accountNextIter->first > candidateIter->seqProxy &&
433 (feeNextIter == byFee_.end() || byFee_.value_comp()(accountNextIter->second, *feeNextIter));
435 auto const candidateNextIter = byFee_.erase(candidateIter);
436 txQAccount.transactions.erase(accountIter);
438 return useAccountNext ? byFee_.iterator_to(accountNextIter->second) : candidateNextIter;
444 TxQ::TxQAccount::TxMap::const_iterator begin,
445 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
447 for (
auto it = begin; it != end; ++it)
449 byFee_.erase(byFee_.iterator_to(it->second));
451 return txQAccount.transactions.erase(begin, end);
459 TxQ::AccountMap::iterator
const& accountIter,
460 TxQAccount::TxMap::iterator beginTxIter,
470 beginTxIter != accountIter->second.transactions.end(),
471 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
475 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
478 auto const requiredTotalFeeLevel =
483 if (!requiredTotalFeeLevel.first)
486 auto const totalFeeLevelPaid =
487 std::accumulate(beginTxIter, endTxIter, feeLevelPaid, [](
auto const& total,
auto const& txn) {
488 return total + txn.second.feeLevel;
492 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
497 for (
auto it = beginTxIter; it != endTxIter; ++it)
499 auto txResult = it->second.apply(app, view, j);
504 --it->second.retriesRemaining;
505 it->second.lastResult = txResult.ter;
526 if (!txResult.applied)
529 return {txResult.ter,
false};
534 auto const txResult =
doApply(
preclaim(pfResult, app, view), app, view);
536 if (txResult.applied)
540 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
542 if (endTxIter != accountIter->second.transactions.end() && endTxIter->first == tSeqProx)
670 auto const pfResult =
preflight(app, view.
rules(), *tx, flags, j);
672 return {pfResult.ter,
false};
677 return *directApplied;
687 auto const account = (*tx)[sfAccount];
689 auto const sleAccount = view.
read(accountKey);
695 SeqProxy const txSeqProx = tx->getSeqProxy();
712 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
724 TxIter(TxQAccount::TxMap::iterator first_, TxQAccount::TxMap::iterator end_) : first(first_), end(end_)
728 TxQAccount::TxMap::iterator first;
729 TxQAccount::TxMap::iterator end;
733 if (!accountIsInQueue)
738 TxQAccount::TxMap::iterator
const firstIter = acctTxs.
lower_bound(acctSeqProx);
740 if (firstIter == acctTxs.
end())
745 return {TxIter{firstIter, acctTxs.
end()}};
748 auto const acctTxCount{!txIter ? 0 :
std::distance(txIter->first, txIter->end)};
755 if (pfResult.consequences.isBlocker())
762 <<
". Account has other queued transactions.";
765 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
769 <<
". Blocker does not replace lone queued transaction.";
777 if (accountIsInQueue)
802 if (acctTxCount == 1 && txIter->first->second.consequences().isBlocker() && (txIter->first->first != txSeqProx))
816 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
818 JLOG(
j_.
trace()) <<
"Found transaction in queue for account " << account <<
" with " << txSeqProx
819 <<
" new txn fee level is " << feeLevelPaid <<
", old txn fee level is "
820 << existingIter->second.feeLevel <<
", new txn needs fee level of " << requiredRetryLevel;
821 if (feeLevelPaid > requiredRetryLevel)
826 JLOG(
j_.
trace()) <<
"Removing transaction from queue " << existingIter->second.txID <<
" in favor of "
833 << existingIter->second.txID;
844 MultiTxn(
OpenView& view,
ApplyFlags flags) : applyView(&view, flags), openView(&applyView)
851 if (acctTxCount == 0)
856 if (txSeqProx.
isSeq())
858 if (acctSeqProx > txSeqProx)
860 if (acctSeqProx < txSeqProx)
869 TxQAccount const& txQAcct = accountIter->second;
871 if (acctSeqProx > txSeqProx)
880 bool requiresMultiTxn =
false;
881 if (acctTxCount > 1 || !replacedTxIter)
885 TER const ter{
canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
889 requiresMultiTxn =
true;
892 if (requiresMultiTxn)
906 TxQAccount::TxMap::const_iterator
const prevIter = txQAcct.
getPrevTx(txSeqProx);
914 XRPL_ASSERT(prevIter != txIter->end,
"xrpl::TxQ::apply : not end");
915 if (prevIter == txIter->end || txSeqProx < prevIter->first)
919 if (txSeqProx.
isSeq())
921 if (txSeqProx < acctSeqProx)
923 else if (txSeqProx > acctSeqProx)
927 else if (!replacedTxIter)
943 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
948 if (iter->first != txSeqProx)
950 totalFee += iter->second.consequences().fee();
951 potentialSpend += iter->second.consequences().potentialSpend();
958 totalFee += pfResult.consequences.fee();
959 potentialSpend += pfResult.consequences.potentialSpend();
992 auto const balance = (*sleAccount)[sfBalance].xrp();
1011 auto const base = view.
fees().
base;
1012 if (totalFee >= balance || (reserve > 10 * base && totalFee >= reserve))
1015 JLOG(
j_.
trace()) <<
"Ignoring transaction " <<
transactionID <<
". Total fees in flight too high.";
1020 multiTxn.
emplace(view, flags);
1022 auto const sleBump = multiTxn->applyView.peek(accountKey);
1029 auto const potentialTotalSpend = totalFee +
std::min(balance -
std::min(balance, reserve), potentialSpend);
1032 (potentialTotalSpend ==
XRPAmount{0} && multiTxn->applyView.fees().base == 0),
1033 "xrpl::TxQ::apply : total spend check");
1034 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1040 sleBump->at(sfSequence) =
1056 auto const pcresult =
preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1057 if (!pcresult.likelyToClaimFee)
1058 return {pcresult.
ter,
false};
1061 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"xrpl::TxQ::apply : minimum fee");
1063 JLOG(
j_.
trace()) <<
"Transaction " <<
transactionID <<
" from account " << account <<
" has fee level of "
1064 << feeLevelPaid <<
" needs at least " << requiredFeeLevel
1065 <<
" to get in the open ledger, which has " << view.
txCount() <<
" entries.";
1104 sandbox.
apply(view);
1115 TER const ter{
canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1120 return {ter,
false};
1127 if (!replacedTxIter &&
isFull())
1129 auto lastRIter =
byFee_.rbegin();
1130 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1134 if (lastRIter ==
byFee_.rend())
1143 <<
" would kick a transaction from the same account (" << account <<
") out of the queue.";
1146 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1147 auto endEffectiveFeeLevel = [&]() {
1151 if (lastRIter->feeLevel > feeLevelPaid || endAccount.transactions.size() == 1)
1152 return lastRIter->feeLevel;
1156 endAccount.transactions.begin(),
1157 endAccount.transactions.end(),
1161 auto next = txn.second.feeLevel / endAccount.transactions.size();
1162 auto mod = txn.second.feeLevel % endAccount.transactions.size();
1163 if (total.first >= max - next || total.second >= max - mod)
1164 return {max, FeeLevel64{0}};
1166 return {total.first + next, total.second + mod};
1168 return endTotal.first + endTotal.second / endAccount.transactions.size();
1170 if (feeLevelPaid > endEffectiveFeeLevel)
1174 auto dropRIter = endAccount.transactions.rbegin();
1176 dropRIter->second.account == lastRIter->account,
"xrpl::TxQ::apply : cheapest transaction found");
1177 JLOG(
j_.
info()) <<
"Removing last item of account " << lastRIter->account
1178 <<
" from queue with average fee of " << endEffectiveFeeLevel <<
" in favor of "
1185 <<
" fee is lower than end item's account average fee";
1193 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1196 if (!accountIsInQueue)
1199 [[maybe_unused]]
bool created =
false;
1200 std::tie(accountIter, created) = byAccount_.emplace(account, TxQAccount(tx));
1201 XRPL_ASSERT(created,
"xrpl::TxQ::apply : account created");
1211 auto& candidate = accountIter->second.add({tx,
transactionID, feeLevelPaid, flags, pfResult});
1214 byFee_.insert(candidate);
1215 JLOG(j_.debug()) <<
"Added transaction " << candidate.txID <<
" with result " <<
transToken(pfResult.ter)
1216 <<
" from " << (accountIsInQueue ?
"existing" :
"new") <<
" account " << candidate.account
1218 <<
" Flags: " << flags;
1240 feeMetrics_.update(app, view, timeLeap, setup_);
1241 auto const& snapshot = feeMetrics_.getSnapshot();
1246 maxSize_ =
std::max(snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1249 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1251 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1253 byAccount_.at(candidateIter->account).dropPenalty =
true;
1254 candidateIter =
erase(candidateIter);
1264 for (
auto txQAccountIter = byAccount_.begin(); txQAccountIter != byAccount_.end();)
1266 if (txQAccountIter->second.empty())
1267 txQAccountIter = byAccount_.erase(txQAccountIter);
1311 auto ledgerChanged =
false;
1315 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1317 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1319 auto& account = byAccount_.at(candidateIter->account);
1320 auto const beginIter = account.transactions.begin();
1321 if (candidateIter->seqProxy.isSeq() && candidateIter->seqProxy > beginIter->first)
1326 JLOG(j_.trace()) <<
"Skipping queued transaction " << candidateIter->txID <<
" from account "
1327 << candidateIter->account <<
" as it is not the first.";
1331 auto const requiredFeeLevel = getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1332 auto const feeLevelPaid = candidateIter->feeLevel;
1333 JLOG(j_.trace()) <<
"Queued transaction " << candidateIter->txID <<
" from account " << candidateIter->account
1334 <<
" has fee level of " << feeLevelPaid <<
" needs at least " << requiredFeeLevel;
1335 if (feeLevelPaid >= requiredFeeLevel)
1337 JLOG(j_.trace()) <<
"Applying queued transaction " << candidateIter->txID <<
" to open ledger.";
1339 auto const [txnResult, didApply, _metadata] = candidateIter->apply(app, view, j_);
1344 JLOG(j_.debug()) <<
"Queued transaction " << candidateIter->txID <<
" applied successfully with "
1345 <<
transToken(txnResult) <<
". Remove from queue.";
1347 candidateIter = eraseAndAdvance(candidateIter);
1348 ledgerChanged =
true;
1352 if (candidateIter->retriesRemaining <= 0)
1353 account.retryPenalty =
true;
1355 account.dropPenalty =
true;
1356 JLOG(j_.debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1357 <<
transToken(txnResult) <<
". Remove from queue.";
1358 candidateIter = eraseAndAdvance(candidateIter);
1362 JLOG(j_.debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1363 <<
transToken(txnResult) <<
". Leave in queue."
1364 <<
" Applied: " << didApply <<
". Flags: " << candidateIter->flags;
1365 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1366 candidateIter->retriesRemaining = 1;
1368 --candidateIter->retriesRemaining;
1369 candidateIter->lastResult = txnResult;
1370 if (account.dropPenalty && account.transactions.size() > 1 && isFull<95>())
1375 if (candidateIter->seqProxy.isTicket())
1379 JLOG(j_.info()) <<
"Queue is nearly full, and transaction " << candidateIter->txID
1381 <<
". Removing ticketed tx from account " << account.account;
1382 candidateIter = eraseAndAdvance(candidateIter);
1390 auto dropRIter = account.transactions.rbegin();
1392 dropRIter->second.account == candidateIter->account,
"xrpl::TxQ::accept : account check");
1394 JLOG(j_.info()) <<
"Queue is nearly full, and transaction " << candidateIter->txID
1396 <<
". Removing last item from account " << account.account;
1397 auto endIter = byFee_.iterator_to(dropRIter->second);
1398 if (endIter != candidateIter)
1418 if (parentHash == parentHash_)
1419 JLOG(j_.warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1421 parentHash_ = parentHash;
1423 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1434 MaybeTx::parentHashComp = parentHash;
1436 for (
auto& [_, account] : byAccount_)
1438 for (
auto& [_, candidate] : account.transactions)
1440 byFee_.insert(candidate);
1443 XRPL_ASSERT(byFee_.size() == startingSize,
"xrpl::TxQ::accept : byFee size match");
1445 return ledgerChanged;
1455 return nextQueuableSeqImpl(sleAccount, lock);
1469 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1470 return SeqProxy::sequence(0);
1472 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1475 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1476 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1484 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1486 if (txIter == acctTxs.
end() || !txIter->first.isSeq() || txIter->first != acctSeqProx)
1496 SeqProxy attempt = txIter->second.consequences().followingSeq();
1497 while (++txIter != acctTxs.
cend())
1499 if (attempt < txIter->first)
1502 attempt = txIter->second.consequences().followingSeq();
1508TxQ::getRequiredFeeLevel(
1514 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1525 auto const account = (*tx)[sfAccount];
1526 auto const sleAccount = view.
read(keylet::account(account));
1532 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1533 SeqProxy const txSeqProx = tx->getSeqProxy();
1537 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1540 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1542 return getRequiredFeeLevel(view, flags, feeMetrics_.getSnapshot(), lock);
1549 if (feeLevelPaid >= requiredFeeLevel)
1553 JLOG(j_.trace()) <<
"Applying transaction " <<
transactionID <<
" to open ledger.";
1555 auto const [txnResult, didApply, metadata] =
xrpl::apply(app, view, *tx, flags, j);
1558 << (didApply ?
" applied successfully with " :
" failed with ") <<
transToken(txnResult);
1566 AccountMap::iterator accountIter = byAccount_.find(account);
1567 if (accountIter != byAccount_.end())
1573 removeFromByFee(existingIter, tx);
1577 return ApplyResult{txnResult, didApply, metadata};
1583TxQ::removeFromByFee(
1587 if (replacedTxIter && tx)
1591 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1592 XRPL_ASSERT(deleteIter != byFee_.end(),
"xrpl::TxQ::removeFromByFee : found in byFee");
1593 XRPL_ASSERT(&(*replacedTxIter)->second == &*deleteIter,
"xrpl::TxQ::removeFromByFee : matching transaction");
1594 XRPL_ASSERT(deleteIter->seqProxy == tx->getSeqProxy(),
"xrpl::TxQ::removeFromByFee : matching sequence");
1595 XRPL_ASSERT(deleteIter->account == (*tx)[sfAccount],
"xrpl::TxQ::removeFromByFee : matching account");
1609 auto const snapshot = feeMetrics_.getSnapshot();
1611 result.
txCount = byFee_.size();
1617 result.
medFeeLevel = snapshot.escalationMultiplier;
1626 auto const account = (*tx)[sfAccount];
1630 auto const snapshot = feeMetrics_.getSnapshot();
1632 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1634 auto const sle = view.
read(keylet::account(account));
1636 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1637 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1651 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1653 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1656 result.
reserve(accountIter->second.transactions.size());
1657 for (
auto const& tx : accountIter->second.transactions)
1671 result.
reserve(byFee_.size());
1673 for (
auto const& tx : byFee_)
1685 BOOST_ASSERT(
false);
1689 auto const metrics = getMetrics(*view);
1695 ret[jss::ledger_current_index] = view->header().seq;
1696 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1697 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1699 if (metrics.txQMaxSize)
1702 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1703 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1704 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1705 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1707 auto const baseFee = view->fees().base;
1711 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1712 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1718 drops[jss::base_fee] = to_string(baseFee);
1719 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1720 drops[jss::minimum_fee] = to_string(
1721 toDrops(metrics.minProcessingFeeLevel, metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1722 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1723 if (effectiveBaseFee &&
toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1725 drops[jss::open_ledger_fee] = to_string(openFee);
1736 auto const& section = config.
section(
"transaction_queue");
1745 if (
set(max,
"maximum_txn_in_ledger", section))
1749 Throw<std::runtime_error>(
1750 "The minimum number of low-fee transactions allowed "
1751 "per ledger (minimum_txn_in_ledger) exceeds "
1752 "the maximum number of low-fee transactions allowed per "
1753 "ledger (maximum_txn_in_ledger).");
1757 Throw<std::runtime_error>(
1758 "The minimum number of low-fee transactions allowed "
1759 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1760 "the maximum number of low-fee transactions allowed per "
1761 "ledger (maximum_txn_in_ledger).");
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
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.
Fees const & fees() const override
Returns the fees for the base ledger.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
LedgerHeader const & header() const override
Returns information about the ledger.
void apply(TxsRawView &to) const
Apply changes.
Rules const & rules() const override
Returns the tx processing rules.
bool exists(Keylet const &k) const override
Determine if a state item exists.
virtual LedgerHeader const & header() 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 isTicket() const
constexpr std::uint32_t value() const
constexpr bool isSeq() const
virtual OpenLedger & openLedger()=0
Snapshot getSnapshot() const
Get the current Snapshot.
std::size_t txnsExpected_
Number of transactions expected per ledger.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
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...
beast::Journal const j_
Journal.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
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.
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
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.
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.
static LedgerHash parentHashComp
The hash of the parent ledger.
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfResult)
Constructor.
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
TxMap transactions
Sequence number will be used as the key.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
TxQ(Setup const &setup, beast::Journal j)
Constructor.
FeeMetrics feeMetrics_
Tracks the current state of the queue.
FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::lock_guard< std::mutex > const &lock) const
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
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...
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.
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.
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::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
virtual ~TxQ()
Destructor.
static constexpr FeeLevel64 baseLevel
Fee level for single-signed reference transaction.
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level)
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
Setup const setup_
Setup parameters used to control the behavior of the queue.
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)
static ticket_t const ticket
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
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,...
FeeLevel< std::uint64_t > FeeLevel64
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
constexpr struct xrpl::open_ledger_t open_ledger
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
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.
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
std::string transToken(TER code)
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
bool isTefFailure(TER x) noexcept
auto constexpr muldiv_max
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
ApplyResult doApply(PreclaimResult const &preclaimResult, Application &app, OpenView &view)
Apply a prechecked transaction to an OpenView.
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
@ transactionID
transaction plus signature to give transaction ID
bool isTesSuccess(TER x) noexcept
PreclaimResult preclaim(PreflightResult const &preflightResult, Application &app, OpenView const &view)
Gate a transaction based on static ledger information.
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
bool isTemMalformed(TER x) noexcept
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.
std::size_t txCount
Number of transactions in the queue.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txInLedger
Number of transactions currently in the open ledger.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
std::size_t txPerLedger
Number of transactions expected per ledger.
Structure used to customize TxQ behavior.
bool standAlone
Use standalone mode behavior.
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::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 minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::size_t queueSizeMin
The smallest limit the queue is allowed.
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...