mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 01:55:48 +00:00
Update TxQ developer docs:
* Rename a couple of member variables for clarity.
This commit is contained in:
committed by
Nik Bougalis
parent
cd1c5a30dd
commit
e14f913244
@@ -105,6 +105,9 @@ WARN_LOGFILE =
|
||||
#---------------------------------------------------------------------------
|
||||
INPUT = \
|
||||
\
|
||||
../src/ripple/app/misc/TxQ.h \
|
||||
../src/ripple/app/tx/apply.h \
|
||||
../src/ripple/app/tx/applySteps.h \
|
||||
../src/ripple/protocol/STObject.h \
|
||||
../src/ripple/protocol/JsonFields.h \
|
||||
../src/test/jtx/AbstractClient.h \
|
||||
|
||||
@@ -1609,8 +1609,8 @@ NetworkOPsImp::ServerFeeSummary::operator !=(NetworkOPsImp::ServerFeeSummary con
|
||||
|
||||
if(em && b.em)
|
||||
{
|
||||
return (em->minFeeLevel != b.em->minFeeLevel ||
|
||||
em->expFeeLevel != b.em->expFeeLevel ||
|
||||
return (em->minProcessingFeeLevel != b.em->minProcessingFeeLevel ||
|
||||
em->openLedgerFeeLevel != b.em->openLedgerFeeLevel ||
|
||||
em->referenceFeeLevel != b.em->referenceFeeLevel);
|
||||
}
|
||||
|
||||
@@ -1653,12 +1653,12 @@ void NetworkOPsImp::pubServer ()
|
||||
{
|
||||
auto const loadFactor =
|
||||
std::max(static_cast<std::uint64_t>(f.loadFactorServer),
|
||||
mulDiv(f.em->expFeeLevel, f.loadBaseServer,
|
||||
mulDiv(f.em->openLedgerFeeLevel, f.loadBaseServer,
|
||||
f.em->referenceFeeLevel).second);
|
||||
|
||||
jvObj [jss::load_factor] = clamp(loadFactor);
|
||||
jvObj [jss::load_factor_fee_escalation] = clamp(f.em->expFeeLevel);
|
||||
jvObj [jss::load_factor_fee_queue] = clamp(f.em->minFeeLevel);
|
||||
jvObj [jss::load_factor_fee_escalation] = clamp(f.em->openLedgerFeeLevel);
|
||||
jvObj [jss::load_factor_fee_queue] = clamp(f.em->minProcessingFeeLevel);
|
||||
jvObj [jss::load_factor_fee_reference]
|
||||
= clamp(f.em->referenceFeeLevel);
|
||||
|
||||
@@ -2195,7 +2195,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
|
||||
auto const loadFactorServer = app_.getFeeTrack().getLoadFactor();
|
||||
auto const loadBaseServer = app_.getFeeTrack().getLoadBase();
|
||||
auto const loadFactorFeeEscalation = escalationMetrics ?
|
||||
escalationMetrics->expFeeLevel : 1;
|
||||
escalationMetrics->openLedgerFeeLevel : 1;
|
||||
auto const loadBaseFeeEscalation = escalationMetrics ?
|
||||
escalationMetrics->referenceFeeLevel : 1;
|
||||
|
||||
@@ -2221,7 +2221,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
|
||||
max32, loadFactorFeeEscalation));
|
||||
info[jss::load_factor_fee_queue] =
|
||||
static_cast<std::uint32_t> (std::min(
|
||||
max32, escalationMetrics->minFeeLevel));
|
||||
max32, escalationMetrics->minProcessingFeeLevel));
|
||||
info[jss::load_factor_fee_reference] =
|
||||
static_cast<std::uint32_t> (std::min(
|
||||
max32, loadBaseFeeEscalation));
|
||||
@@ -2258,11 +2258,12 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
|
||||
info[jss::load_factor_fee_escalation] =
|
||||
static_cast<double> (loadFactorFeeEscalation) /
|
||||
escalationMetrics->referenceFeeLevel;
|
||||
if (escalationMetrics->minFeeLevel !=
|
||||
if (escalationMetrics->minProcessingFeeLevel !=
|
||||
escalationMetrics->referenceFeeLevel)
|
||||
info[jss::load_factor_fee_queue] =
|
||||
static_cast<double> (escalationMetrics->minFeeLevel) /
|
||||
escalationMetrics->referenceFeeLevel;
|
||||
static_cast<double> (
|
||||
escalationMetrics->minProcessingFeeLevel) /
|
||||
escalationMetrics->referenceFeeLevel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Config;
|
||||
|
||||
Once enough transactions are added to the open ledger, the required
|
||||
fee will jump dramatically. If additional transactions are added,
|
||||
the fee will grow exponentially.
|
||||
the fee will grow exponentially from there.
|
||||
|
||||
Transactions that don't have a high enough fee to be applied to
|
||||
the ledger are added to the queue in order from highest fee level to
|
||||
@@ -47,30 +47,95 @@ class Config;
|
||||
are first applied from the queue to the open ledger in fee level order
|
||||
until either all transactions are applied or the fee again jumps
|
||||
too high for the remaining transactions.
|
||||
|
||||
For further information and a high-level overview of how transactions
|
||||
are processed with the `TxQ`, see FeeEscalation.md
|
||||
*/
|
||||
class TxQ
|
||||
{
|
||||
public:
|
||||
/// Fee level for single-signed reference transaction.
|
||||
static constexpr std::uint64_t baseLevel = 256;
|
||||
|
||||
/**
|
||||
Structure used to customize @ref TxQ behavior.
|
||||
*/
|
||||
struct Setup
|
||||
{
|
||||
/// Default constructor
|
||||
explicit Setup() = default;
|
||||
|
||||
/** Number of ledgers' worth of transactions to allow
|
||||
in the queue. For example, if the last ledger had
|
||||
150 transactions, then up to 3000 transactions can
|
||||
be queued.
|
||||
|
||||
Can be overridden by @ref queueSizeMin
|
||||
*/
|
||||
std::size_t ledgersInQueue = 20;
|
||||
/** The smallest limit the queue is allowed.
|
||||
|
||||
Will allow more than `ledgersInQueue` in the queue
|
||||
if ledgers are small.
|
||||
*/
|
||||
std::size_t queueSizeMin = 2000;
|
||||
/** Extra percentage required on the fee level of a queued
|
||||
transaction to replace that transaction with another
|
||||
with the same sequence number.
|
||||
|
||||
If queued transaction for account "Alice" with seq 45
|
||||
has a fee level of 512, a replacement transaction for
|
||||
"Alice" with seq 45 must have a fee level of at least
|
||||
512 * (1 + 0.25) = 640 to be considered.
|
||||
*/
|
||||
std::uint32_t retrySequencePercent = 25;
|
||||
// TODO: eahennis. Can we remove the multi tx factor?
|
||||
/** Extra percentage required on the fee level of a
|
||||
queued transaction to queue the transaction with
|
||||
the next sequence number.
|
||||
|
||||
If queued transaction for account "Alice" with seq 45
|
||||
has a fee level of 512, a transaction with seq 46 must
|
||||
have a fee level of at least
|
||||
512 * (1 + -0.90) = 51.2 ~= 52 to
|
||||
be considered.
|
||||
|
||||
@todo eahennis. Can we remove the multi tx factor?
|
||||
*/
|
||||
std::int32_t multiTxnPercent = -90;
|
||||
/// Minimum value of the escalation multiplier, regardless
|
||||
/// of the prior ledger's median fee level.
|
||||
std::uint32_t minimumEscalationMultiplier = baseLevel * 500;
|
||||
/// Minimum number of transactions to allow into the ledger
|
||||
/// before escalation, regardless of the prior ledger's size.
|
||||
std::uint32_t minimumTxnInLedger = 5;
|
||||
/// Like @ref minimumTxnInLedger for standalone mode.
|
||||
/// Primarily so that tests don't need to worry about queuing.
|
||||
std::uint32_t minimumTxnInLedgerSA = 1000;
|
||||
/// Number of transactions per ledger that fee escalation "works
|
||||
/// towards".
|
||||
std::uint32_t targetTxnInLedger = 50;
|
||||
/** Optional maximum allowed value of transactions per ledger before
|
||||
fee escalation kicks in. By default, the maximum is an emergent
|
||||
property of network, validator, and consensus performance. This
|
||||
setting can override that behavior to prevent fee escalation from
|
||||
allowing more than `maximumTxnInLedger` "cheap" transactions into
|
||||
the open ledger.
|
||||
|
||||
@todo eahennis. This setting seems to go against our goals and
|
||||
values. Can it be removed?
|
||||
*/
|
||||
boost::optional<std::uint32_t> maximumTxnInLedger;
|
||||
/// Maximum number of transactions that can be queued by one account.
|
||||
std::uint32_t maximumTxnPerAccount = 10;
|
||||
/** Minimum difference between the current ledger sequence and a
|
||||
transaction's `LastLedgerSequence` for the transaction to be
|
||||
queueable. Decreases the chance a transaction will get queued
|
||||
and broadcast only to expire before it gets a chance to be
|
||||
processed.
|
||||
*/
|
||||
std::uint32_t minimumLastLedgerBuffer = 2;
|
||||
/* So we don't deal with infinite fee levels, treat
|
||||
any transaction with a 0 base fee (ie. SetRegularKey
|
||||
/** So we don't deal with "infinite" fee levels, treat
|
||||
any transaction with a 0 base fee (ie SetRegularKey
|
||||
password recovery) as having this fee level.
|
||||
Should the network behavior change in the future such
|
||||
that these transactions are unable to be processed,
|
||||
@@ -78,56 +143,115 @@ public:
|
||||
bikeshedding for now.
|
||||
*/
|
||||
std::uint64_t zeroBaseFeeTransactionFeeLevel = 256000;
|
||||
/// Use standalone mode behavior.
|
||||
bool standAlone = false;
|
||||
};
|
||||
|
||||
/**
|
||||
Structure returned by @ref TxQ::getMetrics, expressed in
|
||||
reference fee level units.
|
||||
*/
|
||||
struct Metrics
|
||||
{
|
||||
/// Default constructor
|
||||
explicit Metrics() = default;
|
||||
|
||||
std::size_t txCount; // Transactions in the queue
|
||||
boost::optional<std::size_t> txQMaxSize; // Max txns in queue
|
||||
std::size_t txInLedger; // Amount currently in the ledger
|
||||
std::size_t txPerLedger; // Amount expected per ledger
|
||||
std::uint64_t referenceFeeLevel; // Reference transaction fee level
|
||||
std::uint64_t minFeeLevel; // Minimum fee level to get in the queue
|
||||
std::uint64_t medFeeLevel; // Median fee level of the last ledger
|
||||
std::uint64_t expFeeLevel; // Estimated fee level to get in next ledger
|
||||
/// Number of transactions in the queue
|
||||
std::size_t txCount;
|
||||
/// Max transactions currently allowed in queue
|
||||
boost::optional<std::size_t> txQMaxSize;
|
||||
/// Number of transactions currently in the open ledger
|
||||
std::size_t txInLedger;
|
||||
/// Number of transactions expected per ledger
|
||||
std::size_t txPerLedger;
|
||||
/// Reference transaction fee level
|
||||
std::uint64_t referenceFeeLevel;
|
||||
/// Minimum fee level for a transaction to be considered for
|
||||
/// the open ledger or the queue
|
||||
std::uint64_t minProcessingFeeLevel;
|
||||
/// Median fee level of the last ledger
|
||||
std::uint64_t medFeeLevel;
|
||||
/// Minimum fee level to get into the current open ledger,
|
||||
/// bypassing the queue
|
||||
std::uint64_t openLedgerFeeLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
Structure returned by @ref TxQ::getAccountTxs to describe
|
||||
transactions in the queue for an account.
|
||||
*/
|
||||
struct AccountTxDetails
|
||||
{
|
||||
/// Default constructor
|
||||
explicit AccountTxDetails() = default;
|
||||
|
||||
/// Fee level of the queued transaction
|
||||
uint64_t feeLevel;
|
||||
/// LastValidLedger field of the queued transaction, if any
|
||||
boost::optional<LedgerIndex const> lastValid;
|
||||
/** Potential @ref TxConsequences of applying the queued transaction
|
||||
to the open ledger, if known.
|
||||
|
||||
@note `consequences` is lazy-computed, so may not be known at any
|
||||
given time.
|
||||
*/
|
||||
boost::optional<TxConsequences const> consequences;
|
||||
};
|
||||
|
||||
/**
|
||||
Structure that describes a transaction in the queue
|
||||
waiting to be applied to the current open ledger.
|
||||
A collection of these is returned by @ref TxQ::getTxs.
|
||||
*/
|
||||
struct TxDetails : AccountTxDetails
|
||||
{
|
||||
/// Default constructor
|
||||
explicit TxDetails() = default;
|
||||
|
||||
/// The account the transaction is queued for
|
||||
AccountID account;
|
||||
/// The full transaction
|
||||
std::shared_ptr<STTx const> txn;
|
||||
/** Number of times the transactor can return a retry / `ter` result
|
||||
when attempting to apply this transaction to the open ledger
|
||||
from the queue. If the transactor returns `ter` and no retries are
|
||||
left, this transaction will be dropped.
|
||||
*/
|
||||
int retriesRemaining;
|
||||
/** The *intermediate* result returned by @ref preflight before
|
||||
this transaction was queued, or after it is queued, but before
|
||||
a failed attempt to `apply` it to the open ledger. This will
|
||||
usually be `tesSUCCESS`, but there are some edge cases where
|
||||
it has another value. Those edge cases are interesting enough
|
||||
that this value is made available here. Specifically, if the
|
||||
`rules` change between attempts, `preflight` will be run again
|
||||
in `TxQ::MaybeTx::apply`.
|
||||
*/
|
||||
TER preflightResult;
|
||||
/** If the transactor attempted to apply the transaction to the open
|
||||
ledger from the queue and *failed*, then this is the transactor
|
||||
result from the last attempt. Should never be a `tec`, `tef`,
|
||||
`tem`, or `tesSUCCESS`, because those results cause the
|
||||
transaction to be removed from the queue.
|
||||
*/
|
||||
boost::optional<TER> lastResult;
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
TxQ(Setup const& setup,
|
||||
beast::Journal j);
|
||||
|
||||
/// Destructor
|
||||
virtual ~TxQ();
|
||||
|
||||
/**
|
||||
Add a new transaction to the open ledger, hold it in the queue,
|
||||
or reject it.
|
||||
|
||||
@return A pair with the TER and a bool indicating
|
||||
whether or not the transaction was applied.
|
||||
If the transaction is queued, will return
|
||||
{ terQUEUED, false }.
|
||||
@return A pair with the `TER` and a `bool` indicating
|
||||
whether or not the transaction was applied to
|
||||
the open ledger. If the transaction is queued,
|
||||
will return `{ terQUEUED, false }`.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
apply(Application& app, OpenView& view,
|
||||
@@ -136,17 +260,29 @@ public:
|
||||
|
||||
/**
|
||||
Fill the new open ledger with transactions from the queue.
|
||||
As we apply more transactions to the ledger, the required
|
||||
fee will increase.
|
||||
|
||||
@return Whether any txs were added to the view.
|
||||
@note As more transactions are applied to the ledger, the
|
||||
required fee may increase. The required fee may rise above
|
||||
the fee level of the queued items before the queue is emptied,
|
||||
which will end the process, leaving those in the queue for
|
||||
the next open ledger.
|
||||
|
||||
@return Whether any transactions were added to the `view`.
|
||||
*/
|
||||
bool
|
||||
accept(Application& app, OpenView& view);
|
||||
|
||||
/**
|
||||
Update stats and clean up the queue in preparation for
|
||||
Update fee metrics and clean up the queue in preparation for
|
||||
the next ledger.
|
||||
|
||||
@note Fee metrics are updated based on the fee levels of the
|
||||
txs in the validated ledger and whether consensus is slow.
|
||||
Maximum queue size is adjusted to be enough to hold
|
||||
`ledgersInQueue` ledgers or `queueSizeMin` transactions.
|
||||
Any transactions for which the `LastLedgerSequence` has
|
||||
passed are removed from the queue, and any account objects
|
||||
that have no candidates under them are removed.
|
||||
*/
|
||||
void
|
||||
processClosedLedger(Application& app,
|
||||
@@ -154,8 +290,8 @@ public:
|
||||
|
||||
/** Returns fee metrics in reference fee level units.
|
||||
|
||||
@returns Uninitialized @ref optional if the FeeEscalation
|
||||
amendment is not enabled.
|
||||
@returns Uninitialized boost::optional if the
|
||||
FeeEscalation amendment is not enabled.
|
||||
*/
|
||||
boost::optional<Metrics>
|
||||
getMetrics(OpenView const& view,
|
||||
@@ -164,9 +300,9 @@ public:
|
||||
/** Returns information about the transactions currently
|
||||
in the queue for the account.
|
||||
|
||||
@returns Uninitialized @ref optional if the FeeEscalation
|
||||
amendment is not enabled, OR if the account has no transactions
|
||||
in the queue.
|
||||
@returns Empty `map` if the
|
||||
FeeEscalation amendment is not enabled, OR if the
|
||||
account has no transactions in the queue.
|
||||
*/
|
||||
std::map<TxSeq, AccountTxDetails const>
|
||||
getAccountTxs(AccountID const& account, ReadView const& view) const;
|
||||
@@ -174,46 +310,53 @@ public:
|
||||
/** Returns information about all transactions currently
|
||||
in the queue.
|
||||
|
||||
@returns Uninitialized @ref optional if the FeeEscalation
|
||||
@returns Empty `vector` if the FeeEscalation
|
||||
amendment is not enabled, OR if there are no transactions
|
||||
in the queue.
|
||||
*/
|
||||
std::vector<TxDetails>
|
||||
getTxs(ReadView const& view) const;
|
||||
|
||||
/** Packages up fee metrics for the `fee` RPC command.
|
||||
/** Summarize current fee metrics for the `fee` RPC command.
|
||||
|
||||
@returns a `Json objectvalue`
|
||||
*/
|
||||
Json::Value
|
||||
doRPC(Application& app) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
Track and use the fee escalation metrics of the
|
||||
current open ledger. Does the work of scaling fees
|
||||
as the open ledger grows.
|
||||
*/
|
||||
class FeeMetrics
|
||||
{
|
||||
private:
|
||||
// Fee escalation
|
||||
|
||||
// Minimum value of txnsExpected.
|
||||
/// Minimum value of txnsExpected.
|
||||
std::size_t const minimumTxnCount_;
|
||||
// Limit of the txnsExpected value after a
|
||||
// time leap.
|
||||
/// Number of transactions per ledger that fee escalation "works
|
||||
/// towards".
|
||||
std::size_t const targetTxnCount_;
|
||||
// Maximum value of txnsExpected
|
||||
/// Maximum value of txnsExpected
|
||||
boost::optional<std::size_t> const maximumTxnCount_;
|
||||
// Number of transactions expected per ledger.
|
||||
// One more than this value will be accepted
|
||||
// before escalation kicks in.
|
||||
/// Number of transactions expected per ledger.
|
||||
/// One more than this value will be accepted
|
||||
/// before escalation kicks in.
|
||||
std::size_t txnsExpected_;
|
||||
// Recent history of transaction counts that
|
||||
// exceed the targetTxnCount_
|
||||
/// Recent history of transaction counts that
|
||||
/// exceed the targetTxnCount_
|
||||
boost::circular_buffer<std::size_t> recentTxnCounts_;
|
||||
// Minimum value of escalationMultiplier.
|
||||
/// Minimum value of escalationMultiplier.
|
||||
std::uint64_t const minimumMultiplier_;
|
||||
// Based on the median fee of the LCL. Used
|
||||
// when fee escalation kicks in.
|
||||
/// Based on the median fee of the LCL. Used
|
||||
/// when fee escalation kicks in.
|
||||
std::uint64_t escalationMultiplier_;
|
||||
/// Journal
|
||||
beast::Journal j_;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
FeeMetrics(Setup const& setup, beast::Journal j)
|
||||
: minimumTxnCount_(setup.standAlone ?
|
||||
setup.minimumTxnInLedgerSA :
|
||||
@@ -236,9 +379,11 @@ private:
|
||||
Updates fee metrics based on the transactions in the ReadView
|
||||
for use in fee escalation calculations.
|
||||
|
||||
@param app Rippled Application object.
|
||||
@param view View of the LCL that was just closed or received.
|
||||
@param timeLeap Indicates that rippled is under load so fees
|
||||
should grow faster.
|
||||
@param setup Customization params.
|
||||
*/
|
||||
std::size_t
|
||||
update(Application& app,
|
||||
@@ -258,6 +403,7 @@ private:
|
||||
std::uint64_t const escalationMultiplier;
|
||||
};
|
||||
|
||||
/// Get the current @ref Snapshot
|
||||
Snapshot
|
||||
getSnapshot() const
|
||||
{
|
||||
@@ -267,19 +413,54 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
/** Use the number of transactions in the current open ledger
|
||||
to compute the fee level a transaction must pay to bypass the
|
||||
queue.
|
||||
|
||||
@param view Current open ledger.
|
||||
@param txCountPadding Optional number of "extra" transactions
|
||||
to assume are in the ledger. Can be used to determine a
|
||||
padded fee, so a transaction can pay more if the user is
|
||||
concerned that more transactions will get into the open
|
||||
ledger between the time this fee is computed and the
|
||||
transaction is submitted.
|
||||
|
||||
@return A fee level value.
|
||||
*/
|
||||
static
|
||||
std::uint64_t
|
||||
scaleFeeLevel(Snapshot const& snapshot, OpenView const& view,
|
||||
std::uint32_t txCountPadding = 0);
|
||||
|
||||
/**
|
||||
Returns the total fee level for all transactions in a series.
|
||||
Assumes that there are already more than txnsExpected_ txns in
|
||||
the view. If there aren't, the math still works, but the level
|
||||
will be high.
|
||||
Computes the total fee level for all transactions in a series.
|
||||
Assumes that there are already more than @ref txnsExpected_ txns
|
||||
between the view and `extraCount`. If there aren't, the result
|
||||
will be sensible (e.g. there won't be any underflows or
|
||||
overflows), but the level will be higher than actually required.
|
||||
|
||||
Returns: A `std::pair` as returned from `mulDiv` indicating whether
|
||||
the calculation result is safe.
|
||||
@note A "series" is a set of transactions for the same account
|
||||
with sequential sequence numbers. In the context of this
|
||||
function, the series is already in the queue, and the series
|
||||
starts with the account's current sequence number. This
|
||||
function is called by @ref tryClearAccountQueue to figure
|
||||
out if a newly submitted transaction is paying enough to
|
||||
get all of the queued transactions plus itself out of the
|
||||
queue and into the open ledger while accounting for the
|
||||
escalating fee as each one is processed. The idea is that
|
||||
if a series of transactions are taking too long to get out
|
||||
of the queue, a user can "rescue" them without having to
|
||||
resubmit each one with an individually higher fee.
|
||||
|
||||
@param view Current open / working ledger. (May be a sandbox.)
|
||||
@param extraCount Number of additional transactions to count as
|
||||
in the ledger. (If `view` is a sandbox, should be the number of
|
||||
transactions in the parent ledger.)
|
||||
@param seriesSize Total number of transactions in the series to be
|
||||
processed.
|
||||
|
||||
@return A `std::pair` as returned from @ref `mulDiv` indicating
|
||||
whether the calculation result overflows.
|
||||
*/
|
||||
static
|
||||
std::pair<bool, std::uint64_t>
|
||||
@@ -287,34 +468,65 @@ private:
|
||||
std::size_t extraCount, std::size_t seriesSize);
|
||||
};
|
||||
|
||||
/**
|
||||
Represents transactions in the queue which may be applied
|
||||
later to the open ledger.
|
||||
*/
|
||||
class MaybeTx
|
||||
{
|
||||
public:
|
||||
// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below
|
||||
// to put each MaybeTx object into more than one
|
||||
// set without copies, pointers, etc.
|
||||
/// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below
|
||||
/// to put each MaybeTx object into more than one
|
||||
/// set without copies, pointers, etc.
|
||||
boost::intrusive::set_member_hook<> byFeeListHook;
|
||||
|
||||
/// The complete transaction.
|
||||
std::shared_ptr<STTx const> txn;
|
||||
|
||||
/// Potential @ref TxConsequences of applying this transaction
|
||||
/// to the open ledger.
|
||||
boost::optional<TxConsequences const> consequences;
|
||||
|
||||
/// Computed fee level that the transaction will pay.
|
||||
uint64_t const feeLevel;
|
||||
/// Transaction ID.
|
||||
TxID const txID;
|
||||
/// Prior transaction ID (`sfAccountTxnID` field).
|
||||
boost::optional<TxID> priorTxID;
|
||||
/// Account submitting the transaction.
|
||||
AccountID const account;
|
||||
/// Expiration ledger for the transaction
|
||||
/// (`sfLastLedgerSequence` field).
|
||||
boost::optional<LedgerIndex> lastValid;
|
||||
/**
|
||||
A transaction at the front of the queue will be given
|
||||
several attempts to succeed before being dropped from
|
||||
the queue. If dropped, one of the account's penalty
|
||||
flags will be set, and other transactions may have
|
||||
their `retriesRemaining` forced down as part of the
|
||||
penalty.
|
||||
*/
|
||||
int retriesRemaining;
|
||||
/// Transaction sequence number (`sfSequence` field).
|
||||
TxSeq const sequence;
|
||||
/// Flags provided to `apply`. If the transaction is later
|
||||
/// attempted with different flags, it will need to be
|
||||
/// `preflight`ed again.
|
||||
ApplyFlags const flags;
|
||||
boost::optional<TER> lastResult;
|
||||
// Invariant: pfresult is never allowed to be empty. The
|
||||
// boost::optional is leveraged to allow `emplace`d
|
||||
// construction and replacement without a copy
|
||||
// assignment operation.
|
||||
/** Cached result of the `preflight` operation. Because
|
||||
`preflight` is expensive, minimize the number of times
|
||||
it needs to be done.
|
||||
@invariant `pfresult` is never allowed to be empty. The
|
||||
`boost::optional` is leveraged to allow `emplace`d
|
||||
construction and replacement without a copy
|
||||
assignment operation.
|
||||
*/
|
||||
boost::optional<PreflightResult const> pfresult;
|
||||
|
||||
/* In TxQ::accept, the required fee level may be low
|
||||
/** Starting retry count for newly queued transactions.
|
||||
|
||||
In TxQ::accept, the required fee level may be low
|
||||
enough that this transaction gets a chance to apply
|
||||
to the ledger, but it may get a retry ter result for
|
||||
another reason (eg. insufficient balance). When that
|
||||
@@ -329,33 +541,42 @@ private:
|
||||
static constexpr int retriesAllowed = 10;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
MaybeTx(std::shared_ptr<STTx const> const&,
|
||||
TxID const& txID, std::uint64_t feeLevel,
|
||||
ApplyFlags const flags,
|
||||
PreflightResult const& pfresult);
|
||||
|
||||
/// Attempt to apply the queued transaction to the open ledger.
|
||||
std::pair<TER, bool>
|
||||
apply(Application& app, OpenView& view, beast::Journal j);
|
||||
};
|
||||
|
||||
/// Used for sorting @ref MaybeTx by `feeLevel`
|
||||
class GreaterFee
|
||||
{
|
||||
public:
|
||||
/// Default constructor
|
||||
explicit GreaterFee() = default;
|
||||
|
||||
/// Is the fee level of `lhs` greater than the fee level of `rhs`?
|
||||
bool operator()(const MaybeTx& lhs, const MaybeTx& rhs) const
|
||||
{
|
||||
return lhs.feeLevel > rhs.feeLevel;
|
||||
}
|
||||
};
|
||||
|
||||
/** Used to represent an account to the queue, and stores the
|
||||
transactions queued for that account by sequence.
|
||||
*/
|
||||
class TxQAccount
|
||||
{
|
||||
public:
|
||||
using TxMap = std::map <TxSeq, MaybeTx>;
|
||||
|
||||
/// The account
|
||||
AccountID const account;
|
||||
// Sequence number will be used as the key.
|
||||
/// Sequence number will be used as the key.
|
||||
TxMap transactions;
|
||||
/* If this account has had any transaction retry more than
|
||||
`retriesAllowed` times so that it was dropped from the
|
||||
@@ -373,24 +594,34 @@ private:
|
||||
bool dropPenalty = false;
|
||||
|
||||
public:
|
||||
/// Construct from a transaction
|
||||
explicit TxQAccount(std::shared_ptr<STTx const> const& txn);
|
||||
/// Construct from an account
|
||||
explicit TxQAccount(const AccountID& account);
|
||||
|
||||
/// Return the number of transactions currently queued for this account
|
||||
std::size_t
|
||||
getTxnCount() const
|
||||
{
|
||||
return transactions.size();
|
||||
}
|
||||
|
||||
/// Checks if this account has no transactions queued
|
||||
bool
|
||||
empty() const
|
||||
{
|
||||
return !getTxnCount();
|
||||
}
|
||||
|
||||
/// Add a transaction candidate to this account for queuing
|
||||
MaybeTx&
|
||||
add(MaybeTx&&);
|
||||
|
||||
/** Remove the candidate with given sequence number from this
|
||||
account.
|
||||
|
||||
@return Whether a candidate was removed
|
||||
*/
|
||||
bool
|
||||
remove(TxSeq const& sequence);
|
||||
};
|
||||
@@ -405,41 +636,68 @@ private:
|
||||
|
||||
using AccountMap = std::map <AccountID, TxQAccount>;
|
||||
|
||||
/// Setup parameters used to control the behavior of the queue
|
||||
Setup const setup_;
|
||||
/// Journal
|
||||
beast::Journal j_;
|
||||
|
||||
// These members must always and only be accessed under
|
||||
// locked mutex_
|
||||
/** Tracks the current state of the queue.
|
||||
@note This member must always and only be accessed under
|
||||
locked mutex_
|
||||
*/
|
||||
FeeMetrics feeMetrics_;
|
||||
/** The queue itself: the collection of transactions ordered
|
||||
by fee level.
|
||||
@note This member must always and only be accessed under
|
||||
locked mutex_
|
||||
*/
|
||||
FeeMultiSet byFee_;
|
||||
/** All of the accounts which currently have any transactions
|
||||
in the queue. Entries are created and destroyed dynamically
|
||||
as transactions are added and removed.
|
||||
@note This member must always and only be accessed under
|
||||
locked mutex_
|
||||
*/
|
||||
AccountMap byAccount_;
|
||||
/** Maximum number of transactions allowed in the queue based
|
||||
on the current metrics. If uninitialized, there is no limit,
|
||||
but that condition cannot last for long in practice.
|
||||
@note This member must always and only be accessed under
|
||||
locked mutex_
|
||||
*/
|
||||
boost::optional<size_t> maxSize_;
|
||||
|
||||
// Most queue operations are done under the master lock,
|
||||
// but use this mutex for the RPC "fee" command, which isn't.
|
||||
/** Most queue operations are done under the master lock,
|
||||
but use this mutex for the RPC "fee" command, which isn't.
|
||||
*/
|
||||
std::mutex mutable mutex_;
|
||||
|
||||
private:
|
||||
/// Is the queue at least `fillPercentage` full?
|
||||
template<size_t fillPercentage = 100>
|
||||
bool
|
||||
isFull() const;
|
||||
|
||||
/** Checks if the indicated transaction fits the conditions
|
||||
for being stored in the queue.
|
||||
*/
|
||||
bool canBeHeld(STTx const&, OpenView const&,
|
||||
AccountMap::iterator,
|
||||
boost::optional<FeeMultiSet::iterator>);
|
||||
|
||||
// Erase and return the next entry in byFee_ (lower fee level)
|
||||
/// Erase and return the next entry in byFee_ (lower fee level)
|
||||
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type);
|
||||
// Erase and return the next entry for the account (if fee level
|
||||
// is higher), or next entry in byFee_ (lower fee level).
|
||||
// Used to get the next "applyable" MaybeTx for accept().
|
||||
/** Erase and return the next entry for the account (if fee level
|
||||
is higher), or next entry in byFee_ (lower fee level).
|
||||
Used to get the next "applyable" MaybeTx for accept().
|
||||
*/
|
||||
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type);
|
||||
// Erase a range of items, based on TxQAccount::TxMap iterators
|
||||
/// Erase a range of items, based on TxQAccount::TxMap iterators
|
||||
TxQAccount::TxMap::iterator
|
||||
erase(TxQAccount& txQAccount, TxQAccount::TxMap::const_iterator begin,
|
||||
TxQAccount::TxMap::const_iterator end);
|
||||
|
||||
/*
|
||||
/**
|
||||
All-or-nothing attempt to try to apply all the queued txs for `accountIter`
|
||||
up to and including `tx`.
|
||||
*/
|
||||
@@ -454,9 +712,15 @@ private:
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
Build a @ref TxQ::Setup object from application configuration.
|
||||
*/
|
||||
TxQ::Setup
|
||||
setup_TxQ(Config const&);
|
||||
|
||||
/**
|
||||
@ref TxQ object factory.
|
||||
*/
|
||||
std::unique_ptr<TxQ>
|
||||
make_TxQ(TxQ::Setup const&, beast::Journal);
|
||||
|
||||
|
||||
@@ -1389,10 +1389,10 @@ TxQ::getMetrics(OpenView const& view, std::uint32_t txCountPadding) const
|
||||
result.txInLedger = view.txCount();
|
||||
result.txPerLedger = snapshot.txnsExpected;
|
||||
result.referenceFeeLevel = baseLevel;
|
||||
result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
|
||||
result.minProcessingFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
|
||||
baseLevel;
|
||||
result.medFeeLevel = snapshot.escalationMultiplier;
|
||||
result.expFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view,
|
||||
result.openLedgerFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view,
|
||||
txCountPadding);
|
||||
|
||||
return result;
|
||||
@@ -1496,9 +1496,9 @@ TxQ::doRPC(Application& app) const
|
||||
ret[jss::max_queue_size] = to_string(*metrics->txQMaxSize);
|
||||
|
||||
levels[jss::reference_level] = to_string(metrics->referenceFeeLevel);
|
||||
levels[jss::minimum_level] = to_string(metrics->minFeeLevel);
|
||||
levels[jss::minimum_level] = to_string(metrics->minProcessingFeeLevel);
|
||||
levels[jss::median_level] = to_string(metrics->medFeeLevel);
|
||||
levels[jss::open_ledger_level] = to_string(metrics->expFeeLevel);
|
||||
levels[jss::open_ledger_level] = to_string(metrics->openLedgerFeeLevel);
|
||||
|
||||
auto const baseFee = view->fees().base;
|
||||
auto& drops = ret[jss::drops] = Json::Value();
|
||||
@@ -1508,16 +1508,16 @@ TxQ::doRPC(Application& app) const
|
||||
metrics->referenceFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second);
|
||||
drops[jss::minimum_fee] = to_string(mulDiv(
|
||||
metrics->minFeeLevel, baseFee,
|
||||
metrics->minProcessingFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second);
|
||||
drops[jss::median_fee] = to_string(mulDiv(
|
||||
metrics->medFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second);
|
||||
auto escalatedFee = mulDiv(
|
||||
metrics->expFeeLevel, baseFee,
|
||||
metrics->openLedgerFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second;
|
||||
if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
|
||||
baseFee).second < metrics->expFeeLevel)
|
||||
baseFee).second < metrics->openLedgerFeeLevel)
|
||||
++escalatedFee;
|
||||
|
||||
drops[jss::open_ledger_fee] = to_string(escalatedFee);
|
||||
|
||||
@@ -707,10 +707,10 @@ Json::Value checkFee (
|
||||
{
|
||||
auto const baseFee = ledger->fees().base;
|
||||
auto escalatedFee = mulDiv(
|
||||
metrics->expFeeLevel, baseFee,
|
||||
metrics->openLedgerFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second;
|
||||
if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
|
||||
baseFee).second < metrics->expFeeLevel)
|
||||
baseFee).second < metrics->openLedgerFeeLevel)
|
||||
++escalatedFee;
|
||||
fee = std::max(fee, escalatedFee);
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ class TxQ_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(metrics.txQMaxSize == expectedMaxCount);
|
||||
BEAST_EXPECT(metrics.txInLedger == expectedInLedger);
|
||||
BEAST_EXPECT(metrics.txPerLedger == expectedPerLedger);
|
||||
BEAST_EXPECT(metrics.minFeeLevel == expectedMinFeeLevel);
|
||||
BEAST_EXPECT(metrics.minProcessingFeeLevel == expectedMinFeeLevel);
|
||||
BEAST_EXPECT(metrics.medFeeLevel == expectedMedFeeLevel);
|
||||
auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ?
|
||||
expectedMedFeeLevel * expectedInLedger * expectedInLedger /
|
||||
(expectedPerLedger * expectedPerLedger) :
|
||||
metrics.referenceFeeLevel;
|
||||
BEAST_EXPECT(metrics.expFeeLevel == expectedCurFeeLevel);
|
||||
BEAST_EXPECT(metrics.openLedgerFeeLevel == expectedCurFeeLevel);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -91,7 +91,7 @@ class TxQ_test : public beast::unit_test::suite
|
||||
return fee(none);
|
||||
|
||||
// Don't care about the overflow flag
|
||||
return fee(mulDiv(metrics->expFeeLevel,
|
||||
return fee(mulDiv(metrics->openLedgerFeeLevel,
|
||||
view.fees().base, metrics->referenceFeeLevel).second + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -2203,7 +2203,8 @@ public:
|
||||
auto metrics = env.app().getTxQ().getMetrics(*env.current());
|
||||
if (!BEAST_EXPECT(metrics))
|
||||
break;
|
||||
if (metrics->expFeeLevel > metrics->minFeeLevel)
|
||||
if (metrics->openLedgerFeeLevel >
|
||||
metrics->minProcessingFeeLevel)
|
||||
break;
|
||||
env(noop(env.master));
|
||||
}
|
||||
|
||||
@@ -1313,7 +1313,7 @@ class LedgerRPC_test : public beast::unit_test::suite
|
||||
auto metrics = env.app().getTxQ().getMetrics(*env.current());
|
||||
if (! BEAST_EXPECT(metrics))
|
||||
break;
|
||||
if (metrics->expFeeLevel > metrics->minFeeLevel)
|
||||
if (metrics->openLedgerFeeLevel > metrics->minProcessingFeeLevel)
|
||||
break;
|
||||
env(noop(alice));
|
||||
}
|
||||
|
||||
@@ -290,14 +290,14 @@ class NoRippleCheckLimits_test : public beast::unit_test::suite
|
||||
env.memoize(gw);
|
||||
env (pay (env.master, gw, XRP(1000)),
|
||||
seq (autofill),
|
||||
fee (txq.getMetrics(*env.current())->expFeeLevel + 1),
|
||||
fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1),
|
||||
sig (autofill));
|
||||
env (fset (gw, asfDefaultRipple),
|
||||
seq (autofill),
|
||||
fee (txq.getMetrics(*env.current())->expFeeLevel + 1),
|
||||
fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1),
|
||||
sig (autofill));
|
||||
env (trust (alice, gw["USD"](10)),
|
||||
fee (txq.getMetrics(*env.current())->expFeeLevel + 1));
|
||||
fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1));
|
||||
env.close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user