1#include <xrpld/app/ledger/OpenLedger.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/TxQ.h>
5#include <xrpl/basics/mulDiv.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/jss.h>
8#include <xrpl/protocol/st.h>
9#include <xrpl/tx/apply.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)
58 return mulDiv(level, 100 + increasePercent, 100)
72 auto const txBegin = view.
txs.
begin();
73 auto const txEnd = view.
txs.
end();
80 XRPL_ASSERT(size == feeLevels.
size(),
"xrpl::TxQ::FeeMetrics::update : fee levels size");
83 <<
"Ledger " << view.
header().
seq <<
" has " << size <<
" transactions. "
84 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
85 <<
". Expected transactions is currently " <<
txnsExpected_ <<
" and multiplier is "
107 auto const next = [&] {
135 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) / 2;
184 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
188static_assert(sumOfFirstSquares(1).first ==
true);
189static_assert(sumOfFirstSquares(1).second == 1);
191static_assert(sumOfFirstSquares(2).first ==
true);
192static_assert(sumOfFirstSquares(2).second == 5);
194static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
195static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
197static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
217 auto const last =
current + seriesSize - 1;
224 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
239 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
240 auto const totalFeeLevel =
241 mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target);
243 return {totalFeeLevel.has_value(), *totalFeeLevel};
255 , feeLevel(feeLevel_)
257 , account(txn_->getAccountID(sfAccount))
259 , seqProxy(txn_->getSeqProxy())
260 , retriesRemaining(retriesAllowed)
262 , pfResult(pfResult_)
270 XRPL_ASSERT(pfResult,
"xrpl::TxQ::MaybeTx::apply : preflight result is set");
273 if (pfResult->rules != view.
rules() || pfResult->flags != flags)
275 JLOG(j.
debug()) <<
"Queued transaction " << txID
276 <<
" rules or flags have changed. Flags from " << pfResult->flags <<
" to "
279 pfResult.emplace(
preflight(app, view.
rules(), pfResult->tx, flags, pfResult->j));
282 auto pcresult =
preclaim(*pfResult, app, view);
284 return doApply(pcresult, app, view);
296TxQ::TxQAccount::TxMap::const_iterator
301 auto sameOrPrevIter = transactions.lower_bound(seqProx);
302 if (sameOrPrevIter != transactions.begin())
304 return sameOrPrevIter;
312 auto result = transactions.emplace(seqProx, std::move(txn));
313 XRPL_ASSERT(result.second,
"xrpl::TxQ::TxQAccount::add : emplace succeeded");
314 XRPL_ASSERT(&result.first->second != &txn,
"xrpl::TxQ::TxQAccount::add : transaction moved");
316 return result.first->second;
322 return transactions.erase(seqProx) != 0;
337template <
size_t fillPercentage>
341 static_assert(fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
351 AccountMap::iterator
const& accountIter,
381 TxQAccount const& txQAcct = accountIter->second;
388 if (txSeqProx.isTicket())
395 if (txSeqProx != nextQueuable)
411TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter) -> FeeMultiSet::iterator_type
413 auto& txQAccount = byAccount_.at(candidateIter->account);
414 auto const seqProx = candidateIter->seqProxy;
415 auto const newCandidateIter = byFee_.erase(candidateIter);
419 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
420 XRPL_ASSERT(found,
"xrpl::TxQ::erase : account removed");
422 return newCandidateIter;
427 -> FeeMultiSet::iterator_type
429 auto& txQAccount = byAccount_.at(candidateIter->account);
430 auto const accountIter = txQAccount.transactions.find(candidateIter->seqProxy);
432 accountIter != txQAccount.transactions.end(),
"xrpl::TxQ::eraseAndAdvance : account found");
438 candidateIter->seqProxy.isTicket() || accountIter == txQAccount.transactions.begin(),
439 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
441 byFee_.iterator_to(accountIter->second) == candidateIter,
442 "xrpl::TxQ::eraseAndAdvance : found in byFee");
443 auto const accountNextIter =
std::next(accountIter);
447 auto const feeNextIter =
std::next(candidateIter);
448 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
449 accountNextIter->first > candidateIter->seqProxy &&
450 (feeNextIter == byFee_.end() || byFee_.value_comp()(accountNextIter->second, *feeNextIter));
452 auto const candidateNextIter = byFee_.erase(candidateIter);
453 txQAccount.transactions.erase(accountIter);
455 return useAccountNext ? byFee_.iterator_to(accountNextIter->second) : candidateNextIter;
461 TxQ::TxQAccount::TxMap::const_iterator begin,
462 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
464 for (
auto it = begin; it != end; ++it)
466 byFee_.erase(byFee_.iterator_to(it->second));
468 return txQAccount.transactions.erase(begin, end);
476 TxQ::AccountMap::iterator
const& accountIter,
477 TxQAccount::TxMap::iterator beginTxIter,
487 beginTxIter != accountIter->second.transactions.end(),
488 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
492 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
495 auto const requiredTotalFeeLevel =
500 if (!requiredTotalFeeLevel.first)
504 beginTxIter, endTxIter, feeLevelPaid, [](
auto const& total,
auto const& txn) {
505 return total + txn.second.feeLevel;
509 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
514 for (
auto it = beginTxIter; it != endTxIter; ++it)
516 auto txResult = it->second.apply(app, view, j);
521 --it->second.retriesRemaining;
522 it->second.lastResult = txResult.ter;
543 if (!txResult.applied)
546 return {txResult.ter,
false};
551 auto const txResult =
doApply(
preclaim(pfResult, app, view), app, view);
553 if (txResult.applied)
557 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
559 if (endTxIter != accountIter->second.transactions.end() && endTxIter->first == tSeqProx)
692 auto const pfResult =
preflight(app, view.
rules(), *tx, flags, j);
694 return {pfResult.ter,
false};
699 return *directApplied;
709 auto const account = (*tx)[sfAccount];
711 auto const sleAccount = view.
read(accountKey);
717 SeqProxy const txSeqProx = tx->getSeqProxy();
734 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
746 TxIter(TxQAccount::TxMap::iterator first_, TxQAccount::TxMap::iterator end_)
747 : first(first_), end(end_)
751 TxQAccount::TxMap::iterator first;
752 TxQAccount::TxMap::iterator end;
757 if (!accountIsInQueue)
762 TxQAccount::TxMap::iterator
const firstIter = acctTxs.
lower_bound(acctSeqProx);
764 if (firstIter == acctTxs.
end())
769 return {TxIter{firstIter, acctTxs.
end()}};
772 auto const acctTxCount{!txIter ? 0 :
std::distance(txIter->first, txIter->end)};
779 if (pfResult.consequences.isBlocker())
786 <<
". Account has other queued transactions.";
789 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
793 <<
". Blocker does not replace lone queued transaction.";
800 auto replacedTxIter = [accountIsInQueue,
803 if (accountIsInQueue)
828 if (acctTxCount == 1 && txIter->first->second.consequences().isBlocker() &&
829 (txIter->first->first != txSeqProx))
843 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
844 auto requiredRetryLevel =
846 JLOG(
j_.
trace()) <<
"Found transaction in queue for account " << account <<
" with "
847 << txSeqProx <<
" new txn fee level is " << feeLevelPaid
848 <<
", old txn fee level is " << existingIter->second.feeLevel
849 <<
", new txn needs fee level of " << requiredRetryLevel;
850 if (feeLevelPaid > requiredRetryLevel)
855 JLOG(
j_.
trace()) <<
"Removing transaction from queue " << existingIter->second.txID
862 <<
" in favor of queued " << existingIter->second.txID;
873 MultiTxn(
OpenView& view,
ApplyFlags flags) : applyView(&view, flags), openView(&applyView)
880 if (acctTxCount == 0)
885 if (txSeqProx.
isSeq())
887 if (acctSeqProx > txSeqProx)
889 if (acctSeqProx < txSeqProx)
898 TxQAccount const& txQAcct = accountIter->second;
900 if (acctSeqProx > txSeqProx)
909 bool requiresMultiTxn =
false;
910 if (acctTxCount > 1 || !replacedTxIter)
915 canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
919 requiresMultiTxn =
true;
922 if (requiresMultiTxn)
936 TxQAccount::TxMap::const_iterator
const prevIter = txQAcct.
getPrevTx(txSeqProx);
944 XRPL_ASSERT(prevIter != txIter->end,
"xrpl::TxQ::apply : not end");
945 if (prevIter == txIter->end || txSeqProx < prevIter->first)
949 if (txSeqProx.
isSeq())
951 if (txSeqProx < acctSeqProx)
953 else if (txSeqProx > acctSeqProx)
957 else if (!replacedTxIter)
973 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
978 if (iter->first != txSeqProx)
980 totalFee += iter->second.consequences().fee();
981 potentialSpend += iter->second.consequences().potentialSpend();
988 totalFee += pfResult.consequences.fee();
989 potentialSpend += pfResult.consequences.potentialSpend();
1022 auto const balance = (*sleAccount)[sfBalance].xrp();
1041 auto const base = view.
fees().
base;
1042 if (totalFee >= balance || (reserve > 10 * base && totalFee >= reserve))
1046 <<
". Total fees in flight too high.";
1051 multiTxn.
emplace(view, flags);
1053 auto const sleBump = multiTxn->applyView.peek(accountKey);
1060 auto const potentialTotalSpend =
1064 (potentialTotalSpend ==
XRPAmount{0} && multiTxn->applyView.fees().base == 0),
1065 "xrpl::TxQ::apply : total spend check");
1066 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1072 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1089 auto const pcresult =
preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1090 if (!pcresult.likelyToClaimFee)
1091 return {pcresult.
ter,
false};
1094 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"xrpl::TxQ::apply : minimum fee");
1097 <<
" has fee level of " << feeLevelPaid <<
" needs at least "
1098 << requiredFeeLevel <<
" to get in the open ledger, which has "
1099 << view.
txCount() <<
" entries.";
1120 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1138 sandbox.
apply(view);
1149 TER const ter{
canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1154 return {ter,
false};
1161 if (!replacedTxIter &&
isFull())
1163 auto lastRIter =
byFee_.rbegin();
1164 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1168 if (lastRIter ==
byFee_.rend())
1177 <<
" would kick a transaction from the same account (" << account
1178 <<
") out of the queue.";
1181 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1182 auto endEffectiveFeeLevel = [&]() {
1186 if (lastRIter->feeLevel > feeLevelPaid || endAccount.transactions.size() == 1)
1187 return lastRIter->feeLevel;
1191 endAccount.transactions.begin(),
1192 endAccount.transactions.end(),
1196 auto next = txn.second.feeLevel / endAccount.transactions.size();
1197 auto mod = txn.second.feeLevel % endAccount.transactions.size();
1198 if (total.first >= max - next || total.second >= max - mod)
1199 return {max, FeeLevel64{0}};
1201 return {total.first + next, total.second + mod};
1203 return endTotal.first + endTotal.second / endAccount.transactions.size();
1205 if (feeLevelPaid > endEffectiveFeeLevel)
1209 auto dropRIter = endAccount.transactions.rbegin();
1211 dropRIter->second.account == lastRIter->account,
1212 "xrpl::TxQ::apply : cheapest transaction found");
1213 JLOG(
j_.
info()) <<
"Removing last item of account " << lastRIter->account
1214 <<
" from queue with average fee of " << endEffectiveFeeLevel
1215 <<
" in favor of " <<
transactionID <<
" with fee of " << feeLevelPaid;
1221 <<
" fee is lower than end item's account average fee";
1229 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1232 if (!accountIsInQueue)
1235 [[maybe_unused]]
bool created =
false;
1236 std::tie(accountIter, created) = byAccount_.emplace(account, TxQAccount(tx));
1237 XRPL_ASSERT(created,
"xrpl::TxQ::apply : account created");
1247 auto& candidate = accountIter->second.add({tx,
transactionID, feeLevelPaid, flags, pfResult});
1250 byFee_.insert(candidate);
1251 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID <<
" with result "
1253 << (accountIsInQueue ?
"existing" :
"new") <<
" account " << candidate.account
1255 <<
" Flags: " << flags;
1277 feeMetrics_.update(app, view, timeLeap, setup_);
1278 auto const& snapshot = feeMetrics_.getSnapshot();
1283 maxSize_ =
std::max(snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1286 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1288 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1290 byAccount_.at(candidateIter->account).dropPenalty =
true;
1291 candidateIter =
erase(candidateIter);
1301 for (
auto txQAccountIter = byAccount_.begin(); txQAccountIter != byAccount_.end();)
1303 if (txQAccountIter->second.empty())
1304 txQAccountIter = byAccount_.erase(txQAccountIter);
1348 auto ledgerChanged =
false;
1352 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1354 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1356 auto& account = byAccount_.at(candidateIter->account);
1357 auto const beginIter = account.transactions.begin();
1358 if (candidateIter->seqProxy.isSeq() && candidateIter->seqProxy > beginIter->first)
1363 JLOG(j_.
trace()) <<
"Skipping queued transaction " << candidateIter->txID
1364 <<
" from account " << candidateIter->account
1365 <<
" as it is not the first.";
1369 auto const requiredFeeLevel = getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1370 auto const feeLevelPaid = candidateIter->feeLevel;
1371 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID <<
" from account "
1372 << candidateIter->account <<
" has fee level of " << feeLevelPaid
1373 <<
" needs at least " << requiredFeeLevel;
1374 if (feeLevelPaid >= requiredFeeLevel)
1376 JLOG(j_.
trace()) <<
"Applying queued transaction " << candidateIter->txID
1377 <<
" to open ledger.";
1379 auto const [txnResult, didApply, _metadata] = candidateIter->apply(app, view, j_);
1384 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1385 <<
" applied successfully with " <<
transToken(txnResult)
1386 <<
". Remove from queue.";
1388 candidateIter = eraseAndAdvance(candidateIter);
1389 ledgerChanged =
true;
1393 candidateIter->retriesRemaining <= 0)
1395 if (candidateIter->retriesRemaining <= 0)
1396 account.retryPenalty =
true;
1398 account.dropPenalty =
true;
1399 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1400 <<
transToken(txnResult) <<
". Remove from queue.";
1401 candidateIter = eraseAndAdvance(candidateIter);
1405 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1406 <<
transToken(txnResult) <<
". Leave in queue."
1407 <<
" Applied: " << didApply <<
". Flags: " << candidateIter->flags;
1408 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1409 candidateIter->retriesRemaining = 1;
1411 --candidateIter->retriesRemaining;
1412 candidateIter->lastResult = txnResult;
1413 if (account.dropPenalty && account.transactions.size() > 1 && isFull<95>())
1418 if (candidateIter->seqProxy.isTicket())
1423 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1425 <<
". Removing ticketed tx from account " << account.account;
1426 candidateIter = eraseAndAdvance(candidateIter);
1434 auto dropRIter = account.transactions.rbegin();
1436 dropRIter->second.account == candidateIter->account,
1437 "xrpl::TxQ::accept : account check");
1440 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1442 <<
". Removing last item from account " << account.account;
1443 auto endIter = byFee_.iterator_to(dropRIter->second);
1444 if (endIter != candidateIter)
1464 if (parentHash == parentHash_)
1465 JLOG(j_.
warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1467 parentHash_ = parentHash;
1469 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1480 MaybeTx::parentHashComp = parentHash;
1482 for (
auto& [_, account] : byAccount_)
1484 for (
auto& [_, candidate] : account.transactions)
1486 byFee_.insert(candidate);
1489 XRPL_ASSERT(byFee_.size() == startingSize,
"xrpl::TxQ::accept : byFee size match");
1491 return ledgerChanged;
1501 return nextQueuableSeqImpl(sleAccount, lock);
1511TxQ::nextQueuableSeqImpl(
1517 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1518 return SeqProxy::sequence(0);
1520 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1523 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1524 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1532 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1534 if (txIter == acctTxs.
end() || !txIter->first.isSeq() || txIter->first != acctSeqProx)
1544 SeqProxy attempt = txIter->second.consequences().followingSeq();
1545 while (++txIter != acctTxs.
cend())
1547 if (attempt < txIter->first)
1550 attempt = txIter->second.consequences().followingSeq();
1556TxQ::getRequiredFeeLevel(
1562 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1573 auto const account = (*tx)[sfAccount];
1574 auto const sleAccount = view.
read(keylet::account(account));
1580 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1581 SeqProxy const txSeqProx = tx->getSeqProxy();
1585 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1588 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1590 return getRequiredFeeLevel(view, flags, feeMetrics_.getSnapshot(), lock);
1597 if (feeLevelPaid >= requiredFeeLevel)
1603 auto const [txnResult, didApply, metadata] =
xrpl::apply(app, view, *tx, flags, j);
1606 << (didApply ?
" applied successfully with " :
" failed with ")
1615 AccountMap::iterator accountIter = byAccount_.find(account);
1616 if (accountIter != byAccount_.end())
1622 removeFromByFee(existingIter, tx);
1626 return ApplyResult{txnResult, didApply, metadata};
1632TxQ::removeFromByFee(
1636 if (replacedTxIter && tx)
1640 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1641 XRPL_ASSERT(deleteIter != byFee_.end(),
"xrpl::TxQ::removeFromByFee : found in byFee");
1643 &(*replacedTxIter)->second == &*deleteIter,
1644 "xrpl::TxQ::removeFromByFee : matching transaction");
1646 deleteIter->seqProxy == tx->getSeqProxy(),
1647 "xrpl::TxQ::removeFromByFee : matching sequence");
1649 deleteIter->account == (*tx)[sfAccount],
1650 "xrpl::TxQ::removeFromByFee : matching account");
1664 auto const snapshot = feeMetrics_.getSnapshot();
1666 result.
txCount = byFee_.size();
1672 result.
medFeeLevel = snapshot.escalationMultiplier;
1681 auto const account = (*tx)[sfAccount];
1685 auto const snapshot = feeMetrics_.getSnapshot();
1687 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1689 auto const sle = view.
read(keylet::account(account));
1691 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1692 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1694 mulDiv(fee, baseFee, baseLevel)
1707 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1709 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1712 result.
reserve(accountIter->second.transactions.size());
1713 for (
auto const& tx : accountIter->second.transactions)
1727 result.
reserve(byFee_.size());
1729 for (
auto const& tx : byFee_)
1741 BOOST_ASSERT(
false);
1745 auto const metrics = getMetrics(*view);
1751 ret[jss::ledger_current_index] = view->header().seq;
1752 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1753 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1755 if (metrics.txQMaxSize)
1758 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1759 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1760 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1761 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1763 auto const baseFee = view->fees().base;
1767 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1768 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1774 drops[jss::base_fee] = to_string(baseFee);
1775 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1776 drops[jss::minimum_fee] = to_string(
toDrops(
1777 metrics.minProcessingFeeLevel,
1778 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1779 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1780 if (effectiveBaseFee &&
toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1782 drops[jss::open_ledger_fee] = to_string(openFee);
1793 auto const& section = config.
section(
"transaction_queue");
1802 if (
set(max,
"maximum_txn_in_ledger", section))
1806 Throw<std::runtime_error>(
1807 "The minimum number of low-fee transactions allowed "
1808 "per ledger (minimum_txn_in_ledger) exceeds "
1809 "the maximum number of low-fee transactions allowed per "
1810 "ledger (maximum_txn_in_ledger).");
1814 Throw<std::runtime_error>(
1815 "The minimum number of low-fee transactions allowed "
1816 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1817 "the maximum number of low-fee transactions allowed per "
1818 "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
PreflightResult preflight(ServiceRegistry ®istry, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
PreclaimResult preclaim(PreflightResult const &preflightResult, ServiceRegistry ®istry, OpenView const &view)
Gate a transaction based on static ledger information.
ApplyResult apply(ServiceRegistry ®istry, 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.
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)
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
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.
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry ®istry, OpenView &view)
Apply a prechecked transaction to an OpenView.
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,...