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")
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");
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)
741 auto const pfresult =
preflight(app, view.
rules(), *tx, flags, j);
743 return {pfresult.ter,
false};
748 return *directApplied;
758 auto const account = (*tx)[sfAccount];
760 auto const sleAccount = view.
read(accountKey);
766 SeqProxy const txSeqProx = tx->getSeqProxy();
784 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
797 TxQAccount::TxMap::iterator first_,
798 TxQAccount::TxMap::iterator end_)
799 : first(first_), end(end_)
803 TxQAccount::TxMap::iterator first;
804 TxQAccount::TxMap::iterator end;
811 if (!accountIsInQueue)
816 TxQAccount::TxMap::iterator
const firstIter =
819 if (firstIter == acctTxs.
end())
824 return {TxIter{firstIter, acctTxs.
end()}};
827 auto const acctTxCount{
835 if (pfresult.consequences.isBlocker())
843 <<
". Account has other queued transactions.";
846 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
851 <<
". Blocker does not replace lone queued transaction.";
858 auto replacedTxIter = [accountIsInQueue, &accountIter, txSeqProx]()
860 if (accountIsInQueue)
874 auto const requiredFeeLevel =
886 if (acctTxCount == 1 &&
887 txIter->first->second.consequences().isBlocker() &&
888 (txIter->first->first != txSeqProx))
902 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
906 <<
"Found transaction in queue for account " << account
907 <<
" with " << txSeqProx <<
" new txn fee level is "
908 << feeLevelPaid <<
", old txn fee level is "
909 << existingIter->second.feeLevel
910 <<
", new txn needs fee level of " << requiredRetryLevel;
911 if (feeLevelPaid > requiredRetryLevel)
916 JLOG(
j_.
trace()) <<
"Removing transaction from queue "
917 << existingIter->second.txID <<
" in favor of "
925 <<
" in favor of queued " << existingIter->second.txID;
937 : applyView(&view, flags), openView(&applyView)
944 if (acctTxCount == 0)
949 if (txSeqProx.
isSeq())
951 if (acctSeqProx > txSeqProx)
953 if (acctSeqProx < txSeqProx)
962 TxQAccount const& txQAcct = accountIter->second;
964 if (acctSeqProx > txSeqProx)
973 bool requiresMultiTxn =
false;
974 if (acctTxCount > 1 || !replacedTxIter)
989 requiresMultiTxn =
true;
992 if (requiresMultiTxn)
1006 TxQAccount::TxMap::const_iterator
const prevIter =
1016 prevIter != txIter->end,
"ripple::TxQ::apply : not end");
1017 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1021 if (txSeqProx.
isSeq())
1023 if (txSeqProx < acctSeqProx)
1025 else if (txSeqProx > acctSeqProx)
1029 else if (!replacedTxIter)
1036 if (txSeqProx.
isSeq() &&
1046 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1051 if (iter->first != txSeqProx)
1053 totalFee += iter->second.consequences().fee();
1055 iter->second.consequences().potentialSpend();
1057 else if (
std::next(iter) != txIter->end)
1062 totalFee += pfresult.consequences.fee();
1063 potentialSpend += pfresult.consequences.potentialSpend();
1096 auto const balance = (*sleAccount)[sfBalance].xrp();
1115 auto const base = view.
fees().
base;
1116 if (totalFee >= balance ||
1117 (reserve > 10 * base && totalFee >= reserve))
1121 <<
". Total fees in flight too high.";
1126 multiTxn.
emplace(view, flags);
1128 auto const sleBump = multiTxn->applyView.peek(accountKey);
1135 auto const potentialTotalSpend = totalFee +
1140 multiTxn->applyView.fees().base == 0),
1141 "ripple::TxQ::apply : total spend check");
1142 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1148 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1165 auto const pcresult =
1166 preclaim(pfresult, app, multiTxn ? multiTxn->openView : view);
1167 if (!pcresult.likelyToClaimFee)
1168 return {pcresult.
ter,
false};
1171 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"ripple::TxQ::apply : minimum fee");
1174 << account <<
" has fee level of " << feeLevelPaid
1175 <<
" needs at least " << requiredFeeLevel
1176 <<
" to get in the open ledger, which has "
1177 << view.
txCount() <<
" entries.";
1198 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1216 sandbox.
apply(view);
1228 *tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1234 return {ter,
false};
1241 if (!replacedTxIter &&
isFull())
1243 auto lastRIter =
byFee_.rbegin();
1244 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1248 if (lastRIter ==
byFee_.rend())
1258 <<
" would kick a transaction from the same account ("
1259 << account <<
") out of the queue.";
1262 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1263 auto endEffectiveFeeLevel = [&]() {
1267 if (lastRIter->feeLevel > feeLevelPaid ||
1268 endAccount.transactions.size() == 1)
1269 return lastRIter->feeLevel;
1273 endAccount.transactions.begin(),
1274 endAccount.transactions.end(),
1276 [&](
auto const& total,
1280 txn.second.feeLevel / endAccount.transactions.size();
1282 txn.second.feeLevel % endAccount.transactions.size();
1283 if (total.first >= max - next || total.second >= max - mod)
1284 return {max, FeeLevel64{0}};
1286 return {total.first + next, total.second + mod};
1288 return endTotal.first +
1289 endTotal.second / endAccount.transactions.size();
1291 if (feeLevelPaid > endEffectiveFeeLevel)
1295 auto dropRIter = endAccount.transactions.rbegin();
1297 dropRIter->second.account == lastRIter->account,
1298 "ripple::TxQ::apply : cheapest transaction found");
1300 <<
"Removing last item of account " << lastRIter->account
1301 <<
" from queue with average fee of " << endEffectiveFeeLevel
1310 <<
" fee is lower than end item's account average fee";
1318 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1321 if (!accountIsInQueue)
1324 [[maybe_unused]]
bool created =
false;
1326 byAccount_.emplace(account, TxQAccount(tx));
1327 XRPL_ASSERT(created,
"ripple::TxQ::apply : account created");
1337 auto& candidate = accountIter->second.add(
1341 byFee_.insert(candidate);
1342 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID
1343 <<
" with result " <<
transToken(pfresult.ter) <<
" from "
1344 << (accountIsInQueue ?
"existing" :
"new") <<
" account "
1345 << candidate.account <<
" to queue."
1346 <<
" Flags: " << flags;
1368 feeMetrics_.update(app, view, timeLeap, setup_);
1369 auto const& snapshot = feeMetrics_.getSnapshot();
1371 auto ledgerSeq = view.
info().
seq;
1375 snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1378 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1380 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1382 byAccount_.at(candidateIter->account).dropPenalty =
true;
1383 candidateIter =
erase(candidateIter);
1393 for (
auto txQAccountIter = byAccount_.begin();
1394 txQAccountIter != byAccount_.end();)
1396 if (txQAccountIter->second.empty())
1397 txQAccountIter = byAccount_.erase(txQAccountIter);
1441 auto ledgerChanged =
false;
1445 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1447 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1449 auto& account = byAccount_.at(candidateIter->account);
1450 auto const beginIter = account.transactions.begin();
1451 if (candidateIter->seqProxy.isSeq() &&
1452 candidateIter->seqProxy > beginIter->first)
1458 <<
"Skipping queued transaction " << candidateIter->txID
1459 <<
" from account " << candidateIter->account
1460 <<
" as it is not the first.";
1464 auto const requiredFeeLevel =
1465 getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1466 auto const feeLevelPaid = candidateIter->feeLevel;
1467 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID
1468 <<
" from account " << candidateIter->account
1469 <<
" has fee level of " << feeLevelPaid
1470 <<
" needs at least " << requiredFeeLevel;
1471 if (feeLevelPaid >= requiredFeeLevel)
1473 JLOG(j_.
trace()) <<
"Applying queued transaction "
1474 << candidateIter->txID <<
" to open ledger.";
1476 auto const [txnResult, didApply, _metadata] =
1477 candidateIter->apply(app, view, j_);
1483 <<
"Queued transaction " << candidateIter->txID
1484 <<
" applied successfully with " <<
transToken(txnResult)
1485 <<
". Remove from queue.";
1487 candidateIter = eraseAndAdvance(candidateIter);
1488 ledgerChanged =
true;
1492 candidateIter->retriesRemaining <= 0)
1494 if (candidateIter->retriesRemaining <= 0)
1495 account.retryPenalty =
true;
1497 account.dropPenalty =
true;
1498 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1500 <<
". Remove from queue.";
1501 candidateIter = eraseAndAdvance(candidateIter);
1505 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1507 <<
". Leave in queue."
1508 <<
" Applied: " << didApply
1509 <<
". Flags: " << candidateIter->flags;
1510 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1511 candidateIter->retriesRemaining = 1;
1513 --candidateIter->retriesRemaining;
1514 candidateIter->lastResult = txnResult;
1515 if (account.dropPenalty && account.transactions.size() > 1 &&
1521 if (candidateIter->seqProxy.isTicket())
1526 <<
"Queue is nearly full, and transaction "
1527 << candidateIter->txID <<
" failed with "
1529 <<
". Removing ticketed tx from account "
1531 candidateIter = eraseAndAdvance(candidateIter);
1539 auto dropRIter = account.transactions.rbegin();
1541 dropRIter->second.account == candidateIter->account,
1542 "ripple::TxQ::accept : account check");
1545 <<
"Queue is nearly full, and transaction "
1546 << candidateIter->txID <<
" failed with "
1548 <<
". Removing last item from account "
1550 auto endIter = byFee_.iterator_to(dropRIter->second);
1551 if (endIter != candidateIter)
1571 if (parentHash == parentHash_)
1572 JLOG(j_.
warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1574 parentHash_ = parentHash;
1576 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1587 MaybeTx::parentHashComp = parentHash;
1589 for (
auto& [_, account] : byAccount_)
1591 for (
auto& [_, candidate] : account.transactions)
1593 byFee_.insert(candidate);
1597 byFee_.size() == startingSize,
1598 "ripple::TxQ::accept : byFee size match");
1600 return ledgerChanged;
1610 return nextQueuableSeqImpl(sleAccount, lock);
1620TxQ::nextQueuableSeqImpl(
1626 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1627 return SeqProxy::sequence(0);
1629 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1632 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1633 if (accountIter == byAccount_.end() ||
1634 accountIter->second.transactions.empty())
1642 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1644 if (txIter == acctTxs.
end() || !txIter->first.isSeq() ||
1645 txIter->first != acctSeqProx)
1655 SeqProxy attempt = txIter->second.consequences().followingSeq();
1656 while (++txIter != acctTxs.
cend())
1658 if (attempt < txIter->first)
1661 attempt = txIter->second.consequences().followingSeq();
1667TxQ::getRequiredFeeLevel(
1673 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1684 auto const account = (*tx)[sfAccount];
1685 auto const sleAccount = view.
read(keylet::account(account));
1691 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1692 SeqProxy const txSeqProx = tx->getSeqProxy();
1696 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1699 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1701 return getRequiredFeeLevel(
1702 view, flags, feeMetrics_.getSnapshot(), lock);
1709 if (feeLevelPaid >= requiredFeeLevel)
1714 <<
" to open ledger.";
1716 auto const [txnResult, didApply, metadata] =
1720 << (didApply ?
" applied successfully with "
1730 AccountMap::iterator accountIter = byAccount_.find(account);
1731 if (accountIter != byAccount_.end())
1734 if (
auto const existingIter =
1738 removeFromByFee(existingIter, tx);
1742 return ApplyResult{txnResult, didApply, metadata};
1748TxQ::removeFromByFee(
1752 if (replacedTxIter && tx)
1756 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1758 deleteIter != byFee_.end(),
1759 "ripple::TxQ::removeFromByFee : found in byFee");
1761 &(*replacedTxIter)->second == &*deleteIter,
1762 "ripple::TxQ::removeFromByFee : matching transaction");
1764 deleteIter->seqProxy == tx->getSeqProxy(),
1765 "ripple::TxQ::removeFromByFee : matching sequence");
1767 deleteIter->account == (*tx)[sfAccount],
1768 "ripple::TxQ::removeFromByFee : matching account");
1782 auto const snapshot = feeMetrics_.getSnapshot();
1784 result.
txCount = byFee_.size();
1790 isFull() ? byFee_.rbegin()->feeLevel +
FeeLevel64{1} : baseLevel;
1791 result.
medFeeLevel = snapshot.escalationMultiplier;
1798TxQ::getTxRequiredFeeAndSeq(
1802 auto const account = (*tx)[sfAccount];
1806 auto const snapshot = feeMetrics_.getSnapshot();
1808 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1810 auto const sle = view.
read(keylet::account(account));
1812 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1813 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1815 mulDiv(fee, baseFee, baseLevel)
1828 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1830 if (accountIter == byAccount_.end() ||
1831 accountIter->second.transactions.empty())
1834 result.
reserve(accountIter->second.transactions.size());
1835 for (
auto const& tx : accountIter->second.transactions)
1849 result.
reserve(byFee_.size());
1851 for (
auto const& tx : byFee_)
1863 BOOST_ASSERT(
false);
1867 auto const metrics = getMetrics(*view);
1873 ret[jss::ledger_current_index] = view->info().seq;
1874 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1875 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1877 if (metrics.txQMaxSize)
1880 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1881 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1882 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1883 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1885 auto const baseFee = view->fees().base;
1889 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1890 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1896 drops[jss::base_fee] = to_string(baseFee);
1897 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1898 drops[jss::minimum_fee] = to_string(
toDrops(
1899 metrics.minProcessingFeeLevel,
1900 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1901 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1902 if (effectiveBaseFee &&
1903 toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1905 drops[jss::open_ledger_fee] = to_string(openFee);
1916 auto const& section = config.
section(
"transaction_queue");
1921 "minimum_escalation_multiplier",
1925 "minimum_txn_in_ledger_standalone",
1929 if (
set(max,
"maximum_txn_in_ledger", section))
1933 Throw<std::runtime_error>(
1934 "The minimum number of low-fee transactions allowed "
1935 "per ledger (minimum_txn_in_ledger) exceeds "
1936 "the maximum number of low-fee transactions allowed per "
1937 "ledger (maximum_txn_in_ledger).");
1941 Throw<std::runtime_error>(
1942 "The minimum number of low-fee transactions allowed "
1943 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1944 "the maximum number of low-fee transactions allowed per "
1945 "ledger (maximum_txn_in_ledger).");
1958 "normal_consensus_increase_percent",
1968 "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.
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.
constexpr struct ripple::open_ledger_t open_ledger
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)
bool isTefFailure(TER x) noexcept
std::string transToken(TER code)
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
bool isTesSuccess(TER x) noexcept
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
bool isTemMalformed(TER x) noexcept
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.
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.
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,...