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