20#include <xrpld/app/ledger/OpenLedger.h>
21#include <xrpld/app/main/Application.h>
22#include <xrpld/app/misc/TxQ.h>
23#include <xrpld/app/tx/apply.h>
25#include <xrpl/basics/mulDiv.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/jss.h>
28#include <xrpl/protocol/st.h>
41 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
47 XRPAmount const mod = [&view, &tx, baseFee]() {
51 return def.signum() == 0 ?
XRPAmount{1} : def;
53 return std::pair{baseFee + mod, feePaid + mod};
56 XRPL_ASSERT(baseFee.signum() > 0,
"ripple::getFeeLevelPaid : positive fee");
57 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
77 return mulDiv(level, 100 + increasePercent, 100)
91 auto const txBegin = view.
txs.
begin();
92 auto const txEnd = view.
txs.
end();
100 size == feeLevels.
size(),
101 "ripple::TxQ::FeeMetrics::update : fee levels 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>(
133 auto const next = [&] {
161 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
212 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
216static_assert(sumOfFirstSquares(1).first ==
true);
217static_assert(sumOfFirstSquares(1).second == 1);
219static_assert(sumOfFirstSquares(2).first ==
true);
220static_assert(sumOfFirstSquares(2).second == 5);
222static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
223static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
225static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
227 sumOfFirstSquares(0x200000).second ==
248 auto const last =
current + seriesSize - 1;
255 "ripple::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
270 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
271 auto const totalFeeLevel =
mulDiv(
272 multiplier, sumNlast.second - sumNcurrent.second, target * target);
274 return {totalFeeLevel.has_value(), *totalFeeLevel};
286 , feeLevel(feeLevel_)
288 , account(txn_->getAccountID(sfAccount))
290 , seqProxy(txn_->getSeqProxy())
291 , retriesRemaining(retriesAllowed)
293 , pfresult(pfresult_)
302 pfresult,
"ripple::TxQ::MaybeTx::apply : preflight result is set");
306 if (pfresult->rules != view.
rules() || pfresult->flags != flags)
308 JLOG(j.
debug()) <<
"Queued transaction " << txID
309 <<
" rules or flags have changed. Flags from "
310 << pfresult->flags <<
" to " << flags;
316 auto pcresult =
preclaim(*pfresult, app, view);
318 return doApply(pcresult, app, view);
330TxQ::TxQAccount::TxMap::const_iterator
335 auto sameOrPrevIter = transactions.lower_bound(seqProx);
336 if (sameOrPrevIter != transactions.begin())
338 return sameOrPrevIter;
346 auto result = transactions.emplace(seqProx, std::move(txn));
348 result.second,
"ripple::TxQ::TxQAccount::add : emplace succeeded");
350 &result.first->second != &txn,
351 "ripple::TxQ::TxQAccount::add : transaction moved");
353 return result.first->second;
359 return transactions.erase(seqProx) != 0;
374template <
size_t fillPercentage>
379 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
389 AccountMap::iterator
const& accountIter,
420 TxQAccount const& txQAcct = accountIter->second;
427 if (txSeqProx.isTicket())
434 if (txSeqProx != nextQueuable)
450TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
451 -> FeeMultiSet::iterator_type
453 auto& txQAccount = byAccount_.at(candidateIter->account);
454 auto const seqProx = candidateIter->seqProxy;
455 auto const newCandidateIter = byFee_.erase(candidateIter);
459 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
460 XRPL_ASSERT(found,
"ripple::TxQ::erase : account removed");
462 return newCandidateIter;
467 -> FeeMultiSet::iterator_type
469 auto& txQAccount = byAccount_.at(candidateIter->account);
470 auto const accountIter =
471 txQAccount.transactions.find(candidateIter->seqProxy);
473 accountIter != txQAccount.transactions.end(),
474 "ripple::TxQ::eraseAndAdvance : account found");
480 candidateIter->seqProxy.isTicket() ||
481 accountIter == txQAccount.transactions.begin(),
482 "ripple::TxQ::eraseAndAdvance : ticket or sequence");
484 byFee_.iterator_to(accountIter->second) == candidateIter,
485 "ripple::TxQ::eraseAndAdvance : found in byFee");
486 auto const accountNextIter =
std::next(accountIter);
490 auto const feeNextIter =
std::next(candidateIter);
491 bool const useAccountNext =
492 accountNextIter != txQAccount.transactions.end() &&
493 accountNextIter->first > candidateIter->seqProxy &&
494 (feeNextIter == byFee_.end() ||
495 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
497 auto const candidateNextIter = byFee_.erase(candidateIter);
498 txQAccount.transactions.erase(accountIter);
500 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
507 TxQ::TxQAccount::TxMap::const_iterator begin,
508 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
510 for (
auto it = begin; it != end; ++it)
512 byFee_.erase(byFee_.iterator_to(it->second));
514 return txQAccount.transactions.erase(begin, end);
522 TxQ::AccountMap::iterator
const& accountIter,
523 TxQAccount::TxMap::iterator beginTxIter,
533 beginTxIter != accountIter->second.transactions.end(),
534 "ripple::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
538 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
542 metricsSnapshot, view, txExtraCount, dist + 1);
546 if (!requiredTotalFeeLevel.first)
553 [](
auto const& total,
auto const& txn) {
554 return total + txn.second.feeLevel;
558 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
563 for (
auto it = beginTxIter; it != endTxIter; ++it)
565 auto txResult = it->second.apply(app, view, j);
570 --it->second.retriesRemaining;
571 it->second.lastResult = txResult.ter;
592 if (!txResult.applied)
595 return {txResult.ter,
false};
600 auto const txResult =
doApply(
preclaim(pfresult, app, view), app, view);
602 if (txResult.applied)
606 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
608 if (endTxIter != accountIter->second.transactions.end() &&
609 endTxIter->first == tSeqProx)
743 return *directApplied;
755 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
757 return {pfresult.ter,
false};
760 auto const account = (*tx)[sfAccount];
762 auto const sleAccount = view.
read(accountKey);
768 SeqProxy const txSeqProx = tx->getSeqProxy();
786 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
799 TxQAccount::TxMap::iterator first_,
800 TxQAccount::TxMap::iterator end_)
801 : first(first_), end(end_)
805 TxQAccount::TxMap::iterator first;
806 TxQAccount::TxMap::iterator end;
813 if (!accountIsInQueue)
818 TxQAccount::TxMap::iterator
const firstIter =
821 if (firstIter == acctTxs.
end())
826 return {TxIter{firstIter, acctTxs.
end()}};
829 auto const acctTxCount{
837 if (pfresult.consequences.isBlocker())
845 <<
". Account has other queued transactions.";
848 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
853 <<
". Blocker does not replace lone queued transaction.";
860 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
862 if (accountIsInQueue)
876 auto const requiredFeeLevel =
888 if (acctTxCount == 1 &&
889 txIter->first->second.consequences().isBlocker() &&
890 (txIter->first->first != txSeqProx))
904 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
908 <<
"Found transaction in queue for account " << account
909 <<
" with " << txSeqProx <<
" new txn fee level is "
910 << feeLevelPaid <<
", old txn fee level is "
911 << existingIter->second.feeLevel
912 <<
", new txn needs fee level of " << requiredRetryLevel;
913 if (feeLevelPaid > requiredRetryLevel)
918 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
919 << existingIter->second.txID <<
" in favor of "
927 <<
" in favor of queued " << existingIter->second.txID;
939 : applyView(&view, flags), openView(&applyView)
946 if (acctTxCount == 0)
951 if (txSeqProx.
isSeq())
953 if (acctSeqProx > txSeqProx)
955 if (acctSeqProx < txSeqProx)
964 TxQAccount const& txQAcct = accountIter->second;
966 if (acctSeqProx > txSeqProx)
975 bool requiresMultiTxn =
false;
976 if (acctTxCount > 1 || !replacedTxIter)
991 requiresMultiTxn =
true;
994 if (requiresMultiTxn)
1008 TxQAccount::TxMap::const_iterator
const prevIter =
1018 prevIter != txIter->end,
"ripple::TxQ::apply : not end");
1019 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1023 if (txSeqProx.
isSeq())
1025 if (txSeqProx < acctSeqProx)
1027 else if (txSeqProx > acctSeqProx)
1031 else if (!replacedTxIter)
1038 if (txSeqProx.
isSeq() &&
1048 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1053 if (iter->first != txSeqProx)
1055 totalFee += iter->second.consequences().fee();
1057 iter->second.consequences().potentialSpend();
1059 else if (
std::next(iter) != txIter->end)
1064 totalFee += pfresult.consequences.fee();
1065 potentialSpend += pfresult.consequences.potentialSpend();
1098 auto const balance = (*sleAccount)[sfBalance].xrp();
1117 auto const base = view.
fees().
base;
1118 if (totalFee >= balance ||
1119 (reserve > 10 * base && totalFee >= reserve))
1123 <<
". Total fees in flight too high.";
1128 multiTxn.
emplace(view, flags);
1130 auto const sleBump = multiTxn->applyView.peek(accountKey);
1137 auto const potentialTotalSpend = totalFee +
1142 multiTxn->applyView.fees().base == 0),
1143 "ripple::TxQ::apply : total spend check");
1144 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1150 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1167 auto const pcresult =
1168 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1169 if (!pcresult.likelyToClaimFee)
1170 return {pcresult.ter,
false};
1173 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"ripple::TxQ::apply : minimum fee");
1176 << account <<
" has fee level of " << feeLevelPaid
1177 <<
" needs at least " << requiredFeeLevel
1178 <<
" to get in the open ledger, which has "
1179 << view.
txCount() <<
" entries.";
1200 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1218 sandbox.
apply(view);
1230 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1236 return {ter,
false};
1243 if (!replacedTxIter &&
isFull())
1245 auto lastRIter =
byFee_.rbegin();
1246 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1250 if (lastRIter ==
byFee_.rend())
1260 <<
" would kick a transaction from the same account ("
1261 << account <<
") out of the queue.";
1264 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1265 auto endEffectiveFeeLevel = [&]() {
1269 if (lastRIter->feeLevel > feeLevelPaid ||
1270 endAccount.transactions.size() == 1)
1271 return lastRIter->feeLevel;
1275 endAccount.transactions.begin(),
1276 endAccount.transactions.end(),
1278 [&](
auto const& total,
1282 txn.second.feeLevel / endAccount.transactions.size();
1284 txn.second.feeLevel % endAccount.transactions.size();
1285 if (total.first >= max - next || total.second >= max - mod)
1286 return {max, FeeLevel64{0}};
1288 return {total.first + next, total.second + mod};
1290 return endTotal.first +
1291 endTotal.second / endAccount.transactions.size();
1293 if (feeLevelPaid > endEffectiveFeeLevel)
1297 auto dropRIter = endAccount.transactions.rbegin();
1299 dropRIter->second.account == lastRIter->account,
1300 "ripple::TxQ::apply : cheapest transaction found");
1302 <<
"Removing last item of account " << lastRIter->account
1303 <<
" from queue with average fee of " << endEffectiveFeeLevel
1306 erase(byFee_.iterator_to(dropRIter->second));
1312 <<
" fee is lower than end item's account average fee";
1320 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1323 if (!accountIsInQueue)
1326 [[maybe_unused]]
bool created =
false;
1328 byAccount_.emplace(account, TxQAccount(tx));
1329 XRPL_ASSERT(created,
"ripple::TxQ::apply : account created");
1339 auto& candidate = accountIter->second.add(
1343 byFee_.insert(candidate);
1344 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1345 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1346 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1347 << candidate.account <<
" to queue."
1348 <<
" Flags: " << flags;
1350 return {terQUEUED,
false};
1370 feeMetrics_.update(app, view, timeLeap, setup_);
1371 auto const& snapshot = feeMetrics_.getSnapshot();
1373 auto ledgerSeq = view.
info().
seq;
1377 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1380 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1382 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1384 byAccount_.at(candidateIter->account).dropPenalty =
true;
1385 candidateIter =
erase(candidateIter);
1395 for (
auto txQAccountIter = byAccount_.begin();
1396 txQAccountIter != byAccount_.end();)
1398 if (txQAccountIter->second.empty())
1399 txQAccountIter = byAccount_.erase(txQAccountIter);
1443 auto ledgerChanged =
false;
1447 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1449 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1451 auto& account = byAccount_.at(candidateIter->account);
1452 auto const beginIter = account.transactions.begin();
1453 if (candidateIter->seqProxy.isSeq() &&
1454 candidateIter->seqProxy > beginIter->first)
1460 <<
"Skipping queued transaction " << candidateIter->txID
1461 <<
" from account " << candidateIter->account
1462 <<
" as it is not the first.";
1466 auto const requiredFeeLevel =
1467 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1468 auto const feeLevelPaid = candidateIter->feeLevel;
1469 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1470 <<
" from account " << candidateIter->account
1471 <<
" has fee level of " << feeLevelPaid
1472 <<
" needs at least " << requiredFeeLevel;
1473 if (feeLevelPaid >= requiredFeeLevel)
1475 JLOG(j_.
trace()) <<
"Applying queued transaction "
1476 << candidateIter->txID <<
" to open ledger.";
1478 auto const [txnResult, didApply, _metadata] =
1479 candidateIter->apply(app, view, j_);
1485 <<
"Queued transaction " << candidateIter->txID
1486 <<
" applied successfully with " <<
transToken(txnResult)
1487 <<
". Remove from queue.";
1489 candidateIter = eraseAndAdvance(candidateIter);
1490 ledgerChanged =
true;
1494 candidateIter->retriesRemaining <= 0)
1496 if (candidateIter->retriesRemaining <= 0)
1497 account.retryPenalty =
true;
1499 account.dropPenalty =
true;
1500 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1502 <<
". Remove from queue.";
1503 candidateIter = eraseAndAdvance(candidateIter);
1508 <<
"Queued transaction " << candidateIter->txID
1510 <<
". Leave in queue." <<
" Applied: " << didApply
1511 <<
". Flags: " << candidateIter->flags;
1512 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1513 candidateIter->retriesRemaining = 1;
1515 --candidateIter->retriesRemaining;
1516 candidateIter->lastResult = txnResult;
1517 if (account.dropPenalty && account.transactions.size() > 1 &&
1523 if (candidateIter->seqProxy.isTicket())
1528 <<
"Queue is nearly full, and transaction "
1529 << candidateIter->txID <<
" failed with "
1531 <<
". Removing ticketed tx from account "
1533 candidateIter = eraseAndAdvance(candidateIter);
1541 auto dropRIter = account.transactions.rbegin();
1543 dropRIter->second.account == candidateIter->account,
1544 "ripple::TxQ::accept : account check");
1547 <<
"Queue is nearly full, and transaction "
1548 << candidateIter->txID <<
" failed with "
1550 <<
". Removing last item from account "
1552 auto endIter = byFee_.iterator_to(dropRIter->second);
1553 if (endIter != candidateIter)
1573 if (parentHash == parentHash_)
1574 JLOG(j_.
warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1576 parentHash_ = parentHash;
1578 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1589 MaybeTx::parentHashComp = parentHash;
1591 for (
auto& [_, account] : byAccount_)
1593 for (
auto& [_, candidate] : account.transactions)
1595 byFee_.insert(candidate);
1599 byFee_.size() == startingSize,
1600 "ripple::TxQ::accept : byFee size match");
1602 return ledgerChanged;
1612 return nextQueuableSeqImpl(sleAccount, lock);
1622TxQ::nextQueuableSeqImpl(
1628 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1629 return SeqProxy::sequence(0);
1631 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1634 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1635 if (accountIter == byAccount_.end() ||
1636 accountIter->second.transactions.empty())
1644 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1646 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1647 txIter->first != acctSeqProx)
1657 SeqProxy attempt = txIter->second.consequences().followingSeq();
1658 while (++txIter != acctTxs.
cend())
1660 if (attempt < txIter->first)
1663 attempt = txIter->second.consequences().followingSeq();
1669TxQ::getRequiredFeeLevel(
1675 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1686 auto const account = (*tx)[sfAccount];
1687 auto const sleAccount = view.
read(keylet::account(account));
1693 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1694 SeqProxy const txSeqProx = tx->getSeqProxy();
1698 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1701 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1703 return getRequiredFeeLevel(
1704 view, flags, feeMetrics_.getSnapshot(), lock);
1711 if (feeLevelPaid >= requiredFeeLevel)
1716 <<
" to open ledger.";
1718 auto const [txnResult, didApply, metadata] =
1722 << (didApply ?
" applied successfully with "
1732 AccountMap::iterator accountIter = byAccount_.find(account);
1733 if (accountIter != byAccount_.end())
1736 if (
auto const existingIter =
1740 removeFromByFee(existingIter, tx);
1744 return ApplyResult{txnResult, didApply, metadata};
1750TxQ::removeFromByFee(
1754 if (replacedTxIter && tx)
1758 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1760 deleteIter != byFee_.end(),
1761 "ripple::TxQ::removeFromByFee : found in byFee");
1763 &(*replacedTxIter)->second == &*deleteIter,
1764 "ripple::TxQ::removeFromByFee : matching transaction");
1766 deleteIter->seqProxy == tx->getSeqProxy(),
1767 "ripple::TxQ::removeFromByFee : matching sequence");
1769 deleteIter->account == (*tx)[sfAccount],
1770 "ripple::TxQ::removeFromByFee : matching account");
1774 return std::nullopt;
1784 auto const snapshot = feeMetrics_.getSnapshot();
1786 result.
txCount = byFee_.size();
1792 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1793 result.
medFeeLevel = snapshot.escalationMultiplier;
1800TxQ::getTxRequiredFeeAndSeq(
1804 auto const account = (*tx)[sfAccount];
1808 auto const snapshot = feeMetrics_.getSnapshot();
1810 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1812 auto const sle = view.
read(keylet::account(account));
1814 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1815 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1817 mulDiv(fee, baseFee, baseLevel)
1830 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1832 if (accountIter == byAccount_.end() ||
1833 accountIter->second.transactions.empty())
1836 result.
reserve(accountIter->second.transactions.size());
1837 for (
auto const& tx : accountIter->second.transactions)
1851 result.
reserve(byFee_.size());
1853 for (
auto const& tx : byFee_)
1865 BOOST_ASSERT(
false);
1869 auto const metrics = getMetrics(*view);
1875 ret[jss::ledger_current_index] = view->info().seq;
1876 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1877 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1879 if (metrics.txQMaxSize)
1882 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1883 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1884 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1885 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1887 auto const baseFee = view->fees().base;
1891 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1892 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1898 drops[jss::base_fee] = to_string(baseFee);
1899 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1900 drops[jss::minimum_fee] = to_string(
toDrops(
1901 metrics.minProcessingFeeLevel,
1902 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1903 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1904 if (effectiveBaseFee &&
1905 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1907 drops[jss::open_ledger_fee] = to_string(openFee);
1918 auto const& section = config.
section(
"transaction_queue");
1923 "minimum_escalation_multiplier",
1927 "minimum_txn_in_ledger_standalone",
1931 if (
set(max,
"maximum_txn_in_ledger", section))
1935 Throw<std::runtime_error>(
1936 "The minimum number of low-fee transactions allowed "
1937 "per ledger (minimum_txn_in_ledger) exceeds "
1938 "the maximum number of low-fee transactions allowed per "
1939 "ledger (maximum_txn_in_ledger).");
1943 Throw<std::runtime_error>(
1944 "The minimum number of low-fee transactions allowed "
1945 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1946 "the maximum number of low-fee transactions allowed per "
1947 "ledger (maximum_txn_in_ledger).");
1960 "normal_consensus_increase_percent",
1970 "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.
LedgerInfo const & info() const override
Returns information about the ledger.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Fees const & fees() const override
Returns the fees for the base ledger.
void apply(TxsRawView &to) const
Apply changes.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
RAII class to set and restore the STAmount canonicalize switchover.
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 isSeq() const
constexpr std::uint32_t value() const
constexpr bool isTicket() const
std::size_t txnsExpected_
Number of transactions expected per ledger.
beast::Journal const j_
Journal.
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...
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
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.
Snapshot getSnapshot() const
Get the current Snapshot.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
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.
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.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfresult)
Constructor.
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
static LedgerHash parentHashComp
The hash of the parent ledger.
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
TxMap transactions
Sequence number will be used as the key.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level)
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
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::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
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.
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...
FeeMetrics feeMetrics_
Tracks the current state of the queue.
virtual ~TxQ()
Destructor.
FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::lock_guard< std::mutex > const &lock) const
TxQ(Setup const &setup, beast::Journal j)
Constructor.
static constexpr FeeLevel64 baseLevel
Fee level for single-signed reference transaction.
Setup const setup_
Setup parameters used to control the behavior of the queue.
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
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.
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)
Keylet account(AccountID const &id) noexcept
AccountID root.
static ticket_t const ticket
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
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.
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
ApplyResult doApply(PreclaimResult const &preclaimResult, Application &app, OpenView &view)
Apply a prechecked transaction to an OpenView.
auto constexpr muldiv_max
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
PreclaimResult preclaim(PreflightResult const &preflightResult, Application &app, OpenView const &view)
Gate a transaction based on static ledger information.
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,...
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
std::string transToken(TER code)
bool isTemMalformed(TER x)
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
FeeLevel< std::uint64_t > FeeLevel64
open_ledger_t const open_ledger
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
@ transactionID
transaction plus signature to give transaction ID
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
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.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txPerLedger
Number of transactions expected per ledger.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
std::size_t txInLedger
Number of transactions currently in the open ledger.
std::size_t txCount
Number of transactions in the queue.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
Structure used to customize TxQ behavior.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
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::size_t queueSizeMin
The smallest limit the queue is allowed.
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 retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
bool standAlone
Use standalone mode behavior.
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...