20 #include <ripple/app/misc/TxQ.h>
21 #include <ripple/app/ledger/OpenLedger.h>
22 #include <ripple/app/main/Application.h>
23 #include <ripple/app/misc/LoadFeeTrack.h>
24 #include <ripple/app/tx/apply.h>
25 #include <ripple/protocol/Feature.h>
26 #include <ripple/protocol/jss.h>
27 #include <ripple/protocol/st.h>
28 #include <ripple/basics/mulDiv.h>
29 #include <boost/algorithm/clamp.hpp>
46 if (refTxnCostDrops == 0)
56 refTxnCostDrops).second;
60 boost::optional<LedgerIndex>
73 return mulDiv(level, 100 + increasePercent, 100).second;
87 auto const txBegin = view.
txs.
begin();
88 auto const txEnd = view.
txs.
end();
101 assert(size == feeLevels.
size());
104 " has " << size <<
" transactions. " <<
105 "Ledgers are processing " <<
106 (timeLeap ?
"slowly" :
"as expected") <<
107 ". Expected transactions is currently " <<
118 auto const upperLimit = std::max<std::uint64_t>(
133 auto const next = [&]
163 feeLevels[(size - 1) / 2] +
FeeLevel64{ 1 }) / 2;
167 JLOG(
j_.
debug()) <<
"Expected transactions updated to " <<
191 target * target).second;
231 auto const last =
current + seriesSize - 1;
250 return { sumNlast.first,
FeeLevel64{ sumNlast.second } };
251 auto const totalFeeLevel =
mulDiv(multiplier,
252 sumNlast.second - sumNcurrent.second, target * target);
254 return totalFeeLevel;
264 , feeLevel(feeLevel_)
267 , sequence(txn_->getSequence())
268 , retriesRemaining(retriesAllowed)
270 , pfresult(pfresult_)
283 if (pfresult->rules != view.
rules() ||
284 pfresult->flags != flags)
286 JLOG(j.
debug()) <<
"Queued transaction " <<
287 txID <<
" rules or flags have changed. Flags from " <<
288 pfresult->flags <<
" to " << flags ;
298 *pfresult, app, view);
300 return doApply(pcresult, app, view);
319 auto result = transactions.emplace(sequence, std::move(txn));
320 assert(result.second);
321 assert(&result.first->second != &txn);
323 return result.first->second;
329 return transactions.erase(sequence) != 0;
348 template<
size_t fillPercentage>
352 static_assert(fillPercentage > 0 &&
353 fillPercentage <= 100,
354 "Invalid fill percentage");
361 AccountMap::iterator accountIter,
362 boost::optional<FeeMultiSet::iterator> replacementIter)
395 canBeHeld = replacementIter.is_initialized();
401 canBeHeld = accountIter->second.getTxnCount() <
411 canBeHeld = tSeq < accountIter->second.transactions.rbegin()->first;
418 TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
419 -> FeeMultiSet::iterator_type
421 auto& txQAccount = byAccount_.at(candidateIter->account);
422 auto const sequence = candidateIter->sequence;
423 auto const newCandidateIter = byFee_.erase(candidateIter);
427 auto const found = txQAccount.remove(sequence);
431 return newCandidateIter;
436 -> FeeMultiSet::iterator_type
438 auto& txQAccount = byAccount_.at(candidateIter->account);
439 auto const accountIter = txQAccount.transactions.find(
440 candidateIter->sequence);
441 assert(accountIter != txQAccount.transactions.end());
442 assert(accountIter == txQAccount.transactions.begin());
443 assert(byFee_.iterator_to(accountIter->second) == candidateIter);
444 auto const accountNextIter =
std::next(accountIter);
458 auto const feeNextIter =
std::next(candidateIter);
459 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
460 accountNextIter->first == candidateIter->sequence + 1 &&
461 (feeNextIter == byFee_.end() ||
462 accountNextIter->second.feeLevel > feeNextIter->feeLevel);
463 auto const candidateNextIter = byFee_.erase(candidateIter);
464 txQAccount.transactions.erase(accountIter);
465 return useAccountNext ?
466 byFee_.iterator_to(accountNextIter->second) :
473 TxQ::TxQAccount::TxMap::const_iterator begin,
474 TxQ::TxQAccount::TxMap::const_iterator end)
475 -> TxQAccount::TxMap::iterator
477 for (
auto it = begin; it != end; ++it)
479 byFee_.erase(byFee_.iterator_to(it->second));
481 return txQAccount.transactions.erase(begin, end);
486 STTx const& tx, TxQ::AccountMap::iterator
const& accountIter,
487 TxQAccount::TxMap::iterator beginTxIter,
FeeLevel64 feeLevelPaid,
493 assert(beginTxIter != accountIter->second.transactions.end());
494 auto const aSeq = beginTxIter->first;
497 metricsSnapshot, view, txExtraCount, tSeq - aSeq + 1);
502 if (!requiredTotalFeeLevel.first)
507 auto endTxIter = accountIter->second.transactions.lower_bound(tSeq);
511 [](
auto const& total,
auto const& txn)
513 return total + txn.second.feeLevel;
517 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
522 for (
auto it = beginTxIter; it != endTxIter; ++it)
524 auto txResult = it->second.apply(app, view, j);
529 --it->second.retriesRemaining;
530 it->second.lastResult = txResult.first;
531 if (!txResult.second)
539 auto const txResult =
doApply (
preclaim (pfresult, app, view), app, view);
544 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
546 if (endTxIter != accountIter->second.transactions.end() &&
547 endTxIter->first == tSeq)
623 auto const tSeq = tx->getSequence();
630 return{ pfresult.ter,
false };
634 explicit MultiTxn() =
default;
636 boost::optional<ApplyViewImpl> applyView;
637 boost::optional<OpenView> openView;
639 TxQAccount::TxMap::iterator nextTxIter;
643 bool includeCurrentFee =
false;
646 boost::optional<MultiTxn> multiTxn;
647 boost::optional<TxConsequences const> consequences;
648 boost::optional<FeeMultiSet::iterator> replacedItemDeleteIter;
662 auto const requiredFeeLevel = [&]()
679 auto& txQAcct = accountIter->second;
681 if (existingIter != txQAcct.transactions.end())
686 existingIter->second.feeLevel,
688 JLOG(
j_.
trace()) <<
"Found transaction in queue for account " <<
689 account <<
" with sequence number " << tSeq <<
690 " new txn fee level is " << feeLevelPaid <<
691 ", old txn fee level is " <<
692 existingIter->second.feeLevel <<
693 ", new txn needs fee level of " <<
695 if (feeLevelPaid > requiredRetryLevel
696 || (existingIter->second.feeLevel < requiredFeeLevel &&
697 feeLevelPaid >= requiredFeeLevel &&
698 existingIter == txQAcct.transactions.begin()))
708 if (
std::next(existingIter) != txQAcct.transactions.end())
714 if (!existingIter->second.consequences)
715 existingIter->second.consequences.emplace(
717 *existingIter->second.pfresult));
719 if (existingIter->second.consequences->category ==
722 assert(!consequences);
725 if (consequences->category ==
731 "Ignoring blocker transaction " <<
733 " in favor of normal queued " <<
734 existingIter->second.txID;
743 "Removing transaction from queue " <<
744 existingIter->second.txID <<
750 auto deleteIter =
byFee_.iterator_to(existingIter->second);
751 assert(deleteIter !=
byFee_.end());
752 assert(&existingIter->second == &*deleteIter);
753 assert(deleteIter->sequence == tSeq);
754 assert(deleteIter->account == txQAcct.account);
755 replacedItemDeleteIter = deleteIter;
761 "Ignoring transaction " <<
763 " in favor of queued " <<
764 existingIter->second.txID;
777 auto& txQAcct = accountIter->second;
786 if (
canBeHeld(*tx, flags, view, accountIter, replacedItemDeleteIter))
797 multiTxn->nextTxIter = txQAcct.transactions.find(aSeq);
798 auto workingIter = multiTxn->nextTxIter;
799 auto workingSeq = aSeq;
800 for (; workingIter != txQAcct.transactions.end();
801 ++workingIter, ++workingSeq)
803 if (workingSeq < tSeq &&
804 workingIter->first != workingSeq)
810 if (workingIter->first == tSeq - 1)
815 workingIter->second.feeLevel,
818 if (feeLevelPaid <= requiredMultiLevel)
822 "Ignoring transaction " <<
824 ". Needs fee level of " <<
826 requiredMultiLevel <<
832 if (workingIter->first == tSeq)
836 assert(replacedItemDeleteIter);
837 multiTxn->includeCurrentFee =
839 txQAcct.transactions.end();
842 if (!workingIter->second.consequences)
843 workingIter->second.consequences.emplace(
845 *workingIter->second.pfresult));
848 if (workingIter->first < tSeq &&
849 workingIter->second.consequences->category ==
855 "Ignoring transaction " <<
857 ". A blocker-type transaction " <<
862 workingIter->second.consequences->fee;
863 multiTxn->potentialSpend +=
864 workingIter->second.consequences->potentialSpend;
866 if (workingSeq < tSeq)
907 auto const balance = (*sle)[
sfBalance].xrp();
920 auto totalFee = multiTxn->fee;
921 if (multiTxn->includeCurrentFee)
922 totalFee += (*tx)[
sfFee].xrp();
923 if (totalFee >= balance ||
928 "Ignoring transaction " <<
930 ". Total fees in flight too high.";
935 multiTxn->applyView.emplace(&view, flags);
936 multiTxn->openView.emplace(&*multiTxn->applyView);
938 auto const sleBump = multiTxn->applyView->peek(
943 auto const potentialTotalSpend = multiTxn->fee +
945 multiTxn->potentialSpend);
946 assert(potentialTotalSpend >
XRPAmount{0});
948 balance - potentialTotalSpend);
955 assert(!multiTxn || multiTxn->openView);
956 auto const pcresult =
preclaim(pfresult, app,
957 multiTxn ? *multiTxn->openView : view);
958 if (!pcresult.likelyToClaimFee)
959 return{ pcresult.ter,
false };
964 JLOG(
j_.
trace()) <<
"Transaction " <<
966 " from account " << account <<
967 " has fee level of " << feeLevelPaid <<
968 " needs at least " << requiredFeeLevel <<
969 " to get in the open ledger, which has " <<
970 view.txCount() <<
" entries.";
990 multiTxn.is_initialized() &&
991 multiTxn->nextTxIter->second.retriesRemaining ==
993 feeLevelPaid > requiredFeeLevel &&
1000 multiTxn->nextTxIter, feeLevelPaid, pfresult, view.txCount(),
1001 flags, metricsSnapshot, j);
1004 sandbox.
apply(view);
1013 if (!multiTxn && feeLevelPaid >= requiredFeeLevel)
1017 JLOG(
j_.
trace()) <<
"Applying transaction " <<
1021 auto const [txnResult, didApply] =
doApply(pcresult, app, view);
1023 JLOG(
j_.
trace()) <<
"New transaction " <<
1025 (didApply ?
" applied successfully with " :
1029 if (didApply && replacedItemDeleteIter)
1030 erase(*replacedItemDeleteIter);
1031 return { txnResult, didApply };
1036 !
canBeHeld(*tx, flags, view, accountIter, replacedItemDeleteIter))
1039 JLOG(
j_.
trace()) <<
"Transaction " <<
1048 if (!replacedItemDeleteIter &&
isFull())
1050 auto lastRIter =
byFee_.rbegin();
1051 if (lastRIter->account == account)
1053 JLOG(
j_.
warn()) <<
"Queue is full, and transaction " <<
1055 " would kick a transaction from the same account (" <<
1056 account <<
") out of the queue.";
1059 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1060 auto endEffectiveFeeLevel = [&]()
1065 if (lastRIter->feeLevel > feeLevelPaid
1066 || endAccount.transactions.size() == 1)
1067 return lastRIter->feeLevel;
1072 endAccount.transactions.end(),
1074 [&](
auto const& total,
auto const& txn)
1077 auto next = txn.second.feeLevel /
1078 endAccount.transactions.size();
1079 auto mod = txn.second.feeLevel %
1080 endAccount.transactions.size();
1081 if (total.first >= max - next ||
1082 total.second >= max - mod)
1083 return std::make_pair(max, FeeLevel64 { 0 });
1086 return endTotal.first + endTotal.second /
1087 endAccount.transactions.size();
1089 if (feeLevelPaid > endEffectiveFeeLevel)
1093 auto dropRIter = endAccount.transactions.rbegin();
1094 assert(dropRIter->second.account == lastRIter->account);
1096 "Removing last item of account " <<
1097 lastRIter->account <<
1098 " from queue with average fee of " <<
1099 endEffectiveFeeLevel <<
" in favor of " <<
1102 erase(byFee_.iterator_to(dropRIter->second));
1106 JLOG(j_.
warn()) <<
"Queue is full, and transaction " <<
1108 " fee is lower than end item's account average fee";
1114 if (replacedItemDeleteIter)
1115 erase(*replacedItemDeleteIter);
1120 std::tie(accountIter, created) = byAccount_.emplace(
1121 account, TxQAccount(tx));
1136 auto& candidate = accountIter->second.add(
1143 candidate.consequences.emplace(*consequences);
1145 byFee_.insert(candidate);
1146 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID <<
1147 " with result " <<
transToken(pfresult.ter) <<
1148 " from " << (accountExists ?
"existing" :
"new") <<
1149 " account " << candidate.account <<
" to queue." <<
1150 " Flags: " << flags;
1152 return { terQUEUED,
false };
1169 ReadView const& view,
bool timeLeap)
1173 feeMetrics_.update(app, view, timeLeap, setup_);
1174 auto const& snapshot = feeMetrics_.getSnapshot();
1176 auto ledgerSeq = view.
info().
seq;
1179 maxSize_ =
std::max (snapshot.txnsExpected * setup_.ledgersInQueue,
1180 setup_.queueSizeMin);
1183 for(
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end(); )
1185 if (candidateIter->lastValid
1186 && *candidateIter->lastValid <= ledgerSeq)
1188 byAccount_.at(candidateIter->account).dropPenalty =
true;
1189 candidateIter =
erase(candidateIter);
1199 for (
auto txQAccountIter = byAccount_.begin();
1200 txQAccountIter != byAccount_.end();)
1202 if (txQAccountIter->second.empty())
1203 txQAccountIter = byAccount_.erase(txQAccountIter);
1248 auto ledgerChanged =
false;
1252 auto const metricSnapshot = feeMetrics_.getSnapshot();
1254 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1256 auto& account = byAccount_.at(candidateIter->account);
1257 if (candidateIter->sequence >
1258 account.transactions.begin()->first)
1262 JLOG(j_.
trace()) <<
"Skipping queued transaction " <<
1263 candidateIter->txID <<
" from account " <<
1264 candidateIter->account <<
" as it is not the first.";
1268 auto const requiredFeeLevel = FeeMetrics::scaleFeeLevel(
1269 metricSnapshot, view);
1270 auto const feeLevelPaid = candidateIter->feeLevel;
1271 JLOG(j_.
trace()) <<
"Queued transaction " <<
1272 candidateIter->txID <<
" from account " <<
1273 candidateIter->account <<
" has fee level of " <<
1274 feeLevelPaid <<
" needs at least " <<
1276 if (feeLevelPaid >= requiredFeeLevel)
1278 auto firstTxn = candidateIter->txn;
1280 JLOG(j_.
trace()) <<
"Applying queued transaction " <<
1281 candidateIter->txID <<
" to open ledger.";
1283 auto const [txnResult, didApply] = candidateIter->apply(app, view, j_);
1288 JLOG(j_.
debug()) <<
"Queued transaction " <<
1289 candidateIter->txID <<
1290 " applied successfully with " <<
1291 transToken(txnResult) <<
". Remove from queue.";
1293 candidateIter = eraseAndAdvance(candidateIter);
1294 ledgerChanged =
true;
1297 candidateIter->retriesRemaining <= 0)
1299 if (candidateIter->retriesRemaining <= 0)
1300 account.retryPenalty =
true;
1302 account.dropPenalty =
true;
1303 JLOG(j_.
debug()) <<
"Queued transaction " <<
1304 candidateIter->txID <<
" failed with " <<
1305 transToken(txnResult) <<
". Remove from queue.";
1306 candidateIter = eraseAndAdvance(candidateIter);
1310 JLOG(j_.
debug()) <<
"Queued transaction " <<
1311 candidateIter->txID <<
" failed with " <<
1312 transToken(txnResult) <<
". Leave in queue." <<
1313 " Applied: " << didApply <<
1315 candidateIter->flags;
1316 if (account.retryPenalty &&
1317 candidateIter->retriesRemaining > 2)
1318 candidateIter->retriesRemaining = 1;
1320 --candidateIter->retriesRemaining;
1321 candidateIter->lastResult = txnResult;
1322 if (account.dropPenalty &&
1323 account.transactions.size() > 1 && isFull<95>())
1331 auto dropRIter = account.transactions.rbegin();
1332 assert(dropRIter->second.account == candidateIter->account);
1334 "Queue is nearly full, and transaction " <<
1335 candidateIter->txID <<
" failed with " <<
1337 ". Removing last item of account " <<
1339 auto endIter = byFee_.iterator_to(dropRIter->second);
1340 assert(endIter != candidateIter);
1354 return ledgerChanged;
1364 auto const snapshot = feeMetrics_.getSnapshot();
1366 result.
txCount = byFee_.size();
1372 byFee_.rbegin()->feeLevel +
FeeLevel64{ 1 } :
1374 result.
medFeeLevel = snapshot.escalationMultiplier;
1388 auto const snapshot = feeMetrics_.getSnapshot();
1391 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1393 auto const accountSeq = [&view, &account]() ->
std::uint32_t {
1394 auto const sle = view.
read(keylet::account(account));
1400 auto availableSeq = accountSeq;
1402 if (
auto iter {byAccount_.find(account)}; iter != byAccount_.end())
1404 auto& txQAcct = iter->second;
1405 for (
auto const& [seq, _] : txQAcct.transactions)
1408 if (seq >= availableSeq)
1409 availableSeq = seq + 1;
1413 return {
mulDiv(fee, baseFee, baseLevel).second, accountSeq, availableSeq};
1422 auto accountIter = byAccount_.find(account);
1423 if (accountIter == byAccount_.end() ||
1424 accountIter->second.transactions.empty())
1429 for (
auto const& tx : accountIter->second.transactions)
1433 AccountTxDetails resultTx;
1434 resultTx.feeLevel = tx.second.feeLevel;
1435 if (tx.second.lastValid)
1436 resultTx.lastValid.emplace(*tx.second.lastValid);
1437 if (tx.second.consequences)
1438 resultTx.consequences.emplace(*tx.second.consequences);
1455 result.
reserve(byFee_.size());
1457 for (
auto const& tx : byFee_)
1464 resultTx.
lastValid.emplace(*tx.lastValid);
1465 if (tx.consequences)
1467 resultTx.
account = tx.account;
1468 resultTx.
txn = tx.txn;
1470 BOOST_ASSERT(tx.pfresult);
1487 BOOST_ASSERT(
false);
1491 auto const metrics = getMetrics(*view);
1497 ret[jss::ledger_current_index] = view->info().seq;
1498 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1499 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1501 if (metrics.txQMaxSize)
1504 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1505 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1506 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1507 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1509 auto const baseFee = view->fees().base;
1513 drops[jss::base_fee] = to_string(
toDrops(
1514 metrics.referenceFeeLevel, baseFee).second);
1515 drops[jss::minimum_fee] = to_string(
toDrops(
1516 metrics.minProcessingFeeLevel, baseFee).second);
1517 drops[jss::median_fee] = to_string(
toDrops(
1518 metrics.medFeeLevel, baseFee).second);
1519 drops[jss::open_ledger_fee] = to_string(
toDrops(
1520 metrics.openLedgerFeeLevel -
FeeLevel64{ 1 }, baseFee).second +
1532 auto const& section = config.
section(
"transaction_queue");
1538 "minimum_escalation_multiplier", section);
1541 "minimum_txn_in_ledger_standalone", section);
1544 if (
set(max,
"maximum_txn_in_ledger", section))
1548 Throw<std::runtime_error>(
1549 "The minimum number of low-fee transactions allowed "
1550 "per ledger (minimum_txn_in_ledger) exceeds "
1551 "the maximum number of low-fee transactions allowed per "
1552 "ledger (maximum_txn_in_ledger)."
1557 Throw<std::runtime_error>(
1558 "The minimum number of low-fee transactions allowed "
1559 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1560 "the maximum number of low-fee transactions allowed per "
1561 "ledger (maximum_txn_in_ledger)."
1575 "normal_consensus_increase_percent", section);
1590 "minimum_last_ledger_buffer", section);
1592 "zero_basefee_transaction_feelevel", section);
1602 return std::make_unique<TxQ>(setup, std::move(j));