Refactor View, MetaView, and tx processing:

This tidies up the View interface and makes transaction
application a free function, with the removal of the
TransactionEngine class. A new class ApplyContext provides
all the state information needed to apply a Transactor. The
Transactor is refactored to perform all the processing
activities previously part of TransactionEngine.

The calculation of metadata from a MetaView is improved.
A new apply function performs all the steps for calculating
and inserting metadata into the tx map.

Transaction processing code path is passed a Config instead
of retrieving the global, and uses the Journal supplied in
the call to apply() consistently.

To support transaction processing and RPC operations, a
new POD type ViewInfo is added which consolidates static
information about open and closed ledgers, such as the ledger
sequence number or the closing times. Ledger and MetaView are
refactored to use this info.

The ViewInfo now contains the "open ledger" setting. The
tapOPEN_LEDGER ViewFlag is removed. The view property of
being an open ledger is obtained from the base or by using
the MetaView constructor which presents a closed ledger as
an open one.

View, MetaView:

* Fix missing includes
* Add apply free function
* Use Journal in TransactionEngine
* Use BasicView in TransactionEngine
* inline NetworkOPs::batchApply
* Add shallow_copy, open_ledger MetaView ctor tags
* Add ViewInfo with open flag, seq, close times
* Make parent_ a reference
* Tidy up ctor arguments and base_ name
* Remove tapOPEN_LEDGER
* add assert to MetaView::apply
* ViewInfo comment
* Throw, pass Journal in txInsert
* Add BasicView::txCount

TransactionEngine:

* Add apply
* Make TransactionEngine private
* Refactor MetaView::apply and apply()
* Rename to TxMeta
* Refactor treatment of metadata in MetaView, TransactionEngine
* Rename to ApplyContext
* Use ApplyContext& in Transactor
* Pass Config in ApplyContext
* Declare Transactor classes in headers
* Use view flags in Transactor
This commit is contained in:
Vinnie Falco
2015-06-26 17:14:06 -07:00
parent b11b9939f4
commit f96ac3db67
71 changed files with 4229 additions and 3615 deletions

View File

@@ -18,8 +18,8 @@
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/tx/impl/Payment.h>
#include <ripple/app/paths/RippleCalc.h>
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/TxFlags.h>
@@ -27,379 +27,349 @@ namespace ripple {
// See https://ripple.com/wiki/Transaction_Format#Payment_.280.29
class Payment
: public Transactor
TER
Payment::preCheck ()
{
/* The largest number of paths we allow */
static std::size_t const MaxPathSize = 6;
std::uint32_t const uTxFlags = mTxn.getFlags ();
/* The longest path we allow */
static std::size_t const MaxPathLength = 8;
public:
Payment (
STTx const& txn,
ViewFlags params,
TransactionEngine* engine)
: Transactor (
txn,
params,
engine,
deprecatedLogs().journal("Payment"))
if (uTxFlags & tfPaymentMask)
{
j_.trace << "Malformed transaction: " <<
"Invalid flags set.";
return temINVALID_FLAG;
}
TER preCheck () override
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
bool const limitQuality = uTxFlags & tfLimitQuality;
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
if (bMax)
maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
else if (saDstAmount.native ())
maxSourceAmount = saDstAmount;
else
maxSourceAmount = STAmount (
{ saDstAmount.getCurrency (), mTxnAccountID },
saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
auto const& uSrcCurrency = maxSourceAmount.getCurrency ();
auto const& uDstCurrency = saDstAmount.getCurrency ();
// isZero() is XRP. FIX!
bool const bXRPDirect = uSrcCurrency.isZero () && uDstCurrency.isZero ();
if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount))
return temBAD_AMOUNT;
AccountID const uDstAccountID (mTxn.getAccountID (sfDestination));
if (!uDstAccountID)
{
std::uint32_t const uTxFlags = mTxn.getFlags ();
if (uTxFlags & tfPaymentMask)
{
m_journal.trace << "Malformed transaction: " <<
"Invalid flags set.";
return temINVALID_FLAG;
}
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
bool const limitQuality = uTxFlags & tfLimitQuality;
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
if (bMax)
maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
else if (saDstAmount.native ())
maxSourceAmount = saDstAmount;
else
maxSourceAmount = STAmount (
{ saDstAmount.getCurrency (), mTxnAccountID },
saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
auto const& uSrcCurrency = maxSourceAmount.getCurrency ();
auto const& uDstCurrency = saDstAmount.getCurrency ();
// isZero() is XRP. FIX!
bool const bXRPDirect = uSrcCurrency.isZero () && uDstCurrency.isZero ();
if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount))
return temBAD_AMOUNT;
AccountID const uDstAccountID (mTxn.getAccountID (sfDestination));
if (!uDstAccountID)
{
m_journal.trace << "Malformed transaction: " <<
"Payment destination account not specified.";
return temDST_NEEDED;
}
if (bMax && maxSourceAmount <= zero)
{
m_journal.trace << "Malformed transaction: " <<
"bad max amount: " << maxSourceAmount.getFullText ();
return temBAD_AMOUNT;
}
if (saDstAmount <= zero)
{
m_journal.trace << "Malformed transaction: "<<
"bad dst amount: " << saDstAmount.getFullText ();
return temBAD_AMOUNT;
}
if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency)
{
m_journal.trace <<"Malformed transaction: " <<
"Bad currency.";
return temBAD_CURRENCY;
}
if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths)
{
// You're signing yourself a payment.
// If bPaths is true, you might be trying some arbitrage.
m_journal.trace << "Malformed transaction: " <<
"Redundant payment from " << to_string (mTxnAccountID) <<
" to self without path for " << to_string (uDstCurrency);
return temREDUNDANT;
}
if (bXRPDirect && bMax)
{
// Consistent but redundant transaction.
m_journal.trace << "Malformed transaction: " <<
"SendMax specified for XRP to XRP.";
return temBAD_SEND_XRP_MAX;
}
if (bXRPDirect && bPaths)
{
// XRP is sent without paths.
m_journal.trace << "Malformed transaction: " <<
"Paths specified for XRP to XRP.";
return temBAD_SEND_XRP_PATHS;
}
if (bXRPDirect && partialPaymentAllowed)
{
// Consistent but redundant transaction.
m_journal.trace << "Malformed transaction: " <<
"Partial payment specified for XRP to XRP.";
return temBAD_SEND_XRP_PARTIAL;
}
if (bXRPDirect && limitQuality)
{
// Consistent but redundant transaction.
m_journal.trace << "Malformed transaction: " <<
"Limit quality specified for XRP to XRP.";
return temBAD_SEND_XRP_LIMIT;
}
if (bXRPDirect && !defaultPathsAllowed)
{
// Consistent but redundant transaction.
m_journal.trace << "Malformed transaction: " <<
"No ripple direct specified for XRP to XRP.";
return temBAD_SEND_XRP_NO_DIRECT;
}
return Transactor::preCheck ();
j_.trace << "Malformed transaction: " <<
"Payment destination account not specified.";
return temDST_NEEDED;
}
if (bMax && maxSourceAmount <= zero)
{
j_.trace << "Malformed transaction: " <<
"bad max amount: " << maxSourceAmount.getFullText ();
return temBAD_AMOUNT;
}
if (saDstAmount <= zero)
{
j_.trace << "Malformed transaction: "<<
"bad dst amount: " << saDstAmount.getFullText ();
return temBAD_AMOUNT;
}
if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency)
{
j_.trace <<"Malformed transaction: " <<
"Bad currency.";
return temBAD_CURRENCY;
}
if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths)
{
// You're signing yourself a payment.
// If bPaths is true, you might be trying some arbitrage.
j_.trace << "Malformed transaction: " <<
"Redundant payment from " << to_string (mTxnAccountID) <<
" to self without path for " << to_string (uDstCurrency);
return temREDUNDANT;
}
if (bXRPDirect && bMax)
{
// Consistent but redundant transaction.
j_.trace << "Malformed transaction: " <<
"SendMax specified for XRP to XRP.";
return temBAD_SEND_XRP_MAX;
}
if (bXRPDirect && bPaths)
{
// XRP is sent without paths.
j_.trace << "Malformed transaction: " <<
"Paths specified for XRP to XRP.";
return temBAD_SEND_XRP_PATHS;
}
if (bXRPDirect && partialPaymentAllowed)
{
// Consistent but redundant transaction.
j_.trace << "Malformed transaction: " <<
"Partial payment specified for XRP to XRP.";
return temBAD_SEND_XRP_PARTIAL;
}
if (bXRPDirect && limitQuality)
{
// Consistent but redundant transaction.
j_.trace << "Malformed transaction: " <<
"Limit quality specified for XRP to XRP.";
return temBAD_SEND_XRP_LIMIT;
}
if (bXRPDirect && !defaultPathsAllowed)
{
// Consistent but redundant transaction.
j_.trace << "Malformed transaction: " <<
"No ripple direct specified for XRP to XRP.";
return temBAD_SEND_XRP_NO_DIRECT;
}
TER doApply () override
return Transactor::preCheck ();
}
TER
Payment::doApply ()
{
// Ripple if source or destination is non-native or if there are paths.
std::uint32_t const uTxFlags = mTxn.getFlags ();
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
bool const limitQuality = uTxFlags & tfLimitQuality;
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
AccountID const uDstAccountID (mTxn.getAccountID (sfDestination));
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
if (bMax)
maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
else if (saDstAmount.native ())
maxSourceAmount = saDstAmount;
else
maxSourceAmount = STAmount (
{saDstAmount.getCurrency (), mTxnAccountID},
saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
j_.trace <<
"maxSourceAmount=" << maxSourceAmount.getFullText () <<
" saDstAmount=" << saDstAmount.getFullText ();
// Open a ledger for editing.
auto const k = keylet::account(uDstAccountID);
SLE::pointer sleDst = view().peek (k);
if (!sleDst)
{
// Ripple if source or destination is non-native or if there are paths.
std::uint32_t const uTxFlags = mTxn.getFlags ();
bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
bool const limitQuality = uTxFlags & tfLimitQuality;
bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect);
bool const bPaths = mTxn.isFieldPresent (sfPaths);
bool const bMax = mTxn.isFieldPresent (sfSendMax);
AccountID const uDstAccountID (mTxn.getAccountID (sfDestination));
STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
STAmount maxSourceAmount;
if (bMax)
maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
else if (saDstAmount.native ())
maxSourceAmount = saDstAmount;
else
maxSourceAmount = STAmount (
{saDstAmount.getCurrency (), mTxnAccountID},
saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
m_journal.trace <<
"maxSourceAmount=" << maxSourceAmount.getFullText () <<
" saDstAmount=" << saDstAmount.getFullText ();
// Open a ledger for editing.
auto const k = keylet::account(uDstAccountID);
SLE::pointer sleDst = mEngine->view().peek (k);
if (!sleDst)
// Destination account does not exist.
if (!saDstAmount.native ())
{
// Destination account does not exist.
if (!saDstAmount.native ())
{
m_journal.trace <<
"Delay transaction: Destination account does not exist.";
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 (mParams & tapOPEN_LEDGER && partialPaymentAllowed)
{
// You cannot fund an account with a partial payment.
// Make retry work smaller, by rejecting this.
m_journal.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 (mEngine->view().fees().accountReserve(0)))
{
// getReserve() is the minimum amount that an account can have.
// Reserve is not scaled by load.
m_journal.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);
mEngine->view().insert(sleDst);
// Another transaction could create the account and then this
// transaction would succeed.
return tecNO_DST;
}
else if ((sleDst->getFlags () & lsfRequireDestTag) &&
!mTxn.isFieldPresent (sfDestinationTag))
else if (view().open()
&& partialPaymentAllowed)
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
// 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.";
// We didn't make this test for a newly-formed account because there's
// no way for this field to be set.
m_journal.trace << "Malformed transaction: DestinationTag required.";
return tecDST_TAG_NEEDED;
// Another transaction could create the account and then this
// transaction would succeed.
return telNO_DST_PARTIAL;
}
else
else if (saDstAmount < STAmount (view().fees().accountReserve(0)))
{
// Tell the engine that we are intending to change the the destination
// account. The source account gets always charged a fee so it's always
// marked as modified.
mEngine->view().update (sleDst);
// getReserve() 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;
}
TER terResult;
// Create the account.
sleDst = std::make_shared<SLE>(k);
sleDst->setAccountID (sfAccount, uDstAccountID);
sleDst->setFieldU32 (sfSequence, 1);
view().insert(sleDst);
}
else if ((sleDst->getFlags () & lsfRequireDestTag) &&
!mTxn.isFieldPresent (sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
bool const bRipple = bPaths || bMax || !saDstAmount.native ();
// XXX Should bMax be sufficient to imply ripple?
// 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.";
if (bRipple)
return tecDST_TAG_NEEDED;
}
else
{
// Tell the engine that we are intending to change the the destination
// account. The source account gets always charged a fee so it's always
// marked as modified.
view().update (sleDst);
}
TER terResult;
bool const bRipple = bPaths || bMax || !saDstAmount.native ();
// XXX Should bMax be sufficient to imply ripple?
if (bRipple)
{
// Ripple payment with at least one intermediate step and uses
// transitive balances.
// Copy paths into an editable class.
STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths);
try
{
// Ripple payment with at least one intermediate step and uses
// transitive balances.
path::RippleCalc::Input rcInput;
rcInput.partialPaymentAllowed = partialPaymentAllowed;
rcInput.defaultPathsAllowed = defaultPathsAllowed;
rcInput.limitQuality = limitQuality;
rcInput.deleteUnfundedOffers = true;
rcInput.isLedgerOpen = view().open();
// Copy paths into an editable class.
STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths);
bool pathTooBig = spsPaths.size () > MaxPathSize;
try
for (auto const& path : spsPaths)
if (path.size () > MaxPathLength)
pathTooBig = true;
if (rcInput.isLedgerOpen && pathTooBig)
{
path::RippleCalc::Input rcInput;
rcInput.partialPaymentAllowed = partialPaymentAllowed;
rcInput.defaultPathsAllowed = defaultPathsAllowed;
rcInput.limitQuality = limitQuality;
rcInput.deleteUnfundedOffers = true;
rcInput.isLedgerOpen = static_cast<bool>(mParams & tapOPEN_LEDGER);
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;
{
PaymentView view (&mEngine->view());
rc = path::RippleCalc::rippleCalculate (
view,
maxSourceAmount,
saDstAmount,
uDstAccountID,
mTxnAccountID,
spsPaths,
&rcInput);
// VFALCO NOTE We might not need to apply, depending
// on the TER. But always applying *should*
// be safe.
view.apply(mEngine->view());
}
// TODO: is this right? If the amount is the correct amount, was
// the delivered amount previously set?
if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount)
mEngine->deliverAmount (rc.actualAmountOut);
terResult = rc.result ();
}
// TODO(tom): what's going on here?
if (isTerRetry (terResult))
terResult = tecPATH_DRY;
}
catch (std::exception const& e)
{
m_journal.trace <<
"Caught throw: " << e.what ();
terResult = tefEXCEPTION;
}
}
else
{
// Direct XRP payment.
// uOwnerCount is the number of entries in this legder for this
// account that require a reserve.
auto const uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
// This is the total reserve in drops.
auto const uReserve =
mEngine->view().fees().accountReserve(uOwnerCount);
// mPriorBalance is the balance on the sending account BEFORE the
// 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(mTxn.getTransactionFee (),
STAmount (uReserve));
if (mPriorBalance < saDstAmount + mmm)
{
// Vote no. However the transaction might succeed, if applied in
// a different order.
m_journal.trace << "Delay transaction: Insufficient funds: " <<
" " << mPriorBalance.getText () <<
" / " << (saDstAmount + mmm).getText () <<
" (" << uReserve << ")";
terResult = tecUNFUNDED_PAYMENT;
terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger.
}
else
{
// The source account does have enough money, so do the
// arithmetic for the transfer and make the ledger change.
mTxnAccount->setFieldAmount (sfBalance,
mSourceBalance - saDstAmount);
sleDst->setFieldAmount (sfBalance,
sleDst->getFieldAmount (sfBalance) + saDstAmount);
path::RippleCalc::Output rc;
{
PaymentView pv (view(), view().flags());
rc = path::RippleCalc::rippleCalculate (
pv,
maxSourceAmount,
saDstAmount,
uDstAccountID,
mTxnAccountID,
spsPaths,
&rcInput);
// VFALCO NOTE We might not need to apply, depending
// on the TER. But always applying *should*
// be safe.
pv.apply(view());
}
// Re-arm the password change fee if we can and need to.
if ((sleDst->getFlags () & lsfPasswordSpent))
sleDst->clearFlag (lsfPasswordSpent);
// TODO: is this right? If the amount is the correct amount, was
// the delivered amount previously set?
if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount)
ctx_.deliverAmount (rc.actualAmountOut);
terResult = tesSUCCESS;
terResult = rc.result ();
}
// TODO(tom): what's going on here?
if (isTerRetry (terResult))
terResult = tecPATH_DRY;
}
std::string strToken;
std::string strHuman;
if (transResultInfo (terResult, strToken, strHuman))
catch (std::exception const& e)
{
m_journal.trace <<
strToken << ": " << strHuman;
j_.trace <<
"Caught throw: " << e.what ();
terResult = tefEXCEPTION;
}
}
else
{
// Direct XRP payment.
// uOwnerCount is the number of entries in this legder for this
// account that require a reserve.
auto const uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
// This is the total reserve in drops.
auto const uReserve =
view().fees().accountReserve(uOwnerCount);
// mPriorBalance is the balance on the sending account BEFORE the
// 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(mTxn.getTransactionFee (),
STAmount (uReserve));
if (mPriorBalance < saDstAmount + mmm)
{
// Vote no. However the transaction might succeed, if applied in
// a different order.
j_.trace << "Delay transaction: Insufficient funds: " <<
" " << mPriorBalance.getText () <<
" / " << (saDstAmount + mmm).getText () <<
" (" << uReserve << ")";
terResult = tecUNFUNDED_PAYMENT;
}
else
{
assert (false);
// The source account does have enough money, so do the
// arithmetic for the transfer and make the ledger change.
mTxnAccount->setFieldAmount (sfBalance,
mSourceBalance - saDstAmount);
sleDst->setFieldAmount (sfBalance,
sleDst->getFieldAmount (sfBalance) + saDstAmount);
// Re-arm the password change fee if we can and need to.
if ((sleDst->getFlags () & lsfPasswordSpent))
sleDst->clearFlag (lsfPasswordSpent);
terResult = tesSUCCESS;
}
return terResult;
}
};
TER
transact_Payment (
STTx const& txn,
ViewFlags params,
TransactionEngine* engine)
{
return Payment(txn, params, engine).apply ();
std::string strToken;
std::string strHuman;
if (transResultInfo (terResult, strToken, strHuman))
{
j_.trace <<
strToken << ": " << strHuman;
}
else
{
assert (false);
}
return terResult;
}
} // ripple