Add preclaim functionality to transactors:

The preclaim() function performs static validity
analysis of transactions using limited information
from a ledger.
This commit is contained in:
Edward Hennis
2015-07-27 17:43:14 -04:00
committed by Vinnie Falco
parent ad8e9a76ed
commit 9b80081122
26 changed files with 1056 additions and 543 deletions

View File

@@ -1745,6 +1745,8 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2487,6 +2487,9 @@
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
<Filter>ripple\app\tx\impl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
<Filter>ripple\app\tx\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
<Filter>ripple\app\tx\impl</Filter>
</ClCompile>

View File

@@ -32,33 +32,6 @@ namespace ripple {
class Application;
/** Gate a transaction based on static information.
The transaction is checked against all possible
validity constraints that do not require a ledger.
@return The TER code (a `tem` or tesSUCCESS)
*/
TER
preflight (Rules const& rules, STTx const& tx,
ApplyFlags flags, SigVerify verify,
Config const& config, beast::Journal j);
/** Apply a prechecked transaction to an OpenView.
See also: apply()
Precondition: The transaction has been checked
and validated using the above function(s).
@return A pair with the TER and a bool indicating
whether or not the transaction was applied.
*/
std::pair<TER, bool>
doapply(Application& app, OpenView& view,
STTx const& tx, ApplyFlags flags,
Config const& config, beast::Journal j);
/** Apply a transaction to a ReadView.
Throws:

View File

@@ -28,12 +28,13 @@
namespace ripple {
ApplyContext::ApplyContext(Application& app_,
OpenView& base, STTx const& tx_,
ApplyFlags flags, Config const& config_,
OpenView& base, STTx const& tx_, TER preclaimResult_,
std::uint64_t baseFee_, ApplyFlags flags,
beast::Journal journal_)
: app(app_)
, tx (tx_)
, config (config_)
, tx(tx_)
, preclaimResult(preclaimResult_)
, baseFee(baseFee_)
, journal(journal_)
, base_ (base)
, flags_(flags)

View File

@@ -39,13 +39,14 @@ class ApplyContext
public:
explicit
ApplyContext (Application& app, OpenView& base,
STTx const& tx, ApplyFlags flags,
Config const& config,
STTx const& tx, TER preclaimResult,
std::uint64_t baseFee, ApplyFlags flags,
beast::Journal = {});
Application& app;
STTx const& tx;
Config const& config;
TER const preclaimResult;
std::uint64_t const baseFee;
beast::Journal const journal;
ApplyView&

View File

@@ -20,8 +20,7 @@
#include <BeastConfig.h>
#include <ripple/app/tx/impl/CancelOffer.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <ripple/ledger/View.h>
namespace ripple {
@@ -56,31 +55,46 @@ CancelOffer::preflight (PreflightContext const& ctx)
//------------------------------------------------------------------------------
TER
CancelOffer::doApply ()
CancelOffer::preclaim(PreclaimContext const& ctx)
{
std::uint32_t const uOfferSequence = tx().getFieldU32 (sfOfferSequence);
auto const id = ctx.tx[sfAccount];
auto const offerSequence = ctx.tx[sfOfferSequence];
auto const sle = view().read(
keylet::account(account_));
if (sle->getFieldU32 (sfSequence) - 1 <= uOfferSequence)
auto const sle = ctx.view.read(
keylet::account(id));
if ((*sle)[sfSequence] <= offerSequence)
{
j_.trace << "Malformed transaction: " <<
"Sequence " << uOfferSequence << " is invalid.";
ctx.j.trace << "Malformed transaction: " <<
"Sequence " << offerSequence << " is invalid.";
return temBAD_SEQUENCE;
}
uint256 const offerIndex (getOfferIndex (account_, uOfferSequence));
return tesSUCCESS;
}
//------------------------------------------------------------------------------
TER
CancelOffer::doApply ()
{
auto const offerSequence = ctx_.tx[sfOfferSequence];
auto const sle = view().read(
keylet::account(account_));
uint256 const offerIndex (getOfferIndex (account_, offerSequence));
auto sleOffer = view().peek (
keylet::offer(offerIndex));
if (sleOffer)
{
j_.debug << "Trying to cancel offer #" << uOfferSequence;
return offerDelete (view(), sleOffer, ctx_.app.journal ("View"));
JLOG(j_.debug) << "Trying to cancel offer #" << offerSequence;
return offerDelete (view(), sleOffer, ctx_.app.journal("View"));
}
j_.debug << "Offer #" << uOfferSequence << " can't be found.";
JLOG(j_.debug) << "Offer #" << offerSequence << " can't be found.";
return tesSUCCESS;
}

View File

@@ -40,6 +40,10 @@ public:
TER
preflight (PreflightContext const& ctx);
static
TER
preclaim(PreclaimContext const& ctx);
TER doApply () override;
};

View File

@@ -43,7 +43,7 @@ CancelTicket::preflight (PreflightContext const& ctx)
TER
CancelTicket::doApply ()
{
uint256 const ticketId = tx().getFieldH256 (sfTicketID);
uint256 const ticketId = ctx_.tx.getFieldH256 (sfTicketID);
// VFALCO This is highly suspicious, we're requiring that the
// transaction provide the return value of getTicketIndex?

View File

@@ -31,6 +31,10 @@ namespace ripple {
TER
Change::preflight (PreflightContext const& ctx)
{
auto const ret = preflight0(ctx);
if (!isTesSuccess(ret))
return ret;
auto account = ctx.tx.getAccountID(sfAccount);
if (account != zero)
{
@@ -62,36 +66,45 @@ Change::preflight (PreflightContext const& ctx)
}
TER
Change::doApply()
Change::preclaim(PreclaimContext const &ctx)
{
// If tapOPEN_LEDGER is resurrected into ApplyFlags,
// this block can be moved to preflight.
if (view().open())
if (ctx.view.open())
{
j_.warning << "Change transaction against open ledger";
ctx.j.warning << "Change transaction against open ledger";
return temINVALID;
}
if (tx().getTxnType () == ttAMENDMENT)
if (ctx.tx.getTxnType() != ttAMENDMENT
&& ctx.tx.getTxnType() != ttFEE)
return temUNKNOWN;
return tesSUCCESS;
}
TER
Change::doApply()
{
if (ctx_.tx.getTxnType () == ttAMENDMENT)
return applyAmendment ();
if (tx().getTxnType () == ttFEE)
assert(ctx_.tx.getTxnType() == ttFEE);
return applyFee ();
return temUNKNOWN;
}
void
Change::preCompute()
{
account_ = tx().getAccountID(sfAccount);
account_ = ctx_.tx.getAccountID(sfAccount);
assert(account_ == zero);
}
TER
Change::applyAmendment()
{
uint256 amendment (tx().getFieldH256 (sfAmendment));
uint256 amendment (ctx_.tx.getFieldH256 (sfAmendment));
auto const k = keylet::amendments();
@@ -111,7 +124,7 @@ Change::applyAmendment()
amendment) != amendments.end ())
return tefALREADY;
auto flags = tx().getFlags ();
auto flags = ctx_.tx.getFlags ();
const bool gotMajority = (flags & tfGotMajority) != 0;
const bool lostMajority = (flags & tfLostMajority) != 0;
@@ -193,13 +206,13 @@ Change::applyFee()
// "Previous fee object: " << feeObject->getJson (0);
feeObject->setFieldU64 (
sfBaseFee, tx().getFieldU64 (sfBaseFee));
sfBaseFee, ctx_.tx.getFieldU64 (sfBaseFee));
feeObject->setFieldU32 (
sfReferenceFeeUnits, tx().getFieldU32 (sfReferenceFeeUnits));
sfReferenceFeeUnits, ctx_.tx.getFieldU32 (sfReferenceFeeUnits));
feeObject->setFieldU32 (
sfReserveBase, tx().getFieldU32 (sfReserveBase));
sfReserveBase, ctx_.tx.getFieldU32 (sfReserveBase));
feeObject->setFieldU32 (
sfReserveIncrement, tx().getFieldU32 (sfReserveIncrement));
sfReserveIncrement, ctx_.tx.getFieldU32 (sfReserveIncrement));
view().update (feeObject);

View File

@@ -45,15 +45,22 @@ public:
TER doApply () override;
void preCompute() override;
static
std::uint64_t
calculateBaseFee (
PreclaimContext const& ctx)
{
return 0;
}
static
TER
preclaim(PreclaimContext const &ctx);
private:
TER applyAmendment ();
TER applyFee ();
std::uint64_t calculateBaseFee () override
{
return 0;
}
};
}

View File

@@ -22,7 +22,7 @@
#include <ripple/app/tx/impl/OfferStream.h>
#include <ripple/app/tx/impl/Taker.h>
#include <ripple/app/ledger/Ledger.h>
#include <ripple/protocol/Quality.h>
#include <ripple/protocol/st.h>
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <ripple/ledger/Sandbox.h>
@@ -80,8 +80,8 @@ CreateOffer::preflight (PreflightContext const& ctx)
return temBAD_SEQUENCE;
}
STAmount saTakerPays = tx.getFieldAmount (sfTakerPays);
STAmount saTakerGets = tx.getFieldAmount (sfTakerGets);
STAmount saTakerPays = tx[sfTakerPays];
STAmount saTakerGets = tx[sfTakerGets];
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
return temBAD_AMOUNT;
@@ -131,33 +131,109 @@ CreateOffer::preflight (PreflightContext const& ctx)
}
TER
CreateOffer::checkAcceptAsset(Issue const& issue) const
CreateOffer::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
auto saTakerPays = ctx.tx[sfTakerPays];
auto saTakerGets = ctx.tx[sfTakerGets];
auto const& uPaysIssuerID = saTakerPays.getIssuer();
auto const& uPaysCurrency = saTakerPays.getCurrency();
auto const& uGetsIssuerID = saTakerGets.getIssuer();
auto const cancelSequence = ctx.tx[~sfOfferSequence];
auto const sleCreator = ctx.view.read(keylet::account(id));
std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
auto viewJ = ctx.app.journal("View");
if (isGlobalFrozen(ctx.view, uPaysIssuerID) ||
isGlobalFrozen(ctx.view, uGetsIssuerID))
{
JLOG(ctx.j.warning) <<
"Offer involves frozen asset";
return tecFROZEN;
}
else if (accountFunds(ctx.view, id, saTakerGets,
fhZERO_IF_FROZEN, viewJ) <= zero)
{
JLOG(ctx.j.debug) <<
"delay: Offers must be at least partially funded.";
return tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (cancelSequence && (uAccountSequence <= *cancelSequence))
{
JLOG(ctx.j.debug) <<
"uAccountSequenceNext=" << uAccountSequence <<
" uOfferSequence=" << *cancelSequence;
return temBAD_SEQUENCE;
}
auto const expiration = ctx.tx[~sfExpiration];
// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (expiration &&
(ctx.view.parentCloseTime() >= *expiration))
{
// Note that this will get checked again in applyGuts,
// but it saves us a call to checkAcceptAsset and
// possible false negative.
return tesSUCCESS;
}
// Make sure that we are authorized to hold what the taker will pay us.
if (!saTakerPays.native())
{
auto result = checkAcceptAsset(ctx.view, ctx.flags,
id, ctx.j, Issue(uPaysCurrency, uPaysIssuerID));
if (result != tesSUCCESS)
return result;
}
return tesSUCCESS;
}
TER
CreateOffer::checkAcceptAsset(ReadView const& view,
ApplyFlags const flags, AccountID const id,
beast::Journal const j, Issue const& issue)
{
// Only valid for custom currencies
assert (!isXRP (issue.currency));
auto const issuerAccount = ctx_.view().read(
auto const issuerAccount = view.read(
keylet::account(issue.account));
if (!issuerAccount)
{
if (j_.warning) j_.warning <<
JLOG(j.warning) <<
"delay: can't receive IOUs from non-existent issuer: " <<
to_string (issue.account);
return (view().flags() & tapRETRY)
return (flags & tapRETRY)
? terNO_ACCOUNT
: tecNO_ISSUER;
}
if (issuerAccount->getFieldU32 (sfFlags) & lsfRequireAuth)
if ((*issuerAccount)[sfFlags] & lsfRequireAuth)
{
auto const trustLine = ctx_.view().read(
keylet::line(account_, issue.account, issue.currency));
auto const trustLine = view.read(
keylet::line(id, issue.account, issue.currency));
if (!trustLine)
{
return (view().flags() & tapRETRY)
return (flags & tapRETRY)
? terNO_LINE
: tecNO_LINE;
}
@@ -165,17 +241,17 @@ CreateOffer::checkAcceptAsset(Issue const& issue) const
// Entries have a canonical representation, determined by a
// lexicographical "greater than" comparison employing strict weak
// ordering. Determine which entry we need to access.
bool const canonical_gt (account_ > issue.account);
bool const canonical_gt (id > issue.account);
bool const is_authorized (trustLine->getFieldU32 (sfFlags) &
bool const is_authorized ((*trustLine)[sfFlags] &
(canonical_gt ? lsfLowAuth : lsfHighAuth));
if (!is_authorized)
{
if (j_.debug) j_.debug <<
JLOG(j.debug) <<
"delay: can't receive IOUs from issuer without auth.";
return (view().flags() & tapRETRY)
return (flags & tapRETRY)
? terNO_AUTH
: tecNO_AUTH;
}
@@ -513,7 +589,7 @@ CreateOffer::cross (
beast::WrappedSink takerSink (j_, "Taker ");
Taker taker (cross_type_, view, account_, taker_amount,
tx().getFlags(), beast::Journal (takerSink));
ctx_.tx.getFlags(), beast::Journal (takerSink));
try
{
@@ -548,9 +624,9 @@ CreateOffer::preCompute()
{
cross_type_ = CrossType::IouToIou;
bool const pays_xrp =
tx().getFieldAmount (sfTakerPays).native ();
ctx_.tx.getFieldAmount (sfTakerPays).native ();
bool const gets_xrp =
tx().getFieldAmount (sfTakerGets).native ();
ctx_.tx.getFieldAmount (sfTakerGets).native ();
if (pays_xrp && !gets_xrp)
cross_type_ = CrossType::IouToXrp;
else if (gets_xrp && !pays_xrp)
@@ -562,29 +638,22 @@ CreateOffer::preCompute()
std::pair<TER, bool>
CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
{
std::uint32_t const uTxFlags = tx().getFlags ();
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
bool const bPassive (uTxFlags & tfPassive);
bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel);
bool const bFillOrKill (uTxFlags & tfFillOrKill);
bool const bSell (uTxFlags & tfSell);
STAmount saTakerPays = tx().getFieldAmount (sfTakerPays);
STAmount saTakerGets = tx().getFieldAmount (sfTakerGets);
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
return { temBAD_AMOUNT, true };
auto saTakerPays = ctx_.tx[sfTakerPays];
auto saTakerGets = ctx_.tx[sfTakerGets];
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
auto const& uPaysCurrency = saTakerPays.getCurrency ();
auto const& uGetsIssuerID = saTakerGets.getIssuer ();
bool const bHaveExpiration (tx().isFieldPresent (sfExpiration));
bool const bHaveCancel (tx().isFieldPresent (sfOfferSequence));
std::uint32_t const uExpiration = tx().getFieldU32 (sfExpiration);
std::uint32_t const uCancelSequence = tx().getFieldU32 (sfOfferSequence);
auto const cancelSequence = ctx_.tx[~sfOfferSequence];
// FIXME understand why we use SequenceNext instead of current transaction
// sequence to determine the transaction. Why is the offer sequence
@@ -592,84 +661,49 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
auto const sleCreator = view.peek (keylet::account(account_));
deprecatedWrongOwnerCount_ = sleCreator->getFieldU32(sfOwnerCount);
deprecatedWrongOwnerCount_ = (*sleCreator)[sfOwnerCount];
std::uint32_t const uAccountSequenceNext = sleCreator->getFieldU32 (sfSequence);
std::uint32_t const uSequence = tx().getSequence ();
auto const uAccountSequenceNext = (*sleCreator)[sfSequence];
auto const uSequence = ctx_.tx.getSequence ();
// This is the original rate of the offer, and is the rate at which
// it will be placed, even if crossing offers change the amounts that
// end up on the books.
std::uint64_t const uRate = getRate (saTakerGets, saTakerPays);
auto const uRate = getRate (saTakerGets, saTakerPays);
auto viewJ = ctx_.app.journal ("View");
auto viewJ = ctx_.app.journal("View");
TER result = tesSUCCESS;
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
if (isGlobalFrozen (view, uPaysIssuerID) || isGlobalFrozen (view, uGetsIssuerID))
{
if (j_.warning) j_.warning <<
"Offer involves frozen asset";
result = tecFROZEN;
}
else if (accountFunds(view, account_, saTakerGets,
fhZERO_IF_FROZEN, viewJ) <= zero)
{
if (j_.debug) j_.debug <<
"delay: Offers must be at least partially funded.";
result = tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (bHaveCancel && (uAccountSequenceNext - 1 <= uCancelSequence))
{
if (j_.debug) j_.debug <<
"uAccountSequenceNext=" << uAccountSequenceNext <<
" uOfferSequence=" << uCancelSequence;
result = temBAD_SEQUENCE;
}
if (result != tesSUCCESS)
{
j_.debug << "final result: " << transToken (result);
return { result, true };
}
auto result = tesSUCCESS;
// Process a cancellation request that's passed along with an offer.
if (bHaveCancel)
if (cancelSequence)
{
auto const sleCancel = view.peek(
keylet::offer(account_, uCancelSequence));
keylet::offer(account_, *cancelSequence));
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed. If it is found, however, it's an error
// to fail to delete it.
if (sleCancel)
{
j_.debug << "Create cancels order " << uCancelSequence;
JLOG(j_.debug) << "Create cancels order " << *cancelSequence;
result = offerDelete (view, sleCancel, viewJ);
}
}
auto const expiration = ctx_.tx[~sfExpiration];
// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (bHaveExpiration &&
(ctx_.view().parentCloseTime() >= uExpiration))
if (expiration &&
(ctx_.view().parentCloseTime() >= *expiration))
{
return { tesSUCCESS, true };
// If the offer has expired, the transaction has successfully
// done nothing, so short circuit from here.
return{ tesSUCCESS, true };
}
// Make sure that we are authorized to hold what the taker will pay us.
if (result == tesSUCCESS && !saTakerPays.native ())
result = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID));
bool const bOpenLedger =
ctx_.view().open();
bool crossed = false;
@@ -684,13 +718,13 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
// empty (fully crossed), or something in-between.
Amounts place_offer;
j_.debug << "Attempting cross: " <<
JLOG(j_.debug) << "Attempting cross: " <<
to_string (taker_amount.in.issue ()) << " -> " <<
to_string (taker_amount.out.issue ());
if (j_.trace)
{
j_.debug << " mode: " <<
j_.trace << " mode: " <<
(bPassive ? "passive " : "") <<
(bSell ? "sell" : "buy");
j_.trace <<" in: " << format_amount (taker_amount.in);
@@ -698,7 +732,9 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
}
std::tie(result, place_offer) = cross (view, view_cancel, taker_amount);
assert (result != tefINTERNAL);
// We expect the implementation of cross to succeed
// or give a tec.
assert(result == tesSUCCESS || isTecClaim(result));
if (j_.trace)
{
@@ -855,8 +891,8 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
sleOffer->setFieldU64 (sfBookNode, uBookNode);
if (uExpiration)
sleOffer->setFieldU32 (sfExpiration, uExpiration);
if (expiration)
sleOffer->setFieldU32 (sfExpiration, *expiration);
if (bPassive)
sleOffer->setFlag (lsfPassive);
if (bSell)

View File

@@ -49,6 +49,10 @@ public:
TER
preflight (PreflightContext const& ctx);
static
TER
preclaim(PreclaimContext const& ctx);
void
preCompute() override;
@@ -60,8 +64,11 @@ public:
private:
/** Determine if we are authorized to hold the asset we want to get */
static
TER
checkAcceptAsset(Issue const& issue) const;
checkAcceptAsset(ReadView const& view,
ApplyFlags const flags, AccountID const id,
beast::Journal const j, Issue const& issue);
bool
dry_offer (ApplyView& view, Offer const& offer);

View File

@@ -68,25 +68,25 @@ CreateTicket::doApply ()
std::uint32_t expiration (0);
if (tx().isFieldPresent (sfExpiration))
if (ctx_.tx.isFieldPresent (sfExpiration))
{
expiration = tx().getFieldU32 (sfExpiration);
expiration = ctx_.tx.getFieldU32 (sfExpiration);
if (view().parentCloseTime() >= expiration)
return tesSUCCESS;
}
SLE::pointer sleTicket = std::make_shared<SLE>(ltTICKET,
getTicketIndex (account_, tx().getSequence ()));
getTicketIndex (account_, ctx_.tx.getSequence ()));
sleTicket->setAccountID (sfAccount, account_);
sleTicket->setFieldU32 (sfSequence, tx().getSequence ());
sleTicket->setFieldU32 (sfSequence, ctx_.tx.getSequence ());
if (expiration != 0)
sleTicket->setFieldU32 (sfExpiration, expiration);
view().insert (sleTicket);
if (tx().isFieldPresent (sfTarget))
if (ctx_.tx.isFieldPresent (sfTarget))
{
AccountID const target_account (tx().getAccountID (sfTarget));
AccountID const target_account (ctx_.tx.getAccountID (sfTarget));
SLE::pointer sleTarget = view().peek (keylet::account(target_account));

View File

@@ -21,7 +21,7 @@
#include <ripple/app/tx/impl/Payment.h>
#include <ripple/app/paths/RippleCalc.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <ripple/protocol/JsonFields.h>
namespace ripple {
@@ -31,11 +31,12 @@ namespace ripple {
// Mon Aug 17 11:00:00am PDT
static std::uint32_t const deliverMinTime = 493149600;
static
bool
allowDeliverMin (ApplyView const& view)
allowDeliverMin (ReadView const& view, ApplyFlags const& flags)
{
return view.info().parentCloseTime > deliverMinTime ||
(view.flags() & tapENABLE_TESTING);
(flags & tapENABLE_TESTING);
}
TER
@@ -158,7 +159,8 @@ Payment::preflight (PreflightContext const& ctx)
return temBAD_SEND_XRP_NO_DIRECT;
}
if (tx.isFieldPresent(sfDeliverMin))
auto const deliverMin = tx[~sfDeliverMin];
if (deliverMin)
{
if (! partialPaymentAllowed)
{
@@ -167,7 +169,7 @@ Payment::preflight (PreflightContext const& ctx)
return temBAD_AMOUNT;
}
auto const dMin = tx.getFieldAmount(sfDeliverMin);
auto const dMin = *deliverMin;
if (!isLegalNet(dMin) || dMin <= zero)
{
JLOG(j.trace) << "Malformed transaction: Invalid " <<
@@ -195,29 +197,128 @@ Payment::preflight (PreflightContext const& ctx)
}
TER
Payment::doApply ()
Payment::preclaim(PreclaimContext const& ctx)
{
bool const deliverMin = tx().isFieldPresent(sfDeliverMin);
auto const deliverMin = ctx.tx[~sfDeliverMin];
if (deliverMin)
{
if (! allowDeliverMin(view()))
if (!allowDeliverMin(ctx.view, ctx.flags))
return temMALFORMED;
}
auto const id = ctx.tx[sfAccount];
// Ripple if source or destination is non-native or if there are paths.
std::uint32_t const uTxFlags = tx().getFlags ();
std::uint32_t const uTxFlags = ctx.tx.getFlags();
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
auto const paths = ctx.tx.isFieldPresent(sfPaths);
auto const sendMax = ctx.tx[~sfSendMax];
AccountID const uDstAccountID(ctx.tx[sfDestination]);
STAmount const saDstAmount(ctx.tx[sfAmount]);
auto const k = keylet::account(uDstAccountID);
auto const sleDst = ctx.view.read(k);
if (!sleDst)
{
// Destination account does not exist.
if (!saDstAmount.native())
{
JLOG(ctx.j.trace) <<
"Delay transaction: Destination account does not exist.";
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST;
}
else if (ctx.view.open()
&& partialPaymentAllowed)
{
// You cannot fund an account with a partial payment.
// Make retry work smaller, by rejecting this.
JLOG(ctx.j.trace) <<
"Delay transaction: Partial payment not allowed to create account.";
// Another transaction could create the account and then this
// transaction would succeed.
return telNO_DST_PARTIAL;
}
else if (saDstAmount < STAmount(ctx.view.fees().accountReserve(0)))
{
// accountReserve is the minimum amount that an account can have.
// Reserve is not scaled by load.
JLOG(ctx.j.trace) <<
"Delay transaction: Destination account does not exist. " <<
"Insufficent payment to create account.";
// TODO: dedupe
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST_INSUF_XRP;
}
}
else if ((sleDst->getFlags() & lsfRequireDestTag) &&
!ctx.tx.isFieldPresent(sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
// We didn't make this test for a newly-formed account because there's
// no way for this field to be set.
JLOG(ctx.j.trace) << "Malformed transaction: DestinationTag required.";
return tecDST_TAG_NEEDED;
}
if (paths || sendMax || !saDstAmount.native())
{
// Ripple payment with at least one intermediate step and uses
// transitive balances.
// Copy paths into an editable class.
STPathSet const spsPaths = ctx.tx.getFieldPathSet(sfPaths);
auto pathTooBig = spsPaths.size() > MaxPathSize;
if(!pathTooBig)
for (auto const& path : spsPaths)
if (path.size() > MaxPathLength)
{
pathTooBig = true;
break;
}
if (ctx.view.open() && pathTooBig)
{
return telBAD_PATH_COUNT; // Too many paths for proposed ledger.
}
}
return tesSUCCESS;
}
TER
Payment::doApply ()
{
auto const deliverMin = ctx_.tx[~sfDeliverMin];
// Ripple if source or destination is non-native or if there are paths.
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
bool const limitQuality = uTxFlags & tfLimitQuality;
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = tx().isFieldPresent (sfPaths);
bool const bMax = tx().isFieldPresent (sfSendMax);
auto const paths = ctx_.tx.isFieldPresent(sfPaths);
auto const sendMax = ctx_.tx[~sfSendMax];
AccountID const uDstAccountID (tx().getAccountID (sfDestination));
STAmount const saDstAmount (tx().getFieldAmount (sfAmount));
AccountID const uDstAccountID (ctx_.tx.getAccountID (sfDestination));
STAmount const saDstAmount (ctx_.tx.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
if (bMax)
maxSourceAmount = tx().getFieldAmount (sfSendMax);
if (sendMax)
maxSourceAmount = *sendMax;
else if (saDstAmount.native ())
maxSourceAmount = saDstAmount;
else
@@ -226,7 +327,7 @@ Payment::doApply ()
saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
j_.trace <<
JLOG(j_.trace) <<
"maxSourceAmount=" << maxSourceAmount.getFullText () <<
" saDstAmount=" << saDstAmount.getFullText ();
@@ -236,64 +337,15 @@ Payment::doApply ()
if (!sleDst)
{
// Destination account does not exist.
if (!saDstAmount.native ())
{
j_.trace <<
"Delay transaction: Destination account does not exist.";
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST;
}
else if (view().open()
&& partialPaymentAllowed)
{
// You cannot fund an account with a partial payment.
// Make retry work smaller, by rejecting this.
j_.trace <<
"Delay transaction: Partial payment not allowed to create account.";
// Another transaction could create the account and then this
// transaction would succeed.
return telNO_DST_PARTIAL;
}
else if (saDstAmount < STAmount (view().fees().accountReserve(0)))
{
// accountReserve is the minimum amount that an account can have.
// Reserve is not scaled by load.
j_.trace <<
"Delay transaction: Destination account does not exist. " <<
"Insufficent payment to create account.";
// TODO: dedupe
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST_INSUF_XRP;
}
// Create the account.
sleDst = std::make_shared<SLE>(k);
sleDst->setAccountID (sfAccount, uDstAccountID);
sleDst->setFieldU32 (sfSequence, 1);
sleDst->setAccountID(sfAccount, uDstAccountID);
sleDst->setFieldU32(sfSequence, 1);
view().insert(sleDst);
}
else if ((sleDst->getFlags () & lsfRequireDestTag) &&
!tx().isFieldPresent (sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
// We didn't make this test for a newly-formed account because there's
// no way for this field to be set.
j_.trace << "Malformed transaction: DestinationTag required.";
return tecDST_TAG_NEEDED;
}
else
{
// Tell the engine that we are intending to change the the destination
// Tell the engine that we are intending to change the destination
// account. The source account gets always charged a fee so it's always
// marked as modified.
view().update (sleDst);
@@ -301,8 +353,8 @@ Payment::doApply ()
TER terResult;
bool const bRipple = bPaths || bMax || !saDstAmount.native ();
// XXX Should bMax be sufficient to imply ripple?
bool const bRipple = paths || sendMax || !saDstAmount.native ();
// XXX Should sendMax be sufficient to imply ripple?
if (bRipple)
{
@@ -310,10 +362,8 @@ Payment::doApply ()
// transitive balances.
// Copy paths into an editable class.
STPathSet spsPaths = tx().getFieldPathSet (sfPaths);
STPathSet spsPaths = ctx_.tx.getFieldPathSet (sfPaths);
try
{
path::RippleCalc::Input rcInput;
rcInput.partialPaymentAllowed = partialPaymentAllowed;
rcInput.defaultPathsAllowed = defaultPathsAllowed;
@@ -321,18 +371,6 @@ Payment::doApply ()
rcInput.deleteUnfundedOffers = true;
rcInput.isLedgerOpen = view().open();
bool pathTooBig = spsPaths.size () > MaxPathSize;
for (auto const& path : spsPaths)
if (path.size () > MaxPathLength)
pathTooBig = true;
if (rcInput.isLedgerOpen && pathTooBig)
{
terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger.
}
else
{
path::RippleCalc::Output rc;
{
PaymentSandbox pv(&view());
@@ -357,27 +395,20 @@ Payment::doApply ()
rc.actualAmountOut != saDstAmount)
{
if (deliverMin && rc.actualAmountOut <
tx().getFieldAmount (sfDeliverMin))
*deliverMin)
rc.setResult (tecPATH_PARTIAL);
else
ctx_.deliver (rc.actualAmountOut);
}
terResult = rc.result ();
}
// TODO(tom): what's going on here?
// Because of its overhead, if RippleCalc
// fails with a retry code, claim a fee
// instead. Maybe the user will be more
// careful with their path spec next time.
if (isTerRetry (terResult))
terResult = tecPATH_DRY;
}
catch (std::exception const& e)
{
j_.trace <<
"Caught throw: " << e.what ();
terResult = tefEXCEPTION;
}
}
else
{
@@ -397,13 +428,13 @@ Payment::doApply ()
// fees were charged. We want to make sure we have enough reserve
// to send. Allow final spend to use reserve for fee.
auto const mmm = std::max(reserve,
tx().getFieldAmount (sfFee).xrp ());
ctx_.tx.getFieldAmount (sfFee).xrp ());
if (mPriorBalance < saDstAmount.xrp () + mmm)
{
// Vote no. However the transaction might succeed, if applied in
// a different order.
j_.trace << "Delay transaction: Insufficient funds: " <<
JLOG(j_.trace) << "Delay transaction: Insufficient funds: " <<
" " << to_string (mPriorBalance) <<
" / " << to_string (saDstAmount.xrp () + mmm) <<
" (" << to_string (reserve) << ")";
@@ -432,7 +463,7 @@ Payment::doApply ()
if (transResultInfo (terResult, strToken, strHuman))
{
j_.trace <<
JLOG(j_.trace) <<
strToken << ": " << strHuman;
}
else

View File

@@ -48,6 +48,10 @@ public:
TER
preflight (PreflightContext const& ctx);
static
TER
preclaim(PreclaimContext const& ctx);
TER doApply () override;
};

View File

@@ -24,7 +24,7 @@
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Quality.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <ripple/ledger/View.h>
namespace ripple {
@@ -104,13 +104,60 @@ SetAccount::preflight (PreflightContext const& ctx)
}
}
auto const messageKey = tx[~sfMessageKey];
if (messageKey && messageKey->size() > PUBLIC_BYTES_MAX)
{
JLOG(j.trace) << "message key too long";
return telBAD_PUBLIC_KEY;
}
auto const domain = tx[~sfDomain];
if (domain&& domain->size() > DOMAIN_BYTES_MAX)
{
JLOG(j.trace) << "domain too long";
return telBAD_DOMAIN;
}
return preflight2(ctx);
}
TER
SetAccount::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
std::uint32_t const uTxFlags = ctx.tx.getFlags();
auto const sle = ctx.view.read(
keylet::account(id));
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag);
// legacy AccountSet flags
bool bSetRequireAuth = (uTxFlags & tfRequireAuth) || (uSetFlag == asfRequireAuth);
//
// RequireAuth
//
if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth))
{
if (!dirIsEmpty(ctx.view,
keylet::ownerDir(id)))
{
JLOG(ctx.j.trace) << "Retry: Owner directory not empty.";
return (ctx.flags & tapRETRY) ? terOWNERS : tecOWNERS;
}
}
return tesSUCCESS;
}
TER
SetAccount::doApply ()
{
std::uint32_t const uTxFlags = tx().getFlags ();
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
auto const sle = view().peek(
keylet::account(account_));
@@ -118,8 +165,8 @@ SetAccount::doApply ()
std::uint32_t const uFlagsIn = sle->getFieldU32 (sfFlags);
std::uint32_t uFlagsOut = uFlagsIn;
std::uint32_t const uSetFlag = tx().getFieldU32 (sfSetFlag);
std::uint32_t const uClearFlag = tx().getFieldU32 (sfClearFlag);
std::uint32_t const uSetFlag = ctx_.tx.getFieldU32 (sfSetFlag);
std::uint32_t const uClearFlag = ctx_.tx.getFieldU32 (sfClearFlag);
// legacy AccountSet flags
bool bSetRequireDest = (uTxFlags & TxFlag::requireDestTag) || (uSetFlag == asfRequireDest);
@@ -134,13 +181,6 @@ SetAccount::doApply ()
//
if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth))
{
if (! dirIsEmpty (view(),
keylet::ownerDir(account_)))
{
j_.trace << "Retry: Owner directory not empty.";
return (view().flags() & tapRETRY) ? terOWNERS : tecOWNERS;
}
j_.trace << "Set RequireAuth.";
uFlagsOut |= lsfRequireAuth;
}
@@ -199,7 +239,8 @@ SetAccount::doApply ()
// Prevent transaction changes until we're ready.
if (view().flags() & tapENABLE_TESTING ||
view().rules().enabled(featureMultiSign, ctx_.config.features))
view().rules().enabled(featureMultiSign,
ctx_.app.config().features))
return tecNO_ALTERNATIVE_KEY;
return tecNO_REGULAR_KEY;
@@ -277,9 +318,9 @@ SetAccount::doApply ()
//
// EmailHash
//
if (tx().isFieldPresent (sfEmailHash))
if (ctx_.tx.isFieldPresent (sfEmailHash))
{
uint128 const uHash = tx().getFieldH128 (sfEmailHash);
uint128 const uHash = ctx_.tx.getFieldH128 (sfEmailHash);
if (!uHash)
{
@@ -296,9 +337,9 @@ SetAccount::doApply ()
//
// WalletLocator
//
if (tx().isFieldPresent (sfWalletLocator))
if (ctx_.tx.isFieldPresent (sfWalletLocator))
{
uint256 const uHash = tx().getFieldH256 (sfWalletLocator);
uint256 const uHash = ctx_.tx.getFieldH256 (sfWalletLocator);
if (!uHash)
{
@@ -315,15 +356,9 @@ SetAccount::doApply ()
//
// MessageKey
//
if (tx().isFieldPresent (sfMessageKey))
if (ctx_.tx.isFieldPresent (sfMessageKey))
{
Blob const messageKey = tx().getFieldVL (sfMessageKey);
if (messageKey.size () > PUBLIC_BYTES_MAX)
{
j_.trace << "message key too long";
return telBAD_PUBLIC_KEY;
}
Blob const messageKey = ctx_.tx.getFieldVL (sfMessageKey);
if (messageKey.empty ())
{
@@ -340,15 +375,9 @@ SetAccount::doApply ()
//
// Domain
//
if (tx().isFieldPresent (sfDomain))
if (ctx_.tx.isFieldPresent (sfDomain))
{
Blob const domain = tx().getFieldVL (sfDomain);
if (domain.size () > DOMAIN_BYTES_MAX)
{
j_.trace << "domain too long";
return telBAD_DOMAIN;
}
Blob const domain = ctx_.tx.getFieldVL (sfDomain);
if (domain.empty ())
{
@@ -365,9 +394,9 @@ SetAccount::doApply ()
//
// TransferRate
//
if (tx().isFieldPresent (sfTransferRate))
if (ctx_.tx.isFieldPresent (sfTransferRate))
{
std::uint32_t uRate = tx().getFieldU32 (sfTransferRate);
std::uint32_t uRate = ctx_.tx.getFieldU32 (sfTransferRate);
if (uRate == 0 || uRate == QUALITY_ONE)
{

View File

@@ -45,6 +45,10 @@ public:
TER
preflight (PreflightContext const& ctx);
static
TER
preclaim(PreclaimContext const& ctx);
TER doApply () override;
};

View File

@@ -26,19 +26,25 @@
namespace ripple {
std::uint64_t
SetRegularKey::calculateBaseFee ()
SetRegularKey::calculateBaseFee (
PreclaimContext const& ctx)
{
auto const sle = view().peek(
keylet::account(account_));
auto const id = ctx.tx.getAccountID(sfAccount);
auto const pk =
RippleAddress::createAccountPublic(
ctx.tx.getSigningPubKey());
auto const sle = ctx.view.read(
keylet::account(id));
if ( sle
&& (! (sle->getFlags () & lsfPasswordSpent))
&& (calcAccountID(mSigningPubKey) == account_))
&& (calcAccountID(pk) == id))
{
// flag is armed and they signed with the right account
return 0;
}
return Transactor::calculateBaseFee ();
return Transactor::calculateBaseFee (ctx);
}
TER
@@ -70,10 +76,10 @@ SetRegularKey::doApply ()
if (mFeeDue == zero)
sle->setFlag (lsfPasswordSpent);
if (tx().isFieldPresent (sfRegularKey))
if (ctx_.tx.isFieldPresent (sfRegularKey))
{
sle->setAccountID (sfRegularKey,
tx().getAccountID (sfRegularKey));
ctx_.tx.getAccountID (sfRegularKey));
}
else
{

View File

@@ -30,8 +30,6 @@ namespace ripple {
class SetRegularKey
: public Transactor
{
std::uint64_t calculateBaseFee () override;
public:
SetRegularKey (ApplyContext& ctx)
: Transactor(ctx)
@@ -42,6 +40,11 @@ public:
TER
preflight (PreflightContext const& ctx);
static
std::uint64_t
calculateBaseFee (
PreclaimContext const& ctx);
TER doApply () override;
};

View File

@@ -134,7 +134,7 @@ void
SetSignerList::preCompute()
{
// Get the quorum and operation info.
auto result = determineOperation(tx(), view().flags(), j_);
auto result = determineOperation(ctx_.tx, view().flags(), j_);
assert(std::get<0>(result) == tesSUCCESS);
assert(std::get<3>(result) != unknown);

View File

@@ -23,7 +23,7 @@
#include <ripple/app/tx/impl/SetTrust.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <ripple/ledger/View.h>
namespace ripple {
@@ -87,14 +87,57 @@ SetTrust::preflight (PreflightContext const& ctx)
return preflight2 (ctx);
}
TER
SetTrust::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(
keylet::account(id));
std::uint32_t const uTxFlags = ctx.tx.getFlags();
bool const bSetAuth = (uTxFlags & tfSetfAuth);
if (bSetAuth && !(sle->getFieldU32(sfFlags) & lsfRequireAuth))
{
JLOG(ctx.j.trace) <<
"Retry: Auth not required.";
return tefNO_AUTH_REQUIRED;
}
auto const saLimitAmount = ctx.tx[sfLimitAmount];
auto const currency = saLimitAmount.getCurrency();
auto const uDstAccountID = saLimitAmount.getIssuer();
if (id == uDstAccountID)
{
// Prevent trustline to self from being created,
// unless one has somehow already been created
// (in which case doApply will clean it up).
auto const sleDelete = ctx.view.read(
keylet::line(id, uDstAccountID, currency));
if (!sleDelete)
{
JLOG(ctx.j.trace) <<
"Malformed transaction: Can not extend credit to self.";
return temDST_IS_SRC;
}
}
return tesSUCCESS;
}
TER
SetTrust::doApply ()
{
TER terResult = tesSUCCESS;
STAmount const saLimitAmount (tx().getFieldAmount (sfLimitAmount));
bool const bQualityIn (tx().isFieldPresent (sfQualityIn));
bool const bQualityOut (tx().isFieldPresent (sfQualityOut));
STAmount const saLimitAmount (ctx_.tx.getFieldAmount (sfLimitAmount));
bool const bQualityIn (ctx_.tx.isFieldPresent (sfQualityIn));
bool const bQualityOut (ctx_.tx.isFieldPresent (sfQualityOut));
Currency const currency (saLimitAmount.getCurrency ());
AccountID uDstAccountID (saLimitAmount.getIssuer ());
@@ -123,13 +166,13 @@ SetTrust::doApply ()
? XRPAmount (zero)
: view().fees().accountReserve(uOwnerCount + 1));
std::uint32_t uQualityIn (bQualityIn ? tx().getFieldU32 (sfQualityIn) : 0);
std::uint32_t uQualityOut (bQualityOut ? tx().getFieldU32 (sfQualityOut) : 0);
std::uint32_t uQualityIn (bQualityIn ? ctx_.tx.getFieldU32 (sfQualityIn) : 0);
std::uint32_t uQualityOut (bQualityOut ? ctx_.tx.getFieldU32 (sfQualityOut) : 0);
if (bQualityOut && QUALITY_ONE == uQualityOut)
uQualityOut = 0;
std::uint32_t const uTxFlags = tx().getFlags ();
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
bool const bSetAuth = (uTxFlags & tfSetfAuth);
bool const bSetNoRipple = (uTxFlags & tfSetNoRipple);
@@ -139,13 +182,6 @@ SetTrust::doApply ()
auto viewJ = ctx_.app.journal ("View");
if (bSetAuth && !(sle->getFieldU32 (sfFlags) & lsfRequireAuth))
{
j_.trace <<
"Retry: Auth not required.";
return tefNO_AUTH_REQUIRED;
}
if (account_ == uDstAccountID)
{
// The only purpose here is to allow a mistakenly created
@@ -155,21 +191,12 @@ SetTrust::doApply ()
SLE::pointer sleDelete = view().peek (
keylet::line(account_, uDstAccountID, currency));
if (sleDelete)
{
j_.warning <<
"Clearing redundant line.";
return trustDelete (view(),
sleDelete, account_, uDstAccountID, viewJ);
}
else
{
j_.trace <<
"Malformed transaction: Can not extend credit to self.";
return temDST_IS_SRC;
}
}
SLE::pointer sleDst =
view().peek (keylet::account(uDstAccountID));
@@ -405,7 +432,7 @@ SetTrust::doApply ()
(! bQualityOut || ! uQualityOut) && // Not setting quality out or setting default quality out.
(! ((view().flags() & tapENABLE_TESTING) ||
view().rules().enabled(featureTrustSetAuth,
ctx_.config.features)) || ! bSetAuth))
ctx_.app.config().features)) || ! bSetAuth))
{
j_.trace <<
"Redundant: Setting non-existent ripple line to defaults.";

View File

@@ -41,6 +41,10 @@ public:
TER
preflight (PreflightContext const& ctx);
static
TER
preclaim(PreclaimContext const& ctx);
TER doApply () override;
};

View File

@@ -32,10 +32,30 @@
namespace ripple {
/** Performs early sanity checks on the txid */
TER
preflight0(PreflightContext const& ctx)
{
auto const txID = ctx.tx.getTransactionID();
if (txID == beast::zero)
{
JLOG(ctx.j.warning) <<
"applyTransaction: transaction id may not be zero";
return temINVALID;
}
return tesSUCCESS;
}
/** Performs early sanity checks on the account and fee fields */
TER
preflight1 (PreflightContext const& ctx)
{
auto const ret = preflight0(ctx);
if (!isTesSuccess(ret))
return ret;
auto const id = ctx.tx.getAccountID(sfAccount);
if (id == zero)
{
@@ -83,62 +103,77 @@ preflight2 (PreflightContext const& ctx)
return tesSUCCESS;
}
static
XRPAmount
calculateFee(Application& app, std::uint64_t const baseFee,
Fees const& fees, ApplyFlags flags)
{
return app.getFeeTrack().scaleFeeLoad(
baseFee, fees.base, fees.units, flags & tapADMIN);
}
//------------------------------------------------------------------------------
Transactor::Transactor(
ApplyContext& ctx)
: ctx_ (ctx)
, j_ (ctx.journal)
, mHasAuthKey (false)
, mSigMaster (false)
{
}
std::uint64_t Transactor::calculateBaseFee ()
std::uint64_t Transactor::calculateBaseFee (
PreclaimContext const& ctx)
{
// Returns the fee in fee units.
// The computation has two parts:
// * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction.
std::uint64_t baseFee = view().fees().units;
std::uint64_t baseFee = ctx.view.fees().units;
// Each signer adds one more baseFee to the minimum required fee
// for the transaction.
std::uint32_t signerCount = 0;
if (tx().isFieldPresent (sfSigners))
signerCount = tx().getFieldArray (sfSigners).size();
if (ctx.tx.isFieldPresent (sfSigners))
signerCount = ctx.tx.getFieldArray (sfSigners).size();
return baseFee + (signerCount * baseFee);
}
TER Transactor::payFee ()
TER
Transactor::checkFee (PreclaimContext const& ctx, std::uint64_t baseFee)
{
auto const feePaid = tx().getFieldAmount (sfFee).xrp ();
auto const feePaid = ctx.tx[sfFee].xrp ();
if (!isLegalAmount (feePaid) || feePaid < beast::zero)
return temBAD_FEE;
auto const feeDue = ripple::calculateFee(ctx.app,
baseFee, ctx.view.fees(), ctx.flags);
// Only check fee is sufficient when the ledger is open.
if (view().open() && feePaid < mFeeDue)
if (ctx.view.open() && feePaid < feeDue)
{
JLOG(j_.trace) << "Insufficient fee paid: " <<
to_string (feePaid) << "/" << to_string (mFeeDue);
JLOG(ctx.j.trace) << "Insufficient fee paid: " <<
to_string (feePaid) << "/" << to_string (feeDue);
return telINSUF_FEE_P;
}
if (feePaid == zero)
return tesSUCCESS;
auto const sle = view().peek(
keylet::account(account_));
auto const id = ctx.tx.getAccountID(sfAccount);
auto const sle = ctx.view.read(
keylet::account(id));
auto const balance = (*sle)[sfBalance].xrp();
if (mSourceBalance < feePaid)
if (balance < feePaid)
{
JLOG(j_.trace) << "Insufficient balance:" <<
" balance=" << to_string (mSourceBalance) <<
" paid=" << to_string (feePaid);
JLOG(ctx.j.trace) << "Insufficient balance:" <<
" balance=" << to_string(balance) <<
" paid=" << to_string(feePaid);
if ((mSourceBalance > zero) && ! view().open())
if ((balance > zero) && !ctx.view.open())
{
// Closed ledger, non-zero balance, less than fee
return tecINSUFF_FEE;
@@ -147,8 +182,18 @@ TER Transactor::payFee ()
return terINSUF_FEE_B;
}
return tesSUCCESS;
}
TER Transactor::payFee ()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const sle = view().peek(
keylet::account(account_));
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back, if the transaction succeeds.
// Will only write the account back if the transaction succeeds.
mSourceBalance -= feePaid;
sle->setFieldAmount (sfBalance, mSourceBalance);
@@ -158,56 +203,76 @@ TER Transactor::payFee ()
return tesSUCCESS;
}
TER Transactor::checkSeq ()
TER
Transactor::checkSeq (PreclaimContext const& ctx)
{
auto const sle = view().peek(
keylet::account(account_));
auto const id = ctx.tx.getAccountID(sfAccount);
std::uint32_t const t_seq = tx().getSequence ();
auto const sle = ctx.view.read(
keylet::account(id));
if (!sle)
{
JLOG(ctx.j.trace) <<
"applyTransaction: delay: source account does not exist " <<
toBase58(ctx.tx.getAccountID(sfAccount));
return terNO_ACCOUNT;
}
std::uint32_t const t_seq = ctx.tx.getSequence ();
std::uint32_t const a_seq = sle->getFieldU32 (sfSequence);
if (t_seq != a_seq)
{
if (a_seq < t_seq)
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: has future sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq;
return terPRE_SEQ;
}
if (view().txExists(tx().getTransactionID ()))
if (ctx.view.txExists(ctx.tx.getTransactionID ()))
return tefALREADY;
JLOG(j_.trace) << "applyTransaction: has past sequence number " <<
JLOG(ctx.j.trace) << "applyTransaction: has past sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq;
return tefPAST_SEQ;
}
if (tx().isFieldPresent (sfAccountTxnID) &&
(sle->getFieldH256 (sfAccountTxnID) != tx().getFieldH256 (sfAccountTxnID)))
if (ctx.tx.isFieldPresent (sfAccountTxnID) &&
(sle->getFieldH256 (sfAccountTxnID) != ctx.tx.getFieldH256 (sfAccountTxnID)))
return tefWRONG_PRIOR;
if (tx().isFieldPresent (sfLastLedgerSequence) &&
(view().seq() > tx().getFieldU32 (sfLastLedgerSequence)))
if (ctx.tx.isFieldPresent (sfLastLedgerSequence) &&
(ctx.view.seq() > ctx.tx.getFieldU32 (sfLastLedgerSequence)))
return tefMAX_LEDGER;
return tesSUCCESS;
}
void
Transactor::setSeq ()
{
auto const sle = view().peek(
keylet::account(account_));
std::uint32_t const t_seq = ctx_.tx.getSequence ();
sle->setFieldU32 (sfSequence, t_seq + 1);
if (sle->isFieldPresent (sfAccountTxnID))
sle->setFieldH256 (sfAccountTxnID, tx().getTransactionID ());
return tesSUCCESS;
sle->setFieldH256 (sfAccountTxnID, ctx_.tx.getTransactionID ());
}
// check stuff before you bother to lock the ledger
void Transactor::preCompute ()
{
account_ = tx().getAccountID(sfAccount);
account_ = ctx_.tx.getAccountID(sfAccount);
assert(account_ != zero);
mSigningPubKey =
RippleAddress::createAccountPublic(
tx().getSigningPubKey());
ctx_.tx.getSigningPubKey());
}
TER Transactor::apply ()
@@ -218,34 +283,25 @@ TER Transactor::apply ()
// list one, preflight will have already a flagged a failure.
auto const sle = view().peek (keylet::account(account_));
if (sle == nullptr && account_ != zero)
{
JLOG (j_.trace) <<
"apply: source account " << toBase58(account_) << " not found.";
return terNO_ACCOUNT;
}
// sle must exist except for transactions
// that allow zero account.
assert(sle != nullptr || account_ == zero);
auto const& fees = view().fees();
mFeeDue = ctx_.app.getFeeTrack().scaleFeeLoad(
calculateBaseFee(), fees.base, fees.units, view().flags() & tapADMIN);
mFeeDue = calculateFee(ctx_.app, ctx_.baseFee,
view().fees(), view().flags());
if (sle)
{
mPriorBalance = STAmount ((*sle)[sfBalance]).xrp ();
mSourceBalance = mPriorBalance;
mHasAuthKey = sle->isFieldPresent (sfRegularKey);
auto terResult = checkSeq ();
setSeq();
auto terResult = payFee ();
if (terResult != tesSUCCESS) return terResult;
terResult = payFee ();
if (terResult != tesSUCCESS) return terResult;
terResult = checkSign ();
if (terResult != tesSUCCESS) return terResult;
checkMasterSign ();
view().update (sle);
}
@@ -253,49 +309,76 @@ TER Transactor::apply ()
return doApply ();
}
TER Transactor::checkSign ()
TER
Transactor::checkSign (PreclaimContext const& ctx)
{
// Make sure multisigning is enabled before we check for multisignatures.
if ((view().flags() & tapENABLE_TESTING) ||
(view().rules().enabled(featureMultiSign, ctx_.config.features)))
if ((ctx.flags & tapENABLE_TESTING) ||
(ctx.view.rules().enabled(featureMultiSign,
ctx.app.config().features)))
{
// If the mSigningPubKey is empty, then we must be multisigning.
if (mSigningPubKey.getAccountPublic ().empty ())
return checkMultiSign ();
auto pk =
RippleAddress::createAccountPublic(
ctx.tx.getSigningPubKey());
// If the pk is empty, then we must be multi-signing.
if (pk.getAccountPublic ().empty ())
return checkMultiSign (ctx);
}
return checkSingleSign ();
return checkSingleSign (ctx);
}
TER Transactor::checkSingleSign ()
void
Transactor::checkMasterSign ()
{
auto const sle = view().peek(keylet::account(account_));
if (! sle)
return tefFAILURE; // We really expected to find the account.
if ((view().flags() & tapENABLE_TESTING) ||
(view().rules().enabled(featureMultiSign,
ctx_.app.config().features)))
{
if (mSigningPubKey.getAccountPublic().empty())
// Multisign obviously doesn't use the master key
return;
}
mSigMaster = calcAccountID(mSigningPubKey) == account_;
}
TER
Transactor::checkSingleSign (PreclaimContext const& ctx)
{
auto const id = ctx.tx.getAccountID(sfAccount);
auto const sle = ctx.view.read(
keylet::account(id));
auto const hasAuthKey = sle->isFieldPresent (sfRegularKey);
// Consistency: Check signature
// Verify the transaction's signing public key is authorized for signing.
AccountID const idFromPubKey = calcAccountID(mSigningPubKey);
if (idFromPubKey == account_)
auto const pk =
RippleAddress::createAccountPublic(
ctx.tx.getSigningPubKey());
auto const pkAccount = calcAccountID(pk);
if (pkAccount == id)
{
// Authorized to continue.
mSigMaster = true;
if (sle->isFlag(lsfDisableMaster))
return tefMASTER_DISABLED;
}
else if (mHasAuthKey && (idFromPubKey == sle->getAccountID (sfRegularKey)))
else if (hasAuthKey &&
(pkAccount == sle->getAccountID (sfRegularKey)))
{
// Authorized to continue.
}
else if (mHasAuthKey)
else if (hasAuthKey)
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Delay: Not authorized to use account.";
return tefBAD_AUTH;
}
else
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Invalid: Not authorized to use account.";
return tefBAD_AUTH_MASTER;
}
@@ -303,15 +386,16 @@ TER Transactor::checkSingleSign ()
return tesSUCCESS;
}
TER Transactor::checkMultiSign ()
TER Transactor::checkMultiSign (PreclaimContext const& ctx)
{
auto const id = ctx.tx.getAccountID(sfAccount);
// Get mTxnAccountID's SignerList and Quorum.
std::shared_ptr<STLedgerEntry const> sleAccountSigners =
view().read (keylet::signers(account_));
ctx.view.read (keylet::signers(id));
// If the signer list doesn't exist the account is not multi-signing.
if (!sleAccountSigners)
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Invalid: Not a multi-signing account.";
return tefNOT_MULTI_SIGNING;
}
@@ -322,12 +406,12 @@ TER Transactor::checkMultiSign ()
assert (sleAccountSigners->getFieldU32 (sfSignerListID) == 0);
auto accountSigners =
SignerEntries::deserialize (*sleAccountSigners, j_, "ledger");
SignerEntries::deserialize (*sleAccountSigners, ctx.j, "ledger");
if (accountSigners.second != tesSUCCESS)
return accountSigners.second;
// Get the array of transaction signers.
STArray const& txSigners (tx().getFieldArray (sfSigners));
STArray const& txSigners (ctx.tx.getFieldArray (sfSigners));
// Walk the accountSigners performing a variety of checks and see if
// the quorum is met.
@@ -346,7 +430,7 @@ TER Transactor::checkMultiSign ()
{
if (++iter == accountSigners.first.end ())
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Invalid SigningAccount.Account.";
return tefBAD_SIGNATURE;
}
@@ -354,7 +438,7 @@ TER Transactor::checkMultiSign ()
if (iter->account != txSignerAcctID)
{
// The SigningAccount is not in the SignerEntries.
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Invalid SigningAccount.Account.";
return tefBAD_SIGNATURE;
}
@@ -391,8 +475,8 @@ TER Transactor::checkMultiSign ()
// In any of these cases we need to know whether the account is in
// the ledger. Determine that now.
std::shared_ptr<STLedgerEntry const> sleTxSignerRoot =
view().read (keylet::account(txSignerAcctID));
auto sleTxSignerRoot =
ctx.view.read (keylet::account(txSignerAcctID));
if (signingAcctIDFromPubKey == txSignerAcctID)
{
@@ -405,7 +489,7 @@ TER Transactor::checkMultiSign ()
if (signerAccountFlags & lsfDisableMaster)
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Signer:Account lsfDisableMaster.";
return tefMASTER_DISABLED;
}
@@ -417,21 +501,21 @@ TER Transactor::checkMultiSign ()
// Public key must hash to the account's regular key.
if (!sleTxSignerRoot)
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Non-phantom signer lacks account root.";
return tefBAD_SIGNATURE;
}
if (!sleTxSignerRoot->isFieldPresent (sfRegularKey))
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Account lacks RegularKey.";
return tefBAD_SIGNATURE;
}
if (signingAcctIDFromPubKey !=
sleTxSignerRoot->getAccountID (sfRegularKey))
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Account doesn't match RegularKey.";
return tefBAD_SIGNATURE;
}
@@ -443,7 +527,7 @@ TER Transactor::checkMultiSign ()
// Cannot perform transaction if quorum is not met.
if (weightSum < sleAccountSigners->getFieldU32 (sfSignerQuorum))
{
JLOG(j_.trace) <<
JLOG(ctx.j.trace) <<
"applyTransaction: Signers failed to meet quorum.";
return tefBAD_QUORUM;
}
@@ -494,47 +578,33 @@ Transactor::operator()()
JLOG(j_.trace) <<
"applyTransaction>";
uint256 const& txID = tx().getTransactionID ();
if (!txID)
{
JLOG(j_.warning) <<
"applyTransaction: transaction id may not be zero";
auto const result =
std::make_pair(temINVALID_FLAG, false);
log(result, j_);
return result;
}
auto const txID = ctx_.tx.getTransactionID ();
#ifdef BEAST_DEBUG
{
Serializer ser;
tx().add (ser);
ctx_.tx.add (ser);
SerialIter sit(ser.slice());
STTx s2 (sit);
if (! s2.isEquivalent(tx()))
if (! s2.isEquivalent(ctx_.tx))
{
JLOG(j_.fatal) <<
"Transaction serdes mismatch";
JLOG(j_.info) << to_string(tx().getJson (0));
JLOG(j_.info) << to_string(ctx_.tx.getJson (0));
JLOG(j_.fatal) << s2.getJson (0);
assert (false);
}
}
#endif
TER terResult = apply();
auto terResult = ctx_.preclaimResult;
if (terResult == tesSUCCESS)
terResult = apply();
if (terResult == temUNKNOWN)
{
JLOG(j_.warning) <<
"applyTransaction: Invalid transaction: unknown transaction type";
auto const result =
std::make_pair(temUNKNOWN, false);
log(result, j_);
return result;
}
// No transaction can return temUNKNOWN from apply,
// and it can't be passed in from a preclaim.
assert(terResult != temUNKNOWN);
if (j_.debug)
{
@@ -550,7 +620,7 @@ Transactor::operator()()
}
bool didApply = isTesSuccess (terResult);
auto fee = tx().getFieldAmount(sfFee).xrp ();
auto fee = ctx_.tx.getFieldAmount(sfFee).xrp ();
if (ctx_.size() > 5200)
terResult = tecOVERSIZE;
@@ -589,32 +659,19 @@ Transactor::operator()()
ctx_.discard();
auto const txnAcct = view().peek(
keylet::account(tx().getAccountID(sfAccount)));
keylet::account(ctx_.tx.getAccountID(sfAccount)));
if (txnAcct)
{
std::uint32_t t_seq = tx().getSequence ();
std::uint32_t t_seq = ctx_.tx.getSequence ();
std::uint32_t a_seq = txnAcct->getFieldU32 (sfSequence);
if (a_seq < t_seq)
terResult = terPRE_SEQ;
else if (a_seq > t_seq)
terResult = tefPAST_SEQ;
else
{
auto const balance = txnAcct->getFieldAmount (sfBalance).xrp ();
// balance should have already been
// checked in checkFee / preFlight.
assert(balance != zero && (!view().open() || balance >= fee));
// We retry/reject the transaction if the account
// balance is zero or we're applying against an open
// ledger and the balance is less than the fee
if ((balance == zero) ||
(view().open() && (balance < fee)))
{
// Account has no funds or ledger is open
terResult = terINSUF_FEE_B;
}
else
{
if (fee > balance)
fee = balance;
txnAcct->setFieldAmount (sfBalance, balance - fee);
@@ -626,13 +683,6 @@ Transactor::operator()()
view().update (txnAcct);
didApply = true;
}
}
}
else
{
terResult = terNO_ACCOUNT;
}
}
else if (!didApply)
{
JLOG(j_.debug) << "Not applying transaction " << txID;

View File

@@ -51,6 +51,66 @@ public:
}
};
struct PreflightResult
{
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. */
struct PreclaimContext
{
public:
Application& app;
ReadView const& view;
TER preflightResult;
STTx const& tx;
ApplyFlags flags;
beast::Journal j;
PreclaimContext(Application& app_, ReadView const& view_,
TER preflightResult_, STTx const& tx_,
ApplyFlags flags_, beast::Journal j_ = {})
: app(app_)
, view(view_)
, preflightResult(preflightResult_)
, tx(tx_)
, flags(flags_)
, j(j_)
{
}
};
struct PreclaimResult
{
public:
PreclaimContext const ctx;
TER const ter;
std::uint64_t const baseFee;
PreclaimResult(PreclaimContext const& ctx_,
TER ter_, std::uint64_t const& baseFee_)
: ctx(ctx_)
, ter(ter_)
, baseFee(baseFee_)
{
}
PreclaimResult(PreclaimContext const& ctx_,
std::pair<TER, std::uint64_t> const& result)
: PreclaimResult(ctx_, result.first, result.second)
{
}
};
class Transactor
{
protected:
@@ -61,7 +121,6 @@ protected:
XRPAmount mFeeDue;
XRPAmount mPriorBalance; // Balance before fees.
XRPAmount mSourceBalance; // Balance after fees.
bool mHasAuthKey;
bool mSigMaster;
RippleAddress mSigningPubKey;
@@ -82,11 +141,43 @@ public:
return ctx_.view();
}
STTx const&
tx() const
/////////////////////////////////////////////////////
/*
These static functions are called from invoke_preclaim<Tx>
using name hiding to accomplish compile-time polymorphism,
so derived classes can override for different or extra
functionality. Use with care, as these are not really
virtual and so don't have the compiler-time protection that
comes with it.
*/
static
TER
checkSeq (PreclaimContext const& ctx);
static
TER
checkFee (PreclaimContext const& ctx, std::uint64_t baseFee);
static
TER
checkSign (PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load.
static
std::uint64_t
calculateBaseFee (
PreclaimContext const& ctx);
static
TER
preclaim(PreclaimContext const &ctx)
{
return ctx_.tx;
// Most transactors do nothing
// after checkSeq/Fee/Sign.
return tesSUCCESS;
}
/////////////////////////////////////////////////////
protected:
TER
@@ -95,21 +186,22 @@ protected:
explicit
Transactor (ApplyContext& ctx);
// Returns the fee in fee units, not scaled for load.
virtual std::uint64_t calculateBaseFee ();
virtual void preCompute();
virtual TER doApply () = 0;
private:
TER checkSeq ();
TER checkSign ();
void setSeq ();
TER payFee ();
TER checkSingleSign ();
TER checkMultiSign ();
void checkMasterSign ();
static TER checkSingleSign (PreclaimContext const& ctx);
static TER checkMultiSign (PreclaimContext const& ctx);
};
/** Performs early sanity checks on the txid */
TER
preflight0(PreflightContext const& ctx);
/** Performs early sanity checks on the account and fee fields */
TER
preflight1 (PreflightContext const& ctx);

View File

@@ -19,6 +19,7 @@
#include <BeastConfig.h>
#include <ripple/app/tx/apply.h>
#include <ripple/app/tx/impl/applyImpl.h>
#include <ripple/app/tx/impl/ApplyContext.h>
#include <ripple/app/tx/impl/CancelOffer.h>
#include <ripple/app/tx/impl/CancelTicket.h>
@@ -55,10 +56,82 @@ invoke_preflight (PreflightContext const& 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)
@@ -80,57 +153,108 @@ invoke_apply (ApplyContext& ctx)
case ttAMENDMENT:
case ttFEE: { Change p(ctx); return p(); }
default:
assert(false);
return { temUNKNOWN, false };
}
}
//------------------------------------------------------------------------------
TER
PreflightResult
preflight (Rules const& rules, STTx const& tx,
ApplyFlags flags, SigVerify verify,
Config const& config, beast::Journal j)
{
PreflightContext const pfctx(tx,
rules, flags, verify, config, j);
try
{
PreflightContext pfctx(tx,
rules, flags, verify, config, j);
return invoke_preflight(pfctx);
return{ pfctx, invoke_preflight(pfctx) };
}
catch (std::exception const& e)
{
JLOG(j.fatal) <<
"apply: " << e.what();
return tefEXCEPTION;
return{ pfctx, tefEXCEPTION };
}
catch (...)
{
JLOG(j.fatal) <<
"apply: <unknown exception>";
return tefEXCEPTION;
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(view.rules(),
preflightResult.ctx.tx, preflightResult.ctx.flags,
preflightResult.ctx.verify, preflightResult.ctx.config,
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(Application& app, OpenView& view,
STTx const& tx, ApplyFlags flags,
Config const& config, beast::Journal j)
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
{
ApplyContext ctx(app, view,
tx, flags, config, j);
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(j.fatal) <<
JLOG(preclaimResult.ctx.j.fatal) <<
"apply: " << e.what();
return { tefEXCEPTION, false };
}
catch (...)
{
JLOG(j.fatal) <<
JLOG(preclaimResult.ctx.j.fatal) <<
"apply: <unknown exception>";
return { tefEXCEPTION, false };
}
@@ -144,9 +268,8 @@ apply (Application& app, OpenView& view,
{
auto pfresult = preflight(view.rules(),
tx, flags, verify, config, j);
if (pfresult != tesSUCCESS)
return { pfresult, false };
return doapply(app, view, tx, flags, config, j);
auto pcresult = preclaim(pfresult, app, view);
return doApply(pcresult, app, view);
}
} // ripple

View File

@@ -0,0 +1,79 @@
//------------------------------------------------------------------------------
/*
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(Rules const& rules, STTx const& tx,
ApplyFlags flags, SigVerify verify,
Config const& config, 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