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. or the number of transactions in the validated ledger.
3. Once there are more transactions in the open ledger than indicated 3. Once there are more transactions in the open ledger than indicated
by the limit, the required fee level jumps drastically. by the limit, the required fee level jumps drastically.
* The formula is `( baseFeeLevel * lastLedgerMedianFeeLevel * * The formula is `( lastLedgerMedianFeeLevel *
TransactionsInOpenLedger^2 / limit^2 )`, TransactionsInOpenLedger^2 / limit^2 )`,
and returns a [fee level](#fee-level). and returns a [fee level](#fee-level).
4. That may still be pretty small, but as more transactions get 4. That may still be pretty small, but as more transactions get
into the ledger, the fee level increases exponentially. into the ledger, the fee level increases exponentially.
* For example, if the limit is 6, and the median fee is minimal, * For example, if the limit is 6, and the median fee is minimal,
and assuming all [reference transactions](#reference-transaction), 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, or about 6800 drops,
but the 20th transaction requires a [level](#fee-level) of about 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 5. Finally, as each ledger closes, the median fee level of that ledger is
computed and used as `lastLedgerMedianFeeLevel` (with a 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. in the fee escalation formula for the next open ledger.
* Continuing the example above, if ledger consensus completes with * Continuing the example above, if ledger consensus completes with
only those 20 transactions, and all of those transactions paid the only those 20 transactions, and all of those transactions paid the
minimum required fee at each step, the limit will be adjusted from minimum required fee at each step, the limit will be adjusted from
6 to 20, and the `lastLedgerMedianFeeLevel` will be about 393,000, 6 to 20, and the `lastLedgerMedianFeeLevel` will be about 322,000,
which is 15,000 drops for a which is 12,600 drops for a
[reference transaction](#reference-transaction). [reference transaction](#reference-transaction).
* This will cause the first 21 transactions only require 10 * This will cause the first 21 transactions only require 10
drops, but the 22nd transaction will require 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 * This example assumes a cold-start scenario, with a single, possibly
malicious, user willing to pay arbitrary amounts to get transactions 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 traffic periods, and give those transactions a much better chance to
succeed. succeed.
1. If an incoming transaction meets the base [fee level](#fee-level), 1. If an incoming transaction meets both the base [fee
but does not have a high enough [fee level](#fee-level) to immediately level](#fee-level) and the load fee minimum, but does not have a high
go into the open ledger, it is instead put into the queue and broadcast enough [fee level](#fee-level) to immediately go into the open ledger,
to peers. Each peer will then make an independent decision about whether it is instead put into the queue and broadcast to peers. Each peer will
to put the transaction into its open ledger or the queue. In principle, then make an independent decision about whether to put the transaction
peers with identical open ledgers will come to identical decisions. Any into its open ledger or the queue. In principle, peers with identical
discrepancies will be resolved as usual during consensus. 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 2. When consensus completes, the open ledger limit is adjusted, and
the required [fee level](#fee-level) drops back to the base the required [fee level](#fee-level) drops back to the base
[fee level](#fee-level). Before the ledger is made available to [fee level](#fee-level). Before the ledger is made available to
@@ -105,10 +106,11 @@ but in practice, either
times](#other-constants), times](#other-constants),
* its last ledger sequence number will expire, * its last ledger sequence number will expire,
* the user will replace it by submitting another transaction with the same * 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. * 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 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 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 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, chance to get processed out of the queue before getting discarded,
particularly since it may have dependent transactions also in the queue, particularly since it may have dependent transactions also in the queue,
which will never succeed if this one is discarded. 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 ### `fee` command
**The `fee` RPC and WebSocket command is still experimental, and may **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. "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. "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. "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" : { "drops" : {
"base_fee" : "10", // base fee of a reference transaction in 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. "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. "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 (); info[jss::load] = m_job_queue.getJson ();
auto const escalationMetrics = app_.getTxQ().getMetrics( auto const escalationMetrics = app_.getTxQ().getMetrics(
app_, *app_.openLedger().current()); app_.config(), *app_.openLedger().current());
if (!human) if (!human)
{ {
info[jss::load_base] = app_.getFeeTrack ().getLoadBase (); info[jss::load_base] = app_.getFeeTrack ().getLoadBase ();

View File

@@ -51,13 +51,15 @@ class Application;
class TxQ class TxQ
{ {
public: public:
static constexpr std::uint64_t baseLevel = 256;
struct Setup struct Setup
{ {
std::size_t ledgersInQueue = 20; std::size_t ledgersInQueue = 20;
std::uint32_t retrySequencePercent = 25; std::uint32_t retrySequencePercent = 25;
// TODO: eahennis. Can we remove the multi tx factor? // TODO: eahennis. Can we remove the multi tx factor?
std::int32_t multiTxnPercent = -90; std::int32_t multiTxnPercent = -90;
std::uint32_t minimumEscalationMultiplier = 500; std::uint32_t minimumEscalationMultiplier = baseLevel * 500;
std::uint32_t minimumTxnInLedger = 5; std::uint32_t minimumTxnInLedger = 5;
std::uint32_t minimumTxnInLedgerSA = 1000; std::uint32_t minimumTxnInLedgerSA = 1000;
std::uint32_t targetTxnInLedger = 50; std::uint32_t targetTxnInLedger = 50;
@@ -128,7 +130,8 @@ public:
/** Returns fee metrics in reference fee level units. /** Returns fee metrics in reference fee level units.
*/ */
boost::optional<Metrics> 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. /** Packages up fee metrics for the `fee` RPC command.
*/ */
@@ -161,9 +164,6 @@ private:
std::mutex mutable lock_; std::mutex mutable lock_;
public:
static constexpr std::uint64_t baseLevel = 256;
public: public:
FeeMetrics(Setup const& setup, beast::Journal j) FeeMetrics(Setup const& setup, beast::Journal j)
: minimumTxnCount_(setup.standAlone ? : minimumTxnCount_(setup.standAlone ?
@@ -212,7 +212,7 @@ private:
} }
std::uint64_t std::uint64_t
scaleFeeLevel(OpenView const& view) const; scaleFeeLevel(OpenView const& view, std::uint32_t txCountPadding = 0) const;
}; };
// Alternate name: MaybeTx // Alternate name: MaybeTx

View File

@@ -154,12 +154,11 @@ TxQ::FeeMetrics::update(Application& app,
} }
std::uint64_t 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 // Transactions in the open ledger so far
auto const current = view.txCount(); auto const current = view.txCount() + txCountPadding;
std::size_t target; std::size_t target;
std::uint32_t multiplier; std::uint32_t multiplier;
@@ -177,11 +176,11 @@ TxQ::FeeMetrics::scaleFeeLevel(OpenView const& view) const
{ {
// Compute escalated fee level // Compute escalated fee level
// Don't care about the overflow flag // Don't care about the overflow flag
fee = mulDiv(fee, current * current * return mulDiv(multiplier, current * current,
multiplier, target * target).second; target * target).second;
} }
return fee; return baseLevel;
} }
TxQ::MaybeTx::MaybeTx( TxQ::MaybeTx::MaybeTx(
@@ -481,7 +480,7 @@ TxQ::apply(Application& app, OpenView& view,
// preclaim? // preclaim?
auto const baseFee = calculateBaseFee(app, view, *tx, j); auto const baseFee = calculateBaseFee(app, view, *tx, j);
auto const feeLevelPaid = getFeeLevelPaid(*tx, auto const feeLevelPaid = getFeeLevelPaid(*tx,
feeMetrics_.baseLevel, baseFee, setup_); baseLevel, baseFee, setup_);
auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view); auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
auto accountIter = byAccount_.find(account); auto accountIter = byAccount_.find(account);
@@ -758,7 +757,7 @@ TxQ::apply(Application& app, OpenView& view,
return{ pcresult.ter, false }; return{ pcresult.ter, false };
// Too low of a fee should get caught by preclaim // Too low of a fee should get caught by preclaim
assert(feeLevelPaid >= feeMetrics_.baseLevel); assert(feeLevelPaid >= baseLevel);
JLOG(j_.trace()) << "Transaction " << JLOG(j_.trace()) << "Transaction " <<
transactionID << transactionID <<
@@ -1118,12 +1117,13 @@ TxQ::accept(Application& app,
} }
auto 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> -> boost::optional<Metrics>
{ {
auto const allowEscalation = auto const allowEscalation =
(view.rules().enabled(featureFeeEscalation, (view.rules().enabled(featureFeeEscalation,
app.config().features)); config.features));
if (!allowEscalation) if (!allowEscalation)
return boost::none; return boost::none;
@@ -1135,11 +1135,11 @@ TxQ::getMetrics(Application& app, OpenView const& view) const
result.txQMaxSize = maxSize_; result.txQMaxSize = maxSize_;
result.txInLedger = view.txCount(); result.txInLedger = view.txCount();
result.txPerLedger = feeMetrics_.getTxnsExpected(); result.txPerLedger = feeMetrics_.getTxnsExpected();
result.referenceFeeLevel = feeMetrics_.baseLevel; result.referenceFeeLevel = baseLevel;
result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 : result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
feeMetrics_.baseLevel; baseLevel;
result.medFeeLevel = feeMetrics_.getEscalationMultiplier(); result.medFeeLevel = feeMetrics_.getEscalationMultiplier();
result.expFeeLevel = feeMetrics_.scaleFeeLevel(view); result.expFeeLevel = feeMetrics_.scaleFeeLevel(view, txCountPadding);
return result; return result;
} }
@@ -1150,8 +1150,8 @@ TxQ::doRPC(Application& app) const
using std::to_string; using std::to_string;
auto const view = app.openLedger().current(); auto const view = app.openLedger().current();
auto const metrics = getMetrics(app, *view); auto const metrics = getMetrics(app.config(), *view);
assert(metrics);
if (!metrics) if (!metrics)
return{}; return{};
@@ -1183,9 +1183,14 @@ TxQ::doRPC(Application& app) const
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);
drops[jss::open_ledger_fee] = to_string(mulDiv( auto escalatedFee = mulDiv(
metrics->expFeeLevel, baseFee, 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; return ret;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/contract.h> #include <ripple/basics/contract.h>
#include <ripple/core/LoadFeeTrack.h> #include <ripple/core/LoadFeeTrack.h>
#include <ripple/json/json_reader.h> #include <ripple/json/json_reader.h>
@@ -1841,10 +1842,8 @@ public:
void testAutoFillFees () void testAutoFillFees ()
{ {
test::jtx::Env env(*this); test::jtx::Env env(*this);
std::shared_ptr<const ReadView> ledger = auto ledger = env.current();
std::make_shared<Ledger>(create_genesis, LoadFeeTrack const& feeTrack = env.app().getFeeTrack();
env.app().config(), env.app().family());
LoadFeeTrack const feeTrack;
{ {
Json::Value req; Json::Value req;
@@ -1852,9 +1851,12 @@ public:
"{ \"fee_mult_max\" : 1, \"tx_json\" : { } } ", req); "{ \"fee_mult_max\" : 1, \"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee (req, Role::ADMIN, true, 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 (! 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); "\"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee(req, Role::ADMIN, true, 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(!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); "{ \"fee_mult_max\" : 0, \"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee (req, Role::ADMIN, true, 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 (RPC::contains_error (result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
} }
{ {
@@ -1889,9 +1896,11 @@ public:
"\"tx_json\" : { } } ", req); "\"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee(req, Role::ADMIN, true, 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(RPC::contains_error(result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
} }
{ {
@@ -1901,9 +1910,11 @@ public:
"\"tx_json\" : { } } ", req); "\"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee(req, Role::ADMIN, true, 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(RPC::contains_error(result), "Invalid checkFee");
expect(!req[jss::tx_json].isMember(jss::Fee));
} }
{ {
@@ -1913,12 +1924,153 @@ public:
"\"tx_json\" : { } } ", req); "\"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee(req, Role::ADMIN, true, 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(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. // A function that can be called as though it would process a transaction.
static void fakeProcessTransaction ( static void fakeProcessTransaction (
std::shared_ptr<Transaction>&, bool, bool, NetworkOPs::FailHard) std::shared_ptr<Transaction>&, bool, bool, NetworkOPs::FailHard)
@@ -1951,8 +2103,6 @@ public:
env(pay(g, env.master, USD(50))); env(pay(g, env.master, USD(50)));
env.close(); env.close();
auto const ledger = env.current();
ProcessTransactionFn processTxn = fakeProcessTransaction; ProcessTransactionFn processTxn = fakeProcessTransaction;
// A list of all the functions we want to test. // A list of all the functions we want to test.
@@ -1961,8 +2111,7 @@ public:
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
Role role, Role role,
std::chrono::seconds validatedLedgerAge, std::chrono::seconds validatedLedgerAge,
Application& app, Application& app);
std::shared_ptr<ReadView const> const& ledger);
using submitFunc = Json::Value (*) ( using submitFunc = Json::Value (*) (
Json::Value params, Json::Value params,
@@ -1970,7 +2119,6 @@ public:
Role role, Role role,
std::chrono::seconds validatedLedgerAge, std::chrono::seconds validatedLedgerAge,
Application& app, Application& app,
std::shared_ptr<ReadView const> const& ledger,
ProcessTransactionFn const& processTransaction); ProcessTransactionFn const& processTransaction);
using TestStuff = using TestStuff =
@@ -2010,8 +2158,7 @@ public:
NetworkOPs::FailHard::yes, NetworkOPs::FailHard::yes,
testRole, testRole,
1s, 1s,
env.app(), env.app());
ledger);
} }
else else
{ {
@@ -2023,7 +2170,6 @@ public:
testRole, testRole,
1s, 1s,
env.app(), env.app(),
ledger,
processTxn); processTxn);
} }
@@ -2044,6 +2190,7 @@ public:
void run () void run ()
{ {
testAutoFillFees (); testAutoFillFees ();
testAutoFillEscalatedFees ();
testTransactionRPC (); testTransactionRPC ();
} }
}; };

View File

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