1#include <xrpld/app/ledger/OpenLedger.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/TxQ.h>
4#include <xrpld/app/tx/apply.h>
6#include <xrpl/basics/mulDiv.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/jss.h>
9#include <xrpl/protocol/st.h>
22 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
28 XRPAmount const mod = [&view, &tx, baseFee]() {
32 return def.signum() == 0 ?
XRPAmount{1} : def;
34 return std::pair{baseFee + mod, feePaid + mod};
37 XRPL_ASSERT(baseFee.signum() > 0,
"xrpl::getFeeLevelPaid : positive fee");
38 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
58 return mulDiv(level, 100 + increasePercent, 100)
72 auto const txBegin = view.
txs.
begin();
73 auto const txEnd = view.
txs.
end();
81 size == feeLevels.
size(),
82 "xrpl::TxQ::FeeMetrics::update : fee levels size");
85 <<
"Ledger " << view.
header().
seq <<
" has " << size
87 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
115 auto const next = [&] {
143 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
194 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
198static_assert(sumOfFirstSquares(1).first ==
true);
199static_assert(sumOfFirstSquares(1).second == 1);
201static_assert(sumOfFirstSquares(2).first ==
true);
202static_assert(sumOfFirstSquares(2).second == 5);
204static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
205static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
207static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
209 sumOfFirstSquares(0x200000).second ==
230 auto const last =
current + seriesSize - 1;
237 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
252 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
253 auto const totalFeeLevel =
mulDiv(
254 multiplier, sumNlast.second - sumNcurrent.second, target * target);
256 return {totalFeeLevel.has_value(), *totalFeeLevel};
268 , feeLevel(feeLevel_)
270 , account(txn_->getAccountID(sfAccount))
272 , seqProxy(txn_->getSeqProxy())
273 , retriesRemaining(retriesAllowed)
275 , pfResult(pfResult_)
284 pfResult,
"xrpl::TxQ::MaybeTx::apply : preflight result is set");
287 if (pfResult->rules != view.
rules() || pfResult->flags != flags)
289 JLOG(j.
debug()) <<
"Queued transaction " << txID
290 <<
" rules or flags have changed. Flags from "
291 << pfResult->flags <<
" to " << flags;
297 auto pcresult =
preclaim(*pfResult, app, view);
299 return doApply(pcresult, app, view);
311TxQ::TxQAccount::TxMap::const_iterator
316 auto sameOrPrevIter = transactions.lower_bound(seqProx);
317 if (sameOrPrevIter != transactions.begin())
319 return sameOrPrevIter;
327 auto result = transactions.emplace(seqProx, std::move(txn));
329 result.second,
"xrpl::TxQ::TxQAccount::add : emplace succeeded");
331 &result.first->second != &txn,
332 "xrpl::TxQ::TxQAccount::add : transaction moved");
334 return result.first->second;
340 return transactions.erase(seqProx) != 0;
355template <
size_t fillPercentage>
360 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
370 AccountMap::iterator
const& accountIter,
401 TxQAccount const& txQAcct = accountIter->second;
408 if (txSeqProx.isTicket())
415 if (txSeqProx != nextQueuable)
431TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
432 -> FeeMultiSet::iterator_type
434 auto& txQAccount = byAccount_.at(candidateIter->account);
435 auto const seqProx = candidateIter->seqProxy;
436 auto const newCandidateIter = byFee_.erase(candidateIter);
440 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
441 XRPL_ASSERT(found,
"xrpl::TxQ::erase : account removed");
443 return newCandidateIter;
448 -> FeeMultiSet::iterator_type
450 auto& txQAccount = byAccount_.at(candidateIter->account);
451 auto const accountIter =
452 txQAccount.transactions.find(candidateIter->seqProxy);
454 accountIter != txQAccount.transactions.end(),
455 "xrpl::TxQ::eraseAndAdvance : account found");
461 candidateIter->seqProxy.isTicket() ||
462 accountIter == txQAccount.transactions.begin(),
463 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
465 byFee_.iterator_to(accountIter->second) == candidateIter,
466 "xrpl::TxQ::eraseAndAdvance : found in byFee");
467 auto const accountNextIter =
std::next(accountIter);
471 auto const feeNextIter =
std::next(candidateIter);
472 bool const useAccountNext =
473 accountNextIter != txQAccount.transactions.end() &&
474 accountNextIter->first > candidateIter->seqProxy &&
475 (feeNextIter == byFee_.end() ||
476 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
478 auto const candidateNextIter = byFee_.erase(candidateIter);
479 txQAccount.transactions.erase(accountIter);
481 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
488 TxQ::TxQAccount::TxMap::const_iterator begin,
489 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
491 for (
auto it = begin; it != end; ++it)
493 byFee_.erase(byFee_.iterator_to(it->second));
495 return txQAccount.transactions.erase(begin, end);
503 TxQ::AccountMap::iterator
const& accountIter,
504 TxQAccount::TxMap::iterator beginTxIter,
514 beginTxIter != accountIter->second.transactions.end(),
515 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
519 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
523 metricsSnapshot, view, txExtraCount, dist + 1);
527 if (!requiredTotalFeeLevel.first)
534 [](
auto const& total,
auto const& txn) {
535 return total + txn.second.feeLevel;
539 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
544 for (
auto it = beginTxIter; it != endTxIter; ++it)
546 auto txResult = it->second.apply(app, view, j);
551 --it->second.retriesRemaining;
552 it->second.lastResult = txResult.ter;
573 if (!txResult.applied)
576 return {txResult.ter,
false};
581 auto const txResult =
doApply(
preclaim(pfResult, app, view), app, view);
583 if (txResult.applied)
587 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
589 if (endTxIter != accountIter->second.transactions.end() &&
590 endTxIter->first == tSeqProx)
723 auto const pfResult =
preflight(app, view.
rules(), *tx, flags, j);
725 return {pfResult.ter,
false};
730 return *directApplied;
740 auto const account = (*tx)[sfAccount];
742 auto const sleAccount = view.
read(accountKey);
748 SeqProxy const txSeqProx = tx->getSeqProxy();
766 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
779 TxQAccount::TxMap::iterator first_,
780 TxQAccount::TxMap::iterator end_)
781 : first(first_), end(end_)
785 TxQAccount::TxMap::iterator first;
786 TxQAccount::TxMap::iterator end;
793 if (!accountIsInQueue)
798 TxQAccount::TxMap::iterator
const firstIter =
801 if (firstIter == acctTxs.
end())
806 return {TxIter{firstIter, acctTxs.
end()}};
809 auto const acctTxCount{
817 if (pfResult.consequences.isBlocker())
825 <<
". Account has other queued transactions.";
828 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
833 <<
". Blocker does not replace lone queued transaction.";
840 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
842 if (accountIsInQueue)
856 auto const requiredFeeLevel =
868 if (acctTxCount == 1 &&
869 txIter->first->second.consequences().isBlocker() &&
870 (txIter->first->first != txSeqProx))
884 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
888 <<
"Found transaction in queue for account " << account
889 <<
" with " << txSeqProx <<
" new txn fee level is "
890 << feeLevelPaid <<
", old txn fee level is "
891 << existingIter->second.feeLevel
892 <<
", new txn needs fee level of " << requiredRetryLevel;
893 if (feeLevelPaid > requiredRetryLevel)
898 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
899 << existingIter->second.txID <<
" in favor of "
907 <<
" in favor of queued " << existingIter->second.txID;
919 : applyView(&view, flags), openView(&applyView)
926 if (acctTxCount == 0)
931 if (txSeqProx.
isSeq())
933 if (acctSeqProx > txSeqProx)
935 if (acctSeqProx < txSeqProx)
944 TxQAccount const& txQAcct = accountIter->second;
946 if (acctSeqProx > txSeqProx)
955 bool requiresMultiTxn =
false;
956 if (acctTxCount > 1 || !replacedTxIter)
971 requiresMultiTxn =
true;
974 if (requiresMultiTxn)
988 TxQAccount::TxMap::const_iterator
const prevIter =
997 XRPL_ASSERT(prevIter != txIter->end,
"xrpl::TxQ::apply : not end");
998 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1002 if (txSeqProx.
isSeq())
1004 if (txSeqProx < acctSeqProx)
1006 else if (txSeqProx > acctSeqProx)
1010 else if (!replacedTxIter)
1017 if (txSeqProx.
isSeq() &&
1027 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1032 if (iter->first != txSeqProx)
1034 totalFee += iter->second.consequences().fee();
1036 iter->second.consequences().potentialSpend();
1038 else if (
std::next(iter) != txIter->end)
1043 totalFee += pfResult.consequences.fee();
1044 potentialSpend += pfResult.consequences.potentialSpend();
1077 auto const balance = (*sleAccount)[sfBalance].xrp();
1096 auto const base = view.
fees().
base;
1097 if (totalFee >= balance ||
1098 (reserve > 10 * base && totalFee >= reserve))
1102 <<
". Total fees in flight too high.";
1107 multiTxn.
emplace(view, flags);
1109 auto const sleBump = multiTxn->applyView.peek(accountKey);
1116 auto const potentialTotalSpend = totalFee +
1121 multiTxn->applyView.fees().base == 0),
1122 "xrpl::TxQ::apply : total spend check");
1123 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1129 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1146 auto const pcresult =
1147 preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1148 if (!pcresult.likelyToClaimFee)
1149 return {pcresult.
ter,
false};
1152 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"xrpl::TxQ::apply : minimum fee");
1155 << account <<
" has fee level of " << feeLevelPaid
1156 <<
" needs at least " << requiredFeeLevel
1157 <<
" to get in the open ledger, which has "
1158 << view.
txCount() <<
" entries.";
1179 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1197 sandbox.
apply(view);
1209 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1215 return {ter,
false};
1222 if (!replacedTxIter &&
isFull())
1224 auto lastRIter =
byFee_.rbegin();
1225 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1229 if (lastRIter ==
byFee_.rend())
1239 <<
" would kick a transaction from the same account ("
1240 << account <<
") out of the queue.";
1243 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1244 auto endEffectiveFeeLevel = [&]() {
1248 if (lastRIter->feeLevel > feeLevelPaid ||
1249 endAccount.transactions.size() == 1)
1250 return lastRIter->feeLevel;
1254 endAccount.transactions.begin(),
1255 endAccount.transactions.end(),
1257 [&](
auto const& total,
1261 txn.second.feeLevel / endAccount.transactions.size();
1263 txn.second.feeLevel % endAccount.transactions.size();
1264 if (total.first >= max - next || total.second >= max - mod)
1265 return {max, FeeLevel64{0}};
1267 return {total.first + next, total.second + mod};
1269 return endTotal.first +
1270 endTotal.second / endAccount.transactions.size();
1272 if (feeLevelPaid > endEffectiveFeeLevel)
1276 auto dropRIter = endAccount.transactions.rbegin();
1278 dropRIter->second.account == lastRIter->account,
1279 "xrpl::TxQ::apply : cheapest transaction found");
1281 <<
"Removing last item of account " << lastRIter->account
1282 <<
" from queue with average fee of " << endEffectiveFeeLevel
1291 <<
" fee is lower than end item's account average fee";
1299 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1302 if (!accountIsInQueue)
1305 [[maybe_unused]]
bool created =
false;
1307 byAccount_.emplace(account, TxQAccount(tx));
1308 XRPL_ASSERT(created,
"xrpl::TxQ::apply : account created");
1318 auto& candidate = accountIter->second.add(
1322 byFee_.insert(candidate);
1323 JLOG(j_.debug()) <<
"Added transaction " << candidate.txID
1324 <<
" with result " <<
transToken(pfResult.ter) <<
" from "
1325 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1326 << candidate.account <<
" to queue."
1327 <<
" Flags: " << flags;
1349 feeMetrics_.update(app, view, timeLeap, setup_);
1350 auto const& snapshot = feeMetrics_.getSnapshot();
1356 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1359 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1361 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1363 byAccount_.at(candidateIter->account).dropPenalty =
true;
1364 candidateIter =
erase(candidateIter);
1374 for (
auto txQAccountIter = byAccount_.begin();
1375 txQAccountIter != byAccount_.end();)
1377 if (txQAccountIter->second.empty())
1378 txQAccountIter = byAccount_.erase(txQAccountIter);
1422 auto ledgerChanged =
false;
1426 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1428 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1430 auto& account = byAccount_.at(candidateIter->account);
1431 auto const beginIter = account.transactions.begin();
1432 if (candidateIter->seqProxy.isSeq() &&
1433 candidateIter->seqProxy > beginIter->first)
1439 <<
"Skipping queued transaction " << candidateIter->txID
1440 <<
" from account " << candidateIter->account
1441 <<
" as it is not the first.";
1445 auto const requiredFeeLevel =
1446 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1447 auto const feeLevelPaid = candidateIter->feeLevel;
1448 JLOG(j_.trace()) <<
"Queued transaction " << candidateIter->txID
1449 <<
" from account " << candidateIter->account
1450 <<
" has fee level of " << feeLevelPaid
1451 <<
" needs at least " << requiredFeeLevel;
1452 if (feeLevelPaid >= requiredFeeLevel)
1454 JLOG(j_.trace()) <<
"Applying queued transaction "
1455 << candidateIter->txID <<
" to open ledger.";
1457 auto const [txnResult, didApply, _metadata] =
1458 candidateIter->apply(app, view, j_);
1464 <<
"Queued transaction " << candidateIter->txID
1465 <<
" applied successfully with " <<
transToken(txnResult)
1466 <<
". Remove from queue.";
1468 candidateIter = eraseAndAdvance(candidateIter);
1469 ledgerChanged =
true;
1473 candidateIter->retriesRemaining <= 0)
1475 if (candidateIter->retriesRemaining <= 0)
1476 account.retryPenalty =
true;
1478 account.dropPenalty =
true;
1479 JLOG(j_.debug()) <<
"Queued transaction " << candidateIter->txID
1481 <<
". Remove from queue.";
1482 candidateIter = eraseAndAdvance(candidateIter);
1486 JLOG(j_.debug()) <<
"Queued transaction " << candidateIter->txID
1488 <<
". Leave in queue."
1489 <<
" Applied: " << didApply
1490 <<
". Flags: " << candidateIter->flags;
1491 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1492 candidateIter->retriesRemaining = 1;
1494 --candidateIter->retriesRemaining;
1495 candidateIter->lastResult = txnResult;
1496 if (account.dropPenalty && account.transactions.size() > 1 &&
1502 if (candidateIter->seqProxy.isTicket())
1507 <<
"Queue is nearly full, and transaction "
1508 << candidateIter->txID <<
" failed with "
1510 <<
". Removing ticketed tx from account "
1512 candidateIter = eraseAndAdvance(candidateIter);
1520 auto dropRIter = account.transactions.rbegin();
1522 dropRIter->second.account == candidateIter->account,
1523 "xrpl::TxQ::accept : account check");
1526 <<
"Queue is nearly full, and transaction "
1527 << candidateIter->txID <<
" failed with "
1529 <<
". Removing last item from account "
1531 auto endIter = byFee_.iterator_to(dropRIter->second);
1532 if (endIter != candidateIter)
1552 if (parentHash == parentHash_)
1553 JLOG(j_.warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1555 parentHash_ = parentHash;
1557 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1568 MaybeTx::parentHashComp = parentHash;
1570 for (
auto& [_, account] : byAccount_)
1572 for (
auto& [_, candidate] : account.transactions)
1574 byFee_.insert(candidate);
1578 byFee_.size() == startingSize,
"xrpl::TxQ::accept : byFee size match");
1580 return ledgerChanged;
1590 return nextQueuableSeqImpl(sleAccount, lock);
1600TxQ::nextQueuableSeqImpl(
1606 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1607 return SeqProxy::sequence(0);
1609 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
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();
1647TxQ::getRequiredFeeLevel(
1653 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1664 auto const account = (*tx)[sfAccount];
1665 auto const sleAccount = view.
read(keylet::account(account));
1671 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
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)
1693 JLOG(j_.trace()) <<
"Applying transaction " <<
transactionID
1694 <<
" to open ledger.";
1696 auto const [txnResult, didApply, metadata] =
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 ApplyResult{txnResult, didApply, metadata};
1728TxQ::removeFromByFee(
1732 if (replacedTxIter && tx)
1736 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1738 deleteIter != byFee_.end(),
1739 "xrpl::TxQ::removeFromByFee : found in byFee");
1741 &(*replacedTxIter)->second == &*deleteIter,
1742 "xrpl::TxQ::removeFromByFee : matching transaction");
1744 deleteIter->seqProxy == tx->getSeqProxy(),
1745 "xrpl::TxQ::removeFromByFee : matching sequence");
1747 deleteIter->account == (*tx)[sfAccount],
1748 "xrpl::TxQ::removeFromByFee : matching account");
1762 auto const snapshot = feeMetrics_.getSnapshot();
1764 result.
txCount = byFee_.size();
1770 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1771 result.
medFeeLevel = snapshot.escalationMultiplier;
1778TxQ::getTxRequiredFeeAndSeq(
1782 auto const account = (*tx)[sfAccount];
1786 auto const snapshot = feeMetrics_.getSnapshot();
1788 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1790 auto const sle = view.
read(keylet::account(account));
1792 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1793 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1795 mulDiv(fee, baseFee, baseLevel)
1808 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1810 if (accountIter == byAccount_.end() ||
1811 accountIter->second.transactions.empty())
1814 result.
reserve(accountIter->second.transactions.size());
1815 for (
auto const& tx : accountIter->second.transactions)
1829 result.
reserve(byFee_.size());
1831 for (
auto const& tx : byFee_)
1843 BOOST_ASSERT(
false);
1847 auto const metrics = getMetrics(*view);
1853 ret[jss::ledger_current_index] = view->header().seq;
1854 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1855 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1857 if (metrics.txQMaxSize)
1860 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1861 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1862 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1863 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1865 auto const baseFee = view->fees().base;
1869 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1870 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1876 drops[jss::base_fee] = to_string(baseFee);
1877 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1878 drops[jss::minimum_fee] = to_string(
toDrops(
1879 metrics.minProcessingFeeLevel,
1880 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1881 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1882 if (effectiveBaseFee &&
1883 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1885 drops[jss::open_ledger_fee] = to_string(openFee);
1896 auto const& section = config.
section(
"transaction_queue");
1901 "minimum_escalation_multiplier",
1905 "minimum_txn_in_ledger_standalone",
1909 if (
set(max,
"maximum_txn_in_ledger", section))
1913 Throw<std::runtime_error>(
1914 "The minimum number of low-fee transactions allowed "
1915 "per ledger (minimum_txn_in_ledger) exceeds "
1916 "the maximum number of low-fee transactions allowed per "
1917 "ledger (maximum_txn_in_ledger).");
1921 Throw<std::runtime_error>(
1922 "The minimum number of low-fee transactions allowed "
1923 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1924 "the maximum number of low-fee transactions allowed per "
1925 "ledger (maximum_txn_in_ledger).");
1938 "normal_consensus_increase_percent",
1948 "slow_consensus_decrease_percent",
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
virtual OpenLedger & openLedger()=0
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
Snapshot getSnapshot() const
Get the current Snapshot.
std::size_t txnsExpected_
Number of transactions expected per ledger.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
static FeeLevel64 scaleFeeLevel(Snapshot const &snapshot, OpenView const &view)
Use the number of transactions in the current open ledger to compute the fee level a transaction must...
beast::Journal const j_
Journal.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
std::size_t update(Application &app, ReadView const &view, bool timeLeap, TxQ::Setup const &setup)
Updates fee metrics based on the transactions in the ReadView for use in fee escalation calculations.
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
static std::pair< bool, FeeLevel64 > escalatedSeriesFeeLevel(Snapshot const &snapshot, OpenView const &view, std::size_t extraCount, std::size_t seriesSize)
Computes the total fee level for all transactions in a series.
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Represents a transaction in the queue which may be applied later to the open ledger.
static LedgerHash parentHashComp
The hash of the parent ledger.
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfResult)
Constructor.
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
TxMap transactions
Sequence number will be used as the key.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
TxQ(Setup const &setup, beast::Journal j)
Constructor.
FeeMetrics feeMetrics_
Tracks the current state of the queue.
FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::lock_guard< std::mutex > const &lock) const
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
bool isFull() const
Is the queue at least fillPercentage full?
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type)
Erase and return the next entry for the account (if fee level is higher), or next entry in byFee_ (lo...
ApplyResult tryClearAccountQueueUpThruTx(Application &app, OpenView &view, STTx const &tx, AccountMap::iterator const &accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const &pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, beast::Journal j)
All-or-nothing attempt to try to apply the queued txs for accountIter up to and including tx.
ApplyResult apply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Add a new transaction to the open ledger, hold it in the queue, or reject it.
TER canBeHeld(STTx const &, ApplyFlags const, OpenView const &, std::shared_ptr< SLE const > const &sleAccount, AccountMap::iterator const &, std::optional< TxQAccount::TxMap::iterator > const &, std::lock_guard< std::mutex > const &lock)
Checks if the indicated transaction fits the conditions for being stored in the queue.
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
virtual ~TxQ()
Destructor.
static constexpr FeeLevel64 baseLevel
Fee level for single-signed reference transaction.
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level)
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
Setup const setup_
Setup parameters used to control the behavior of the queue.
constexpr int signum() const noexcept
Return the sign of the amount.
static constexpr std::size_t size()
T emplace_back(T... args)
@ objectValue
object value (collection of name/value pairs).
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
static ticket_t const ticket
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
FeeLevel< std::uint64_t > FeeLevel64
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
constexpr struct xrpl::open_ledger_t open_ledger
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
TxQ::Setup setup_TxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
std::string transToken(TER code)
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
bool isTefFailure(TER x) noexcept
auto constexpr muldiv_max
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
ApplyResult doApply(PreclaimResult const &preclaimResult, Application &app, OpenView &view)
Apply a prechecked transaction to an OpenView.
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
@ transactionID
transaction plus signature to give transaction ID
bool isTesSuccess(TER x) noexcept
PreclaimResult preclaim(PreflightResult const &preflightResult, Application &app, OpenView const &view)
Gate a transaction based on static ledger information.
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
bool isTemMalformed(TER x) noexcept
A pair of SHAMap key and LedgerEntryType.
TER const ter
Intermediate transaction result.
Describes the results of the preflight check.
Snapshot of the externally relevant FeeMetrics fields at any given time.
std::size_t const txnsExpected
FeeLevel64 const escalationMultiplier
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
std::size_t txCount
Number of transactions in the queue.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txInLedger
Number of transactions currently in the open ledger.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
std::size_t txPerLedger
Number of transactions expected per ledger.
Structure used to customize TxQ behavior.
bool standAlone
Use standalone mode behavior.
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "works towards".
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::size_t queueSizeMin
The smallest limit the queue is allowed.
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...