mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 18:15:50 +00:00
Transaction queue and fee escalation (RIPD-598):
The first few transactions are added to the open ledger at the base fee (ie. 10 drops). Once enough transactions are added, the required fee will jump dramatically. If additional transactions are added, the fee will grow exponentially. Transactions that don't have a high enough fee to be applied to the ledger are added to the queue in order from highest fee to lowest. Whenever a new ledger is accepted as validated, transactions are first applied from the queue to the open ledger in fee order until either all transactions are applied or the fee again jumps too high for the remaining transactions. Current implementation is restricted to one transaction in the queue per account. Some groundwork has been laid to expand in the future. Note that this fee logic escalates independently of the load-based fee logic (ie. LoadFeeTrack). Submitted transactions must meet the load fee to be considered for the queue, and must meet both fees to be put into open ledger.
This commit is contained in:
@@ -1510,6 +1510,10 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\TxQ.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -1526,6 +1530,8 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\TxQ.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -1710,8 +1716,14 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\TxQ_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\apply.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\applySteps.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -1722,8 +1734,10 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\applySteps.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -3179,6 +3193,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Fee1.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -2256,6 +2256,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\impl\TxQ.cpp">
|
||||
<Filter>ripple\app\misc\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClCompile>
|
||||
@@ -2274,6 +2277,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\misc\TxQ.h">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp">
|
||||
<Filter>ripple\app\misc</Filter>
|
||||
</ClCompile>
|
||||
@@ -2439,9 +2445,15 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Taker.test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\TxQ_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\apply.h">
|
||||
<Filter>ripple\app\tx</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\applySteps.h">
|
||||
<Filter>ripple\app\tx</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -2451,9 +2463,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\applySteps.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -3861,6 +3873,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature1.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Fee1.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -373,6 +373,30 @@
|
||||
# connections to or from this server.
|
||||
#
|
||||
#
|
||||
# [transaction_queue] EXPERIMENTAL
|
||||
#
|
||||
# This section is EXPERIMENTAL, and should not be
|
||||
# present for production configuration settings.
|
||||
#
|
||||
# A set of key/value pair parameters to tune the performance of the
|
||||
# transaction queue.
|
||||
#
|
||||
# ledgers_in_queue = <number>
|
||||
#
|
||||
# The queue will be limited to this <number> of average ledgers'
|
||||
# worth of transactions. If the queue fills up, the transactions
|
||||
# with the lowest fees will be dropped from the queue any time a
|
||||
# transaction with a higher fee level is added. Default: 20.
|
||||
#
|
||||
# retry_sequence_percent = <number>
|
||||
#
|
||||
# If a client resubmits a transaction, the new transaction's fee
|
||||
# must be more than <number> percent higher than the original
|
||||
# transaction's fee, or meet the current open ledger fee to be
|
||||
# considered. Default: 125.
|
||||
#
|
||||
#
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 3. Ripple Protocol
|
||||
@@ -667,7 +691,7 @@
|
||||
#
|
||||
# [debug_logfile]
|
||||
#
|
||||
# Specifies were a debug logfile is kept. By default, no debug log is kept.
|
||||
# Specifies where a debug logfile is kept. By default, no debug log is kept.
|
||||
# Unless absolute, the path is relative the directory containing this file.
|
||||
#
|
||||
# Example: debug.log
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <ripple/ledger/CachedSLEs.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/UnorderedContainers.h>
|
||||
#include <ripple/core/Config.h>
|
||||
@@ -165,7 +164,6 @@ public:
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
OrderedTxs const& locals, bool retriesFirst,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
HashRouter& router,
|
||||
std::string const& suffix = "",
|
||||
modify_type const& f = {});
|
||||
|
||||
@@ -180,7 +178,7 @@ public:
|
||||
apply (Application& app, OpenView& view,
|
||||
ReadView const& check, FwdRange const& txs,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
HashRouter& router, beast::Journal j);
|
||||
beast::Journal j);
|
||||
|
||||
private:
|
||||
enum Result
|
||||
@@ -199,7 +197,7 @@ private:
|
||||
apply_one (Application& app, OpenView& view,
|
||||
std::shared_ptr< STTx const> const& tx,
|
||||
bool retry, ApplyFlags flags,
|
||||
HashRouter& router, beast::Journal j);
|
||||
beast::Journal j);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -209,7 +207,7 @@ void
|
||||
OpenLedger::apply (Application& app, OpenView& view,
|
||||
ReadView const& check, FwdRange const& txs,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
HashRouter& router, beast::Journal j)
|
||||
beast::Journal j)
|
||||
{
|
||||
for (auto iter = txs.begin();
|
||||
iter != txs.end(); ++iter)
|
||||
@@ -222,7 +220,7 @@ OpenLedger::apply (Application& app, OpenView& view,
|
||||
if (check.txExists(tx->getTransactionID()))
|
||||
continue;
|
||||
auto const result = apply_one(app, view,
|
||||
tx, true, flags, router, j);
|
||||
tx, true, flags, j);
|
||||
if (result == Result::retry)
|
||||
retries.insert(tx);
|
||||
}
|
||||
@@ -243,7 +241,7 @@ OpenLedger::apply (Application& app, OpenView& view,
|
||||
{
|
||||
switch (apply_one(app, view,
|
||||
iter->second, retry, flags,
|
||||
router, j))
|
||||
j))
|
||||
{
|
||||
case Result::success:
|
||||
++changes;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/tx/TransactionAcquire.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
@@ -1055,6 +1056,10 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
applyTransactions (app_, set.get(), accum,
|
||||
newLCL, retriableTxs, tapNONE);
|
||||
}
|
||||
// Update fee computations.
|
||||
app_.getTxQ().processValidatedLedger(app_, accum,
|
||||
mCurrentMSeconds > 5000);
|
||||
|
||||
accum.apply(*newLCL);
|
||||
}
|
||||
|
||||
@@ -1064,6 +1069,7 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
|
||||
newLCL->updateSkipList ();
|
||||
|
||||
{
|
||||
int asf = newLCL->stateMap().flushDirty (
|
||||
hotACCOUNT_NODE, newLCL->info().seq);
|
||||
int tmf = newLCL->txMap().flushDirty (
|
||||
@@ -1071,6 +1077,7 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
JLOG (j_.debug) << "Flushed " <<
|
||||
asf << " accounts and " <<
|
||||
tmf << " transaction nodes";
|
||||
}
|
||||
|
||||
// Accept ledger
|
||||
newLCL->setAccepted (closeTime, mCloseResolution, closeTimeCorrect, app_.config());
|
||||
@@ -1140,6 +1147,7 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
// See if we can accept a ledger as fully-validated
|
||||
ledgerMaster_.consensusBuilt (newLCL);
|
||||
|
||||
{
|
||||
// Apply disputed transactions that didn't get in
|
||||
//
|
||||
// The first crack of transactions to get into the new
|
||||
@@ -1178,7 +1186,6 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Build new open ledger
|
||||
auto lock = beast::make_lock(
|
||||
app_.getMasterMutex(), std::defer_lock);
|
||||
@@ -1198,7 +1205,12 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
|
||||
rules.emplace();
|
||||
app_.openLedger().accept(app_, *rules,
|
||||
newLCL, localTx, anyDisputes, retriableTxs, tapNONE,
|
||||
app_.getHashRouter(), "consensus");
|
||||
"consensus",
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return app_.getTxQ().accept(app_, view);
|
||||
});
|
||||
}
|
||||
|
||||
mNewLedgerHash = newLCL->getHash ();
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
#include <ripple/app/misc/SHAMapStore.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/paths/PathRequests.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
@@ -378,8 +379,8 @@ public:
|
||||
for (auto const& it : mHeldTransactions)
|
||||
{
|
||||
ApplyFlags flags = tapNONE;
|
||||
auto const result = apply(app_, view,
|
||||
*it.second, flags, j);
|
||||
auto const result = app_.getTxQ().apply(
|
||||
app_, view, it.second, flags, j);
|
||||
if (result.second)
|
||||
any = true;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
|
||||
std::shared_ptr<Ledger const> const& ledger,
|
||||
OrderedTxs const& locals, bool retriesFirst,
|
||||
OrderedTxs& retries, ApplyFlags flags,
|
||||
HashRouter& router, std::string const& suffix,
|
||||
std::string const& suffix,
|
||||
modify_type const& f)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
@@ -89,7 +89,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
|
||||
std::vector<std::shared_ptr<
|
||||
STTx const>>;
|
||||
apply (app, *next, *ledger, empty{},
|
||||
retries, flags, router, j_);
|
||||
retries, flags, j_);
|
||||
}
|
||||
// Block calls to modify, otherwise
|
||||
// new tx going into the open ledger
|
||||
@@ -107,7 +107,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
|
||||
{
|
||||
return p.first;
|
||||
}),
|
||||
retries, flags, router, j_);
|
||||
retries, flags, j_);
|
||||
// Apply local tx
|
||||
for (auto const& item : locals)
|
||||
ripple::apply(app, *next,
|
||||
@@ -137,7 +137,7 @@ auto
|
||||
OpenLedger::apply_one (Application& app, OpenView& view,
|
||||
std::shared_ptr<STTx const> const& tx,
|
||||
bool retry, ApplyFlags flags,
|
||||
HashRouter& router,beast::Journal j) -> Result
|
||||
beast::Journal j) -> Result
|
||||
{
|
||||
if (retry)
|
||||
flags = flags | tapRETRY;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/SHAMapStore.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/paths/Pathfinder.h>
|
||||
#include <ripple/app/paths/PathRequests.h>
|
||||
@@ -326,6 +327,7 @@ public:
|
||||
std::unique_ptr <HashRouter> mHashRouter;
|
||||
std::unique_ptr <Validations> mValidations;
|
||||
std::unique_ptr <LoadManager> m_loadManager;
|
||||
std::unique_ptr <TxQ> txQ_;
|
||||
beast::DeadlineTimer m_sweepTimer;
|
||||
beast::DeadlineTimer m_entropyTimer;
|
||||
|
||||
@@ -460,6 +462,8 @@ public:
|
||||
|
||||
, m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager")))
|
||||
|
||||
, txQ_(make_TxQ(setup_TxQ(*config_), logs_->journal("TxQ")))
|
||||
|
||||
, m_sweepTimer (this)
|
||||
|
||||
, m_entropyTimer (this)
|
||||
@@ -683,6 +687,12 @@ public:
|
||||
return *m_overlay;
|
||||
}
|
||||
|
||||
TxQ& getTxQ() override
|
||||
{
|
||||
assert(txQ_.get() != nullptr);
|
||||
return *txQ_;
|
||||
}
|
||||
|
||||
DatabaseCon& getTxnDB () override
|
||||
{
|
||||
assert (mTxnDB.get() != nullptr);
|
||||
|
||||
@@ -62,6 +62,7 @@ class AccountIDCache;
|
||||
class STLedgerEntry;
|
||||
class TimeKeeper;
|
||||
class TransactionMaster;
|
||||
class TxQ;
|
||||
class Validations;
|
||||
|
||||
class DatabaseCon;
|
||||
@@ -114,6 +115,7 @@ public:
|
||||
virtual LoadFeeTrack& getFeeTrack () = 0;
|
||||
virtual LoadManager& getLoadManager () = 0;
|
||||
virtual Overlay& overlay () = 0;
|
||||
virtual TxQ& getTxQ() = 0;
|
||||
virtual UniqueNodeList& getUNL () = 0;
|
||||
virtual Validations& getValidations () = 0;
|
||||
virtual NodeStore::Database& getNodeStore () = 0;
|
||||
|
||||
222
src/ripple/app/misc/FeeEscalation.md
Normal file
222
src/ripple/app/misc/FeeEscalation.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Fees
|
||||
|
||||
Rippled's fee mechanism consists of several interrelated processes:
|
||||
|
||||
1. [Rapid Fee escalation](#fee-escalation)
|
||||
2. [The Transaction Queue](#transaction-queue)
|
||||
|
||||
## Fee Escalation
|
||||
|
||||
The guiding principal of fee escalation is that when things are going
|
||||
smoothly, fees stay low, but as soon as high levels of traffic appear
|
||||
on the network, fees will grow quickly to extreme levels. This should
|
||||
dissuade malicious users from abusing the system, while giving
|
||||
legitimate users the ability to pay a higher fee to get high-priority
|
||||
transactions into the open ledger, even during unfavorable conditions.
|
||||
|
||||
How fees escalate:
|
||||
|
||||
1. There is a base [fee level](#fee-level) of 256,
|
||||
which is the minimum that a typical transaction
|
||||
is required to pay. For a [reference
|
||||
transaction](#reference-transaction), that corresponds to the
|
||||
network base fee, which is currently 10 drops.
|
||||
2. However, there is a limit on the number of transactions that
|
||||
can get into an open ledger for that base fee level. The limit
|
||||
will vary based on the [health](#consensus-health) of the
|
||||
consensus process, but will be at least [5](#other-constants).
|
||||
* If consensus stays [healthy](#consensus-health), the limit will
|
||||
be the max of the current limit or the number of transactions in
|
||||
the validated ledger until it gets to [50](#other-constants), at
|
||||
which point, the limit will only be updated to the number of
|
||||
transactions in the validated ledger if it is larger than 50.
|
||||
* If consensus does not stay [healthy](#consensus-health),
|
||||
the limit will clamp down to the smaller of [50](#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 *
|
||||
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
|
||||
or about 6800 drops,
|
||||
but the 20th transaction requires a [level](#fee-level) of about
|
||||
1,422,000 or about 56,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))
|
||||
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
|
||||
[reference transaction](#reference-transaction).
|
||||
* This will cause the first 21 transactions only require 10
|
||||
drops, but the 22st transaction will require
|
||||
a level of about 110,000,000 or about 4.3 million drops (4.3XRP).
|
||||
|
||||
## Transaction Queue
|
||||
|
||||
An integral part of making fee escalation work for users of the network
|
||||
is the transaction queue. The queue allows legitimate transactions to be
|
||||
considered by the network for future ledgers if the escalated open
|
||||
ledger fee gets too high. This allows users to submit low priority
|
||||
transactions with a low fee, and wait for high fees to drop. It also
|
||||
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.
|
||||
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
|
||||
external transactions, transactions are applied from the queue to the
|
||||
ledger from highest [fee level](#fee-level) to lowest. These transactions
|
||||
count against the open ledger limit, so the required [fee level](#fee-level)
|
||||
may start rising during this process.
|
||||
3. Once the queue is empty, or required the [fee level](#fee-level)
|
||||
jumps too high for the remaining transactions in the queue, the ledger
|
||||
is opened up for normal transaction processing.
|
||||
4. A transaction in the queue can stay there indefinitely in principle,
|
||||
but in practice, either
|
||||
* it will eventually get applied to the ledger,
|
||||
* its last ledger sequence number will expire,
|
||||
* the user will replace it by submitting another transaction with the same
|
||||
sequence number and a higher fee, 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 lower the transaction's fee, the more likely that it will get dropped if the
|
||||
network is busy.
|
||||
|
||||
Currently, there is an additional restriction that the queue can only hold one
|
||||
transaction per account at a time. Future development will make the queue
|
||||
aware of transaction dependencies so that more than one can be
|
||||
queued up at a time per account.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Fee Level
|
||||
|
||||
"Fee level" is used to allow the cost of different types of transactions
|
||||
to be compared directly. For a [reference
|
||||
transaction](#reference-transaction), the base fee
|
||||
level is 256. If a transaction is submitted with a higher `Fee` field,
|
||||
the fee level is scaled appropriately.
|
||||
|
||||
Examples, assuming a base fee of 10 drops:
|
||||
|
||||
1. A single-signed [reference transaction](#reference-transaction)
|
||||
with `Fee=20` will have a fee level of 512.
|
||||
2. A multi-signed transaction with 3 signatures (base fee = 40 drops)
|
||||
and `Fee=60` will have a fee level of 384.
|
||||
3. A hypothetical future non-reference transaction with a base
|
||||
fee of 15 drops multi-signed with 5 signatures and `Fee=90` will
|
||||
have a fee level of 256.
|
||||
|
||||
This demonstrates that a simpler transaction paying less XRP can be more
|
||||
likely to get into the open ledger, or be sorted earlier in the queue
|
||||
than a more complex transaction paying more XRP.
|
||||
|
||||
### Reference Transaction
|
||||
|
||||
In this document, a "Reference Transaction" is any currently implemented
|
||||
single-signed transaction (eg. Payment, Account Set, Offer Create, etc)
|
||||
that requires a fee.
|
||||
|
||||
In the future, there may be other transaction types that require
|
||||
more (or less) work for rippled to process. Those transactions may have
|
||||
a higher (or lower) base fee, requiring a correspondingly higher (or
|
||||
lower) fee to get into the same position as a reference transaction.
|
||||
|
||||
### Consensus Health
|
||||
|
||||
For consensus to be considered healthy, the consensus process must take
|
||||
less than 5 seconds. This time limit was chosen based on observed past
|
||||
behavior of the ripple network. Note that this is not necessarily the
|
||||
time between ledger closings, as consensus usually starts some amount
|
||||
of time after a ledger opens.
|
||||
|
||||
### Other Constants
|
||||
|
||||
* *Base fee transaction limit per ledger*. The minimum value of 5 was
|
||||
chosen to ensure the limit never gets so small that the ledger becomes
|
||||
unusable. The "target" value of 50 was chosen so the limit never gets large
|
||||
enough to invite abuse, but keeps up if the network stays healthy and
|
||||
active. These exact values were chosen experimentally, and can easily
|
||||
change in the future.
|
||||
* *Minimum `lastLedgerMedianFeeLevel`*. The value of 500 was chosen to
|
||||
ensure that the first escalated fee was more significant and noticable
|
||||
than what the default would allow. This exact value was chosen
|
||||
experimentally, and can easily change in the future.
|
||||
* *Transaction queue size limit*. The limit is computed based on the
|
||||
base fee transaction limit per ledger, so that the queue can grow
|
||||
automatically as the ripple network's performance improves, allowing
|
||||
more transactions per second, and thus more transactions per ledger
|
||||
to process successfully. The limit of 20 ledgers was used to provide
|
||||
a balance between resource (specifically memory) usage, and giving
|
||||
transactions a realistic chance to be processed. This exact value was
|
||||
chosen experimentally, and can easily change in the future.
|
||||
|
||||
### `fee` command
|
||||
|
||||
**The `fee` RPC and WebSocket command is still experimental, and may
|
||||
change without warning.**
|
||||
|
||||
`fee` takes no parameters, and returns information about the current local
|
||||
[fee escalation](#fee-escalation) and [transaction queue](#transaction-queue)
|
||||
state as both fee levels and drops. The drop values assume a
|
||||
single-singed reference transaction. It is up to the user to compute the
|
||||
neccessary fees for other types of transactions. (E.g. multiply all drop
|
||||
values by 5 for a multi-signed transaction with 4 signatures.)
|
||||
|
||||
The `fee` result is always instantanteous, and relates to the open
|
||||
ledger. Thus, it does not include any sequence number or IDs, and may
|
||||
not make sense if rippled is not synced to the network.
|
||||
|
||||
Result format:
|
||||
```
|
||||
{
|
||||
"result" : {
|
||||
"current_ledger_size" : "16", // number of transactions in the open ledger
|
||||
"current_queue_size" : "2", // number of transactions waiting in the queue
|
||||
"expected_ledger_size" : "15", // one less than the number of transactions that can get into the open ledger for the base fee.
|
||||
"max_queue_size" : "300", // number of transactions allowed into the queue
|
||||
"levels" : {
|
||||
"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.
|
||||
},
|
||||
"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.
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enabling Fee Escalation
|
||||
|
||||
These features are disabled by default and need to be activated by a
|
||||
feature in your rippled.cfg. Add a `[features]` section if one is not
|
||||
already present, and add `FeeEscalation` (case-sensitive) to that
|
||||
list, then restart rippled.
|
||||
|
||||
```
|
||||
[features]
|
||||
FeeEscalation
|
||||
```
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <ripple/app/main/LocalCredentials.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/misc/Validations.h>
|
||||
#include <ripple/app/misc/impl/AccountTxPaging.h>
|
||||
#include <ripple/app/misc/UniqueNodeList.h>
|
||||
@@ -839,8 +840,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
||||
if (e.admin)
|
||||
flags = flags | tapADMIN;
|
||||
|
||||
auto const result = ripple::apply(app_, view,
|
||||
*e.transaction->getSTransaction(), flags, j);
|
||||
auto const result = app_.getTxQ().apply(
|
||||
app_, view, e.transaction->getSTransaction(),
|
||||
flags, j);
|
||||
e.result = result.first;
|
||||
e.applied = result.second;
|
||||
changed = changed || result.second;
|
||||
@@ -887,6 +889,16 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
||||
m_journal.info << "Transaction is obsolete";
|
||||
e.transaction->setStatus (OBSOLETE);
|
||||
}
|
||||
else if (e.result == terQUEUED)
|
||||
{
|
||||
JLOG(m_journal.info) << "Transaction is likely to claim a " <<
|
||||
"fee, but is queued until fee drops";
|
||||
e.transaction->setStatus(HELD);
|
||||
// Add to held transactions, because it could get
|
||||
// kicked out of the queue, and this will try to
|
||||
// put it back.
|
||||
m_ledgerMaster.addHeldTransaction(e.transaction);
|
||||
}
|
||||
else if (isTerRetry (e.result))
|
||||
{
|
||||
if (e.failType == FailHard::yes)
|
||||
@@ -914,8 +926,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
||||
e.transaction->getSTransaction());
|
||||
}
|
||||
|
||||
if (e.applied ||
|
||||
((mMode != omFULL) && (e.failType != FailHard::yes) && e.local))
|
||||
if (e.applied || ((mMode != omFULL) &&
|
||||
(e.failType != FailHard::yes) && e.local) ||
|
||||
(e.result == terQUEUED))
|
||||
{
|
||||
std::set<Peer::id_t> peers;
|
||||
|
||||
@@ -929,6 +942,7 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
||||
tx.set_rawtransaction (&s.getData().front(), s.getLength());
|
||||
tx.set_status (protocol::tsCURRENT);
|
||||
tx.set_receivetimestamp (app_.timeKeeper().now().time_since_epoch().count());
|
||||
tx.set_deferred(e.result == terQUEUED);
|
||||
// FIXME: This should be when we received it
|
||||
app_.overlay().foreach (send_if_not (
|
||||
std::make_shared<Message> (tx, protocol::mtTRANSACTION),
|
||||
@@ -1254,6 +1268,11 @@ void NetworkOPsImp::switchLastClosedLedger (
|
||||
|
||||
clearNeedNetworkLedger ();
|
||||
newLCL->setClosed ();
|
||||
|
||||
// Update fee computations.
|
||||
// TODO: Needs an open ledger
|
||||
//app_.getTxQ().processValidatedLedger(app_, *newLCL, true);
|
||||
|
||||
// Caller must own master lock
|
||||
{
|
||||
// Apply tx in old open ledger to new
|
||||
@@ -1269,7 +1288,12 @@ void NetworkOPsImp::switchLastClosedLedger (
|
||||
rules.emplace();
|
||||
app_.openLedger().accept(app_, *rules,
|
||||
newLCL, OrderedTxs({}), false, retries,
|
||||
tapNONE, app_.getHashRouter(), "jump");
|
||||
tapNONE, "jump",
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return app_.getTxQ().accept(app_, view);
|
||||
});
|
||||
}
|
||||
|
||||
m_ledgerMaster.switchLCL (newLCL);
|
||||
|
||||
376
src/ripple/app/misc/TxQ.h
Normal file
376
src/ripple/app/misc/TxQ.h
Normal file
@@ -0,0 +1,376 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-14 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TXQ_H_INCLUDED
|
||||
#define RIPPLE_TXQ_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/ledger/ApplyView.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/core/LoadFeeTrack.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Application;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class FeeMetrics
|
||||
{
|
||||
private:
|
||||
// Fee escalation
|
||||
|
||||
// Limit of the txnsExpected value after a
|
||||
// time leap.
|
||||
std::size_t const targetTxnCount_;
|
||||
// Minimum value of txnsExpected.
|
||||
std::size_t minimumTxnCount_;
|
||||
// Number of transactions expected per ledger.
|
||||
// One more than this value will be accepted
|
||||
// before escalation kicks in.
|
||||
std::size_t txnsExpected_;
|
||||
// Minimum value of escalationMultiplier.
|
||||
std::uint32_t const minimumMultiplier_;
|
||||
// Based on the median fee of the LCL. Used
|
||||
// when fee escalation kicks in.
|
||||
std::uint32_t escalationMultiplier_;
|
||||
beast::Journal j_;
|
||||
|
||||
std::mutex mutable lock_;
|
||||
|
||||
public:
|
||||
static const std::uint64_t baseLevel = 256;
|
||||
|
||||
public:
|
||||
FeeMetrics(bool standAlone, beast::Journal j)
|
||||
: targetTxnCount_(50)
|
||||
, minimumTxnCount_(standAlone ? 1000 : 5)
|
||||
, txnsExpected_(minimumTxnCount_)
|
||||
, minimumMultiplier_(500)
|
||||
, escalationMultiplier_(minimumMultiplier_)
|
||||
, j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
Updates fee metrics based on the transactions in the ReadView
|
||||
for use in fee escalation calculations.
|
||||
|
||||
@param view View of the LCL that was just closed or received.
|
||||
@param timeLeap Indicates that rippled is under load so fees
|
||||
should grow faster.
|
||||
*/
|
||||
std::size_t
|
||||
updateFeeMetrics(Application& app,
|
||||
ReadView const& view, bool timeLeap);
|
||||
|
||||
/** Used by tests only.
|
||||
*/
|
||||
std::size_t
|
||||
setMinimumTx(int m)
|
||||
{
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
|
||||
auto const old = minimumTxnCount_;
|
||||
minimumTxnCount_ = m;
|
||||
txnsExpected_ = m;
|
||||
return old;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
getTxnsExpected() const
|
||||
{
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
|
||||
return txnsExpected_;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getEscalationMultiplier() const
|
||||
{
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
|
||||
return escalationMultiplier_;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
scaleFeeLevel(OpenView const& view) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Transaction Queue. Used to manage transactions in conjunction with
|
||||
fee escalation. See also: RIPD-598, and subissues
|
||||
RIPD-852, 853, and 854.
|
||||
|
||||
Once enough transactions are added to the open ledger, the required
|
||||
fee will jump dramatically. If additional transactions are added,
|
||||
the fee will grow exponentially.
|
||||
|
||||
Transactions that don't have a high enough fee to be applied to
|
||||
the ledger are added to the queue in order from highest fee to
|
||||
lowest. Whenever a new ledger is accepted as validated, transactions
|
||||
are first applied from the queue to the open ledger in fee order
|
||||
until either all transactions are applied or the fee again jumps
|
||||
too high for the remaining transactions.
|
||||
*/
|
||||
class TxQ
|
||||
{
|
||||
public:
|
||||
struct Setup
|
||||
{
|
||||
std::size_t ledgersInQueue = 20;
|
||||
std::uint32_t retrySequencePercent = 125;
|
||||
bool standAlone = false;
|
||||
};
|
||||
|
||||
struct Metrics
|
||||
{
|
||||
std::size_t txCount; // Transactions in the queue
|
||||
boost::optional<std::size_t> txQMaxSize; // Max txns in queue
|
||||
std::size_t txInLedger; // Amount currently in the ledger
|
||||
std::size_t txPerLedger; // Amount expected per ledger
|
||||
std::uint64_t referenceFeeLevel; // Reference transaction fee level
|
||||
std::uint64_t minFeeLevel; // Minimum fee level to get in the queue
|
||||
std::uint64_t medFeeLevel; // Median fee level of the last ledger
|
||||
std::uint64_t expFeeLevel; // Estimated fee level to get in next ledger
|
||||
};
|
||||
|
||||
TxQ(Setup const& setup,
|
||||
beast::Journal j);
|
||||
|
||||
virtual ~TxQ();
|
||||
|
||||
/**
|
||||
Add a new transaction to the open ledger, hold it in the queue,
|
||||
or reject it.
|
||||
|
||||
How the decision is made:
|
||||
1. Is there already a transaction for the same account with the
|
||||
same sequence number in the queue?
|
||||
Yes: Is `txn`'s fee higher than the queued transaction's fee?
|
||||
Yes: Remove the queued transaction. Continue to step 2.
|
||||
No: Reject `txn` with a low fee TER code. Stop.
|
||||
No: Continue to step 2.
|
||||
2. Is the `txn`s fee level >= the required fee level?
|
||||
Yes: `txn` can be applied to the ledger. Pass it
|
||||
to the engine and return that result.
|
||||
No: Can it be held in the queue? (See TxQImpl::canBeHeld).
|
||||
No: Reject `txn` with a low fee TER code.
|
||||
Yes: Is the queue full?
|
||||
No: Put `txn` in the queue.
|
||||
Yes: Is the `txn`'s fee higher than the end item's
|
||||
fee?
|
||||
Yes: Remove the end item, and add `txn`.
|
||||
No: Reject `txn` with a low fee TER code.
|
||||
|
||||
If the transaction is queued, addTransaction will return
|
||||
{ TD_held, terQUEUED }
|
||||
|
||||
|
||||
@param txn The transaction to be attempted.
|
||||
@param params Flags to control engine behaviors.
|
||||
@param engine Transaction Engine.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
apply(Application& app, OpenView& view,
|
||||
std::shared_ptr<STTx const> const& tx,
|
||||
ApplyFlags flags, beast::Journal j);
|
||||
|
||||
/**
|
||||
Fill the new open ledger with transactions from the queue.
|
||||
As we apply more transactions to the ledger, the required
|
||||
fee will increase.
|
||||
|
||||
Iterate over the transactions from highest fee to lowest.
|
||||
For each transaction, compute the required fee.
|
||||
Is the transaction fee is less than the required fee?
|
||||
Yes: Stop. We're done.
|
||||
No: Try to apply the transaction. Did it apply?
|
||||
Yes: Take it out of the queue.
|
||||
No: Leave it in the queue, and continue iterating.
|
||||
|
||||
@return Whether any txs were added to the view.
|
||||
*/
|
||||
bool
|
||||
accept(Application& app, OpenView& view,
|
||||
ApplyFlags flags = tapNONE);
|
||||
|
||||
/**
|
||||
We have a new last validated ledger, update and clean up the
|
||||
queue.
|
||||
|
||||
1) Keep track of the average non-empty ledger size. Once there
|
||||
are enough data points, the maximum queue size will be
|
||||
enough to hold 20 ledgers. (Parameters for this are
|
||||
experimentally configurable, but should be left alone.)
|
||||
1a) If the new limit makes the queue full, trim excess
|
||||
transactions from the end of the queue.
|
||||
2) Remove any transactions from the queue whos the
|
||||
`LastLedgerSequence` has passed.
|
||||
|
||||
*/
|
||||
void
|
||||
processValidatedLedger(Application& app,
|
||||
OpenView const& view, bool timeLeap,
|
||||
ApplyFlags flags = tapNONE);
|
||||
|
||||
/** Used by tests only.
|
||||
*/
|
||||
std::size_t
|
||||
setMinimumTx(int m);
|
||||
|
||||
/** Returns fee metrics in reference fee (level) units.
|
||||
*/
|
||||
struct Metrics
|
||||
getMetrics(OpenView const& view) const;
|
||||
|
||||
/** Packages up fee metrics for the `fee` RPC command.
|
||||
*/
|
||||
Json::Value
|
||||
doRPC(Application& app) const;
|
||||
|
||||
/** Return the instantaneous fee to get into the current
|
||||
open ledger for a reference transaction.
|
||||
*/
|
||||
XRPAmount
|
||||
openLedgerFee(OpenView const& view) const;
|
||||
|
||||
private:
|
||||
class CandidateTxn
|
||||
{
|
||||
public:
|
||||
// Used by the TxQ::FeeHook and TxQ::FeeMultiSet below
|
||||
// to put each candidate object into more than one
|
||||
// set without copies, pointers, etc.
|
||||
boost::intrusive::set_member_hook<> byFeeListHook;
|
||||
|
||||
std::shared_ptr<STTx const> txn;
|
||||
|
||||
uint64_t const feeLevel;
|
||||
TxID const txID;
|
||||
boost::optional<TxID> priorTxID;
|
||||
AccountID const account;
|
||||
boost::optional<LedgerIndex> lastValid;
|
||||
TxSeq const sequence;
|
||||
ApplyFlags const flags;
|
||||
// pfresult_ is never allowed to be empty. The
|
||||
// boost::optional is leveraged to allow `emplace`d
|
||||
// construction and replacement without a copy
|
||||
// assignment operation.
|
||||
boost::optional<PreflightResult const> pfresult;
|
||||
|
||||
public:
|
||||
CandidateTxn(std::shared_ptr<STTx const> const&,
|
||||
TxID const& txID, std::uint64_t feeLevel,
|
||||
ApplyFlags const flags,
|
||||
PreflightResult const& pfresult);
|
||||
};
|
||||
|
||||
class GreaterFee
|
||||
{
|
||||
public:
|
||||
bool operator()(const CandidateTxn& lhs, const CandidateTxn& rhs) const
|
||||
{
|
||||
return lhs.feeLevel > rhs.feeLevel;
|
||||
}
|
||||
};
|
||||
|
||||
class TxQAccount
|
||||
{
|
||||
public:
|
||||
|
||||
AccountID const account;
|
||||
uint64_t totalFees;
|
||||
// Sequence number will be used as the key.
|
||||
std::map <TxSeq, CandidateTxn> transactions;
|
||||
|
||||
public:
|
||||
explicit TxQAccount(std::shared_ptr<STTx const> const& txn);
|
||||
explicit TxQAccount(const AccountID& account);
|
||||
|
||||
std::size_t
|
||||
getTxnCount() const
|
||||
{
|
||||
return transactions.size();
|
||||
}
|
||||
|
||||
bool
|
||||
empty() const
|
||||
{
|
||||
return !getTxnCount();
|
||||
}
|
||||
|
||||
CandidateTxn&
|
||||
addCandidate(CandidateTxn&&);
|
||||
|
||||
bool
|
||||
removeCandidate(TxSeq const& sequence);
|
||||
|
||||
CandidateTxn const*
|
||||
findCandidateAt(TxSeq const& sequence) const;
|
||||
};
|
||||
|
||||
|
||||
using FeeHook = boost::intrusive::member_hook
|
||||
<CandidateTxn, boost::intrusive::set_member_hook<>,
|
||||
&CandidateTxn::byFeeListHook>;
|
||||
|
||||
using FeeMultiSet = boost::intrusive::multiset
|
||||
< CandidateTxn, FeeHook,
|
||||
boost::intrusive::compare <GreaterFee> >;
|
||||
|
||||
Setup const setup_;
|
||||
beast::Journal j_;
|
||||
|
||||
detail::FeeMetrics feeMetrics_;
|
||||
FeeMultiSet byFee_;
|
||||
std::map <AccountID, TxQAccount> byAccount_;
|
||||
boost::optional<size_t> maxSize_;
|
||||
|
||||
// Most queue operations are done under the master lock,
|
||||
// but use this mutex for the RPC "fee" command, which isn't.
|
||||
std::mutex mutable mutex_;
|
||||
|
||||
private:
|
||||
bool isFull() const
|
||||
{
|
||||
return maxSize_ && byFee_.size() >= *maxSize_;
|
||||
}
|
||||
|
||||
bool canBeHeld(std::shared_ptr<STTx const> const&);
|
||||
|
||||
FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type);
|
||||
|
||||
};
|
||||
|
||||
TxQ::Setup
|
||||
setup_TxQ(Config const&);
|
||||
|
||||
std::unique_ptr<TxQ>
|
||||
make_TxQ(TxQ::Setup const&, beast::Journal);
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
753
src/ripple/app/misc/impl/TxQ.cpp
Normal file
753
src/ripple/app/misc/impl/TxQ.cpp
Normal file
@@ -0,0 +1,753 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/ledger/OpenLedger.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <boost/algorithm/clamp.hpp>
|
||||
#include <limits>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static
|
||||
std::uint64_t
|
||||
getRequiredFeeLevel(TxType txType)
|
||||
{
|
||||
if ((txType == ttAMENDMENT) || (txType == ttFEE))
|
||||
return 0;
|
||||
|
||||
// For now, all valid non-pseudo transactions have a level of 256.
|
||||
// This code can be changed to support variable transaction fees
|
||||
return 256;
|
||||
}
|
||||
|
||||
static
|
||||
std::uint64_t
|
||||
getFeeLevelPaid(
|
||||
STTx const& tx,
|
||||
std::uint64_t baseRefLevel,
|
||||
std::uint64_t refTxnCostDrops)
|
||||
{
|
||||
// Compute the minimum XRP fee the transaction could pay
|
||||
auto requiredFee = getRequiredFeeLevel(tx.getTxnType());
|
||||
|
||||
if (requiredFee == 0 ||
|
||||
refTxnCostDrops == 0)
|
||||
// If nothing is required, or the cost is 0,
|
||||
// the level is effectively infinite.
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
|
||||
// TODO: getRequiredFeeLevel(ttREFERENCE)?
|
||||
auto referenceFee =
|
||||
ripple::getRequiredFeeLevel(ttACCOUNT_SET);
|
||||
return mulDivNoThrow(tx[sfFee].xrp().drops(),
|
||||
baseRefLevel * referenceFee,
|
||||
refTxnCostDrops * requiredFee);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
std::size_t
|
||||
FeeMetrics::updateFeeMetrics(Application& app,
|
||||
ReadView const& view, bool timeLeap)
|
||||
{
|
||||
std::vector<uint64_t> feeLevels;
|
||||
std::size_t txnsExpected;
|
||||
std::size_t mimimumTx;
|
||||
std::uint32_t escalationMultiplier;
|
||||
{
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
feeLevels.reserve(txnsExpected_);
|
||||
txnsExpected = txnsExpected_;
|
||||
mimimumTx = minimumTxnCount_;
|
||||
escalationMultiplier = escalationMultiplier_;
|
||||
}
|
||||
for (auto const& tx : view.txs)
|
||||
{
|
||||
auto const baseFee = calculateBaseFee(app, view,
|
||||
*tx.first, j_);
|
||||
feeLevels.push_back(getFeeLevelPaid(*tx.first,
|
||||
baseLevel, baseFee));
|
||||
}
|
||||
std::sort(feeLevels.begin(), feeLevels.end());
|
||||
auto const size = feeLevels.size();
|
||||
|
||||
JLOG(j_.debug) << "Ledger " << view.info().seq <<
|
||||
" has " << size << " transactions. " <<
|
||||
"Ledgers are processing " <<
|
||||
(timeLeap ? "slowly" : "as expected") <<
|
||||
". Expected transactions is currently " <<
|
||||
txnsExpected << " and multiplier is " <<
|
||||
escalationMultiplier;
|
||||
|
||||
if (timeLeap)
|
||||
{
|
||||
// Ledgers are taking to long to process,
|
||||
// so clamp down on limits.
|
||||
txnsExpected = boost::algorithm::clamp(feeLevels.size(),
|
||||
mimimumTx, targetTxnCount_ - 1);
|
||||
}
|
||||
else if (feeLevels.size() > txnsExpected ||
|
||||
feeLevels.size() > targetTxnCount_)
|
||||
{
|
||||
// Ledgers are processing in a timely manner,
|
||||
// so keep the limit high, but don't let it
|
||||
// grow without bound.
|
||||
txnsExpected = feeLevels.size();
|
||||
}
|
||||
|
||||
if (feeLevels.empty())
|
||||
{
|
||||
escalationMultiplier = minimumMultiplier_;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the case of an odd number of elements, this
|
||||
// evaluates to the middle element; for an even
|
||||
// number of elements, it will add the two elements
|
||||
// on either side of the "middle" and average them.
|
||||
escalationMultiplier = (feeLevels[size / 2] +
|
||||
feeLevels[(size - 1) / 2] + 1) / 2;
|
||||
escalationMultiplier = std::max(escalationMultiplier,
|
||||
minimumMultiplier_);
|
||||
}
|
||||
JLOG(j_.debug) << "Expected transactions updated to " <<
|
||||
txnsExpected << " and multiplier updated to " <<
|
||||
escalationMultiplier;
|
||||
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
txnsExpected_ = txnsExpected;
|
||||
escalationMultiplier_ = escalationMultiplier;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
FeeMetrics::scaleFeeLevel(OpenView const& view) const
|
||||
{
|
||||
auto fee = baseLevel;
|
||||
|
||||
// Transactions in the open ledger so far
|
||||
auto const current = view.txCount();
|
||||
|
||||
std::size_t target;
|
||||
std::uint32_t multiplier;
|
||||
{
|
||||
std::lock_guard <std::mutex> sl(lock_);
|
||||
|
||||
// Target number of transactions allowed
|
||||
target = txnsExpected_;
|
||||
multiplier = escalationMultiplier_;
|
||||
}
|
||||
|
||||
// Once the open ledger bypasses the target,
|
||||
// escalate the fee quickly.
|
||||
if (current > target)
|
||||
{
|
||||
// Compute escalated fee level
|
||||
fee = mulDivNoThrow(fee, current * current *
|
||||
multiplier, target * target);
|
||||
}
|
||||
|
||||
return fee;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
TxQ::CandidateTxn::CandidateTxn(
|
||||
std::shared_ptr<STTx const> const& txn_,
|
||||
TxID const& txID_, std::uint64_t feeLevel_,
|
||||
ApplyFlags const flags_,
|
||||
PreflightResult const& pfresult_)
|
||||
: txn(txn_)
|
||||
, feeLevel(feeLevel_)
|
||||
, txID(txID_)
|
||||
, account(txn_->getAccountID(sfAccount))
|
||||
, sequence(txn_->getSequence())
|
||||
, flags(flags_)
|
||||
, pfresult(pfresult_)
|
||||
{
|
||||
if (txn->isFieldPresent(sfLastLedgerSequence))
|
||||
lastValid = txn->getFieldU32(sfLastLedgerSequence);
|
||||
|
||||
if (txn->isFieldPresent(sfAccountTxnID))
|
||||
priorTxID = txn->getFieldH256(sfAccountTxnID);
|
||||
}
|
||||
|
||||
TxQ::TxQAccount::TxQAccount(std::shared_ptr<STTx const> const& txn)
|
||||
:TxQAccount(txn->getAccountID(sfAccount))
|
||||
{
|
||||
}
|
||||
|
||||
TxQ::TxQAccount::TxQAccount(const AccountID& account_)
|
||||
: account(account_)
|
||||
, totalFees(0)
|
||||
{
|
||||
}
|
||||
|
||||
auto
|
||||
TxQ::TxQAccount::addCandidate(CandidateTxn&& txn)
|
||||
-> CandidateTxn&
|
||||
{
|
||||
auto fee = txn.feeLevel;
|
||||
totalFees += fee;
|
||||
auto sequence = txn.sequence;
|
||||
|
||||
auto result = transactions.emplace(sequence, std::move(txn));
|
||||
assert(result.second);
|
||||
assert(&result.first->second != &txn);
|
||||
|
||||
return result.first->second;
|
||||
}
|
||||
|
||||
bool
|
||||
TxQ::TxQAccount::removeCandidate(TxSeq const& sequence)
|
||||
{
|
||||
return transactions.erase(sequence) != 0;
|
||||
}
|
||||
|
||||
auto
|
||||
TxQ::TxQAccount::findCandidateAt(TxSeq const& sequence) const
|
||||
-> CandidateTxn const*
|
||||
{
|
||||
auto iter = transactions.find(sequence);
|
||||
return iter == transactions.end() ?
|
||||
nullptr : &iter->second;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TxQ::TxQ(Setup const& setup,
|
||||
beast::Journal j)
|
||||
: setup_(setup)
|
||||
, j_(j)
|
||||
, feeMetrics_(setup.standAlone, j)
|
||||
, maxSize_(boost::none)
|
||||
{
|
||||
}
|
||||
|
||||
TxQ::~TxQ()
|
||||
{
|
||||
byFee_.clear();
|
||||
}
|
||||
|
||||
/** Used by tests only.
|
||||
*/
|
||||
std::size_t
|
||||
TxQ::setMinimumTx(int m)
|
||||
{
|
||||
return feeMetrics_.setMinimumTx(m);
|
||||
}
|
||||
|
||||
bool
|
||||
TxQ::canBeHeld(std::shared_ptr<STTx const> const& tx)
|
||||
{
|
||||
// PreviousTxnID is deprecated and should never be used
|
||||
// AccountTxnID is not supported by the transaction
|
||||
// queue yet, but should be added in the future
|
||||
bool canBeHeld =
|
||||
! tx->isFieldPresent(sfPreviousTxnID) &&
|
||||
! tx->isFieldPresent(sfAccountTxnID);
|
||||
if (canBeHeld)
|
||||
{
|
||||
auto accountIter = byAccount_.find(tx->getAccountID(sfAccount));
|
||||
canBeHeld = accountIter == byAccount_.end()
|
||||
|| accountIter->second.empty();
|
||||
}
|
||||
return canBeHeld;
|
||||
}
|
||||
|
||||
auto
|
||||
TxQ::erase(TxQ::FeeMultiSet::const_iterator_type candidateIter)
|
||||
-> FeeMultiSet::iterator_type
|
||||
{
|
||||
auto& txQAccount = byAccount_.at(candidateIter->account);
|
||||
auto sequence = candidateIter->sequence;
|
||||
auto newCandidateIter = byFee_.erase(candidateIter);
|
||||
// Now that the candidate has been removed from the
|
||||
// intrusive list remove it from the TxQAccount
|
||||
// so the memory can be freed.
|
||||
auto found = txQAccount.removeCandidate(sequence);
|
||||
(void)found;
|
||||
assert(found);
|
||||
|
||||
return newCandidateIter;
|
||||
}
|
||||
|
||||
std::pair<TER, bool>
|
||||
TxQ::apply(Application& app, OpenView& view,
|
||||
std::shared_ptr<STTx const> const& tx,
|
||||
ApplyFlags flags, beast::Journal j)
|
||||
{
|
||||
auto const allowEscalation =
|
||||
(flags & tapENABLE_TESTING) ||
|
||||
(view.rules().enabled(featureFeeEscalation,
|
||||
app.config().features));
|
||||
if (!allowEscalation)
|
||||
{
|
||||
return ripple::apply(app, view, *tx, flags, j);
|
||||
}
|
||||
|
||||
auto const account = (*tx)[sfAccount];
|
||||
auto currentSeq = true;
|
||||
|
||||
// If there are other transactions in the queue
|
||||
// for this account, account for that before the pre-checks,
|
||||
// so we don't get a false terPRE_SEQ.
|
||||
auto accountIter = byAccount_.find(account);
|
||||
if (accountIter != byAccount_.end())
|
||||
{
|
||||
auto const sle = view.read(
|
||||
keylet::account(account));
|
||||
|
||||
if (sle)
|
||||
{
|
||||
auto const& txQAcct = accountIter->second;
|
||||
auto const t_seq = tx->getSequence();
|
||||
auto const a_seq = sle->getFieldU32(sfSequence);
|
||||
|
||||
currentSeq = a_seq == t_seq;
|
||||
for (auto seq = a_seq; !currentSeq && seq < t_seq; ++seq)
|
||||
{
|
||||
auto const existingCandidate =
|
||||
txQAcct.findCandidateAt(seq);
|
||||
currentSeq = !existingCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See if the transaction is likely to claim a fee.
|
||||
auto const pfresult = preflight(app, view.rules(),
|
||||
*tx, flags | (currentSeq ? tapNONE: tapPOST_SEQ), j);
|
||||
auto const pcresult = preclaim(pfresult, app, view);
|
||||
if (!pcresult.likelyToClaimFee)
|
||||
return{ pcresult.ter, false };
|
||||
|
||||
auto const baseFee = pcresult.baseFee;
|
||||
auto const feeLevelPaid = getFeeLevelPaid(*tx,
|
||||
feeMetrics_.baseLevel, baseFee);
|
||||
auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
|
||||
auto const transactionID = tx->getTransactionID();
|
||||
|
||||
// Too low of a fee should get caught by preclaim
|
||||
assert(feeLevelPaid >= feeMetrics_.baseLevel);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// Is there a transaction for the same account with the
|
||||
// same sequence number already in the queue?
|
||||
if (accountIter != byAccount_.end())
|
||||
{
|
||||
auto const sequence = tx->getSequence();
|
||||
auto& txQAcct = accountIter->second;
|
||||
auto existingCandidate = txQAcct.findCandidateAt(sequence);
|
||||
if (existingCandidate)
|
||||
{
|
||||
// Is the current transaction's fee higher than
|
||||
// the queued transaction's fee?
|
||||
auto requiredRetryLevel = mulDivNoThrow(
|
||||
existingCandidate->feeLevel,
|
||||
setup_.retrySequencePercent, 100);
|
||||
JLOG(j_.trace) << "Found transaction in queue for account " <<
|
||||
account << " with sequence number " << sequence <<
|
||||
" new txn fee level is " << feeLevelPaid <<
|
||||
", old txn fee level is " <<
|
||||
existingCandidate->feeLevel <<
|
||||
", new txn needs fee level of " <<
|
||||
requiredRetryLevel;
|
||||
if (feeLevelPaid > requiredRetryLevel
|
||||
|| (existingCandidate->feeLevel < requiredFeeLevel &&
|
||||
feeLevelPaid >= requiredFeeLevel))
|
||||
{
|
||||
// The fee is high enough to either retry or
|
||||
// the prior txn could not get into the open ledger,
|
||||
// but this one can.
|
||||
// Remove the queued transaction and continue
|
||||
JLOG(j_.trace) <<
|
||||
"Removing transaction from queue " <<
|
||||
existingCandidate->txID <<
|
||||
" in favor of " << transactionID;
|
||||
auto byFeeIter = byFee_.iterator_to(*existingCandidate);
|
||||
assert(byFeeIter != byFee_.end());
|
||||
assert(existingCandidate == &*byFeeIter);
|
||||
assert(byFeeIter->sequence == sequence);
|
||||
assert(byFeeIter->account == txQAcct.account);
|
||||
erase(byFeeIter);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Drop the current transaction
|
||||
JLOG(j_.trace) <<
|
||||
"Ignoring transaction " <<
|
||||
transactionID <<
|
||||
" in favor of queued " <<
|
||||
existingCandidate->txID;
|
||||
return { telINSUF_FEE_P, false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j_.trace) << "Transaction " <<
|
||||
transactionID <<
|
||||
" from account " << account <<
|
||||
" has fee level of " << feeLevelPaid <<
|
||||
" needs at least " << requiredFeeLevel <<
|
||||
" to get in the open ledger, which has " <<
|
||||
view.txCount() << " entries.";
|
||||
|
||||
// Can transaction go in open ledger?
|
||||
if (currentSeq && feeLevelPaid >= requiredFeeLevel)
|
||||
{
|
||||
// Transaction fee is sufficient to go in open ledger immediately
|
||||
|
||||
JLOG(j_.trace) << "Applying transaction " <<
|
||||
transactionID <<
|
||||
" to open ledger.";
|
||||
ripple::TER txnResult;
|
||||
bool didApply;
|
||||
|
||||
std::tie(txnResult, didApply) = doApply(pcresult, app, view);
|
||||
|
||||
if (didApply)
|
||||
{
|
||||
JLOG(j_.trace) << "Transaction " <<
|
||||
transactionID <<
|
||||
" applied successfully with " <<
|
||||
transToken(txnResult);
|
||||
|
||||
return { txnResult, true };
|
||||
}
|
||||
// failure
|
||||
JLOG(j_.trace) << "Transaction " <<
|
||||
transactionID <<
|
||||
" failed with " << transToken(txnResult);
|
||||
|
||||
return { txnResult, false };
|
||||
}
|
||||
|
||||
if (! canBeHeld(tx))
|
||||
{
|
||||
// Bail, transaction cannot be held
|
||||
JLOG(j_.trace) << "Transaction " <<
|
||||
transactionID <<
|
||||
" can not be held";
|
||||
return { feeLevelPaid >= requiredFeeLevel ?
|
||||
terPRE_SEQ : telINSUF_FEE_P, false };
|
||||
}
|
||||
|
||||
// It's pretty unlikely that the queue will be "overfilled",
|
||||
// but should it happen, take the opportunity to fix it now.
|
||||
while (isFull())
|
||||
{
|
||||
auto lastRIter = byFee_.rbegin();
|
||||
if (feeLevelPaid > lastRIter->feeLevel)
|
||||
{
|
||||
// The queue is full, and this transaction is more
|
||||
// valuable, so kick out the cheapest transaction.
|
||||
JLOG(j_.warning) <<
|
||||
"Removing end item from queue with fee of" <<
|
||||
lastRIter->feeLevel << " in favor of " <<
|
||||
transactionID << " with fee of " <<
|
||||
feeLevelPaid;
|
||||
erase(byFee_.iterator_to(*lastRIter));
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.warning) << "Queue is full, and transaction " <<
|
||||
transactionID <<
|
||||
" fee is lower than end item";
|
||||
return { telINSUF_FEE_P, false };
|
||||
}
|
||||
}
|
||||
|
||||
// Hold the transaction.
|
||||
// accountIter was already set when looking for duplicate seq.
|
||||
std::string op = "existing";
|
||||
if (accountIter == byAccount_.end())
|
||||
{
|
||||
// Create a new TxQAccount object and add the byAccount lookup.
|
||||
bool created;
|
||||
std::tie(accountIter, created) = byAccount_.emplace(
|
||||
account, TxQAccount(tx));
|
||||
(void)created;
|
||||
assert(created);
|
||||
op = "new";
|
||||
}
|
||||
auto& candidate = accountIter->second.addCandidate(
|
||||
{ tx, transactionID, feeLevelPaid, flags, pfresult });
|
||||
// Then index it into the byFee lookup.
|
||||
byFee_.insert(candidate);
|
||||
JLOG(j_.debug) << "Added transaction " << candidate.txID <<
|
||||
" from " << op << " account " << candidate.account <<
|
||||
" to queue.";
|
||||
|
||||
return { terQUEUED, false };
|
||||
}
|
||||
|
||||
void
|
||||
TxQ::processValidatedLedger(Application& app,
|
||||
OpenView const& view, bool timeLeap,
|
||||
ApplyFlags flags)
|
||||
{
|
||||
auto const allowEscalation =
|
||||
(flags & tapENABLE_TESTING) ||
|
||||
(view.rules().enabled(featureFeeEscalation,
|
||||
app.config().features));
|
||||
if (!allowEscalation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
feeMetrics_.updateFeeMetrics(app, view, timeLeap);
|
||||
|
||||
auto ledgerSeq = view.info().seq;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!timeLeap)
|
||||
maxSize_ = feeMetrics_.getTxnsExpected() * setup_.ledgersInQueue;
|
||||
|
||||
// Remove any queued candidates whos LastLedgerSequence has gone by.
|
||||
// Stop if we leave maxSize_ candidates.
|
||||
size_t keptCandidates = 0;
|
||||
auto candidateIter = byFee_.begin();
|
||||
while (candidateIter != byFee_.end()
|
||||
&& (!maxSize_ || keptCandidates < *maxSize_))
|
||||
{
|
||||
if (candidateIter->lastValid
|
||||
&& *candidateIter->lastValid >= ledgerSeq)
|
||||
{
|
||||
candidateIter = erase(candidateIter);
|
||||
}
|
||||
else
|
||||
{
|
||||
++keptCandidates;
|
||||
++candidateIter;
|
||||
}
|
||||
}
|
||||
// Erase any candidates more than maxSize_.
|
||||
// This can help keep the queue from getting overfull.
|
||||
for (; candidateIter != byFee_.end(); ++candidateIter)
|
||||
candidateIter = erase(candidateIter);
|
||||
|
||||
// Remove any TxQAccounts that don't have candidates
|
||||
// under them
|
||||
for (auto txQAccountIter = byAccount_.begin();
|
||||
txQAccountIter != byAccount_.end();)
|
||||
{
|
||||
if (txQAccountIter->second.empty())
|
||||
txQAccountIter = byAccount_.erase(txQAccountIter);
|
||||
else
|
||||
++txQAccountIter;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TxQ::accept(Application& app,
|
||||
OpenView& view, ApplyFlags flags)
|
||||
{
|
||||
auto const allowEscalation =
|
||||
(flags & tapENABLE_TESTING) ||
|
||||
(view.rules().enabled(featureFeeEscalation,
|
||||
app.config().features));
|
||||
if (!allowEscalation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Move transactions from the queue from largest fee to smallest.
|
||||
As we add more transactions, the required fee will increase.
|
||||
Stop when the transaction fee gets lower than the required fee.
|
||||
*/
|
||||
|
||||
auto ledgerChanged = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
|
||||
{
|
||||
auto const requiredFeeLevel = feeMetrics_.scaleFeeLevel(view);
|
||||
auto const feeLevelPaid = candidateIter->feeLevel;
|
||||
JLOG(j_.trace) << "Queued transaction " <<
|
||||
candidateIter->txID << " from account " <<
|
||||
candidateIter->account << " has fee level of " <<
|
||||
feeLevelPaid << " needs at least " <<
|
||||
requiredFeeLevel;
|
||||
if (feeLevelPaid >= requiredFeeLevel)
|
||||
{
|
||||
auto firstTxn = candidateIter->txn;
|
||||
|
||||
JLOG(j_.trace) << "Applying queued transaction " <<
|
||||
candidateIter->txID << " to open ledger.";
|
||||
|
||||
// If the rules or flags change, preflight again
|
||||
assert(candidateIter->pfresult);
|
||||
if (candidateIter->pfresult->rules != view.rules() ||
|
||||
candidateIter->pfresult->flags != candidateIter->flags)
|
||||
{
|
||||
candidateIter->pfresult.emplace(
|
||||
preflight(app, view.rules(),
|
||||
candidateIter->pfresult->tx,
|
||||
candidateIter->flags,
|
||||
candidateIter->pfresult->j));
|
||||
}
|
||||
|
||||
auto pcresult = preclaim(
|
||||
*candidateIter->pfresult, app, view);
|
||||
|
||||
TER txnResult;
|
||||
bool didApply;
|
||||
std::tie(txnResult, didApply) = doApply(pcresult, app, view);
|
||||
|
||||
if (didApply)
|
||||
{
|
||||
// Remove the candidate from the queue
|
||||
JLOG(j_.debug) << "Queued transaction " <<
|
||||
candidateIter->txID <<
|
||||
" applied successfully. Remove from queue.";
|
||||
|
||||
candidateIter = erase(candidateIter);
|
||||
ledgerChanged = true;
|
||||
}
|
||||
else if (isTefFailure(txnResult) || isTemMalformed(txnResult) ||
|
||||
isTelLocal(txnResult))
|
||||
{
|
||||
JLOG(j_.debug) << "Queued transaction " <<
|
||||
candidateIter->txID << " failed with " <<
|
||||
transToken(txnResult) << ". Remove from queue.";
|
||||
candidateIter = erase(candidateIter);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug) << "Transaction " <<
|
||||
candidateIter->txID << " failed with " <<
|
||||
transToken(txnResult) << ". Leave in queue.";
|
||||
candidateIter++;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ledgerChanged;
|
||||
}
|
||||
|
||||
TxQ::Metrics
|
||||
TxQ::getMetrics(OpenView const& view) const
|
||||
{
|
||||
Metrics result;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
result.txCount = byFee_.size();
|
||||
result.txQMaxSize = maxSize_;
|
||||
result.txInLedger = view.txCount();
|
||||
result.txPerLedger = feeMetrics_.getTxnsExpected();
|
||||
result.referenceFeeLevel = feeMetrics_.baseLevel;
|
||||
result.minFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 :
|
||||
feeMetrics_.baseLevel;
|
||||
result.medFeeLevel = feeMetrics_.getEscalationMultiplier();
|
||||
result.expFeeLevel = feeMetrics_.scaleFeeLevel(view);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
TxQ::doRPC(Application& app) const
|
||||
{
|
||||
using std::to_string;
|
||||
|
||||
Json::Value ret(Json::objectValue);
|
||||
|
||||
auto& levels = ret[jss::levels] = Json::objectValue;
|
||||
|
||||
auto const view = app.openLedger().current();
|
||||
auto const metrics = getMetrics(*view);
|
||||
|
||||
ret[jss::expected_ledger_size] = to_string(metrics.txPerLedger);
|
||||
ret[jss::current_ledger_size] = to_string(metrics.txInLedger);
|
||||
ret[jss::current_queue_size] = to_string(metrics.txCount);
|
||||
if (metrics.txQMaxSize)
|
||||
ret[jss::max_queue_size] = to_string(*metrics.txQMaxSize);
|
||||
|
||||
levels[jss::reference_level] = to_string(metrics.referenceFeeLevel);
|
||||
levels[jss::minimum_level] = to_string(metrics.minFeeLevel);
|
||||
levels[jss::median_level] = to_string(metrics.medFeeLevel);
|
||||
levels[jss::open_ledger_level] = to_string(metrics.expFeeLevel);
|
||||
|
||||
auto const baseFee = view->fees().base;
|
||||
auto& drops = ret[jss::drops] = Json::Value();
|
||||
|
||||
drops[jss::base_fee] = to_string(mulDivNoThrow(
|
||||
metrics.referenceFeeLevel, baseFee,
|
||||
metrics.referenceFeeLevel));
|
||||
drops[jss::minimum_fee] = to_string(mulDivNoThrow(
|
||||
metrics.minFeeLevel, baseFee,
|
||||
metrics.referenceFeeLevel));
|
||||
drops[jss::median_fee] = to_string(mulDivNoThrow(
|
||||
metrics.medFeeLevel, baseFee,
|
||||
metrics.referenceFeeLevel));
|
||||
drops[jss::open_ledger_fee] = to_string(mulDivNoThrow(
|
||||
metrics.expFeeLevel, baseFee,
|
||||
metrics.referenceFeeLevel));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
TxQ::openLedgerFee(OpenView const& view) const
|
||||
{
|
||||
auto metrics = getMetrics(view);
|
||||
|
||||
return mulDivNoThrow(metrics.expFeeLevel,
|
||||
view.fees().base, metrics.referenceFeeLevel) + 1;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TxQ::Setup
|
||||
setup_TxQ(Config const& config)
|
||||
{
|
||||
TxQ::Setup setup;
|
||||
auto const& section = config.section("transaction_queue");
|
||||
set(setup.ledgersInQueue, "ledgers_in_queue", section);
|
||||
set(setup.retrySequencePercent, "retry_sequence_percent", section);
|
||||
setup.standAlone = config.RUN_STANDALONE;
|
||||
return setup;
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<TxQ>
|
||||
make_TxQ(TxQ::Setup const& setup, beast::Journal j)
|
||||
{
|
||||
return std::make_unique<TxQ>(setup, std::move(j));
|
||||
}
|
||||
|
||||
} // ripple
|
||||
368
src/ripple/app/tests/TxQ_test.cpp
Normal file
368
src/ripple/app/tests/TxQ_test.cpp
Normal file
@@ -0,0 +1,368 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/ledger/LedgerConsensus.h>
|
||||
#include <ripple/core/LoadFeeTrack.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/TestSuite.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class TxQ_test : public TestSuite
|
||||
{
|
||||
void
|
||||
checkMetrics(
|
||||
jtx::Env& env,
|
||||
std::size_t expectedCount,
|
||||
boost::optional<std::size_t> expectedMaxCount,
|
||||
std::size_t expectedInLedger,
|
||||
std::size_t expectedPerLedger,
|
||||
std::uint64_t expectedMinFeeLevel,
|
||||
std::uint64_t expectedMedFeeLevel)
|
||||
{
|
||||
auto metrics = env.app().getTxQ().getMetrics(*env.open());
|
||||
expect(metrics.referenceFeeLevel == 256, "referenceFeeLevel");
|
||||
expect(metrics.txCount == expectedCount, "txCount");
|
||||
expect(metrics.txQMaxSize == expectedMaxCount, "txQMaxSize");
|
||||
expect(metrics.txInLedger == expectedInLedger, "txInLedger");
|
||||
expect(metrics.txPerLedger == expectedPerLedger, "txPerLedger");
|
||||
expect(metrics.minFeeLevel == expectedMinFeeLevel, "minFeeLevel");
|
||||
expect(metrics.medFeeLevel == expectedMedFeeLevel, "medFeeLevel");
|
||||
auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ?
|
||||
metrics.referenceFeeLevel * expectedMedFeeLevel *
|
||||
expectedInLedger * expectedInLedger /
|
||||
(expectedPerLedger * expectedPerLedger) :
|
||||
metrics.referenceFeeLevel;
|
||||
expect(metrics.expFeeLevel == expectedCurFeeLevel, "expFeeLevel");
|
||||
}
|
||||
|
||||
void
|
||||
close(jtx::Env& env, size_t expectedTxSetSize, bool timeLeap = false)
|
||||
{
|
||||
{
|
||||
auto const view = env.open();
|
||||
expect(view->txCount() == expectedTxSetSize, "TxSet size mismatch");
|
||||
// Update fee computations.
|
||||
// Note implementing this way assumes that everything
|
||||
// in the open ledger _will_ make it into the closed
|
||||
// ledger, but for metrics that's probably good enough.
|
||||
env.app().getTxQ().processValidatedLedger(
|
||||
env.app(), *view, timeLeap, tapENABLE_TESTING);
|
||||
}
|
||||
|
||||
env.close(
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return env.app().getTxQ().accept(env.app(), view,
|
||||
tapENABLE_TESTING);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
submit(jtx::Env& env, jtx::JTx const& jt)
|
||||
{
|
||||
// Env checks this, but this test shouldn't
|
||||
// generate any malformed txns.
|
||||
expect(jt.stx);
|
||||
|
||||
bool didApply;
|
||||
TER ter;
|
||||
|
||||
env.openLedger.modify(
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
std::tie(ter, didApply) =
|
||||
env.app().getTxQ().apply(env.app(),
|
||||
view, jt.stx, tapENABLE_TESTING,
|
||||
env.journal);
|
||||
|
||||
return didApply;
|
||||
}
|
||||
);
|
||||
|
||||
env.postconditions(jt, ter, didApply);
|
||||
}
|
||||
|
||||
static
|
||||
std::unique_ptr<Config const>
|
||||
makeConfig()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*p);
|
||||
auto& section = p->section("transaction_queue");
|
||||
section.set("ledgers_in_queue", "2");
|
||||
section.set("min_ledgers_to_compute_size_limit", "3");
|
||||
section.set("max_ledger_counts_to_store", "100");
|
||||
section.set("retry_sequence_percent", "125");
|
||||
return std::unique_ptr<Config const>(p.release());
|
||||
}
|
||||
|
||||
public:
|
||||
void run()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, makeConfig());
|
||||
|
||||
auto& txq = env.app().getTxQ();
|
||||
txq.setMinimumTx(3);
|
||||
|
||||
auto alice = Account("alice");
|
||||
auto bob = Account("bob");
|
||||
auto charlie = Account("charlie");
|
||||
auto daria = Account("daria");
|
||||
auto elmo = Account("elmo");
|
||||
auto fred = Account("fred");
|
||||
auto gwen = Account("gwen");
|
||||
auto hank = Account("hank");
|
||||
|
||||
auto queued = ter(terQUEUED);
|
||||
|
||||
expectEquals(env.open()->fees().base, 10);
|
||||
|
||||
checkMetrics(env, 0, boost::none, 0, 3, 256, 500);
|
||||
|
||||
// Create several accounts while the fee is cheap so they all apply.
|
||||
env.fund(XRP(50000), noripple(alice, bob, charlie, daria));
|
||||
checkMetrics(env, 0, boost::none, 4, 3, 256, 500);
|
||||
|
||||
// Alice - price starts exploding: held
|
||||
submit(env,
|
||||
env.jt(noop(alice), queued));
|
||||
checkMetrics(env, 1, boost::none, 4, 3, 256, 500);
|
||||
|
||||
// Alice - Alice is already in the queue, so can't hold.
|
||||
submit(env,
|
||||
env.jt(noop(alice), seq(env.seq(alice) + 1),
|
||||
ter(telINSUF_FEE_P)));
|
||||
checkMetrics(env, 1, boost::none, 4, 3, 256, 500);
|
||||
|
||||
auto openLedgerFee =
|
||||
[&]()
|
||||
{
|
||||
return fee(txq.openLedgerFee(*env.open()));
|
||||
};
|
||||
// Alice's next transaction -
|
||||
// fails because the item in the TxQ hasn't applied.
|
||||
submit(env,
|
||||
env.jt(noop(alice), openLedgerFee(),
|
||||
seq(env.seq(alice) + 1), ter(terPRE_SEQ)));
|
||||
checkMetrics(env, 1, boost::none, 4, 3, 256, 500);
|
||||
|
||||
// Bob with really high fee - applies
|
||||
submit(env,
|
||||
env.jt(noop(bob), openLedgerFee()));
|
||||
checkMetrics(env, 1, boost::none, 5, 3, 256, 500);
|
||||
|
||||
// Daria with low fee: hold
|
||||
submit(env,
|
||||
env.jt(noop(daria), fee(1000), queued));
|
||||
checkMetrics(env, 2, boost::none, 5, 3, 256, 500);
|
||||
|
||||
close(env, 5);
|
||||
// Verify that the held transactions got applied
|
||||
auto lastMedian = 500;
|
||||
checkMetrics(env, 0, 10, 2, 5, 256, lastMedian);
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
// Make some more accounts. We'll need them later to abuse the queue.
|
||||
env.fund(XRP(50000), noripple(elmo, fred, gwen, hank));
|
||||
checkMetrics(env, 0, 10, 6, 5, 256, lastMedian);
|
||||
|
||||
// Now get a bunch of transactions held.
|
||||
submit(env,
|
||||
env.jt(noop(alice), fee(12), queued));
|
||||
checkMetrics(env, 1, 10, 6, 5, 256, lastMedian);
|
||||
|
||||
submit(env,
|
||||
env.jt(noop(bob), fee(10), queued)); // won't clear the queue
|
||||
submit(env,
|
||||
env.jt(noop(charlie), fee(20), queued));
|
||||
submit(env,
|
||||
env.jt(noop(daria), fee(15), queued));
|
||||
submit(env,
|
||||
env.jt(noop(elmo), fee(11), queued));
|
||||
submit(env,
|
||||
env.jt(noop(fred), fee(19), queued));
|
||||
submit(env,
|
||||
env.jt(noop(gwen), fee(16), queued));
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(18), queued));
|
||||
checkMetrics(env, 8, 10, 6, 5, 256, lastMedian);
|
||||
|
||||
close(env, 6);
|
||||
// Verify that the held transactions got applied
|
||||
lastMedian = 500;
|
||||
checkMetrics(env, 1, 12, 7, 6, 256, lastMedian);
|
||||
|
||||
// Bob's transaction is still stuck in the queue.
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
// Hank sends another txn
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(10), queued));
|
||||
// But he's not going to leave it in the queue
|
||||
checkMetrics(env, 2, 12, 7, 6, 256, lastMedian);
|
||||
|
||||
// Hank sees his txn got held and bumps the fee,
|
||||
// but doesn't even bump it enough to requeue
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(11), ter(telINSUF_FEE_P)));
|
||||
checkMetrics(env, 2, 12, 7, 6, 256, lastMedian);
|
||||
|
||||
// Hank sees his txn got held and bumps the fee,
|
||||
// enough to requeue, but doesn't bump it enough to
|
||||
// apply to the ledger
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(6000), queued));
|
||||
// But he's not going to leave it in the queue
|
||||
checkMetrics(env, 2, 12, 7, 6, 256, lastMedian);
|
||||
|
||||
// Hank sees his txn got held and bumps the fee,
|
||||
// high enough to get into the open ledger, because
|
||||
// he doesn't want to wait.
|
||||
submit(env,
|
||||
env.jt(noop(hank), openLedgerFee()));
|
||||
checkMetrics(env, 1, 12, 8, 6, 256, lastMedian);
|
||||
|
||||
// Hank then sends another, less important txn
|
||||
// (In addition to the metrics, this will verify that
|
||||
// the original txn got removed.)
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(6000), queued));
|
||||
checkMetrics(env, 2, 12, 8, 6, 256, lastMedian);
|
||||
|
||||
close(env, 8);
|
||||
|
||||
// Verify that bob and hank's txns were applied
|
||||
lastMedian = 500;
|
||||
checkMetrics(env, 0, 16, 2, 8, 256, lastMedian);
|
||||
|
||||
// Close again with a simulated time leap to
|
||||
// reset the escalation limit down to minimum
|
||||
lastMedian = 76928;
|
||||
close(env, 2, true);
|
||||
checkMetrics(env, 0, 16, 0, 3, 256, lastMedian);
|
||||
// Then close once more without the time leap
|
||||
// to reset the queue maxsize down to minimum
|
||||
lastMedian = 500;
|
||||
close(env, 0);
|
||||
checkMetrics(env, 0, 6, 0, 3, 256, lastMedian);
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
// At this point, the queue should have a limit of 6.
|
||||
// Stuff the ledger and queue so we can verify that
|
||||
// stuff gets kicked out.
|
||||
submit(env,
|
||||
env.jt(noop(hank)));
|
||||
submit(env,
|
||||
env.jt(noop(gwen)));
|
||||
submit(env,
|
||||
env.jt(noop(fred)));
|
||||
submit(env,
|
||||
env.jt(noop(elmo)));
|
||||
checkMetrics(env, 0, 6, 4, 3, 256, lastMedian);
|
||||
|
||||
// Use explicit fees so we can control which txn
|
||||
// will get dropped
|
||||
submit(env,
|
||||
env.jt(noop(alice), fee(20), queued));
|
||||
submit(env,
|
||||
env.jt(noop(hank), fee(19), queued));
|
||||
submit(env,
|
||||
env.jt(noop(gwen), fee(18), queued));
|
||||
submit(env,
|
||||
env.jt(noop(fred), fee(17), queued));
|
||||
submit(env,
|
||||
env.jt(noop(elmo), fee(16), queued));
|
||||
// This one gets into the queue, but gets dropped when the
|
||||
// higher fee one is added later.
|
||||
submit(env,
|
||||
env.jt(noop(daria), fee(15), queued));
|
||||
|
||||
// Queue is full now.
|
||||
checkMetrics(env, 6, 6, 4, 3, 385, lastMedian);
|
||||
|
||||
// Try to add another transaction with the default (low) fee,
|
||||
// it should fail because the queue is full.
|
||||
submit(env,
|
||||
env.jt(noop(charlie), ter(telINSUF_FEE_P)));
|
||||
|
||||
// Add another transaction, with a higher fee,
|
||||
// Not high enough to get into the ledger, but high
|
||||
// enough to get into the queue (and kick somebody out)
|
||||
submit(env,
|
||||
env.jt(noop(charlie), fee(100), queued));
|
||||
|
||||
// Queue is still full, of course, but the min fee has gone up
|
||||
checkMetrics(env, 6, 6, 4, 3, 410, lastMedian);
|
||||
|
||||
close(env, 4);
|
||||
lastMedian = 500;
|
||||
checkMetrics(env, 1, 8, 5, 4, 256, lastMedian);
|
||||
|
||||
lastMedian = 500;
|
||||
close(env, 5);
|
||||
checkMetrics(env, 0, 10, 1, 5, 256, lastMedian);
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Cleanup:
|
||||
|
||||
// Create a few more transactions, so that
|
||||
// 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.open());
|
||||
expect(metrics.txCount == 0, "txCount");
|
||||
auto txnsNeeded = metrics.txPerLedger - metrics.txInLedger;
|
||||
|
||||
// Stuff the ledger.
|
||||
for (int i = 0; i <= txnsNeeded; ++i)
|
||||
{
|
||||
submit(env,
|
||||
env.jt(noop(env.master)));
|
||||
}
|
||||
|
||||
// Queue one straightforward transaction
|
||||
submit(env,
|
||||
env.jt(noop(env.master), fee(20), queued));
|
||||
++metrics.txCount;
|
||||
|
||||
checkMetrics(env, metrics.txCount,
|
||||
metrics.txQMaxSize, metrics.txPerLedger + 1,
|
||||
metrics.txPerLedger,
|
||||
256, lastMedian);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(TxQ,app,ripple);
|
||||
|
||||
}
|
||||
}
|
||||
161
src/ripple/app/tx/applySteps.h
Normal file
161
src/ripple/app/tx/applySteps.h
Normal file
@@ -0,0 +1,161 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_APPLYSTEPS_H_INCLUDED
|
||||
#define RIPPLE_TX_APPLYSTEPS_H_INCLUDED
|
||||
|
||||
#include <ripple/ledger/ApplyViewImpl.h>
|
||||
#include <beast/utility/Journal.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Application;
|
||||
class STTx;
|
||||
|
||||
struct PreflightResult
|
||||
{
|
||||
public:
|
||||
// from the context
|
||||
STTx const& tx;
|
||||
Rules const rules;
|
||||
ApplyFlags const flags;
|
||||
beast::Journal const j;
|
||||
|
||||
// result
|
||||
TER const ter;
|
||||
|
||||
template<class Context>
|
||||
PreflightResult(Context const& ctx_,
|
||||
TER ter_)
|
||||
: tx(ctx_.tx)
|
||||
, rules(ctx_.rules)
|
||||
, flags(ctx_.flags)
|
||||
, j(ctx_.j)
|
||||
, ter(ter_)
|
||||
{
|
||||
}
|
||||
|
||||
PreflightResult& operator=(PreflightResult const&) = delete;
|
||||
};
|
||||
|
||||
struct PreclaimResult
|
||||
{
|
||||
public:
|
||||
// from the context
|
||||
ReadView const& view;
|
||||
STTx const& tx;
|
||||
ApplyFlags const flags;
|
||||
beast::Journal const j;
|
||||
|
||||
// result
|
||||
TER const ter;
|
||||
std::uint64_t const baseFee;
|
||||
bool const likelyToClaimFee;
|
||||
|
||||
template<class Context>
|
||||
PreclaimResult(Context const& ctx_,
|
||||
TER ter_, std::uint64_t const& baseFee_)
|
||||
: view(ctx_.view)
|
||||
, tx(ctx_.tx)
|
||||
, flags(ctx_.flags)
|
||||
, j(ctx_.j)
|
||||
, ter(ter_)
|
||||
, baseFee(baseFee_)
|
||||
, likelyToClaimFee(ter == tesSUCCESS
|
||||
|| isTecClaim(ter))
|
||||
{
|
||||
}
|
||||
|
||||
template<class Context>
|
||||
PreclaimResult(Context const& ctx_,
|
||||
std::pair<TER, std::uint64_t> const& result)
|
||||
: PreclaimResult(ctx_, result.first, result.second)
|
||||
{
|
||||
}
|
||||
|
||||
PreclaimResult& operator=(PreclaimResult const&) = delete;
|
||||
};
|
||||
|
||||
/** Gate a transaction based on static information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that do not require a ledger.
|
||||
|
||||
@return A PreflightResult object constaining, among
|
||||
other things, the TER code.
|
||||
*/
|
||||
PreflightResult
|
||||
preflight(Application& app, Rules const& rules,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
beast::Journal j);
|
||||
|
||||
/** Gate a transaction based on static ledger information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that DO require a ledger.
|
||||
|
||||
If preclaim succeeds, then the transaction is very
|
||||
likely to claim a fee. This will determine if the
|
||||
transaction is safe to relay without being applied
|
||||
to the open ledger.
|
||||
|
||||
"Succeeds" in this case is defined as returning a
|
||||
`tes` or `tec`, since both lead to claiming a fee.
|
||||
|
||||
@return A PreclaimResult object containing, among
|
||||
other things the TER code and the base fee value for
|
||||
this transaction.
|
||||
*/
|
||||
PreclaimResult
|
||||
preclaim(PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view);
|
||||
|
||||
/** Compute only the expected base fee for a transaction.
|
||||
|
||||
Base fees are transaction specific, so any calculation
|
||||
needing them must get the base fee for each transaction.
|
||||
|
||||
No validation is done or implied by this function.
|
||||
|
||||
Caller is responsible for handling any exceptions.
|
||||
Since none should be thrown, that will usually
|
||||
mean terminating.
|
||||
|
||||
@return The base fee.
|
||||
*/
|
||||
std::uint64_t
|
||||
calculateBaseFee(Application& app, ReadView const& view,
|
||||
STTx const& tx, beast::Journal j);
|
||||
/** Apply a prechecked transaction to an OpenView.
|
||||
|
||||
See also: apply()
|
||||
|
||||
Precondition: The transaction has been checked
|
||||
and validated using the above functions.
|
||||
|
||||
@return A pair with the TER and a bool indicating
|
||||
whether or not the transaction was applied.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -236,6 +236,11 @@ Transactor::checkSeq (PreclaimContext const& ctx)
|
||||
{
|
||||
if (a_seq < t_seq)
|
||||
{
|
||||
if (ctx.flags & tapPOST_SEQ)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: has future sequence number " <<
|
||||
"a_seq=" << a_seq << " t_seq=" << t_seq;
|
||||
|
||||
@@ -32,27 +32,15 @@ struct PreflightContext
|
||||
public:
|
||||
Application& app;
|
||||
STTx const& tx;
|
||||
Rules const& rules;
|
||||
Rules const rules;
|
||||
ApplyFlags flags;
|
||||
beast::Journal j;
|
||||
|
||||
PreflightContext(Application& app_, STTx const& tx_,
|
||||
Rules const& rules_, ApplyFlags flags_,
|
||||
beast::Journal j_);
|
||||
};
|
||||
|
||||
struct PreflightResult
|
||||
{
|
||||
public:
|
||||
PreflightContext const ctx;
|
||||
TER const ter;
|
||||
|
||||
PreflightResult(PreflightContext const& ctx_,
|
||||
TER ter_)
|
||||
: ctx(ctx_)
|
||||
, ter(ter_)
|
||||
{
|
||||
}
|
||||
PreflightContext& operator=(PreflightContext const&) = delete;
|
||||
};
|
||||
|
||||
/** State information when determining if a tx is likely to claim a fee. */
|
||||
@@ -77,28 +65,8 @@ public:
|
||||
, j(j_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct PreclaimResult
|
||||
{
|
||||
public:
|
||||
PreclaimContext const ctx;
|
||||
TER const ter;
|
||||
std::uint64_t const baseFee;
|
||||
|
||||
PreclaimResult(PreclaimContext const& ctx_,
|
||||
TER ter_, std::uint64_t const& baseFee_)
|
||||
: ctx(ctx_)
|
||||
, ter(ter_)
|
||||
, baseFee(baseFee_)
|
||||
{
|
||||
}
|
||||
|
||||
PreclaimResult(PreclaimContext const& ctx_,
|
||||
std::pair<TER, std::uint64_t> const& result)
|
||||
: PreclaimResult(ctx_, result.first, result.second)
|
||||
{
|
||||
}
|
||||
PreclaimContext& operator=(PreclaimContext const&) = delete;
|
||||
};
|
||||
|
||||
class Transactor
|
||||
|
||||
@@ -20,19 +20,7 @@
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/HashRouter.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/app/tx/impl/applyImpl.h>
|
||||
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/app/tx/impl/CancelTicket.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/CreateOffer.h>
|
||||
#include <ripple/app/tx/impl/CreateTicket.h>
|
||||
#include <ripple/app/tx/impl/Payment.h>
|
||||
#include <ripple/app/tx/impl/SetAccount.h>
|
||||
#include <ripple/app/tx/impl/SetRegularKey.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/app/tx/impl/SusPay.h>
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -43,129 +31,6 @@ namespace ripple {
|
||||
#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed
|
||||
#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed
|
||||
|
||||
static
|
||||
TER
|
||||
invoke_preflight (PreflightContext const& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return SetAccount ::preflight(ctx);
|
||||
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
|
||||
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
|
||||
case ttPAYMENT: return Payment ::preflight(ctx);
|
||||
case ttSUSPAY_CREATE: return SusPayCreate ::preflight(ctx);
|
||||
case ttSUSPAY_FINISH: return SusPayFinish ::preflight(ctx);
|
||||
case ttSUSPAY_CANCEL: return SusPayCancel ::preflight(ctx);
|
||||
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
|
||||
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
|
||||
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
|
||||
case ttTICKET_CREATE: return CreateTicket ::preflight(ctx);
|
||||
case ttTRUST_SET: return SetTrust ::preflight(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change ::preflight(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
invoke_preclaim<T> uses name hiding to accomplish
|
||||
compile-time polymorphism of (presumably) static
|
||||
class functions for Transactor and derived classes.
|
||||
*/
|
||||
template<class T>
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// If the transactor requires a valid account and the transaction doesn't
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const baseFee = T::calculateBaseFee(ctx);
|
||||
TER result;
|
||||
if (id != zero)
|
||||
{
|
||||
result = T::checkSeq(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::checkFee(ctx, baseFee);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::checkSign(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::preclaim(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return{ result, 0 };
|
||||
}
|
||||
else
|
||||
{
|
||||
result = tesSUCCESS;
|
||||
}
|
||||
|
||||
return { tesSUCCESS, baseFee };
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim (PreclaimContext const& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return invoke_preclaim<SetAccount>(ctx);
|
||||
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
|
||||
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
|
||||
case ttPAYMENT: return invoke_preclaim<Payment>(ctx);
|
||||
case ttSUSPAY_CREATE: return invoke_preclaim<SusPayCreate>(ctx);
|
||||
case ttSUSPAY_FINISH: return invoke_preclaim<SusPayFinish>(ctx);
|
||||
case ttSUSPAY_CANCEL: return invoke_preclaim<SusPayCancel>(ctx);
|
||||
case ttREGULAR_KEY_SET: return invoke_preclaim<SetRegularKey>(ctx);
|
||||
case ttSIGNER_LIST_SET: return invoke_preclaim<SetSignerList>(ctx);
|
||||
case ttTICKET_CANCEL: return invoke_preclaim<CancelTicket>(ctx);
|
||||
case ttTICKET_CREATE: return invoke_preclaim<CreateTicket>(ctx);
|
||||
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return invoke_preclaim<Change>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, bool>
|
||||
invoke_apply (ApplyContext& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
|
||||
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
|
||||
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
|
||||
case ttPAYMENT: { Payment p(ctx); return p(); }
|
||||
case ttSUSPAY_CREATE: { SusPayCreate p(ctx); return p(); }
|
||||
case ttSUSPAY_FINISH: { SusPayFinish p(ctx); return p(); }
|
||||
case ttSUSPAY_CANCEL: { SusPayCancel p(ctx); return p(); }
|
||||
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
|
||||
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
|
||||
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
|
||||
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
|
||||
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: { Change p(ctx); return p(); }
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, false };
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::pair<Validity, std::string>
|
||||
@@ -246,105 +111,6 @@ forceValidity(HashRouter& router, uint256 const& txid,
|
||||
router.setFlags(txid, flags);
|
||||
}
|
||||
|
||||
PreflightResult
|
||||
preflight (Application& app, Rules const& rules,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
beast::Journal j)
|
||||
{
|
||||
PreflightContext const pfctx(app, tx,
|
||||
rules, flags, j);
|
||||
try
|
||||
{
|
||||
return{ pfctx, invoke_preflight(pfctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
}
|
||||
|
||||
PreclaimResult
|
||||
preclaim (PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view)
|
||||
{
|
||||
boost::optional<PreclaimContext const> ctx;
|
||||
if (preflightResult.ctx.rules != view.rules())
|
||||
{
|
||||
auto secondFlight = preflight(app, view.rules(),
|
||||
preflightResult.ctx.tx, preflightResult.ctx.flags,
|
||||
preflightResult.ctx.j);
|
||||
ctx.emplace(app, view, secondFlight.ter, secondFlight.ctx.tx,
|
||||
secondFlight.ctx.flags, secondFlight.ctx.j);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.emplace(
|
||||
app, view, preflightResult.ter, preflightResult.ctx.tx,
|
||||
preflightResult.ctx.flags, preflightResult.ctx.j);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ctx->preflightResult != tesSUCCESS)
|
||||
return { *ctx, ctx->preflightResult, 0 };
|
||||
return{ *ctx, invoke_preclaim(*ctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<TER, bool>
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view)
|
||||
{
|
||||
if (preclaimResult.ctx.view.seq() != view.seq())
|
||||
{
|
||||
// Logic error from the caller. Don't have enough
|
||||
// info to recover.
|
||||
return{ tefEXCEPTION, false };
|
||||
}
|
||||
try
|
||||
{
|
||||
if (preclaimResult.ter != tesSUCCESS
|
||||
&& !isTecClaim(preclaimResult.ter))
|
||||
return{ preclaimResult.ter, false };
|
||||
ApplyContext ctx(app, view,
|
||||
preclaimResult.ctx.tx, preclaimResult.ter,
|
||||
preclaimResult.baseFee, preclaimResult.ctx.flags,
|
||||
preclaimResult.ctx.j);
|
||||
return invoke_apply(ctx);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(preclaimResult.ctx.j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(preclaimResult.ctx.j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<TER, bool>
|
||||
apply (Application& app, OpenView& view,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2015 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_APPLYIMPL_H_INCLUDED
|
||||
#define RIPPLE_TX_APPLYIMPL_H_INCLUDED
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
|
||||
struct PreflightResult;
|
||||
struct PreclaimResult;
|
||||
|
||||
/** Gate a transaction based on static information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that do not require a ledger.
|
||||
|
||||
@return A PreflightResult object constaining, among
|
||||
other things, the TER code.
|
||||
*/
|
||||
PreflightResult
|
||||
preflight(Application& app, Rules const& rules,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
beast::Journal j);
|
||||
|
||||
/** Gate a transaction based on static ledger information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that DO require a ledger.
|
||||
|
||||
If preclaim succeeds, then the transaction is very
|
||||
likely to claim a fee. This will determine if the
|
||||
transaction is safe to relay without being applied
|
||||
to the open ledger.
|
||||
|
||||
"Succeeds" in this case is defined as returning a
|
||||
`tes` or `tec`, since both lead to claiming a fee.
|
||||
|
||||
@return A PreclaimResult object containing, among
|
||||
other things the TER code and the base fee value for
|
||||
this transaction.
|
||||
*/
|
||||
PreclaimResult
|
||||
preclaim(PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view);
|
||||
|
||||
/** Apply a prechecked transaction to an OpenView.
|
||||
|
||||
See also: apply()
|
||||
|
||||
Precondition: The transaction has been checked
|
||||
and validated using the above functions.
|
||||
|
||||
@return A pair with the TER and a bool indicating
|
||||
whether or not the transaction was applied.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
294
src/ripple/app/tx/impl/applySteps.cpp
Normal file
294
src/ripple/app/tx/impl/applySteps.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/tx/applySteps.h>
|
||||
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/app/tx/impl/CancelTicket.h>
|
||||
#include <ripple/app/tx/impl/Change.h>
|
||||
#include <ripple/app/tx/impl/CreateOffer.h>
|
||||
#include <ripple/app/tx/impl/CreateTicket.h>
|
||||
#include <ripple/app/tx/impl/Payment.h>
|
||||
#include <ripple/app/tx/impl/SetAccount.h>
|
||||
#include <ripple/app/tx/impl/SetRegularKey.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/app/tx/impl/SusPay.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static
|
||||
TER
|
||||
invoke_preflight (PreflightContext const& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return SetAccount ::preflight(ctx);
|
||||
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
|
||||
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
|
||||
case ttPAYMENT: return Payment ::preflight(ctx);
|
||||
case ttSUSPAY_CREATE: return SusPayCreate ::preflight(ctx);
|
||||
case ttSUSPAY_FINISH: return SusPayFinish ::preflight(ctx);
|
||||
case ttSUSPAY_CANCEL: return SusPayCancel ::preflight(ctx);
|
||||
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
|
||||
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
|
||||
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
|
||||
case ttTICKET_CREATE: return CreateTicket ::preflight(ctx);
|
||||
case ttTRUST_SET: return SetTrust ::preflight(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change ::preflight(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/* invoke_preclaim<T> uses name hiding to accomplish
|
||||
compile-time polymorphism of (presumably) static
|
||||
class functions for Transactor and derived classes.
|
||||
*/
|
||||
template<class T>
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// If the transactor requires a valid account and the transaction doesn't
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const baseFee = T::calculateBaseFee(ctx);
|
||||
TER result;
|
||||
if (id != zero)
|
||||
{
|
||||
result = T::checkSeq(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, baseFee };
|
||||
|
||||
result = T::checkFee(ctx, baseFee);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, baseFee };
|
||||
|
||||
result = T::checkSign(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, baseFee };
|
||||
|
||||
result = T::preclaim(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return{ result, baseFee };
|
||||
}
|
||||
else
|
||||
{
|
||||
result = tesSUCCESS;
|
||||
}
|
||||
|
||||
return { tesSUCCESS, baseFee };
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim (PreclaimContext const& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return invoke_preclaim<SetAccount>(ctx);
|
||||
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
|
||||
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
|
||||
case ttPAYMENT: return invoke_preclaim<Payment>(ctx);
|
||||
case ttSUSPAY_CREATE: return invoke_preclaim<SusPayCreate>(ctx);
|
||||
case ttSUSPAY_FINISH: return invoke_preclaim<SusPayFinish>(ctx);
|
||||
case ttSUSPAY_CANCEL: return invoke_preclaim<SusPayCancel>(ctx);
|
||||
case ttREGULAR_KEY_SET: return invoke_preclaim<SetRegularKey>(ctx);
|
||||
case ttSIGNER_LIST_SET: return invoke_preclaim<SetSignerList>(ctx);
|
||||
case ttTICKET_CANCEL: return invoke_preclaim<CancelTicket>(ctx);
|
||||
case ttTICKET_CREATE: return invoke_preclaim<CreateTicket>(ctx);
|
||||
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return invoke_preclaim<Change>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::uint64_t
|
||||
invoke_calculateBaseFee(PreclaimContext const& ctx)
|
||||
{
|
||||
switch (ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return SetAccount::calculateBaseFee(ctx);
|
||||
case ttOFFER_CANCEL: return CancelOffer::calculateBaseFee(ctx);
|
||||
case ttOFFER_CREATE: return CreateOffer::calculateBaseFee(ctx);
|
||||
case ttPAYMENT: return Payment::calculateBaseFee(ctx);
|
||||
case ttSUSPAY_CREATE: return SusPayCreate::calculateBaseFee(ctx);
|
||||
case ttSUSPAY_FINISH: return SusPayFinish::calculateBaseFee(ctx);
|
||||
case ttSUSPAY_CANCEL: return SusPayCancel::calculateBaseFee(ctx);
|
||||
case ttREGULAR_KEY_SET: return SetRegularKey::calculateBaseFee(ctx);
|
||||
case ttSIGNER_LIST_SET: return SetSignerList::calculateBaseFee(ctx);
|
||||
case ttTICKET_CANCEL: return CancelTicket::calculateBaseFee(ctx);
|
||||
case ttTICKET_CREATE: return CreateTicket::calculateBaseFee(ctx);
|
||||
case ttTRUST_SET: return SetTrust::calculateBaseFee(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change::calculateBaseFee(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, bool>
|
||||
invoke_apply (ApplyContext& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
|
||||
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
|
||||
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
|
||||
case ttPAYMENT: { Payment p(ctx); return p(); }
|
||||
case ttSUSPAY_CREATE: { SusPayCreate p(ctx); return p(); }
|
||||
case ttSUSPAY_FINISH: { SusPayFinish p(ctx); return p(); }
|
||||
case ttSUSPAY_CANCEL: { SusPayCancel p(ctx); return p(); }
|
||||
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
|
||||
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
|
||||
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
|
||||
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
|
||||
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: { Change p(ctx); return p(); }
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, false };
|
||||
}
|
||||
}
|
||||
|
||||
PreflightResult
|
||||
preflight(Application& app, Rules const& rules,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
beast::Journal j)
|
||||
{
|
||||
PreflightContext const pfctx(app, tx,
|
||||
rules, flags, j);
|
||||
try
|
||||
{
|
||||
return{ pfctx, invoke_preflight(pfctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
}
|
||||
|
||||
PreclaimResult
|
||||
preclaim (PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view)
|
||||
{
|
||||
boost::optional<PreclaimContext const> ctx;
|
||||
if (preflightResult.rules != view.rules())
|
||||
{
|
||||
auto secondFlight = preflight(app, view.rules(),
|
||||
preflightResult.tx, preflightResult.flags,
|
||||
preflightResult.j);
|
||||
ctx.emplace(app, view, secondFlight.ter, secondFlight.tx,
|
||||
secondFlight.flags, secondFlight.j);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.emplace(
|
||||
app, view, preflightResult.ter, preflightResult.tx,
|
||||
preflightResult.flags, preflightResult.j);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ctx->preflightResult != tesSUCCESS)
|
||||
return { *ctx, ctx->preflightResult, 0 };
|
||||
return{ *ctx, invoke_preclaim(*ctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
calculateBaseFee(Application& app, ReadView const& view,
|
||||
STTx const& tx, beast::Journal j)
|
||||
{
|
||||
PreclaimContext const ctx(
|
||||
app, view, tesSUCCESS, tx,
|
||||
tapNONE, j);
|
||||
|
||||
return invoke_calculateBaseFee(ctx);
|
||||
}
|
||||
|
||||
std::pair<TER, bool>
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view)
|
||||
{
|
||||
if (preclaimResult.view.seq() != view.seq())
|
||||
{
|
||||
// Logic error from the caller. Don't have enough
|
||||
// info to recover.
|
||||
return{ tefEXCEPTION, false };
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!preclaimResult.likelyToClaimFee)
|
||||
return{ preclaimResult.ter, false };
|
||||
ApplyContext ctx(app, view,
|
||||
preclaimResult.tx, preclaimResult.ter,
|
||||
preclaimResult.baseFee, preclaimResult.flags,
|
||||
preclaimResult.j);
|
||||
return invoke_apply(ctx);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(preclaimResult.j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(preclaimResult.j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
}
|
||||
|
||||
} // ripple
|
||||
@@ -38,6 +38,10 @@ enum ApplyFlags
|
||||
//
|
||||
tapENABLE_TESTING = 0x02,
|
||||
|
||||
// We expect the transaction to have a later
|
||||
// sequence number than the account in the ledger
|
||||
tapPOST_SEQ = 0x04,
|
||||
|
||||
// This is not the transaction's last pass
|
||||
// Transaction can be retried, soft failures allowed
|
||||
tapRETRY = 0x20,
|
||||
|
||||
@@ -109,8 +109,8 @@ Rules::changed (DigestAwareReadView const& ledger) const
|
||||
bool
|
||||
Rules::operator== (Rules const& other) const
|
||||
{
|
||||
#if 0
|
||||
if (! impl_ && ! other.impl_)
|
||||
#if 1
|
||||
if (impl_.get() == other.impl_.get())
|
||||
return true;
|
||||
if (! impl_ || ! other.impl_)
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ feature (const char* name);
|
||||
extern uint256 const featureMultiSign;
|
||||
extern uint256 const featureSusPay;
|
||||
extern uint256 const featureTrustSetAuth;
|
||||
extern uint256 const featureFeeEscalation;
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ JSS ( count ); // in: AccountTx*
|
||||
JSS ( currency ); // in: paths/PathRequest, STAmount
|
||||
// out: paths/Node, STPathSet, STAmount
|
||||
JSS ( current ); // out: OwnerInfo
|
||||
JSS ( current_ledger_size ); // out: TxQ
|
||||
JSS ( current_queue_size ); // out: TxQ
|
||||
JSS ( data ); // out: LedgerData
|
||||
JSS ( date ); // out: tx/Transaction, NetworkOPs
|
||||
JSS ( dbKBLedger ); // out: getCounts
|
||||
@@ -136,6 +138,7 @@ JSS ( dir_entry ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_index ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_root ); // out: DirectoryEntryIterator
|
||||
JSS ( directory ); // in: LedgerEntry
|
||||
JSS ( drops ); // out: TxQ
|
||||
JSS ( enabled ); // out: AmendmentTable
|
||||
JSS ( engine_result ); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS ( engine_result_code ); // out: NetworkOPs, TransactionSign, Submit
|
||||
@@ -145,6 +148,7 @@ JSS ( error_code ); // out: error
|
||||
JSS ( error_exception ); // out: Submit
|
||||
JSS ( error_message ); // out: error
|
||||
JSS ( expand ); // in: handler/Ledger
|
||||
JSS ( expected_ledger_size ); // out: TxQ
|
||||
JSS ( fail_hard ); // in: Sign, Submit
|
||||
JSS ( failed ); // out: InboundLedger
|
||||
JSS ( feature ); // in: Feature
|
||||
@@ -232,6 +236,9 @@ JSS ( master_key ); // out: WalletPropose
|
||||
JSS ( master_seed ); // out: WalletPropose
|
||||
JSS ( master_seed_hex ); // out: WalletPropose
|
||||
JSS ( max_ledger ); // in/out: LedgerCleaner
|
||||
JSS ( max_queue_size ); // out: TxQ
|
||||
JSS ( median_fee ); // out: TxQ
|
||||
JSS ( median_level ); // out: TxQ
|
||||
JSS ( message ); // error.
|
||||
JSS ( meta ); // out: NetworkOPs, AccountTx*, Tx
|
||||
JSS ( metaData );
|
||||
@@ -239,6 +246,8 @@ JSS ( metadata ); // out: TransactionEntry
|
||||
JSS ( method ); // RPC
|
||||
JSS ( min_count ); // in: GetCounts
|
||||
JSS ( min_ledger ); // in: LedgerCleaner
|
||||
JSS ( minimum_fee ); // out: TxQ
|
||||
JSS ( minimum_level ); // out: TxQ
|
||||
JSS ( missingCommand ); // error
|
||||
JSS ( name ); // out: AmendmentTableImpl, PeerImp
|
||||
JSS ( needed_state_hashes ); // out: InboundLedger
|
||||
@@ -261,6 +270,8 @@ JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe
|
||||
JSS ( offline ); // in: TransactionSign
|
||||
JSS ( offset ); // in/out: AccountTxOld
|
||||
JSS ( open ); // out: handlers/Ledger
|
||||
JSS ( open_ledger_fee ); // out: TxQ
|
||||
JSS ( open_ledger_level ); // out: TxQ
|
||||
JSS ( owner ); // in: LedgerEntry, out: NetworkOPs
|
||||
JSS ( owner_funds ); // out: NetworkOPs, AcceptedLedgerTx
|
||||
JSS ( params ); // RPC
|
||||
@@ -293,6 +304,7 @@ JSS ( quality_out ); // out: AccountLines
|
||||
JSS ( random ); // out: Random
|
||||
JSS ( raw_meta ); // out: AcceptedLedgerTx
|
||||
JSS ( receive_currencies ); // out: AccountCurrencies
|
||||
JSS ( reference_level ); // out: TxQ
|
||||
JSS ( regular_seed ); // in/out: LedgerEntry
|
||||
JSS ( remote ); // out: Logic.h
|
||||
JSS ( request ); // RPC
|
||||
|
||||
@@ -407,6 +407,14 @@ inline bool isXRP(STAmount const& amount)
|
||||
std::uint64_t
|
||||
mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div);
|
||||
|
||||
/**
|
||||
A utility function to compute (value)*(mul)/(div) while avoiding
|
||||
overflow but keeping precision. Will return the max uint64_t
|
||||
value if mulDiv would overflow anyway.
|
||||
*/
|
||||
std::uint64_t
|
||||
mulDivNoThrow(std::uint64_t value, std::uint64_t mul, std::uint64_t div);
|
||||
|
||||
template <class T1, class T2>
|
||||
void lowestTerms(T1& a, T2& b)
|
||||
{
|
||||
|
||||
@@ -144,6 +144,7 @@ enum TER
|
||||
// burden network.
|
||||
terLAST, // Process after all other transactions
|
||||
terNO_RIPPLE, // Rippling not allowed
|
||||
terQUEUED, // Transaction is being held in TxQ until fee drops
|
||||
|
||||
// 0: S Success (success)
|
||||
// Causes:
|
||||
|
||||
@@ -48,5 +48,6 @@ feature (const char* name)
|
||||
uint256 const featureMultiSign = feature("MultiSign");
|
||||
uint256 const featureSusPay = feature("SusPay");
|
||||
uint256 const featureTrustSetAuth = feature("TrustSetAuth");
|
||||
uint256 const featureFeeEscalation = feature("FeeEscalation");
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -1277,4 +1277,18 @@ mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
|
||||
return value * mul / div;
|
||||
}
|
||||
|
||||
// compute (value)*(mul)/(div) - avoid overflow but keep precision
|
||||
std::uint64_t
|
||||
mulDivNoThrow(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
|
||||
{
|
||||
try
|
||||
{
|
||||
return mulDiv(value, mul, div);
|
||||
}
|
||||
catch (std::overflow_error)
|
||||
{
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -138,6 +138,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
|
||||
{ terNO_LINE, "terNO_LINE", "No such line." },
|
||||
{ terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." },
|
||||
{ terOWNERS, "terOWNERS", "Non-zero owner count." },
|
||||
{ terQUEUED, "terQUEUED", "Held until fee drops." },
|
||||
|
||||
{ tesSUCCESS, "tesSUCCESS", "The transaction was applied. Only final in a validated ledger." },
|
||||
};
|
||||
|
||||
41
src/ripple/rpc/handlers/Fee1.cpp
Normal file
41
src/ripple/rpc/handlers/Fee1.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2015 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/misc/TxQ.h>
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
Json::Value doFee(RPC::Context& context)
|
||||
{
|
||||
// Bail if fee escalation is not enabled.
|
||||
if (!context.app.getLedgerMaster().getValidatedRules().
|
||||
enabled(featureFeeEscalation, context.app.config().features))
|
||||
{
|
||||
RPC::inject_error(rpcNOT_ENABLED, context.params);
|
||||
return context.params;
|
||||
}
|
||||
|
||||
return context.app.getTxQ().doRPC(context.app);
|
||||
}
|
||||
} // ripple
|
||||
@@ -38,6 +38,7 @@ Json::Value doCanDelete (RPC::Context&);
|
||||
Json::Value doConnect (RPC::Context&);
|
||||
Json::Value doConsensusInfo (RPC::Context&);
|
||||
Json::Value doFeature (RPC::Context&);
|
||||
Json::Value doFee (RPC::Context&);
|
||||
Json::Value doFetchInfo (RPC::Context&);
|
||||
Json::Value doGatewayBalances (RPC::Context&);
|
||||
Json::Value doGetCounts (RPC::Context&);
|
||||
|
||||
@@ -115,6 +115,7 @@ Handler handlerArray[] {
|
||||
{ "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION },
|
||||
{ "internal", byRef (&doInternal), Role::ADMIN, NO_CONDITION },
|
||||
{ "feature", byRef (&doFeature), Role::ADMIN, NO_CONDITION },
|
||||
{ "fee", byRef (&doFee), Role::ADMIN, NO_CONDITION },
|
||||
{ "fetch_info", byRef (&doFetchInfo), Role::ADMIN, NO_CONDITION },
|
||||
{ "ledger_accept", byRef (&doLedgerAccept), Role::ADMIN, NEEDS_CURRENT_LEDGER },
|
||||
{ "ledger_cleaner", byRef (&doLedgerCleaner), Role::ADMIN, NEEDS_NETWORK_CONNECTION },
|
||||
|
||||
@@ -153,6 +153,8 @@ public:
|
||||
Env (Env const&) = delete;
|
||||
Env& operator= (Env const&) = delete;
|
||||
|
||||
Env (beast::unit_test::suite& test_,
|
||||
std::unique_ptr<Config const> config);
|
||||
Env (beast::unit_test::suite& test_);
|
||||
|
||||
Application&
|
||||
|
||||
@@ -76,12 +76,13 @@ makeConfig()
|
||||
}
|
||||
|
||||
// VFALCO Could wrap the log in a Journal here
|
||||
Env::Env (beast::unit_test::suite& test_)
|
||||
Env::Env(beast::unit_test::suite& test_,
|
||||
std::unique_ptr<Config const> config)
|
||||
: test (test_)
|
||||
, master ("master", generateKeyPair(
|
||||
KeyType::secp256k1,
|
||||
generateSeed("masterpassphrase")))
|
||||
, bundle_ (makeConfig())
|
||||
, bundle_ (std::move(config))
|
||||
, closed_ (std::make_shared<Ledger>(
|
||||
create_genesis, app().config(), app().family()))
|
||||
, cachedSLEs_ (std::chrono::seconds(5), stopwatch_)
|
||||
@@ -91,6 +92,11 @@ Env::Env (beast::unit_test::suite& test_)
|
||||
Pathfinder::initPathTable();
|
||||
}
|
||||
|
||||
Env::Env(beast::unit_test::suite& test_)
|
||||
: Env(test_, makeConfig())
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<OpenView const>
|
||||
Env::open() const
|
||||
{
|
||||
@@ -118,13 +124,12 @@ Env::close(NetClock::time_point const& closeTime,
|
||||
for (auto iter = cur->txs.begin();
|
||||
iter != cur->txs.end(); ++iter)
|
||||
txs.push_back(iter->first);
|
||||
auto router = std::make_unique<HashRouter>(60);
|
||||
OrderedTxs retries(uint256{});
|
||||
{
|
||||
OpenView accum(&*next);
|
||||
OpenLedger::apply(app(), accum, *closed_,
|
||||
txs, retries, applyFlags(), *router,
|
||||
journal);
|
||||
txs, retries, applyFlags(), journal);
|
||||
|
||||
accum.apply(*next);
|
||||
}
|
||||
// To ensure that the close time is exact and not rounded, we don't
|
||||
@@ -135,7 +140,7 @@ Env::close(NetClock::time_point const& closeTime,
|
||||
ledgerPossibleTimeResolutions[0], false, app().config());
|
||||
OrderedTxs locals({});
|
||||
openLedger.accept(app(), next->rules(), next,
|
||||
locals, false, retries, applyFlags(), *router, "", f);
|
||||
locals, false, retries, applyFlags(), "", f);
|
||||
closed_ = next;
|
||||
cachedSLEs_.expire();
|
||||
}
|
||||
|
||||
@@ -40,4 +40,3 @@
|
||||
#include <ripple/app/ledger/impl/LedgerTiming.cpp>
|
||||
#include <ripple/app/ledger/impl/OpenLedger.cpp>
|
||||
#include <ripple/app/ledger/impl/LedgerToJson.cpp>
|
||||
|
||||
|
||||
@@ -29,3 +29,4 @@
|
||||
#include <ripple/app/misc/Validations.cpp>
|
||||
|
||||
#include <ripple/app/misc/impl/AccountTxPaging.cpp>
|
||||
#include <ripple/app/misc/impl/TxQ.cpp>
|
||||
|
||||
@@ -33,3 +33,4 @@
|
||||
#include <ripple/app/tests/SetAuth_test.cpp>
|
||||
#include <ripple/app/tests/OversizeMeta_test.cpp>
|
||||
#include <ripple/app/tests/Taker.test.cpp>
|
||||
#include <ripple/app/tests/TxQ_test.cpp>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <BeastConfig.h>
|
||||
|
||||
#include <ripple/app/tx/impl/apply.cpp>
|
||||
#include <ripple/app/tx/impl/applySteps.cpp>
|
||||
#include <ripple/app/tx/impl/BookTip.cpp>
|
||||
#include <ripple/app/tx/impl/CancelOffer.cpp>
|
||||
#include <ripple/app/tx/impl/CancelTicket.cpp>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include <ripple/rpc/handlers/Connect.cpp>
|
||||
#include <ripple/rpc/handlers/ConsensusInfo.cpp>
|
||||
#include <ripple/rpc/handlers/Feature1.cpp>
|
||||
#include <ripple/rpc/handlers/Fee1.cpp>
|
||||
#include <ripple/rpc/handlers/FetchInfo.cpp>
|
||||
#include <ripple/rpc/handlers/GatewayBalances.cpp>
|
||||
#include <ripple/rpc/handlers/GetCounts.cpp>
|
||||
|
||||
Reference in New Issue
Block a user