20#include <xrpld/app/ledger/OpenLedger.h>
21#include <xrpld/app/main/Application.h>
22#include <xrpld/app/misc/LoadFeeTrack.h>
23#include <xrpld/app/misc/TxQ.h>
24#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>
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 XRPL_ASSERT(baseFee.signum() > 0,
"ripple::getFeeLevelPaid : positive fee");
56 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
76 return mulDiv(level, 100 + increasePercent, 100)
90 auto const txBegin = view.
txs.
begin();
91 auto const txEnd = view.
txs.
end();
99 size == feeLevels.
size(),
100 "ripple::TxQ::FeeMetrics::update : fee levels size");
103 <<
"Ledger " << view.
info().
seq <<
" has " << size <<
" transactions. "
104 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
115 auto const upperLimit = std::max<std::uint64_t>(
132 auto const next = [&] {
160 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) /
211 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
215static_assert(sumOfFirstSquares(1).first ==
true);
216static_assert(sumOfFirstSquares(1).second == 1);
218static_assert(sumOfFirstSquares(2).first ==
true);
219static_assert(sumOfFirstSquares(2).second == 5);
221static_assert(sumOfFirstSquares(0x1FFFFF).first ==
true,
"");
222static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
224static_assert(sumOfFirstSquares(0x200000).first ==
false,
"");
226 sumOfFirstSquares(0x200000).second ==
247 auto const last =
current + seriesSize - 1;
254 "ripple::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
269 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
270 auto const totalFeeLevel =
mulDiv(
271 multiplier, sumNlast.second - sumNcurrent.second, target * target);
273 return {totalFeeLevel.has_value(), *totalFeeLevel};
285 , feeLevel(feeLevel_)
287 , account(txn_->getAccountID(sfAccount))
289 , seqProxy(txn_->getSeqProxy())
290 , retriesRemaining(retriesAllowed)
292 , pfresult(pfresult_)
301 pfresult,
"ripple::TxQ::MaybeTx::apply : preflight result is set");
305 if (pfresult->rules != view.
rules() || pfresult->flags != flags)
307 JLOG(j.
debug()) <<
"Queued transaction " << txID
308 <<
" rules or flags have changed. Flags from "
309 << pfresult->flags <<
" to " << flags;
315 auto pcresult =
preclaim(*pfresult, app, view);
317 return doApply(pcresult, app, view);
329TxQ::TxQAccount::TxMap::const_iterator
334 auto sameOrPrevIter = transactions.lower_bound(seqProx);
335 if (sameOrPrevIter != transactions.begin())
337 return sameOrPrevIter;
345 auto result = transactions.emplace(seqProx, std::move(txn));
347 result.second,
"ripple::TxQ::TxQAccount::add : emplace succeeded");
349 &result.first->second != &txn,
350 "ripple::TxQ::TxQAccount::add : transaction moved");
352 return result.first->second;
358 return transactions.erase(seqProx) != 0;
373template <
size_t fillPercentage>
378 fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
388 AccountMap::iterator
const& accountIter,
419 TxQAccount const& txQAcct = accountIter->second;
426 if (txSeqProx.isTicket())
433 if (txSeqProx != nextQueuable)
449TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
450 -> FeeMultiSet::iterator_type
452 auto& txQAccount = byAccount_.at(candidateIter->account);
453 auto const seqProx = candidateIter->seqProxy;
454 auto const newCandidateIter = byFee_.erase(candidateIter);
458 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
459 XRPL_ASSERT(found,
"ripple::TxQ::erase : account removed");
461 return newCandidateIter;
466 -> FeeMultiSet::iterator_type
468 auto& txQAccount = byAccount_.at(candidateIter->account);
469 auto const accountIter =
470 txQAccount.transactions.find(candidateIter->seqProxy);
472 accountIter != txQAccount.transactions.end(),
473 "ripple::TxQ::eraseAndAdvance : account found");
479 candidateIter->seqProxy.isTicket() ||
480 accountIter == txQAccount.transactions.begin(),
481 "ripple::TxQ::eraseAndAdvance : ticket or sequence");
483 byFee_.iterator_to(accountIter->second) == candidateIter,
484 "ripple::TxQ::eraseAndAdvance : found in byFee");
485 auto const accountNextIter =
std::next(accountIter);
489 auto const feeNextIter =
std::next(candidateIter);
490 bool const useAccountNext =
491 accountNextIter != txQAccount.transactions.end() &&
492 accountNextIter->first > candidateIter->seqProxy &&
493 (feeNextIter == byFee_.end() ||
494 byFee_.value_comp()(accountNextIter->second, *feeNextIter));
496 auto const candidateNextIter = byFee_.erase(candidateIter);
497 txQAccount.transactions.erase(accountIter);
499 return useAccountNext ? byFee_.iterator_to(accountNextIter->second)
506 TxQ::TxQAccount::TxMap::const_iterator begin,
507 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
509 for (
auto it = begin; it != end; ++it)
511 byFee_.erase(byFee_.iterator_to(it->second));
513 return txQAccount.transactions.erase(begin, end);
521 TxQ::AccountMap::iterator
const& accountIter,
522 TxQAccount::TxMap::iterator beginTxIter,
532 beginTxIter != accountIter->second.transactions.end(),
533 "ripple::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
537 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
541 metricsSnapshot, view, txExtraCount, dist + 1);
545 if (!requiredTotalFeeLevel.first)
552 [](
auto const& total,
auto const& txn) {
553 return total + txn.second.feeLevel;
557 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
562 for (
auto it = beginTxIter; it != endTxIter; ++it)
564 auto txResult = it->second.apply(app, view, j);
569 --it->second.retriesRemaining;
570 it->second.lastResult = txResult.ter;
591 if (!txResult.applied)
594 return {txResult.ter,
false};
599 auto const txResult =
doApply(
preclaim(pfresult, app, view), app, view);
601 if (txResult.applied)
605 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
607 if (endTxIter != accountIter->second.transactions.end() &&
608 endTxIter->first == tSeqProx)
742 return *directApplied;
754 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
756 return {pfresult.ter,
false};
759 auto const account = (*tx)[sfAccount];
761 auto const sleAccount = view.
read(accountKey);
767 SeqProxy const txSeqProx = tx->getSeqProxy();
785 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
798 TxQAccount::TxMap::iterator first_,
799 TxQAccount::TxMap::iterator end_)
800 : first(first_), end(end_)
804 TxQAccount::TxMap::iterator first;
805 TxQAccount::TxMap::iterator end;
812 if (!accountIsInQueue)
817 TxQAccount::TxMap::iterator
const firstIter =
820 if (firstIter == acctTxs.
end())
825 return {TxIter{firstIter, acctTxs.
end()}};
828 auto const acctTxCount{
836 if (pfresult.consequences.isBlocker())
844 <<
". Account has other queued transactions.";
847 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
852 <<
". Blocker does not replace lone queued transaction.";
859 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
861 if (accountIsInQueue)
875 auto const requiredFeeLevel =
887 if (acctTxCount == 1 &&
888 txIter->first->second.consequences().isBlocker() &&
889 (txIter->first->first != txSeqProx))
903 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
907 <<
"Found transaction in queue for account " << account
908 <<
" with " << txSeqProx <<
" new txn fee level is "
909 << feeLevelPaid <<
", old txn fee level is "
910 << existingIter->second.feeLevel
911 <<
", new txn needs fee level of " << requiredRetryLevel;
912 if (feeLevelPaid > requiredRetryLevel)
917 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
918 << existingIter->second.txID <<
" in favor of "
926 <<
" in favor of queued " << existingIter->second.txID;
938 : applyView(&view, flags), openView(&applyView)
945 if (acctTxCount == 0)
950 if (txSeqProx.
isSeq())
952 if (acctSeqProx > txSeqProx)
954 if (acctSeqProx < txSeqProx)
963 TxQAccount const& txQAcct = accountIter->second;
965 if (acctSeqProx > txSeqProx)
974 bool requiresMultiTxn =
false;
975 if (acctTxCount > 1 || !replacedTxIter)
990 requiresMultiTxn =
true;
993 if (requiresMultiTxn)
1007 TxQAccount::TxMap::const_iterator
const prevIter =
1017 prevIter != txIter->end,
"ripple::TxQ::apply : not end");
1018 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1022 if (txSeqProx.
isSeq())
1024 if (txSeqProx < acctSeqProx)
1026 else if (txSeqProx > acctSeqProx)
1030 else if (!replacedTxIter)
1037 if (txSeqProx.
isSeq() &&
1047 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1052 if (iter->first != txSeqProx)
1054 totalFee += iter->second.consequences().fee();
1056 iter->second.consequences().potentialSpend();
1058 else if (
std::next(iter) != txIter->end)
1063 totalFee += pfresult.consequences.fee();
1064 potentialSpend += pfresult.consequences.potentialSpend();
1097 auto const balance = (*sleAccount)[sfBalance].xrp();
1116 auto const base = view.
fees().
base;
1117 if (totalFee >= balance ||
1118 (reserve > 10 * base && totalFee >= reserve))
1122 <<
". Total fees in flight too high.";
1127 multiTxn.
emplace(view, flags);
1129 auto const sleBump = multiTxn->applyView.peek(accountKey);
1136 auto const potentialTotalSpend = totalFee +
1141 multiTxn->applyView.fees().base == 0),
1142 "ripple::TxQ::apply : total spend check");
1143 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1149 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1166 auto const pcresult =
1167 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1168 if (!pcresult.likelyToClaimFee)
1169 return {pcresult.ter,
false};
1172 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"ripple::TxQ::apply : minimum fee");
1175 << account <<
" has fee level of " << feeLevelPaid
1176 <<
" needs at least " << requiredFeeLevel
1177 <<
" to get in the open ledger, which has "
1178 << view.
txCount() <<
" entries.";
1199 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1217 sandbox.
apply(view);
1229 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1235 return {ter,
false};
1242 if (!replacedTxIter &&
isFull())
1244 auto lastRIter =
byFee_.rbegin();
1245 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1249 if (lastRIter ==
byFee_.rend())
1259 <<
" would kick a transaction from the same account ("
1260 << account <<
") out of the queue.";
1263 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1264 auto endEffectiveFeeLevel = [&]() {
1268 if (lastRIter->feeLevel > feeLevelPaid ||
1269 endAccount.transactions.size() == 1)
1270 return lastRIter->feeLevel;
1274 endAccount.transactions.begin(),
1275 endAccount.transactions.end(),
1277 [&](
auto const& total,
1281 txn.second.feeLevel / endAccount.transactions.size();
1283 txn.second.feeLevel % endAccount.transactions.size();
1284 if (total.first >= max - next || total.second >= max - mod)
1285 return {max, FeeLevel64{0}};
1287 return {total.first + next, total.second + mod};
1289 return endTotal.first +
1290 endTotal.second / endAccount.transactions.size();
1292 if (feeLevelPaid > endEffectiveFeeLevel)
1296 auto dropRIter = endAccount.transactions.rbegin();
1298 dropRIter->second.account == lastRIter->account,
1299 "ripple::TxQ::apply : cheapest transaction found");
1301 <<
"Removing last item of account " << lastRIter->account
1302 <<
" from queue with average fee of " << endEffectiveFeeLevel
1305 erase(byFee_.iterator_to(dropRIter->second));
1311 <<
" fee is lower than end item's account average fee";
1319 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1322 if (!accountIsInQueue)
1325 [[maybe_unused]]
bool created =
false;
1327 byAccount_.emplace(account, TxQAccount(tx));
1328 XRPL_ASSERT(created,
"ripple::TxQ::apply : account created");
1338 auto& candidate = accountIter->second.add(
1342 byFee_.insert(candidate);
1343 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1344 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1345 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1346 << candidate.account <<
" to queue."
1347 <<
" Flags: " << flags;
1349 return {terQUEUED,
false};
1369 feeMetrics_.update(app, view, timeLeap, setup_);
1370 auto const& snapshot = feeMetrics_.getSnapshot();
1372 auto ledgerSeq = view.
info().
seq;
1376 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1379 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1381 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1383 byAccount_.at(candidateIter->account).dropPenalty =
true;
1384 candidateIter =
erase(candidateIter);
1394 for (
auto txQAccountIter = byAccount_.begin();
1395 txQAccountIter != byAccount_.end();)
1397 if (txQAccountIter->second.empty())
1398 txQAccountIter = byAccount_.erase(txQAccountIter);
1442 auto ledgerChanged =
false;
1446 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1448 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1450 auto& account = byAccount_.at(candidateIter->account);
1451 auto const beginIter = account.transactions.begin();
1452 if (candidateIter->seqProxy.isSeq() &&
1453 candidateIter->seqProxy > beginIter->first)
1459 <<
"Skipping queued transaction " << candidateIter->txID
1460 <<
" from account " << candidateIter->account
1461 <<
" as it is not the first.";
1465 auto const requiredFeeLevel =
1466 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1467 auto const feeLevelPaid = candidateIter->feeLevel;
1468 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1469 <<
" from account " << candidateIter->account
1470 <<
" has fee level of " << feeLevelPaid
1471 <<
" needs at least " << requiredFeeLevel;
1472 if (feeLevelPaid >= requiredFeeLevel)
1474 JLOG(j_.
trace()) <<
"Applying queued transaction "
1475 << candidateIter->txID <<
" to open ledger.";
1477 auto const [txnResult, didApply, _metadata] =
1478 candidateIter->apply(app, view, j_);
1484 <<
"Queued transaction " << candidateIter->txID
1485 <<
" applied successfully with " <<
transToken(txnResult)
1486 <<
". Remove from queue.";
1488 candidateIter = eraseAndAdvance(candidateIter);
1489 ledgerChanged =
true;
1493 candidateIter->retriesRemaining <= 0)
1495 if (candidateIter->retriesRemaining <= 0)
1496 account.retryPenalty =
true;
1498 account.dropPenalty =
true;
1499 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1501 <<
". Remove from queue.";
1502 candidateIter = eraseAndAdvance(candidateIter);
1507 <<
"Queued transaction " << candidateIter->txID
1509 <<
". Leave in queue." <<
" Applied: " << didApply
1510 <<
". Flags: " << candidateIter->flags;
1511 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1512 candidateIter->retriesRemaining = 1;
1514 --candidateIter->retriesRemaining;
1515 candidateIter->lastResult = txnResult;
1516 if (account.dropPenalty && account.transactions.size() > 1 &&
1522 if (candidateIter->seqProxy.isTicket())
1527 <<
"Queue is nearly full, and transaction "
1528 << candidateIter->txID <<
" failed with "
1530 <<
". Removing ticketed tx from account "
1532 candidateIter = eraseAndAdvance(candidateIter);
1540 auto dropRIter = account.transactions.rbegin();
1542 dropRIter->second.account == candidateIter->account,
1543 "ripple::TxQ::accept : account check");
1546 <<
"Queue is nearly full, and transaction "
1547 << candidateIter->txID <<
" failed with "
1549 <<
". Removing last item from account "
1551 auto endIter = byFee_.iterator_to(dropRIter->second);
1552 if (endIter != candidateIter)
1573 auto const startingSize = byFee_.
size();
1575 parentHash != parentHash_,
"ripple::TxQ::accept : new parent hash");
1576 parentHash_ = parentHash;
1588 MaybeTx::parentHashComp = parentHash;
1590 for (
auto& [_, account] : byAccount_)
1592 for (
auto& [_, candidate] : account.transactions)
1594 byFee_.insert(candidate);
1598 byFee_.size() == startingSize,
1599 "ripple::TxQ::accept : byFee size match");
1601 return ledgerChanged;
1611 return nextQueuableSeqImpl(sleAccount, lock);
1621TxQ::nextQueuableSeqImpl(
1627 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1628 return SeqProxy::sequence(0);
1630 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1633 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1634 if (accountIter == byAccount_.end() ||
1635 accountIter->second.transactions.empty())
1643 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1645 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1646 txIter->first != acctSeqProx)
1656 SeqProxy attempt = txIter->second.consequences().followingSeq();
1657 while (++txIter != acctTxs.
cend())
1659 if (attempt < txIter->first)
1662 attempt = txIter->second.consequences().followingSeq();
1668TxQ::getRequiredFeeLevel(
1674 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1685 auto const account = (*tx)[sfAccount];
1686 auto const sleAccount = view.
read(keylet::account(account));
1692 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1693 SeqProxy const txSeqProx = tx->getSeqProxy();
1697 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1700 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1702 return getRequiredFeeLevel(
1703 view, flags, feeMetrics_.getSnapshot(), lock);
1710 if (feeLevelPaid >= requiredFeeLevel)
1715 <<
" to open ledger.";
1717 auto const [txnResult, didApply, metadata] =
1721 << (didApply ?
" applied successfully with "
1731 AccountMap::iterator accountIter = byAccount_.find(account);
1732 if (accountIter != byAccount_.end())
1735 if (
auto const existingIter =
1739 removeFromByFee(existingIter, tx);
1743 return ApplyResult{txnResult, didApply, metadata};
1749TxQ::removeFromByFee(
1753 if (replacedTxIter && tx)
1757 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1759 deleteIter != byFee_.end(),
1760 "ripple::TxQ::removeFromByFee : found in byFee");
1762 &(*replacedTxIter)->second == &*deleteIter,
1763 "ripple::TxQ::removeFromByFee : matching transaction");
1765 deleteIter->seqProxy == tx->getSeqProxy(),
1766 "ripple::TxQ::removeFromByFee : matching sequence");
1768 deleteIter->account == (*tx)[sfAccount],
1769 "ripple::TxQ::removeFromByFee : matching account");
1773 return std::nullopt;
1783 auto const snapshot = feeMetrics_.getSnapshot();
1785 result.
txCount = byFee_.size();
1791 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1792 result.
medFeeLevel = snapshot.escalationMultiplier;
1799TxQ::getTxRequiredFeeAndSeq(
1803 auto const account = (*tx)[sfAccount];
1807 auto const snapshot = feeMetrics_.getSnapshot();
1809 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1811 auto const sle = view.
read(keylet::account(account));
1813 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1814 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1816 mulDiv(fee, baseFee, baseLevel)
1829 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1831 if (accountIter == byAccount_.end() ||
1832 accountIter->second.transactions.empty())
1835 result.
reserve(accountIter->second.transactions.size());
1836 for (
auto const& tx : accountIter->second.transactions)
1850 result.
reserve(byFee_.size());
1852 for (
auto const& tx : byFee_)
1864 BOOST_ASSERT(
false);
1868 auto const metrics = getMetrics(*view);
1874 ret[jss::ledger_current_index] = view->info().seq;
1875 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1876 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1878 if (metrics.txQMaxSize)
1881 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1882 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1883 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1884 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1886 auto const baseFee = view->fees().base;
1890 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1891 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1897 drops[jss::base_fee] = to_string(baseFee);
1898 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1899 drops[jss::minimum_fee] = to_string(
toDrops(
1900 metrics.minProcessingFeeLevel,
1901 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1902 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1903 if (effectiveBaseFee &&
1904 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1906 drops[jss::open_ledger_fee] = to_string(openFee);
1917 auto const& section = config.
section(
"transaction_queue");
1922 "minimum_escalation_multiplier",
1926 "minimum_txn_in_ledger_standalone",
1930 if (
set(max,
"maximum_txn_in_ledger", section))
1934 Throw<std::runtime_error>(
1935 "The minimum number of low-fee transactions allowed "
1936 "per ledger (minimum_txn_in_ledger) exceeds "
1937 "the maximum number of low-fee transactions allowed per "
1938 "ledger (maximum_txn_in_ledger).");
1942 Throw<std::runtime_error>(
1943 "The minimum number of low-fee transactions allowed "
1944 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1945 "the maximum number of low-fee transactions allowed per "
1946 "ledger (maximum_txn_in_ledger).");
1959 "normal_consensus_increase_percent",
1969 "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,...