mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Change fee escalation algorithms (RIPD-1177):
* Minimum factor 256*500, don't multiply by base fee
* Change autofill fee behavior to pay the open ledger fee.
** Experimental options: x-assume-tx - assume <int> more transactions in
the open queue when computing escalated fee, x-queue-okay - if true
and escalated fee is over limit, try with load fee.
* Port of 75af4ed.
This commit is contained in:
committed by
Nik Bougalis
parent
321e2a94fe
commit
7f52249e40
@@ -35,30 +35,30 @@ consensus process, but will be at least [5](#other-constants).
|
||||
or the number of transactions in the validated ledger.
|
||||
3. Once there are more transactions in the open ledger than indicated
|
||||
by the limit, the required fee level jumps drastically.
|
||||
* The formula is `( baseFeeLevel * lastLedgerMedianFeeLevel *
|
||||
* The formula is `( lastLedgerMedianFeeLevel *
|
||||
TransactionsInOpenLedger^2 / limit^2 )`,
|
||||
and returns a [fee level](#fee-level).
|
||||
4. That may still be pretty small, but as more transactions get
|
||||
into the ledger, the fee level increases exponentially.
|
||||
* For example, if the limit is 6, and the median fee is minimal,
|
||||
and assuming all [reference transactions](#reference-transaction),
|
||||
the 7th transaction only requires a [level](#fee-level) of about 174,000
|
||||
the 8th transaction only requires a [level](#fee-level) of about 174,000
|
||||
or about 6800 drops,
|
||||
but the 20th transaction requires a [level](#fee-level) of about
|
||||
1,422,000 or about 56,000 drops.
|
||||
1,283,000 or about 50,000 drops.
|
||||
5. Finally, as each ledger closes, the median fee level of that ledger is
|
||||
computed and used as `lastLedgerMedianFeeLevel` (with a
|
||||
[minimum value of 500](#other-constants))
|
||||
[minimum value of 128,000](#other-constants))
|
||||
in the fee escalation formula for the next open ledger.
|
||||
* Continuing the example above, if ledger consensus completes with
|
||||
only those 20 transactions, and all of those transactions paid the
|
||||
minimum required fee at each step, the limit will be adjusted from
|
||||
6 to 20, and the `lastLedgerMedianFeeLevel` will be about 393,000,
|
||||
which is 15,000 drops for a
|
||||
6 to 20, and the `lastLedgerMedianFeeLevel` will be about 322,000,
|
||||
which is 12,600 drops for a
|
||||
[reference transaction](#reference-transaction).
|
||||
* This will cause the first 21 transactions only require 10
|
||||
drops, but the 22nd transaction will require
|
||||
a level of about 110,000,000 or about 4.3 million drops (4.3XRP).
|
||||
a level of about 355,000 or about 13,800 drops.
|
||||
|
||||
* This example assumes a cold-start scenario, with a single, possibly
|
||||
malicious, user willing to pay arbitrary amounts to get transactions
|
||||
@@ -80,13 +80,14 @@ allows legitimate users to continue submitting transactions during high
|
||||
traffic periods, and give those transactions a much better chance to
|
||||
succeed.
|
||||
|
||||
1. If an incoming transaction meets the base [fee level](#fee-level),
|
||||
but does not have a high enough [fee level](#fee-level) to immediately
|
||||
go into the open ledger, it is instead put into the queue and broadcast
|
||||
to peers. Each peer will then make an independent decision about whether
|
||||
to put the transaction into its open ledger or the queue. In principle,
|
||||
peers with identical open ledgers will come to identical decisions. Any
|
||||
discrepancies will be resolved as usual during consensus.
|
||||
1. If an incoming transaction meets both the base [fee
|
||||
level](#fee-level) and the load fee minimum, but does not have a high
|
||||
enough [fee level](#fee-level) to immediately go into the open ledger,
|
||||
it is instead put into the queue and broadcast to peers. Each peer will
|
||||
then make an independent decision about whether to put the transaction
|
||||
into its open ledger or the queue. In principle, peers with identical
|
||||
open ledgers will come to identical decisions. Any discrepancies will be
|
||||
resolved as usual during consensus.
|
||||
2. When consensus completes, the open ledger limit is adjusted, and
|
||||
the required [fee level](#fee-level) drops back to the base
|
||||
[fee level](#fee-level). Before the ledger is made available to
|
||||
@@ -105,10 +106,11 @@ but in practice, either
|
||||
times](#other-constants),
|
||||
* its last ledger sequence number will expire,
|
||||
* the user will replace it by submitting another transaction with the same
|
||||
sequence number and at least a 25% higher fee, or
|
||||
sequence number and at least a [25% higher fee](#other-constants), or
|
||||
* it will get dropped when the queue fills up with more valuable transactions.
|
||||
The size limit is computed dynamically, and can hold transactions for
|
||||
the next [20 ledgers](#other-constants).
|
||||
the next [20 ledgers](#other-constants). The lower the transaction's
|
||||
fee, the more likely that it will get dropped if the network is busy.
|
||||
|
||||
If a transaction is submitted for an account with one or more transactions
|
||||
already in the queue, and a sequence number that is sequential with the other
|
||||
@@ -222,6 +224,13 @@ the lifespan of the transaction, and giving a queued transaction a
|
||||
chance to get processed out of the queue before getting discarded,
|
||||
particularly since it may have dependent transactions also in the queue,
|
||||
which will never succeed if this one is discarded.
|
||||
* *Replaced transaction fee increase*. Any transaction in the queue can be
|
||||
replaced by another transaction with the same sequence number and at
|
||||
least a 25% higher fee level. The 25% increase is intended to cover the
|
||||
resource cost incurred by broadcasting the original transaction to the
|
||||
network. This value was chosen experimentally, and can easily change in
|
||||
the future.
|
||||
|
||||
### `fee` command
|
||||
|
||||
**The `fee` RPC and WebSocket command is still experimental, and may
|
||||
@@ -250,13 +259,13 @@ Result format:
|
||||
"reference_level" : "256", // level of a reference transaction. Always 256.
|
||||
"minimum_level" : "256", // minimum fee level to get into the queue. If >256, indicates the queue is full.
|
||||
"median_level" : "281600", // lastLedgerMedianFeeLevel used in escalation calculations.
|
||||
"open_ledger_level" : "82021944" // minimum fee level to get into the open ledger immediately.
|
||||
"open_ledger_level" : "320398" // minimum fee level to get into the open ledger immediately.
|
||||
},
|
||||
"drops" : {
|
||||
"base_fee" : "10", // base fee of a reference transaction in drops.
|
||||
"minimum_fee" : "10", // minimum drops to get a reference transaction into the queue. If >base_fee, indicates the queue is full.
|
||||
"median_fee" : "11000", // drop equivalent of "median_level" for a reference transaction.
|
||||
"open_ledger_fee" : "3203982" // minimum drops to get a reference transaction into the open ledger immediately.
|
||||
"open_ledger_fee" : "12516" // minimum drops to get a reference transaction into the open ledger immediately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2093,7 +2093,7 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
|
||||
info[jss::load] = m_job_queue.getJson ();
|
||||
|
||||
auto const escalationMetrics = app_.getTxQ().getMetrics(
|
||||
app_, *app_.openLedger().current());
|
||||
app_.config(), *app_.openLedger().current());
|
||||
if (!human)
|
||||
{
|
||||
info[jss::load_base] = app_.getFeeTrack ().getLoadBase ();
|
||||
|
||||
@@ -51,13 +51,15 @@ class Application;
|
||||
class TxQ
|
||||
{
|
||||
public:
|
||||
static constexpr std::uint64_t baseLevel = 256;
|
||||
|
||||
struct Setup
|
||||
{
|
||||
std::size_t ledgersInQueue = 20;
|
||||
std::uint32_t retrySequencePercent = 25;
|
||||
// TODO: eahennis. Can we remove the multi tx factor?
|
||||
std::int32_t multiTxnPercent = -90;
|
||||
std::uint32_t minimumEscalationMultiplier = 500;
|
||||
std::uint32_t minimumEscalationMultiplier = baseLevel * 500;
|
||||
std::uint32_t minimumTxnInLedger = 5;
|
||||
std::uint32_t minimumTxnInLedgerSA = 1000;
|
||||
std::uint32_t targetTxnInLedger = 50;
|
||||
@@ -128,7 +130,8 @@ public:
|
||||
/** Returns fee metrics in reference fee level units.
|
||||
*/
|
||||
boost::optional<Metrics>
|
||||
getMetrics(Application& app, OpenView const& view) const;
|
||||
getMetrics(Config const& config, OpenView const& view,
|
||||
std::uint32_t txCountPadding = 0) const;
|
||||
|
||||
/** Packages up fee metrics for the `fee` RPC command.
|
||||
*/
|
||||
@@ -161,9 +164,6 @@ private:
|
||||
|
||||
std::mutex mutable lock_;
|
||||
|
||||
public:
|
||||
static constexpr std::uint64_t baseLevel = 256;
|
||||
|
||||
public:
|
||||
FeeMetrics(Setup const& setup, beast::Journal j)
|
||||
: minimumTxnCount_(setup.standAlone ?
|
||||
@@ -212,7 +212,7 @@ private:
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
scaleFeeLevel(OpenView const& view) const;
|
||||
scaleFeeLevel(OpenView const& view, std::uint32_t txCountPadding = 0) const;
|
||||
};
|
||||
|
||||
// Alternate name: MaybeTx
|
||||
|
||||
@@ -154,12 +154,11 @@ TxQ::FeeMetrics::update(Application& app,
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
TxQ::FeeMetrics::scaleFeeLevel(OpenView const& view) const
|
||||
TxQ::FeeMetrics::scaleFeeLevel(OpenView const& view,
|
||||
std::uint32_t txCountPadding) const
|
||||
{
|
||||
auto fee = baseLevel;
|
||||
|
||||
// Transactions in the open ledger so far
|
||||
auto const current = view.txCount();
|
||||
auto const current = view.txCount() + txCountPadding;
|
||||
|
||||
std::size_t target;
|
||||
std::uint32_t multiplier;
|
||||
@@ -177,11 +176,11 @@ TxQ::FeeMetrics::scaleFeeLevel(OpenView const& view) const
|
||||
{
|
||||
// Compute escalated fee level
|
||||
// Don't care about the overflow flag
|
||||
fee = mulDiv(fee, current * current *
|
||||
multiplier, target * target).second;
|
||||
return mulDiv(multiplier, current * current,
|
||||
target * target).second;
|
||||
}
|
||||
|
||||
return fee;
|
||||
return baseLevel;
|
||||
}
|
||||
|
||||
TxQ::MaybeTx::MaybeTx(
|
||||
@@ -481,7 +480,7 @@ TxQ::apply(Application& app, OpenView& view,
|
||||
// preclaim?
|
||||
auto const baseFee = calculateBaseFee(app, view, *tx, j);
|
||||
auto const feeLevelPaid = getFeeLevelPaid(*tx,
|
||||
feeMetrics_.baseLevel, baseFee, setup_);
|
||||
baseLevel, baseFee, setup_);
|
||||
auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
|
||||
|
||||
auto accountIter = byAccount_.find(account);
|
||||
@@ -758,7 +757,7 @@ TxQ::apply(Application& app, OpenView& view,
|
||||
return{ pcresult.ter, false };
|
||||
|
||||
// Too low of a fee should get caught by preclaim
|
||||
assert(feeLevelPaid >= feeMetrics_.baseLevel);
|
||||
assert(feeLevelPaid >= baseLevel);
|
||||
|
||||
JLOG(j_.trace()) << "Transaction " <<
|
||||
transactionID <<
|
||||
@@ -1118,12 +1117,13 @@ TxQ::accept(Application& app,
|
||||
}
|
||||
|
||||
auto
|
||||
TxQ::getMetrics(Application& app, OpenView const& view) const
|
||||
-> boost::optional<Metrics>
|
||||
TxQ::getMetrics(Config const& config, OpenView const& view,
|
||||
std::uint32_t txCountPadding) const
|
||||
-> boost::optional<Metrics>
|
||||
{
|
||||
auto const allowEscalation =
|
||||
(view.rules().enabled(featureFeeEscalation,
|
||||
app.config().features));
|
||||
config.features));
|
||||
if (!allowEscalation)
|
||||
return boost::none;
|
||||
|
||||
@@ -1135,11 +1135,11 @@ TxQ::getMetrics(Application& app, OpenView const& view) const
|
||||
result.txQMaxSize = maxSize_;
|
||||
result.txInLedger = view.txCount();
|
||||
result.txPerLedger = feeMetrics_.getTxnsExpected();
|
||||
result.referenceFeeLevel = feeMetrics_.baseLevel;
|
||||
result.referenceFeeLevel = baseLevel;
|
||||
result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
|
||||
feeMetrics_.baseLevel;
|
||||
baseLevel;
|
||||
result.medFeeLevel = feeMetrics_.getEscalationMultiplier();
|
||||
result.expFeeLevel = feeMetrics_.scaleFeeLevel(view);
|
||||
result.expFeeLevel = feeMetrics_.scaleFeeLevel(view, txCountPadding);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1150,8 +1150,8 @@ TxQ::doRPC(Application& app) const
|
||||
using std::to_string;
|
||||
|
||||
auto const view = app.openLedger().current();
|
||||
auto const metrics = getMetrics(app, *view);
|
||||
assert(metrics);
|
||||
auto const metrics = getMetrics(app.config(), *view);
|
||||
|
||||
if (!metrics)
|
||||
return{};
|
||||
|
||||
@@ -1183,9 +1183,14 @@ TxQ::doRPC(Application& app) const
|
||||
drops[jss::median_fee] = to_string(mulDiv(
|
||||
metrics->medFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second);
|
||||
drops[jss::open_ledger_fee] = to_string(mulDiv(
|
||||
auto escalatedFee = mulDiv(
|
||||
metrics->expFeeLevel, baseFee,
|
||||
metrics->referenceFeeLevel).second);
|
||||
metrics->referenceFeeLevel).second;
|
||||
if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
|
||||
baseFee).second < metrics->expFeeLevel)
|
||||
++escalatedFee;
|
||||
|
||||
drops[jss::open_ledger_fee] = to_string(escalatedFee);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user