diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 45c6727218..1be97fbee6 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -1745,6 +1745,8 @@
+
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 05386ba033..82f225507a 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -2487,6 +2487,9 @@
ripple\app\tx\impl
+
+ ripple\app\tx\impl
+
ripple\app\tx\impl
diff --git a/src/ripple/app/tx/apply.h b/src/ripple/app/tx/apply.h
index ca1cc8338f..9164f64528 100644
--- a/src/ripple/app/tx/apply.h
+++ b/src/ripple/app/tx/apply.h
@@ -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
-doapply(Application& app, OpenView& view,
- STTx const& tx, ApplyFlags flags,
- Config const& config, beast::Journal j);
-
/** Apply a transaction to a ReadView.
Throws:
diff --git a/src/ripple/app/tx/impl/ApplyContext.cpp b/src/ripple/app/tx/impl/ApplyContext.cpp
index 8039805d90..f18e90fa7f 100644
--- a/src/ripple/app/tx/impl/ApplyContext.cpp
+++ b/src/ripple/app/tx/impl/ApplyContext.cpp
@@ -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)
diff --git a/src/ripple/app/tx/impl/ApplyContext.h b/src/ripple/app/tx/impl/ApplyContext.h
index 95a6d581cd..5a2fc2a517 100644
--- a/src/ripple/app/tx/impl/ApplyContext.h
+++ b/src/ripple/app/tx/impl/ApplyContext.h
@@ -39,13 +39,14 @@ class ApplyContext
public:
explicit
ApplyContext (Application& app, OpenView& base,
- STTx const& tx, ApplyFlags flags,
- Config const& config,
- beast::Journal = {});
+ 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&
diff --git a/src/ripple/app/tx/impl/CancelOffer.cpp b/src/ripple/app/tx/impl/CancelOffer.cpp
index 17c8a98a7f..e20b213257 100644
--- a/src/ripple/app/tx/impl/CancelOffer.cpp
+++ b/src/ripple/app/tx/impl/CancelOffer.cpp
@@ -20,8 +20,7 @@
#include
#include
#include
-#include
-#include
+#include
#include
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;
}
diff --git a/src/ripple/app/tx/impl/CancelOffer.h b/src/ripple/app/tx/impl/CancelOffer.h
index b673306cd7..c471d72fba 100644
--- a/src/ripple/app/tx/impl/CancelOffer.h
+++ b/src/ripple/app/tx/impl/CancelOffer.h
@@ -40,6 +40,10 @@ public:
TER
preflight (PreflightContext const& ctx);
+ static
+ TER
+ preclaim(PreclaimContext const& ctx);
+
TER doApply () override;
};
diff --git a/src/ripple/app/tx/impl/CancelTicket.cpp b/src/ripple/app/tx/impl/CancelTicket.cpp
index ab147b7138..9da1f87dab 100644
--- a/src/ripple/app/tx/impl/CancelTicket.cpp
+++ b/src/ripple/app/tx/impl/CancelTicket.cpp
@@ -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?
diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp
index 89693c1365..36e7b967ab 100644
--- a/src/ripple/app/tx/impl/Change.cpp
+++ b/src/ripple/app/tx/impl/Change.cpp
@@ -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)
- return applyFee ();
-
- return temUNKNOWN;
+ assert(ctx_.tx.getTxnType() == ttFEE);
+ return applyFee ();
}
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);
diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h
index 14d4c25336..2a9a53f5bc 100644
--- a/src/ripple/app/tx/impl/Change.h
+++ b/src/ripple/app/tx/impl/Change.h
@@ -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;
- }
};
}
diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp
index aafbf881db..73c305d692 100644
--- a/src/ripple/app/tx/impl/CreateOffer.cpp
+++ b/src/ripple/app/tx/impl/CreateOffer.cpp
@@ -22,7 +22,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -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
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)
diff --git a/src/ripple/app/tx/impl/CreateOffer.h b/src/ripple/app/tx/impl/CreateOffer.h
index d0072aad13..695b193517 100644
--- a/src/ripple/app/tx/impl/CreateOffer.h
+++ b/src/ripple/app/tx/impl/CreateOffer.h
@@ -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);
diff --git a/src/ripple/app/tx/impl/CreateTicket.cpp b/src/ripple/app/tx/impl/CreateTicket.cpp
index be472356c2..f4ec8f89ee 100644
--- a/src/ripple/app/tx/impl/CreateTicket.cpp
+++ b/src/ripple/app/tx/impl/CreateTicket.cpp
@@ -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(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));
diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp
index b39e8f92c6..e2cf85ed26 100644
--- a/src/ripple/app/tx/impl/Payment.cpp
+++ b/src/ripple/app/tx/impl/Payment.cpp
@@ -21,7 +21,7 @@
#include
#include
#include
-#include
+#include
#include
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(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,74 +362,53 @@ 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;
+ rcInput.limitQuality = limitQuality;
+ rcInput.deleteUnfundedOffers = true;
+ rcInput.isLedgerOpen = view().open();
+
+ path::RippleCalc::Output rc;
{
- path::RippleCalc::Input rcInput;
- rcInput.partialPaymentAllowed = partialPaymentAllowed;
- rcInput.defaultPathsAllowed = defaultPathsAllowed;
- rcInput.limitQuality = limitQuality;
- rcInput.deleteUnfundedOffers = true;
- rcInput.isLedgerOpen = view().open();
+ PaymentSandbox pv(&view());
+ rc = path::RippleCalc::rippleCalculate (
+ pv,
+ maxSourceAmount,
+ saDstAmount,
+ uDstAccountID,
+ account_,
+ spsPaths,
+ ctx_.app.logs(),
+ &rcInput);
+ // VFALCO NOTE We might not need to apply, depending
+ // on the TER. But always applying *should*
+ // be safe.
+ pv.apply(ctx_.rawView());
+ }
- 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.
- }
+ // TODO: is this right? If the amount is the correct amount, was
+ // the delivered amount previously set?
+ if (rc.result () == tesSUCCESS &&
+ rc.actualAmountOut != saDstAmount)
+ {
+ if (deliverMin && rc.actualAmountOut <
+ *deliverMin)
+ rc.setResult (tecPATH_PARTIAL);
else
- {
- path::RippleCalc::Output rc;
- {
- PaymentSandbox pv(&view());
- rc = path::RippleCalc::rippleCalculate (
- pv,
- maxSourceAmount,
- saDstAmount,
- uDstAccountID,
- account_,
- spsPaths,
- ctx_.app.logs(),
- &rcInput);
- // VFALCO NOTE We might not need to apply, depending
- // on the TER. But always applying *should*
- // be safe.
- pv.apply(ctx_.rawView());
- }
-
- // TODO: is this right? If the amount is the correct amount, was
- // the delivered amount previously set?
- if (rc.result () == tesSUCCESS &&
- rc.actualAmountOut != saDstAmount)
- {
- if (deliverMin && rc.actualAmountOut <
- tx().getFieldAmount (sfDeliverMin))
- rc.setResult (tecPATH_PARTIAL);
- else
- ctx_.deliver (rc.actualAmountOut);
- }
-
- terResult = rc.result ();
- }
-
- // TODO(tom): what's going on here?
- if (isTerRetry (terResult))
- terResult = tecPATH_DRY;
-
+ ctx_.deliver (rc.actualAmountOut);
}
- catch (std::exception const& e)
- {
- j_.trace <<
- "Caught throw: " << e.what ();
- terResult = tefEXCEPTION;
- }
+ terResult = rc.result ();
+
+ // 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;
}
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
diff --git a/src/ripple/app/tx/impl/Payment.h b/src/ripple/app/tx/impl/Payment.h
index 86eb2f8fb6..f7fb67c96a 100644
--- a/src/ripple/app/tx/impl/Payment.h
+++ b/src/ripple/app/tx/impl/Payment.h
@@ -48,6 +48,10 @@ public:
TER
preflight (PreflightContext const& ctx);
+ static
+ TER
+ preclaim(PreclaimContext const& ctx);
+
TER doApply () override;
};
diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp
index cc560c7703..fab6cc10ae 100644
--- a/src/ripple/app/tx/impl/SetAccount.cpp
+++ b/src/ripple/app/tx/impl/SetAccount.cpp
@@ -24,7 +24,7 @@
#include
#include
#include
-#include
+#include
#include
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,8 +239,9 @@ SetAccount::doApply ()
// Prevent transaction changes until we're ready.
if (view().flags() & tapENABLE_TESTING ||
- view().rules().enabled(featureMultiSign, ctx_.config.features))
- return tecNO_ALTERNATIVE_KEY;
+ 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)
{
diff --git a/src/ripple/app/tx/impl/SetAccount.h b/src/ripple/app/tx/impl/SetAccount.h
index 292f3fac7e..4b6d6a95ff 100644
--- a/src/ripple/app/tx/impl/SetAccount.h
+++ b/src/ripple/app/tx/impl/SetAccount.h
@@ -45,6 +45,10 @@ public:
TER
preflight (PreflightContext const& ctx);
+ static
+ TER
+ preclaim(PreclaimContext const& ctx);
+
TER doApply () override;
};
diff --git a/src/ripple/app/tx/impl/SetRegularKey.cpp b/src/ripple/app/tx/impl/SetRegularKey.cpp
index 8cd67cd001..2db6b5121a 100644
--- a/src/ripple/app/tx/impl/SetRegularKey.cpp
+++ b/src/ripple/app/tx/impl/SetRegularKey.cpp
@@ -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
{
diff --git a/src/ripple/app/tx/impl/SetRegularKey.h b/src/ripple/app/tx/impl/SetRegularKey.h
index 67b8978656..057a9aa71a 100644
--- a/src/ripple/app/tx/impl/SetRegularKey.h
+++ b/src/ripple/app/tx/impl/SetRegularKey.h
@@ -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;
};
diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/ripple/app/tx/impl/SetSignerList.cpp
index fa6b5110bc..dc05ac8a93 100644
--- a/src/ripple/app/tx/impl/SetSignerList.cpp
+++ b/src/ripple/app/tx/impl/SetSignerList.cpp
@@ -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);
diff --git a/src/ripple/app/tx/impl/SetTrust.cpp b/src/ripple/app/tx/impl/SetTrust.cpp
index 50a39a6e6b..4cf7948015 100644
--- a/src/ripple/app/tx/impl/SetTrust.cpp
+++ b/src/ripple/app/tx/impl/SetTrust.cpp
@@ -23,7 +23,7 @@
#include
#include
#include
-#include
+#include
#include
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,20 +191,11 @@ SetTrust::doApply ()
SLE::pointer sleDelete = view().peek (
keylet::line(account_, uDstAccountID, currency));
- if (sleDelete)
- {
- j_.warning <<
- "Clearing redundant line.";
+ 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;
- }
+ return trustDelete (view(),
+ sleDelete, account_, uDstAccountID, viewJ);
}
SLE::pointer sleDst =
@@ -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.";
diff --git a/src/ripple/app/tx/impl/SetTrust.h b/src/ripple/app/tx/impl/SetTrust.h
index 6503b16829..9ec7936e66 100644
--- a/src/ripple/app/tx/impl/SetTrust.h
+++ b/src/ripple/app/tx/impl/SetTrust.h
@@ -41,6 +41,10 @@ public:
TER
preflight (PreflightContext const& ctx);
+ static
+ TER
+ preclaim(PreclaimContext const& ctx);
+
TER doApply () override;
};
diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp
index 9aaca259c7..e97bea1e3c 100644
--- a/src/ripple/app/tx/impl/Transactor.cpp
+++ b/src/ripple/app/tx/impl/Transactor.cpp
@@ -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 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 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,49 +659,29 @@ 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 a_seq = txnAcct->getFieldU32 (sfSequence);
+ 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 ();
+ auto const balance = txnAcct->getFieldAmount (sfBalance).xrp ();
- // 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);
- txnAcct->setFieldU32 (sfSequence, t_seq + 1);
+ // 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 (fee > balance)
+ fee = balance;
+ txnAcct->setFieldAmount (sfBalance, balance - fee);
+ txnAcct->setFieldU32 (sfSequence, t_seq + 1);
- if (terResult == tecOVERSIZE)
- removeUnfundedOffers (view(), removedOffers, ctx_.app.journal ("View"));
+ if (terResult == tecOVERSIZE)
+ removeUnfundedOffers (view(), removedOffers, ctx_.app.journal ("View"));
- view().update (txnAcct);
- didApply = true;
- }
- }
- }
- else
- {
- terResult = terNO_ACCOUNT;
- }
+ view().update (txnAcct);
+ didApply = true;
}
else if (!didApply)
{
diff --git a/src/ripple/app/tx/impl/Transactor.h b/src/ripple/app/tx/impl/Transactor.h
index 65245ddb12..03d84363b3 100644
--- a/src/ripple/app/tx/impl/Transactor.h
+++ b/src/ripple/app/tx/impl/Transactor.h
@@ -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 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
+ 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);
diff --git a/src/ripple/app/tx/impl/apply.cpp b/src/ripple/app/tx/impl/apply.cpp
index d26ad34677..510abba7f9 100644
--- a/src/ripple/app/tx/impl/apply.cpp
+++ b/src/ripple/app/tx/impl/apply.cpp
@@ -19,6 +19,7 @@
#include
#include
+#include
#include
#include
#include
@@ -55,10 +56,82 @@ invoke_preflight (PreflightContext const& ctx)
case ttAMENDMENT:
case ttFEE: return Change ::preflight(ctx);
default:
+ assert(false);
return temUNKNOWN;
}
}
+/*
+ invoke_preclaim uses name hiding to accomplish
+ compile-time polymorphism of (presumably) static
+ class functions for Transactor and derived classes.
+*/
+template
+static
+std::pair
+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
+invoke_preclaim (PreclaimContext const& ctx)
+{
+ switch(ctx.tx.getTxnType())
+ {
+ case ttACCOUNT_SET: return invoke_preclaim(ctx);
+ case ttOFFER_CANCEL: return invoke_preclaim(ctx);
+ case ttOFFER_CREATE: return invoke_preclaim(ctx);
+ case ttPAYMENT: return invoke_preclaim(ctx);
+ case ttSUSPAY_CREATE: return invoke_preclaim(ctx);
+ case ttSUSPAY_FINISH: return invoke_preclaim(ctx);
+ case ttSUSPAY_CANCEL: return invoke_preclaim(ctx);
+ case ttREGULAR_KEY_SET: return invoke_preclaim(ctx);
+ case ttSIGNER_LIST_SET: return invoke_preclaim(ctx);
+ case ttTICKET_CANCEL: return invoke_preclaim(ctx);
+ case ttTICKET_CREATE: return invoke_preclaim(ctx);
+ case ttTRUST_SET: return invoke_preclaim(ctx);
+ case ttAMENDMENT:
+ case ttFEE: return invoke_preclaim(ctx);
+ default:
+ assert(false);
+ return { temUNKNOWN, 0 };
+ }
+}
+
static
std::pair
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: ";
- return tefEXCEPTION;
+ return{ pfctx, tefEXCEPTION };
+ }
+}
+
+PreclaimResult
+preclaim (PreflightResult const& preflightResult,
+ Application& app, OpenView const& view)
+{
+ boost::optional 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: ";
+ return{ *ctx, tefEXCEPTION, 0 };
}
}
std::pair
-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: ";
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
diff --git a/src/ripple/app/tx/impl/applyImpl.h b/src/ripple/app/tx/impl/applyImpl.h
new file mode 100644
index 0000000000..f27361c4f5
--- /dev/null
+++ b/src/ripple/app/tx/impl/applyImpl.h
@@ -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
+doApply(PreclaimResult const& preclaimResult,
+ Application& app, OpenView& view);
+
+}
+
+#endif