mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-25 21:45:52 +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;
|
||||
}
|
||||
|
||||
@@ -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,10 +60,9 @@ 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 /
|
||||
(expectedPerLedger * expectedPerLedger) :
|
||||
metrics.referenceFeeLevel;
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ Json::Value doSignFor (RPC::Context& context)
|
||||
failType,
|
||||
context.role,
|
||||
context.ledgerMaster.getValidatedLedgerAge(),
|
||||
context.app,
|
||||
context.ledgerMaster.getCurrentLedger());
|
||||
context.app);
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -43,8 +43,7 @@ Json::Value doSign (RPC::Context& context)
|
||||
failType,
|
||||
context.role,
|
||||
context.ledgerMaster.getValidatedLedgerAge(),
|
||||
context.app,
|
||||
context.ledgerMaster.getCurrentLedger());
|
||||
context.app);
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -55,7 +55,6 @@ Json::Value doSubmit (RPC::Context& context)
|
||||
context.role,
|
||||
context.ledgerMaster.getValidatedLedgerAge(),
|
||||
context.app,
|
||||
context.ledgerMaster.getCurrentLedger(),
|
||||
RPC::getProcessTxnFn (context.netOps));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ Json::Value doSubmitMultiSigned (RPC::Context& context)
|
||||
context.role,
|
||||
context.ledgerMaster.getValidatedLedgerAge(),
|
||||
context.app,
|
||||
context.ledgerMaster.getCurrentLedger(),
|
||||
RPC::getProcessTxnFn (context.netOps));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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];
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user