mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 01:07:57 +00:00
Add preclaim functionality to transactors:
The preclaim() function performs static validity analysis of transactions using limited information from a ledger.
This commit is contained in:
committed by
Vinnie Falco
parent
ad8e9a76ed
commit
9b80081122
@@ -1745,6 +1745,8 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -2487,6 +2487,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\ApplyContext.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\applyImpl.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\BookTip.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -32,33 +32,6 @@ namespace ripple {
|
||||
|
||||
class Application;
|
||||
|
||||
/** Gate a transaction based on static information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that do not require a ledger.
|
||||
|
||||
@return The TER code (a `tem` or tesSUCCESS)
|
||||
*/
|
||||
TER
|
||||
preflight (Rules const& rules, STTx const& tx,
|
||||
ApplyFlags flags, SigVerify verify,
|
||||
Config const& config, beast::Journal j);
|
||||
|
||||
/** Apply a prechecked transaction to an OpenView.
|
||||
|
||||
See also: apply()
|
||||
|
||||
Precondition: The transaction has been checked
|
||||
and validated using the above function(s).
|
||||
|
||||
@return A pair with the TER and a bool indicating
|
||||
whether or not the transaction was applied.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
doapply(Application& app, OpenView& view,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
Config const& config, beast::Journal j);
|
||||
|
||||
/** Apply a transaction to a ReadView.
|
||||
|
||||
Throws:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -39,13 +39,14 @@ class ApplyContext
|
||||
public:
|
||||
explicit
|
||||
ApplyContext (Application& app, OpenView& base,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
Config const& config,
|
||||
STTx const& tx, TER preclaimResult,
|
||||
std::uint64_t baseFee, ApplyFlags flags,
|
||||
beast::Journal = {});
|
||||
|
||||
Application& app;
|
||||
STTx const& tx;
|
||||
Config const& config;
|
||||
TER const preclaimResult;
|
||||
std::uint64_t const baseFee;
|
||||
beast::Journal const journal;
|
||||
|
||||
ApplyView&
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -56,31 +55,46 @@ CancelOffer::preflight (PreflightContext const& ctx)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
CancelOffer::doApply ()
|
||||
CancelOffer::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
std::uint32_t const uOfferSequence = tx().getFieldU32 (sfOfferSequence);
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
auto const offerSequence = ctx.tx[sfOfferSequence];
|
||||
|
||||
auto const sle = view().read(
|
||||
keylet::account(account_));
|
||||
if (sle->getFieldU32 (sfSequence) - 1 <= uOfferSequence)
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
|
||||
if ((*sle)[sfSequence] <= offerSequence)
|
||||
{
|
||||
j_.trace << "Malformed transaction: " <<
|
||||
"Sequence " << uOfferSequence << " is invalid.";
|
||||
ctx.j.trace << "Malformed transaction: " <<
|
||||
"Sequence " << offerSequence << " is invalid.";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
uint256 const offerIndex (getOfferIndex (account_, uOfferSequence));
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
CancelOffer::doApply ()
|
||||
{
|
||||
auto const offerSequence = ctx_.tx[sfOfferSequence];
|
||||
|
||||
auto const sle = view().read(
|
||||
keylet::account(account_));
|
||||
|
||||
uint256 const offerIndex (getOfferIndex (account_, offerSequence));
|
||||
|
||||
auto sleOffer = view().peek (
|
||||
keylet::offer(offerIndex));
|
||||
|
||||
if (sleOffer)
|
||||
{
|
||||
j_.debug << "Trying to cancel offer #" << uOfferSequence;
|
||||
return offerDelete (view(), sleOffer, ctx_.app.journal ("View"));
|
||||
JLOG(j_.debug) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete (view(), sleOffer, ctx_.app.journal("View"));
|
||||
}
|
||||
|
||||
j_.debug << "Offer #" << uOfferSequence << " can't be found.";
|
||||
JLOG(j_.debug) << "Offer #" << offerSequence << " can't be found.";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ public:
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER doApply () override;
|
||||
};
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace ripple {
|
||||
TER
|
||||
Change::preflight (PreflightContext const& ctx)
|
||||
{
|
||||
auto const ret = preflight0(ctx);
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto account = ctx.tx.getAccountID(sfAccount);
|
||||
if (account != zero)
|
||||
{
|
||||
@@ -62,36 +66,45 @@ Change::preflight (PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
TER
|
||||
Change::doApply()
|
||||
Change::preclaim(PreclaimContext const &ctx)
|
||||
{
|
||||
// If tapOPEN_LEDGER is resurrected into ApplyFlags,
|
||||
// this block can be moved to preflight.
|
||||
if (view().open())
|
||||
if (ctx.view.open())
|
||||
{
|
||||
j_.warning << "Change transaction against open ledger";
|
||||
ctx.j.warning << "Change transaction against open ledger";
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
if (tx().getTxnType () == ttAMENDMENT)
|
||||
if (ctx.tx.getTxnType() != ttAMENDMENT
|
||||
&& ctx.tx.getTxnType() != ttFEE)
|
||||
return temUNKNOWN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
TER
|
||||
Change::doApply()
|
||||
{
|
||||
if (ctx_.tx.getTxnType () == ttAMENDMENT)
|
||||
return applyAmendment ();
|
||||
|
||||
if (tx().getTxnType () == ttFEE)
|
||||
assert(ctx_.tx.getTxnType() == ttFEE);
|
||||
return applyFee ();
|
||||
|
||||
return temUNKNOWN;
|
||||
}
|
||||
|
||||
void
|
||||
Change::preCompute()
|
||||
{
|
||||
account_ = tx().getAccountID(sfAccount);
|
||||
account_ = ctx_.tx.getAccountID(sfAccount);
|
||||
assert(account_ == zero);
|
||||
}
|
||||
|
||||
TER
|
||||
Change::applyAmendment()
|
||||
{
|
||||
uint256 amendment (tx().getFieldH256 (sfAmendment));
|
||||
uint256 amendment (ctx_.tx.getFieldH256 (sfAmendment));
|
||||
|
||||
auto const k = keylet::amendments();
|
||||
|
||||
@@ -111,7 +124,7 @@ Change::applyAmendment()
|
||||
amendment) != amendments.end ())
|
||||
return tefALREADY;
|
||||
|
||||
auto flags = tx().getFlags ();
|
||||
auto flags = ctx_.tx.getFlags ();
|
||||
|
||||
const bool gotMajority = (flags & tfGotMajority) != 0;
|
||||
const bool lostMajority = (flags & tfLostMajority) != 0;
|
||||
@@ -193,13 +206,13 @@ Change::applyFee()
|
||||
// "Previous fee object: " << feeObject->getJson (0);
|
||||
|
||||
feeObject->setFieldU64 (
|
||||
sfBaseFee, tx().getFieldU64 (sfBaseFee));
|
||||
sfBaseFee, ctx_.tx.getFieldU64 (sfBaseFee));
|
||||
feeObject->setFieldU32 (
|
||||
sfReferenceFeeUnits, tx().getFieldU32 (sfReferenceFeeUnits));
|
||||
sfReferenceFeeUnits, ctx_.tx.getFieldU32 (sfReferenceFeeUnits));
|
||||
feeObject->setFieldU32 (
|
||||
sfReserveBase, tx().getFieldU32 (sfReserveBase));
|
||||
sfReserveBase, ctx_.tx.getFieldU32 (sfReserveBase));
|
||||
feeObject->setFieldU32 (
|
||||
sfReserveIncrement, tx().getFieldU32 (sfReserveIncrement));
|
||||
sfReserveIncrement, ctx_.tx.getFieldU32 (sfReserveIncrement));
|
||||
|
||||
view().update (feeObject);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <ripple/app/tx/impl/OfferStream.h>
|
||||
#include <ripple/app/tx/impl/Taker.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/protocol/Quality.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/ledger/Sandbox.h>
|
||||
@@ -80,8 +80,8 @@ CreateOffer::preflight (PreflightContext const& ctx)
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
STAmount saTakerPays = tx.getFieldAmount (sfTakerPays);
|
||||
STAmount saTakerGets = tx.getFieldAmount (sfTakerGets);
|
||||
STAmount saTakerPays = tx[sfTakerPays];
|
||||
STAmount saTakerGets = tx[sfTakerGets];
|
||||
|
||||
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
|
||||
return temBAD_AMOUNT;
|
||||
@@ -131,33 +131,109 @@ CreateOffer::preflight (PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
TER
|
||||
CreateOffer::checkAcceptAsset(Issue const& issue) const
|
||||
CreateOffer::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
auto saTakerPays = ctx.tx[sfTakerPays];
|
||||
auto saTakerGets = ctx.tx[sfTakerGets];
|
||||
|
||||
auto const& uPaysIssuerID = saTakerPays.getIssuer();
|
||||
auto const& uPaysCurrency = saTakerPays.getCurrency();
|
||||
|
||||
auto const& uGetsIssuerID = saTakerGets.getIssuer();
|
||||
|
||||
auto const cancelSequence = ctx.tx[~sfOfferSequence];
|
||||
|
||||
auto const sleCreator = ctx.view.read(keylet::account(id));
|
||||
|
||||
std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
|
||||
|
||||
auto viewJ = ctx.app.journal("View");
|
||||
|
||||
if (isGlobalFrozen(ctx.view, uPaysIssuerID) ||
|
||||
isGlobalFrozen(ctx.view, uGetsIssuerID))
|
||||
{
|
||||
JLOG(ctx.j.warning) <<
|
||||
"Offer involves frozen asset";
|
||||
|
||||
return tecFROZEN;
|
||||
}
|
||||
else if (accountFunds(ctx.view, id, saTakerGets,
|
||||
fhZERO_IF_FROZEN, viewJ) <= zero)
|
||||
{
|
||||
JLOG(ctx.j.debug) <<
|
||||
"delay: Offers must be at least partially funded.";
|
||||
|
||||
return tecUNFUNDED_OFFER;
|
||||
}
|
||||
// This can probably be simplified to make sure that you cancel sequences
|
||||
// before the transaction sequence number.
|
||||
else if (cancelSequence && (uAccountSequence <= *cancelSequence))
|
||||
{
|
||||
JLOG(ctx.j.debug) <<
|
||||
"uAccountSequenceNext=" << uAccountSequence <<
|
||||
" uOfferSequence=" << *cancelSequence;
|
||||
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
auto const expiration = ctx.tx[~sfExpiration];
|
||||
|
||||
// Expiration is defined in terms of the close time of the parent ledger,
|
||||
// because we definitively know the time that it closed but we do not
|
||||
// know the closing time of the ledger that is under construction.
|
||||
if (expiration &&
|
||||
(ctx.view.parentCloseTime() >= *expiration))
|
||||
{
|
||||
// Note that this will get checked again in applyGuts,
|
||||
// but it saves us a call to checkAcceptAsset and
|
||||
// possible false negative.
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Make sure that we are authorized to hold what the taker will pay us.
|
||||
if (!saTakerPays.native())
|
||||
{
|
||||
auto result = checkAcceptAsset(ctx.view, ctx.flags,
|
||||
id, ctx.j, Issue(uPaysCurrency, uPaysIssuerID));
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CreateOffer::checkAcceptAsset(ReadView const& view,
|
||||
ApplyFlags const flags, AccountID const id,
|
||||
beast::Journal const j, Issue const& issue)
|
||||
{
|
||||
// Only valid for custom currencies
|
||||
assert (!isXRP (issue.currency));
|
||||
|
||||
auto const issuerAccount = ctx_.view().read(
|
||||
auto const issuerAccount = view.read(
|
||||
keylet::account(issue.account));
|
||||
|
||||
if (!issuerAccount)
|
||||
{
|
||||
if (j_.warning) j_.warning <<
|
||||
JLOG(j.warning) <<
|
||||
"delay: can't receive IOUs from non-existent issuer: " <<
|
||||
to_string (issue.account);
|
||||
|
||||
return (view().flags() & tapRETRY)
|
||||
return (flags & tapRETRY)
|
||||
? terNO_ACCOUNT
|
||||
: tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (issuerAccount->getFieldU32 (sfFlags) & lsfRequireAuth)
|
||||
if ((*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||
{
|
||||
auto const trustLine = ctx_.view().read(
|
||||
keylet::line(account_, issue.account, issue.currency));
|
||||
auto const trustLine = view.read(
|
||||
keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
if (!trustLine)
|
||||
{
|
||||
return (view().flags() & tapRETRY)
|
||||
return (flags & tapRETRY)
|
||||
? terNO_LINE
|
||||
: tecNO_LINE;
|
||||
}
|
||||
@@ -165,17 +241,17 @@ CreateOffer::checkAcceptAsset(Issue const& issue) const
|
||||
// Entries have a canonical representation, determined by a
|
||||
// lexicographical "greater than" comparison employing strict weak
|
||||
// ordering. Determine which entry we need to access.
|
||||
bool const canonical_gt (account_ > issue.account);
|
||||
bool const canonical_gt (id > issue.account);
|
||||
|
||||
bool const is_authorized (trustLine->getFieldU32 (sfFlags) &
|
||||
bool const is_authorized ((*trustLine)[sfFlags] &
|
||||
(canonical_gt ? lsfLowAuth : lsfHighAuth));
|
||||
|
||||
if (!is_authorized)
|
||||
{
|
||||
if (j_.debug) j_.debug <<
|
||||
JLOG(j.debug) <<
|
||||
"delay: can't receive IOUs from issuer without auth.";
|
||||
|
||||
return (view().flags() & tapRETRY)
|
||||
return (flags & tapRETRY)
|
||||
? terNO_AUTH
|
||||
: tecNO_AUTH;
|
||||
}
|
||||
@@ -513,7 +589,7 @@ CreateOffer::cross (
|
||||
beast::WrappedSink takerSink (j_, "Taker ");
|
||||
|
||||
Taker taker (cross_type_, view, account_, taker_amount,
|
||||
tx().getFlags(), beast::Journal (takerSink));
|
||||
ctx_.tx.getFlags(), beast::Journal (takerSink));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -548,9 +624,9 @@ CreateOffer::preCompute()
|
||||
{
|
||||
cross_type_ = CrossType::IouToIou;
|
||||
bool const pays_xrp =
|
||||
tx().getFieldAmount (sfTakerPays).native ();
|
||||
ctx_.tx.getFieldAmount (sfTakerPays).native ();
|
||||
bool const gets_xrp =
|
||||
tx().getFieldAmount (sfTakerGets).native ();
|
||||
ctx_.tx.getFieldAmount (sfTakerGets).native ();
|
||||
if (pays_xrp && !gets_xrp)
|
||||
cross_type_ = CrossType::IouToXrp;
|
||||
else if (gets_xrp && !pays_xrp)
|
||||
@@ -562,29 +638,22 @@ CreateOffer::preCompute()
|
||||
std::pair<TER, bool>
|
||||
CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
|
||||
{
|
||||
std::uint32_t const uTxFlags = tx().getFlags ();
|
||||
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
|
||||
|
||||
bool const bPassive (uTxFlags & tfPassive);
|
||||
bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel);
|
||||
bool const bFillOrKill (uTxFlags & tfFillOrKill);
|
||||
bool const bSell (uTxFlags & tfSell);
|
||||
|
||||
STAmount saTakerPays = tx().getFieldAmount (sfTakerPays);
|
||||
STAmount saTakerGets = tx().getFieldAmount (sfTakerGets);
|
||||
|
||||
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
|
||||
return { temBAD_AMOUNT, true };
|
||||
auto saTakerPays = ctx_.tx[sfTakerPays];
|
||||
auto saTakerGets = ctx_.tx[sfTakerGets];
|
||||
|
||||
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
|
||||
auto const& uPaysCurrency = saTakerPays.getCurrency ();
|
||||
|
||||
auto const& uGetsIssuerID = saTakerGets.getIssuer ();
|
||||
|
||||
bool const bHaveExpiration (tx().isFieldPresent (sfExpiration));
|
||||
bool const bHaveCancel (tx().isFieldPresent (sfOfferSequence));
|
||||
|
||||
std::uint32_t const uExpiration = tx().getFieldU32 (sfExpiration);
|
||||
std::uint32_t const uCancelSequence = tx().getFieldU32 (sfOfferSequence);
|
||||
auto const cancelSequence = ctx_.tx[~sfOfferSequence];
|
||||
|
||||
// FIXME understand why we use SequenceNext instead of current transaction
|
||||
// sequence to determine the transaction. Why is the offer sequence
|
||||
@@ -592,84 +661,49 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
|
||||
|
||||
auto const sleCreator = view.peek (keylet::account(account_));
|
||||
|
||||
deprecatedWrongOwnerCount_ = sleCreator->getFieldU32(sfOwnerCount);
|
||||
deprecatedWrongOwnerCount_ = (*sleCreator)[sfOwnerCount];
|
||||
|
||||
std::uint32_t const uAccountSequenceNext = sleCreator->getFieldU32 (sfSequence);
|
||||
std::uint32_t const uSequence = tx().getSequence ();
|
||||
auto const uAccountSequenceNext = (*sleCreator)[sfSequence];
|
||||
auto const uSequence = ctx_.tx.getSequence ();
|
||||
|
||||
// This is the original rate of the offer, and is the rate at which
|
||||
// it will be placed, even if crossing offers change the amounts that
|
||||
// end up on the books.
|
||||
std::uint64_t const uRate = getRate (saTakerGets, saTakerPays);
|
||||
auto const uRate = getRate (saTakerGets, saTakerPays);
|
||||
|
||||
auto viewJ = ctx_.app.journal ("View");
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
// This is the ledger view that we work against. Transactions are applied
|
||||
// as we go on processing transactions.
|
||||
|
||||
if (isGlobalFrozen (view, uPaysIssuerID) || isGlobalFrozen (view, uGetsIssuerID))
|
||||
{
|
||||
if (j_.warning) j_.warning <<
|
||||
"Offer involves frozen asset";
|
||||
|
||||
result = tecFROZEN;
|
||||
}
|
||||
else if (accountFunds(view, account_, saTakerGets,
|
||||
fhZERO_IF_FROZEN, viewJ) <= zero)
|
||||
{
|
||||
if (j_.debug) j_.debug <<
|
||||
"delay: Offers must be at least partially funded.";
|
||||
|
||||
result = tecUNFUNDED_OFFER;
|
||||
}
|
||||
// This can probably be simplified to make sure that you cancel sequences
|
||||
// before the transaction sequence number.
|
||||
else if (bHaveCancel && (uAccountSequenceNext - 1 <= uCancelSequence))
|
||||
{
|
||||
if (j_.debug) j_.debug <<
|
||||
"uAccountSequenceNext=" << uAccountSequenceNext <<
|
||||
" uOfferSequence=" << uCancelSequence;
|
||||
|
||||
result = temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
{
|
||||
j_.debug << "final result: " << transToken (result);
|
||||
return { result, true };
|
||||
}
|
||||
auto result = tesSUCCESS;
|
||||
|
||||
// Process a cancellation request that's passed along with an offer.
|
||||
if (bHaveCancel)
|
||||
if (cancelSequence)
|
||||
{
|
||||
auto const sleCancel = view.peek(
|
||||
keylet::offer(account_, uCancelSequence));
|
||||
keylet::offer(account_, *cancelSequence));
|
||||
|
||||
// It's not an error to not find the offer to cancel: it might have
|
||||
// been consumed or removed. If it is found, however, it's an error
|
||||
// to fail to delete it.
|
||||
if (sleCancel)
|
||||
{
|
||||
j_.debug << "Create cancels order " << uCancelSequence;
|
||||
JLOG(j_.debug) << "Create cancels order " << *cancelSequence;
|
||||
result = offerDelete (view, sleCancel, viewJ);
|
||||
}
|
||||
}
|
||||
|
||||
auto const expiration = ctx_.tx[~sfExpiration];
|
||||
|
||||
// Expiration is defined in terms of the close time of the parent ledger,
|
||||
// because we definitively know the time that it closed but we do not
|
||||
// know the closing time of the ledger that is under construction.
|
||||
if (bHaveExpiration &&
|
||||
(ctx_.view().parentCloseTime() >= uExpiration))
|
||||
if (expiration &&
|
||||
(ctx_.view().parentCloseTime() >= *expiration))
|
||||
{
|
||||
return { tesSUCCESS, true };
|
||||
// If the offer has expired, the transaction has successfully
|
||||
// done nothing, so short circuit from here.
|
||||
return{ tesSUCCESS, true };
|
||||
}
|
||||
|
||||
// Make sure that we are authorized to hold what the taker will pay us.
|
||||
if (result == tesSUCCESS && !saTakerPays.native ())
|
||||
result = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID));
|
||||
|
||||
bool const bOpenLedger =
|
||||
ctx_.view().open();
|
||||
bool crossed = false;
|
||||
@@ -684,13 +718,13 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
|
||||
// empty (fully crossed), or something in-between.
|
||||
Amounts place_offer;
|
||||
|
||||
j_.debug << "Attempting cross: " <<
|
||||
JLOG(j_.debug) << "Attempting cross: " <<
|
||||
to_string (taker_amount.in.issue ()) << " -> " <<
|
||||
to_string (taker_amount.out.issue ());
|
||||
|
||||
if (j_.trace)
|
||||
{
|
||||
j_.debug << " mode: " <<
|
||||
j_.trace << " mode: " <<
|
||||
(bPassive ? "passive " : "") <<
|
||||
(bSell ? "sell" : "buy");
|
||||
j_.trace <<" in: " << format_amount (taker_amount.in);
|
||||
@@ -698,7 +732,9 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
|
||||
}
|
||||
|
||||
std::tie(result, place_offer) = cross (view, view_cancel, taker_amount);
|
||||
assert (result != tefINTERNAL);
|
||||
// We expect the implementation of cross to succeed
|
||||
// or give a tec.
|
||||
assert(result == tesSUCCESS || isTecClaim(result));
|
||||
|
||||
if (j_.trace)
|
||||
{
|
||||
@@ -855,8 +891,8 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
|
||||
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
|
||||
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
|
||||
sleOffer->setFieldU64 (sfBookNode, uBookNode);
|
||||
if (uExpiration)
|
||||
sleOffer->setFieldU32 (sfExpiration, uExpiration);
|
||||
if (expiration)
|
||||
sleOffer->setFieldU32 (sfExpiration, *expiration);
|
||||
if (bPassive)
|
||||
sleOffer->setFlag (lsfPassive);
|
||||
if (bSell)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -68,25 +68,25 @@ CreateTicket::doApply ()
|
||||
|
||||
std::uint32_t expiration (0);
|
||||
|
||||
if (tx().isFieldPresent (sfExpiration))
|
||||
if (ctx_.tx.isFieldPresent (sfExpiration))
|
||||
{
|
||||
expiration = tx().getFieldU32 (sfExpiration);
|
||||
expiration = ctx_.tx.getFieldU32 (sfExpiration);
|
||||
|
||||
if (view().parentCloseTime() >= expiration)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
SLE::pointer sleTicket = std::make_shared<SLE>(ltTICKET,
|
||||
getTicketIndex (account_, tx().getSequence ()));
|
||||
getTicketIndex (account_, ctx_.tx.getSequence ()));
|
||||
sleTicket->setAccountID (sfAccount, account_);
|
||||
sleTicket->setFieldU32 (sfSequence, tx().getSequence ());
|
||||
sleTicket->setFieldU32 (sfSequence, ctx_.tx.getSequence ());
|
||||
if (expiration != 0)
|
||||
sleTicket->setFieldU32 (sfExpiration, expiration);
|
||||
view().insert (sleTicket);
|
||||
|
||||
if (tx().isFieldPresent (sfTarget))
|
||||
if (ctx_.tx.isFieldPresent (sfTarget))
|
||||
{
|
||||
AccountID const target_account (tx().getAccountID (sfTarget));
|
||||
AccountID const target_account (ctx_.tx.getAccountID (sfTarget));
|
||||
|
||||
SLE::pointer sleTarget = view().peek (keylet::account(target_account));
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <ripple/app/tx/impl/Payment.h>
|
||||
#include <ripple/app/paths/RippleCalc.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -31,11 +31,12 @@ namespace ripple {
|
||||
// Mon Aug 17 11:00:00am PDT
|
||||
static std::uint32_t const deliverMinTime = 493149600;
|
||||
|
||||
static
|
||||
bool
|
||||
allowDeliverMin (ApplyView const& view)
|
||||
allowDeliverMin (ReadView const& view, ApplyFlags const& flags)
|
||||
{
|
||||
return view.info().parentCloseTime > deliverMinTime ||
|
||||
(view.flags() & tapENABLE_TESTING);
|
||||
(flags & tapENABLE_TESTING);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -158,7 +159,8 @@ Payment::preflight (PreflightContext const& ctx)
|
||||
return temBAD_SEND_XRP_NO_DIRECT;
|
||||
}
|
||||
|
||||
if (tx.isFieldPresent(sfDeliverMin))
|
||||
auto const deliverMin = tx[~sfDeliverMin];
|
||||
if (deliverMin)
|
||||
{
|
||||
if (! partialPaymentAllowed)
|
||||
{
|
||||
@@ -167,7 +169,7 @@ Payment::preflight (PreflightContext const& ctx)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
auto const dMin = tx.getFieldAmount(sfDeliverMin);
|
||||
auto const dMin = *deliverMin;
|
||||
if (!isLegalNet(dMin) || dMin <= zero)
|
||||
{
|
||||
JLOG(j.trace) << "Malformed transaction: Invalid " <<
|
||||
@@ -195,29 +197,128 @@ Payment::preflight (PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
TER
|
||||
Payment::doApply ()
|
||||
Payment::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
bool const deliverMin = tx().isFieldPresent(sfDeliverMin);
|
||||
auto const deliverMin = ctx.tx[~sfDeliverMin];
|
||||
|
||||
if (deliverMin)
|
||||
{
|
||||
if (! allowDeliverMin(view()))
|
||||
if (!allowDeliverMin(ctx.view, ctx.flags))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
// Ripple if source or destination is non-native or if there are paths.
|
||||
std::uint32_t const uTxFlags = tx().getFlags ();
|
||||
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
||||
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
|
||||
auto const paths = ctx.tx.isFieldPresent(sfPaths);
|
||||
auto const sendMax = ctx.tx[~sfSendMax];
|
||||
|
||||
AccountID const uDstAccountID(ctx.tx[sfDestination]);
|
||||
STAmount const saDstAmount(ctx.tx[sfAmount]);
|
||||
|
||||
auto const k = keylet::account(uDstAccountID);
|
||||
auto const sleDst = ctx.view.read(k);
|
||||
|
||||
if (!sleDst)
|
||||
{
|
||||
// Destination account does not exist.
|
||||
if (!saDstAmount.native())
|
||||
{
|
||||
JLOG(ctx.j.trace) <<
|
||||
"Delay transaction: Destination account does not exist.";
|
||||
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST;
|
||||
}
|
||||
else if (ctx.view.open()
|
||||
&& partialPaymentAllowed)
|
||||
{
|
||||
// You cannot fund an account with a partial payment.
|
||||
// Make retry work smaller, by rejecting this.
|
||||
JLOG(ctx.j.trace) <<
|
||||
"Delay transaction: Partial payment not allowed to create account.";
|
||||
|
||||
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return telNO_DST_PARTIAL;
|
||||
}
|
||||
else if (saDstAmount < STAmount(ctx.view.fees().accountReserve(0)))
|
||||
{
|
||||
// accountReserve is the minimum amount that an account can have.
|
||||
// Reserve is not scaled by load.
|
||||
JLOG(ctx.j.trace) <<
|
||||
"Delay transaction: Destination account does not exist. " <<
|
||||
"Insufficent payment to create account.";
|
||||
|
||||
// TODO: dedupe
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST_INSUF_XRP;
|
||||
}
|
||||
}
|
||||
else if ((sleDst->getFlags() & lsfRequireDestTag) &&
|
||||
!ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
|
||||
// We didn't make this test for a newly-formed account because there's
|
||||
// no way for this field to be set.
|
||||
JLOG(ctx.j.trace) << "Malformed transaction: DestinationTag required.";
|
||||
|
||||
return tecDST_TAG_NEEDED;
|
||||
}
|
||||
|
||||
if (paths || sendMax || !saDstAmount.native())
|
||||
{
|
||||
// Ripple payment with at least one intermediate step and uses
|
||||
// transitive balances.
|
||||
|
||||
// Copy paths into an editable class.
|
||||
STPathSet const spsPaths = ctx.tx.getFieldPathSet(sfPaths);
|
||||
|
||||
auto pathTooBig = spsPaths.size() > MaxPathSize;
|
||||
|
||||
if(!pathTooBig)
|
||||
for (auto const& path : spsPaths)
|
||||
if (path.size() > MaxPathLength)
|
||||
{
|
||||
pathTooBig = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx.view.open() && pathTooBig)
|
||||
{
|
||||
return telBAD_PATH_COUNT; // Too many paths for proposed ledger.
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
TER
|
||||
Payment::doApply ()
|
||||
{
|
||||
auto const deliverMin = ctx_.tx[~sfDeliverMin];
|
||||
|
||||
// Ripple if source or destination is non-native or if there are paths.
|
||||
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
|
||||
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
|
||||
bool const limitQuality = uTxFlags & tfLimitQuality;
|
||||
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
|
||||
bool const bPaths = tx().isFieldPresent (sfPaths);
|
||||
bool const bMax = tx().isFieldPresent (sfSendMax);
|
||||
auto const paths = ctx_.tx.isFieldPresent(sfPaths);
|
||||
auto const sendMax = ctx_.tx[~sfSendMax];
|
||||
|
||||
AccountID const uDstAccountID (tx().getAccountID (sfDestination));
|
||||
STAmount const saDstAmount (tx().getFieldAmount (sfAmount));
|
||||
AccountID const uDstAccountID (ctx_.tx.getAccountID (sfDestination));
|
||||
STAmount const saDstAmount (ctx_.tx.getFieldAmount (sfAmount));
|
||||
STAmount maxSourceAmount;
|
||||
if (bMax)
|
||||
maxSourceAmount = tx().getFieldAmount (sfSendMax);
|
||||
if (sendMax)
|
||||
maxSourceAmount = *sendMax;
|
||||
else if (saDstAmount.native ())
|
||||
maxSourceAmount = saDstAmount;
|
||||
else
|
||||
@@ -226,7 +327,7 @@ Payment::doApply ()
|
||||
saDstAmount.mantissa(), saDstAmount.exponent (),
|
||||
saDstAmount < zero);
|
||||
|
||||
j_.trace <<
|
||||
JLOG(j_.trace) <<
|
||||
"maxSourceAmount=" << maxSourceAmount.getFullText () <<
|
||||
" saDstAmount=" << saDstAmount.getFullText ();
|
||||
|
||||
@@ -236,64 +337,15 @@ Payment::doApply ()
|
||||
|
||||
if (!sleDst)
|
||||
{
|
||||
// Destination account does not exist.
|
||||
if (!saDstAmount.native ())
|
||||
{
|
||||
j_.trace <<
|
||||
"Delay transaction: Destination account does not exist.";
|
||||
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST;
|
||||
}
|
||||
else if (view().open()
|
||||
&& partialPaymentAllowed)
|
||||
{
|
||||
// You cannot fund an account with a partial payment.
|
||||
// Make retry work smaller, by rejecting this.
|
||||
j_.trace <<
|
||||
"Delay transaction: Partial payment not allowed to create account.";
|
||||
|
||||
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return telNO_DST_PARTIAL;
|
||||
}
|
||||
else if (saDstAmount < STAmount (view().fees().accountReserve(0)))
|
||||
{
|
||||
// accountReserve is the minimum amount that an account can have.
|
||||
// Reserve is not scaled by load.
|
||||
j_.trace <<
|
||||
"Delay transaction: Destination account does not exist. " <<
|
||||
"Insufficent payment to create account.";
|
||||
|
||||
// TODO: dedupe
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST_INSUF_XRP;
|
||||
}
|
||||
|
||||
// Create the account.
|
||||
sleDst = std::make_shared<SLE>(k);
|
||||
sleDst->setAccountID (sfAccount, uDstAccountID);
|
||||
sleDst->setFieldU32 (sfSequence, 1);
|
||||
sleDst->setAccountID(sfAccount, uDstAccountID);
|
||||
sleDst->setFieldU32(sfSequence, 1);
|
||||
view().insert(sleDst);
|
||||
}
|
||||
else if ((sleDst->getFlags () & lsfRequireDestTag) &&
|
||||
!tx().isFieldPresent (sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
|
||||
// We didn't make this test for a newly-formed account because there's
|
||||
// no way for this field to be set.
|
||||
j_.trace << "Malformed transaction: DestinationTag required.";
|
||||
|
||||
return tecDST_TAG_NEEDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tell the engine that we are intending to change the the destination
|
||||
// Tell the engine that we are intending to change the destination
|
||||
// account. The source account gets always charged a fee so it's always
|
||||
// marked as modified.
|
||||
view().update (sleDst);
|
||||
@@ -301,8 +353,8 @@ Payment::doApply ()
|
||||
|
||||
TER terResult;
|
||||
|
||||
bool const bRipple = bPaths || bMax || !saDstAmount.native ();
|
||||
// XXX Should bMax be sufficient to imply ripple?
|
||||
bool const bRipple = paths || sendMax || !saDstAmount.native ();
|
||||
// XXX Should sendMax be sufficient to imply ripple?
|
||||
|
||||
if (bRipple)
|
||||
{
|
||||
@@ -310,10 +362,8 @@ Payment::doApply ()
|
||||
// transitive balances.
|
||||
|
||||
// Copy paths into an editable class.
|
||||
STPathSet spsPaths = tx().getFieldPathSet (sfPaths);
|
||||
STPathSet spsPaths = ctx_.tx.getFieldPathSet (sfPaths);
|
||||
|
||||
try
|
||||
{
|
||||
path::RippleCalc::Input rcInput;
|
||||
rcInput.partialPaymentAllowed = partialPaymentAllowed;
|
||||
rcInput.defaultPathsAllowed = defaultPathsAllowed;
|
||||
@@ -321,18 +371,6 @@ Payment::doApply ()
|
||||
rcInput.deleteUnfundedOffers = true;
|
||||
rcInput.isLedgerOpen = view().open();
|
||||
|
||||
bool pathTooBig = spsPaths.size () > MaxPathSize;
|
||||
|
||||
for (auto const& path : spsPaths)
|
||||
if (path.size () > MaxPathLength)
|
||||
pathTooBig = true;
|
||||
|
||||
if (rcInput.isLedgerOpen && pathTooBig)
|
||||
{
|
||||
terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger.
|
||||
}
|
||||
else
|
||||
{
|
||||
path::RippleCalc::Output rc;
|
||||
{
|
||||
PaymentSandbox pv(&view());
|
||||
@@ -357,27 +395,20 @@ Payment::doApply ()
|
||||
rc.actualAmountOut != saDstAmount)
|
||||
{
|
||||
if (deliverMin && rc.actualAmountOut <
|
||||
tx().getFieldAmount (sfDeliverMin))
|
||||
*deliverMin)
|
||||
rc.setResult (tecPATH_PARTIAL);
|
||||
else
|
||||
ctx_.deliver (rc.actualAmountOut);
|
||||
}
|
||||
|
||||
terResult = rc.result ();
|
||||
}
|
||||
|
||||
// TODO(tom): what's going on here?
|
||||
// Because of its overhead, if RippleCalc
|
||||
// fails with a retry code, claim a fee
|
||||
// instead. Maybe the user will be more
|
||||
// careful with their path spec next time.
|
||||
if (isTerRetry (terResult))
|
||||
terResult = tecPATH_DRY;
|
||||
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
j_.trace <<
|
||||
"Caught throw: " << e.what ();
|
||||
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -397,13 +428,13 @@ Payment::doApply ()
|
||||
// fees were charged. We want to make sure we have enough reserve
|
||||
// to send. Allow final spend to use reserve for fee.
|
||||
auto const mmm = std::max(reserve,
|
||||
tx().getFieldAmount (sfFee).xrp ());
|
||||
ctx_.tx.getFieldAmount (sfFee).xrp ());
|
||||
|
||||
if (mPriorBalance < saDstAmount.xrp () + mmm)
|
||||
{
|
||||
// Vote no. However the transaction might succeed, if applied in
|
||||
// a different order.
|
||||
j_.trace << "Delay transaction: Insufficient funds: " <<
|
||||
JLOG(j_.trace) << "Delay transaction: Insufficient funds: " <<
|
||||
" " << to_string (mPriorBalance) <<
|
||||
" / " << to_string (saDstAmount.xrp () + mmm) <<
|
||||
" (" << to_string (reserve) << ")";
|
||||
@@ -432,7 +463,7 @@ Payment::doApply ()
|
||||
|
||||
if (transResultInfo (terResult, strToken, strHuman))
|
||||
{
|
||||
j_.trace <<
|
||||
JLOG(j_.trace) <<
|
||||
strToken << ": " << strHuman;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER doApply () override;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/Quality.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -104,13 +104,60 @@ SetAccount::preflight (PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
auto const messageKey = tx[~sfMessageKey];
|
||||
if (messageKey && messageKey->size() > PUBLIC_BYTES_MAX)
|
||||
{
|
||||
JLOG(j.trace) << "message key too long";
|
||||
return telBAD_PUBLIC_KEY;
|
||||
}
|
||||
|
||||
auto const domain = tx[~sfDomain];
|
||||
if (domain&& domain->size() > DOMAIN_BYTES_MAX)
|
||||
{
|
||||
JLOG(j.trace) << "domain too long";
|
||||
return telBAD_DOMAIN;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SetAccount::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
||||
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
|
||||
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
|
||||
|
||||
std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag);
|
||||
|
||||
// legacy AccountSet flags
|
||||
bool bSetRequireAuth = (uTxFlags & tfRequireAuth) || (uSetFlag == asfRequireAuth);
|
||||
|
||||
//
|
||||
// RequireAuth
|
||||
//
|
||||
if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth))
|
||||
{
|
||||
if (!dirIsEmpty(ctx.view,
|
||||
keylet::ownerDir(id)))
|
||||
{
|
||||
JLOG(ctx.j.trace) << "Retry: Owner directory not empty.";
|
||||
return (ctx.flags & tapRETRY) ? terOWNERS : tecOWNERS;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetAccount::doApply ()
|
||||
{
|
||||
std::uint32_t const uTxFlags = tx().getFlags ();
|
||||
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
|
||||
|
||||
auto const sle = view().peek(
|
||||
keylet::account(account_));
|
||||
@@ -118,8 +165,8 @@ SetAccount::doApply ()
|
||||
std::uint32_t const uFlagsIn = sle->getFieldU32 (sfFlags);
|
||||
std::uint32_t uFlagsOut = uFlagsIn;
|
||||
|
||||
std::uint32_t const uSetFlag = tx().getFieldU32 (sfSetFlag);
|
||||
std::uint32_t const uClearFlag = tx().getFieldU32 (sfClearFlag);
|
||||
std::uint32_t const uSetFlag = ctx_.tx.getFieldU32 (sfSetFlag);
|
||||
std::uint32_t const uClearFlag = ctx_.tx.getFieldU32 (sfClearFlag);
|
||||
|
||||
// legacy AccountSet flags
|
||||
bool bSetRequireDest = (uTxFlags & TxFlag::requireDestTag) || (uSetFlag == asfRequireDest);
|
||||
@@ -134,13 +181,6 @@ SetAccount::doApply ()
|
||||
//
|
||||
if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth))
|
||||
{
|
||||
if (! dirIsEmpty (view(),
|
||||
keylet::ownerDir(account_)))
|
||||
{
|
||||
j_.trace << "Retry: Owner directory not empty.";
|
||||
return (view().flags() & tapRETRY) ? terOWNERS : tecOWNERS;
|
||||
}
|
||||
|
||||
j_.trace << "Set RequireAuth.";
|
||||
uFlagsOut |= lsfRequireAuth;
|
||||
}
|
||||
@@ -199,7 +239,8 @@ SetAccount::doApply ()
|
||||
|
||||
// Prevent transaction changes until we're ready.
|
||||
if (view().flags() & tapENABLE_TESTING ||
|
||||
view().rules().enabled(featureMultiSign, ctx_.config.features))
|
||||
view().rules().enabled(featureMultiSign,
|
||||
ctx_.app.config().features))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
return tecNO_REGULAR_KEY;
|
||||
@@ -277,9 +318,9 @@ SetAccount::doApply ()
|
||||
//
|
||||
// EmailHash
|
||||
//
|
||||
if (tx().isFieldPresent (sfEmailHash))
|
||||
if (ctx_.tx.isFieldPresent (sfEmailHash))
|
||||
{
|
||||
uint128 const uHash = tx().getFieldH128 (sfEmailHash);
|
||||
uint128 const uHash = ctx_.tx.getFieldH128 (sfEmailHash);
|
||||
|
||||
if (!uHash)
|
||||
{
|
||||
@@ -296,9 +337,9 @@ SetAccount::doApply ()
|
||||
//
|
||||
// WalletLocator
|
||||
//
|
||||
if (tx().isFieldPresent (sfWalletLocator))
|
||||
if (ctx_.tx.isFieldPresent (sfWalletLocator))
|
||||
{
|
||||
uint256 const uHash = tx().getFieldH256 (sfWalletLocator);
|
||||
uint256 const uHash = ctx_.tx.getFieldH256 (sfWalletLocator);
|
||||
|
||||
if (!uHash)
|
||||
{
|
||||
@@ -315,15 +356,9 @@ SetAccount::doApply ()
|
||||
//
|
||||
// MessageKey
|
||||
//
|
||||
if (tx().isFieldPresent (sfMessageKey))
|
||||
if (ctx_.tx.isFieldPresent (sfMessageKey))
|
||||
{
|
||||
Blob const messageKey = tx().getFieldVL (sfMessageKey);
|
||||
|
||||
if (messageKey.size () > PUBLIC_BYTES_MAX)
|
||||
{
|
||||
j_.trace << "message key too long";
|
||||
return telBAD_PUBLIC_KEY;
|
||||
}
|
||||
Blob const messageKey = ctx_.tx.getFieldVL (sfMessageKey);
|
||||
|
||||
if (messageKey.empty ())
|
||||
{
|
||||
@@ -340,15 +375,9 @@ SetAccount::doApply ()
|
||||
//
|
||||
// Domain
|
||||
//
|
||||
if (tx().isFieldPresent (sfDomain))
|
||||
if (ctx_.tx.isFieldPresent (sfDomain))
|
||||
{
|
||||
Blob const domain = tx().getFieldVL (sfDomain);
|
||||
|
||||
if (domain.size () > DOMAIN_BYTES_MAX)
|
||||
{
|
||||
j_.trace << "domain too long";
|
||||
return telBAD_DOMAIN;
|
||||
}
|
||||
Blob const domain = ctx_.tx.getFieldVL (sfDomain);
|
||||
|
||||
if (domain.empty ())
|
||||
{
|
||||
@@ -365,9 +394,9 @@ SetAccount::doApply ()
|
||||
//
|
||||
// TransferRate
|
||||
//
|
||||
if (tx().isFieldPresent (sfTransferRate))
|
||||
if (ctx_.tx.isFieldPresent (sfTransferRate))
|
||||
{
|
||||
std::uint32_t uRate = tx().getFieldU32 (sfTransferRate);
|
||||
std::uint32_t uRate = ctx_.tx.getFieldU32 (sfTransferRate);
|
||||
|
||||
if (uRate == 0 || uRate == QUALITY_ONE)
|
||||
{
|
||||
|
||||
@@ -45,6 +45,10 @@ public:
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER doApply () override;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -87,14 +87,57 @@ SetTrust::preflight (PreflightContext const& ctx)
|
||||
return preflight2 (ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SetTrust::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
|
||||
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
||||
|
||||
bool const bSetAuth = (uTxFlags & tfSetfAuth);
|
||||
|
||||
if (bSetAuth && !(sle->getFieldU32(sfFlags) & lsfRequireAuth))
|
||||
{
|
||||
JLOG(ctx.j.trace) <<
|
||||
"Retry: Auth not required.";
|
||||
return tefNO_AUTH_REQUIRED;
|
||||
}
|
||||
|
||||
auto const saLimitAmount = ctx.tx[sfLimitAmount];
|
||||
|
||||
auto const currency = saLimitAmount.getCurrency();
|
||||
auto const uDstAccountID = saLimitAmount.getIssuer();
|
||||
|
||||
if (id == uDstAccountID)
|
||||
{
|
||||
// Prevent trustline to self from being created,
|
||||
// unless one has somehow already been created
|
||||
// (in which case doApply will clean it up).
|
||||
auto const sleDelete = ctx.view.read(
|
||||
keylet::line(id, uDstAccountID, currency));
|
||||
|
||||
if (!sleDelete)
|
||||
{
|
||||
JLOG(ctx.j.trace) <<
|
||||
"Malformed transaction: Can not extend credit to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetTrust::doApply ()
|
||||
{
|
||||
TER terResult = tesSUCCESS;
|
||||
|
||||
STAmount const saLimitAmount (tx().getFieldAmount (sfLimitAmount));
|
||||
bool const bQualityIn (tx().isFieldPresent (sfQualityIn));
|
||||
bool const bQualityOut (tx().isFieldPresent (sfQualityOut));
|
||||
STAmount const saLimitAmount (ctx_.tx.getFieldAmount (sfLimitAmount));
|
||||
bool const bQualityIn (ctx_.tx.isFieldPresent (sfQualityIn));
|
||||
bool const bQualityOut (ctx_.tx.isFieldPresent (sfQualityOut));
|
||||
|
||||
Currency const currency (saLimitAmount.getCurrency ());
|
||||
AccountID uDstAccountID (saLimitAmount.getIssuer ());
|
||||
@@ -123,13 +166,13 @@ SetTrust::doApply ()
|
||||
? XRPAmount (zero)
|
||||
: view().fees().accountReserve(uOwnerCount + 1));
|
||||
|
||||
std::uint32_t uQualityIn (bQualityIn ? tx().getFieldU32 (sfQualityIn) : 0);
|
||||
std::uint32_t uQualityOut (bQualityOut ? tx().getFieldU32 (sfQualityOut) : 0);
|
||||
std::uint32_t uQualityIn (bQualityIn ? ctx_.tx.getFieldU32 (sfQualityIn) : 0);
|
||||
std::uint32_t uQualityOut (bQualityOut ? ctx_.tx.getFieldU32 (sfQualityOut) : 0);
|
||||
|
||||
if (bQualityOut && QUALITY_ONE == uQualityOut)
|
||||
uQualityOut = 0;
|
||||
|
||||
std::uint32_t const uTxFlags = tx().getFlags ();
|
||||
std::uint32_t const uTxFlags = ctx_.tx.getFlags ();
|
||||
|
||||
bool const bSetAuth = (uTxFlags & tfSetfAuth);
|
||||
bool const bSetNoRipple = (uTxFlags & tfSetNoRipple);
|
||||
@@ -139,13 +182,6 @@ SetTrust::doApply ()
|
||||
|
||||
auto viewJ = ctx_.app.journal ("View");
|
||||
|
||||
if (bSetAuth && !(sle->getFieldU32 (sfFlags) & lsfRequireAuth))
|
||||
{
|
||||
j_.trace <<
|
||||
"Retry: Auth not required.";
|
||||
return tefNO_AUTH_REQUIRED;
|
||||
}
|
||||
|
||||
if (account_ == uDstAccountID)
|
||||
{
|
||||
// The only purpose here is to allow a mistakenly created
|
||||
@@ -155,21 +191,12 @@ SetTrust::doApply ()
|
||||
SLE::pointer sleDelete = view().peek (
|
||||
keylet::line(account_, uDstAccountID, currency));
|
||||
|
||||
if (sleDelete)
|
||||
{
|
||||
j_.warning <<
|
||||
"Clearing redundant line.";
|
||||
|
||||
return trustDelete (view(),
|
||||
sleDelete, account_, uDstAccountID, viewJ);
|
||||
}
|
||||
else
|
||||
{
|
||||
j_.trace <<
|
||||
"Malformed transaction: Can not extend credit to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
SLE::pointer sleDst =
|
||||
view().peek (keylet::account(uDstAccountID));
|
||||
@@ -405,7 +432,7 @@ SetTrust::doApply ()
|
||||
(! bQualityOut || ! uQualityOut) && // Not setting quality out or setting default quality out.
|
||||
(! ((view().flags() & tapENABLE_TESTING) ||
|
||||
view().rules().enabled(featureTrustSetAuth,
|
||||
ctx_.config.features)) || ! bSetAuth))
|
||||
ctx_.app.config().features)) || ! bSetAuth))
|
||||
{
|
||||
j_.trace <<
|
||||
"Redundant: Setting non-existent ripple line to defaults.";
|
||||
|
||||
@@ -41,6 +41,10 @@ public:
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER doApply () override;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,10 +32,30 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Performs early sanity checks on the txid */
|
||||
TER
|
||||
preflight0(PreflightContext const& ctx)
|
||||
{
|
||||
auto const txID = ctx.tx.getTransactionID();
|
||||
|
||||
if (txID == beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.warning) <<
|
||||
"applyTransaction: transaction id may not be zero";
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields */
|
||||
TER
|
||||
preflight1 (PreflightContext const& ctx)
|
||||
{
|
||||
auto const ret = preflight0(ctx);
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
if (id == zero)
|
||||
{
|
||||
@@ -83,62 +103,77 @@ preflight2 (PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
static
|
||||
XRPAmount
|
||||
calculateFee(Application& app, std::uint64_t const baseFee,
|
||||
Fees const& fees, ApplyFlags flags)
|
||||
{
|
||||
return app.getFeeTrack().scaleFeeLoad(
|
||||
baseFee, fees.base, fees.units, flags & tapADMIN);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Transactor::Transactor(
|
||||
ApplyContext& ctx)
|
||||
: ctx_ (ctx)
|
||||
, j_ (ctx.journal)
|
||||
, mHasAuthKey (false)
|
||||
, mSigMaster (false)
|
||||
{
|
||||
}
|
||||
|
||||
std::uint64_t Transactor::calculateBaseFee ()
|
||||
std::uint64_t Transactor::calculateBaseFee (
|
||||
PreclaimContext const& ctx)
|
||||
{
|
||||
// Returns the fee in fee units.
|
||||
|
||||
// The computation has two parts:
|
||||
// * The base fee, which is the same for most transactions.
|
||||
// * The additional cost of each multisignature on the transaction.
|
||||
std::uint64_t baseFee = view().fees().units;
|
||||
std::uint64_t baseFee = ctx.view.fees().units;
|
||||
|
||||
// Each signer adds one more baseFee to the minimum required fee
|
||||
// for the transaction.
|
||||
std::uint32_t signerCount = 0;
|
||||
if (tx().isFieldPresent (sfSigners))
|
||||
signerCount = tx().getFieldArray (sfSigners).size();
|
||||
if (ctx.tx.isFieldPresent (sfSigners))
|
||||
signerCount = ctx.tx.getFieldArray (sfSigners).size();
|
||||
|
||||
return baseFee + (signerCount * baseFee);
|
||||
}
|
||||
|
||||
TER Transactor::payFee ()
|
||||
TER
|
||||
Transactor::checkFee (PreclaimContext const& ctx, std::uint64_t baseFee)
|
||||
{
|
||||
auto const feePaid = tx().getFieldAmount (sfFee).xrp ();
|
||||
auto const feePaid = ctx.tx[sfFee].xrp ();
|
||||
if (!isLegalAmount (feePaid) || feePaid < beast::zero)
|
||||
return temBAD_FEE;
|
||||
|
||||
auto const feeDue = ripple::calculateFee(ctx.app,
|
||||
baseFee, ctx.view.fees(), ctx.flags);
|
||||
|
||||
// Only check fee is sufficient when the ledger is open.
|
||||
if (view().open() && feePaid < mFeeDue)
|
||||
if (ctx.view.open() && feePaid < feeDue)
|
||||
{
|
||||
JLOG(j_.trace) << "Insufficient fee paid: " <<
|
||||
to_string (feePaid) << "/" << to_string (mFeeDue);
|
||||
JLOG(ctx.j.trace) << "Insufficient fee paid: " <<
|
||||
to_string (feePaid) << "/" << to_string (feeDue);
|
||||
return telINSUF_FEE_P;
|
||||
}
|
||||
|
||||
if (feePaid == zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const sle = view().peek(
|
||||
keylet::account(account_));
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
auto const balance = (*sle)[sfBalance].xrp();
|
||||
|
||||
if (mSourceBalance < feePaid)
|
||||
if (balance < feePaid)
|
||||
{
|
||||
JLOG(j_.trace) << "Insufficient balance:" <<
|
||||
" balance=" << to_string (mSourceBalance) <<
|
||||
" paid=" << to_string (feePaid);
|
||||
JLOG(ctx.j.trace) << "Insufficient balance:" <<
|
||||
" balance=" << to_string(balance) <<
|
||||
" paid=" << to_string(feePaid);
|
||||
|
||||
if ((mSourceBalance > zero) && ! view().open())
|
||||
if ((balance > zero) && !ctx.view.open())
|
||||
{
|
||||
// Closed ledger, non-zero balance, less than fee
|
||||
return tecINSUFF_FEE;
|
||||
@@ -147,8 +182,18 @@ TER Transactor::payFee ()
|
||||
return terINSUF_FEE_B;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::payFee ()
|
||||
{
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const sle = view().peek(
|
||||
keylet::account(account_));
|
||||
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back, if the transaction succeeds.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
|
||||
mSourceBalance -= feePaid;
|
||||
sle->setFieldAmount (sfBalance, mSourceBalance);
|
||||
@@ -158,56 +203,76 @@ TER Transactor::payFee ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::checkSeq ()
|
||||
TER
|
||||
Transactor::checkSeq (PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sle = view().peek(
|
||||
keylet::account(account_));
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
std::uint32_t const t_seq = tx().getSequence ();
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: delay: source account does not exist " <<
|
||||
toBase58(ctx.tx.getAccountID(sfAccount));
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
|
||||
std::uint32_t const t_seq = ctx.tx.getSequence ();
|
||||
std::uint32_t const a_seq = sle->getFieldU32 (sfSequence);
|
||||
|
||||
if (t_seq != a_seq)
|
||||
{
|
||||
if (a_seq < t_seq)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: has future sequence number " <<
|
||||
"a_seq=" << a_seq << " t_seq=" << t_seq;
|
||||
return terPRE_SEQ;
|
||||
}
|
||||
|
||||
if (view().txExists(tx().getTransactionID ()))
|
||||
if (ctx.view.txExists(ctx.tx.getTransactionID ()))
|
||||
return tefALREADY;
|
||||
|
||||
JLOG(j_.trace) << "applyTransaction: has past sequence number " <<
|
||||
JLOG(ctx.j.trace) << "applyTransaction: has past sequence number " <<
|
||||
"a_seq=" << a_seq << " t_seq=" << t_seq;
|
||||
return tefPAST_SEQ;
|
||||
}
|
||||
|
||||
if (tx().isFieldPresent (sfAccountTxnID) &&
|
||||
(sle->getFieldH256 (sfAccountTxnID) != tx().getFieldH256 (sfAccountTxnID)))
|
||||
if (ctx.tx.isFieldPresent (sfAccountTxnID) &&
|
||||
(sle->getFieldH256 (sfAccountTxnID) != ctx.tx.getFieldH256 (sfAccountTxnID)))
|
||||
return tefWRONG_PRIOR;
|
||||
|
||||
if (tx().isFieldPresent (sfLastLedgerSequence) &&
|
||||
(view().seq() > tx().getFieldU32 (sfLastLedgerSequence)))
|
||||
if (ctx.tx.isFieldPresent (sfLastLedgerSequence) &&
|
||||
(ctx.view.seq() > ctx.tx.getFieldU32 (sfLastLedgerSequence)))
|
||||
return tefMAX_LEDGER;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
Transactor::setSeq ()
|
||||
{
|
||||
auto const sle = view().peek(
|
||||
keylet::account(account_));
|
||||
|
||||
std::uint32_t const t_seq = ctx_.tx.getSequence ();
|
||||
|
||||
sle->setFieldU32 (sfSequence, t_seq + 1);
|
||||
|
||||
if (sle->isFieldPresent (sfAccountTxnID))
|
||||
sle->setFieldH256 (sfAccountTxnID, tx().getTransactionID ());
|
||||
|
||||
return tesSUCCESS;
|
||||
sle->setFieldH256 (sfAccountTxnID, ctx_.tx.getTransactionID ());
|
||||
}
|
||||
|
||||
// check stuff before you bother to lock the ledger
|
||||
void Transactor::preCompute ()
|
||||
{
|
||||
account_ = tx().getAccountID(sfAccount);
|
||||
account_ = ctx_.tx.getAccountID(sfAccount);
|
||||
assert(account_ != zero);
|
||||
mSigningPubKey =
|
||||
RippleAddress::createAccountPublic(
|
||||
tx().getSigningPubKey());
|
||||
ctx_.tx.getSigningPubKey());
|
||||
}
|
||||
|
||||
TER Transactor::apply ()
|
||||
@@ -218,34 +283,25 @@ TER Transactor::apply ()
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const sle = view().peek (keylet::account(account_));
|
||||
|
||||
if (sle == nullptr && account_ != zero)
|
||||
{
|
||||
JLOG (j_.trace) <<
|
||||
"apply: source account " << toBase58(account_) << " not found.";
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
// sle must exist except for transactions
|
||||
// that allow zero account.
|
||||
assert(sle != nullptr || account_ == zero);
|
||||
|
||||
auto const& fees = view().fees();
|
||||
mFeeDue = ctx_.app.getFeeTrack().scaleFeeLoad(
|
||||
calculateBaseFee(), fees.base, fees.units, view().flags() & tapADMIN);
|
||||
mFeeDue = calculateFee(ctx_.app, ctx_.baseFee,
|
||||
view().fees(), view().flags());
|
||||
|
||||
if (sle)
|
||||
{
|
||||
mPriorBalance = STAmount ((*sle)[sfBalance]).xrp ();
|
||||
mSourceBalance = mPriorBalance;
|
||||
mHasAuthKey = sle->isFieldPresent (sfRegularKey);
|
||||
|
||||
auto terResult = checkSeq ();
|
||||
setSeq();
|
||||
|
||||
auto terResult = payFee ();
|
||||
|
||||
if (terResult != tesSUCCESS) return terResult;
|
||||
|
||||
terResult = payFee ();
|
||||
|
||||
if (terResult != tesSUCCESS) return terResult;
|
||||
|
||||
terResult = checkSign ();
|
||||
|
||||
if (terResult != tesSUCCESS) return terResult;
|
||||
checkMasterSign ();
|
||||
|
||||
view().update (sle);
|
||||
}
|
||||
@@ -253,49 +309,76 @@ TER Transactor::apply ()
|
||||
return doApply ();
|
||||
}
|
||||
|
||||
TER Transactor::checkSign ()
|
||||
TER
|
||||
Transactor::checkSign (PreclaimContext const& ctx)
|
||||
{
|
||||
// Make sure multisigning is enabled before we check for multisignatures.
|
||||
if ((view().flags() & tapENABLE_TESTING) ||
|
||||
(view().rules().enabled(featureMultiSign, ctx_.config.features)))
|
||||
if ((ctx.flags & tapENABLE_TESTING) ||
|
||||
(ctx.view.rules().enabled(featureMultiSign,
|
||||
ctx.app.config().features)))
|
||||
{
|
||||
// If the mSigningPubKey is empty, then we must be multisigning.
|
||||
if (mSigningPubKey.getAccountPublic ().empty ())
|
||||
return checkMultiSign ();
|
||||
auto pk =
|
||||
RippleAddress::createAccountPublic(
|
||||
ctx.tx.getSigningPubKey());
|
||||
|
||||
// If the pk is empty, then we must be multi-signing.
|
||||
if (pk.getAccountPublic ().empty ())
|
||||
return checkMultiSign (ctx);
|
||||
}
|
||||
|
||||
return checkSingleSign ();
|
||||
return checkSingleSign (ctx);
|
||||
}
|
||||
|
||||
TER Transactor::checkSingleSign ()
|
||||
void
|
||||
Transactor::checkMasterSign ()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (! sle)
|
||||
return tefFAILURE; // We really expected to find the account.
|
||||
if ((view().flags() & tapENABLE_TESTING) ||
|
||||
(view().rules().enabled(featureMultiSign,
|
||||
ctx_.app.config().features)))
|
||||
{
|
||||
if (mSigningPubKey.getAccountPublic().empty())
|
||||
// Multisign obviously doesn't use the master key
|
||||
return;
|
||||
}
|
||||
|
||||
mSigMaster = calcAccountID(mSigningPubKey) == account_;
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::checkSingleSign (PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::account(id));
|
||||
auto const hasAuthKey = sle->isFieldPresent (sfRegularKey);
|
||||
|
||||
// Consistency: Check signature
|
||||
// Verify the transaction's signing public key is authorized for signing.
|
||||
AccountID const idFromPubKey = calcAccountID(mSigningPubKey);
|
||||
if (idFromPubKey == account_)
|
||||
auto const pk =
|
||||
RippleAddress::createAccountPublic(
|
||||
ctx.tx.getSigningPubKey());
|
||||
auto const pkAccount = calcAccountID(pk);
|
||||
if (pkAccount == id)
|
||||
{
|
||||
// Authorized to continue.
|
||||
mSigMaster = true;
|
||||
if (sle->isFlag(lsfDisableMaster))
|
||||
return tefMASTER_DISABLED;
|
||||
}
|
||||
else if (mHasAuthKey && (idFromPubKey == sle->getAccountID (sfRegularKey)))
|
||||
else if (hasAuthKey &&
|
||||
(pkAccount == sle->getAccountID (sfRegularKey)))
|
||||
{
|
||||
// Authorized to continue.
|
||||
}
|
||||
else if (mHasAuthKey)
|
||||
else if (hasAuthKey)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Delay: Not authorized to use account.";
|
||||
return tefBAD_AUTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Invalid: Not authorized to use account.";
|
||||
return tefBAD_AUTH_MASTER;
|
||||
}
|
||||
@@ -303,15 +386,16 @@ TER Transactor::checkSingleSign ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::checkMultiSign ()
|
||||
TER Transactor::checkMultiSign (PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
// Get mTxnAccountID's SignerList and Quorum.
|
||||
std::shared_ptr<STLedgerEntry const> sleAccountSigners =
|
||||
view().read (keylet::signers(account_));
|
||||
ctx.view.read (keylet::signers(id));
|
||||
// If the signer list doesn't exist the account is not multi-signing.
|
||||
if (!sleAccountSigners)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Invalid: Not a multi-signing account.";
|
||||
return tefNOT_MULTI_SIGNING;
|
||||
}
|
||||
@@ -322,12 +406,12 @@ TER Transactor::checkMultiSign ()
|
||||
assert (sleAccountSigners->getFieldU32 (sfSignerListID) == 0);
|
||||
|
||||
auto accountSigners =
|
||||
SignerEntries::deserialize (*sleAccountSigners, j_, "ledger");
|
||||
SignerEntries::deserialize (*sleAccountSigners, ctx.j, "ledger");
|
||||
if (accountSigners.second != tesSUCCESS)
|
||||
return accountSigners.second;
|
||||
|
||||
// Get the array of transaction signers.
|
||||
STArray const& txSigners (tx().getFieldArray (sfSigners));
|
||||
STArray const& txSigners (ctx.tx.getFieldArray (sfSigners));
|
||||
|
||||
// Walk the accountSigners performing a variety of checks and see if
|
||||
// the quorum is met.
|
||||
@@ -346,7 +430,7 @@ TER Transactor::checkMultiSign ()
|
||||
{
|
||||
if (++iter == accountSigners.first.end ())
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Invalid SigningAccount.Account.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
@@ -354,7 +438,7 @@ TER Transactor::checkMultiSign ()
|
||||
if (iter->account != txSignerAcctID)
|
||||
{
|
||||
// The SigningAccount is not in the SignerEntries.
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Invalid SigningAccount.Account.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
@@ -391,8 +475,8 @@ TER Transactor::checkMultiSign ()
|
||||
|
||||
// In any of these cases we need to know whether the account is in
|
||||
// the ledger. Determine that now.
|
||||
std::shared_ptr<STLedgerEntry const> sleTxSignerRoot =
|
||||
view().read (keylet::account(txSignerAcctID));
|
||||
auto sleTxSignerRoot =
|
||||
ctx.view.read (keylet::account(txSignerAcctID));
|
||||
|
||||
if (signingAcctIDFromPubKey == txSignerAcctID)
|
||||
{
|
||||
@@ -405,7 +489,7 @@ TER Transactor::checkMultiSign ()
|
||||
|
||||
if (signerAccountFlags & lsfDisableMaster)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Signer:Account lsfDisableMaster.";
|
||||
return tefMASTER_DISABLED;
|
||||
}
|
||||
@@ -417,21 +501,21 @@ TER Transactor::checkMultiSign ()
|
||||
// Public key must hash to the account's regular key.
|
||||
if (!sleTxSignerRoot)
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Non-phantom signer lacks account root.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (!sleTxSignerRoot->isFieldPresent (sfRegularKey))
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Account lacks RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
if (signingAcctIDFromPubKey !=
|
||||
sleTxSignerRoot->getAccountID (sfRegularKey))
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Account doesn't match RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
@@ -443,7 +527,7 @@ TER Transactor::checkMultiSign ()
|
||||
// Cannot perform transaction if quorum is not met.
|
||||
if (weightSum < sleAccountSigners->getFieldU32 (sfSignerQuorum))
|
||||
{
|
||||
JLOG(j_.trace) <<
|
||||
JLOG(ctx.j.trace) <<
|
||||
"applyTransaction: Signers failed to meet quorum.";
|
||||
return tefBAD_QUORUM;
|
||||
}
|
||||
@@ -494,47 +578,33 @@ Transactor::operator()()
|
||||
JLOG(j_.trace) <<
|
||||
"applyTransaction>";
|
||||
|
||||
uint256 const& txID = tx().getTransactionID ();
|
||||
|
||||
if (!txID)
|
||||
{
|
||||
JLOG(j_.warning) <<
|
||||
"applyTransaction: transaction id may not be zero";
|
||||
auto const result =
|
||||
std::make_pair(temINVALID_FLAG, false);
|
||||
log(result, j_);
|
||||
return result;
|
||||
}
|
||||
auto const txID = ctx_.tx.getTransactionID ();
|
||||
|
||||
#ifdef BEAST_DEBUG
|
||||
{
|
||||
Serializer ser;
|
||||
tx().add (ser);
|
||||
ctx_.tx.add (ser);
|
||||
SerialIter sit(ser.slice());
|
||||
STTx s2 (sit);
|
||||
|
||||
if (! s2.isEquivalent(tx()))
|
||||
if (! s2.isEquivalent(ctx_.tx))
|
||||
{
|
||||
JLOG(j_.fatal) <<
|
||||
"Transaction serdes mismatch";
|
||||
JLOG(j_.info) << to_string(tx().getJson (0));
|
||||
JLOG(j_.info) << to_string(ctx_.tx.getJson (0));
|
||||
JLOG(j_.fatal) << s2.getJson (0);
|
||||
assert (false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TER terResult = apply();
|
||||
auto terResult = ctx_.preclaimResult;
|
||||
if (terResult == tesSUCCESS)
|
||||
terResult = apply();
|
||||
|
||||
if (terResult == temUNKNOWN)
|
||||
{
|
||||
JLOG(j_.warning) <<
|
||||
"applyTransaction: Invalid transaction: unknown transaction type";
|
||||
auto const result =
|
||||
std::make_pair(temUNKNOWN, false);
|
||||
log(result, j_);
|
||||
return result;
|
||||
}
|
||||
// No transaction can return temUNKNOWN from apply,
|
||||
// and it can't be passed in from a preclaim.
|
||||
assert(terResult != temUNKNOWN);
|
||||
|
||||
if (j_.debug)
|
||||
{
|
||||
@@ -550,7 +620,7 @@ Transactor::operator()()
|
||||
}
|
||||
|
||||
bool didApply = isTesSuccess (terResult);
|
||||
auto fee = tx().getFieldAmount(sfFee).xrp ();
|
||||
auto fee = ctx_.tx.getFieldAmount(sfFee).xrp ();
|
||||
|
||||
if (ctx_.size() > 5200)
|
||||
terResult = tecOVERSIZE;
|
||||
@@ -589,32 +659,19 @@ Transactor::operator()()
|
||||
ctx_.discard();
|
||||
|
||||
auto const txnAcct = view().peek(
|
||||
keylet::account(tx().getAccountID(sfAccount)));
|
||||
keylet::account(ctx_.tx.getAccountID(sfAccount)));
|
||||
|
||||
if (txnAcct)
|
||||
{
|
||||
std::uint32_t t_seq = tx().getSequence ();
|
||||
std::uint32_t t_seq = ctx_.tx.getSequence ();
|
||||
std::uint32_t a_seq = txnAcct->getFieldU32 (sfSequence);
|
||||
|
||||
if (a_seq < t_seq)
|
||||
terResult = terPRE_SEQ;
|
||||
else if (a_seq > t_seq)
|
||||
terResult = tefPAST_SEQ;
|
||||
else
|
||||
{
|
||||
auto const balance = txnAcct->getFieldAmount (sfBalance).xrp ();
|
||||
|
||||
// balance should have already been
|
||||
// checked in checkFee / preFlight.
|
||||
assert(balance != zero && (!view().open() || balance >= fee));
|
||||
// We retry/reject the transaction if the account
|
||||
// balance is zero or we're applying against an open
|
||||
// ledger and the balance is less than the fee
|
||||
if ((balance == zero) ||
|
||||
(view().open() && (balance < fee)))
|
||||
{
|
||||
// Account has no funds or ledger is open
|
||||
terResult = terINSUF_FEE_B;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fee > balance)
|
||||
fee = balance;
|
||||
txnAcct->setFieldAmount (sfBalance, balance - fee);
|
||||
@@ -626,13 +683,6 @@ Transactor::operator()()
|
||||
view().update (txnAcct);
|
||||
didApply = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
terResult = terNO_ACCOUNT;
|
||||
}
|
||||
}
|
||||
else if (!didApply)
|
||||
{
|
||||
JLOG(j_.debug) << "Not applying transaction " << txID;
|
||||
|
||||
@@ -51,6 +51,66 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct PreflightResult
|
||||
{
|
||||
public:
|
||||
PreflightContext const ctx;
|
||||
TER const ter;
|
||||
|
||||
PreflightResult(PreflightContext const& ctx_,
|
||||
TER ter_)
|
||||
: ctx(ctx_)
|
||||
, ter(ter_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** State information when determining if a tx is likely to claim a fee. */
|
||||
struct PreclaimContext
|
||||
{
|
||||
public:
|
||||
Application& app;
|
||||
ReadView const& view;
|
||||
TER preflightResult;
|
||||
STTx const& tx;
|
||||
ApplyFlags flags;
|
||||
beast::Journal j;
|
||||
|
||||
PreclaimContext(Application& app_, ReadView const& view_,
|
||||
TER preflightResult_, STTx const& tx_,
|
||||
ApplyFlags flags_, beast::Journal j_ = {})
|
||||
: app(app_)
|
||||
, view(view_)
|
||||
, preflightResult(preflightResult_)
|
||||
, tx(tx_)
|
||||
, flags(flags_)
|
||||
, j(j_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct PreclaimResult
|
||||
{
|
||||
public:
|
||||
PreclaimContext const ctx;
|
||||
TER const ter;
|
||||
std::uint64_t const baseFee;
|
||||
|
||||
PreclaimResult(PreclaimContext const& ctx_,
|
||||
TER ter_, std::uint64_t const& baseFee_)
|
||||
: ctx(ctx_)
|
||||
, ter(ter_)
|
||||
, baseFee(baseFee_)
|
||||
{
|
||||
}
|
||||
|
||||
PreclaimResult(PreclaimContext const& ctx_,
|
||||
std::pair<TER, std::uint64_t> const& result)
|
||||
: PreclaimResult(ctx_, result.first, result.second)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class Transactor
|
||||
{
|
||||
protected:
|
||||
@@ -61,7 +121,6 @@ protected:
|
||||
XRPAmount mFeeDue;
|
||||
XRPAmount mPriorBalance; // Balance before fees.
|
||||
XRPAmount mSourceBalance; // Balance after fees.
|
||||
bool mHasAuthKey;
|
||||
bool mSigMaster;
|
||||
RippleAddress mSigningPubKey;
|
||||
|
||||
@@ -82,11 +141,43 @@ public:
|
||||
return ctx_.view();
|
||||
}
|
||||
|
||||
STTx const&
|
||||
tx() const
|
||||
/////////////////////////////////////////////////////
|
||||
/*
|
||||
These static functions are called from invoke_preclaim<Tx>
|
||||
using name hiding to accomplish compile-time polymorphism,
|
||||
so derived classes can override for different or extra
|
||||
functionality. Use with care, as these are not really
|
||||
virtual and so don't have the compiler-time protection that
|
||||
comes with it.
|
||||
*/
|
||||
|
||||
static
|
||||
TER
|
||||
checkSeq (PreclaimContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
checkFee (PreclaimContext const& ctx, std::uint64_t baseFee);
|
||||
|
||||
static
|
||||
TER
|
||||
checkSign (PreclaimContext const& ctx);
|
||||
|
||||
// Returns the fee in fee units, not scaled for load.
|
||||
static
|
||||
std::uint64_t
|
||||
calculateBaseFee (
|
||||
PreclaimContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const &ctx)
|
||||
{
|
||||
return ctx_.tx;
|
||||
// Most transactors do nothing
|
||||
// after checkSeq/Fee/Sign.
|
||||
return tesSUCCESS;
|
||||
}
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
protected:
|
||||
TER
|
||||
@@ -95,21 +186,22 @@ protected:
|
||||
explicit
|
||||
Transactor (ApplyContext& ctx);
|
||||
|
||||
// Returns the fee in fee units, not scaled for load.
|
||||
virtual std::uint64_t calculateBaseFee ();
|
||||
|
||||
virtual void preCompute();
|
||||
|
||||
virtual TER doApply () = 0;
|
||||
|
||||
private:
|
||||
TER checkSeq ();
|
||||
TER checkSign ();
|
||||
void setSeq ();
|
||||
TER payFee ();
|
||||
TER checkSingleSign ();
|
||||
TER checkMultiSign ();
|
||||
void checkMasterSign ();
|
||||
static TER checkSingleSign (PreclaimContext const& ctx);
|
||||
static TER checkMultiSign (PreclaimContext const& ctx);
|
||||
};
|
||||
|
||||
/** Performs early sanity checks on the txid */
|
||||
TER
|
||||
preflight0(PreflightContext const& ctx);
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields */
|
||||
TER
|
||||
preflight1 (PreflightContext const& ctx);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/app/tx/impl/applyImpl.h>
|
||||
#include <ripple/app/tx/impl/ApplyContext.h>
|
||||
#include <ripple/app/tx/impl/CancelOffer.h>
|
||||
#include <ripple/app/tx/impl/CancelTicket.h>
|
||||
@@ -55,10 +56,82 @@ invoke_preflight (PreflightContext const& ctx)
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change ::preflight(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
invoke_preclaim<T> uses name hiding to accomplish
|
||||
compile-time polymorphism of (presumably) static
|
||||
class functions for Transactor and derived classes.
|
||||
*/
|
||||
template<class T>
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// If the transactor requires a valid account and the transaction doesn't
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const baseFee = T::calculateBaseFee(ctx);
|
||||
TER result;
|
||||
if (id != zero)
|
||||
{
|
||||
result = T::checkSeq(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::checkFee(ctx, baseFee);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::checkSign(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return { result, 0 };
|
||||
|
||||
result = T::preclaim(ctx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return{ result, 0 };
|
||||
}
|
||||
else
|
||||
{
|
||||
result = tesSUCCESS;
|
||||
}
|
||||
|
||||
return { tesSUCCESS, baseFee };
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, std::uint64_t>
|
||||
invoke_preclaim (PreclaimContext const& ctx)
|
||||
{
|
||||
switch(ctx.tx.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET: return invoke_preclaim<SetAccount>(ctx);
|
||||
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
|
||||
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
|
||||
case ttPAYMENT: return invoke_preclaim<Payment>(ctx);
|
||||
case ttSUSPAY_CREATE: return invoke_preclaim<SusPayCreate>(ctx);
|
||||
case ttSUSPAY_FINISH: return invoke_preclaim<SusPayFinish>(ctx);
|
||||
case ttSUSPAY_CANCEL: return invoke_preclaim<SusPayCancel>(ctx);
|
||||
case ttREGULAR_KEY_SET: return invoke_preclaim<SetRegularKey>(ctx);
|
||||
case ttSIGNER_LIST_SET: return invoke_preclaim<SetSignerList>(ctx);
|
||||
case ttTICKET_CANCEL: return invoke_preclaim<CancelTicket>(ctx);
|
||||
case ttTICKET_CREATE: return invoke_preclaim<CreateTicket>(ctx);
|
||||
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return invoke_preclaim<Change>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::pair<TER, bool>
|
||||
invoke_apply (ApplyContext& ctx)
|
||||
@@ -80,57 +153,108 @@ invoke_apply (ApplyContext& ctx)
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: { Change p(ctx); return p(); }
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, false };
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
PreflightResult
|
||||
preflight (Rules const& rules, STTx const& tx,
|
||||
ApplyFlags flags, SigVerify verify,
|
||||
Config const& config, beast::Journal j)
|
||||
{
|
||||
PreflightContext const pfctx(tx,
|
||||
rules, flags, verify, config, j);
|
||||
try
|
||||
{
|
||||
PreflightContext pfctx(tx,
|
||||
rules, flags, verify, config, j);
|
||||
return invoke_preflight(pfctx);
|
||||
return{ pfctx, invoke_preflight(pfctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return tefEXCEPTION;
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return tefEXCEPTION;
|
||||
return{ pfctx, tefEXCEPTION };
|
||||
}
|
||||
}
|
||||
|
||||
PreclaimResult
|
||||
preclaim (PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view)
|
||||
{
|
||||
boost::optional<PreclaimContext const> ctx;
|
||||
if (preflightResult.ctx.rules != view.rules())
|
||||
{
|
||||
auto secondFlight = preflight(view.rules(),
|
||||
preflightResult.ctx.tx, preflightResult.ctx.flags,
|
||||
preflightResult.ctx.verify, preflightResult.ctx.config,
|
||||
preflightResult.ctx.j);
|
||||
ctx.emplace(app, view, secondFlight.ter, secondFlight.ctx.tx,
|
||||
secondFlight.ctx.flags, secondFlight.ctx.j);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.emplace(
|
||||
app, view, preflightResult.ter, preflightResult.ctx.tx,
|
||||
preflightResult.ctx.flags, preflightResult.ctx.j);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ctx->preflightResult != tesSUCCESS)
|
||||
return { *ctx, ctx->preflightResult, 0 };
|
||||
return{ *ctx, invoke_preclaim(*ctx) };
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(ctx->j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return{ *ctx, tefEXCEPTION, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<TER, bool>
|
||||
doapply(Application& app, OpenView& view,
|
||||
STTx const& tx, ApplyFlags flags,
|
||||
Config const& config, beast::Journal j)
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view)
|
||||
{
|
||||
if (preclaimResult.ctx.view.seq() != view.seq())
|
||||
{
|
||||
// Logic error from the caller. Don't have enough
|
||||
// info to recover.
|
||||
return{ tefEXCEPTION, false };
|
||||
}
|
||||
try
|
||||
{
|
||||
ApplyContext ctx(app, view,
|
||||
tx, flags, config, j);
|
||||
if (preclaimResult.ter != tesSUCCESS
|
||||
&& !isTecClaim(preclaimResult.ter))
|
||||
return{ preclaimResult.ter, false };
|
||||
ApplyContext ctx(
|
||||
app, view, preclaimResult.ctx.tx, preclaimResult.ter,
|
||||
preclaimResult.baseFee, preclaimResult.ctx.flags,
|
||||
preclaimResult.ctx.j);
|
||||
return invoke_apply(ctx);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
JLOG(preclaimResult.ctx.j.fatal) <<
|
||||
"apply: " << e.what();
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(j.fatal) <<
|
||||
JLOG(preclaimResult.ctx.j.fatal) <<
|
||||
"apply: <unknown exception>";
|
||||
return { tefEXCEPTION, false };
|
||||
}
|
||||
@@ -144,9 +268,8 @@ apply (Application& app, OpenView& view,
|
||||
{
|
||||
auto pfresult = preflight(view.rules(),
|
||||
tx, flags, verify, config, j);
|
||||
if (pfresult != tesSUCCESS)
|
||||
return { pfresult, false };
|
||||
return doapply(app, view, tx, flags, config, j);
|
||||
auto pcresult = preclaim(pfresult, app, view);
|
||||
return doApply(pcresult, app, view);
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
79
src/ripple/app/tx/impl/applyImpl.h
Normal file
79
src/ripple/app/tx/impl/applyImpl.h
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2015 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_APPLYIMPL_H_INCLUDED
|
||||
#define RIPPLE_TX_APPLYIMPL_H_INCLUDED
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
|
||||
struct PreflightResult;
|
||||
struct PreclaimResult;
|
||||
|
||||
/** Gate a transaction based on static information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that do not require a ledger.
|
||||
|
||||
@return A PreflightResult object constaining, among
|
||||
other things, the TER code.
|
||||
*/
|
||||
PreflightResult
|
||||
preflight(Rules const& rules, STTx const& tx,
|
||||
ApplyFlags flags, SigVerify verify,
|
||||
Config const& config, beast::Journal j);
|
||||
|
||||
/** Gate a transaction based on static ledger information.
|
||||
|
||||
The transaction is checked against all possible
|
||||
validity constraints that DO require a ledger.
|
||||
|
||||
If preclaim succeeds, then the transaction is very
|
||||
likely to claim a fee. This will determine if the
|
||||
transaction is safe to relay without being applied
|
||||
to the open ledger.
|
||||
|
||||
"Succeeds" in this case is defined as returning a
|
||||
`tes` or `tec`, since both lead to claiming a fee.
|
||||
|
||||
@return A PreclaimResult object containing, among
|
||||
other things the TER code and the base fee value for
|
||||
this transaction.
|
||||
*/
|
||||
PreclaimResult
|
||||
preclaim(PreflightResult const& preflightResult,
|
||||
Application& app, OpenView const& view);
|
||||
|
||||
/** Apply a prechecked transaction to an OpenView.
|
||||
|
||||
See also: apply()
|
||||
|
||||
Precondition: The transaction has been checked
|
||||
and validated using the above functions.
|
||||
|
||||
@return A pair with the TER and a bool indicating
|
||||
whether or not the transaction was applied.
|
||||
*/
|
||||
std::pair<TER, bool>
|
||||
doApply(PreclaimResult const& preclaimResult,
|
||||
Application& app, OpenView& view);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user