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:
Edward Hennis
2015-07-10 20:00:21 -04:00
parent dc1276efa3
commit 9329aafe53
39 changed files with 2454 additions and 421 deletions

View File

@@ -1510,6 +1510,10 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h"> <ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h">
</ClInclude> </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"> <ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1526,6 +1530,8 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h"> <ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\misc\TxQ.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp"> <ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|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)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile> </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 Include="..\..\src\ripple\app\tx\apply.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\applySteps.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp"> <ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -1722,8 +1734,10 @@
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h"> <ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h"> <ClCompile Include="..\..\src\ripple\app\tx\impl\applySteps.cpp">
</ClInclude> <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"> <ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|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)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile> </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"> <ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2256,6 +2256,9 @@
<ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h"> <ClInclude Include="..\..\src\ripple\app\misc\impl\AccountTxPaging.h">
<Filter>ripple\app\misc\impl</Filter> <Filter>ripple\app\misc\impl</Filter>
</ClInclude> </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"> <ClCompile Include="..\..\src\ripple\app\misc\NetworkOPs.cpp">
<Filter>ripple\app\misc</Filter> <Filter>ripple\app\misc</Filter>
</ClCompile> </ClCompile>
@@ -2274,6 +2277,9 @@
<ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h"> <ClInclude Include="..\..\src\ripple\app\misc\SHAMapStoreImp.h">
<Filter>ripple\app\misc</Filter> <Filter>ripple\app\misc</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\misc\TxQ.h">
<Filter>ripple\app\misc</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp"> <ClCompile Include="..\..\src\ripple\app\misc\UniqueNodeList.cpp">
<Filter>ripple\app\misc</Filter> <Filter>ripple\app\misc</Filter>
</ClCompile> </ClCompile>
@@ -2439,9 +2445,15 @@
<ClCompile Include="..\..\src\ripple\app\tests\Taker.test.cpp"> <ClCompile Include="..\..\src\ripple\app\tests\Taker.test.cpp">
<Filter>ripple\app\tests</Filter> <Filter>ripple\app\tests</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\TxQ_test.cpp">
<Filter>ripple\app\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\apply.h"> <ClInclude Include="..\..\src\ripple\app\tx\apply.h">
<Filter>ripple\app\tx</Filter> <Filter>ripple\app\tx</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\applySteps.h">
<Filter>ripple\app\tx</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp"> <ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp">
<Filter>ripple\app\tx\impl</Filter> <Filter>ripple\app\tx\impl</Filter>
</ClCompile> </ClCompile>
@@ -2451,9 +2463,9 @@
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h"> <ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
<Filter>ripple\app\tx\impl</Filter> <Filter>ripple\app\tx\impl</Filter>
</ClInclude> </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> <Filter>ripple\app\tx\impl</Filter>
</ClInclude> </ClCompile>
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp"> <ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
<Filter>ripple\app\tx\impl</Filter> <Filter>ripple\app\tx\impl</Filter>
</ClCompile> </ClCompile>
@@ -3861,6 +3873,9 @@
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature1.cpp"> <ClCompile Include="..\..\src\ripple\rpc\handlers\Feature1.cpp">
<Filter>ripple\rpc\handlers</Filter> <Filter>ripple\rpc\handlers</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\Fee1.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp"> <ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp">
<Filter>ripple\rpc\handlers</Filter> <Filter>ripple\rpc\handlers</Filter>
</ClCompile> </ClCompile>

View File

@@ -373,6 +373,30 @@
# connections to or from this server. # 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 # 3. Ripple Protocol
@@ -667,7 +691,7 @@
# #
# [debug_logfile] # [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. # Unless absolute, the path is relative the directory containing this file.
# #
# Example: debug.log # Example: debug.log

View File

@@ -24,7 +24,6 @@
#include <ripple/ledger/CachedSLEs.h> #include <ripple/ledger/CachedSLEs.h>
#include <ripple/ledger/OpenView.h> #include <ripple/ledger/OpenView.h>
#include <ripple/app/misc/CanonicalTXSet.h> #include <ripple/app/misc/CanonicalTXSet.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/basics/UnorderedContainers.h> #include <ripple/basics/UnorderedContainers.h>
#include <ripple/core/Config.h> #include <ripple/core/Config.h>
@@ -165,9 +164,8 @@ public:
std::shared_ptr<Ledger const> const& ledger, std::shared_ptr<Ledger const> const& ledger,
OrderedTxs const& locals, bool retriesFirst, OrderedTxs const& locals, bool retriesFirst,
OrderedTxs& retries, ApplyFlags flags, OrderedTxs& retries, ApplyFlags flags,
HashRouter& router, std::string const& suffix = "",
std::string const& suffix = "", modify_type const& f = {});
modify_type const& f = {});
/** Algorithm for applying transactions. /** Algorithm for applying transactions.
@@ -180,7 +178,7 @@ public:
apply (Application& app, OpenView& view, apply (Application& app, OpenView& view,
ReadView const& check, FwdRange const& txs, ReadView const& check, FwdRange const& txs,
OrderedTxs& retries, ApplyFlags flags, OrderedTxs& retries, ApplyFlags flags,
HashRouter& router, beast::Journal j); beast::Journal j);
private: private:
enum Result enum Result
@@ -199,7 +197,7 @@ private:
apply_one (Application& app, OpenView& view, apply_one (Application& app, OpenView& view,
std::shared_ptr< STTx const> const& tx, std::shared_ptr< STTx const> const& tx,
bool retry, ApplyFlags flags, bool retry, ApplyFlags flags,
HashRouter& router, beast::Journal j); beast::Journal j);
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -209,7 +207,7 @@ void
OpenLedger::apply (Application& app, OpenView& view, OpenLedger::apply (Application& app, OpenView& view,
ReadView const& check, FwdRange const& txs, ReadView const& check, FwdRange const& txs,
OrderedTxs& retries, ApplyFlags flags, OrderedTxs& retries, ApplyFlags flags,
HashRouter& router, beast::Journal j) beast::Journal j)
{ {
for (auto iter = txs.begin(); for (auto iter = txs.begin();
iter != txs.end(); ++iter) iter != txs.end(); ++iter)
@@ -222,7 +220,7 @@ OpenLedger::apply (Application& app, OpenView& view,
if (check.txExists(tx->getTransactionID())) if (check.txExists(tx->getTransactionID()))
continue; continue;
auto const result = apply_one(app, view, auto const result = apply_one(app, view,
tx, true, flags, router, j); tx, true, flags, j);
if (result == Result::retry) if (result == Result::retry)
retries.insert(tx); retries.insert(tx);
} }
@@ -243,7 +241,7 @@ OpenLedger::apply (Application& app, OpenView& view,
{ {
switch (apply_one(app, view, switch (apply_one(app, view,
iter->second, retry, flags, iter->second, retry, flags,
router, j)) j))
{ {
case Result::success: case Result::success:
++changes; ++changes;

View File

@@ -30,6 +30,7 @@
#include <ripple/app/misc/CanonicalTXSet.h> #include <ripple/app/misc/CanonicalTXSet.h>
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/Validations.h> #include <ripple/app/misc/Validations.h>
#include <ripple/app/tx/TransactionAcquire.h> #include <ripple/app/tx/TransactionAcquire.h>
#include <ripple/app/tx/apply.h> #include <ripple/app/tx/apply.h>
@@ -1055,6 +1056,10 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
applyTransactions (app_, set.get(), accum, applyTransactions (app_, set.get(), accum,
newLCL, retriableTxs, tapNONE); newLCL, retriableTxs, tapNONE);
} }
// Update fee computations.
app_.getTxQ().processValidatedLedger(app_, accum,
mCurrentMSeconds > 5000);
accum.apply(*newLCL); accum.apply(*newLCL);
} }
@@ -1064,13 +1069,15 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
newLCL->updateSkipList (); newLCL->updateSkipList ();
int asf = newLCL->stateMap().flushDirty ( {
hotACCOUNT_NODE, newLCL->info().seq); int asf = newLCL->stateMap().flushDirty (
int tmf = newLCL->txMap().flushDirty ( hotACCOUNT_NODE, newLCL->info().seq);
hotTRANSACTION_NODE, newLCL->info().seq); int tmf = newLCL->txMap().flushDirty (
JLOG (j_.debug) << "Flushed " << hotTRANSACTION_NODE, newLCL->info().seq);
asf << " accounts and " << JLOG (j_.debug) << "Flushed " <<
tmf << " transaction nodes"; asf << " accounts and " <<
tmf << " transaction nodes";
}
// Accept ledger // Accept ledger
newLCL->setAccepted (closeTime, mCloseResolution, closeTimeCorrect, app_.config()); newLCL->setAccepted (closeTime, mCloseResolution, closeTimeCorrect, app_.config());
@@ -1140,45 +1147,45 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
// See if we can accept a ledger as fully-validated // See if we can accept a ledger as fully-validated
ledgerMaster_.consensusBuilt (newLCL); ledgerMaster_.consensusBuilt (newLCL);
// Apply disputed transactions that didn't get in
//
// The first crack of transactions to get into the new
// open ledger goes to transactions proposed by a validator
// we trust but not included in the consensus set.
//
// These are done first because they are the most likely
// to receive agreement during consensus. They are also
// ordered logically "sooner" than transactions not mentioned
// in the previous consensus round.
//
bool anyDisputes = false;
for (auto& it : mDisputes)
{ {
if (!it.second->getOurVote ()) // Apply disputed transactions that didn't get in
//
// The first crack of transactions to get into the new
// open ledger goes to transactions proposed by a validator
// we trust but not included in the consensus set.
//
// These are done first because they are the most likely
// to receive agreement during consensus. They are also
// ordered logically "sooner" than transactions not mentioned
// in the previous consensus round.
//
bool anyDisputes = false;
for (auto& it : mDisputes)
{ {
// we voted NO if (!it.second->getOurVote ())
try
{ {
JLOG (j_.debug) // we voted NO
<< "Test applying disputed transaction that did" try
<< " not get in"; {
SerialIter sit (it.second->peekTransaction().slice()); JLOG (j_.debug)
<< "Test applying disputed transaction that did"
<< " not get in";
SerialIter sit (it.second->peekTransaction().slice());
auto txn = std::make_shared<STTx const>(sit); auto txn = std::make_shared<STTx const>(sit);
retriableTxs.insert (txn); retriableTxs.insert (txn);
anyDisputes = true; anyDisputes = true;
} }
catch (...) catch (...)
{ {
JLOG (j_.debug) JLOG (j_.debug)
<< "Failed to apply transaction we voted NO on"; << "Failed to apply transaction we voted NO on";
}
} }
} }
}
{
// Build new open ledger // Build new open ledger
auto lock = beast::make_lock( auto lock = beast::make_lock(
app_.getMasterMutex(), std::defer_lock); app_.getMasterMutex(), std::defer_lock);
@@ -1198,7 +1205,12 @@ void LedgerConsensusImp::accept (std::shared_ptr<SHAMap> set)
rules.emplace(); rules.emplace();
app_.openLedger().accept(app_, *rules, app_.openLedger().accept(app_, *rules,
newLCL, localTx, anyDisputes, retriableTxs, tapNONE, 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 (); mNewLedgerHash = newLCL->getHash ();

View File

@@ -32,6 +32,7 @@
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/CanonicalTXSet.h> #include <ripple/app/misc/CanonicalTXSet.h>
#include <ripple/app/misc/SHAMapStore.h> #include <ripple/app/misc/SHAMapStore.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/Validations.h> #include <ripple/app/misc/Validations.h>
#include <ripple/app/paths/PathRequests.h> #include <ripple/app/paths/PathRequests.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
@@ -378,8 +379,8 @@ public:
for (auto const& it : mHeldTransactions) for (auto const& it : mHeldTransactions)
{ {
ApplyFlags flags = tapNONE; ApplyFlags flags = tapNONE;
auto const result = apply(app_, view, auto const result = app_.getTxQ().apply(
*it.second, flags, j); app_, view, it.second, flags, j);
if (result.second) if (result.second)
any = true; any = true;
} }

View File

@@ -76,7 +76,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
std::shared_ptr<Ledger const> const& ledger, std::shared_ptr<Ledger const> const& ledger,
OrderedTxs const& locals, bool retriesFirst, OrderedTxs const& locals, bool retriesFirst,
OrderedTxs& retries, ApplyFlags flags, OrderedTxs& retries, ApplyFlags flags,
HashRouter& router, std::string const& suffix, std::string const& suffix,
modify_type const& f) modify_type const& f)
{ {
JLOG(j_.trace) << JLOG(j_.trace) <<
@@ -89,7 +89,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
std::vector<std::shared_ptr< std::vector<std::shared_ptr<
STTx const>>; STTx const>>;
apply (app, *next, *ledger, empty{}, apply (app, *next, *ledger, empty{},
retries, flags, router, j_); retries, flags, j_);
} }
// Block calls to modify, otherwise // Block calls to modify, otherwise
// new tx going into the open ledger // new tx going into the open ledger
@@ -107,7 +107,7 @@ OpenLedger::accept(Application& app, Rules const& rules,
{ {
return p.first; return p.first;
}), }),
retries, flags, router, j_); retries, flags, j_);
// Apply local tx // Apply local tx
for (auto const& item : locals) for (auto const& item : locals)
ripple::apply(app, *next, ripple::apply(app, *next,
@@ -137,7 +137,7 @@ auto
OpenLedger::apply_one (Application& app, OpenView& view, OpenLedger::apply_one (Application& app, OpenView& view,
std::shared_ptr<STTx const> const& tx, std::shared_ptr<STTx const> const& tx,
bool retry, ApplyFlags flags, bool retry, ApplyFlags flags,
HashRouter& router,beast::Journal j) -> Result beast::Journal j) -> Result
{ {
if (retry) if (retry)
flags = flags | tapRETRY; flags = flags | tapRETRY;

View File

@@ -38,6 +38,7 @@
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/SHAMapStore.h> #include <ripple/app/misc/SHAMapStore.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/Validations.h> #include <ripple/app/misc/Validations.h>
#include <ripple/app/paths/Pathfinder.h> #include <ripple/app/paths/Pathfinder.h>
#include <ripple/app/paths/PathRequests.h> #include <ripple/app/paths/PathRequests.h>
@@ -326,6 +327,7 @@ public:
std::unique_ptr <HashRouter> mHashRouter; std::unique_ptr <HashRouter> mHashRouter;
std::unique_ptr <Validations> mValidations; std::unique_ptr <Validations> mValidations;
std::unique_ptr <LoadManager> m_loadManager; std::unique_ptr <LoadManager> m_loadManager;
std::unique_ptr <TxQ> txQ_;
beast::DeadlineTimer m_sweepTimer; beast::DeadlineTimer m_sweepTimer;
beast::DeadlineTimer m_entropyTimer; beast::DeadlineTimer m_entropyTimer;
@@ -460,6 +462,8 @@ public:
, m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager"))) , m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager")))
, txQ_(make_TxQ(setup_TxQ(*config_), logs_->journal("TxQ")))
, m_sweepTimer (this) , m_sweepTimer (this)
, m_entropyTimer (this) , m_entropyTimer (this)
@@ -683,6 +687,12 @@ public:
return *m_overlay; return *m_overlay;
} }
TxQ& getTxQ() override
{
assert(txQ_.get() != nullptr);
return *txQ_;
}
DatabaseCon& getTxnDB () override DatabaseCon& getTxnDB () override
{ {
assert (mTxnDB.get() != nullptr); assert (mTxnDB.get() != nullptr);

View File

@@ -62,6 +62,7 @@ class AccountIDCache;
class STLedgerEntry; class STLedgerEntry;
class TimeKeeper; class TimeKeeper;
class TransactionMaster; class TransactionMaster;
class TxQ;
class Validations; class Validations;
class DatabaseCon; class DatabaseCon;
@@ -114,6 +115,7 @@ public:
virtual LoadFeeTrack& getFeeTrack () = 0; virtual LoadFeeTrack& getFeeTrack () = 0;
virtual LoadManager& getLoadManager () = 0; virtual LoadManager& getLoadManager () = 0;
virtual Overlay& overlay () = 0; virtual Overlay& overlay () = 0;
virtual TxQ& getTxQ() = 0;
virtual UniqueNodeList& getUNL () = 0; virtual UniqueNodeList& getUNL () = 0;
virtual Validations& getValidations () = 0; virtual Validations& getValidations () = 0;
virtual NodeStore::Database& getNodeStore () = 0; virtual NodeStore::Database& getNodeStore () = 0;

View 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
```

View File

@@ -35,6 +35,7 @@
#include <ripple/app/main/LocalCredentials.h> #include <ripple/app/main/LocalCredentials.h>
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/Validations.h> #include <ripple/app/misc/Validations.h>
#include <ripple/app/misc/impl/AccountTxPaging.h> #include <ripple/app/misc/impl/AccountTxPaging.h>
#include <ripple/app/misc/UniqueNodeList.h> #include <ripple/app/misc/UniqueNodeList.h>
@@ -839,8 +840,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
if (e.admin) if (e.admin)
flags = flags | tapADMIN; flags = flags | tapADMIN;
auto const result = ripple::apply(app_, view, auto const result = app_.getTxQ().apply(
*e.transaction->getSTransaction(), flags, j); app_, view, e.transaction->getSTransaction(),
flags, j);
e.result = result.first; e.result = result.first;
e.applied = result.second; e.applied = result.second;
changed = changed || 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"; m_journal.info << "Transaction is obsolete";
e.transaction->setStatus (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)) else if (isTerRetry (e.result))
{ {
if (e.failType == FailHard::yes) if (e.failType == FailHard::yes)
@@ -914,8 +926,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
e.transaction->getSTransaction()); e.transaction->getSTransaction());
} }
if (e.applied || if (e.applied || ((mMode != omFULL) &&
((mMode != omFULL) && (e.failType != FailHard::yes) && e.local)) (e.failType != FailHard::yes) && e.local) ||
(e.result == terQUEUED))
{ {
std::set<Peer::id_t> peers; 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_rawtransaction (&s.getData().front(), s.getLength());
tx.set_status (protocol::tsCURRENT); tx.set_status (protocol::tsCURRENT);
tx.set_receivetimestamp (app_.timeKeeper().now().time_since_epoch().count()); tx.set_receivetimestamp (app_.timeKeeper().now().time_since_epoch().count());
tx.set_deferred(e.result == terQUEUED);
// FIXME: This should be when we received it // FIXME: This should be when we received it
app_.overlay().foreach (send_if_not ( app_.overlay().foreach (send_if_not (
std::make_shared<Message> (tx, protocol::mtTRANSACTION), std::make_shared<Message> (tx, protocol::mtTRANSACTION),
@@ -1254,6 +1268,11 @@ void NetworkOPsImp::switchLastClosedLedger (
clearNeedNetworkLedger (); clearNeedNetworkLedger ();
newLCL->setClosed (); newLCL->setClosed ();
// Update fee computations.
// TODO: Needs an open ledger
//app_.getTxQ().processValidatedLedger(app_, *newLCL, true);
// Caller must own master lock // Caller must own master lock
{ {
// Apply tx in old open ledger to new // Apply tx in old open ledger to new
@@ -1269,7 +1288,12 @@ void NetworkOPsImp::switchLastClosedLedger (
rules.emplace(); rules.emplace();
app_.openLedger().accept(app_, *rules, app_.openLedger().accept(app_, *rules,
newLCL, OrderedTxs({}), false, retries, 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); m_ledgerMaster.switchLCL (newLCL);

376
src/ripple/app/misc/TxQ.h Normal file
View 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

View 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

View 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);
}
}

View 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

View File

@@ -236,6 +236,11 @@ Transactor::checkSeq (PreclaimContext const& ctx)
{ {
if (a_seq < t_seq) if (a_seq < t_seq)
{ {
if (ctx.flags & tapPOST_SEQ)
{
return tesSUCCESS;
}
JLOG(ctx.j.trace) << JLOG(ctx.j.trace) <<
"applyTransaction: has future sequence number " << "applyTransaction: has future sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq; "a_seq=" << a_seq << " t_seq=" << t_seq;

View File

@@ -32,27 +32,15 @@ struct PreflightContext
public: public:
Application& app; Application& app;
STTx const& tx; STTx const& tx;
Rules const& rules; Rules const rules;
ApplyFlags flags; ApplyFlags flags;
beast::Journal j; beast::Journal j;
PreflightContext(Application& app_, STTx const& tx_, PreflightContext(Application& app_, STTx const& tx_,
Rules const& rules_, ApplyFlags flags_, Rules const& rules_, ApplyFlags flags_,
beast::Journal j_); beast::Journal j_);
};
struct PreflightResult PreflightContext& operator=(PreflightContext const&) = delete;
{
public:
PreflightContext const ctx;
TER const ter;
PreflightResult(PreflightContext const& ctx_,
TER ter_)
: ctx(ctx_)
, ter(ter_)
{
}
}; };
/** State information when determining if a tx is likely to claim a fee. */ /** State information when determining if a tx is likely to claim a fee. */
@@ -77,28 +65,8 @@ public:
, j(j_) , j(j_)
{ {
} }
};
struct PreclaimResult PreclaimContext& operator=(PreclaimContext const&) = delete;
{
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)
{
}
}; };
class Transactor class Transactor

View File

@@ -20,19 +20,7 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/app/tx/apply.h> #include <ripple/app/tx/apply.h>
#include <ripple/app/tx/impl/applyImpl.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>
#include <ripple/protocol/Feature.h> #include <ripple/protocol/Feature.h>
namespace ripple { namespace ripple {
@@ -43,129 +31,6 @@ namespace ripple {
#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed #define SF_LOCALBAD SF_PRIVATE3 // Local checks failed
#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed #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> std::pair<Validity, std::string>
@@ -246,105 +111,6 @@ forceValidity(HashRouter& router, uint256 const& txid,
router.setFlags(txid, flags); 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> std::pair<TER, bool>
apply (Application& app, OpenView& view, apply (Application& app, OpenView& view,
STTx const& tx, ApplyFlags flags, STTx const& tx, ApplyFlags flags,

View File

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

View 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

View File

@@ -38,6 +38,10 @@ enum ApplyFlags
// //
tapENABLE_TESTING = 0x02, 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 // This is not the transaction's last pass
// Transaction can be retried, soft failures allowed // Transaction can be retried, soft failures allowed
tapRETRY = 0x20, tapRETRY = 0x20,

View File

@@ -109,8 +109,8 @@ Rules::changed (DigestAwareReadView const& ledger) const
bool bool
Rules::operator== (Rules const& other) const Rules::operator== (Rules const& other) const
{ {
#if 0 #if 1
if (! impl_ && ! other.impl_) if (impl_.get() == other.impl_.get())
return true; return true;
if (! impl_ || ! other.impl_) if (! impl_ || ! other.impl_)
return false; return false;

View File

@@ -37,6 +37,7 @@ feature (const char* name);
extern uint256 const featureMultiSign; extern uint256 const featureMultiSign;
extern uint256 const featureSusPay; extern uint256 const featureSusPay;
extern uint256 const featureTrustSetAuth; extern uint256 const featureTrustSetAuth;
extern uint256 const featureFeeEscalation;
} // ripple } // ripple

View File

@@ -119,6 +119,8 @@ JSS ( count ); // in: AccountTx*
JSS ( currency ); // in: paths/PathRequest, STAmount JSS ( currency ); // in: paths/PathRequest, STAmount
// out: paths/Node, STPathSet, STAmount // out: paths/Node, STPathSet, STAmount
JSS ( current ); // out: OwnerInfo JSS ( current ); // out: OwnerInfo
JSS ( current_ledger_size ); // out: TxQ
JSS ( current_queue_size ); // out: TxQ
JSS ( data ); // out: LedgerData JSS ( data ); // out: LedgerData
JSS ( date ); // out: tx/Transaction, NetworkOPs JSS ( date ); // out: tx/Transaction, NetworkOPs
JSS ( dbKBLedger ); // out: getCounts JSS ( dbKBLedger ); // out: getCounts
@@ -136,6 +138,7 @@ JSS ( dir_entry ); // out: DirectoryEntryIterator
JSS ( dir_index ); // out: DirectoryEntryIterator JSS ( dir_index ); // out: DirectoryEntryIterator
JSS ( dir_root ); // out: DirectoryEntryIterator JSS ( dir_root ); // out: DirectoryEntryIterator
JSS ( directory ); // in: LedgerEntry JSS ( directory ); // in: LedgerEntry
JSS ( drops ); // out: TxQ
JSS ( enabled ); // out: AmendmentTable JSS ( enabled ); // out: AmendmentTable
JSS ( engine_result ); // out: NetworkOPs, TransactionSign, Submit JSS ( engine_result ); // out: NetworkOPs, TransactionSign, Submit
JSS ( engine_result_code ); // 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_exception ); // out: Submit
JSS ( error_message ); // out: error JSS ( error_message ); // out: error
JSS ( expand ); // in: handler/Ledger JSS ( expand ); // in: handler/Ledger
JSS ( expected_ledger_size ); // out: TxQ
JSS ( fail_hard ); // in: Sign, Submit JSS ( fail_hard ); // in: Sign, Submit
JSS ( failed ); // out: InboundLedger JSS ( failed ); // out: InboundLedger
JSS ( feature ); // in: Feature JSS ( feature ); // in: Feature
@@ -232,6 +236,9 @@ JSS ( master_key ); // out: WalletPropose
JSS ( master_seed ); // out: WalletPropose JSS ( master_seed ); // out: WalletPropose
JSS ( master_seed_hex ); // out: WalletPropose JSS ( master_seed_hex ); // out: WalletPropose
JSS ( max_ledger ); // in/out: LedgerCleaner 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 ( message ); // error.
JSS ( meta ); // out: NetworkOPs, AccountTx*, Tx JSS ( meta ); // out: NetworkOPs, AccountTx*, Tx
JSS ( metaData ); JSS ( metaData );
@@ -239,6 +246,8 @@ JSS ( metadata ); // out: TransactionEntry
JSS ( method ); // RPC JSS ( method ); // RPC
JSS ( min_count ); // in: GetCounts JSS ( min_count ); // in: GetCounts
JSS ( min_ledger ); // in: LedgerCleaner JSS ( min_ledger ); // in: LedgerCleaner
JSS ( minimum_fee ); // out: TxQ
JSS ( minimum_level ); // out: TxQ
JSS ( missingCommand ); // error JSS ( missingCommand ); // error
JSS ( name ); // out: AmendmentTableImpl, PeerImp JSS ( name ); // out: AmendmentTableImpl, PeerImp
JSS ( needed_state_hashes ); // out: InboundLedger JSS ( needed_state_hashes ); // out: InboundLedger
@@ -261,6 +270,8 @@ JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe
JSS ( offline ); // in: TransactionSign JSS ( offline ); // in: TransactionSign
JSS ( offset ); // in/out: AccountTxOld JSS ( offset ); // in/out: AccountTxOld
JSS ( open ); // out: handlers/Ledger 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 ); // in: LedgerEntry, out: NetworkOPs
JSS ( owner_funds ); // out: NetworkOPs, AcceptedLedgerTx JSS ( owner_funds ); // out: NetworkOPs, AcceptedLedgerTx
JSS ( params ); // RPC JSS ( params ); // RPC
@@ -293,6 +304,7 @@ JSS ( quality_out ); // out: AccountLines
JSS ( random ); // out: Random JSS ( random ); // out: Random
JSS ( raw_meta ); // out: AcceptedLedgerTx JSS ( raw_meta ); // out: AcceptedLedgerTx
JSS ( receive_currencies ); // out: AccountCurrencies JSS ( receive_currencies ); // out: AccountCurrencies
JSS ( reference_level ); // out: TxQ
JSS ( regular_seed ); // in/out: LedgerEntry JSS ( regular_seed ); // in/out: LedgerEntry
JSS ( remote ); // out: Logic.h JSS ( remote ); // out: Logic.h
JSS ( request ); // RPC JSS ( request ); // RPC

View File

@@ -407,6 +407,14 @@ inline bool isXRP(STAmount const& amount)
std::uint64_t std::uint64_t
mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div); 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> template <class T1, class T2>
void lowestTerms(T1& a, T2& b) void lowestTerms(T1& a, T2& b)
{ {

View File

@@ -144,6 +144,7 @@ enum TER
// burden network. // burden network.
terLAST, // Process after all other transactions terLAST, // Process after all other transactions
terNO_RIPPLE, // Rippling not allowed terNO_RIPPLE, // Rippling not allowed
terQUEUED, // Transaction is being held in TxQ until fee drops
// 0: S Success (success) // 0: S Success (success)
// Causes: // Causes:

View File

@@ -48,5 +48,6 @@ feature (const char* name)
uint256 const featureMultiSign = feature("MultiSign"); uint256 const featureMultiSign = feature("MultiSign");
uint256 const featureSusPay = feature("SusPay"); uint256 const featureSusPay = feature("SusPay");
uint256 const featureTrustSetAuth = feature("TrustSetAuth"); uint256 const featureTrustSetAuth = feature("TrustSetAuth");
uint256 const featureFeeEscalation = feature("FeeEscalation");
} // ripple } // ripple

View File

@@ -1277,4 +1277,18 @@ mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
return value * mul / 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 } // ripple

View File

@@ -138,6 +138,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ terNO_LINE, "terNO_LINE", "No such line." }, { terNO_LINE, "terNO_LINE", "No such line." },
{ terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." }, { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." },
{ terOWNERS, "terOWNERS", "Non-zero owner count." }, { terOWNERS, "terOWNERS", "Non-zero owner count." },
{ terQUEUED, "terQUEUED", "Held until fee drops." },
{ tesSUCCESS, "tesSUCCESS", "The transaction was applied. Only final in a validated ledger." }, { tesSUCCESS, "tesSUCCESS", "The transaction was applied. Only final in a validated ledger." },
}; };

View 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

View File

@@ -38,6 +38,7 @@ Json::Value doCanDelete (RPC::Context&);
Json::Value doConnect (RPC::Context&); Json::Value doConnect (RPC::Context&);
Json::Value doConsensusInfo (RPC::Context&); Json::Value doConsensusInfo (RPC::Context&);
Json::Value doFeature (RPC::Context&); Json::Value doFeature (RPC::Context&);
Json::Value doFee (RPC::Context&);
Json::Value doFetchInfo (RPC::Context&); Json::Value doFetchInfo (RPC::Context&);
Json::Value doGatewayBalances (RPC::Context&); Json::Value doGatewayBalances (RPC::Context&);
Json::Value doGetCounts (RPC::Context&); Json::Value doGetCounts (RPC::Context&);

View File

@@ -115,6 +115,7 @@ Handler handlerArray[] {
{ "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION }, { "get_counts", byRef (&doGetCounts), Role::ADMIN, NO_CONDITION },
{ "internal", byRef (&doInternal), Role::ADMIN, NO_CONDITION }, { "internal", byRef (&doInternal), Role::ADMIN, NO_CONDITION },
{ "feature", byRef (&doFeature), 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 }, { "fetch_info", byRef (&doFetchInfo), Role::ADMIN, NO_CONDITION },
{ "ledger_accept", byRef (&doLedgerAccept), Role::ADMIN, NEEDS_CURRENT_LEDGER }, { "ledger_accept", byRef (&doLedgerAccept), Role::ADMIN, NEEDS_CURRENT_LEDGER },
{ "ledger_cleaner", byRef (&doLedgerCleaner), Role::ADMIN, NEEDS_NETWORK_CONNECTION }, { "ledger_cleaner", byRef (&doLedgerCleaner), Role::ADMIN, NEEDS_NETWORK_CONNECTION },

View File

@@ -153,6 +153,8 @@ public:
Env (Env const&) = delete; Env (Env const&) = delete;
Env& operator= (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_); Env (beast::unit_test::suite& test_);
Application& Application&

View File

@@ -76,12 +76,13 @@ makeConfig()
} }
// VFALCO Could wrap the log in a Journal here // 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_) : test (test_)
, master ("master", generateKeyPair( , master ("master", generateKeyPair(
KeyType::secp256k1, KeyType::secp256k1,
generateSeed("masterpassphrase"))) generateSeed("masterpassphrase")))
, bundle_ (makeConfig()) , bundle_ (std::move(config))
, closed_ (std::make_shared<Ledger>( , closed_ (std::make_shared<Ledger>(
create_genesis, app().config(), app().family())) create_genesis, app().config(), app().family()))
, cachedSLEs_ (std::chrono::seconds(5), stopwatch_) , cachedSLEs_ (std::chrono::seconds(5), stopwatch_)
@@ -91,6 +92,11 @@ Env::Env (beast::unit_test::suite& test_)
Pathfinder::initPathTable(); Pathfinder::initPathTable();
} }
Env::Env(beast::unit_test::suite& test_)
: Env(test_, makeConfig())
{
}
std::shared_ptr<OpenView const> std::shared_ptr<OpenView const>
Env::open() const Env::open() const
{ {
@@ -118,13 +124,12 @@ Env::close(NetClock::time_point const& closeTime,
for (auto iter = cur->txs.begin(); for (auto iter = cur->txs.begin();
iter != cur->txs.end(); ++iter) iter != cur->txs.end(); ++iter)
txs.push_back(iter->first); txs.push_back(iter->first);
auto router = std::make_unique<HashRouter>(60);
OrderedTxs retries(uint256{}); OrderedTxs retries(uint256{});
{ {
OpenView accum(&*next); OpenView accum(&*next);
OpenLedger::apply(app(), accum, *closed_, OpenLedger::apply(app(), accum, *closed_,
txs, retries, applyFlags(), *router, txs, retries, applyFlags(), journal);
journal);
accum.apply(*next); accum.apply(*next);
} }
// To ensure that the close time is exact and not rounded, we don't // 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()); ledgerPossibleTimeResolutions[0], false, app().config());
OrderedTxs locals({}); OrderedTxs locals({});
openLedger.accept(app(), next->rules(), next, openLedger.accept(app(), next->rules(), next,
locals, false, retries, applyFlags(), *router, "", f); locals, false, retries, applyFlags(), "", f);
closed_ = next; closed_ = next;
cachedSLEs_.expire(); cachedSLEs_.expire();
} }

View File

@@ -40,4 +40,3 @@
#include <ripple/app/ledger/impl/LedgerTiming.cpp> #include <ripple/app/ledger/impl/LedgerTiming.cpp>
#include <ripple/app/ledger/impl/OpenLedger.cpp> #include <ripple/app/ledger/impl/OpenLedger.cpp>
#include <ripple/app/ledger/impl/LedgerToJson.cpp> #include <ripple/app/ledger/impl/LedgerToJson.cpp>

View File

@@ -29,3 +29,4 @@
#include <ripple/app/misc/Validations.cpp> #include <ripple/app/misc/Validations.cpp>
#include <ripple/app/misc/impl/AccountTxPaging.cpp> #include <ripple/app/misc/impl/AccountTxPaging.cpp>
#include <ripple/app/misc/impl/TxQ.cpp>

View File

@@ -33,3 +33,4 @@
#include <ripple/app/tests/SetAuth_test.cpp> #include <ripple/app/tests/SetAuth_test.cpp>
#include <ripple/app/tests/OversizeMeta_test.cpp> #include <ripple/app/tests/OversizeMeta_test.cpp>
#include <ripple/app/tests/Taker.test.cpp> #include <ripple/app/tests/Taker.test.cpp>
#include <ripple/app/tests/TxQ_test.cpp>

View File

@@ -20,6 +20,7 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/tx/impl/apply.cpp> #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/BookTip.cpp>
#include <ripple/app/tx/impl/CancelOffer.cpp> #include <ripple/app/tx/impl/CancelOffer.cpp>
#include <ripple/app/tx/impl/CancelTicket.cpp> #include <ripple/app/tx/impl/CancelTicket.cpp>

View File

@@ -47,6 +47,7 @@
#include <ripple/rpc/handlers/Connect.cpp> #include <ripple/rpc/handlers/Connect.cpp>
#include <ripple/rpc/handlers/ConsensusInfo.cpp> #include <ripple/rpc/handlers/ConsensusInfo.cpp>
#include <ripple/rpc/handlers/Feature1.cpp> #include <ripple/rpc/handlers/Feature1.cpp>
#include <ripple/rpc/handlers/Fee1.cpp>
#include <ripple/rpc/handlers/FetchInfo.cpp> #include <ripple/rpc/handlers/FetchInfo.cpp>
#include <ripple/rpc/handlers/GatewayBalances.cpp> #include <ripple/rpc/handlers/GatewayBalances.cpp>
#include <ripple/rpc/handlers/GetCounts.cpp> #include <ripple/rpc/handlers/GetCounts.cpp>