TER doApply () override { // Divvy 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 & tfNoDivvyDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); AccountID const uDstAccountID (mTxn.getFieldAccount160 (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 index = getAccountRootIndex (uDstAccountID); SLE::pointer sleDst (mEngine->view().entryCache (ltACCOUNT_ROOT, index)); if (!sleDst) { // Destination account does not exist. if (!saDstAmount.native ()) { m_journal.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->getLedger ()->getReserve (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_XDV; } // Create the account. sleDst = std::make_shared<SLE>(ltACCOUNT_ROOT, getAccountRootIndex (uDstAccountID)); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); mEngine->view().entryCreate(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. // 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; } 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. mEngine->view().entryModify (sleDst); } TER terResult; bool const bDivvy = bPaths || bMax || !saDstAmount.native (); // XXX Should bMax be sufficient to imply divvy? if (bDivvy) { // Divvy payment with at least one intermediate step and uses // transitive balances. // Copy paths into an editable class. STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths); try { path::DivvyCalc::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::DivvyCalc::Output rc; { ScopedDeferCredits g (mEngine->view ()); rc = path::DivvyCalc::divvyCalculate ( mEngine->view (), maxSourceAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, &rcInput); } // 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->view ().setDeliveredAmount (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 XDV 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. std::uint64_t const uReserve = mEngine->getLedger ()->getReserve (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; } 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); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags () & lsfPasswordSpent)) sleDst->clearFlag (lsfPasswordSpent); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo (terResult, strToken, strHuman)) { m_journal.trace << strToken << ": " << strHuman; } else { assert (false); } return terResult; }
TER doApply () override { // 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); Account const uDstAccountID (mTxn.getFieldAccount160 (sfDestination)); STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount)); STAmount maxSourceAmount; if (bMax) maxSourceAmount = mTxn.getFieldAmount (sfSendMax); else if (saDstAmount.isNative ()) 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 (); m_journal.trace << "maxSourceAmount=" << maxSourceAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount)) return temBAD_AMOUNT; if (uTxFlags & tfPaymentMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (!uDstAccountID) { m_journal.trace << "Malformed transaction: Payment destination account not specified."; return temDST_NEEDED; } else if (bMax && maxSourceAmount <= zero) { m_journal.trace << "Malformed transaction: bad max amount: " << maxSourceAmount.getFullText (); return temBAD_AMOUNT; } else if (saDstAmount <= zero) { m_journal.trace << "Malformed transaction: bad dst amount: " << saDstAmount.getFullText (); return temBAD_AMOUNT; } else if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) { m_journal.trace << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } else 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 transaction:" << " src=" << to_string (mTxnAccountID) << " dst=" << to_string (uDstAccountID) << " src_cur=" << to_string (uSrcCurrency) << " dst_cur=" << to_string (uDstCurrency); return temREDUNDANT; } else if (bMax && maxSourceAmount == saDstAmount && maxSourceAmount.getCurrency () == saDstAmount.getCurrency ()) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Redundant SendMax."; return temREDUNDANT_SEND_MAX; } else if (bXRPDirect && bMax) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: SendMax specified for XRP to XRP."; return temBAD_SEND_XRP_MAX; } else if (bXRPDirect && bPaths) { // XRP is sent without paths. m_journal.trace << "Malformed transaction: Paths specified for XRP to XRP."; return temBAD_SEND_XRP_PATHS; } else if (bXRPDirect && partialPaymentAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } else if (bXRPDirect && limitQuality) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Limit quality specified for XRP to XRP."; return temBAD_SEND_XRP_LIMIT; } else 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; } // // Open a ledger for editing. auto const index = getAccountRootIndex (uDstAccountID); SLE::pointer sleDst (mEngine->entryCache (ltACCOUNT_ROOT, index)); if (!sleDst) { // Destination account does not exist. if (!saDstAmount.isNative ()) { m_journal.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.getNValue () < mEngine->getLedger ()->getReserve (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. auto const newIndex = getAccountRootIndex (uDstAccountID); sleDst = mEngine->entryCreate (ltACCOUNT_ROOT, newIndex); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); } 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. // 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 tefDST_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. mEngine->entryModify (sleDst); } TER terResult; bool const bRipple = bPaths || bMax || !saDstAmount.isNative (); // 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 { 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 { auto rc = path::RippleCalc::rippleCalculate ( mEngine->view (), maxSourceAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, &rcInput); // 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->view ().setDeliveredAmount (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. std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); // This is the total reserve in drops. // TODO(tom): there should be a class for this. std::uint64_t const uReserve (mEngine->getLedger ()->getReserve (uOwnerCount)); // mPriorBalance is the balance on the sending account BEFORE the fees were charged. // // Make sure have enough reserve to send. Allow final spend to use // reserve for fee. auto const mmm = std::max(uReserve, mTxn.getTransactionFee ().getNValue ()); if (mPriorBalance < saDstAmount + mmm) { // Vote no. // However, transaction might succeed, if applied in a different order. m_journal.trace << "Delay transaction: Insufficient funds: " << " " << mPriorBalance.getText () << " / " << (saDstAmount + uReserve).getText () << " (" << uReserve << ")"; terResult = tecUNFUNDED_PAYMENT; } 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); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags () & lsfPasswordSpent)) sleDst->clearFlag (lsfPasswordSpent); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo (terResult, strToken, strHuman)) { m_journal.trace << strToken << ": " << strHuman; } else { assert (false); } return terResult; }