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:
Edward Hennis
2016-06-02 20:12:26 -04:00
committed by Nik Bougalis
parent 321e2a94fe
commit 7f52249e40
13 changed files with 316 additions and 128 deletions

View File

@@ -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.
}
}
}

View File

@@ -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 ();

View File

@@ -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

View File

@@ -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
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;
}

View File

@@ -45,10 +45,10 @@ class TxQ_test : public beast::unit_test::suite
std::size_t expectedInLedger,
std::size_t expectedPerLedger,
std::uint64_t expectedMinFeeLevel,
std::uint64_t expectedMedFeeLevel = 500)
std::uint64_t expectedMedFeeLevel = 256 * 500)
{
auto optMetrics = env.app().getTxQ().getMetrics(env.app(),
*env.current());
auto optMetrics = env.app().getTxQ().getMetrics(
env.app().config(), *env.current());
if (!expect(optMetrics))
return;
auto& metrics = *optMetrics;
@@ -60,8 +60,7 @@ class TxQ_test : public beast::unit_test::suite
expect(metrics.minFeeLevel == expectedMinFeeLevel, "minFeeLevel");
expect(metrics.medFeeLevel == expectedMedFeeLevel, "medFeeLevel");
auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ?
metrics.referenceFeeLevel * expectedMedFeeLevel *
expectedInLedger * expectedInLedger /
expectedMedFeeLevel * expectedInLedger * expectedInLedger /
(expectedPerLedger * expectedPerLedger) :
metrics.referenceFeeLevel;
expect(metrics.expFeeLevel == expectedCurFeeLevel, "expFeeLevel");
@@ -72,7 +71,7 @@ class TxQ_test : public beast::unit_test::suite
jtx::Env& env,
jtx::Account const& account)
{
auto metrics = env.app().getTxQ().getMetrics(env.app(),
auto metrics = env.app().getTxQ().getMetrics(env.app().config(),
*env.current());
if (!expect(metrics))
return;
@@ -86,7 +85,7 @@ class TxQ_test : public beast::unit_test::suite
using namespace jtx;
auto const& view = *env.current();
auto metrics = env.app().getTxQ().getMetrics(env.app(),
auto metrics = env.app().getTxQ().getMetrics(env.app().config(),
view);
if (!expect(metrics))
return fee(none);
@@ -222,7 +221,7 @@ public:
// Close again with a simulated time leap to
// reset the escalation limit down to minimum
env.close(env.now() + 5s, 10000ms);
checkMetrics(env, 0, 16, 0, 3, 256, 76928);
checkMetrics(env, 0, 16, 0, 3, 256);
// Then close once more without the time leap
// to reset the queue maxsize down to minimum
env.close();
@@ -232,10 +231,10 @@ public:
// Stuff the ledger and queue so we can verify that
// stuff gets kicked out.
env(noop(hank));
env(noop(gwen));
env(noop(fred));
env(noop(elmo));
env(noop(hank), fee(7000));
env(noop(gwen), fee(7000));
env(noop(fred), fee(7000));
env(noop(elmo), fee(7000));
checkMetrics(env, 0, 6, 4, 3, 256);
// Use explicit fees so we can control which txn
@@ -270,7 +269,7 @@ public:
// point, daria's transaction that was dropped from the queue
// is put back in. Neat.
env.close();
checkMetrics(env, 2, 8, 5, 4, 256);
checkMetrics(env, 2, 8, 5, 4, 256, 256 * 700);
env.close();
checkMetrics(env, 0, 10, 2, 5, 256);
@@ -282,7 +281,7 @@ public:
// we can be sure that there's one in the queue when the
// test ends and the TxQ is destructed.
auto metrics = txq.getMetrics(env.app(), *env.current());
auto metrics = txq.getMetrics(env.app().config(), *env.current());
expect(metrics->txCount == 0, "txCount");
// Stuff the ledger.
@@ -399,50 +398,50 @@ public:
queued);
// Queue items with higher fees to force the previous
// txn to wait.
env(noop(bob), fee(20), queued);
env(noop(charlie), fee(20), queued);
env(noop(daria), fee(20), queued);
env(noop(edgar), fee(20), queued);
env(noop(bob), fee(7000), queued);
env(noop(charlie), fee(7000), queued);
env(noop(daria), fee(7000), queued);
env(noop(edgar), fee(7000), queued);
checkMetrics(env, 5, boost::none, 3, 2, 256);
env.close();
checkMetrics(env, 1, 6, 4, 3, 256);
// Keep alice's transaction waiting.
env(noop(bob), fee(20), queued);
env(noop(charlie), fee(20), queued);
env(noop(daria), fee(20), queued);
env(noop(edgar), fee(20), queued);
env(noop(felicia), fee(20), queued);
env(noop(bob), fee(7000), queued);
env(noop(charlie), fee(7000), queued);
env(noop(daria), fee(7000), queued);
env(noop(edgar), fee(7000), queued);
env(noop(felicia), fee(7000), queued);
checkMetrics(env, 6, 6, 4, 3, 257);
env.close();
// alice's transaction is still hanging around
checkMetrics(env, 1, 8, 5, 4, 256, 512);
checkMetrics(env, 1, 8, 5, 4, 256, 700 * 256);
expect(env.seq(alice) == 1);
// Keep alice's transaction waiting.
env(noop(bob), fee(20), queued);
env(noop(charlie), fee(20), queued);
env(noop(daria), fee(20), queued);
env(noop(daria), fee(20), seq(env.seq(daria) + 1),
env(noop(bob), fee(8000), queued);
env(noop(charlie), fee(8000), queued);
env(noop(daria), fee(8000), queued);
env(noop(daria), fee(8000), seq(env.seq(daria) + 1),
queued);
env(noop(edgar), fee(20), queued);
env(noop(felicia), fee(20), queued);
env(noop(felicia), fee(20), seq(env.seq(felicia) + 1),
env(noop(edgar), fee(8000), queued);
env(noop(felicia), fee(8000), queued);
env(noop(felicia), fee(8000), seq(env.seq(felicia) + 1),
queued);
checkMetrics(env, 8, 8, 5, 4, 257, 512);
checkMetrics(env, 8, 8, 5, 4, 257, 700 * 256);
env.close();
// alice's transaction expired without getting
// into the ledger, so her transaction is gone,
// though one of felicia's is still in the queue.
checkMetrics(env, 1, 10, 6, 5, 256, 512);
checkMetrics(env, 1, 10, 6, 5, 256, 700 * 256);
expect(env.seq(alice) == 1);
env.close();
// And now the queue is empty
checkMetrics(env, 0, 12, 1, 6, 256, 512);
checkMetrics(env, 0, 12, 1, 6, 256, 800 * 256);
expect(env.seq(alice) == 1);
}
@@ -544,12 +543,12 @@ public:
env.close();
checkMetrics(env, 0, 12, 4, 6, 256, 1600);
checkMetrics(env, 0, 12, 4, 6, 256);
expect(env.seq(bob) == seqBob + 1);
expect(env.seq(carol) == seqCarol + 1);
env.close();
checkMetrics(env, 0, 12, 0, 6, 256, 2739);
checkMetrics(env, 0, 12, 0, 6, 256);
expect(env.seq(bob) == seqBob + 1);
expect(env.seq(carol) == seqCarol + 1);
}
@@ -824,10 +823,10 @@ public:
env.close();
// All of Alice's transactions applied.
checkMetrics(env, 0, 12, 4, 6, 256, 640);
checkMetrics(env, 0, 12, 4, 6, 256);
env.close();
checkMetrics(env, 0, 12, 0, 6, 256, 1203);
checkMetrics(env, 0, 12, 0, 6, 256);
// Alice is broke
env.require(balance(alice, XRP(0)));
@@ -980,7 +979,7 @@ public:
auto alice = Account("alice");
expect(!env.app().getTxQ().getMetrics(env.app(),
expect(!env.app().getTxQ().getMetrics(env.app().config(),
*env.current()));
env.fund(XRP(50000), noripple(alice));
@@ -992,7 +991,7 @@ public:
env(noop(alice), fee(30));
env.close();
expect(!env.app().getTxQ().getMetrics(env.app(),
expect(!env.app().getTxQ().getMetrics(env.app().config(),
*env.current()));
}

View File

@@ -50,8 +50,7 @@ Json::Value doSignFor (RPC::Context& context)
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app,
context.ledgerMaster.getCurrentLedger());
context.app);
}
} // ripple

View File

@@ -43,8 +43,7 @@ Json::Value doSign (RPC::Context& context)
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app,
context.ledgerMaster.getCurrentLedger());
context.app);
}
} // ripple

View File

@@ -55,7 +55,6 @@ Json::Value doSubmit (RPC::Context& context)
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app,
context.ledgerMaster.getCurrentLedger(),
RPC::getProcessTxnFn (context.netOps));
}

View File

@@ -50,7 +50,6 @@ Json::Value doSubmitMultiSigned (RPC::Context& context)
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app,
context.ledgerMaster.getCurrentLedger(),
RPC::getProcessTxnFn (context.netOps));
}

View File

@@ -20,8 +20,10 @@
#include <BeastConfig.h>
#include <ripple/rpc/impl/TransactionSign.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/paths/Pathfinder.h>
#include <ripple/app/tx/apply.h> // Validity::Valid
#include <ripple/basics/Log.h>
@@ -31,6 +33,7 @@
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/Sign.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/TxFlags.h>
@@ -339,7 +342,7 @@ transactionPreProcessImpl (
SigningForParams& signingArgs,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger)
std::shared_ptr<OpenView const> const& ledger)
{
auto j = app.journal ("RPCHandler");
@@ -393,6 +396,7 @@ transactionPreProcessImpl (
verify && signingArgs.editFields(),
app.config(),
app.getFeeTrack(),
app.getTxQ(),
ledger);
if (RPC::contains_error (err))
@@ -627,7 +631,8 @@ Json::Value checkFee (
bool doAutoFill,
Config const& config,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> const& ledger)
TxQ const& txQ,
std::shared_ptr<OpenView const> const& ledger)
{
Json::Value& tx (request[jss::tx_json]);
if (tx.isMember (jss::Fee))
@@ -670,14 +675,42 @@ Json::Value checkFee (
std::uint64_t const feeDefault = config.TRANSACTION_FEE_BASE;
// Administrative and identified endpoints are exempt from local fees.
std::uint64_t const fee =
std::uint64_t const loadFee =
feeTrack.scaleFeeLoad (feeDefault,
ledger->fees().base, ledger->fees().units, isUnlimited (role));
std::uint64_t fee = loadFee;
if (ledger->rules().enabled(featureFeeEscalation,
config.features))
{
auto const assumeTx = request.isMember("x-assume-tx") &&
request["x-assume-tx"].isConvertibleTo(Json::uintValue) ?
request["x-assume-tx"].asUInt() : 0;
auto const metrics = txQ.getMetrics(config, *ledger, assumeTx);
assert(metrics);
if(metrics)
{
auto const baseFee = ledger->fees().base;
auto escalatedFee = mulDiv(
metrics->expFeeLevel, baseFee,
metrics->referenceFeeLevel).second;
if (mulDiv(escalatedFee, metrics->referenceFeeLevel,
baseFee).second < metrics->expFeeLevel)
++escalatedFee;
fee = std::max(fee, escalatedFee);
}
}
auto const limit = mulDivThrow(feeTrack.scaleFeeBase (
feeDefault, ledger->fees().base, ledger->fees().units),
mult, div);
if (fee > limit && fee != loadFee &&
request.isMember("x-queue-okay") &&
request["x-queue-okay"].isBool() &&
request["x-queue-okay"].asBool())
{
fee = loadFee;
}
if (fee > limit)
{
std::stringstream ss;
@@ -698,11 +731,11 @@ Json::Value transactionSign (
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger)
Application& app)
{
using namespace detail;
auto const& ledger = app.openLedger().current();
auto j = app.journal ("RPCHandler");
JLOG (j.debug()) << "transactionSign: " << jvRequest;
@@ -733,11 +766,11 @@ Json::Value transactionSubmit (
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction)
{
using namespace detail;
auto const& ledger = app.openLedger().current();
auto j = app.journal ("RPCHandler");
JLOG (j.debug()) << "transactionSubmit: " << jvRequest;
@@ -860,9 +893,9 @@ Json::Value transactionSignFor (
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger)
Application& app)
{
auto const& ledger = app.openLedger().current();
auto j = app.journal ("RPCHandler");
JLOG (j.debug()) << "transactionSignFor: " << jvRequest;
@@ -975,9 +1008,9 @@ Json::Value transactionSubmitMultiSigned (
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction)
{
auto const& ledger = app.openLedger().current();
auto j = app.journal ("RPCHandler");
JLOG (j.debug())
<< "transactionSubmitMultiSigned: " << jvRequest;
@@ -1018,7 +1051,8 @@ Json::Value transactionSubmitMultiSigned (
{
Json::Value err = checkFee (
jvRequest, role, false, app.config(), app.getFeeTrack(), ledger);
jvRequest, role, false, app.config(), app.getFeeTrack(),
app.getTxQ(), ledger);
if (RPC::contains_error(err))
return err;

View File

@@ -30,6 +30,7 @@ namespace ripple {
class Application;
class LoadFeeTrack;
class Transaction;
class TxQ;
namespace RPC {
@@ -66,7 +67,8 @@ Json::Value checkFee (
bool doAutoFill,
Config const& config,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> const& ledger);
TxQ const& txQ,
std::shared_ptr<OpenView const> const& ledger);
// Return a std::function<> that calls NetworkOPs::processTransaction.
using ProcessTransactionFn =
@@ -88,8 +90,7 @@ Json::Value transactionSign (
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger);
Application& app);
/** Returns a Json::objectValue. */
Json::Value transactionSubmit (
@@ -98,7 +99,6 @@ Json::Value transactionSubmit (
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction);
/** Returns a Json::objectValue. */
@@ -107,8 +107,7 @@ Json::Value transactionSignFor (
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger);
Application& app);
/** Returns a Json::objectValue. */
Json::Value transactionSubmitMultiSigned (
@@ -117,7 +116,6 @@ Json::Value transactionSubmitMultiSigned (
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction);
} // RPC

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/contract.h>
#include <ripple/core/LoadFeeTrack.h>
#include <ripple/json/json_reader.h>
@@ -1841,10 +1842,8 @@ public:
void testAutoFillFees ()
{
test::jtx::Env env(*this);
std::shared_ptr<const ReadView> ledger =
std::make_shared<Ledger>(create_genesis,
env.app().config(), env.app().family());
LoadFeeTrack const feeTrack;
auto ledger = env.current();
LoadFeeTrack const& feeTrack = env.app().getFeeTrack();
{
Json::Value req;
@@ -1852,9 +1851,12 @@ public:
"{ \"fee_mult_max\" : 1, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee (req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect (! RPC::contains_error (result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
@@ -1864,9 +1866,12 @@ public:
"\"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
@@ -1875,9 +1880,11 @@ public:
"{ \"fee_mult_max\" : 0, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee (req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect (RPC::contains_error (result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
}
{
@@ -1889,9 +1896,11 @@ public:
"\"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect(RPC::contains_error(result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
}
{
@@ -1901,9 +1910,11 @@ public:
"\"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect(RPC::contains_error(result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
}
{
@@ -1913,12 +1924,153 @@ public:
"\"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack, ledger);
env.app().config(), feeTrack,
env.app().getTxQ(), ledger);
expect(RPC::contains_error(result), "Divide by 0");
expect(!req[jss::tx_json].isMember(jss::Fee));
}
}
static
std::unique_ptr<Config>
makeConfig()
{
auto p = std::make_unique<Config>();
test::setupConfigForUnitTests(*p);
auto& section = p->section("transaction_queue");
section.set("minimum_txn_in_ledger_standalone", "3");
return p;
}
void testAutoFillEscalatedFees ()
{
test::jtx::Env env(*this, makeConfig(),
test::jtx::features(featureFeeEscalation));
LoadFeeTrack const& feeTrack = env.app().getFeeTrack();
{
// 1: high mult, no queue, no pad
Json::Value req;
Json::Reader ().parse (
"{ \"fee_mult_max\" : 1000, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee (req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect (! RPC::contains_error (result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
// 2: high mult, can queue, no pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 1000, \"x-queue-okay\" : true \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
// 3: high mult, no queue, 4 pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 1000, \"x-assume-tx\" : 4, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 8889);
}
{
// 4: high mult, can queue, 4 pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 1000, \"x-assume-tx\" : 4, \"x-queue-okay\" : true \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 8889);
}
///////////////////
{
// 5: low mult, no queue, no pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 5, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
// 6: low mult, can queue, no pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 5, \"x-queue-okay\" : true \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
{
// 7: low mult, no queue, 4 pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 5, \"x-assume-tx\" : 4, \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(RPC::contains_error(result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
}
{
// 8: : low mult, can queue, 4 pad
Json::Value req;
Json::Reader().parse(
"{ \"fee_mult_max\" : 5, \"x-assume-tx\" : 4, \"x-queue-okay\" : true \"tx_json\" : { } } ", req);
Json::Value result =
checkFee(req, Role::ADMIN, true,
env.app().config(), feeTrack,
env.app().getTxQ(), env.current());
expect(!RPC::contains_error(result), "Legal checkFee");
expect(req[jss::tx_json].isMember(jss::Fee) &&
req[jss::tx_json][jss::Fee] == 10);
}
}
// A function that can be called as though it would process a transaction.
static void fakeProcessTransaction (
std::shared_ptr<Transaction>&, bool, bool, NetworkOPs::FailHard)
@@ -1951,8 +2103,6 @@ public:
env(pay(g, env.master, USD(50)));
env.close();
auto const ledger = env.current();
ProcessTransactionFn processTxn = fakeProcessTransaction;
// A list of all the functions we want to test.
@@ -1961,8 +2111,7 @@ public:
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger);
Application& app);
using submitFunc = Json::Value (*) (
Json::Value params,
@@ -1970,7 +2119,6 @@ public:
Role role,
std::chrono::seconds validatedLedgerAge,
Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction);
using TestStuff =
@@ -2010,8 +2158,7 @@ public:
NetworkOPs::FailHard::yes,
testRole,
1s,
env.app(),
ledger);
env.app());
}
else
{
@@ -2023,7 +2170,6 @@ public:
testRole,
1s,
env.app(),
ledger,
processTxn);
}
@@ -2044,6 +2190,7 @@ public:
void run ()
{
testAutoFillFees ();
testAutoFillEscalatedFees ();
testTransactionRPC ();
}
};

View File

@@ -313,7 +313,7 @@ public:
jv[jss::flags] ==
(vfFullyCanonicalSig | STValidation::kFullFlag) &&
jv[jss::full] == true &&
jv[jss::load_fee] == 256000 &&
!jv.isMember(jss::load_fee) &&
jv[jss::signature] &&
jv[jss::signing_time];
}));