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 PathCursor::reverseLiquidityForAccount () const { TER terResult = tesSUCCESS; auto const lastNodeIndex = nodeSize () - 1; auto const isFinalNode = (nodeIndex_ == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex_ || previousNode().isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode().isAccount(); AccountID const& previousAccountID = previousNodeIsAccount ? previousNode().account_ : node().account_; AccountID const& nextAccountID = nextNodeIsAccount ? nextNode().account_ : node().account_; // Offers are always issue. // This is the quality from from the previous node to this one. auto const qualityIn = (nodeIndex_ != 0) ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : parityRate; // And this is the quality from the next one to this one. auto const qualityOut = (nodeIndex_ != lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : parityRate; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex_ != 0) ? creditBalance (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex_ != 0) ? creditLimit (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex_ != lastNodeIndex) ? creditBalance (view(), node().account_, nextAccountID, node().issue_.currency) : STAmount (node().issue_); JLOG (j_.trace()) << "reverseLiquidityForAccount>" << " nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node().account_ << " nextAccountID=" << nextAccountID << " currency=" << node().issue_.currency << " qualityIn=" << qualityIn << " qualityOut=" << qualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > beast::zero) ? saPrvOwed : STAmount (saPrvOwed.issue ()); // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < beast::zero) ? saPrvLimit + saPrvOwed : saPrvLimit; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode().saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( {deliverCurrency, previousNode().saRevDeliver.getIssuer ()}, -1); // -1 means unlimited delivery. // Set to zero, because we're trying to hit the previous node. auto saCurRedeemAct = node().saRevRedeem.zeroed(); // Track the amount we actually redeem. auto saCurIssueAct = node().saRevIssue.zeroed(); // For !nextNodeIsAccount auto saCurDeliverAct = node().saRevDeliver.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saNxtOwed:" << saNxtOwed; // VFALCO-FIXME this generates errors //JLOG (j_.trace()) << pathState_.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!node().saRevRedeem || -saNxtOwed >= node().saRevRedeem); assert (!node().saRevIssue // If not issuing, fine. || saNxtOwed >= beast::zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == node().saRevRedeem); // If issue req, then redeem req must consume all owed. if (nodeIndex_ == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex_ = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState_.outReq() - pathState_.outAct(), saPrvLimit + saPrvOwed); auto saCurWantedAct = saCurWantedReq.zeroed (); JLOG (j_.trace()) << "reverseLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); previousNode().saRevRedeem = saCurWantedAct; uRateMax = STAmount::uRateOne; JLOG (j_.trace()) << "reverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) previousNode.saRevRedeem=" << previousNode().saRevRedeem << " uRateMax=" << amountFromQuality (uRateMax).getText (); } else { previousNode().saRevRedeem.clear (saPrvRedeemReq); } // Calculate issuing. previousNode().saRevIssue.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, saCurWantedReq, previousNode().saRevIssue, saCurWantedAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: Issuing: Rate: " << "quality in : 1.0" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (node().saRevRedeem // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs // as 1:1. rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvRedeemReq, node().saRevRedeem, previousNode().saRevRedeem, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (node().saRevRedeem != saCurRedeemAct // The current node has more IOUs to redeem. && previousNode().saRevRedeem == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, qualityIn, qualityOut, saPrvIssueReq, node().saRevRedeem, previousNode().saRevIssue, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (node().saRevIssue // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && previousNode().saRevRedeem != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevIssue, previousNode().saRevRedeem, saCurIssueAct, uRateMax); JLOG (j_.debug()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (node().saRevIssue != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevIssue, previousNode().saRevIssue, saCurIssueAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > beast::zero // Previous has IOUs to redeem. && node().saRevDeliver) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevDeliver, previousNode().saRevRedeem, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed. && node().saRevDeliver != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevDeliver, previousNode().saRevIssue, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << " node.saRevDeliver:" << node().saRevDeliver << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. STAmount const& saCurWantedReq = pathState_.outReq() - pathState_.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState_.outAct() << " saOutReq:" << pathState_.outReq(); if (saCurWantedReq <= beast::zero) { assert(false); JLOG (j_.fatal()) << "CurWantReq was not positive"; return tefEXCEPTION; } // The previous node is an offer; we are receiving our own currency. // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvDeliverReq, saCurWantedReq, previousNode().saRevDeliver, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " previousNode().saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (node().saRevRedeem) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality // out. // // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvDeliverReq, node().saRevRedeem, previousNode().saRevDeliver, saCurRedeemAct, uRateMax); } // deliver -> issue. if (node().saRevRedeem == saCurRedeemAct // Can only issue if previously redeemed all. && node().saRevIssue) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevIssue, previousNode().saRevDeliver, saCurIssueAct, uRateMax); } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " node.saRevRedeem:" << node().saRevRedeem << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " node.saRevIssue:" << node().saRevIssue; if (!previousNode().saRevDeliver) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace()) << "reverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevDeliver, previousNode().saRevDeliver, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }
TER PaymentTransactor::doApply () { // Ripple if source or destination is non-native or if there are paths. std::uint32_t const uTxFlags = mTxn.getFlags (); //bool const bPartialPayment = is_bit_set(uTxFlags, tfPartialPayment); bool const bLimitQuality = is_bit_set (uTxFlags, tfLimitQuality); bool const bNoRippleDirect = is_bit_set (uTxFlags, tfNoRippleDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); uint160 const uDstAccountID = mTxn.getFieldAccount160 (sfDestination); STAmount const saDstAmount = mTxn.getFieldAmount (sfAmount); STAmount const saMaxAmount = bMax ? mTxn.getFieldAmount (sfSendMax) : saDstAmount.isNative () ? saDstAmount : STAmount (saDstAmount.getCurrency (), mTxnAccountID, saDstAmount.getMantissa (), saDstAmount.getExponent (), saDstAmount < zero); uint160 const uSrcCurrency = saMaxAmount.getCurrency (); uint160 const uDstCurrency = saDstAmount.getCurrency (); bool const bSTRDirect = uSrcCurrency.isZero () && uDstCurrency.isZero (); m_journal.trace << "saMaxAmount=" << saMaxAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); if (!saDstAmount.isLegalNet () || !saMaxAmount.isLegalNet ()) 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 && saMaxAmount <= zero) { m_journal.trace << "Malformed transaction: bad max amount: " << saMaxAmount.getFullText (); return temBAD_AMOUNT; } else if (saDstAmount <= zero) { m_journal.trace << "Malformed transaction: bad dst amount: " << saDstAmount.getFullText (); return temBAD_AMOUNT; } else if (CURRENCY_BAD == uSrcCurrency || CURRENCY_BAD == uDstCurrency) { m_journal.trace << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { 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 && saMaxAmount == saDstAmount && saMaxAmount.getCurrency () == saDstAmount.getCurrency ()) { m_journal.trace << "Malformed transaction: Redundant SendMax."; return temREDUNDANT_SEND_MAX; } else if (bSTRDirect && bMax) { m_journal.trace << "Malformed transaction: SendMax specified for STR to STR."; return temBAD_SEND_STR_MAX; } else if (bSTRDirect && bPaths) { m_journal.trace << "Malformed transaction: Paths specified for STR to STR."; return temBAD_SEND_STR_PATHS; } else if (bSTRDirect && bLimitQuality) { m_journal.trace << "Malformed transaction: Limit quality specified for STR to STR."; return temBAD_SEND_STR_LIMIT; } else if (bSTRDirect && bNoRippleDirect) { m_journal.trace << "Malformed transaction: No ripple direct specified for STR to STR."; return temBAD_SEND_STR_NO_DIRECT; } SLE::pointer sleDst (mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID))); 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; } // Note: Reserve is not scaled by load. else if (saDstAmount.getNValue () < mEngine->getLedger ()->getReserve (0)) { m_journal.trace << "Delay transaction: Destination account does not exist. " << "Insufficient payment to create account."; // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST_INSUF_STR; } // Create the account. sleDst = mEngine->entryCreate ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID)); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); } else if ((sleDst->getFlags () & lsfRequireDestTag) && !mTxn.isFieldPresent (sfDestinationTag)) { m_journal.trace << "Malformed transaction: DestinationTag required."; return tefDST_TAG_NEEDED; } else { mEngine->entryModify (sleDst); } TER terResult; // XXX Should bMax be sufficient to imply ripple? bool const bRipple = bPaths || bMax || !saDstAmount.isNative (); if (bRipple) { // Ripple payment STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths); std::vector<PathState::pointer> vpsExpanded; STAmount saMaxAmountAct; STAmount saDstAmountAct; try { bool const openLedger = is_bit_set (mParams, tapOPEN_LEDGER); bool tooManyPaths = false; if (spsPaths.size() > MAX_NUM_PATHS) tooManyPaths = true; else { for (auto const& path : spsPaths) { if (path.size() > MAX_PATH_SIZE) { tooManyPaths = true; break; } } } terResult = openLedger && tooManyPaths ? telBAD_PATH_COUNT // Too many paths for proposed ledger. : RippleCalc::rippleCalc( mEngine->view(), saMaxAmountAct, saDstAmountAct, vpsExpanded, saMaxAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, false, bLimitQuality, bNoRippleDirect, // Always compute for finalizing ledger. false, // Not standalone, delete unfundeds. is_bit_set(mParams, tapOPEN_LEDGER)); if (isTerRetry(terResult)) terResult = tecPATH_DRY; if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount)) mEngine->view().setDeliveredAmount(saDstAmountAct); } catch (std::exception const& e) { m_journal.trace << "Caught throw: " << e.what (); terResult = tefEXCEPTION; } } else { // Direct STR payment. std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); std::uint64_t const uReserve (mEngine->getLedger ()->getReserve (uOwnerCount)); // Make sure have enough reserve to send. Allow final spend to use reserve for fee. if (mPriorBalance < saDstAmount + std::max(uReserve, mTxn.getTransactionFee ().getNValue ())) { // 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 { mTxnAccount->setFieldAmount (sfBalance, mSourceBalance - saDstAmount); sleDst->setFieldAmount (sfBalance, sleDst->getFieldAmount (sfBalance) + saDstAmount); 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 (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; }
// Make sure an offer is still valid. If not, mark it unfunded. bool OfferCreateTransactor::bValidOffer ( SLE::ref sleOffer, uint256 const& uOfferIndex, const uint160& uOfferOwnerID, const STAmount& saOfferPays, const STAmount& saOfferGets, const uint160& uTakerAccountID, boost::unordered_set<uint256>& usOfferUnfundedFound, boost::unordered_set<uint256>& usOfferUnfundedBecame, boost::unordered_set<uint160>& usAccountTouched, STAmount& saOfferFunds) // <-- { bool bValid = false; if (sleOffer->isFieldPresent (sfExpiration) && sleOffer->getFieldU32 (sfExpiration) <= mEngine->getLedger ()->getParentCloseTimeNC ()) { // Offer is expired. Expired offers are considered unfunded. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered expired offer"; usOfferUnfundedFound.insert (uOfferIndex); bValid = false; } else if (uOfferOwnerID == uTakerAccountID) { // Would take own offer. Consider old offer expired. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered taker's own old offer"; usOfferUnfundedFound.insert (uOfferIndex); bValid = false; } else if (!saOfferGets.isPositive () || !saOfferPays.isPositive ()) { // Offer has bad amounts. Consider offer expired. Delete it. WriteLog (lsWARNING, OfferCreateTransactor) << boost::str (boost::format ("bValidOffer: BAD OFFER: saOfferPays=%s saOfferGets=%s") % saOfferPays % saOfferGets); usOfferUnfundedFound.insert (uOfferIndex); bValid = false; } else { WriteLog (lsTRACE, OfferCreateTransactor) << "bValidOffer: saOfferPays=" << saOfferPays.getFullText (); saOfferFunds = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays); if (!saOfferFunds.isPositive ()) { // Offer is unfunded, possibly due to previous balance action. WriteLog (lsDEBUG, OfferCreateTransactor) << "bValidOffer: offer unfunded: delete"; boost::unordered_set<uint160>::iterator account = usAccountTouched.find (uOfferOwnerID); if (account != usAccountTouched.end ()) { // Previously touched account. usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. } else { // Never touched source account. usOfferUnfundedFound.insert (uOfferIndex); // Delete found unfunded offer when possible. } bValid = false; } else { bValid = true; } } return bValid; }
path::RippleCalc::Output flow ( PaymentSandbox& sb, STAmount const& deliver, AccountID const& src, AccountID const& dst, STPathSet const& paths, bool defaultPaths, bool partialPayment, bool ownerPaysTransferFee, boost::optional<Quality> const& limitQuality, boost::optional<STAmount> const& sendMax, beast::Journal j, path::detail::FlowDebugInfo* flowDebugInfo) { Issue const srcIssue = [&] { if (sendMax) return sendMax->issue (); if (!isXRP (deliver.issue ().currency)) return Issue (deliver.issue ().currency, src); return xrpIssue (); }(); Issue const dstIssue = deliver.issue (); boost::optional<Issue> sendMaxIssue; if (sendMax) sendMaxIssue = sendMax->issue (); // convert the paths to a collection of strands. Each strand is the collection // of account->account steps and book steps that may be used in this payment. auto sr = toStrands (sb, src, dst, dstIssue, sendMaxIssue, paths, defaultPaths, ownerPaysTransferFee, j); if (sr.first != tesSUCCESS) { path::RippleCalc::Output result; result.setResult (sr.first); return result; } auto& strands = sr.second; if (j.trace()) { j.trace() << "\nsrc: " << src << "\ndst: " << dst << "\nsrcIssue: " << srcIssue << "\ndstIssue: " << dstIssue; j.trace() << "\nNumStrands: " << strands.size (); for (auto const& curStrand : strands) { j.trace() << "NumSteps: " << curStrand.size (); for (auto const& step : curStrand) { j.trace() << '\n' << *step << '\n'; } } } const bool srcIsXRP = isXRP (srcIssue.currency); const bool dstIsXRP = isXRP (dstIssue.currency); auto const asDeliver = toAmountSpec (deliver); // The src account may send either xrp or iou. The dst account may receive // either xrp or iou. Since XRP and IOU amounts are represented by different // types, use templates to tell `flow` about the amount types. if (srcIsXRP && dstIsXRP) { return finishFlow (sb, srcIssue, dstIssue, flow<XRPAmount, XRPAmount> ( sb, strands, asDeliver.xrp, defaultPaths, partialPayment, limitQuality, sendMax, j, flowDebugInfo)); } if (srcIsXRP && !dstIsXRP) { return finishFlow (sb, srcIssue, dstIssue, flow<XRPAmount, IOUAmount> ( sb, strands, asDeliver.iou, defaultPaths, partialPayment, limitQuality, sendMax, j, flowDebugInfo)); } if (!srcIsXRP && dstIsXRP) { return finishFlow (sb, srcIssue, dstIssue, flow<IOUAmount, XRPAmount> ( sb, strands, asDeliver.xrp, defaultPaths, partialPayment, limitQuality, sendMax, j, flowDebugInfo)); } assert (!srcIsXRP && !dstIsXRP); return finishFlow (sb, srcIssue, dstIssue, flow<IOUAmount, IOUAmount> ( sb, strands, asDeliver.iou, defaultPaths, partialPayment, limitQuality, sendMax, j, flowDebugInfo)); }
static Json::Value signPayment( Json::Value const& params, Json::Value& tx_json, RippleAddress const& raSrcAddressID, RPCDetail::LedgerFacade& ledgerFacade, Role role) { RippleAddress dstAccountID; if (!tx_json.isMember ("Amount")) return RPC::missing_field_error ("tx_json.Amount"); STAmount amount; if (! amountFromJsonNoThrow (amount, tx_json ["Amount"])) return RPC::invalid_field_error ("tx_json.Amount"); if (!tx_json.isMember ("Destination")) return RPC::missing_field_error ("tx_json.Destination"); if (!dstAccountID.setAccountID (tx_json["Destination"].asString ())) return RPC::invalid_field_error ("tx_json.Destination"); if (tx_json.isMember ("Paths") && params.isMember ("build_path")) return RPC::make_error (rpcINVALID_PARAMS, "Cannot specify both 'tx_json.Paths' and 'build_path'"); if (!tx_json.isMember ("Paths") && tx_json.isMember ("Amount") && params.isMember ("build_path")) { // Need a ripple path. Currency uSrcCurrencyID; Account uSrcIssuerID; STAmount saSendMax; if (tx_json.isMember ("SendMax")) { if (! amountFromJsonNoThrow (saSendMax, tx_json ["SendMax"])) return RPC::invalid_field_error ("tx_json.SendMax"); } else { // If no SendMax, default to Amount with sender as issuer. saSendMax = amount; saSendMax.setIssuer (raSrcAddressID.getAccountID ()); } if (saSendMax.isNative () && amount.isNative ()) return RPC::make_error (rpcINVALID_PARAMS, "Cannot build XRP to XRP paths."); { LegacyPathFind lpf (role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); STPathSet spsPaths; STPath fullLiquidityPath; bool valid = ledgerFacade.findPathsForOneIssuer ( dstAccountID, saSendMax.issue (), amount, getConfig ().PATH_SEARCH_OLD, 4, // iMaxPaths spsPaths, fullLiquidityPath); if (!valid) { WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: No paths found."; return rpcError (rpcNO_PATH); } WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: " << spsPaths.getJson (0); if (!spsPaths.empty ()) tx_json["Paths"] = spsPaths.getJson (0); } } return Json::Value(); }
TER computeReverseLiquidityForAccount ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality) { TER terResult = tesSUCCESS; auto const lastNodeIndex = pathState.nodes().size () - 1; auto const isFinalNode = (nodeIndex == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; auto& previousNode = pathState.nodes()[nodeIndex ? nodeIndex - 1 : 0]; auto& node = pathState.nodes()[nodeIndex]; auto& nextNode = pathState.nodes()[isFinalNode ? lastNodeIndex : nodeIndex + 1]; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex || previousNode.isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode.isAccount(); const uint160& previousAccountID = previousNodeIsAccount ? previousNode.account_ : node.account_; const uint160& nextAccountID = nextNodeIsAccount ? nextNode.account_ : node.account_; // Offers are always issue. // This is the quality from from the previous node to this one. const std::uint32_t uQualityIn = (nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleQualityIn ( node.account_, previousAccountID, node.currency_) : QUALITY_ONE; // And this is the quality from the next one to this one. const std::uint32_t uQualityOut = (nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleQualityOut ( node.account_, nextAccountID, node.currency_) : QUALITY_ONE; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, previousAccountID, node.currency_) : STAmount (node.currency_, node.account_); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleLimit ( node.account_, previousAccountID, node.currency_) : STAmount (node.currency_, node.account_); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, nextAccountID, node.currency_) : STAmount (node.currency_, node.account_); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount>" << " nodeIndex=%d/%d" << nodeIndex << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node.account_ << " nextAccountID=" << nextAccountID << " currency_=" << node.currency_ << " uQualityIn=" << uQualityIn << " uQualityOut=" << uQualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > zero) ? saPrvOwed : STAmount (saPrvOwed.getCurrency (), saPrvOwed.getIssuer ()); // This is the amount we're actually going to be setting for the previous // node. STAmount& saPrvRedeemAct = previousNode.saRevRedeem; // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < zero) ? saPrvLimit + saPrvOwed : saPrvLimit; STAmount& saPrvIssueAct = previousNode.saRevIssue; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode.saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( deliverCurrency, previousNode.saRevDeliver.getIssuer (), -1); // Unlimited delivery. STAmount& saPrvDeliverAct = previousNode.saRevDeliver; // For nextNodeIsAccount const STAmount& saCurRedeemReq = node.saRevRedeem; // Set to zero, because we're trying to hit the previous node. STAmount saCurRedeemAct ( saCurRedeemReq.getCurrency (), saCurRedeemReq.getIssuer ()); const STAmount& saCurIssueReq = node.saRevIssue; // Track the amount we actually redeem. STAmount saCurIssueAct ( saCurIssueReq.getCurrency (), saCurIssueReq.getIssuer ()); // For !nextNodeIsAccount const STAmount& saCurDeliverReq = node.saRevDeliver; STAmount saCurDeliverAct ( saCurDeliverReq.getCurrency (), saCurDeliverReq.getIssuer ()); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saNxtOwed:" << saNxtOwed; WriteLog (lsTRACE, RippleCalc) << pathState.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); assert (!saCurIssueReq // If not issuing, fine. || saNxtOwed >= zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. if (nodeIndex == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState.outReq() - pathState.outAct(), saPrvLimit + saPrvOwed); STAmount saCurWantedAct ( saCurWantedReq.getCurrency (), saCurWantedReq.getIssuer ()); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: account --> ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); saPrvRedeemAct = saCurWantedAct; uRateMax = STAmount::uRateOne; WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) saPrvRedeemAct=" << saPrvRedeemAct << " uRateMax=" << STAmount::saFromRate (uRateMax).getText (); } else { saPrvRedeemAct.clear (saPrvRedeemReq); } // Calculate issuing. saPrvIssueAct.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Issuing: Rate: quality in : 1.0" << " saPrvIssueAct:" << saPrvIssueAct << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (saCurRedeemReq // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs as 1:1. computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (saCurRedeemReq != saCurRedeemAct // The current node has more IOUs to redeem. && saPrvRedeemAct == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out computeRippleLiquidity ( rippleCalc, uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (saCurIssueReq // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); WriteLog (lsDEBUG, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > zero // Previous has IOUs to redeem. && saCurDeliverReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. && saCurDeliverReq != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << " saCurDeliverReq:" << saCurDeliverReq << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. const STAmount& saCurWantedReq = pathState.outReq() - pathState.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState.outAct() << " saOutReq:" << pathState.outReq(); if (saCurWantedReq <= zero) { // TEMPORARY emergency fix // // TODO(tom): why can't saCurWantedReq be -1 if you want to // compute maximum liquidity? This might be unimplemented // functionality. TODO(tom): should the same check appear in // other paths or even be pulled up? WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive"; return tefEXCEPTION; } assert (saCurWantedReq > zero); // FIXME: We got one of these // The previous node is an offer; we are receiving our own currency; // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (saCurRedeemReq) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality out. // // Rate : 1.0 : quality out computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); } // deliver -> issue. if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. && saCurIssueReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " saCurRedeemReq:" << saCurRedeemReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saCurIssueReq:" << saCurIssueReq; if (!saPrvDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedger ( context.params, lpLedger, context.netOps); if (!lpLedger) return jvResult; } if (!context.params.isMember (jss::source_account)) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params[jss::source_account].isString () || !raSrc.setAccountID ( context.params[jss::source_account].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember (jss::destination_account)) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params[jss::destination_account].isString () || !raDst.setAccountID ( context.params[jss::destination_account].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember (jss::destination_amount) || ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember (jss::source_currencies) && (!context.params[jss::source_currencies].isArray () || !context.params[jss::source_currencies].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.netOps.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember (jss::source_currencies)) { jvSrcCurrencies = context.params[jss::source_currencies]; } else { jvSrcCurrencies = buildSrcCurrencies(raSrc, cache); } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult[jss::destination_currencies] = jvDestCur; jvResult[jss::destination_account] = raDst.humanAccountID (); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember(jss::search_depth) && context.params[jss::search_depth].isIntegral()) { int rLev = context.params[jss::search_depth].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } auto contextPaths = context.params.isMember(jss::paths) ? boost::optional<Json::Value>(context.params[jss::paths]) : boost::optional<Json::Value>(boost::none); auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount, lpLedger, jvSrcCurrencies, contextPaths, level); if (!pathFindResult.first) return pathFindResult.second; // Each alternative differs by source currency. jvResult[jss::alternatives] = pathFindResult.second; } WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("ripple_path_find< %s") % jvResult); return jvResult; }
/** Fill in the fee on behalf of the client. This is called when the client does not explicitly specify the fee. The client may also put a ceiling on the amount of the fee. This ceiling is expressed as a multiplier based on the current ledger's fee schedule. JSON fields "Fee" The fee paid by the transaction. Omitted when the client wants the fee filled in. "fee_mult_max" A multiplier applied to the current ledger's transaction fee that caps the maximum the fee server should auto fill. If this optional field is not specified, then a default multiplier is used. @param tx The JSON corresponding to the transaction to fill in @param ledger A ledger for retrieving the current fee schedule @param result A JSON object for injecting error results, if any @param admin `true` if this is called by an administrative endpoint. */ static void autofill_fee ( Json::Value& request, RPCDetail::LedgerFacade& ledgerFacade, Json::Value& result, bool admin) { Json::Value& tx (request["tx_json"]); if (tx.isMember ("Fee")) return; std::uint64_t feeByTrans = 0; if (tx.isMember("TransactionType") && tx["TransactionType"].asString() == "Payment") { if (!tx.isMember("Destination")) { RPC::inject_error (rpcINVALID_PARAMS, "no destination account", result); return; } Config d; std::string dstAccountID = tx["Destination"].asString(); RippleAddress dstAddress; if (!dstAddress.setAccountID(dstAccountID)) { RPC::inject_error (rpcINVALID_PARAMS, "invalid account id", result); return; } //dst account not exist yet, charge a fix amount of fee(0.01) for creating if (!ledgerFacade.isAccountExist(dstAddress.getAccountID())) { feeByTrans = d.FEE_DEFAULT_CREATE; } //if currency is native(VRP/VBC), charge 1/1000 of transfer amount, //otherwise charge a fix amount of fee(0.001) if (tx.isMember("Amount")) { STAmount amount; if (!amountFromJsonNoThrow(amount, tx["Amount"])) { RPC::inject_error (rpcINVALID_PARAMS, "wrong amount format", result); return; } feeByTrans += amount.isNative() ? amount.getNValue() * d.FEE_DEFAULT_RATE_NATIVE : d.FEE_DEFAULT_NONE_NATIVE; } } int mult = Tuning::defaultAutoFillFeeMultiplier; if (request.isMember ("fee_mult_max")) { if (request["fee_mult_max"].isNumeric ()) { mult = request["fee_mult_max"].asInt(); } else { RPC::inject_error (rpcHIGH_FEE, RPC::expected_field_message ( "fee_mult_max", "a number"), result); return; } } // Default fee in fee units. std::uint64_t const feeDefault = getConfig().TRANSACTION_FEE_BASE; // Administrative endpoints are exempt from local fees. std::uint64_t const fee = ledgerFacade.scaleFeeLoad (feeDefault, admin); std::uint64_t const limit = mult * ledgerFacade.scaleFeeBase (feeDefault); if (fee > limit) { std::stringstream ss; ss << "Fee of " << fee << " exceeds the requested tx limit of " << limit; RPC::inject_error (rpcHIGH_FEE, ss.str(), result); return; } std::stringstream ss; ss << std::max(fee, feeByTrans); tx["Fee"] = ss.str(); }
void rippleLiquidity ( RippleCalc& rippleCalc, std::uint32_t const uQualityIn, std::uint32_t const uQualityOut, STAmount const& saPrvReq, // --> in limit including fees, <0 = unlimited STAmount const& saCurReq, // --> out limit STAmount& saPrvAct, // <-> in limit including achieved so far: <-- <= --> STAmount& saCurAct, // <-> out limit including achieved so far: <-- <= --> std::uint64_t& uRateMax) { WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity>" << " uQualityIn=" << uQualityIn << " uQualityOut=" << uQualityOut << " saPrvReq=" << saPrvReq << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct << " saCurAct=" << saCurAct; // saCurAct was once zero in a production server. assert (saCurReq != zero); assert (saCurReq > zero); assert (saPrvReq.getCurrency () == saCurReq.getCurrency ()); assert (saPrvReq.getCurrency () == saPrvAct.getCurrency ()); assert (saPrvReq.getIssuer () == saPrvAct.getIssuer ()); const bool bPrvUnlimited = (saPrvReq < zero); // -1 means unlimited. // Unlimited stays unlimited - don't do calculations. // How much could possibly flow through the previous node? const STAmount saPrv = bPrvUnlimited ? saPrvReq : saPrvReq - saPrvAct; // How much could possibly flow through the current node? const STAmount saCur = saCurReq - saCurAct; WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity: " << " bPrvUnlimited=" << bPrvUnlimited << " saPrv=" << saPrv << " saCur=" << saCur; // If nothing can flow, we might as well not do any work. if (saPrv == zero || saCur == zero) return; if (uQualityIn >= uQualityOut) { // You're getting better quality than you asked for, so no fee. WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity: No fees"; // Only process if the current rate, 1:1, is not worse than the previous // rate, uRateMax - otherwise there is no flow. if (!uRateMax || STAmount::uRateOne <= uRateMax) { // Limit amount to transfer if need - the minimum of amount being // paid and the amount that's wanted. STAmount saTransfer = bPrvUnlimited ? saCur : std::min (saPrv, saCur); // In reverse, we want to propagate the limited cur to prv and set // actual cur. // // In forward, we want to propagate the limited prv to cur and set // actual prv. // // This is the actual flow. saPrvAct += saTransfer; saCurAct += saTransfer; // If no rate limit, set rate limit to avoid combining with // something with a worse rate. if (uRateMax == 0) uRateMax = STAmount::uRateOne; } } else { // If the quality is worse than the previous WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity: Fee"; std::uint64_t uRate = getRate ( STAmount (uQualityOut), STAmount (uQualityIn)); // If the next rate is at least as good as the current rate, process. if (!uRateMax || uRate <= uRateMax) { auto currency = saCur.getCurrency (); auto uCurIssuerID = saCur.getIssuer (); // current actual = current request * (quality out / quality in). auto numerator = mulRound ( saCur, uQualityOut, {currency, uCurIssuerID}, true); // True means "round up" to get best flow. STAmount saCurIn = divRound ( numerator, uQualityIn, {currency, uCurIssuerID}, true); WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity:" << " bPrvUnlimited=" << bPrvUnlimited << " saPrv=" << saPrv << " saCurIn=" << saCurIn; if (bPrvUnlimited || saCurIn <= saPrv) { // All of current. Some amount of previous. saCurAct += saCur; saPrvAct += saCurIn; WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity:3c:" << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct; } else { // There wasn't enough money to start with - so given the // limited input, how much could we deliver? // current actual = previous request // * (quality in / quality out). // This is inverted compared to the code above because we're // going the other way Issue issue{currency, uCurIssuerID}; auto numerator = mulRound ( saPrv, uQualityIn, issue, true); // A part of current. All of previous. (Cur is the driver // variable.) STAmount saCurOut = divRound ( numerator, uQualityOut, issue, true); WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity:4: saCurReq=" << saCurReq; saCurAct += saCurOut; saPrvAct = saPrvReq; } if (!uRateMax) uRateMax = uRate; } } WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity<" << " uQualityIn=" << uQualityIn << " uQualityOut=" << uQualityOut << " saPrvReq=" << saPrvReq << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct << " saCurAct=" << saCurAct; }
TER WalletAddTransactor::doApply () { std::uint32_t const uTxFlags = mTxn.getFlags (); if (uTxFlags & tfUniversalMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } Blob const vucPubKey = mTxn.getFieldVL (sfPublicKey); Blob const vucSignature = mTxn.getFieldVL (sfSignature); uint160 const uAuthKeyID (mTxn.getFieldAccount160 (sfRegularKey)); RippleAddress const naMasterPubKey ( RippleAddress::createAccountPublic (vucPubKey)); uint160 const uDstAccountID (naMasterPubKey.getAccountID ()); // FIXME: This should be moved to the transaction's signature check logic and cached if (!naMasterPubKey.verifySignature( Serializer::getSHA512Half (uAuthKeyID.begin (), uAuthKeyID.size ()), vucSignature)) { m_journal.trace << "Unauthorized: bad signature "; return tefBAD_ADD_AUTH; } SLE::pointer sleDst (mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID))); if (sleDst) { m_journal.trace << "account already created"; return tefCREATED; } // Direct STR payment. STAmount saDstAmount = mTxn.getFieldAmount (sfAmount); STAmount saPaid = mTxn.getTransactionFee (); STAmount const saSrcBalance = mTxnAccount->getFieldAmount (sfBalance); std::uint32_t const uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount); std::uint64_t const uReserve = mEngine->getLedger ()->getReserve (uOwnerCount); // Make sure have enough reserve to send. Allow final spend to use reserve // for fee. // Note: Reserve is not scaled by fee. if (saSrcBalance + saPaid < saDstAmount + uReserve) { // Vote no. However, transaction might succeed, if applied in a // different order. m_journal.trace << "Delay transaction: Insufficient funds: %s / %s (%d)" << saSrcBalance.getText () << " / " << (saDstAmount + uReserve).getText () << " with reserve = " << uReserve; return tecUNFUNDED_ADD; } // Deduct initial balance from source account. mTxnAccount->setFieldAmount (sfBalance, saSrcBalance - saDstAmount); // Create the account. sleDst = mEngine->entryCreate (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID)); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); sleDst->setFieldAmount (sfBalance, saDstAmount); sleDst->setFieldAccount (sfRegularKey, uAuthKeyID); return tesSUCCESS; }
static Json::Value signPayment( Json::Value const& params, Json::Value& tx_json, RippleAddress const& raSrcAddressID, Ledger::pointer lSnapshot, int role) { RippleAddress dstAccountID; if (!tx_json.isMember ("Amount")) return RPC::missing_field_error ("tx_json.Amount"); STAmount amount; if (!amount.bSetJson (tx_json ["Amount"])) return RPC::invalid_field_error ("tx_json.Amount"); if (!tx_json.isMember ("Destination")) return RPC::missing_field_error ("tx_json.Destination"); if (!dstAccountID.setAccountID (tx_json["Destination"].asString ())) return RPC::invalid_field_error ("tx_json.Destination"); if (tx_json.isMember ("Paths") && params.isMember ("build_path")) return RPC::make_error (rpcINVALID_PARAMS, "Cannot specify both 'tx_json.Paths' and 'tx_json.build_path'"); if (!tx_json.isMember ("Paths") && tx_json.isMember ("Amount") && params.isMember ("build_path")) { // Need a ripple path. STPathSet spsPaths; uint160 uSrcCurrencyID; uint160 uSrcIssuerID; STAmount saSendMax; if (tx_json.isMember ("SendMax")) { if (!saSendMax.bSetJson (tx_json ["SendMax"])) return RPC::invalid_field_error ("tx_json.SendMax"); } else { // If no SendMax, default to Amount with sender as issuer. saSendMax = amount; saSendMax.setIssuer (raSrcAddressID.getAccountID ()); } if (saSendMax.isNative () && amount.isNative ()) return RPC::make_error (rpcINVALID_PARAMS, "Cannot build STR to STR paths."); { LegacyPathFind lpf (role == Config::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); bool bValid; auto cache = boost::make_shared<RippleLineCache> (lSnapshot); Pathfinder pf (cache, raSrcAddressID, dstAccountID, saSendMax.getCurrency (), saSendMax.getIssuer (), amount, bValid); STPath extraPath; if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_OLD, 4, spsPaths, extraPath)) { WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: No paths found."; return rpcError (rpcNO_PATH); } WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: " << spsPaths.getJson (0); if (!spsPaths.isEmpty ()) tx_json["Paths"] = spsPaths.getJson (0); } } return Json::Value(); }
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; }
// Take as much as possible. Adjusts account balances. Charges fees on top to taker. // --> uBookBase: The order book to take against. // --> saTakerPays: What the taker offers (w/ issuer) // --> saTakerGets: What the taker wanted (w/ issuer) // <-- saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer. // <-- saTakerGot: What taker got not including fees. To reduce an offer. // <-- terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING // <-- bUnfunded: if tesSUCCESS, consider offer unfunded after taking. TER OfferCreateTransactor::takeOffers ( const bool bOpenLedger, const bool bPassive, const bool bSell, uint256 const& uBookBase, const uint160& uTakerAccountID, SLE::ref sleTakerAccount, const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, bool& bUnfunded) { // The book has the most elements. Take the perspective of the book. // Book is ordered for taker: taker pays / taker gets (smaller is better) // The order is for the other books currencys for get and pays are opposites. // We want the same ratio for the respective currencies. // So we swap paid and gets for determing take quality. assert (saTakerPays && saTakerGets); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString (); LedgerEntrySet& lesActive = mEngine->getNodes (); uint256 uTipIndex = uBookBase; const uint256 uBookEnd = Ledger::getQualityNext (uBookBase); const uint64 uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); STAmount saTakerRate = STAmount::setRate (uTakeQuality); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer (); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer (); TER terResult = temUNCERTAIN; boost::unordered_set<uint256> usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set<uint160> usAccountTouched; // Accounts touched. saTakerPaid = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ()); saTakerGot = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ()); bUnfunded = false; while (temUNCERTAIN == terResult) { SLE::pointer sleOfferDir; uint64 uTipQuality = 0; STAmount saTakerFunds = lesActive.accountFunds (uTakerAccountID, saTakerPays); STAmount saSubTakerPays = saTakerPays - saTakerPaid; // How much more to spend. STAmount saSubTakerGets = saTakerGets - saTakerGot; // How much more is wanted. // Figure out next offer to take, if needed. if (saTakerFunds.isPositive () // Taker has funds available. && saSubTakerPays.isPositive () && saSubTakerGets.isPositive ()) { sleOfferDir = mEngine->entryCache (ltDIR_NODE, lesActive.getNextLedgerIndex (uTipIndex, uBookEnd)); if (sleOfferDir) { uTipIndex = sleOfferDir->getIndex (); uTipQuality = Ledger::getQuality (uTipIndex); WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: possible counter offer found: uTipQuality=%d uTipIndex=%s") % uTipQuality % uTipIndex.ToString ()); } else { WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: counter offer book is empty: " << uTipIndex.ToString () << " ... " << uBookEnd.ToString (); } } if (!saTakerFunds.isPositive ()) // Taker has no funds. { // Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: done: taker unfunded."; bUnfunded = true; // Don't create an order. terResult = tesSUCCESS; } else if (!sleOfferDir // No offer directory to take. || uTakeQuality < uTipQuality // No offers of sufficient quality available. || (bPassive && uTakeQuality == uTipQuality)) { // Done. STAmount saTipRate = sleOfferDir ? STAmount::setRate (uTipQuality) : saTakerRate; WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d") % !!sleOfferDir % uTakeQuality % (uTakeQuality == uTipQuality ? '=' : uTakeQuality < uTipQuality ? '<' : '>') % uTipQuality % saTakerRate % (saTakerRate == saTipRate ? '=' : saTakerRate < saTipRate ? '<' : '>') % saTipRate % bPassive); terResult = tesSUCCESS; } else { // Have an offer directory to consider. WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: considering dir: " << sleOfferDir->getJson (0); SLE::pointer sleBookNode; unsigned int uBookEntry; uint256 uOfferIndex; lesActive.dirFirst (uTipIndex, sleBookNode, uBookEntry, uOfferIndex); SLE::pointer sleOffer = mEngine->entryCache (ltOFFER, uOfferIndex); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0); const uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets); STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays); STAmount saOfferFunds; // Funds of offer owner to payout. bool bValid; bValid = bValidOffer ( sleOffer, uOfferIndex, uOfferOwnerID, saOfferPays, saOfferGets, uTakerAccountID, usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched, saOfferFunds); if (bValid) { STAmount saSubTakerPaid; STAmount saSubTakerGot; STAmount saTakerIssuerFee; STAmount saOfferIssuerFee; STAmount saOfferRate = STAmount::setRate (uTipQuality); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferRate: " << saOfferRate.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText (); bool bOfferDelete = STAmount::applyOffer ( bSell, lesActive.rippleTransferRate (uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), lesActive.rippleTransferRate (uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), saOfferRate, saOfferFunds, saTakerFunds, saOfferPays, saOfferGets, saSubTakerPays, saSubTakerGets, saSubTakerPaid, saSubTakerGot, saTakerIssuerFee, saOfferIssuerFee); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); // Adjust offer // Offer owner will pay less. Subtract what taker just got. sleOffer->setFieldAmount (sfTakerGets, saOfferPays -= saSubTakerGot); // Offer owner will get less. Subtract what owner just paid. sleOffer->setFieldAmount (sfTakerPays, saOfferGets -= saSubTakerPaid); mEngine->entryModify (sleOffer); if (bOfferDelete) { // Offer now fully claimed or now unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete."; usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert (uOfferOwnerID); } else if (saSubTakerGot) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim."; if (!saOfferPays.isPositive () || !saOfferGets.isPositive ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT."; bUnfunded = true; terResult = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } } else { // Taker got nothing, probably due to rounding. Consider taker unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: No claim."; bUnfunded = true; terResult = tesSUCCESS; // Done. } assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ()); assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ()); if (!bUnfunded) { // Distribute funds. The sends charge appropriate fees which are implied by offer. terResult = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker. if (tesSUCCESS == terResult) terResult = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner. if (!bSell) { // Buy semantics: Reduce amount considered paid by taker's rate. Not by actual cost which is lower. // That is, take less as to just satify our buy requirement. STAmount saTakerCould = saTakerPays - saTakerPaid; // Taker could pay. if (saTakerFunds < saTakerCould) saTakerCould = saTakerFunds; STAmount saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerCould: " << saTakerCould.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerRate: " << saTakerRate.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerUsed: " << saTakerUsed.getFullText (); saSubTakerPaid = std::min (saTakerCould, saTakerUsed); } saTakerPaid += saSubTakerPaid; saTakerGot += saSubTakerGot; if (tesSUCCESS == terResult) terResult = temUNCERTAIN; } } } } WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult); if (tesSUCCESS == terResult) { // On success, delete offers that became unfunded. BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString (); terResult = lesActive.offerDelete (uOfferIndex); if (tesSUCCESS != terResult) break; }
// To deliver from an order book, when computing TER PathCursor::deliverNodeReverseImpl ( AccountID const& uOutAccountID, // --> Output owner's account. STAmount const& saOutReq, // --> Funds requested to be // delivered for an increment. STAmount& saOutAct, // <-- Funds actually delivered for an // increment bool callerHasLiquidity ) const { TER resultCode = tesSUCCESS; // Accumulation of what the previous node must deliver. // Possible optimization: Note this gets zeroed on each increment, ideally // only on first increment, then it could be a limit on the forward pass. saOutAct.clear (saOutReq); JLOG (j_.trace()) << "deliverNodeReverse>" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; assert (saOutReq != zero); int loopCount = 0; auto viewJ = rippleCalc_.logs_.journal ("View"); // While we did not deliver as much as requested: while (saOutAct < saOutReq) { if (++loopCount > (multiQuality_ ? CALC_NODE_DELIVER_MAX_LOOPS_MQ : CALC_NODE_DELIVER_MAX_LOOPS)) { JLOG (j_.warn()) << "loop count exceeded"; return telFAILED_PROCESSING; } resultCode = advanceNode (saOutAct, true, callerHasLiquidity); // If needed, advance to next funded offer. if (resultCode != tesSUCCESS || !node().offerIndex_) // Error or out of offers. break; auto const hasFee = node().offerOwnerAccount_ == node().issue_.account || uOutAccountID == node().issue_.account; // Issuer sending or receiving. const STAmount saOutFeeRate = hasFee ? STAmount::saOne // No fee. : node().transferRate_; // Transfer rate of issuer. JLOG (j_.trace()) << "deliverNodeReverse:" << " offerOwnerAccount_=" << node().offerOwnerAccount_ << " uOutAccountID=" << uOutAccountID << " node().issue_.account=" << node().issue_.account << " node().transferRate_=" << node().transferRate_ << " saOutFeeRate=" << saOutFeeRate; if (multiQuality_) { // In multi-quality mode, ignore rate. } else if (!node().saRateMax) { // Set initial rate. node().saRateMax = saOutFeeRate; JLOG (j_.trace()) << "deliverNodeReverse: Set initial rate:" << " node().saRateMax=" << node().saRateMax << " saOutFeeRate=" << saOutFeeRate; } else if (saOutFeeRate > node().saRateMax) { // Offer exceeds initial rate. JLOG (j_.trace()) << "deliverNodeReverse: Offer exceeds initial rate:" << " node().saRateMax=" << node().saRateMax << " saOutFeeRate=" << saOutFeeRate; break; // Done. Don't bother looking for smaller transferRates. } else if (saOutFeeRate < node().saRateMax) { // Reducing rate. Additional offers will only considered for this // increment if they are at least this good. // // At this point, the overall rate is reducing, while the overall // rate is not saOutFeeRate, it would be wrong to add anything with // a rate above saOutFeeRate. // // The rate would be reduced if the current offer was from the // issuer and the previous offer wasn't. node().saRateMax = saOutFeeRate; JLOG (j_.trace()) << "deliverNodeReverse: Reducing rate:" << " node().saRateMax=" << node().saRateMax; } // Amount that goes to the taker. STAmount saOutPassReq = std::min ( std::min (node().saOfferFunds, node().saTakerGets), saOutReq - saOutAct); // Maximum out - assuming no out fees. STAmount saOutPassAct = saOutPassReq; // Amount charged to the offer owner. // // The fee goes to issuer. The fee is paid by offer owner and not passed // as a cost to taker. // // Round down: prefer liquidity rather than microscopic fees. STAmount saOutPlusFees = mulRound ( saOutPassAct, saOutFeeRate, saOutPassAct.issue (), false); // Offer out with fees. JLOG (j_.trace()) << "deliverNodeReverse:" << " saOutReq=" << saOutReq << " saOutAct=" << saOutAct << " node().saTakerGets=" << node().saTakerGets << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; if (saOutPlusFees > node().saOfferFunds) { // Offer owner can not cover all fees, compute saOutPassAct based on // node().saOfferFunds. saOutPlusFees = node().saOfferFunds; // Round up: prefer liquidity rather than microscopic fees. But, // limit by requested. auto fee = divRound (saOutPlusFees, saOutFeeRate, saOutPlusFees.issue (), true); saOutPassAct = std::min (saOutPassReq, fee); JLOG (j_.trace()) << "deliverNodeReverse: Total exceeds fees:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees << " node().saOfferFunds=" << node().saOfferFunds; } // Compute portion of input needed to cover actual output. auto outputFee = mulRound ( saOutPassAct, node().saOfrRate, node().saTakerPays.issue (), true); if (*stAmountCalcSwitchover == false && ! outputFee) { JLOG (j_.fatal()) << "underflow computing outputFee " << "saOutPassAct: " << saOutPassAct << " saOfrRate: " << node ().saOfrRate; return telFAILED_PROCESSING; } STAmount saInPassReq = std::min (node().saTakerPays, outputFee); STAmount saInPassAct; JLOG (j_.trace()) << "deliverNodeReverse:" << " outputFee=" << outputFee << " saInPassReq=" << saInPassReq << " node().saOfrRate=" << node().saOfrRate << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; if (!saInPassReq) // FIXME: This is bogus { // After rounding did not want anything. JLOG (j_.debug()) << "deliverNodeReverse: micro offer is unfunded."; node().bEntryAdvance = true; continue; } // Find out input amount actually available at current rate. else if (!isXRP(previousNode().account_)) { // account --> OFFER --> ? // Due to node expansion, previous is guaranteed to be the issuer. // // Previous is the issuer and receiver is an offer, so no fee or // quality. // // Previous is the issuer and has unlimited funds. // // Offer owner is obtaining IOUs via an offer, so credit line limits // are ignored. As limits are ignored, don't need to adjust // previous account's balance. saInPassAct = saInPassReq; JLOG (j_.trace()) << "deliverNodeReverse: account --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } else { // offer --> OFFER --> ? // Compute in previous offer node how much could come in. // TODO(tom): Fix nasty recursion here! resultCode = increment(-1).deliverNodeReverseImpl( node().offerOwnerAccount_, saInPassReq, saInPassAct, saOutAct > zero); if (amendmentRIPD1141(view().info().parentCloseTime)) { // The recursive call is dry this time, but we have liquidity // from previous calls if (resultCode == tecPATH_DRY && saOutAct > zero) { resultCode = tesSUCCESS; break; } } JLOG (j_.trace()) << "deliverNodeReverse: offer --> OFFER --> ? :" << " saInPassAct=" << saInPassAct; } if (resultCode != tesSUCCESS) break; if (saInPassAct < saInPassReq) { // Adjust output to conform to limited input. auto outputRequirements = divRound (saInPassAct, node ().saOfrRate, node ().saTakerGets.issue (), true); saOutPassAct = std::min (saOutPassReq, outputRequirements); auto outputFees = mulRound (saOutPassAct, saOutFeeRate, saOutPassAct.issue (), true); saOutPlusFees = std::min (node().saOfferFunds, outputFees); JLOG (j_.trace()) << "deliverNodeReverse: adjusted:" << " saOutPassAct=" << saOutPassAct << " saOutPlusFees=" << saOutPlusFees; } else { // TODO(tom): more logging here. assert (saInPassAct == saInPassReq); } // Funds were spent. node().bFundsDirty = true; // Want to deduct output to limit calculations while computing reverse. // Don't actually need to send. // // Sending could be complicated: could fund a previous offer not yet // visited. However, these deductions and adjustments are tenative. // // Must reset balances when going forward to perform actual transfers. resultCode = accountSend(view(), node().offerOwnerAccount_, node().issue_.account, saOutPassAct, viewJ); if (resultCode != tesSUCCESS) break; // Adjust offer STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct; STAmount saTakerPaysNew = node().saTakerPays - saInPassAct; if (saTakerPaysNew < zero || saTakerGetsNew < zero) { JLOG (j_.warn()) << "deliverNodeReverse: NEGATIVE:" << " node().saTakerPaysNew=" << saTakerPaysNew << " node().saTakerGetsNew=" << saTakerGetsNew; resultCode = telFAILED_PROCESSING; break; } node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew); node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew); view().update (node().sleOffer); if (saOutPassAct == node().saTakerGets) { // Offer became unfunded. JLOG (j_.debug()) << "deliverNodeReverse: offer became unfunded."; node().bEntryAdvance = true; // XXX When don't we want to set advance? } else { assert (saOutPassAct < node().saTakerGets); } saOutAct += saOutPassAct; // Accumulate what is to be delivered from previous node. previousNode().saRevDeliver += saInPassAct; } if (saOutAct > saOutReq) { JLOG (j_.warn()) << "deliverNodeReverse: TOO MUCH:" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq; } assert(saOutAct <= saOutReq); if (resultCode == tesSUCCESS && !saOutAct) resultCode = tecPATH_DRY; // Unable to meet request, consider path dry. // Design invariant: if nothing was actually delivered, return tecPATH_DRY. JLOG (j_.trace()) << "deliverNodeReverse<" << " saOutAct=" << saOutAct << " saOutReq=" << saOutReq << " saPrvDlvReq=" << previousNode().saRevDeliver; return resultCode; }
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) { m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal"); ScopedLockType sl (mLock); if (!isValid (cache)) return jvStatus; jvStatus = Json::objectValue; auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto usCurrencies = accountSourceCurrencies (*raSrcAccount, cache, true); bool sameAccount = *raSrcAccount == *raDstAccount; for (auto const& c: usCurrencies) { if (!sameAccount || (c != saDstAmount.getCurrency ())) { if (c.isZero ()) sourceCurrencies.insert ({c, xrpAccount()}); else sourceCurrencies.insert ({c, *raSrcAccount}); } } } if (hasCompletion ()) { // Old ripple_path_find API gives destination_currencies auto& destCurrencies = (jvStatus[jss::destination_currencies] = Json::arrayValue); auto usCurrencies = accountDestCurrencies (*raDstAccount, cache, true); for (auto const& c : usCurrencies) destCurrencies.append (to_string (c)); } jvStatus[jss::source_account] = getApp().accountIDCache().toBase58(*raSrcAccount); jvStatus[jss::destination_account] = getApp().accountIDCache().toBase58(*raDstAccount); jvStatus[jss::destination_amount] = saDstAmount.getJson (0); jvStatus[jss::full_reply] = ! fast; if (jvId) jvStatus["id"] = jvId; Json::Value jvArray = Json::arrayValue; int iLevel = iLastLevel; bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) { // first pass if (loaded || fast) iLevel = getConfig().PATH_SEARCH_FAST; else iLevel = getConfig().PATH_SEARCH; } else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast) { // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) { // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else { // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } m_journal.debug << iIdentifier << " processing at level " << iLevel; bool found = false; FindPaths fp ( cache, *raSrcAccount, *raDstAccount, saDstAmount, iLevel, 4); // iMaxPaths for (auto const& currIssuer: sourceCurrencies) { { STAmount test (currIssuer, 1); if (m_journal.debug) { m_journal.debug << iIdentifier << " Trying to find paths: " << test.getFullText (); } } STPathSet& spsPaths = mContext[currIssuer]; STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( currIssuer, spsPaths, fullLiquidityPath); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; if (valid) { boost::optional<PaymentSandbox> sandbox; sandbox.emplace(&*cache->getLedger(), tapNONE); auto& sourceAccount = !isXRP (currIssuer.account) ? currIssuer.account : isXRP (currIssuer.currency) ? xrpAccount() : *raSrcAccount; STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1); saMaxAmount.negate (); m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; auto rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (!fullLiquidityPath.empty() && (rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL)) { m_journal.debug << iIdentifier << " Trying with an extra path element"; spsPaths.push_back (fullLiquidityPath); sandbox.emplace(&*cache->getLedger(), tapNONE); rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (rc.result () != tesSUCCESS) m_journal.warning << iIdentifier << " Failed with covering path " << transHuman (rc.result ()); else m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); rc.actualAmountIn.setIssuer (sourceAccount); jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0); jvEntry[jss::paths_computed] = spsPaths.getJson (0); if (hasCompletion ()) { // Old ripple_path_find API requires this jvEntry[jss::paths_canonical] = Json::arrayValue; } found = true; jvArray.append (jvEntry); } else { m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (rc.result ()); } } else { m_journal.debug << iIdentifier << " No paths found"; } } iLastLevel = iLevel; bLastSuccess = found; if (fast && ptQuickReply.is_not_a_date_time()) { ptQuickReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds()); } else if (!fast && ptFullReply.is_not_a_date_time()) { ptFullReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds()); } jvStatus[jss::alternatives] = jvArray; return jvStatus; }