20 #include <ripple/app/ledger/OpenLedger.h>
21 #include <ripple/app/main/Application.h>
22 #include <ripple/app/misc/LoadFeeTrack.h>
23 #include <ripple/app/misc/TxQ.h>
24 #include <ripple/app/tx/apply.h>
25 #include <ripple/basics/mulDiv.h>
26 #include <ripple/protocol/Feature.h>
27 #include <ripple/protocol/jss.h>
28 #include <ripple/protocol/st.h>
40 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
46 XRPAmount const mod = [&view, &tx, baseFee]() {
50 return def.signum() == 0 ?
XRPAmount{1} : def;
52 return std::pair{baseFee + mod, feePaid + mod};
55 assert(baseFee.signum() > 0);
56 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
76 return mulDiv(level, 100 + increasePercent, 100)
90 auto const txBegin = view.
txs.
begin();
91 auto const txEnd = view.
txs.
end();
98 assert(size == feeLevels.
size());
101 <<
"Ledger " << view.
info().
seq <<
" has " << size <<
" transactions. "
102 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
113 auto const upperLimit = std::max<std::uint64_t>(
130 auto const next = [&] {
158 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
209 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
213 static_assert(sumOfFirstSquares(1).first ==
true);
214 static_assert(sumOfFirstSquares(1).second == 1);
216 static_assert(sumOfFirstSquares(2).first ==
true);
217 static_assert(sumOfFirstSquares(2).second == 5);
219 static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
220 static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
222 static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
224 sumOfFirstSquares(0x200000).second ==
245 auto const last =
current + seriesSize - 1;
264 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
265 auto const totalFeeLevel =
mulDiv(
266 multiplier, sumNlast.second - sumNcurrent.second, target * target);
268 return {totalFeeLevel.has_value(), *totalFeeLevel};
280 , feeLevel(feeLevel_)
284 , seqProxy(txn_->getSeqProxy())
285 , retriesRemaining(retriesAllowed)
287 , pfresult(pfresult_)
299 if (pfresult->rules != view.
rules() || pfresult->flags != flags)
301 JLOG(j.
debug()) <<
"Queued transaction " << txID
302 <<
" rules or flags have changed. Flags from "
303 << pfresult->flags <<
" to " << flags;
309 auto pcresult =
preclaim(*pfresult, app, view);
311 return doApply(pcresult, app, view);
323 TxQ::TxQAccount::TxMap::const_iterator
328 auto sameOrPrevIter = transactions.lower_bound(seqProx);
329 if (sameOrPrevIter != transactions.begin())
331 return sameOrPrevIter;
339 auto result = transactions.emplace(seqProx, std::move(txn));
340 assert(result.second);
341 assert(&result.first->second != &txn);
343 return result.first->second;
349 return transactions.erase(seqProx) != 0;
364 template <
size_t fillPercentage>
369 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
379 AccountMap::iterator
const& accountIter,
410 TxQAccount const& txQAcct = accountIter->second;
417 if (txSeqProx.isTicket())
424 if (txSeqProx != nextQueuable)
440 TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
441 -> FeeMultiSet::iterator_type
443 auto& txQAccount = byAccount_.at(candidateIter->account);
444 auto const seqProx = candidateIter->seqProxy;
445 auto const newCandidateIter = byFee_.erase(candidateIter);
449 auto const found = txQAccount.remove(seqProx);
453 return newCandidateIter;
458 -> FeeMultiSet::iterator_type
460 auto& txQAccount = byAccount_.at(candidateIter->account);
461 auto const accountIter =
462 txQAccount.transactions.find(candidateIter->seqProxy);
463 assert(accountIter != txQAccount.transactions.end());
469 candidateIter->seqProxy.isTicket() ||
470 accountIter == txQAccount.transactions.begin());
471 assert(byFee_.iterator_to(accountIter->second) == candidateIter);
472 auto const accountNextIter =
std::next(accountIter);
476 auto const feeNextIter =
std::next(candidateIter);
477 bool const useAccountNext =
478 accountNextIter != txQAccount.transactions.end() &&
479 accountNextIter->first > candidateIter->seqProxy &&
480 (feeNextIter == byFee_.end() ||
481 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
483 auto const candidateNextIter = byFee_.erase(candidateIter);
484 txQAccount.transactions.erase(accountIter);
486 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
493 TxQ::TxQAccount::TxMap::const_iterator begin,
494 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
496 for (
auto it = begin; it != end; ++it)
498 byFee_.erase(byFee_.iterator_to(it->second));
500 return txQAccount.transactions.erase(begin, end);
508 TxQ::AccountMap::iterator
const& accountIter,
509 TxQAccount::TxMap::iterator beginTxIter,
518 assert(beginTxIter != accountIter->second.transactions.end());
522 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
526 metricsSnapshot, view, txExtraCount, dist + 1);
530 if (!requiredTotalFeeLevel.first)
537 [](
auto const& total,
auto const& txn) {
538 return total + txn.second.feeLevel;
542 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
547 for (
auto it = beginTxIter; it != endTxIter; ++it)
549 auto txResult = it->second.apply(app, view, j);
554 --it->second.retriesRemaining;
555 it->second.lastResult = txResult.first;
576 if (!txResult.second)
579 return {txResult.first,
false};
584 auto const txResult =
doApply(
preclaim(pfresult, app, view), app, view);
590 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
592 if (endTxIter != accountIter->second.transactions.end() &&
593 endTxIter->first == tSeqProx)
727 return *directApplied;
739 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
741 return {pfresult.ter,
false};
746 auto const sleAccount = view.
read(accountKey);
752 SeqProxy const txSeqProx = tx->getSeqProxy();
770 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
783 TxQAccount::TxMap::iterator first_,
784 TxQAccount::TxMap::iterator end_)
785 : first(first_), end(end_)
789 TxQAccount::TxMap::iterator first;
790 TxQAccount::TxMap::iterator end;
797 if (!accountIsInQueue)
802 TxQAccount::TxMap::iterator
const firstIter =
805 if (firstIter == acctTxs.
end())
810 return {TxIter{firstIter, acctTxs.
end()}};
813 auto const acctTxCount{
821 if (pfresult.consequences.isBlocker())
829 <<
". Account has other queued transactions.";
832 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
837 <<
". Blocker does not replace lone queued transaction.";
844 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
846 if (accountIsInQueue)
860 auto const requiredFeeLevel =
872 if (acctTxCount == 1 &&
873 txIter->first->second.consequences().isBlocker() &&
874 (txIter->first->first != txSeqProx))
888 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
892 <<
"Found transaction in queue for account " << account
893 <<
" with " << txSeqProx <<
" new txn fee level is "
894 << feeLevelPaid <<
", old txn fee level is "
895 << existingIter->second.feeLevel
896 <<
", new txn needs fee level of " << requiredRetryLevel;
897 if (feeLevelPaid > requiredRetryLevel)
902 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
903 << existingIter->second.txID <<
" in favor of "
911 <<
" in favor of queued " << existingIter->second.txID;
923 : applyView(&view, flags), openView(&applyView)
930 if (acctTxCount == 0)
935 if (txSeqProx.
isSeq())
937 if (acctSeqProx > txSeqProx)
939 if (acctSeqProx < txSeqProx)
948 TxQAccount const& txQAcct = accountIter->second;
950 if (acctSeqProx > txSeqProx)
959 bool requiresMultiTxn =
false;
960 if (acctTxCount > 1 || !replacedTxIter)
975 requiresMultiTxn =
true;
978 if (requiresMultiTxn)
992 TxQAccount::TxMap::const_iterator
const prevIter =
1001 assert(prevIter != txIter->end);
1002 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1006 if (txSeqProx.
isSeq())
1008 if (txSeqProx < acctSeqProx)
1010 else if (txSeqProx > acctSeqProx)
1014 else if (!replacedTxIter)
1021 if (txSeqProx.
isSeq() &&
1031 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1036 if (iter->first != txSeqProx)
1038 totalFee += iter->second.consequences().fee();
1040 iter->second.consequences().potentialSpend();
1042 else if (
std::next(iter) != txIter->end)
1047 totalFee += pfresult.consequences.fee();
1048 potentialSpend += pfresult.consequences.potentialSpend();
1081 auto const balance = (*sleAccount)[
sfBalance].xrp();
1100 auto const base = view.
fees().
base;
1101 if (totalFee >= balance ||
1102 (reserve > 10 * base && totalFee >= reserve))
1106 <<
". Total fees in flight too high.";
1111 multiTxn.
emplace(view, flags);
1113 auto const sleBump = multiTxn->applyView.peek(accountKey);
1120 auto const potentialTotalSpend = totalFee +
1125 multiTxn->applyView.fees().base == 0));
1126 sleBump->setFieldAmount(
sfBalance, balance - potentialTotalSpend);
1149 auto const pcresult =
1150 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1151 if (!pcresult.likelyToClaimFee)
1152 return {pcresult.ter,
false};
1158 << account <<
" has fee level of " << feeLevelPaid
1159 <<
" needs at least " << requiredFeeLevel
1160 <<
" to get in the open ledger, which has "
1161 << view.txCount() <<
" entries.";
1182 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1200 sandbox.
apply(view);
1212 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1218 return {ter,
false};
1225 if (!replacedTxIter &&
isFull())
1227 auto lastRIter =
byFee_.rbegin();
1228 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1232 if (lastRIter ==
byFee_.rend())
1242 <<
" would kick a transaction from the same account ("
1243 << account <<
") out of the queue.";
1246 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1247 auto endEffectiveFeeLevel = [&]() {
1251 if (lastRIter->feeLevel > feeLevelPaid ||
1252 endAccount.transactions.size() == 1)
1253 return lastRIter->feeLevel;
1257 endAccount.transactions.begin(),
1258 endAccount.transactions.end(),
1260 [&](
auto const& total,
1264 txn.second.feeLevel / endAccount.transactions.size();
1266 txn.second.feeLevel % endAccount.transactions.size();
1267 if (total.first >= max - next || total.second >= max - mod)
1268 return {max, FeeLevel64{0}};
1270 return {total.first + next, total.second + mod};
1272 return endTotal.first +
1273 endTotal.second / endAccount.transactions.size();
1275 if (feeLevelPaid > endEffectiveFeeLevel)
1279 auto dropRIter = endAccount.transactions.rbegin();
1280 assert(dropRIter->second.account == lastRIter->account);
1282 <<
"Removing last item of account " << lastRIter->account
1283 <<
" from queue with average fee of " << endEffectiveFeeLevel
1286 erase(byFee_.iterator_to(dropRIter->second));
1291 <<
"Queue is full, and transaction " << transactionID
1292 <<
" fee is lower than end item's account average fee";
1300 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1303 if (!accountIsInQueue)
1308 byAccount_.emplace(account, TxQAccount(tx));
1320 auto& candidate = accountIter->second.add(
1324 byFee_.insert(candidate);
1325 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1326 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1327 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1328 << candidate.account <<
" to queue."
1329 <<
" Flags: " << flags;
1331 return {terQUEUED,
false};
1351 feeMetrics_.update(app, view, timeLeap, setup_);
1352 auto const& snapshot = feeMetrics_.getSnapshot();
1354 auto ledgerSeq = view.
info().
seq;
1358 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1361 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1363 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1365 byAccount_.at(candidateIter->account).dropPenalty =
true;
1366 candidateIter =
erase(candidateIter);
1376 for (
auto txQAccountIter = byAccount_.begin();
1377 txQAccountIter != byAccount_.end();)
1379 if (txQAccountIter->second.empty())
1380 txQAccountIter = byAccount_.erase(txQAccountIter);
1424 auto ledgerChanged =
false;
1428 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1430 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1432 auto& account = byAccount_.at(candidateIter->account);
1433 auto const beginIter = account.transactions.begin();
1434 if (candidateIter->seqProxy.isSeq() &&
1435 candidateIter->seqProxy > beginIter->first)
1441 <<
"Skipping queued transaction " << candidateIter->txID
1442 <<
" from account " << candidateIter->account
1443 <<
" as it is not the first.";
1447 auto const requiredFeeLevel =
1448 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1449 auto const feeLevelPaid = candidateIter->feeLevel;
1450 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1451 <<
" from account " << candidateIter->account
1452 <<
" has fee level of " << feeLevelPaid
1453 <<
" needs at least " << requiredFeeLevel;
1454 if (feeLevelPaid >= requiredFeeLevel)
1456 JLOG(j_.
trace()) <<
"Applying queued transaction "
1457 << candidateIter->txID <<
" to open ledger.";
1459 auto const [txnResult, didApply] =
1460 candidateIter->apply(app, view, j_);
1466 <<
"Queued transaction " << candidateIter->txID
1467 <<
" applied successfully with " <<
transToken(txnResult)
1468 <<
". Remove from queue.";
1470 candidateIter = eraseAndAdvance(candidateIter);
1471 ledgerChanged =
true;
1475 candidateIter->retriesRemaining <= 0)
1477 if (candidateIter->retriesRemaining <= 0)
1478 account.retryPenalty =
true;
1480 account.dropPenalty =
true;
1481 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1483 <<
". Remove from queue.";
1484 candidateIter = eraseAndAdvance(candidateIter);
1488 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1490 <<
". Leave in queue."
1491 <<
" Applied: " << didApply
1492 <<
". Flags: " << candidateIter->flags;
1493 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1494 candidateIter->retriesRemaining = 1;
1496 --candidateIter->retriesRemaining;
1497 candidateIter->lastResult = txnResult;
1498 if (account.dropPenalty && account.transactions.size() > 1 &&
1504 if (candidateIter->seqProxy.isTicket())
1509 <<
"Queue is nearly full, and transaction "
1510 << candidateIter->txID <<
" failed with "
1512 <<
". Removing ticketed tx from account "
1514 candidateIter = eraseAndAdvance(candidateIter);
1522 auto dropRIter = account.transactions.rbegin();
1524 dropRIter->second.account ==
1525 candidateIter->account);
1528 <<
"Queue is nearly full, and transaction "
1529 << candidateIter->txID <<
" failed with "
1531 <<
". Removing last item from account "
1533 auto endIter = byFee_.iterator_to(dropRIter->second);
1534 if (endIter != candidateIter)
1555 auto const startingSize = byFee_.
size();
1556 assert(parentHash != parentHash_);
1557 parentHash_ = parentHash;
1569 MaybeTx::parentHashComp = parentHash;
1571 for (
auto& [_, account] : byAccount_)
1573 for (
auto& [_, candidate] : account.transactions)
1575 byFee_.insert(candidate);
1578 assert(byFee_.size() == startingSize);
1580 return ledgerChanged;
1590 return nextQueuableSeqImpl(sleAccount, lock);
1600 TxQ::nextQueuableSeqImpl(
1607 return SeqProxy::sequence(0);
1612 auto const accountIter = byAccount_.find((*sleAccount)[
sfAccount]);
1613 if (accountIter == byAccount_.end() ||
1614 accountIter->second.transactions.empty())
1622 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1624 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1625 txIter->first != acctSeqProx)
1635 SeqProxy attempt = txIter->second.consequences().followingSeq();
1636 while (++txIter != acctTxs.
cend())
1638 if (attempt < txIter->first)
1641 attempt = txIter->second.consequences().followingSeq();
1647 TxQ::getRequiredFeeLevel(
1653 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1657 TxQ::tryDirectApply(
1665 auto const sleAccount = view.
read(keylet::account(account));
1672 SeqProxy const txSeqProx = tx->getSeqProxy();
1676 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1679 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1681 return getRequiredFeeLevel(
1682 view, flags, feeMetrics_.getSnapshot(), lock);
1689 if (feeLevelPaid >= requiredFeeLevel)
1694 <<
" to open ledger.";
1696 auto const [txnResult, didApply] =
1700 << (didApply ?
" applied successfully with "
1710 AccountMap::iterator accountIter = byAccount_.find(account);
1711 if (accountIter != byAccount_.end())
1714 if (
auto const existingIter =
1718 removeFromByFee(existingIter, tx);
1722 return {
std::pair(txnResult, didApply)};
1728 TxQ::removeFromByFee(
1732 if (replacedTxIter && tx)
1736 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1737 assert(deleteIter != byFee_.end());
1738 assert(&(*replacedTxIter)->second == &*deleteIter);
1739 assert(deleteIter->seqProxy == tx->getSeqProxy());
1740 assert(deleteIter->account == (*tx)[
sfAccount]);
1744 return std::nullopt;
1754 auto const snapshot = feeMetrics_.getSnapshot();
1756 result.
txCount = byFee_.size();
1762 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1763 result.
medFeeLevel = snapshot.escalationMultiplier;
1770 TxQ::getTxRequiredFeeAndSeq(
1778 auto const snapshot = feeMetrics_.getSnapshot();
1780 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1782 auto const sle = view.
read(keylet::account(account));
1785 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1787 mulDiv(fee, baseFee, baseLevel)
1800 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1802 if (accountIter == byAccount_.end() ||
1803 accountIter->second.transactions.empty())
1806 result.
reserve(accountIter->second.transactions.size());
1807 for (
auto const& tx : accountIter->second.transactions)
1821 result.
reserve(byFee_.size());
1823 for (
auto const& tx : byFee_)
1835 BOOST_ASSERT(
false);
1839 auto const metrics = getMetrics(*view);
1845 ret[jss::ledger_current_index] = view->info().seq;
1846 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1847 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1849 if (metrics.txQMaxSize)
1852 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1853 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1854 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1855 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1857 auto const baseFee = view->fees().base;
1861 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1862 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1868 drops[jss::base_fee] = to_string(baseFee);
1869 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1870 drops[jss::minimum_fee] = to_string(
toDrops(
1871 metrics.minProcessingFeeLevel,
1872 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1873 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1874 if (effectiveBaseFee &&
1875 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1877 drops[jss::open_ledger_fee] = to_string(openFee);
1888 auto const& section = config.
section(
"transaction_queue");
1893 "minimum_escalation_multiplier",
1897 "minimum_txn_in_ledger_standalone",
1901 if (
set(max,
"maximum_txn_in_ledger", section))
1905 Throw<std::runtime_error>(
1906 "The minimum number of low-fee transactions allowed "
1907 "per ledger (minimum_txn_in_ledger) exceeds "
1908 "the maximum number of low-fee transactions allowed per "
1909 "ledger (maximum_txn_in_ledger).");
1913 Throw<std::runtime_error>(
1914 "The minimum number of low-fee transactions allowed "
1915 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1916 "the maximum number of low-fee transactions allowed per "
1917 "ledger (maximum_txn_in_ledger).");
1930 "normal_consensus_increase_percent",
1940 "slow_consensus_decrease_percent",