Update TxQ developer docs:

* Rename a couple of member variables for clarity.
This commit is contained in:
Edward Hennis
2016-12-28 19:33:13 -05:00
committed by Nik Bougalis
parent cd1c5a30dd
commit e14f913244
9 changed files with 362 additions and 93 deletions

View File

@@ -105,6 +105,9 @@ WARN_LOGFILE =
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
INPUT = \ 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/STObject.h \
../src/ripple/protocol/JsonFields.h \ ../src/ripple/protocol/JsonFields.h \
../src/test/jtx/AbstractClient.h \ ../src/test/jtx/AbstractClient.h \

View File

@@ -1609,8 +1609,8 @@ NetworkOPsImp::ServerFeeSummary::operator !=(NetworkOPsImp::ServerFeeSummary con
if(em && b.em) if(em && b.em)
{ {
return (em->minFeeLevel != b.em->minFeeLevel || return (em->minProcessingFeeLevel != b.em->minProcessingFeeLevel ||
em->expFeeLevel != b.em->expFeeLevel || em->openLedgerFeeLevel != b.em->openLedgerFeeLevel ||
em->referenceFeeLevel != b.em->referenceFeeLevel); em->referenceFeeLevel != b.em->referenceFeeLevel);
} }
@@ -1653,12 +1653,12 @@ void NetworkOPsImp::pubServer ()
{ {
auto const loadFactor = auto const loadFactor =
std::max(static_cast<std::uint64_t>(f.loadFactorServer), 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); f.em->referenceFeeLevel).second);
jvObj [jss::load_factor] = clamp(loadFactor); jvObj [jss::load_factor] = clamp(loadFactor);
jvObj [jss::load_factor_fee_escalation] = clamp(f.em->expFeeLevel); jvObj [jss::load_factor_fee_escalation] = clamp(f.em->openLedgerFeeLevel);
jvObj [jss::load_factor_fee_queue] = clamp(f.em->minFeeLevel); jvObj [jss::load_factor_fee_queue] = clamp(f.em->minProcessingFeeLevel);
jvObj [jss::load_factor_fee_reference] jvObj [jss::load_factor_fee_reference]
= clamp(f.em->referenceFeeLevel); = 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 loadFactorServer = app_.getFeeTrack().getLoadFactor();
auto const loadBaseServer = app_.getFeeTrack().getLoadBase(); auto const loadBaseServer = app_.getFeeTrack().getLoadBase();
auto const loadFactorFeeEscalation = escalationMetrics ? auto const loadFactorFeeEscalation = escalationMetrics ?
escalationMetrics->expFeeLevel : 1; escalationMetrics->openLedgerFeeLevel : 1;
auto const loadBaseFeeEscalation = escalationMetrics ? auto const loadBaseFeeEscalation = escalationMetrics ?
escalationMetrics->referenceFeeLevel : 1; escalationMetrics->referenceFeeLevel : 1;
@@ -2221,7 +2221,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
max32, loadFactorFeeEscalation)); max32, loadFactorFeeEscalation));
info[jss::load_factor_fee_queue] = info[jss::load_factor_fee_queue] =
static_cast<std::uint32_t> (std::min( static_cast<std::uint32_t> (std::min(
max32, escalationMetrics->minFeeLevel)); max32, escalationMetrics->minProcessingFeeLevel));
info[jss::load_factor_fee_reference] = info[jss::load_factor_fee_reference] =
static_cast<std::uint32_t> (std::min( static_cast<std::uint32_t> (std::min(
max32, loadBaseFeeEscalation)); max32, loadBaseFeeEscalation));
@@ -2258,11 +2258,12 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
info[jss::load_factor_fee_escalation] = info[jss::load_factor_fee_escalation] =
static_cast<double> (loadFactorFeeEscalation) / static_cast<double> (loadFactorFeeEscalation) /
escalationMetrics->referenceFeeLevel; escalationMetrics->referenceFeeLevel;
if (escalationMetrics->minFeeLevel != if (escalationMetrics->minProcessingFeeLevel !=
escalationMetrics->referenceFeeLevel) escalationMetrics->referenceFeeLevel)
info[jss::load_factor_fee_queue] = info[jss::load_factor_fee_queue] =
static_cast<double> (escalationMetrics->minFeeLevel) / static_cast<double> (
escalationMetrics->referenceFeeLevel; escalationMetrics->minProcessingFeeLevel) /
escalationMetrics->referenceFeeLevel;
} }
} }

View File

@@ -39,7 +39,7 @@ class Config;
Once enough transactions are added to the open ledger, the required Once enough transactions are added to the open ledger, the required
fee will jump dramatically. If additional transactions are added, 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 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 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 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 until either all transactions are applied or the fee again jumps
too high for the remaining transactions. 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 class TxQ
{ {
public: public:
/// Fee level for single-signed reference transaction.
static constexpr std::uint64_t baseLevel = 256; static constexpr std::uint64_t baseLevel = 256;
/**
Structure used to customize @ref TxQ behavior.
*/
struct Setup struct Setup
{ {
/// Default constructor
explicit Setup() = default; 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; 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; 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; 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; 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; 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; 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; std::uint32_t minimumTxnInLedgerSA = 1000;
/// Number of transactions per ledger that fee escalation "works
/// towards".
std::uint32_t targetTxnInLedger = 50; 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; boost::optional<std::uint32_t> maximumTxnInLedger;
/// Maximum number of transactions that can be queued by one account.
std::uint32_t maximumTxnPerAccount = 10; 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; std::uint32_t minimumLastLedgerBuffer = 2;
/* So we don't deal with infinite fee levels, treat /** So we don't deal with "infinite" fee levels, treat
any transaction with a 0 base fee (ie. SetRegularKey any transaction with a 0 base fee (ie SetRegularKey
password recovery) as having this fee level. password recovery) as having this fee level.
Should the network behavior change in the future such Should the network behavior change in the future such
that these transactions are unable to be processed, that these transactions are unable to be processed,
@@ -78,56 +143,115 @@ public:
bikeshedding for now. bikeshedding for now.
*/ */
std::uint64_t zeroBaseFeeTransactionFeeLevel = 256000; std::uint64_t zeroBaseFeeTransactionFeeLevel = 256000;
/// Use standalone mode behavior.
bool standAlone = false; bool standAlone = false;
}; };
/**
Structure returned by @ref TxQ::getMetrics, expressed in
reference fee level units.
*/
struct Metrics struct Metrics
{ {
/// Default constructor
explicit Metrics() = default; explicit Metrics() = default;
std::size_t txCount; // Transactions in the queue /// Number of transactions in the queue
boost::optional<std::size_t> txQMaxSize; // Max txns in queue std::size_t txCount;
std::size_t txInLedger; // Amount currently in the ledger /// Max transactions currently allowed in queue
std::size_t txPerLedger; // Amount expected per ledger boost::optional<std::size_t> txQMaxSize;
std::uint64_t referenceFeeLevel; // Reference transaction fee level /// Number of transactions currently in the open ledger
std::uint64_t minFeeLevel; // Minimum fee level to get in the queue std::size_t txInLedger;
std::uint64_t medFeeLevel; // Median fee level of the last ledger /// Number of transactions expected per ledger
std::uint64_t expFeeLevel; // Estimated fee level to get in next 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 struct AccountTxDetails
{ {
/// Default constructor
explicit AccountTxDetails() = default; explicit AccountTxDetails() = default;
/// Fee level of the queued transaction
uint64_t feeLevel; uint64_t feeLevel;
/// LastValidLedger field of the queued transaction, if any
boost::optional<LedgerIndex const> lastValid; 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; 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 struct TxDetails : AccountTxDetails
{ {
/// Default constructor
explicit TxDetails() = default; explicit TxDetails() = default;
/// The account the transaction is queued for
AccountID account; AccountID account;
/// The full transaction
std::shared_ptr<STTx const> txn; 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; 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; 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; boost::optional<TER> lastResult;
}; };
/// Constructor
TxQ(Setup const& setup, TxQ(Setup const& setup,
beast::Journal j); beast::Journal j);
/// Destructor
virtual ~TxQ(); virtual ~TxQ();
/** /**
Add a new transaction to the open ledger, hold it in the queue, Add a new transaction to the open ledger, hold it in the queue,
or reject it. or reject it.
@return A pair with the TER and a bool indicating @return A pair with the `TER` and a `bool` indicating
whether or not the transaction was applied. whether or not the transaction was applied to
If the transaction is queued, will return the open ledger. If the transaction is queued,
{ terQUEUED, false }. will return `{ terQUEUED, false }`.
*/ */
std::pair<TER, bool> std::pair<TER, bool>
apply(Application& app, OpenView& view, apply(Application& app, OpenView& view,
@@ -136,17 +260,29 @@ public:
/** /**
Fill the new open ledger with transactions from the queue. 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 bool
accept(Application& app, OpenView& view); 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. 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 void
processClosedLedger(Application& app, processClosedLedger(Application& app,
@@ -154,8 +290,8 @@ public:
/** Returns fee metrics in reference fee level units. /** Returns fee metrics in reference fee level units.
@returns Uninitialized @ref optional if the FeeEscalation @returns Uninitialized boost::optional if the
amendment is not enabled. FeeEscalation amendment is not enabled.
*/ */
boost::optional<Metrics> boost::optional<Metrics>
getMetrics(OpenView const& view, getMetrics(OpenView const& view,
@@ -164,9 +300,9 @@ public:
/** Returns information about the transactions currently /** Returns information about the transactions currently
in the queue for the account. in the queue for the account.
@returns Uninitialized @ref optional if the FeeEscalation @returns Empty `map` if the
amendment is not enabled, OR if the account has no transactions FeeEscalation amendment is not enabled, OR if the
in the queue. account has no transactions in the queue.
*/ */
std::map<TxSeq, AccountTxDetails const> std::map<TxSeq, AccountTxDetails const>
getAccountTxs(AccountID const& account, ReadView const& view) const; getAccountTxs(AccountID const& account, ReadView const& view) const;
@@ -174,46 +310,53 @@ public:
/** Returns information about all transactions currently /** Returns information about all transactions currently
in the queue. 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 amendment is not enabled, OR if there are no transactions
in the queue. in the queue.
*/ */
std::vector<TxDetails> std::vector<TxDetails>
getTxs(ReadView const& view) const; 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 Json::Value
doRPC(Application& app) const; doRPC(Application& app) const;
private: 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 class FeeMetrics
{ {
private: private:
// Fee escalation /// Minimum value of txnsExpected.
// Minimum value of txnsExpected.
std::size_t const minimumTxnCount_; std::size_t const minimumTxnCount_;
// Limit of the txnsExpected value after a /// Number of transactions per ledger that fee escalation "works
// time leap. /// towards".
std::size_t const targetTxnCount_; std::size_t const targetTxnCount_;
// Maximum value of txnsExpected /// Maximum value of txnsExpected
boost::optional<std::size_t> const maximumTxnCount_; boost::optional<std::size_t> const maximumTxnCount_;
// Number of transactions expected per ledger. /// Number of transactions expected per ledger.
// One more than this value will be accepted /// One more than this value will be accepted
// before escalation kicks in. /// before escalation kicks in.
std::size_t txnsExpected_; std::size_t txnsExpected_;
// Recent history of transaction counts that /// Recent history of transaction counts that
// exceed the targetTxnCount_ /// exceed the targetTxnCount_
boost::circular_buffer<std::size_t> recentTxnCounts_; boost::circular_buffer<std::size_t> recentTxnCounts_;
// Minimum value of escalationMultiplier. /// Minimum value of escalationMultiplier.
std::uint64_t const minimumMultiplier_; std::uint64_t const minimumMultiplier_;
// Based on the median fee of the LCL. Used /// Based on the median fee of the LCL. Used
// when fee escalation kicks in. /// when fee escalation kicks in.
std::uint64_t escalationMultiplier_; std::uint64_t escalationMultiplier_;
/// Journal
beast::Journal j_; beast::Journal j_;
public: public:
/// Constructor
FeeMetrics(Setup const& setup, beast::Journal j) FeeMetrics(Setup const& setup, beast::Journal j)
: minimumTxnCount_(setup.standAlone ? : minimumTxnCount_(setup.standAlone ?
setup.minimumTxnInLedgerSA : setup.minimumTxnInLedgerSA :
@@ -236,9 +379,11 @@ private:
Updates fee metrics based on the transactions in the ReadView Updates fee metrics based on the transactions in the ReadView
for use in fee escalation calculations. for use in fee escalation calculations.
@param app Rippled Application object.
@param view View of the LCL that was just closed or received. @param view View of the LCL that was just closed or received.
@param timeLeap Indicates that rippled is under load so fees @param timeLeap Indicates that rippled is under load so fees
should grow faster. should grow faster.
@param setup Customization params.
*/ */
std::size_t std::size_t
update(Application& app, update(Application& app,
@@ -258,6 +403,7 @@ private:
std::uint64_t const escalationMultiplier; std::uint64_t const escalationMultiplier;
}; };
/// Get the current @ref Snapshot
Snapshot Snapshot
getSnapshot() const 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 static
std::uint64_t std::uint64_t
scaleFeeLevel(Snapshot const& snapshot, OpenView const& view, scaleFeeLevel(Snapshot const& snapshot, OpenView const& view,
std::uint32_t txCountPadding = 0); std::uint32_t txCountPadding = 0);
/** /**
Returns the total fee level for all transactions in a series. Computes the total fee level for all transactions in a series.
Assumes that there are already more than txnsExpected_ txns in Assumes that there are already more than @ref txnsExpected_ txns
the view. If there aren't, the math still works, but the level between the view and `extraCount`. If there aren't, the result
will be high. 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 @note A "series" is a set of transactions for the same account
the calculation result is safe. 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 static
std::pair<bool, std::uint64_t> std::pair<bool, std::uint64_t>
@@ -287,34 +468,65 @@ private:
std::size_t extraCount, std::size_t seriesSize); std::size_t extraCount, std::size_t seriesSize);
}; };
/**
Represents transactions in the queue which may be applied
later to the open ledger.
*/
class MaybeTx class MaybeTx
{ {
public: public:
// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below /// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below
// to put each MaybeTx object into more than one /// to put each MaybeTx object into more than one
// set without copies, pointers, etc. /// set without copies, pointers, etc.
boost::intrusive::set_member_hook<> byFeeListHook; boost::intrusive::set_member_hook<> byFeeListHook;
/// The complete transaction.
std::shared_ptr<STTx const> txn; std::shared_ptr<STTx const> txn;
/// Potential @ref TxConsequences of applying this transaction
/// to the open ledger.
boost::optional<TxConsequences const> consequences; boost::optional<TxConsequences const> consequences;
/// Computed fee level that the transaction will pay.
uint64_t const feeLevel; uint64_t const feeLevel;
/// Transaction ID.
TxID const txID; TxID const txID;
/// Prior transaction ID (`sfAccountTxnID` field).
boost::optional<TxID> priorTxID; boost::optional<TxID> priorTxID;
/// Account submitting the transaction.
AccountID const account; AccountID const account;
/// Expiration ledger for the transaction
/// (`sfLastLedgerSequence` field).
boost::optional<LedgerIndex> lastValid; 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; int retriesRemaining;
/// Transaction sequence number (`sfSequence` field).
TxSeq const sequence; 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; ApplyFlags const flags;
boost::optional<TER> lastResult; boost::optional<TER> lastResult;
// Invariant: pfresult is never allowed to be empty. The /** Cached result of the `preflight` operation. Because
// boost::optional is leveraged to allow `emplace`d `preflight` is expensive, minimize the number of times
// construction and replacement without a copy it needs to be done.
// assignment operation. @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; 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 enough that this transaction gets a chance to apply
to the ledger, but it may get a retry ter result for to the ledger, but it may get a retry ter result for
another reason (eg. insufficient balance). When that another reason (eg. insufficient balance). When that
@@ -329,33 +541,42 @@ private:
static constexpr int retriesAllowed = 10; static constexpr int retriesAllowed = 10;
public: public:
/// Constructor
MaybeTx(std::shared_ptr<STTx const> const&, MaybeTx(std::shared_ptr<STTx const> const&,
TxID const& txID, std::uint64_t feeLevel, TxID const& txID, std::uint64_t feeLevel,
ApplyFlags const flags, ApplyFlags const flags,
PreflightResult const& pfresult); PreflightResult const& pfresult);
/// Attempt to apply the queued transaction to the open ledger.
std::pair<TER, bool> std::pair<TER, bool>
apply(Application& app, OpenView& view, beast::Journal j); apply(Application& app, OpenView& view, beast::Journal j);
}; };
/// Used for sorting @ref MaybeTx by `feeLevel`
class GreaterFee class GreaterFee
{ {
public: public:
/// Default constructor
explicit GreaterFee() = default; 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 bool operator()(const MaybeTx& lhs, const MaybeTx& rhs) const
{ {
return lhs.feeLevel > rhs.feeLevel; 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 class TxQAccount
{ {
public: public:
using TxMap = std::map <TxSeq, MaybeTx>; using TxMap = std::map <TxSeq, MaybeTx>;
/// The account
AccountID const account; AccountID const account;
// Sequence number will be used as the key. /// Sequence number will be used as the key.
TxMap transactions; TxMap transactions;
/* If this account has had any transaction retry more than /* If this account has had any transaction retry more than
`retriesAllowed` times so that it was dropped from the `retriesAllowed` times so that it was dropped from the
@@ -373,24 +594,34 @@ private:
bool dropPenalty = false; bool dropPenalty = false;
public: public:
/// Construct from a transaction
explicit TxQAccount(std::shared_ptr<STTx const> const& txn); explicit TxQAccount(std::shared_ptr<STTx const> const& txn);
/// Construct from an account
explicit TxQAccount(const AccountID& account); explicit TxQAccount(const AccountID& account);
/// Return the number of transactions currently queued for this account
std::size_t std::size_t
getTxnCount() const getTxnCount() const
{ {
return transactions.size(); return transactions.size();
} }
/// Checks if this account has no transactions queued
bool bool
empty() const empty() const
{ {
return !getTxnCount(); return !getTxnCount();
} }
/// Add a transaction candidate to this account for queuing
MaybeTx& MaybeTx&
add(MaybeTx&&); add(MaybeTx&&);
/** Remove the candidate with given sequence number from this
account.
@return Whether a candidate was removed
*/
bool bool
remove(TxSeq const& sequence); remove(TxSeq const& sequence);
}; };
@@ -405,41 +636,68 @@ private:
using AccountMap = std::map <AccountID, TxQAccount>; using AccountMap = std::map <AccountID, TxQAccount>;
/// Setup parameters used to control the behavior of the queue
Setup const setup_; Setup const setup_;
/// Journal
beast::Journal j_; beast::Journal j_;
// These members must always and only be accessed under /** Tracks the current state of the queue.
// locked mutex_ @note This member must always and only be accessed under
locked mutex_
*/
FeeMetrics feeMetrics_; 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_; 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_; 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_; boost::optional<size_t> maxSize_;
// Most queue operations are done under the master lock, /** Most queue operations are done under the master lock,
// but use this mutex for the RPC "fee" command, which isn't. but use this mutex for the RPC "fee" command, which isn't.
*/
std::mutex mutable mutex_; std::mutex mutable mutex_;
private: private:
/// Is the queue at least `fillPercentage` full?
template<size_t fillPercentage = 100> template<size_t fillPercentage = 100>
bool bool
isFull() const; isFull() const;
/** Checks if the indicated transaction fits the conditions
for being stored in the queue.
*/
bool canBeHeld(STTx const&, OpenView const&, bool canBeHeld(STTx const&, OpenView const&,
AccountMap::iterator, AccountMap::iterator,
boost::optional<FeeMultiSet::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); FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type);
// Erase and return the next entry for the account (if fee level /** Erase and return the next entry for the account (if fee level
// is higher), or next entry in byFee_ (lower fee level). is higher), or next entry in byFee_ (lower fee level).
// Used to get the next "applyable" MaybeTx for accept(). Used to get the next "applyable" MaybeTx for accept().
*/
FeeMultiSet::iterator_type eraseAndAdvance(FeeMultiSet::const_iterator_type); 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 TxQAccount::TxMap::iterator
erase(TxQAccount& txQAccount, TxQAccount::TxMap::const_iterator begin, erase(TxQAccount& txQAccount, TxQAccount::TxMap::const_iterator begin,
TxQAccount::TxMap::const_iterator end); TxQAccount::TxMap::const_iterator end);
/* /**
All-or-nothing attempt to try to apply all the queued txs for `accountIter` All-or-nothing attempt to try to apply all the queued txs for `accountIter`
up to and including `tx`. up to and including `tx`.
*/ */
@@ -454,9 +712,15 @@ private:
}; };
/**
Build a @ref TxQ::Setup object from application configuration.
*/
TxQ::Setup TxQ::Setup
setup_TxQ(Config const&); setup_TxQ(Config const&);
/**
@ref TxQ object factory.
*/
std::unique_ptr<TxQ> std::unique_ptr<TxQ>
make_TxQ(TxQ::Setup const&, beast::Journal); make_TxQ(TxQ::Setup const&, beast::Journal);

View File

@@ -1389,10 +1389,10 @@ TxQ::getMetrics(OpenView const& view, std::uint32_t txCountPadding) const
result.txInLedger = view.txCount(); result.txInLedger = view.txCount();
result.txPerLedger = snapshot.txnsExpected; result.txPerLedger = snapshot.txnsExpected;
result.referenceFeeLevel = baseLevel; result.referenceFeeLevel = baseLevel;
result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 : result.minProcessingFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
baseLevel; baseLevel;
result.medFeeLevel = snapshot.escalationMultiplier; result.medFeeLevel = snapshot.escalationMultiplier;
result.expFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view, result.openLedgerFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view,
txCountPadding); txCountPadding);
return result; return result;
@@ -1496,9 +1496,9 @@ TxQ::doRPC(Application& app) const
ret[jss::max_queue_size] = to_string(*metrics->txQMaxSize); ret[jss::max_queue_size] = to_string(*metrics->txQMaxSize);
levels[jss::reference_level] = to_string(metrics->referenceFeeLevel); 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::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 const baseFee = view->fees().base;
auto& drops = ret[jss::drops] = Json::Value(); auto& drops = ret[jss::drops] = Json::Value();
@@ -1508,16 +1508,16 @@ TxQ::doRPC(Application& app) const
metrics->referenceFeeLevel, baseFee, metrics->referenceFeeLevel, baseFee,
metrics->referenceFeeLevel).second); metrics->referenceFeeLevel).second);
drops[jss::minimum_fee] = to_string(mulDiv( drops[jss::minimum_fee] = to_string(mulDiv(
metrics->minFeeLevel, baseFee, metrics->minProcessingFeeLevel, baseFee,
metrics->referenceFeeLevel).second); metrics->referenceFeeLevel).second);
drops[jss::median_fee] = to_string(mulDiv( drops[jss::median_fee] = to_string(mulDiv(
metrics->medFeeLevel, baseFee, metrics->medFeeLevel, baseFee,
metrics->referenceFeeLevel).second); metrics->referenceFeeLevel).second);
auto escalatedFee = mulDiv( auto escalatedFee = mulDiv(
metrics->expFeeLevel, baseFee, metrics->openLedgerFeeLevel, baseFee,
metrics->referenceFeeLevel).second; metrics->referenceFeeLevel).second;
if (mulDiv(escalatedFee, metrics->referenceFeeLevel, if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
baseFee).second < metrics->expFeeLevel) baseFee).second < metrics->openLedgerFeeLevel)
++escalatedFee; ++escalatedFee;
drops[jss::open_ledger_fee] = to_string(escalatedFee); drops[jss::open_ledger_fee] = to_string(escalatedFee);

View File

@@ -707,10 +707,10 @@ Json::Value checkFee (
{ {
auto const baseFee = ledger->fees().base; auto const baseFee = ledger->fees().base;
auto escalatedFee = mulDiv( auto escalatedFee = mulDiv(
metrics->expFeeLevel, baseFee, metrics->openLedgerFeeLevel, baseFee,
metrics->referenceFeeLevel).second; metrics->referenceFeeLevel).second;
if (mulDiv(escalatedFee, metrics->referenceFeeLevel, if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
baseFee).second < metrics->expFeeLevel) baseFee).second < metrics->openLedgerFeeLevel)
++escalatedFee; ++escalatedFee;
fee = std::max(fee, escalatedFee); fee = std::max(fee, escalatedFee);
} }

View File

@@ -59,13 +59,13 @@ class TxQ_test : public beast::unit_test::suite
BEAST_EXPECT(metrics.txQMaxSize == expectedMaxCount); BEAST_EXPECT(metrics.txQMaxSize == expectedMaxCount);
BEAST_EXPECT(metrics.txInLedger == expectedInLedger); BEAST_EXPECT(metrics.txInLedger == expectedInLedger);
BEAST_EXPECT(metrics.txPerLedger == expectedPerLedger); BEAST_EXPECT(metrics.txPerLedger == expectedPerLedger);
BEAST_EXPECT(metrics.minFeeLevel == expectedMinFeeLevel); BEAST_EXPECT(metrics.minProcessingFeeLevel == expectedMinFeeLevel);
BEAST_EXPECT(metrics.medFeeLevel == expectedMedFeeLevel); BEAST_EXPECT(metrics.medFeeLevel == expectedMedFeeLevel);
auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ? auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ?
expectedMedFeeLevel * expectedInLedger * expectedInLedger / expectedMedFeeLevel * expectedInLedger * expectedInLedger /
(expectedPerLedger * expectedPerLedger) : (expectedPerLedger * expectedPerLedger) :
metrics.referenceFeeLevel; metrics.referenceFeeLevel;
BEAST_EXPECT(metrics.expFeeLevel == expectedCurFeeLevel); BEAST_EXPECT(metrics.openLedgerFeeLevel == expectedCurFeeLevel);
} }
void void
@@ -91,7 +91,7 @@ class TxQ_test : public beast::unit_test::suite
return fee(none); return fee(none);
// Don't care about the overflow flag // Don't care about the overflow flag
return fee(mulDiv(metrics->expFeeLevel, return fee(mulDiv(metrics->openLedgerFeeLevel,
view.fees().base, metrics->referenceFeeLevel).second + 1); view.fees().base, metrics->referenceFeeLevel).second + 1);
} }

View File

@@ -2203,7 +2203,8 @@ public:
auto metrics = env.app().getTxQ().getMetrics(*env.current()); auto metrics = env.app().getTxQ().getMetrics(*env.current());
if (!BEAST_EXPECT(metrics)) if (!BEAST_EXPECT(metrics))
break; break;
if (metrics->expFeeLevel > metrics->minFeeLevel) if (metrics->openLedgerFeeLevel >
metrics->minProcessingFeeLevel)
break; break;
env(noop(env.master)); env(noop(env.master));
} }

View File

@@ -1313,7 +1313,7 @@ class LedgerRPC_test : public beast::unit_test::suite
auto metrics = env.app().getTxQ().getMetrics(*env.current()); auto metrics = env.app().getTxQ().getMetrics(*env.current());
if (! BEAST_EXPECT(metrics)) if (! BEAST_EXPECT(metrics))
break; break;
if (metrics->expFeeLevel > metrics->minFeeLevel) if (metrics->openLedgerFeeLevel > metrics->minProcessingFeeLevel)
break; break;
env(noop(alice)); env(noop(alice));
} }

View File

@@ -290,14 +290,14 @@ class NoRippleCheckLimits_test : public beast::unit_test::suite
env.memoize(gw); env.memoize(gw);
env (pay (env.master, gw, XRP(1000)), env (pay (env.master, gw, XRP(1000)),
seq (autofill), seq (autofill),
fee (txq.getMetrics(*env.current())->expFeeLevel + 1), fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1),
sig (autofill)); sig (autofill));
env (fset (gw, asfDefaultRipple), env (fset (gw, asfDefaultRipple),
seq (autofill), seq (autofill),
fee (txq.getMetrics(*env.current())->expFeeLevel + 1), fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1),
sig (autofill)); sig (autofill));
env (trust (alice, gw["USD"](10)), env (trust (alice, gw["USD"](10)),
fee (txq.getMetrics(*env.current())->expFeeLevel + 1)); fee (txq.getMetrics(*env.current())->openLedgerFeeLevel + 1));
env.close(); env.close();
} }