// Append a node and insert before it any implied nodes. // Offers may go back to back. // <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) { Node pnCur; const bool bFirst = vpnNodes.empty (); const Node& pnPrv = bFirst ? Node () : vpnNodes.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool bAccount = isSetBit (iType, STPathElement::typeAccount); // true, iff currency supplied. // Currency is specified for the output of the current node. const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer); TER terResult = tesSUCCESS; WriteLog (lsTRACE, RippleCalc) << "pushNode> " << iType << ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") << " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") << "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-"); pnCur.uFlags = iType; pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; if (iType & ~STPathElement::typeValidBits) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits."; terResult = temBAD_PATH; } else if (bIssuer && !pnCur.uCurrencyID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP."; terResult = temBAD_PATH; } else if (bIssuer && !uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer."; terResult = temBAD_PATH; } else if (!bAccount && !bCurrency && !bIssuer) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer."; terResult = temBAD_PATH; } else if (bAccount) { // Account link pnCur.uAccountID = uAccountID; pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP; pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (bFirst) { // The first node is always correct as is. nothing (); } else if (!uAccountID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account."; terResult = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account."; terResult = pushImply ( pnCur.uAccountID, // Current account. pnCur.uCurrencyID, // Wanted currency. !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer. // Note: pnPrv may no longer be the immediately previous node. } if (tesSUCCESS == terResult && !vpnNodes.empty ()) { const Node& pnBck = vpnNodes.back (); bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount); if (bBckAccount) { SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); if (!sleRippleState) { WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; WriteLog (lsTRACE, RippleCalc) << getJson (); terResult = terNO_LINE; } else { WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID)); bool bHigh = pnBck.uAccountID > pnCur.uAccountID; if (!sleBck) { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID); terResult = terNO_ACCOUNT; } else if ((isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth) && !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth))) && sleRippleState->getFieldAmount(sfBalance).isZero()) // CHECKME { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth."; terResult = terNO_AUTH; } else if (isSetBit (sleRippleState->getFieldU32 (sfFlags), bHigh ? lsfHighNoRipple : lsfLowNoRipple) && (vpnNodes.size() > 1)) { // If the link leaves the side that set no ripple, it must be the first link WriteLog (lsWARNING, RippleCalc) << "pushNode: illegal use of noRipple link"; terResult = terNO_AUTH; } if (tesSUCCESS == terResult) { STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID); STAmount saLimit; if (!saOwed.isPositive () && -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID))) { WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s") % saOwed % saLimit); terResult = tecPATH_DRY; } } } } } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } else { // Offer link // Offers bridge a change in currency & issuer or just a change in issuer. pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? !!pnPrv.uIssuerID ? pnPrv.uIssuerID // Default to previous issuer : pnPrv.uAccountID // Or previous account if no previous issuer. : ACCOUNT_XRP; pnCur.saRateMax = saZero; pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer."; terResult = temBAD_PATH; } else if (!!pnPrv.uAccountID) { // Previous is an account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. terResult = pushImply ( ACCOUNT_XRP, // Rippling, but offers don't have an account. pnPrv.uCurrencyID, pnPrv.uIssuerID); } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult)); return terResult; }
TER TransactionEngine::applyTransaction ( STTx const& txn, TransactionEngineParams params, bool& didApply) { WriteLog (lsTRACE, TransactionEngine) << "applyTransaction>"; didApply = false; assert (mLedger); uint256 const& txID = txn.getTransactionID (); mNodes.init (mLedger, txID, mLedger->getLedgerSeq (), params); #ifdef BEAST_DEBUG if (1) { Serializer ser; txn.add (ser); SerializerIterator sit (ser); STTx s2 (sit); if (!s2.isEquivalent (txn)) { WriteLog (lsFATAL, TransactionEngine) << "Transaction serdes mismatch"; WriteLog (lsINFO, TransactionEngine) << txn.getJson (0); WriteLog (lsFATAL, TransactionEngine) << s2.getJson (0); assert (false); } } #endif if (!txID) { WriteLog (lsWARNING, TransactionEngine) << "applyTransaction: invalid transaction id"; return temINVALID; } TER terResult = Transactor::transact (txn, params, this); if (terResult == temUNKNOWN) { WriteLog (lsWARNING, TransactionEngine) << "applyTransaction: Invalid transaction: unknown transaction type"; return temUNKNOWN; } if (ShouldLog (lsDEBUG, TransactionEngine)) { std::string strToken; std::string strHuman; transResultInfo (terResult, strToken, strHuman); WriteLog (lsDEBUG, TransactionEngine) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; } if (isTesSuccess (terResult)) didApply = true; else if (isTecClaim (terResult) && !(params & tapRETRY)) { // only claim the transaction fee WriteLog (lsDEBUG, TransactionEngine) << "Reprocessing to only claim fee"; mNodes.clear (); SLE::pointer txnAcct = entryCache (ltACCOUNT_ROOT, getAccountRootIndex (txn.getSourceAccount ())); if (!txnAcct) terResult = terNO_ACCOUNT; else { std::uint32_t t_seq = txn.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 { STAmount fee = txn.getTransactionFee (); STAmount balance = txnAcct->getFieldAmount (sfBalance); STAmount balanceVBC = txnAcct->getFieldAmount(sfBalanceVBC); // 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) || (balanceVBC.getNValue() == 0) || ((params & tapOPEN_LEDGER) && (balance < fee))) { // Account has no funds or ledger is open terResult = terINSUF_FEE_B; } else { if (fee > balance) fee = balance; txnAcct->setFieldAmount (sfBalance, balance - fee); txnAcct->setFieldAmount(sfBalanceVBC, balanceVBC); txnAcct->setFieldU32 (sfSequence, t_seq + 1); entryModify (txnAcct); didApply = true; } } } } else WriteLog (lsDEBUG, TransactionEngine) << "Not applying transaction " << txID; if (didApply) { if (!checkInvariants (terResult, txn, params)) { WriteLog (lsFATAL, TransactionEngine) << "Transaction violates invariants"; WriteLog (lsFATAL, TransactionEngine) << txn.getJson (0); WriteLog (lsFATAL, TransactionEngine) << transToken (terResult) << ": " << transHuman (terResult); WriteLog (lsFATAL, TransactionEngine) << mNodes.getJson (0); didApply = false; terResult = tefINTERNAL; } else { // Transaction succeeded fully or (retries are not allowed and the // transaction could claim a fee) Serializer m; mNodes.calcRawMeta (m, terResult, mTxnSeq++); txnWrite (); Serializer s; txn.add (s); if (params & tapOPEN_LEDGER) { if (!mLedger->addTransaction (txID, s)) { WriteLog (lsFATAL, TransactionEngine) << "Tried to add transaction to open ledger that already had it"; assert (false); throw std::runtime_error ("Duplicate transaction applied"); } } else { if (!mLedger->addTransaction (txID, s, m)) { WriteLog (lsFATAL, TransactionEngine) << "Tried to add transaction to ledger that already had it"; assert (false); throw std::runtime_error ("Duplicate transaction applied to closed ledger"); } // Charge whatever fee they specified. STAmount saPaid = txn.getTransactionFee (); mLedger->destroyCoins (saPaid.getNValue ()); } } } mTxnAccount.reset (); mNodes.clear (); if (!(params & tapOPEN_LEDGER) && isTemMalformed (terResult)) { // XXX Malformed or failed transaction in closed ledger must bow out. } 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; }
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; }
// { // account: <account>|<account_public_key> // account_index: <number> // optional, defaults to 0. // ledger_hash : <ledger> // ledger_index : <ledger_index> // limit: integer // optional // marker: opaque // optional, resume previous query // } Json::Value doAccountOffers (RPC::Context& context) { auto const& params (context.params_); Ledger::pointer ledger; Json::Value result (RPC::lookupLedger (params, ledger, context.netOps_)); if (! ledger) return result; if (! params.isMember (jss::account)) return RPC::missing_field_error ("account"); std::string strIdent (params[jss::account].asString ()); bool bIndex (params.isMember (jss::account_index)); int const iIndex (bIndex ? params[jss::account_index].asUInt () : 0); RippleAddress rippleAddress; result = RPC::accountFromString (ledger, rippleAddress, bIndex, strIdent, iIndex, false, context.netOps_); if (! result.empty ()) return result; // Get info on account. result[jss::account] = rippleAddress.humanAccountID (); if (bIndex) result[jss::account_index] = iIndex; if (! ledger->hasAccount (rippleAddress)) return rpcError (rpcACT_NOT_FOUND); unsigned int limit; if (params.isMember (jss::limit)) { limit = std::max (RPC::Tuning::minOffersPerRequest, std::min (params[jss::limit].asUInt (), RPC::Tuning::maxOffersPerRequest)); } else { limit = RPC::Tuning::defaultOffersPerRequest; } Account const& raAccount (rippleAddress.getAccountID ()); Json::Value& jsonOffers (result[jss::offers] = Json::arrayValue); std::vector <SLE::pointer> offers; unsigned int reserve (limit); uint256 startAfter; std::uint64_t startHint; if (params.isMember(jss::marker)) { // We have a start point. Use limit - 1 from the result and use the // very last one for the resume. Json::Value const& marker (params[jss::marker]); if (! marker.isString ()) return rpcError (rpcACT_MALFORMED); startAfter.SetHex (marker.asString ()); SLE::pointer sleOffer (ledger->getSLEi (startAfter)); if (sleOffer == nullptr || sleOffer->getType () != ltOFFER || raAccount != sleOffer->getFieldAccount160 (sfAccount)) { return rpcError (rpcINVALID_PARAMS); } startHint = sleOffer->getFieldU64(sfOwnerNode); // Caller provided the first offer (startAfter), add it as first result Json::Value& obj (jsonOffers.append (Json::objectValue)); sleOffer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); sleOffer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); obj[jss::seq] = sleOffer->getFieldU32 (sfSequence); obj[jss::flags] = sleOffer->getFieldU32 (sfFlags); offers.reserve (reserve); } else { startHint = 0; // We have no start point, limit should be one higher than requested. offers.reserve (++reserve); } if (! ledger->visitAccountItems (raAccount, startAfter, startHint, reserve, [&offers](SLE::ref offer) { if (offer->getType () == ltOFFER) { offers.emplace_back (offer); return true; } return false; })) { return rpcError (rpcINVALID_PARAMS); } if (offers.size () == reserve) { result[jss::limit] = limit; result[jss::marker] = to_string (offers.back ()->getIndex ()); offers.pop_back (); } for (auto const& offer : offers) { Json::Value& obj (jsonOffers.append (Json::objectValue)); offer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); offer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); obj[jss::seq] = offer->getFieldU32 (sfSequence); obj[jss::flags] = offer->getFieldU32 (sfFlags); } context.loadType_ = Resource::feeMediumBurdenRPC; return result; }