1#include <xrpld/app/misc/TxQ.h>
3#include <xrpld/app/ledger/OpenLedger.h>
4#include <xrpld/app/main/Application.h>
6#include <xrpl/basics/Log.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/basics/mulDiv.h>
9#include <xrpl/beast/utility/Zero.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/config/BasicConfig.h>
12#include <xrpl/config/Constants.h>
13#include <xrpl/json/json_value.h>
14#include <xrpl/ledger/ApplyView.h>
15#include <xrpl/ledger/ApplyViewImpl.h>
16#include <xrpl/ledger/OpenView.h>
17#include <xrpl/ledger/ReadView.h>
18#include <xrpl/ledger/helpers/SponsorHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/Indexes.h>
21#include <xrpl/protocol/Keylet.h>
22#include <xrpl/protocol/LedgerFormats.h>
23#include <xrpl/protocol/Protocol.h>
24#include <xrpl/protocol/RippleLedgerHash.h>
25#include <xrpl/protocol/SField.h>
26#include <xrpl/protocol/STTx.h>
27#include <xrpl/protocol/SeqProxy.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFormats.h>
30#include <xrpl/protocol/Units.h>
31#include <xrpl/protocol/XRPAmount.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/tx/apply.h>
34#include <xrpl/tx/applySteps.h>
36#include <boost/function/function_base.hpp>
62static std::expected<FeeLevel64, TER>
69 auto const [baseFee, effectiveFeePaid] = [&view, &tx, fee = *computedBaseFee]() {
70 XRPAmount const feePaid = tx[sfFee].xrp();
74 XRPAmount const mod = [&view, &tx, fee]() {
78 return def.signum() == 0 ?
XRPAmount{1} : def;
80 return std::pair{fee + mod, feePaid + mod};
83 XRPL_ASSERT(baseFee.signum() > 0,
"xrpl::getFeeLevelPaid : positive fee");
84 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
104 return mulDiv(level, 100 + increasePercent, 100)
118 auto const txBegin = view.
txs.
begin();
119 auto const txEnd = view.
txs.
end();
124 if (maybeFeeLevel.has_value())
131 JLOG(
j_.warn()) <<
"Unable to compute the fee level for a validated transaction "
132 << tx.first->getTransactionID() <<
" in ledger " << view.
header().
seq
138 JLOG((timeLeap ?
j_.warn() :
j_.debug()))
139 <<
"Ledger " << view.
header().
seq <<
" has " << size <<
" transactions. "
140 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
141 <<
". Expected transactions is currently " <<
txnsExpected_ <<
" and multiplier is "
163 auto const next = [&] {
183 if (feeLevels.
empty())
193 auto const count = feeLevels.
size();
195 (feeLevels[count / 2] + feeLevels[(count - 1) / 2] +
FeeLevel64{1}) / 2;
198 JLOG(
j_.debug()) <<
"Expected transactions updated to " <<
txnsExpected_
208 auto const current = view.
txCount();
215 if (current > target)
219 return mulDiv(multiplier, current * current, target * target)
244 return {
true, (x * (x + 1) * ((2 * x) + 1)) / 6};
248static_assert(sumOfFirstSquares(1).first);
249static_assert(sumOfFirstSquares(1).second == 1);
251static_assert(sumOfFirstSquares(2).first);
252static_assert(sumOfFirstSquares(2).second == 5);
254static_assert(sumOfFirstSquares(0x1FFFFF).first);
255static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul);
257static_assert(!sumOfFirstSquares(0x200000).first);
273 auto const current = view.
txCount() + extraCount;
277 auto const last = current + seriesSize - 1;
284 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
299 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
300 auto const totalFeeLevel =
301 mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target);
304 totalFeeLevel.has_value(), *totalFeeLevel};
330 XRPL_ASSERT(
pfResult,
"xrpl::TxQ::MaybeTx::apply : preflight result is set");
335 JLOG(j.
debug()) <<
"Queued transaction " <<
txID
336 <<
" rules or flags have changed. Flags from " <<
pfResult->flags <<
" to "
345 return doApply(pcresult, app, view);
357TxQ::TxQAccount::TxMap::const_iterator
362 auto sameOrPrevIter =
transactions.lower_bound(seqProx);
365 return sameOrPrevIter;
372 [[maybe_unused]]
auto const* txnPtr = &txn;
374 auto result =
transactions.emplace(seqProx, std::move(txn));
375 XRPL_ASSERT(result.second,
"xrpl::TxQ::TxQAccount::add : emplace succeeded");
376 XRPL_ASSERT(&result.first->second != txnPtr,
"xrpl::TxQ::TxQAccount::add : transaction moved");
378 return result.first->second;
399template <
size_t FillPercentage>
403 static_assert(FillPercentage > 0 && FillPercentage <= 100,
"Invalid fill percentage");
413 AccountMap::iterator
const& accountIter,
443 if (lastValid && *lastValid < view.
header().
seq +
setup_.minimumLastLedgerBuffer)
456 TxQAccount const& txQAcct = accountIter->second;
463 if (txSeqProx.isTicket())
472 if (txSeqProx != nextQueuable)
492TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter) -> FeeMultiSet::iterator_type
494 auto& txQAccount = byAccount_.at(candidateIter->account);
495 auto const seqProx = candidateIter->seqProxy;
496 auto const newCandidateIter = byFee_.erase(candidateIter);
500 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
501 XRPL_ASSERT(found,
"xrpl::TxQ::erase : account removed");
503 return newCandidateIter;
508 -> FeeMultiSet::iterator_type
510 auto& txQAccount =
byAccount_.at(candidateIter->account);
511 auto const accountIter = txQAccount.transactions.find(candidateIter->seqProxy);
513 accountIter != txQAccount.transactions.end(),
"xrpl::TxQ::eraseAndAdvance : account found");
519 candidateIter->seqProxy.isTicket() || accountIter == txQAccount.transactions.begin(),
520 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
522 byFee_.iterator_to(accountIter->second) == candidateIter,
523 "xrpl::TxQ::eraseAndAdvance : found in byFee");
524 auto const accountNextIter =
std::next(accountIter);
528 auto const feeNextIter =
std::next(candidateIter);
529 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
530 accountNextIter->first > candidateIter->seqProxy &&
531 (feeNextIter ==
byFee_.end() ||
byFee_.value_comp()(accountNextIter->second, *feeNextIter));
533 auto const candidateNextIter =
byFee_.erase(candidateIter);
534 txQAccount.transactions.erase(accountIter);
536 return useAccountNext ?
byFee_.iterator_to(accountNextIter->second) : candidateNextIter;
542 TxQ::TxQAccount::TxMap::const_iterator begin,
543 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
545 for (
auto it = begin; it != end; ++it)
549 return txQAccount.transactions.erase(begin, end);
557 TxQ::AccountMap::iterator
const& accountIter,
558 TxQAccount::TxMap::iterator beginTxIter,
568 beginTxIter != accountIter->second.transactions.end(),
569 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
573 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
576 auto const requiredTotalFeeLevel =
581 if (!requiredTotalFeeLevel.first)
585 beginTxIter, endTxIter, feeLevelPaid, [](
auto const& total,
auto const& txn) {
586 return total + txn.second.feeLevel;
590 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
595 for (
auto it = beginTxIter; it != endTxIter; ++it)
597 auto txResult = it->second.apply(app, view, j);
602 --it->second.retriesRemaining;
603 it->second.lastResult = txResult.ter;
624 if (!txResult.applied)
627 return {txResult.ter,
false};
632 auto const txResult =
doApply(
preclaim(pfResult, app, view), app, view);
634 if (txResult.applied)
638 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
640 if (endTxIter != accountIter->second.transactions.end() && endTxIter->first == tSeqProx)
771 auto const pfResult =
preflight(app, view.
rules(), *tx, flags, j);
773 return {pfResult.ter,
false};
778 return *directApplied;
791 auto const account = (*tx)[sfAccount];
793 auto const sleAccount = view.
read(accountKey);
799 SeqProxy const txSeqProx = tx->getSeqProxy();
818 bool const accountIsInQueue = accountIter !=
byAccount_.end();
830 TxIter(TxQAccount::TxMap::iterator first, TxQAccount::TxMap::iterator end)
831 : first(first), end(end)
835 TxQAccount::TxMap::iterator first;
836 TxQAccount::TxMap::iterator end;
841 if (!accountIsInQueue)
846 auto const firstIter = acctTxs.
lower_bound(acctSeqProx);
848 if (firstIter == acctTxs.
end())
855 return {TxIter{firstIter, acctTxs.
end()}};
858 auto const acctTxCount{!txIter ? 0 :
std::distance(txIter->first, txIter->end)};
864 auto const transactionID = tx->getTransactionID();
865 if (pfResult.consequences.isBlocker())
871 JLOG(
j_.trace()) <<
"Rejecting blocker transaction " << transactionID
872 <<
". Account has other queued transactions.";
876 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
879 JLOG(
j_.trace()) <<
"Rejecting blocker transaction " << transactionID
880 <<
". Blocker does not replace lone queued transaction.";
887 auto replacedTxIter = [accountIsInQueue,
890 if (accountIsInQueue)
902 auto const metricsSnapshot =
feeMetrics_.getSnapshot();
906 if (!computedFeeLevelPaid.has_value())
908 return {computedFeeLevelPaid.error(),
false};
910 FeeLevel64 const feeLevelPaid = *computedFeeLevelPaid;
923 if (acctTxCount == 1 && txIter->first->second.consequences().isBlocker() &&
924 (txIter->first->first != txSeqProx))
939 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
940 auto requiredRetryLevel =
941 increase(existingIter->second.feeLevel,
setup_.retrySequencePercent);
942 JLOG(
j_.trace()) <<
"Found transaction in queue for account " << account <<
" with "
943 << txSeqProx <<
" new txn fee level is " << feeLevelPaid
944 <<
", old txn fee level is " << existingIter->second.feeLevel
945 <<
", new txn needs fee level of " << requiredRetryLevel;
946 if (feeLevelPaid > requiredRetryLevel)
951 JLOG(
j_.trace()) <<
"Removing transaction from queue " << existingIter->second.txID
952 <<
" in favor of " << transactionID;
957 JLOG(
j_.trace()) <<
"Ignoring transaction " << transactionID
958 <<
" in favor of queued " << existingIter->second.txID;
969 MultiTxn(
OpenView& view,
ApplyFlags flags) : applyView(&view, flags), openView(&applyView)
976 if (acctTxCount == 0)
981 if (txSeqProx.
isSeq())
983 if (acctSeqProx > txSeqProx)
985 if (acctSeqProx < txSeqProx)
994 TxQAccount const& txQAcct = accountIter->second;
996 if (acctSeqProx > txSeqProx)
1005 bool requiresMultiTxn =
false;
1006 if (acctTxCount > 1 || !replacedTxIter)
1011 canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1013 return {ter,
false};
1015 requiresMultiTxn =
true;
1018 if (requiresMultiTxn)
1032 auto const prevIter = txQAcct.
getPrevTx(txSeqProx);
1042 XRPL_ASSERT(prevIter != txIter->end,
"xrpl::TxQ::apply : not end");
1043 if (prevIter == txIter->end || txSeqProx < prevIter->first)
1047 if (txSeqProx.
isSeq())
1049 if (txSeqProx < acctSeqProx)
1053 if (txSeqProx > acctSeqProx)
1059 else if (!replacedTxIter)
1075 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
1080 if (iter->first != txSeqProx)
1082 totalFee += iter->second.consequences().fee();
1083 potentialSpend += iter->second.consequences().potentialSpend();
1085 else if (
std::next(iter) != txIter->end)
1090 totalFee += pfResult.consequences.fee();
1091 potentialSpend += pfResult.consequences.potentialSpend();
1125 auto const balance = (*sleAccount)[sfBalance].xrp();
1144 auto const base = view.
fees().
base;
1145 if (totalFee >= balance || (reserve > 10 * base && totalFee >= reserve))
1148 JLOG(
j_.trace()) <<
"Ignoring transaction " << transactionID
1149 <<
". Total fees in flight too high.";
1154 multiTxn.
emplace(view, flags);
1156 auto const sleBump = multiTxn->applyView.peek(accountKey);
1163 auto const potentialTotalSpend =
1167 (potentialTotalSpend ==
XRPAmount{0} && multiTxn->applyView.fees().base == 0),
1168 "xrpl::TxQ::apply : total spend check");
1169 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1175 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1192 auto const pcresult =
preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1193 if (!pcresult.likelyToClaimFee)
1194 return {pcresult.ter,
false};
1197 XRPL_ASSERT(feeLevelPaid >=
kBaseLevel,
"xrpl::TxQ::apply : minimum fee");
1199 JLOG(
j_.trace()) <<
"Transaction " << transactionID <<
" from account " << account
1200 <<
" has fee level of " << feeLevelPaid <<
" needs at least "
1201 << requiredFeeLevel <<
" to get in the open ledger, which has "
1202 << view.
txCount() <<
" entries.";
1223 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
kBaseLevel)
1241 sandbox.
apply(view);
1252 TER const ter{
canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1256 JLOG(
j_.trace()) <<
"Transaction " << transactionID <<
" cannot be held";
1257 return {ter,
false};
1264 if (!replacedTxIter &&
isFull())
1266 auto lastRIter =
byFee_.rbegin();
1267 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1271 if (lastRIter ==
byFee_.rend())
1279 JLOG(
j_.info()) <<
"Queue is full, and transaction " << transactionID
1280 <<
" would kick a transaction from the same account (" << account
1281 <<
") out of the queue.";
1284 auto const& endAccount =
byAccount_.at(lastRIter->account);
1285 auto endEffectiveFeeLevel = [&]() {
1289 if (lastRIter->feeLevel > feeLevelPaid || endAccount.transactions.size() == 1)
1290 return lastRIter->feeLevel;
1294 endAccount.transactions.begin(),
1295 endAccount.transactions.end(),
1299 auto next = txn.second.feeLevel / endAccount.transactions.size();
1300 auto mod = txn.second.feeLevel % endAccount.transactions.size();
1301 if (total.first >= kMax - next || total.second >= kMax - mod)
1302 return {kMax, FeeLevel64{0}};
1304 return {total.first + next, total.second + mod};
1306 return endTotal.first + endTotal.second / endAccount.transactions.size();
1308 if (feeLevelPaid > endEffectiveFeeLevel)
1312 auto dropRIter = endAccount.transactions.rbegin();
1314 dropRIter->second.account == lastRIter->account,
1315 "xrpl::TxQ::apply : cheapest transaction found");
1316 JLOG(
j_.info()) <<
"Removing last item of account " << lastRIter->account
1317 <<
" from queue with average fee of " << endEffectiveFeeLevel
1318 <<
" in favor of " << transactionID <<
" with fee of " << feeLevelPaid;
1323 JLOG(
j_.info()) <<
"Queue is full, and transaction " << transactionID
1324 <<
" fee is lower than end item's account average fee";
1332 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1335 if (!accountIsInQueue)
1338 [[maybe_unused]]
bool created =
false;
1339 std::tie(accountIter, created) = byAccount_.emplace(account, TxQAccount(tx));
1340 XRPL_ASSERT(created,
"xrpl::TxQ::apply : account created");
1350 auto& candidate = accountIter->second.add({tx, transactionID, feeLevelPaid, flags, pfResult});
1353 byFee_.insert(candidate);
1354 JLOG(j_.debug()) <<
"Added transaction " << candidate.txID <<
" with result "
1356 << (accountIsInQueue ?
"existing" :
"new") <<
" account " << candidate.account
1358 <<
" Flags: " << flags;
1389 for (
auto candidateIter =
byFee_.begin(); candidateIter !=
byFee_.end();)
1391 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1393 byAccount_.at(candidateIter->account).dropPenalty =
true;
1394 candidateIter =
erase(candidateIter);
1406 if (txQAccountIter->second.empty())
1408 txQAccountIter =
byAccount_.erase(txQAccountIter);
1455 auto ledgerChanged =
false;
1459 auto const metricsSnapshot =
feeMetrics_.getSnapshot();
1461 for (
auto candidateIter =
byFee_.begin(); candidateIter !=
byFee_.end();)
1463 auto& account =
byAccount_.at(candidateIter->account);
1464 auto const beginIter = account.transactions.begin();
1465 if (candidateIter->seqProxy.isSeq() && candidateIter->seqProxy > beginIter->first)
1470 JLOG(
j_.trace()) <<
"Skipping queued transaction " << candidateIter->txID
1471 <<
" from account " << candidateIter->account
1472 <<
" as it is not the first.";
1477 auto const feeLevelPaid = candidateIter->feeLevel;
1478 JLOG(
j_.trace()) <<
"Queued transaction " << candidateIter->txID <<
" from account "
1479 << candidateIter->account <<
" has fee level of " << feeLevelPaid
1480 <<
" needs at least " << requiredFeeLevel;
1481 if (feeLevelPaid >= requiredFeeLevel)
1483 JLOG(
j_.trace()) <<
"Applying queued transaction " << candidateIter->txID
1484 <<
" to open ledger.";
1486 auto const [txnResult, didApply, _metadata] = candidateIter->apply(app, view,
j_);
1491 JLOG(
j_.debug()) <<
"Queued transaction " << candidateIter->txID
1492 <<
" applied successfully with " <<
transToken(txnResult)
1493 <<
". Remove from queue.";
1496 ledgerChanged =
true;
1500 candidateIter->retriesRemaining <= 0)
1502 if (candidateIter->retriesRemaining <= 0)
1504 account.retryPenalty =
true;
1508 account.dropPenalty =
true;
1510 JLOG(
j_.debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1511 <<
transToken(txnResult) <<
". Remove from queue.";
1516 JLOG(
j_.debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1517 <<
transToken(txnResult) <<
". Leave in queue."
1518 <<
" Applied: " << didApply <<
". Flags: " << candidateIter->flags;
1519 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1521 candidateIter->retriesRemaining = 1;
1525 --candidateIter->retriesRemaining;
1527 candidateIter->lastResult = txnResult;
1528 if (account.dropPenalty && account.transactions.size() > 1 &&
isFull<95>())
1533 if (candidateIter->seqProxy.isTicket())
1538 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1540 <<
". Removing ticketed tx from account " << account.account;
1549 auto dropRIter = account.transactions.rbegin();
1551 dropRIter->second.account == candidateIter->account,
1552 "xrpl::TxQ::accept : account check");
1555 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1557 <<
". Removing last item from account " << account.account;
1558 auto endIter =
byFee_.iterator_to(dropRIter->second);
1559 if (endIter != candidateIter)
1583 JLOG(
j_.warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1590 [[maybe_unused]]
auto const startingSize =
byFee_.size();
1605 for (
auto& [_, candidate] : account.transactions)
1607 byFee_.insert(candidate);
1610 XRPL_ASSERT(
byFee_.size() == startingSize,
"xrpl::TxQ::accept : byFee size match");
1612 return ledgerChanged;
1636 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1642 auto const accountIter =
byAccount_.find((*sleAccount)[sfAccount]);
1643 if (accountIter ==
byAccount_.end() || accountIter->second.transactions.empty())
1653 if (txIter == acctTxs.
end() || !txIter->first.isSeq() || txIter->first != acctSeqProx)
1665 SeqProxy attempt = txIter->second.consequences().followingSeq();
1666 while (++txIter != acctTxs.
cend())
1668 if (attempt < txIter->first)
1671 attempt = txIter->second.consequences().followingSeq();
1694 auto const account = (*tx)[sfAccount];
1702 SeqProxy const txSeqProx = tx->getSeqProxy();
1706 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1709 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1719 if (!computedFeeLevelPaid.has_value())
1721 return ApplyResult{computedFeeLevelPaid.error(),
false};
1723 FeeLevel64 const feeLevelPaid = *computedFeeLevelPaid;
1725 if (feeLevelPaid >= requiredFeeLevel)
1728 auto const transactionID = tx->getTransactionID();
1729 JLOG(
j_.trace()) <<
"Applying transaction " << transactionID <<
" to open ledger.";
1731 auto const [txnResult, didApply, metadata] =
xrpl::apply(app, view, *tx, flags, j);
1733 JLOG(
j_.trace()) <<
"New transaction " << transactionID
1734 << (didApply ?
" applied successfully with " :
" failed with ")
1743 auto const accountIter =
byAccount_.find(account);
1754 return ApplyResult{txnResult, didApply, metadata};
1764 if (replacedTxIter && tx)
1768 auto deleteIter =
byFee_.iterator_to((*replacedTxIter)->second);
1769 XRPL_ASSERT(deleteIter !=
byFee_.end(),
"xrpl::TxQ::removeFromByFee : found in byFee");
1771 &(*replacedTxIter)->second == &*deleteIter,
1772 "xrpl::TxQ::removeFromByFee : matching transaction");
1774 deleteIter->seqProxy == tx->getSeqProxy(),
1775 "xrpl::TxQ::removeFromByFee : matching sequence");
1777 deleteIter->account == (*tx)[sfAccount],
1778 "xrpl::TxQ::removeFromByFee : matching account");
1782 return std::nullopt;
1801 result.
medFeeLevel = snapshot.escalationMultiplier;
1807std::expected<TxQ::FeeAndSeq, TER>
1810 auto const account = (*tx)[sfAccount];
1816 if (!maybeBaseFee.has_value())
1820 auto const baseFee = *maybeBaseFee;
1825 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1830 .accountSeq = accountSeq,
1831 .availableSeq = availableSeq};
1841 AccountMap::const_iterator
const accountIter{
byAccount_.find(account)};
1843 if (accountIter ==
byAccount_.end() || accountIter->second.transactions.empty())
1846 result.
reserve(accountIter->second.transactions.size());
1847 for (
auto const& tx : accountIter->second.transactions)
1863 for (
auto const& tx :
byFee_)
1875 BOOST_ASSERT(
false);
1885 ret[jss::ledger_current_index] = view->header().seq;
1897 auto const baseFee = view->fees().base;
1901 auto const effectiveBaseFee = [&baseFee, &
metrics]() {
1902 if (!baseFee &&
metrics.openLedgerFeeLevel !=
metrics.referenceFeeLevel)
1908 drops[jss::base_fee] =
to_string(baseFee);
1911 metrics.minProcessingFeeLevel,
1912 metrics.txCount >=
metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1913 auto openFee =
toDrops(
metrics.openLedgerFeeLevel, effectiveBaseFee);
1914 if (effectiveBaseFee &&
toFeeLevel(openFee, effectiveBaseFee) <
metrics.openLedgerFeeLevel)
1916 drops[jss::open_ledger_fee] =
to_string(openFee);
1941 "The minimum number of low-fee transactions allowed "
1942 "per ledger (minimum_txn_in_ledger) exceeds "
1943 "the maximum number of low-fee transactions allowed per "
1944 "ledger (maximum_txn_in_ledger).");
1949 "The minimum number of low-fee transactions allowed "
1950 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1951 "the maximum number of low-fee transactions allowed per "
1952 "ledger (maximum_txn_in_ledger).");
A generic endpoint for log messages.
Editable, discardable view that can build metadata for one tx.
Section & section(std::string const &name)
Returns the section with the given name.
std::shared_ptr< OpenView const > current() const
Returns a view to the current open ledger.
Writable ledger view that accumulates state and tx changes.
std::size_t txCount() const
Return the number of tx inserted since creation.
Fees const & fees() const override
Returns the fees for the base ledger.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
LedgerHeader const & header() const override
Returns information about the ledger.
void apply(TxsRawView &to) const
Apply changes.
Rules const & rules() const override
Returns the tx processing rules.
bool exists(Keylet const &k) const override
Determine if a state item exists.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
std::shared_ptr< STLedgerEntry const > const & ConstRef
std::uint32_t getFieldU32(SField const &field) const
bool isFieldPresent(SField const &field) const
SeqProxy getSeqProxy() const
TxType getTxnType() const
A type that represents either a sequence value or a ticket value.
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
constexpr bool isTicket() const
constexpr std::uint32_t value() const
constexpr bool isSeq() const
virtual OpenLedger & getOpenLedger()=0
std::size_t txnsExpected_
Number of transactions expected per ledger.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "workstowards".
static FeeLevel64 scaleFeeLevel(Snapshot const &snapshot, OpenView const &view)
Use the number of transactions in the current open ledger to compute the fee level a transaction must...
beast::Journal const j_
Journal.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
std::size_t update(Application &app, ReadView const &view, bool timeLeap, TxQ::Setup const &setup)
Updates fee metrics based on the transactions in the ReadView for use in fee escalation calculations.
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
static std::pair< bool, FeeLevel64 > escalatedSeriesFeeLevel(Snapshot const &snapshot, OpenView const &view, std::size_t extraCount, std::size_t seriesSize)
Computes the total fee level for all transactions in a series.
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Represents a transaction in the queue which may be applied later to the open ledger.
static LedgerHash parentHashComp
The hash of the parent ledger.
std::optional< LedgerIndex > const lastValid
Expiration ledger for the transaction (sfLastLedgerSequence field).
TxID const txID
Transaction ID.
FeeLevel64 const feeLevel
Computed fee level that the transaction will pay.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfResult)
Constructor.
ApplyFlags const flags
Flags provided to apply.
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
std::shared_ptr< STTx const > txn
The complete transaction.
static constexpr int kRetriesAllowed
Starting retry count for newly queued transactions.
AccountID const account
Account submitting the transaction.
std::optional< PreflightResult const > pfResult
Cached result of the preflight operation.
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
TxMap transactions
Sequence number will be used as the key.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
AccountID const account
The account.
std::map< SeqProxy, MaybeTx > TxMap
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
TER canBeHeld(STTx const &, ApplyFlags const, OpenView const &, SLE::ConstRef sleAccount, AccountMap::iterator const &, std::optional< TxQAccount::TxMap::iterator > const &, std::scoped_lock< std::mutex > const &lock)
Checks if the indicated transaction fits the conditions for being stored in the queue.
json::Value doRPC(Application &app) const
Summarize current fee metrics for the fee RPC command.
TxQ(Setup const &setup, beast::Journal j)
Constructor.
FeeMetrics feeMetrics_
Tracks the current state of the queue.
SeqProxy nextQueuableSeq(SLE::ConstRef sleAccount) const
Return the next sequence that would go in the TxQ for an account.
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
std::expected< FeeAndSeq, TER > getTxRequiredFeeAndSeq(OpenView const &view, std::shared_ptr< STTx const > const &tx) const
Returns minimum required fee for tx and two sequences: first valid sequence for this account in curre...
void processClosedLedger(Application &app, ReadView const &view, bool timeLeap)
Update fee metrics and clean up the queue in preparation for the next ledger.
std::vector< TxDetails > getAccountTxs(AccountID const &account) const
Returns information about the transactions currently in the queue for the account.
bool isFull() const
Is the queue at least fillPercentage full?
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type)
Erase and return the next entry for the account (if fee level is higher), or next entry in byFee_ (lo...
ApplyResult tryClearAccountQueueUpThruTx(Application &app, OpenView &view, STTx const &tx, AccountMap::iterator const &accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const &pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, beast::Journal j)
All-or-nothing attempt to try to apply the queued txs for accountIter up to and including tx.
static FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::scoped_lock< std::mutex > const &lock)
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.
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
virtual ~TxQ()
Destructor.
SeqProxy nextQueuableSeqImpl(SLE::ConstRef sleAccount, std::scoped_lock< std::mutex > const &) const
bool accept(Application &app, OpenView &view)
Fill the new open ledger with transactions from the queue.
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
std::vector< TxDetails > getTxs() const
Returns information about all transactions currently in the queue.
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
std::optional< TxQAccount::TxMap::iterator > removeFromByFee(std::optional< TxQAccount::TxMap::iterator > const &replacedTxIter, std::shared_ptr< STTx const > const &tx)
LedgerHash parentHash_
parentHash_ used for logging only
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level).
static constexpr FeeLevel64 kBaseLevel
Fee level for single-signed reference transaction.
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
Setup const setup_
Setup parameters used to control the behavior of the queue.
T emplace_back(T... args)
@ Object
object value (collection of name/value pairs).
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
PreflightResult preflight(ServiceRegistry ®istry, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
PreclaimResult preclaim(PreflightResult const &preflightResult, ServiceRegistry ®istry, OpenView const &view)
Gate a transaction based on static ledger information.
std::expected< XRPAmount, TER > calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
ApplyResult apply(ServiceRegistry ®istry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
UInt256 TxID
A transaction identifier.
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
std::string transToken(TER code)
std::string to_string(BaseUInt< Bits, Tag > const &a)
static std::expected< FeeLevel64, TER > getFeeLevelPaid(ReadView const &view, STTx const &tx)
Compute the fee level that a transaction pays.
bool isTefFailure(TER x) noexcept
bool isFeeSponsored(STTx const &tx)
Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set).
FeeLevel< std::uint64_t > FeeLevel64
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
constexpr auto kMuldivMax
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
constexpr struct xrpl::OpenLedgerT kOpenLedger
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry ®istry, OpenView &view)
Apply a prechecked transaction to an OpenView.
TxQ::Setup setupTxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
bool isTemMalformed(TER x) noexcept
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
XRPAmount reserve
Minimum XRP an account must hold to exist on the ledger.
XRPAmount base
Cost of a reference transaction in drops.
A pair of SHAMap key and LedgerEntryType.
static constexpr auto kRetrySequencePercent
static constexpr auto kMinimumEscalationMultiplier
static constexpr auto kMaximumTxnInLedger
static constexpr auto kSlowConsensusDecreasePercent
static constexpr auto kMinimumLastLedgerBuffer
static constexpr auto kMaximumTxnPerAccount
static constexpr auto kTargetTxnInLedger
static constexpr auto kMinimumQueueSize
static constexpr auto kMinimumTxnInLedger
static constexpr auto kNormalConsensusIncreasePercent
static constexpr auto kLedgersInQueue
static constexpr auto kMinimumTxnInLedgerStandalone
Describes the results of the preflight check.
static constexpr auto kTransactionQueue
Snapshot of the externally relevant FeeMetrics fields at any given time.
std::size_t const txnsExpected
FeeLevel64 const escalationMultiplier
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
std::size_t txCount
Number of transactions in the queue.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txInLedger
Number of transactions currently in the open ledger.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
std::size_t txPerLedger
Number of transactions expected per ledger.
Structure used to customize TxQ behavior.
bool standAlone
Use standalone mode behavior.
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "workstowards".
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::size_t queueSizeMin
The smallest limit the queue is allowed.
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...