mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Change how fail_hard transactions are handled.
FIXES: #2847 * Transactions that are submitted with the fail_hard flag and that result in any TER code besides tesSUCCESS shall be neither queued nor held. [FOLD] Keep tec results out of the open ledger when fail_hard: * Improve TransactionStatus const correctness, and remove redundant `local` check * Check open ledger tx count in fail_hard tests * Fix some wrapping * Remove duplicate test
This commit is contained in:
@@ -77,10 +77,10 @@ class NetworkOPsImp final
|
|||||||
class TransactionStatus
|
class TransactionStatus
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<Transaction> transaction;
|
std::shared_ptr<Transaction> const transaction;
|
||||||
bool admin;
|
bool const admin;
|
||||||
bool local;
|
bool const local;
|
||||||
FailHard failType;
|
FailHard const failType;
|
||||||
bool applied;
|
bool applied;
|
||||||
TER result;
|
TER result;
|
||||||
|
|
||||||
@@ -93,7 +93,9 @@ class NetworkOPsImp final
|
|||||||
, admin (a)
|
, admin (a)
|
||||||
, local (l)
|
, local (l)
|
||||||
, failType (f)
|
, failType (f)
|
||||||
{}
|
{
|
||||||
|
assert(local || failType == FailHard::no);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1055,7 +1057,10 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
|||||||
// we check before adding to the batch
|
// we check before adding to the batch
|
||||||
ApplyFlags flags = tapNONE;
|
ApplyFlags flags = tapNONE;
|
||||||
if (e.admin)
|
if (e.admin)
|
||||||
flags = flags | tapUNLIMITED;
|
flags |= tapUNLIMITED;
|
||||||
|
|
||||||
|
if (e.failType == FailHard::yes)
|
||||||
|
flags |= tapFAIL_HARD;
|
||||||
|
|
||||||
auto const result = app_.getTxQ().apply(
|
auto const result = app_.getTxQ().apply(
|
||||||
app_, view, e.transaction->getSTransaction(),
|
app_, view, e.transaction->getSTransaction(),
|
||||||
@@ -1133,8 +1138,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
|||||||
}
|
}
|
||||||
else if (e.result == terQUEUED)
|
else if (e.result == terQUEUED)
|
||||||
{
|
{
|
||||||
JLOG(m_journal.debug()) << "Transaction is likely to claim a " <<
|
JLOG(m_journal.debug()) << "Transaction is likely to claim a"
|
||||||
"fee, but is queued until fee drops";
|
<< " fee, but is queued until fee drops";
|
||||||
|
|
||||||
e.transaction->setStatus(HELD);
|
e.transaction->setStatus(HELD);
|
||||||
// Add to held transactions, because it could get
|
// Add to held transactions, because it could get
|
||||||
// kicked out of the queue, and this will try to
|
// kicked out of the queue, and this will try to
|
||||||
@@ -1145,11 +1151,7 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
|||||||
}
|
}
|
||||||
else if (isTerRetry (e.result))
|
else if (isTerRetry (e.result))
|
||||||
{
|
{
|
||||||
if (e.failType == FailHard::yes)
|
if (e.failType != FailHard::yes)
|
||||||
{
|
|
||||||
addLocal = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// transaction should be held
|
// transaction should be held
|
||||||
JLOG(m_journal.debug())
|
JLOG(m_journal.debug())
|
||||||
@@ -1166,7 +1168,10 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
|||||||
e.transaction->setStatus (INVALID);
|
e.transaction->setStatus (INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addLocal)
|
auto const enforceFailHard = e.failType == FailHard::yes &&
|
||||||
|
!isTesSuccess(e.result);
|
||||||
|
|
||||||
|
if (addLocal && !enforceFailHard)
|
||||||
{
|
{
|
||||||
m_localTX->push_back (
|
m_localTX->push_back (
|
||||||
m_ledgerMaster.getCurrentLedgerIndex(),
|
m_ledgerMaster.getCurrentLedgerIndex(),
|
||||||
@@ -1174,9 +1179,9 @@ void NetworkOPsImp::apply (std::unique_lock<std::mutex>& batchLock)
|
|||||||
e.transaction->setKept();
|
e.transaction->setKept();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.applied || ((mMode != OperatingMode::FULL) &&
|
if ((e.applied || ((mMode != OperatingMode::FULL) &&
|
||||||
(e.failType != FailHard::yes) && e.local) ||
|
(e.failType != FailHard::yes) && e.local) ||
|
||||||
(e.result == terQUEUED))
|
(e.result == terQUEUED)) && !enforceFailHard)
|
||||||
{
|
{
|
||||||
auto const toSkip = app_.getHashRouter().shouldRelay(
|
auto const toSkip = app_.getHashRouter().shouldRelay(
|
||||||
e.transaction->getID());
|
e.transaction->getID());
|
||||||
|
|||||||
@@ -717,7 +717,7 @@ private:
|
|||||||
/** Checks if the indicated transaction fits the conditions
|
/** Checks if the indicated transaction fits the conditions
|
||||||
for being stored in the queue.
|
for being stored in the queue.
|
||||||
*/
|
*/
|
||||||
bool canBeHeld(STTx const&, OpenView const&,
|
bool canBeHeld(STTx const&, ApplyFlags const, OpenView const&,
|
||||||
AccountMap::iterator,
|
AccountMap::iterator,
|
||||||
boost::optional<FeeMultiSet::iterator>);
|
boost::optional<FeeMultiSet::iterator>);
|
||||||
|
|
||||||
|
|||||||
@@ -360,16 +360,18 @@ TxQ::isFull() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TxQ::canBeHeld(STTx const& tx, OpenView const& view,
|
TxQ::canBeHeld(STTx const& tx, ApplyFlags const flags, OpenView const& view,
|
||||||
AccountMap::iterator accountIter,
|
AccountMap::iterator accountIter,
|
||||||
boost::optional<FeeMultiSet::iterator> replacementIter)
|
boost::optional<FeeMultiSet::iterator> replacementIter)
|
||||||
{
|
{
|
||||||
// PreviousTxnID is deprecated and should never be used
|
// PreviousTxnID is deprecated and should never be used
|
||||||
// AccountTxnID is not supported by the transaction
|
// AccountTxnID is not supported by the transaction
|
||||||
// queue yet, but should be added in the future
|
// queue yet, but should be added in the future
|
||||||
|
// tapFAIL_HARD transactions are never held
|
||||||
bool canBeHeld =
|
bool canBeHeld =
|
||||||
! tx.isFieldPresent(sfPreviousTxnID) &&
|
! tx.isFieldPresent(sfPreviousTxnID) &&
|
||||||
! tx.isFieldPresent(sfAccountTxnID);
|
! tx.isFieldPresent(sfAccountTxnID) &&
|
||||||
|
! (flags & tapFAIL_HARD);
|
||||||
if (canBeHeld)
|
if (canBeHeld)
|
||||||
{
|
{
|
||||||
/* To be queued and relayed, the transaction needs to
|
/* To be queued and relayed, the transaction needs to
|
||||||
@@ -798,7 +800,7 @@ TxQ::apply(Application& app, OpenView& view,
|
|||||||
// object to hold the info we need to adjust for
|
// object to hold the info we need to adjust for
|
||||||
// prior txns. Otherwise, let preclaim fail as if
|
// prior txns. Otherwise, let preclaim fail as if
|
||||||
// we didn't have the queue at all.
|
// we didn't have the queue at all.
|
||||||
if (canBeHeld(*tx, view, accountIter, replacedItemDeleteIter))
|
if (canBeHeld(*tx, flags, view, accountIter, replacedItemDeleteIter))
|
||||||
multiTxn.emplace();
|
multiTxn.emplace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,7 +1050,7 @@ TxQ::apply(Application& app, OpenView& view,
|
|||||||
|
|
||||||
// If `multiTxn` has a value, then `canBeHeld` has already been verified
|
// If `multiTxn` has a value, then `canBeHeld` has already been verified
|
||||||
if (! multiTxn &&
|
if (! multiTxn &&
|
||||||
! canBeHeld(*tx, view, accountIter, replacedItemDeleteIter))
|
! canBeHeld(*tx, flags, view, accountIter, replacedItemDeleteIter))
|
||||||
{
|
{
|
||||||
// Bail, transaction cannot be held
|
// Bail, transaction cannot be held
|
||||||
JLOG(j_.trace()) << "Transaction " <<
|
JLOG(j_.trace()) << "Transaction " <<
|
||||||
|
|||||||
@@ -670,7 +670,15 @@ Transactor::operator()()
|
|||||||
if (ctx_.size() > oversizeMetaDataCap)
|
if (ctx_.size() > oversizeMetaDataCap)
|
||||||
result = tecOVERSIZE;
|
result = tecOVERSIZE;
|
||||||
|
|
||||||
if ((result == tecOVERSIZE) || (result == tecKILLED) ||
|
if (isTecClaim(result) && (view().flags() & tapFAIL_HARD))
|
||||||
|
{
|
||||||
|
// If the tapFAIL_HARD flag is set, a tec result
|
||||||
|
// must not do anything
|
||||||
|
|
||||||
|
ctx_.discard();
|
||||||
|
applied = false;
|
||||||
|
}
|
||||||
|
else if ((result == tecOVERSIZE) || (result == tecKILLED) ||
|
||||||
(isTecClaimHardFail (result, view().flags())))
|
(isTecClaimHardFail (result, view().flags())))
|
||||||
{
|
{
|
||||||
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ enum ApplyFlags
|
|||||||
{
|
{
|
||||||
tapNONE = 0x00,
|
tapNONE = 0x00,
|
||||||
|
|
||||||
|
// This is a local transaction with the
|
||||||
|
// fail_hard flag set.
|
||||||
|
tapFAIL_HARD = 0x10,
|
||||||
|
|
||||||
// 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,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ enum TEMcodes : TERUnderlyingType
|
|||||||
// - Not applied
|
// - Not applied
|
||||||
// - Not forwarded
|
// - Not forwarded
|
||||||
// - Reject
|
// - Reject
|
||||||
// - Can not succeed in any imagined ledger.
|
// - Cannot succeed in any imagined ledger.
|
||||||
temMALFORMED = -299,
|
temMALFORMED = -299,
|
||||||
|
|
||||||
temBAD_AMOUNT,
|
temBAD_AMOUNT,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <ripple/basics/Log.h>
|
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
#include <ripple/json/to_string.h>
|
#include <ripple/json/to_string.h>
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
@@ -25,6 +24,8 @@
|
|||||||
#include <ripple/protocol/TxFlags.h>
|
#include <ripple/protocol/TxFlags.h>
|
||||||
#include <ripple/beast/hash/uhash.h>
|
#include <ripple/beast/hash/uhash.h>
|
||||||
#include <ripple/beast/unit_test.h>
|
#include <ripple/beast/unit_test.h>
|
||||||
|
#include <ripple/app/misc/NetworkOPs.h>
|
||||||
|
#include <ripple/app/misc/TxQ.h>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -348,6 +349,81 @@ public:
|
|||||||
env(noop("alice"));
|
env(noop("alice"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rudimentary test to ensure fail_hard
|
||||||
|
// transactions are neither queued nor
|
||||||
|
// held.
|
||||||
|
void
|
||||||
|
testFailHard()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
Env env(*this);
|
||||||
|
auto const gw = Account("gateway");
|
||||||
|
auto const USD = gw["USD"];
|
||||||
|
|
||||||
|
auto const alice = Account {"alice"};
|
||||||
|
env.fund (XRP(10000), alice);
|
||||||
|
|
||||||
|
auto const localTxCnt = env.app().getOPs().getLocalTxCount();
|
||||||
|
auto const queueTxCount = env.app().getTxQ().getMetrics(*env.current()).txCount;
|
||||||
|
auto const openTxCount = env.current()->txCount();
|
||||||
|
BEAST_EXPECT(localTxCnt == 2 && queueTxCount == 0 && openTxCount == 2);
|
||||||
|
|
||||||
|
auto applyTxn = [&env](auto&& ...txnArgs)
|
||||||
|
{
|
||||||
|
auto jt = env.jt (txnArgs...);
|
||||||
|
Serializer s;
|
||||||
|
jt.stx->add (s);
|
||||||
|
|
||||||
|
Json::Value args {Json::objectValue};
|
||||||
|
|
||||||
|
args[jss::tx_blob] = strHex (s.slice ());
|
||||||
|
args[jss::fail_hard] = true;
|
||||||
|
|
||||||
|
return env.rpc ("json", "submit", args.toStyledString());
|
||||||
|
};
|
||||||
|
|
||||||
|
auto jr = applyTxn(noop (alice), fee (1));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "telINSUF_FEE_P");
|
||||||
|
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount);
|
||||||
|
|
||||||
|
jr = applyTxn(noop (alice), sig("bob"));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tefBAD_AUTH");
|
||||||
|
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount);
|
||||||
|
|
||||||
|
jr = applyTxn(noop (alice), seq(20));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "terPRE_SEQ");
|
||||||
|
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount);
|
||||||
|
|
||||||
|
jr = applyTxn(offer(alice, XRP(1000), USD(1000)));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tecUNFUNDED_OFFER");
|
||||||
|
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount);
|
||||||
|
|
||||||
|
jr = applyTxn(noop (alice), fee (drops (-10)));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "temBAD_FEE");
|
||||||
|
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount);
|
||||||
|
|
||||||
|
jr = applyTxn(noop (alice));
|
||||||
|
|
||||||
|
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tesSUCCESS");
|
||||||
|
BEAST_EXPECT(env.app().getOPs().getLocalTxCount () == localTxCnt + 1);
|
||||||
|
BEAST_EXPECT(env.current()->txCount() == openTxCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Multi-sign basics
|
// Multi-sign basics
|
||||||
void
|
void
|
||||||
testMultiSign()
|
testMultiSign()
|
||||||
@@ -774,6 +850,7 @@ public:
|
|||||||
testRequire();
|
testRequire();
|
||||||
testKeyType();
|
testKeyType();
|
||||||
testPayments();
|
testPayments();
|
||||||
|
testFailHard();
|
||||||
testMultiSign();
|
testMultiSign();
|
||||||
testTicket();
|
testTicket();
|
||||||
testJTxProperties();
|
testJTxProperties();
|
||||||
|
|||||||
Reference in New Issue
Block a user