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)
64 return feeLevelPaid.second;
80 return mulDiv(level, 100 + increasePercent, 100).second;
93 auto const txBegin = view.
txs.
begin();
94 auto const txEnd = view.
txs.
end();
101 assert(size == feeLevels.
size());
104 <<
"Ledger " << view.
info().
seq <<
" has " << size <<
" transactions. "
105 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
116 auto const upperLimit = std::max<std::uint64_t>(
130 auto const next = [&] {
158 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
208 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
212 static_assert(sumOfFirstSquares(1).first ==
true);
213 static_assert(sumOfFirstSquares(1).second == 1);
215 static_assert(sumOfFirstSquares(2).first ==
true);
216 static_assert(sumOfFirstSquares(2).second == 5);
218 static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
219 static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
221 static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
223 sumOfFirstSquares(0x200000).second ==
244 auto const last =
current + seriesSize - 1;
263 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
264 auto const totalFeeLevel =
mulDiv(
265 multiplier, sumNlast.second - sumNcurrent.second, target * target);
267 return totalFeeLevel;
279 , feeLevel(feeLevel_)
283 , seqProxy(txn_->getSeqProxy())
284 , retriesRemaining(retriesAllowed)
286 , pfresult(pfresult_)
298 if (pfresult->rules != view.
rules() || pfresult->flags != flags)
300 JLOG(j.
debug()) <<
"Queued transaction " << txID
301 <<
" rules or flags have changed. Flags from "
302 << pfresult->flags <<
" to " << flags;
308 auto pcresult =
preclaim(*pfresult, app, view);
310 return doApply(pcresult, app, view);
322 TxQ::TxQAccount::TxMap::const_iterator
327 auto sameOrPrevIter = transactions.lower_bound(seqProx);
328 if (sameOrPrevIter != transactions.begin())
330 return sameOrPrevIter;
338 auto result = transactions.emplace(seqProx, std::move(txn));
339 assert(result.second);
340 assert(&result.first->second != &txn);
342 return result.first->second;
348 return transactions.erase(seqProx) != 0;
363 template <
size_t fillPercentage>
368 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
378 AccountMap::iterator
const& accountIter,
409 TxQAccount const& txQAcct = accountIter->second;
416 if (txSeqProx.isTicket())
423 if (txSeqProx != nextQueuable)
439 TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
440 -> FeeMultiSet::iterator_type
442 auto& txQAccount = byAccount_.at(candidateIter->account);
443 auto const seqProx = candidateIter->seqProxy;
444 auto const newCandidateIter = byFee_.erase(candidateIter);
448 auto const found = txQAccount.remove(seqProx);
452 return newCandidateIter;
457 -> FeeMultiSet::iterator_type
459 auto& txQAccount = byAccount_.at(candidateIter->account);
460 auto const accountIter =
461 txQAccount.transactions.find(candidateIter->seqProxy);
462 assert(accountIter != txQAccount.transactions.end());
468 candidateIter->seqProxy.isTicket() ||
469 accountIter == txQAccount.transactions.begin());
470 assert(byFee_.iterator_to(accountIter->second) == candidateIter);
471 auto const accountNextIter =
std::next(accountIter);
475 auto const feeNextIter =
std::next(candidateIter);
476 bool const useAccountNext =
477 accountNextIter != txQAccount.transactions.end() &&
478 accountNextIter->first > candidateIter->seqProxy &&
479 (feeNextIter == byFee_.end() ||
480 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
482 auto const candidateNextIter = byFee_.erase(candidateIter);
483 txQAccount.transactions.erase(accountIter);
485 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
492 TxQ::TxQAccount::TxMap::const_iterator begin,
493 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
495 for (
auto it = begin; it != end; ++it)
497 byFee_.erase(byFee_.iterator_to(it->second));
499 return txQAccount.transactions.erase(begin, end);
507 TxQ::AccountMap::iterator
const& accountIter,
508 TxQAccount::TxMap::iterator beginTxIter,
517 assert(beginTxIter != accountIter->second.transactions.end());
521 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
525 metricsSnapshot, view, txExtraCount, dist + 1);
529 if (!requiredTotalFeeLevel.first)
536 [](
auto const& total,
auto const& txn) {
537 return total + txn.second.feeLevel;
541 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
546 for (
auto it = beginTxIter; it != endTxIter; ++it)
548 auto txResult = it->second.apply(app, view, j);
553 --it->second.retriesRemaining;
554 it->second.lastResult = txResult.first;
575 if (!txResult.second)
578 return {txResult.first,
false};
583 auto const txResult =
doApply(
preclaim(pfresult, app, view), app, view);
589 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
591 if (endTxIter != accountIter->second.transactions.end() &&
592 endTxIter->first == tSeqProx)
726 return *directApplied;
738 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
740 return {pfresult.ter,
false};
745 auto const sleAccount = view.
read(accountKey);
751 SeqProxy const txSeqProx = tx->getSeqProxy();
769 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
782 TxQAccount::TxMap::iterator first_,
783 TxQAccount::TxMap::iterator end_)
784 : first(first_), end(end_)
788 TxQAccount::TxMap::iterator first;
789 TxQAccount::TxMap::iterator end;
796 if (!accountIsInQueue)
801 TxQAccount::TxMap::iterator
const firstIter =
804 if (firstIter == acctTxs.
end())
809 return {TxIter{firstIter, acctTxs.
end()}};
812 auto const acctTxCount{
820 if (pfresult.consequences.isBlocker())
828 <<
". Account has other queued transactions.";
831 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
836 <<
". Blocker does not replace lone queued transaction.";
843 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
845 if (accountIsInQueue)
859 auto const requiredFeeLevel =
871 if (acctTxCount == 1 &&
872 txIter->first->second.consequences().isBlocker() &&
873 (txIter->first->first != txSeqProx))
887 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
891 <<
"Found transaction in queue for account " << account
892 <<
" with " << txSeqProx <<
" new txn fee level is "
893 << feeLevelPaid <<
", old txn fee level is "
894 << existingIter->second.feeLevel
895 <<
", new txn needs fee level of " << requiredRetryLevel;
896 if (feeLevelPaid > requiredRetryLevel)
901 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
902 << existingIter->second.txID <<
" in favor of "
910 <<
" in favor of queued " << existingIter->second.txID;
922 : applyView(&view, flags), openView(&applyView)
929 if (acctTxCount == 0)
934 if (txSeqProx.
isSeq())
936 if (acctSeqProx > txSeqProx)
938 if (acctSeqProx < txSeqProx)
947 TxQAccount const& txQAcct = accountIter->second;
949 if (acctSeqProx > txSeqProx)
958 bool requiresMultiTxn =
false;
959 if (acctTxCount > 1 || !replacedTxIter)
974 requiresMultiTxn =
true;
977 if (requiresMultiTxn)
991 TxQAccount::TxMap::const_iterator
const prevIter =
1000 assert(prevIter != txIter->end);
1001 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1005 if (txSeqProx.
isSeq())
1007 if (txSeqProx < acctSeqProx)
1009 else if (txSeqProx > acctSeqProx)
1013 else if (!replacedTxIter)
1020 if (txSeqProx.
isSeq() &&
1030 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1035 if (iter->first != txSeqProx)
1037 totalFee += iter->second.consequences().fee();
1039 iter->second.consequences().potentialSpend();
1041 else if (
std::next(iter) != txIter->end)
1046 totalFee += pfresult.consequences.fee();
1047 potentialSpend += pfresult.consequences.potentialSpend();
1080 auto const balance = (*sleAccount)[
sfBalance].xrp();
1099 auto const base = view.
fees().
base;
1100 if (totalFee >= balance ||
1101 (reserve > 10 * base && totalFee >= reserve))
1105 <<
". Total fees in flight too high.";
1110 multiTxn.
emplace(view, flags);
1112 auto const sleBump = multiTxn->applyView.peek(accountKey);
1119 auto const potentialTotalSpend = totalFee +
1124 multiTxn->applyView.fees().base == 0));
1125 sleBump->setFieldAmount(
sfBalance, balance - potentialTotalSpend);
1148 auto const pcresult =
1149 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1150 if (!pcresult.likelyToClaimFee)
1151 return {pcresult.ter,
false};
1157 << account <<
" has fee level of " << feeLevelPaid
1158 <<
" needs at least " << requiredFeeLevel
1159 <<
" to get in the open ledger, which has "
1160 << view.txCount() <<
" entries.";
1181 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1199 sandbox.
apply(view);
1211 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1217 return {ter,
false};
1224 if (!replacedTxIter &&
isFull())
1226 auto lastRIter =
byFee_.rbegin();
1227 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1231 if (lastRIter ==
byFee_.rend())
1241 <<
" would kick a transaction from the same account ("
1242 << account <<
") out of the queue.";
1245 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1246 auto endEffectiveFeeLevel = [&]() {
1250 if (lastRIter->feeLevel > feeLevelPaid ||
1251 endAccount.transactions.size() == 1)
1252 return lastRIter->feeLevel;
1256 endAccount.transactions.begin(),
1257 endAccount.transactions.end(),
1259 [&](
auto const& total,
1263 txn.second.feeLevel / endAccount.transactions.size();
1265 txn.second.feeLevel % endAccount.transactions.size();
1266 if (total.first >= max - next || total.second >= max - mod)
1267 return {max, FeeLevel64{0}};
1269 return {total.first + next, total.second + mod};
1271 return endTotal.first +
1272 endTotal.second / endAccount.transactions.size();
1274 if (feeLevelPaid > endEffectiveFeeLevel)
1278 auto dropRIter = endAccount.transactions.rbegin();
1279 assert(dropRIter->second.account == lastRIter->account);
1281 <<
"Removing last item of account " << lastRIter->account
1282 <<
" from queue with average fee of " << endEffectiveFeeLevel
1285 erase(byFee_.iterator_to(dropRIter->second));
1290 <<
"Queue is full, and transaction " << transactionID
1291 <<
" fee is lower than end item's account average fee";
1299 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1302 if (!accountIsInQueue)
1307 byAccount_.emplace(account, TxQAccount(tx));
1319 auto& candidate = accountIter->second.add(
1323 byFee_.insert(candidate);
1324 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1325 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1326 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1327 << candidate.account <<
" to queue."
1328 <<
" Flags: " << flags;
1330 return {terQUEUED,
false};
1350 feeMetrics_.update(app, view, timeLeap, setup_);
1351 auto const& snapshot = feeMetrics_.getSnapshot();
1353 auto ledgerSeq = view.
info().
seq;
1357 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1360 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1362 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1364 byAccount_.at(candidateIter->account).dropPenalty =
true;
1365 candidateIter =
erase(candidateIter);
1375 for (
auto txQAccountIter = byAccount_.begin();
1376 txQAccountIter != byAccount_.end();)
1378 if (txQAccountIter->second.empty())
1379 txQAccountIter = byAccount_.erase(txQAccountIter);
1423 auto ledgerChanged =
false;
1427 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1429 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1431 auto& account = byAccount_.at(candidateIter->account);
1432 auto const beginIter = account.transactions.begin();
1433 if (candidateIter->seqProxy.isSeq() &&
1434 candidateIter->seqProxy > beginIter->first)
1440 <<
"Skipping queued transaction " << candidateIter->txID
1441 <<
" from account " << candidateIter->account
1442 <<
" as it is not the first.";
1446 auto const requiredFeeLevel =
1447 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1448 auto const feeLevelPaid = candidateIter->feeLevel;
1449 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1450 <<
" from account " << candidateIter->account
1451 <<
" has fee level of " << feeLevelPaid
1452 <<
" needs at least " << requiredFeeLevel;
1453 if (feeLevelPaid >= requiredFeeLevel)
1455 JLOG(j_.
trace()) <<
"Applying queued transaction "
1456 << candidateIter->txID <<
" to open ledger.";
1458 auto const [txnResult, didApply] =
1459 candidateIter->apply(app, view, j_);
1465 <<
"Queued transaction " << candidateIter->txID
1466 <<
" applied successfully with " <<
transToken(txnResult)
1467 <<
". Remove from queue.";
1469 candidateIter = eraseAndAdvance(candidateIter);
1470 ledgerChanged =
true;
1474 candidateIter->retriesRemaining <= 0)
1476 if (candidateIter->retriesRemaining <= 0)
1477 account.retryPenalty =
true;
1479 account.dropPenalty =
true;
1480 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1482 <<
". Remove from queue.";
1483 candidateIter = eraseAndAdvance(candidateIter);
1487 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1489 <<
". Leave in queue."
1490 <<
" Applied: " << didApply
1491 <<
". Flags: " << candidateIter->flags;
1492 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1493 candidateIter->retriesRemaining = 1;
1495 --candidateIter->retriesRemaining;
1496 candidateIter->lastResult = txnResult;
1497 if (account.dropPenalty && account.transactions.size() > 1 &&
1503 if (candidateIter->seqProxy.isTicket())
1508 <<
"Queue is nearly full, and transaction "
1509 << candidateIter->txID <<
" failed with "
1511 <<
". Removing ticketed tx from account "
1513 candidateIter = eraseAndAdvance(candidateIter);
1521 auto dropRIter = account.transactions.rbegin();
1523 dropRIter->second.account ==
1524 candidateIter->account);
1527 <<
"Queue is nearly full, and transaction "
1528 << candidateIter->txID <<
" failed with "
1530 <<
". Removing last item from account "
1532 auto endIter = byFee_.iterator_to(dropRIter->second);
1533 if (endIter != candidateIter)
1554 auto const startingSize = byFee_.
size();
1555 assert(parentHash != parentHash_);
1556 parentHash_ = parentHash;
1568 MaybeTx::parentHashComp = parentHash;
1570 for (
auto& [_, account] : byAccount_)
1572 for (
auto& [_, candidate] : account.transactions)
1574 byFee_.insert(candidate);
1577 assert(byFee_.size() == startingSize);
1579 return ledgerChanged;
1589 return nextQueuableSeqImpl(sleAccount, lock);
1599 TxQ::nextQueuableSeqImpl(
1606 return SeqProxy::sequence(0);
1611 auto const accountIter = byAccount_.find((*sleAccount)[
sfAccount]);
1612 if (accountIter == byAccount_.end() ||
1613 accountIter->second.transactions.empty())
1621 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1623 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1624 txIter->first != acctSeqProx)
1634 SeqProxy attempt = txIter->second.consequences().followingSeq();
1635 while (++txIter != acctTxs.
cend())
1637 if (attempt < txIter->first)
1640 attempt = txIter->second.consequences().followingSeq();
1646 TxQ::getRequiredFeeLevel(
1652 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1656 TxQ::tryDirectApply(
1664 auto const sleAccount = view.
read(keylet::account(account));
1671 SeqProxy const txSeqProx = tx->getSeqProxy();
1675 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1678 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1680 return getRequiredFeeLevel(
1681 view, flags, feeMetrics_.getSnapshot(), lock);
1688 if (feeLevelPaid >= requiredFeeLevel)
1693 <<
" to open ledger.";
1695 auto const [txnResult, didApply] =
1699 << (didApply ?
" applied successfully with "
1709 AccountMap::iterator accountIter = byAccount_.find(account);
1710 if (accountIter != byAccount_.end())
1713 if (
auto const existingIter =
1717 removeFromByFee(existingIter, tx);
1721 return {
std::pair(txnResult, didApply)};
1727 TxQ::removeFromByFee(
1731 if (replacedTxIter && tx)
1735 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1736 assert(deleteIter != byFee_.end());
1737 assert(&(*replacedTxIter)->second == &*deleteIter);
1738 assert(deleteIter->seqProxy == tx->getSeqProxy());
1739 assert(deleteIter->account == (*tx)[
sfAccount]);
1743 return std::nullopt;
1753 auto const snapshot = feeMetrics_.getSnapshot();
1755 result.
txCount = byFee_.size();
1761 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1762 result.
medFeeLevel = snapshot.escalationMultiplier;
1769 TxQ::getTxRequiredFeeAndSeq(
1777 auto const snapshot = feeMetrics_.getSnapshot();
1779 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1781 auto const sle = view.
read(keylet::account(account));
1784 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1786 return {
mulDiv(fee, baseFee, baseLevel).second, accountSeq, availableSeq};
1796 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1798 if (accountIter == byAccount_.end() ||
1799 accountIter->second.transactions.empty())
1802 result.
reserve(accountIter->second.transactions.size());
1803 for (
auto const& tx : accountIter->second.transactions)
1817 result.
reserve(byFee_.size());
1819 for (
auto const& tx : byFee_)
1831 BOOST_ASSERT(
false);
1835 auto const metrics = getMetrics(*view);
1841 ret[jss::ledger_current_index] = view->info().seq;
1842 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1843 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1845 if (metrics.txQMaxSize)
1848 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1849 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1850 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1851 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1853 auto const baseFee = view->fees().base;
1857 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1858 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1864 drops[jss::base_fee] = to_string(baseFee);
1865 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1866 drops[jss::minimum_fee] = to_string(
toDrops(
1867 metrics.minProcessingFeeLevel,
1868 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1869 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1870 if (effectiveBaseFee &&
1871 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1873 drops[jss::open_ledger_fee] = to_string(openFee);
1884 auto const& section = config.
section(
"transaction_queue");
1889 "minimum_escalation_multiplier",
1893 "minimum_txn_in_ledger_standalone",
1897 if (
set(max,
"maximum_txn_in_ledger", section))
1901 Throw<std::runtime_error>(
1902 "The minimum number of low-fee transactions allowed "
1903 "per ledger (minimum_txn_in_ledger) exceeds "
1904 "the maximum number of low-fee transactions allowed per "
1905 "ledger (maximum_txn_in_ledger).");
1909 Throw<std::runtime_error>(
1910 "The minimum number of low-fee transactions allowed "
1911 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1912 "the maximum number of low-fee transactions allowed per "
1913 "ledger (maximum_txn_in_ledger).");
1926 "normal_consensus_increase_percent",
1936 "slow_consensus_decrease_percent",