hash_set<Currency> accountDestCurrencies ( AccountID const& account, std::shared_ptr<RippleLineCache> const& lrCache, bool includeXRP) { hash_set<Currency> currencies; if (includeXRP) currencies.insert (xrpCurrency()); // Even if account doesn't exist // List of ripple lines. auto& rippleLines = lrCache->getRippleLines (account); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) // Can take more currencies.insert (saBalance.getCurrency ()); } currencies.erase (badCurrency()); return currencies; }
CurrencySet accountDestCurrencies ( RippleAddress const& raAccountID, RippleLineCache::ref lrCache, bool includeXRP) { CurrencySet currencies; if (includeXRP) currencies.insert (xrpCurrency()); // Even if account doesn't exist // List of ripple lines. auto& rippleLines (lrCache->getRippleLines (raAccountID.getAccountID ())); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) // Can take more currencies.insert (saBalance.getCurrency ()); } currencies.erase (badCurrency()); return currencies; }
TER SetTrust::preflight (PreflightContext const& ctx) { auto const ret = preflight1 (ctx); if (!isTesSuccess (ret)) return ret; auto& tx = ctx.tx; auto& j = ctx.j; std::uint32_t const uTxFlags = tx.getFlags (); if (uTxFlags & tfTrustSetMask) { JLOG(j.trace) << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } STAmount const saLimitAmount (tx.getFieldAmount (sfLimitAmount)); if (!isLegalNet (saLimitAmount)) return temBAD_AMOUNT; if (saLimitAmount.native ()) { JLOG(j.trace) << "Malformed transaction: specifies native limit " << saLimitAmount.getFullText (); return temBAD_LIMIT; } if (badCurrency() == saLimitAmount.getCurrency ()) { JLOG(j.trace) << "Malformed transaction: specifies XRP as IOU"; return temBAD_CURRENCY; } if (saLimitAmount < zero) { JLOG(j.trace) << "Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. auto const& issuer = saLimitAmount.getIssuer (); if (!issuer || issuer == noAccount()) { JLOG(j.trace) << "Malformed transaction: no destination account."; return temDST_NEEDED; } return preflight2 (ctx); }
hash_set<Currency> accountSourceCurrencies ( AccountID const& account, RippleLineCache::ref lrCache, bool includeXRP) { hash_set<Currency> currencies; // YYY Only bother if they are above reserve if (includeXRP) { currencies.insert (xrpCurrency()); currencies.insert (vbcCurrency()); } // List of ripple lines. auto& rippleLines = lrCache->getRippleLines (account); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); // Filter out non if (saBalance > zero // Have IOUs to send. || (rspEntry->getLimitPeer () // Peer extends credit. && ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left. { currencies.insert (saBalance.getCurrency ()); } } currencies.erase (badCurrency()); return currencies; }
TER preCheck () override { 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 & tfNoDivvyDirect); 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 XDV. FIX! bool const bXDVDirect = uSrcCurrency.isZero () && uDstCurrency.isZero (); if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount)) return temBAD_AMOUNT; AccountID const uDstAccountID (mTxn.getFieldAccount160 (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 (bXDVDirect && bMax) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "SendMax specified for XDV to XDV."; return temBAD_SEND_XDV_MAX; } if (bXDVDirect && bPaths) { // XDV is sent without paths. m_journal.trace << "Malformed transaction: " << "Paths specified for XDV to XDV."; return temBAD_SEND_XDV_PATHS; } if (bXDVDirect && partialPaymentAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "Partial payment specified for XDV to XDV."; return temBAD_SEND_XDV_PARTIAL; } if (bXDVDirect && limitQuality) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "Limit quality specified for XDV to XDV."; return temBAD_SEND_XDV_LIMIT; } if (bXDVDirect && !defaultPathsAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "No divvy direct specified for XDV to XDV."; return temBAD_SEND_XDV_NO_DIRECT; } return Transactor::preCheck (); }
Json::Value doAccountCurrencies (RPC::Context& context) { auto& params = context.params; // Get the current ledger Ledger::pointer ledger; Json::Value result (RPC::lookupLedger (params, ledger, context.netOps)); if (!ledger) return result; if (!(params.isMember (jss::account) || params.isMember (jss::ident))) return RPC::missing_field_error (jss::account); std::string const strIdent (params.isMember (jss::account) ? params[jss::account].asString () : params[jss::ident].asString ()); int iIndex = 0; if (params.isMember (jss::account_index)) { auto const& accountIndex = params[jss::account_index]; if (!accountIndex.isUInt() && !accountIndex.isInt ()) return RPC::invalid_field_message (jss::account_index); iIndex = accountIndex.asUInt (); } bool const bStrict = params.isMember (jss::strict) && params[jss::strict].asBool (); // Get info on account. bool bIndex; // out param RippleAddress naAccount; // out param Json::Value jvAccepted (RPC::accountFromString ( ledger, naAccount, bIndex, strIdent, iIndex, bStrict, context.netOps)); if (!jvAccepted.empty ()) return jvAccepted; std::set<Currency> send, receive; for (auto const& item : getRippleStateItems (naAccount.getAccountID (), ledger)) { RippleState* rspEntry = (RippleState*) item.get (); STAmount const& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) receive.insert (saBalance.getCurrency ()); if ((-saBalance) < rspEntry->getLimitPeer ()) send.insert (saBalance.getCurrency ()); } send.erase (badCurrency()); receive.erase (badCurrency()); Json::Value& sendCurrencies = (result[jss::send_currencies] = Json::arrayValue); for (auto const& c: send) sendCurrencies.append (to_string (c)); Json::Value& recvCurrencies = (result[jss::receive_currencies] = Json::arrayValue); for (auto const& c: receive) recvCurrencies.append (to_string (c)); return result; }
TER CreateOffer::doApply () { if (m_journal.debug) m_journal.debug << "OfferCreate> " << mTxn.getJson (0); std::uint32_t const uTxFlags = mTxn.getFlags (); bool const bPassive (uTxFlags & tfPassive); bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel); bool const bFillOrKill (uTxFlags & tfFillOrKill); bool const bSell (uTxFlags & tfSell); STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays); STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets); if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ()) return temBAD_AMOUNT; auto const& uPaysIssuerID = saTakerPays.getIssuer (); auto const& uPaysCurrency = saTakerPays.getCurrency (); auto const& uGetsIssuerID = saTakerGets.getIssuer (); auto const& uGetsCurrency = saTakerGets.getCurrency (); bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration)); bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence)); std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration); std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence); // FIXME understand why we use SequenceNext instead of current transaction // sequence to determine the transaction. Why is the offer seuqnce // number insufficient? std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence); std::uint32_t const uSequence = mTxn.getSequence (); const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence); if (m_journal.debug) { m_journal.debug << "Creating offer node: " << to_string (uLedgerIndex) << " uSequence=" << uSequence; if (bImmediateOrCancel) m_journal.debug << "Transaction: IoC set."; if (bFillOrKill) m_journal.debug << "Transaction: FoK set."; } // This is the original rate of this offer, and is the rate at which it will // be placed, even if crossing offers change the amounts. std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays); TER terResult (tesSUCCESS); // This is the ledger view that we work against. Transactions are applied // as we go on processing transactions. core::LedgerView& view (mEngine->view ()); // This is a checkpoint with just the fees paid. If something goes wrong // with this transaction, we roll back to this ledger. core::LedgerView view_checkpoint (view); view.bumpSeq (); // Begin ledger variance. SLE::pointer sleCreator = mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID)); if (uTxFlags & tfOfferCreateMask) { if (m_journal.debug) m_journal.debug << "Malformed transaction: Invalid flags set."; terResult = temINVALID_FLAG; } else if (bImmediateOrCancel && bFillOrKill) { if (m_journal.debug) m_journal.debug << "Malformed transaction: both IoC and FoK set."; terResult = temINVALID_FLAG; } else if (bHaveExpiration && !uExpiration) { m_journal.warning << "Malformed offer: bad expiration"; terResult = temBAD_EXPIRATION; } else if (saTakerPays.isNative () && saTakerGets.isNative ()) { m_journal.warning << "Malformed offer: XRP for XRP"; terResult = temBAD_OFFER; } else if (saTakerPays <= zero || saTakerGets <= zero) { m_journal.warning << "Malformed offer: bad amount"; terResult = temBAD_OFFER; } else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) { m_journal.warning << "Malformed offer: redundant offer"; terResult = temREDUNDANT; } // We don't allow a non-native currency to use the currency code XRP. else if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency) { m_journal.warning << "Malformed offer: Bad currency."; terResult = temBAD_CURRENCY; } else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID) { m_journal.warning << "Malformed offer: bad issuer"; terResult = temBAD_ISSUER; } else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID)) { m_journal.warning << "Offer involves frozen asset"; terResult = tecFROZEN; } else if (view.accountFunds ( mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero) { m_journal.warning << "delay: Offers must be at least partially funded."; terResult = tecUNFUNDED_OFFER; } // This can probably be simplified to make sure that you cancel sequences // before the transaction sequence number. else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence)) { if (m_journal.debug) m_journal.debug << "uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uCancelSequence; terResult = temBAD_SEQUENCE; } if (terResult != tesSUCCESS) { if (m_journal.debug) m_journal.debug << "final terResult=" << transToken (terResult); return terResult; } // Process a cancellation request that's passed along with an offer. if ((terResult == tesSUCCESS) && bHaveCancel) { uint256 const uCancelIndex ( Ledger::getOfferIndex (mTxnAccountID, uCancelSequence)); SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex); // It's not an error to not find the offer to cancel: it might have // been consumed or removed as we are processing. if (sleCancel) { m_journal.warning << "Cancelling order with sequence " << uCancelSequence; terResult = view.offerDelete (sleCancel); } } // 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 && (mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration)) { return tesSUCCESS; } // Make sure that we are authorized to hold what the taker will pay us. if (terResult == tesSUCCESS && !saTakerPays.isNative ()) terResult = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID)); bool crossed = false; bool const bOpenLedger (mParams & tapOPEN_LEDGER); if (terResult == tesSUCCESS) { // We reverse gets and pays because during offer crossing we are taking. core::Amounts const taker_amount (saTakerGets, saTakerPays); // The amount of the offer that we will need to place, after we finish // offer crossing processing. It may be equal to the original amount, // empty (fully crossed), or something in-between. core::Amounts place_offer; std::tie(terResult, place_offer) = crossOffers (view, taker_amount); if (terResult == tecFAILED_PROCESSING && bOpenLedger) terResult = telFAILED_PROCESSING; if (terResult == tesSUCCESS) { // We now need to reduce the offer by the cross flow. We reverse // in and out here, since during crossing we were takers. assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ()); assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ()); assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ()); assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ()); if (taker_amount != place_offer) crossed = true; if (m_journal.debug) { m_journal.debug << "Offer Crossing: " << transToken (terResult); if (terResult == tesSUCCESS) { m_journal.debug << " takerPays: " << saTakerPays.getFullText () << " -> " << place_offer.out.getFullText (); m_journal.debug << " takerGets: " << saTakerGets.getFullText () << " -> " << place_offer.in.getFullText (); } } saTakerPays = place_offer.out; saTakerGets = place_offer.in; } } if (terResult != tesSUCCESS) { m_journal.debug << "final terResult=" << transToken (terResult); return terResult; } if (m_journal.debug) { m_journal.debug << "takeOffers: saTakerPays=" <<saTakerPays.getFullText (); m_journal.debug << "takeOffers: saTakerGets=" << saTakerGets.getFullText (); m_journal.debug << "takeOffers: mTxnAccountID=" << to_string (mTxnAccountID); m_journal.debug << "takeOffers: FUNDS=" << view.accountFunds ( mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText (); } if (saTakerPays < zero || saTakerGets < zero) { // Earlier, we verified that the amounts, as specified in the offer, // were not negative. That they are now suggests that something went // very wrong with offer crossing. m_journal.fatal << (crossed ? "Partially consumed" : "Full") << " offer has negative component:" << " pays=" << saTakerPays.getFullText () << " gets=" << saTakerGets.getFullText (); assert (saTakerPays >= zero); assert (saTakerGets >= zero); return tefINTERNAL; } if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero)) { // Fill or kill and have leftovers. view.swapWith (view_checkpoint); // Restore with just fees paid. return tesSUCCESS; } // What the reserve would be if this offer was placed. auto const accountReserve (mEngine->getLedger ()->getReserve ( sleCreator->getFieldU32 (sfOwnerCount) + 1)); if (saTakerPays == zero || // Wants nothing more. saTakerGets == zero || // Offering nothing more. bImmediateOrCancel) // Do not persist. { // Complete as is. } else if (mPriorBalance.getNValue () < accountReserve) { // If we are here, the signing account had an insufficient reserve // *prior* to our processing. We use the prior balance to simplify // client writing and make the user experience better. if (bOpenLedger) // Ledger is not final, can vote no. { // Hope for more reserve to come in or more offers to consume. If we // specified a local error this transaction will not be retried, so // specify a tec to distribute the transaction and allow it to be // retried. In particular, it may have been successful to a // degree (partially filled) and if it hasn't, it might succeed. terResult = tecINSUF_RESERVE_OFFER; } else if (!crossed) { // Ledger is final, insufficent reserve to create offer, processed // nothing. terResult = tecINSUF_RESERVE_OFFER; } else { // Ledger is final, insufficent reserve to create offer, processed // something. // Consider the offer unfunded. Treat as tesSUCCESS. } } else { assert (saTakerPays > zero); assert (saTakerGets > zero); // We need to place the remainder of the offer into its order book. if (m_journal.debug) m_journal.debug << "offer not fully consumed:" << " saTakerPays=" << saTakerPays.getFullText () << " saTakerGets=" << saTakerGets.getFullText (); std::uint64_t uOwnerNode; std::uint64_t uBookNode; uint256 uDirectory; // Add offer to owner's directory. terResult = view.dirAdd (uOwnerNode, Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex, std::bind ( &Ledger::ownerDirDescriber, std::placeholders::_1, std::placeholders::_2, mTxnAccountID)); if (tesSUCCESS == terResult) { // Update owner count. view.ownerCountAdjust (mTxnAccountID, 1, sleCreator); uint256 const uBookBase (Ledger::getBookBase ( {{uPaysCurrency, uPaysIssuerID}, {uGetsCurrency, uGetsIssuerID}})); if (m_journal.debug) m_journal.debug << "adding to book: " << to_string (uBookBase) << " : " << saTakerPays.getHumanCurrency () << "/" << to_string (saTakerPays.getIssuer ()) << " -> " << saTakerGets.getHumanCurrency () << "/" << to_string (saTakerGets.getIssuer ()); // We use the original rate to place the offer. uDirectory = Ledger::getQualityIndex (uBookBase, uRate); // Add offer to order book. terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex, std::bind ( &Ledger::qualityDirDescriber, std::placeholders::_1, std::placeholders::_2, saTakerPays.getCurrency (), uPaysIssuerID, saTakerGets.getCurrency (), uGetsIssuerID, uRate)); } if (tesSUCCESS == terResult) { if (m_journal.debug) { m_journal.debug << "sfAccount=" << to_string (mTxnAccountID); m_journal.debug << "uPaysIssuerID=" << to_string (uPaysIssuerID); m_journal.debug << "uGetsIssuerID=" << to_string (uGetsIssuerID); m_journal.debug << "saTakerPays.isNative()=" << saTakerPays.isNative (); m_journal.debug << "saTakerGets.isNative()=" << saTakerGets.isNative (); m_journal.debug << "uPaysCurrency=" << saTakerPays.getHumanCurrency (); m_journal.debug << "uGetsCurrency=" << saTakerGets.getHumanCurrency (); } SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex)); sleOffer->setFieldAccount (sfAccount, mTxnAccountID); sleOffer->setFieldU32 (sfSequence, uSequence); sleOffer->setFieldH256 (sfBookDirectory, uDirectory); sleOffer->setFieldAmount (sfTakerPays, saTakerPays); sleOffer->setFieldAmount (sfTakerGets, saTakerGets); sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode); sleOffer->setFieldU64 (sfBookNode, uBookNode); if (uExpiration) sleOffer->setFieldU32 (sfExpiration, uExpiration); if (bPassive) sleOffer->setFlag (lsfPassive); if (bSell) sleOffer->setFlag (lsfSell); if (m_journal.debug) m_journal.debug << "final terResult=" << transToken (terResult) << " sleOffer=" << sleOffer->getJson (0); } } if (terResult != tesSUCCESS) { m_journal.debug << "final terResult=" << transToken (terResult); } return terResult; }
Json::Value doAccountCurrencies (RPC::Context& context) { auto& params = context.params_; // Get the current ledger Ledger::pointer ledger; Json::Value result (RPC::lookupLedger (params, ledger, context.netOps_)); if (!ledger) return result; if (!(params.isMember ("account") || params.isMember ("ident"))) return RPC::missing_field_error ("account"); std::string const strIdent (params.isMember ("account") ? params["account"].asString () : params["ident"].asString ()); int const iIndex (params.isMember ("account_index") ? params["account_index"].asUInt () : 0); bool const bStrict = params.isMember ("strict") && params["strict"].asBool (); // Get info on account. bool bIndex; // out param RippleAddress naAccount; // out param Json::Value jvAccepted (RPC::accountFromString ( ledger, naAccount, bIndex, strIdent, iIndex, bStrict, context.netOps_)); if (!jvAccepted.empty ()) return jvAccepted; std::set<Currency> send, receive; AccountItems rippleLines ( naAccount.getAccountID (), ledger, AccountItem::pointer (new RippleState ())); for (auto item: rippleLines.getItems ()) { RippleState* rspEntry = (RippleState*) item.get (); STAmount const& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) receive.insert (saBalance.getCurrency ()); if ((-saBalance) < rspEntry->getLimitPeer ()) send.insert (saBalance.getCurrency ()); } send.erase (badCurrency()); receive.erase (badCurrency()); Json::Value& sendCurrencies = (result["send_currencies"] = Json::arrayValue); for (auto const& c: send) sendCurrencies.append (to_string (c)); Json::Value& recvCurrencies = (result["receive_currencies"] = Json::arrayValue); for (auto const& c: receive) recvCurrencies.append (to_string (c)); return result; }
int PathRequest::parseJson (const Json::Value& jvParams, bool complete) { int ret = PFR_PJ_NOCHANGE; if (jvParams.isMember ("source_account")) { if (!raSrcAccount.setAccountID (jvParams["source_account"].asString ())) { jvStatus = rpcError (rpcSRC_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcSRC_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("destination_account")) { if (!raDstAccount.setAccountID (jvParams["destination_account"].asString ())) { jvStatus = rpcError (rpcDST_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("destination_amount")) { if (!saDstAmount.bSetJson (jvParams["destination_amount"]) || (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || (saDstAmount.getCurrency () == badCurrency()) || saDstAmount <= zero) { jvStatus = rpcError (rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("source_currencies")) { const Json::Value& jvSrcCur = jvParams["source_currencies"]; if (!jvSrcCur.isArray ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.clear (); for (unsigned i = 0; i < jvSrcCur.size (); ++i) { const Json::Value& jvCur = jvSrcCur[i]; Currency uCur; Account uIss; if (!jvCur.isObject() || !jvCur.isMember ("currency") || !to_currency (uCur, jvCur["currency"].asString ())) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (jvCur.isMember ("issuer") && !to_issuer (uIss, jvCur["issuer"].asString ())) { jvStatus = rpcError (rpcSRC_ISR_MALFORMED); } if (uCur.isZero () && uIss.isNonZero ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.insert ({uCur, uIss}); } } if (jvParams.isMember ("id")) jvId = jvParams["id"]; return ret; }
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; }
TER SetTrust::doApply () { TER terResult = tesSUCCESS; STAmount const saLimitAmount (mTxn.getFieldAmount (sfLimitAmount)); bool const bQualityIn (mTxn.isFieldPresent (sfQualityIn)); bool const bQualityOut (mTxn.isFieldPresent (sfQualityOut)); Currency const currency (saLimitAmount.getCurrency ()); Account uDstAccountID (saLimitAmount.getIssuer ()); // true, iff current is high account. bool const bHigh = mTxnAccountID > uDstAccountID; std::uint32_t uQualityIn (bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0); std::uint32_t uQualityOut (bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0); if (!saLimitAmount.isLegalNet ()) return temBAD_AMOUNT; if (bQualityIn && QUALITY_ONE == uQualityIn) uQualityIn = 0; if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; std::uint32_t const uTxFlags = mTxn.getFlags (); if (uTxFlags & tfTrustSetMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } bool const bSetAuth = (uTxFlags & tfSetfAuth); bool const bSetNoRipple = (uTxFlags & tfSetNoRipple); bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); if (bSetAuth && !(mTxnAccount->getFieldU32 (sfFlags) & lsfRequireAuth)) { m_journal.trace << "Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (saLimitAmount.isNative ()) { m_journal.trace << "Malformed transaction: Native credit limit: " << saLimitAmount.getFullText (); return temBAD_LIMIT; } if (saLimitAmount < zero) { m_journal.trace << "Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. if (!uDstAccountID || uDstAccountID == noAccount()) { m_journal.trace << "Malformed transaction: Destination account not specified."; return temDST_NEEDED; } if (mTxnAccountID == uDstAccountID) { SLE::pointer selDelete ( mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex ( mTxnAccountID, uDstAccountID, currency))); if (selDelete) { m_journal.warning << "Clearing redundant line."; return mEngine->view ().trustDelete ( selDelete, mTxnAccountID, uDstAccountID); } else { m_journal.trace << "Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } SLE::pointer sleDst (mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID))); if (!sleDst) { m_journal.trace << "Delay transaction: Destination account does not exist."; return tecNO_DST; } std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); // The reserve required to create the line. std::uint64_t const uReserveCreate = (uOwnerCount < 2) ? 0 : mEngine->getLedger ()->getReserve (uOwnerCount + 1); STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (mTxnAccountID); SLE::pointer sleRippleState (mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, currency))); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; std::uint32_t uLowQualityIn; std::uint32_t uLowQualityOut; std::uint32_t uHighQualityIn; std::uint32_t uHighQualityOut; auto const& uLowAccountID = !bHigh ? mTxnAccountID : uDstAccountID; auto const& uHighAccountID = bHigh ? mTxnAccountID : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? mTxnAccount : sleDst; SLE::ref sleHighAccount = bHigh ? mTxnAccount : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } std::uint32_t const uFlagsIn (sleRippleState->getFieldU32 (sfFlags)); std::uint32_t uFlagsOut (uFlagsIn); if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance) >= zero) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (bSetFreeze && !bClearFreeze && !mTxnAccount->isFlag (lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; bool const bLowReserveSet = uLowQualityIn || uLowQualityOut || (uFlagsOut & lsfLowNoRipple) || (uFlagsOut & lsfLowFreeze) || saLowLimit || saLowBalance > zero; bool const bLowReserveClear = !bLowReserveSet; bool const bHighReserveSet = uHighQualityIn || uHighQualityOut || (uFlagsOut & lsfHighNoRipple) || (uFlagsOut & lsfHighFreeze) || saHighLimit || saHighBalance > zero; bool const bHighReserveClear = !bHighReserveSet; bool const bDefault = bLowReserveClear && bHighReserveClear; bool const bLowReserved = (uFlagsIn & lsfLowReserve); bool const bHighReserved = (uFlagsIn & lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. mEngine->view ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. mEngine->view ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. mEngine->view ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. mEngine->view ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || badCurrency() == currency) { // Delete. terResult = mEngine->view ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { m_journal.trace << "Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { mEngine->entryModify (sleRippleState); m_journal.trace << "Modify ripple line"; } } // Line does not exist. else if (!saLimitAmount // Setting default limit. && (!bQualityIn || !uQualityIn) // Not setting quality in or setting default quality in. && (!bQualityOut || !uQualityOut)) // Not setting quality out or setting default quality out. { m_journal.trace << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { m_journal.trace << "Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else if (badCurrency() == currency) { terResult = temBAD_CURRENCY; } else { // Zero balance in currency. STAmount saBalance ({currency, noAccount()}); uint256 index (Ledger::getRippleStateIndex ( mTxnAccountID, uDstAccountID, currency)); m_journal.trace << "doTrustSet: Creating ripple line: " << to_string (index); // Create a new ripple line. terResult = mEngine->view ().trustCreate ( bHigh, mTxnAccountID, uDstAccountID, index, mTxnAccount, bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut); } return terResult; }
TER SetTrust::doApply () { TER terResult = tesSUCCESS; STAmount const saLimitAmount (tx().getFieldAmount (sfLimitAmount)); bool const bQualityIn (tx().isFieldPresent (sfQualityIn)); bool const bQualityOut (tx().isFieldPresent (sfQualityOut)); Currency const currency (saLimitAmount.getCurrency ()); AccountID uDstAccountID (saLimitAmount.getIssuer ()); // true, iff current is high account. bool const bHigh = account_ > uDstAccountID; auto const sle = view().peek( keylet::account(account_)); std::uint32_t const uOwnerCount = sle->getFieldU32 (sfOwnerCount); // The reserve required to create the line. Note that we allow up to // two trust lines without requiring a reserve because being able to // exchange currencies is a powerful Ripple feature. // // This is also a security feature: if you're a gateway and you want to // be able to let someone use your services, you would otherwise have to // give them enough XRP to cover the incremental reserve for their trust // line. If they had no intention of using your services, they could use // the XRP for their own purposes. So we make it possible for gateways // to fund accounts in a way where there's no incentive to trick them // into creating an account you have no intention of using. XRPAmount const reserveCreate ((uOwnerCount < 2) ? 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); if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; std::uint32_t const uTxFlags = tx().getFlags (); bool const bSetAuth = (uTxFlags & tfSetfAuth); bool const bSetNoRipple = (uTxFlags & tfSetNoRipple); bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); 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 // trust line to oneself to be deleted. If no such trust // lines exist now, why not remove this code and simply // return an error? 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)); if (!sleDst) { j_.trace << "Delay transaction: Destination account does not exist."; return tecNO_DST; } STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (account_); SLE::pointer sleRippleState = view().peek ( keylet::line(account_, uDstAccountID, currency)); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; std::uint32_t uLowQualityIn; std::uint32_t uLowQualityOut; std::uint32_t uHighQualityIn; std::uint32_t uHighQualityOut; auto const& uLowAccountID = !bHigh ? account_ : uDstAccountID; auto const& uHighAccountID = bHigh ? account_ : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? sle : sleDst; SLE::ref sleHighAccount = bHigh ? sle : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } std::uint32_t const uFlagsIn (sleRippleState->getFieldU32 (sfFlags)); std::uint32_t uFlagsOut (uFlagsIn); if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance) >= zero) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (bSetFreeze && !bClearFreeze && !sle->isFlag (lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple; bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple; bool const bLowReserveSet = uLowQualityIn || uLowQualityOut || ((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple || (uFlagsOut & lsfLowFreeze) || saLowLimit || saLowBalance > zero; bool const bLowReserveClear = !bLowReserveSet; bool const bHighReserveSet = uHighQualityIn || uHighQualityOut || ((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple || (uFlagsOut & lsfHighFreeze) || saHighLimit || saHighBalance > zero; bool const bHighReserveClear = !bHighReserveSet; bool const bDefault = bLowReserveClear && bHighReserveClear; bool const bLowReserved = (uFlagsIn & lsfLowReserve); bool const bHighReserved = (uFlagsIn & lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. adjustOwnerCount(view(), sleLowAccount, 1, viewJ); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. adjustOwnerCount(view(), sleLowAccount, -1, viewJ); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. adjustOwnerCount(view(), sleHighAccount, 1, viewJ); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. adjustOwnerCount(view(), sleHighAccount, -1, viewJ); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || badCurrency() == currency) { // Delete. terResult = trustDelete (view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); } // Reserve is not scaled by load. else if (bReserveIncrease && mPriorBalance < reserveCreate) { j_.trace << "Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { view().update (sleRippleState); j_.trace << "Modify ripple line"; } } // Line does not exist. else if (! saLimitAmount && // Setting default limit. (! bQualityIn || ! uQualityIn) && // Not setting quality in or setting default quality in. (! bQualityOut || ! uQualityOut) && // Not setting quality out or setting default quality out. (! ((view().flags() & tapENABLE_TESTING) || view().rules().enabled(featureTrustSetAuth, ctx_.config.features)) || ! bSetAuth)) { j_.trace << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance < reserveCreate) // Reserve is not scaled by load. { j_.trace << "Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else { // Zero balance in currency. STAmount saBalance ({currency, noAccount()}); uint256 index (getRippleStateIndex ( account_, uDstAccountID, currency)); j_.trace << "doTrustSet: Creating ripple line: " << to_string (index); // Create a new ripple line. terResult = trustCreate (view(), bHigh, account_, uDstAccountID, index, sle, bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut, viewJ); } return terResult; }
int PathRequest::parseJson (Json::Value const& jvParams, bool complete) { int ret = PFR_PJ_NOCHANGE; if (jvParams.isMember (jss::source_account)) { raSrcAccount = parseBase58<AccountID>( jvParams[jss::source_account].asString()); if (! raSrcAccount) { jvStatus = rpcError (rpcSRC_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcSRC_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::destination_account)) { raDstAccount = parseBase58<AccountID>( jvParams[jss::destination_account].asString()); if (! raDstAccount) { jvStatus = rpcError (rpcDST_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::destination_amount)) { if (! amountFromJsonNoThrow ( saDstAmount, jvParams[jss::destination_amount]) || (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || (saDstAmount.getCurrency () == badCurrency ()) || saDstAmount <= zero) { jvStatus = rpcError (rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::source_currencies)) { Json::Value const& jvSrcCur = jvParams[jss::source_currencies]; if (!jvSrcCur.isArray ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.clear (); for (unsigned i = 0; i < jvSrcCur.size (); ++i) { Json::Value const& jvCur = jvSrcCur[i]; Currency uCur; AccountID uIss; if (!jvCur.isObject() || !jvCur.isMember (jss::currency) || !to_currency (uCur, jvCur[jss::currency].asString ())) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (jvCur.isMember (jss::issuer) && !to_issuer (uIss, jvCur[jss::issuer].asString ())) { jvStatus = rpcError (rpcSRC_ISR_MALFORMED); } if (uCur.isZero () && uIss.isNonZero ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (uCur.isNonZero() && uIss.isZero()) { uIss = *raSrcAccount; } sciSourceCurrencies.insert ({uCur, uIss}); } } if (jvParams.isMember ("id")) jvId = jvParams["id"]; return ret; }