1#include <xrpld/app/ledger/OpenLedger.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/TxQ.h>
5#include <xrpl/basics/mulDiv.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/jss.h>
8#include <xrpl/protocol/st.h>
9#include <xrpl/tx/apply.h>
22 auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
24 XRPAmount const feePaid = tx[sfFee].xrp();
28 XRPAmount const mod = [&view, &tx, baseFee]() {
32 return def.signum() == 0 ?
XRPAmount{1} : def;
34 return std::pair{baseFee + mod, feePaid + mod};
37 XRPL_ASSERT(baseFee.signum() > 0,
"xrpl::getFeeLevelPaid : positive fee");
38 if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
58 return mulDiv(level, 100 + increasePercent, 100)
72 auto const txBegin = view.
txs.
begin();
73 auto const txEnd = view.
txs.
end();
80 XRPL_ASSERT(size == feeLevels.
size(),
"xrpl::TxQ::FeeMetrics::update : fee levels size");
83 <<
"Ledger " << view.
header().
seq <<
" has " << size <<
" transactions. "
84 <<
"Ledgers are processing " << (timeLeap ?
"slowly" :
"as expected")
85 <<
". Expected transactions is currently " <<
txnsExpected_ <<
" and multiplier is "
107 auto const next = [&] {
135 (feeLevels[size / 2] + feeLevels[(size - 1) / 2] +
FeeLevel64{1}) / 2;
184 return {
true, (x * (x + 1) * (2 * x + 1)) / 6};
188static_assert(sumOfFirstSquares(1).first);
189static_assert(sumOfFirstSquares(1).second == 1);
191static_assert(sumOfFirstSquares(2).first);
192static_assert(sumOfFirstSquares(2).second == 5);
194static_assert(sumOfFirstSquares(0x1FFFFF).first,
"");
195static_assert(sumOfFirstSquares(0x1FFFFF).second == 0x2AAAA8AAAAB00000ul,
"");
197static_assert(!sumOfFirstSquares(0x200000).first,
"");
217 auto const last =
current + seriesSize - 1;
224 "xrpl::TxQ::FeeMetrics::escalatedSeriesFeeLevel : current over "
239 return {sumNlast.first,
FeeLevel64{sumNlast.second}};
240 auto const totalFeeLevel =
241 mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target);
243 return {totalFeeLevel.has_value(), *totalFeeLevel};
255 , feeLevel(feeLevel_)
257 , account(txn_->getAccountID(sfAccount))
259 , seqProxy(txn_->getSeqProxy())
261 , pfResult(pfResult_)
269 XRPL_ASSERT(pfResult,
"xrpl::TxQ::MaybeTx::apply : preflight result is set");
272 if (pfResult->rules != view.
rules() || pfResult->flags != flags)
274 JLOG(j.
debug()) <<
"Queued transaction " << txID
275 <<
" rules or flags have changed. Flags from " << pfResult->flags <<
" to "
278 pfResult.emplace(
preflight(app, view.
rules(), pfResult->tx, flags, pfResult->j));
281 auto pcresult =
preclaim(*pfResult, app, view);
283 return doApply(pcresult, app, view);
295TxQ::TxQAccount::TxMap::const_iterator
300 auto sameOrPrevIter = transactions.lower_bound(seqProx);
301 if (sameOrPrevIter != transactions.begin())
303 return sameOrPrevIter;
310 [[maybe_unused]]
auto const* txnPtr = &txn;
312 auto result = transactions.emplace(seqProx, std::move(txn));
313 XRPL_ASSERT(result.second,
"xrpl::TxQ::TxQAccount::add : emplace succeeded");
314 XRPL_ASSERT(&result.first->second != txnPtr,
"xrpl::TxQ::TxQAccount::add : transaction moved");
316 return result.first->second;
322 return transactions.erase(seqProx) != 0;
337template <
size_t fillPercentage>
341 static_assert(fillPercentage > 0 && fillPercentage <= 100,
"Invalid fill percentage");
351 AccountMap::iterator
const& accountIter,
381 TxQAccount const& txQAcct = accountIter->second;
388 if (txSeqProx.isTicket())
397 if (txSeqProx != nextQueuable)
417TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter) -> FeeMultiSet::iterator_type
419 auto& txQAccount = byAccount_.at(candidateIter->account);
420 auto const seqProx = candidateIter->seqProxy;
421 auto const newCandidateIter = byFee_.erase(candidateIter);
425 [[maybe_unused]]
auto const found = txQAccount.remove(seqProx);
426 XRPL_ASSERT(found,
"xrpl::TxQ::erase : account removed");
428 return newCandidateIter;
433 -> FeeMultiSet::iterator_type
435 auto& txQAccount = byAccount_.at(candidateIter->account);
436 auto const accountIter = txQAccount.transactions.find(candidateIter->seqProxy);
438 accountIter != txQAccount.transactions.end(),
"xrpl::TxQ::eraseAndAdvance : account found");
444 candidateIter->seqProxy.isTicket() || accountIter == txQAccount.transactions.begin(),
445 "xrpl::TxQ::eraseAndAdvance : ticket or sequence");
447 byFee_.iterator_to(accountIter->second) == candidateIter,
448 "xrpl::TxQ::eraseAndAdvance : found in byFee");
449 auto const accountNextIter =
std::next(accountIter);
453 auto const feeNextIter =
std::next(candidateIter);
454 bool const useAccountNext = accountNextIter != txQAccount.transactions.end() &&
455 accountNextIter->first > candidateIter->seqProxy &&
456 (feeNextIter == byFee_.end() || byFee_.value_comp()(accountNextIter->second, *feeNextIter));
458 auto const candidateNextIter = byFee_.erase(candidateIter);
459 txQAccount.transactions.erase(accountIter);
461 return useAccountNext ? byFee_.iterator_to(accountNextIter->second) : candidateNextIter;
467 TxQ::TxQAccount::TxMap::const_iterator begin,
468 TxQ::TxQAccount::TxMap::const_iterator end) -> TxQAccount::TxMap::iterator
470 for (
auto it = begin; it != end; ++it)
472 byFee_.erase(byFee_.iterator_to(it->second));
474 return txQAccount.transactions.erase(begin, end);
482 TxQ::AccountMap::iterator
const& accountIter,
483 TxQAccount::TxMap::iterator beginTxIter,
493 beginTxIter != accountIter->second.transactions.end(),
494 "xrpl::TxQ::tryClearAccountQueueUpThruTx : non-empty accounts input");
498 auto endTxIter = accountIter->second.transactions.lower_bound(tSeqProx);
501 auto const requiredTotalFeeLevel =
506 if (!requiredTotalFeeLevel.first)
510 beginTxIter, endTxIter, feeLevelPaid, [](
auto const& total,
auto const& txn) {
511 return total + txn.second.feeLevel;
515 if (totalFeeLevelPaid < requiredTotalFeeLevel.second)
520 for (
auto it = beginTxIter; it != endTxIter; ++it)
522 auto txResult = it->second.apply(app, view, j);
527 --it->second.retriesRemaining;
528 it->second.lastResult = txResult.ter;
549 if (!txResult.applied)
552 return {txResult.ter,
false};
557 auto const txResult =
doApply(
preclaim(pfResult, app, view), app, view);
559 if (txResult.applied)
563 endTxIter =
erase(accountIter->second, beginTxIter, endTxIter);
565 if (endTxIter != accountIter->second.transactions.end() && endTxIter->first == tSeqProx)
698 auto const pfResult =
preflight(app, view.
rules(), *tx, flags, j);
700 return {pfResult.ter,
false};
705 return *directApplied;
715 auto const account = (*tx)[sfAccount];
717 auto const sleAccount = view.
read(accountKey);
723 SeqProxy const txSeqProx = tx->getSeqProxy();
742 bool const accountIsInQueue = accountIter !=
byAccount_.
end();
754 TxIter(TxQAccount::TxMap::iterator first_, TxQAccount::TxMap::iterator end_)
755 : first(first_), end(end_)
759 TxQAccount::TxMap::iterator first;
760 TxQAccount::TxMap::iterator end;
765 if (!accountIsInQueue)
770 TxQAccount::TxMap::iterator
const firstIter = acctTxs.
lower_bound(acctSeqProx);
772 if (firstIter == acctTxs.
end())
779 return {TxIter{firstIter, acctTxs.
end()}};
782 auto const acctTxCount{!txIter ? 0 :
std::distance(txIter->first, txIter->end)};
789 if (pfResult.consequences.isBlocker())
796 <<
". Account has other queued transactions.";
799 if (acctTxCount == 1 && (txSeqProx != txIter->first->first))
803 <<
". Blocker does not replace lone queued transaction.";
810 auto replacedTxIter = [accountIsInQueue,
813 if (accountIsInQueue)
838 if (acctTxCount == 1 && txIter->first->second.consequences().isBlocker() &&
839 (txIter->first->first != txSeqProx))
853 TxQAccount::TxMap::iterator
const& existingIter = *replacedTxIter;
854 auto requiredRetryLevel =
856 JLOG(
j_.
trace()) <<
"Found transaction in queue for account " << account <<
" with "
857 << txSeqProx <<
" new txn fee level is " << feeLevelPaid
858 <<
", old txn fee level is " << existingIter->second.feeLevel
859 <<
", new txn needs fee level of " << requiredRetryLevel;
860 if (feeLevelPaid > requiredRetryLevel)
865 JLOG(
j_.
trace()) <<
"Removing transaction from queue " << existingIter->second.txID
872 <<
" in favor of queued " << existingIter->second.txID;
883 MultiTxn(
OpenView& view,
ApplyFlags flags) : applyView(&view, flags), openView(&applyView)
890 if (acctTxCount == 0)
895 if (txSeqProx.
isSeq())
897 if (acctSeqProx > txSeqProx)
899 if (acctSeqProx < txSeqProx)
908 TxQAccount const& txQAcct = accountIter->second;
910 if (acctSeqProx > txSeqProx)
919 bool requiresMultiTxn =
false;
920 if (acctTxCount > 1 || !replacedTxIter)
925 canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
929 requiresMultiTxn =
true;
932 if (requiresMultiTxn)
946 TxQAccount::TxMap::const_iterator
const prevIter = txQAcct.
getPrevTx(txSeqProx);
954 XRPL_ASSERT(prevIter != txIter->end,
"xrpl::TxQ::apply : not end");
955 if (prevIter == txIter->end || txSeqProx < prevIter->first)
959 if (txSeqProx.
isSeq())
961 if (txSeqProx < acctSeqProx)
965 if (txSeqProx > acctSeqProx)
971 else if (!replacedTxIter)
987 for (
auto iter = txIter->first; iter != txIter->end; ++iter)
992 if (iter->first != txSeqProx)
994 totalFee += iter->second.consequences().fee();
995 potentialSpend += iter->second.consequences().potentialSpend();
1002 totalFee += pfResult.consequences.fee();
1003 potentialSpend += pfResult.consequences.potentialSpend();
1036 auto const balance = (*sleAccount)[sfBalance].xrp();
1055 auto const base = view.
fees().
base;
1056 if (totalFee >= balance || (reserve > 10 * base && totalFee >= reserve))
1060 <<
". Total fees in flight too high.";
1065 multiTxn.
emplace(view, flags);
1067 auto const sleBump = multiTxn->applyView.peek(accountKey);
1074 auto const potentialTotalSpend =
1078 (potentialTotalSpend ==
XRPAmount{0} && multiTxn->applyView.fees().base == 0),
1079 "xrpl::TxQ::apply : total spend check");
1080 sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
1086 sleBump->at(sfSequence) = txSeqProx.
isSeq()
1103 auto const pcresult =
preclaim(pfResult, app, multiTxn ? multiTxn->openView : view);
1104 if (!pcresult.likelyToClaimFee)
1105 return {pcresult.
ter,
false};
1108 XRPL_ASSERT(feeLevelPaid >=
baseLevel,
"xrpl::TxQ::apply : minimum fee");
1111 <<
" has fee level of " << feeLevelPaid <<
" needs at least "
1112 << requiredFeeLevel <<
" to get in the open ledger, which has "
1113 << view.
txCount() <<
" entries.";
1134 feeLevelPaid > requiredFeeLevel && requiredFeeLevel >
baseLevel)
1152 sandbox.
apply(view);
1163 TER const ter{
canBeHeld(*tx, flags, view, sleAccount, accountIter, replacedTxIter, lock)};
1168 return {ter,
false};
1175 if (!replacedTxIter &&
isFull())
1177 auto lastRIter =
byFee_.rbegin();
1178 while (lastRIter !=
byFee_.rend() && lastRIter->account == account)
1182 if (lastRIter ==
byFee_.rend())
1191 <<
" would kick a transaction from the same account (" << account
1192 <<
") out of the queue.";
1195 auto const& endAccount =
byAccount_.
at(lastRIter->account);
1196 auto endEffectiveFeeLevel = [&]() {
1200 if (lastRIter->feeLevel > feeLevelPaid || endAccount.transactions.size() == 1)
1201 return lastRIter->feeLevel;
1205 endAccount.transactions.begin(),
1206 endAccount.transactions.end(),
1210 auto next = txn.second.feeLevel / endAccount.transactions.size();
1211 auto mod = txn.second.feeLevel % endAccount.transactions.size();
1212 if (total.first >= max - next || total.second >= max - mod)
1213 return {max, FeeLevel64{0}};
1215 return {total.first + next, total.second + mod};
1217 return endTotal.first + endTotal.second / endAccount.transactions.size();
1219 if (feeLevelPaid > endEffectiveFeeLevel)
1223 auto dropRIter = endAccount.transactions.rbegin();
1225 dropRIter->second.account == lastRIter->account,
1226 "xrpl::TxQ::apply : cheapest transaction found");
1227 JLOG(
j_.
info()) <<
"Removing last item of account " << lastRIter->account
1228 <<
" from queue with average fee of " << endEffectiveFeeLevel
1229 <<
" in favor of " <<
transactionID <<
" with fee of " << feeLevelPaid;
1235 <<
" fee is lower than end item's account average fee";
1243 replacedTxIter = removeFromByFee(replacedTxIter, tx);
1246 if (!accountIsInQueue)
1249 [[maybe_unused]]
bool created =
false;
1250 std::tie(accountIter, created) = byAccount_.emplace(account, TxQAccount(tx));
1251 XRPL_ASSERT(created,
"xrpl::TxQ::apply : account created");
1261 auto& candidate = accountIter->second.add({tx,
transactionID, feeLevelPaid, flags, pfResult});
1264 byFee_.insert(candidate);
1265 JLOG(j_.
debug()) <<
"Added transaction " << candidate.txID <<
" with result "
1267 << (accountIsInQueue ?
"existing" :
"new") <<
" account " << candidate.account
1269 <<
" Flags: " << flags;
1291 feeMetrics_.update(app, view, timeLeap, setup_);
1292 auto const& snapshot = feeMetrics_.getSnapshot();
1297 maxSize_ =
std::max(snapshot.txnsExpected * setup_.ledgersInQueue, setup_.queueSizeMin);
1300 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1302 if (candidateIter->lastValid && *candidateIter->lastValid <= ledgerSeq)
1304 byAccount_.at(candidateIter->account).dropPenalty =
true;
1305 candidateIter =
erase(candidateIter);
1315 for (
auto txQAccountIter = byAccount_.begin(); txQAccountIter != byAccount_.end();)
1317 if (txQAccountIter->second.empty())
1319 txQAccountIter = byAccount_.erase(txQAccountIter);
1366 auto ledgerChanged =
false;
1370 auto const metricsSnapshot = feeMetrics_.getSnapshot();
1372 for (
auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
1374 auto& account = byAccount_.at(candidateIter->account);
1375 auto const beginIter = account.transactions.begin();
1376 if (candidateIter->seqProxy.isSeq() && candidateIter->seqProxy > beginIter->first)
1381 JLOG(j_.
trace()) <<
"Skipping queued transaction " << candidateIter->txID
1382 <<
" from account " << candidateIter->account
1383 <<
" as it is not the first.";
1387 auto const requiredFeeLevel = getRequiredFeeLevel(view,
tapNONE, metricsSnapshot, lock);
1388 auto const feeLevelPaid = candidateIter->feeLevel;
1389 JLOG(j_.
trace()) <<
"Queued transaction " << candidateIter->txID <<
" from account "
1390 << candidateIter->account <<
" has fee level of " << feeLevelPaid
1391 <<
" needs at least " << requiredFeeLevel;
1392 if (feeLevelPaid >= requiredFeeLevel)
1394 JLOG(j_.
trace()) <<
"Applying queued transaction " << candidateIter->txID
1395 <<
" to open ledger.";
1397 auto const [txnResult, didApply, _metadata] = candidateIter->apply(app, view, j_);
1402 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID
1403 <<
" applied successfully with " <<
transToken(txnResult)
1404 <<
". Remove from queue.";
1406 candidateIter = eraseAndAdvance(candidateIter);
1407 ledgerChanged =
true;
1411 candidateIter->retriesRemaining <= 0)
1413 if (candidateIter->retriesRemaining <= 0)
1415 account.retryPenalty =
true;
1419 account.dropPenalty =
true;
1421 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1422 <<
transToken(txnResult) <<
". Remove from queue.";
1423 candidateIter = eraseAndAdvance(candidateIter);
1427 JLOG(j_.
debug()) <<
"Queued transaction " << candidateIter->txID <<
" failed with "
1428 <<
transToken(txnResult) <<
". Leave in queue."
1429 <<
" Applied: " << didApply <<
". Flags: " << candidateIter->flags;
1430 if (account.retryPenalty && candidateIter->retriesRemaining > 2)
1432 candidateIter->retriesRemaining = 1;
1436 --candidateIter->retriesRemaining;
1438 candidateIter->lastResult = txnResult;
1439 if (account.dropPenalty && account.transactions.size() > 1 && isFull<95>())
1444 if (candidateIter->seqProxy.isTicket())
1449 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1451 <<
". Removing ticketed tx from account " << account.account;
1452 candidateIter = eraseAndAdvance(candidateIter);
1460 auto dropRIter = account.transactions.rbegin();
1462 dropRIter->second.account == candidateIter->account,
1463 "xrpl::TxQ::accept : account check");
1466 <<
"Queue is nearly full, and transaction " << candidateIter->txID
1468 <<
". Removing last item from account " << account.account;
1469 auto endIter = byFee_.iterator_to(dropRIter->second);
1470 if (endIter != candidateIter)
1492 if (parentHash == parentHash_)
1494 JLOG(j_.
warn()) <<
"Parent ledger hash unchanged from " << parentHash;
1498 parentHash_ = parentHash;
1501 [[maybe_unused]]
auto const startingSize = byFee_.
size();
1512 MaybeTx::parentHashComp = parentHash;
1514 for (
auto& [_, account] : byAccount_)
1516 for (
auto& [_, candidate] : account.transactions)
1518 byFee_.insert(candidate);
1521 XRPL_ASSERT(byFee_.size() == startingSize,
"xrpl::TxQ::accept : byFee size match");
1523 return ledgerChanged;
1533 return nextQueuableSeqImpl(sleAccount, lock);
1543TxQ::nextQueuableSeqImpl(
1549 if (!sleAccount || sleAccount->getType() != ltACCOUNT_ROOT)
1550 return SeqProxy::sequence(0);
1552 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1555 auto const accountIter = byAccount_.find((*sleAccount)[sfAccount]);
1556 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1564 TxQAccount::TxMap::const_iterator txIter = acctTxs.
lower_bound(acctSeqProx);
1566 if (txIter == acctTxs.
end() || !txIter->first.isSeq() || txIter->first != acctSeqProx)
1578 SeqProxy attempt = txIter->second.consequences().followingSeq();
1579 while (++txIter != acctTxs.
cend())
1581 if (attempt < txIter->first)
1584 attempt = txIter->second.consequences().followingSeq();
1590TxQ::getRequiredFeeLevel(
1596 return FeeMetrics::scaleFeeLevel(metricsSnapshot, view);
1607 auto const account = (*tx)[sfAccount];
1608 auto const sleAccount = view.
read(keylet::account(account));
1614 SeqProxy const acctSeqProx = SeqProxy::sequence((*sleAccount)[sfSequence]);
1615 SeqProxy const txSeqProx = tx->getSeqProxy();
1619 if (txSeqProx.
isSeq() && txSeqProx != acctSeqProx)
1622 FeeLevel64 const requiredFeeLevel = [
this, &view, flags]() {
1624 return getRequiredFeeLevel(view, flags, feeMetrics_.getSnapshot(), lock);
1631 if (feeLevelPaid >= requiredFeeLevel)
1637 auto const [txnResult, didApply, metadata] =
xrpl::apply(app, view, *tx, flags, j);
1640 << (didApply ?
" applied successfully with " :
" failed with ")
1649 AccountMap::iterator
const accountIter = byAccount_.find(account);
1650 if (accountIter != byAccount_.end())
1656 removeFromByFee(existingIter, tx);
1660 return ApplyResult{txnResult, didApply, metadata};
1666TxQ::removeFromByFee(
1670 if (replacedTxIter && tx)
1674 auto deleteIter = byFee_.iterator_to((*replacedTxIter)->second);
1675 XRPL_ASSERT(deleteIter != byFee_.end(),
"xrpl::TxQ::removeFromByFee : found in byFee");
1677 &(*replacedTxIter)->second == &*deleteIter,
1678 "xrpl::TxQ::removeFromByFee : matching transaction");
1680 deleteIter->seqProxy == tx->getSeqProxy(),
1681 "xrpl::TxQ::removeFromByFee : matching sequence");
1683 deleteIter->account == (*tx)[sfAccount],
1684 "xrpl::TxQ::removeFromByFee : matching account");
1698 auto const snapshot = feeMetrics_.getSnapshot();
1700 result.
txCount = byFee_.size();
1706 result.
medFeeLevel = snapshot.escalationMultiplier;
1715 auto const account = (*tx)[sfAccount];
1719 auto const snapshot = feeMetrics_.getSnapshot();
1721 auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
1723 auto const sle = view.
read(keylet::account(account));
1725 std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0;
1726 std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value();
1728 mulDiv(fee, baseFee, baseLevel)
1741 AccountMap::const_iterator
const accountIter{byAccount_.find(account)};
1743 if (accountIter == byAccount_.end() || accountIter->second.transactions.empty())
1746 result.
reserve(accountIter->second.transactions.size());
1747 for (
auto const& tx : accountIter->second.transactions)
1761 result.
reserve(byFee_.size());
1763 for (
auto const& tx : byFee_)
1775 BOOST_ASSERT(
false);
1779 auto const metrics = getMetrics(*view);
1785 ret[jss::ledger_current_index] = view->header().seq;
1786 ret[jss::expected_ledger_size] =
std::to_string(metrics.txPerLedger);
1787 ret[jss::current_ledger_size] =
std::to_string(metrics.txInLedger);
1789 if (metrics.txQMaxSize)
1792 levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
1793 levels[jss::minimum_level] = to_string(metrics.minProcessingFeeLevel);
1794 levels[jss::median_level] = to_string(metrics.medFeeLevel);
1795 levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
1797 auto const baseFee = view->fees().base;
1801 auto const effectiveBaseFee = [&baseFee, &metrics]() {
1802 if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
1808 drops[jss::base_fee] = to_string(baseFee);
1809 drops[jss::median_fee] = to_string(
toDrops(metrics.medFeeLevel, baseFee));
1810 drops[jss::minimum_fee] = to_string(
toDrops(
1811 metrics.minProcessingFeeLevel,
1812 metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
1813 auto openFee =
toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
1814 if (effectiveBaseFee &&
toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
1816 drops[jss::open_ledger_fee] = to_string(openFee);
1827 auto const& section = config.
section(
"transaction_queue");
1836 if (
set(max,
"maximum_txn_in_ledger", section))
1840 Throw<std::runtime_error>(
1841 "The minimum number of low-fee transactions allowed "
1842 "per ledger (minimum_txn_in_ledger) exceeds "
1843 "the maximum number of low-fee transactions allowed per "
1844 "ledger (maximum_txn_in_ledger).");
1848 Throw<std::runtime_error>(
1849 "The minimum number of low-fee transactions allowed "
1850 "per ledger (minimum_txn_in_ledger_standalone) exceeds "
1851 "the maximum number of low-fee transactions allowed per "
1852 "ledger (maximum_txn_in_ledger).");
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Editable, discardable view that can build metadata for one tx.
Section & section(std::string const &name)
Returns the section with the given name.
RAII class to set and restore the Number switchover.
std::shared_ptr< OpenView const > current() const
Returns a view to the current open ledger.
Writable ledger view that accumulates state and tx changes.
std::size_t txCount() const
Return the number of tx inserted since creation.
Fees const & fees() const override
Returns the fees for the base ledger.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
LedgerHeader const & header() const override
Returns information about the ledger.
void apply(TxsRawView &to) const
Apply changes.
Rules const & rules() const override
Returns the tx processing rules.
bool exists(Keylet const &k) const override
Determine if a state item exists.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
std::uint32_t getFieldU32(SField const &field) const
bool isFieldPresent(SField const &field) const
SeqProxy getSeqProxy() const
A type that represents either a sequence value or a ticket value.
static constexpr SeqProxy sequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
constexpr bool isTicket() const
constexpr std::uint32_t value() const
constexpr bool isSeq() const
virtual OpenLedger & getOpenLedger()=0
Snapshot getSnapshot() const
Get the current Snapshot.
std::size_t txnsExpected_
Number of transactions expected per ledger.
std::size_t const targetTxnCount_
Number of transactions per ledger that fee escalation "works towards".
static FeeLevel64 scaleFeeLevel(Snapshot const &snapshot, OpenView const &view)
Use the number of transactions in the current open ledger to compute the fee level a transaction must...
beast::Journal const j_
Journal.
std::optional< std::size_t > const maximumTxnCount_
Maximum value of txnsExpected.
std::size_t update(Application &app, ReadView const &view, bool timeLeap, TxQ::Setup const &setup)
Updates fee metrics based on the transactions in the ReadView for use in fee escalation calculations.
std::size_t const minimumTxnCount_
Minimum value of txnsExpected.
boost::circular_buffer< std::size_t > recentTxnCounts_
Recent history of transaction counts that exceed the targetTxnCount_.
static std::pair< bool, FeeLevel64 > escalatedSeriesFeeLevel(Snapshot const &snapshot, OpenView const &view, std::size_t extraCount, std::size_t seriesSize)
Computes the total fee level for all transactions in a series.
FeeLevel64 escalationMultiplier_
Based on the median fee of the LCL.
Represents a transaction in the queue which may be applied later to the open ledger.
static LedgerHash parentHashComp
The hash of the parent ledger.
static constexpr int retriesAllowed
Starting retry count for newly queued transactions.
MaybeTx(std::shared_ptr< STTx const > const &, TxID const &txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const &pfResult)
Constructor.
ApplyResult apply(Application &app, OpenView &view, beast::Journal j)
Attempt to apply the queued transaction to the open ledger.
SeqProxy const seqProxy
Transaction SeqProxy number (sfSequence or sfTicketSequence field).
Used to represent an account to the queue, and stores the transactions queued for that account by Seq...
TxMap::const_iterator getPrevTx(SeqProxy seqProx) const
Find the entry in transactions that precedes seqProx, if one does.
TxMap transactions
Sequence number will be used as the key.
MaybeTx & add(MaybeTx &&)
Add a transaction candidate to this account for queuing.
std::size_t getTxnCount() const
Return the number of transactions currently queued for this account.
TxQAccount(std::shared_ptr< STTx const > const &txn)
Construct from a transaction.
bool remove(SeqProxy seqProx)
Remove the candidate with given SeqProxy value from this account.
TxQ(Setup const &setup, beast::Journal j)
Constructor.
FeeMetrics feeMetrics_
Tracks the current state of the queue.
std::optional< size_t > maxSize_
Maximum number of transactions allowed in the queue based on the current metrics.
SeqProxy nextQueuableSeqImpl(std::shared_ptr< SLE const > const &sleAccount, std::lock_guard< std::mutex > const &) const
bool isFull() const
Is the queue at least fillPercentage full?
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type)
Erase and return the next entry for the account (if fee level is higher), or next entry in byFee_ (lo...
ApplyResult tryClearAccountQueueUpThruTx(Application &app, OpenView &view, STTx const &tx, AccountMap::iterator const &accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const &pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, beast::Journal j)
All-or-nothing attempt to try to apply the queued txs for accountIter up to and including tx.
ApplyResult apply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
Add a new transaction to the open ledger, hold it in the queue, or reject it.
TER canBeHeld(STTx const &, ApplyFlags const, OpenView const &, std::shared_ptr< SLE const > const &sleAccount, AccountMap::iterator const &, std::optional< TxQAccount::TxMap::iterator > const &, std::lock_guard< std::mutex > const &lock)
Checks if the indicated transaction fits the conditions for being stored in the queue.
std::optional< ApplyResult > tryDirectApply(Application &app, OpenView &view, std::shared_ptr< STTx const > const &tx, ApplyFlags flags, beast::Journal j)
virtual ~TxQ()
Destructor.
static constexpr FeeLevel64 baseLevel
Fee level for single-signed reference transaction.
std::mutex mutex_
Most queue operations are done under the master lock, but use this mutex for the RPC "fee" command,...
FeeMultiSet byFee_
The queue itself: the collection of transactions ordered by fee level.
beast::Journal const j_
Journal.
static FeeLevel64 getRequiredFeeLevel(OpenView &view, ApplyFlags flags, FeeMetrics::Snapshot const &metricsSnapshot, std::lock_guard< std::mutex > const &lock)
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type)
Erase and return the next entry in byFee_ (lower fee level)
AccountMap byAccount_
All of the accounts which currently have any transactions in the queue.
Setup const setup_
Setup parameters used to control the behavior of the queue.
constexpr int signum() const noexcept
Return the sign of the amount.
static constexpr std::size_t size()
T emplace_back(T... args)
@ objectValue
object value (collection of name/value pairs).
static constexpr std::pair< bool, std::uint64_t > sumOfFirstSquares(std::size_t xIn)
static ticket_t const ticket
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ telCAN_NOT_QUEUE_BLOCKED
@ telCAN_NOT_QUEUE_BALANCE
@ telCAN_NOT_QUEUE_BLOCKS
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
FeeLevel< std::uint64_t > FeeLevel64
static std::optional< LedgerIndex > getLastLedgerSequence(STTx const &tx)
constexpr struct xrpl::open_ledger_t open_ledger
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.
ApplyResult apply(ServiceRegistry ®istry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
TxQ::Setup setup_TxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
static FeeLevel64 increase(FeeLevel64 level, std::uint32_t increasePercent)
std::string transToken(TER code)
@ current
This was a new validation and was added.
static FeeLevel64 getFeeLevelPaid(ReadView const &view, STTx const &tx)
bool isTefFailure(TER x) noexcept
auto constexpr muldiv_max
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
XRPAmount calculateDefaultBaseFee(ReadView const &view, STTx const &tx)
Return the minimum fee that an "ordinary" transaction would pay.
@ transactionID
transaction plus signature to give transaction ID
bool isTesSuccess(TER x) noexcept
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry ®istry, OpenView &view)
Apply a prechecked transaction to an OpenView.
bool isTemMalformed(TER x) noexcept
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.
TER const ter
Intermediate transaction result.
Describes the results of the preflight check.
Snapshot of the externally relevant FeeMetrics fields at any given time.
std::size_t const txnsExpected
FeeLevel64 const escalationMultiplier
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
std::size_t txCount
Number of transactions in the queue.
std::optional< std::size_t > txQMaxSize
Max transactions currently allowed in queue.
FeeLevel64 openLedgerFeeLevel
Minimum fee level to get into the current open ledger, bypassing the queue.
std::size_t txInLedger
Number of transactions currently in the open ledger.
FeeLevel64 minProcessingFeeLevel
Minimum fee level for a transaction to be considered for the open ledger or the queue.
FeeLevel64 referenceFeeLevel
Reference transaction fee level.
FeeLevel64 medFeeLevel
Median fee level of the last ledger.
std::size_t txPerLedger
Number of transactions expected per ledger.
Structure used to customize TxQ behavior.
bool standAlone
Use standalone mode behavior.
std::uint32_t maximumTxnPerAccount
Maximum number of transactions that can be queued by one account.
FeeLevel64 minimumEscalationMultiplier
Minimum value of the escalation multiplier, regardless of the prior ledger's median fee level.
std::optional< std::uint32_t > maximumTxnInLedger
Optional maximum allowed value of transactions per ledger before fee escalation kicks in.
std::uint32_t targetTxnInLedger
Number of transactions per ledger that fee escalation "works towards".
std::uint32_t minimumLastLedgerBuffer
Minimum difference between the current ledger sequence and a transaction's LastLedgerSequence for the...
std::size_t ledgersInQueue
Number of ledgers' worth of transactions to allow in the queue.
std::uint32_t retrySequencePercent
Extra percentage required on the fee level of a queued transaction to replace that transaction with a...
std::uint32_t minimumTxnInLedgerSA
Like minimumTxnInLedger for standalone mode.
std::uint32_t slowConsensusDecreasePercent
When consensus takes longer than appropriate, the expected ledger size is updated to the lesser of th...
std::size_t queueSizeMin
The smallest limit the queue is allowed.
std::uint32_t minimumTxnInLedger
Minimum number of transactions to allow into the ledger before escalation, regardless of the prior le...
std::uint32_t normalConsensusIncreasePercent
When the ledger has more transactions than "expected", and performance is humming along nicely,...