TER PathCursor::reverseLiquidity () const { // Every account has a transfer rate for its issuances. // TOMOVE: The account charges // a fee when third parties transfer that account's own issuances. // node.transferRate_ caches the output transfer rate for this node. node().transferRate_ = amountFromRate ( rippleTransferRate (view(), node().issue_.account)); if (node().isAccount ()) return reverseLiquidityForAccount (); // Otherwise the node is an Offer. if (isXRP (nextNode().account_)) { WriteLog (lsTRACE, RippleCalc) << "reverseLiquidityForOffer: " << "OFFER --> offer: nodeIndex_=" << nodeIndex_; return tesSUCCESS; // This control structure ensures deliverNodeReverse is only called for the // rightmost offer in a chain of offers - which means that // deliverNodeReverse has to take all of those offers into consideration. } // Next is an account node, resolve current offer node's deliver. STAmount saDeliverAct; WriteLog (lsTRACE, RippleCalc) << "reverseLiquidityForOffer: OFFER --> account:" << " nodeIndex_=" << nodeIndex_ << " saRevDeliver=" << node().saRevDeliver; // The next node wants the current node to deliver this much: return deliverNodeReverse ( nextNode().account_, node().saRevDeliver, saDeliverAct); }
void fill_fee (Json::Value& jv, ReadView const& view) { if (jv.isMember(jss::Fee)) return; auto fee = view.fees().base; if (jv.isMember (jss::TransactionType) && (jv[jss::TransactionType].asString () == "Payment" || jv[jss::TransactionType].asString () == "ActiveAccount")) { if (jv.isMember (jss::Amount)) { STAmount amount; amountFromJsonNoThrow (amount, jv[jss::Amount]); if (amount.native ()) fee = std::max (multiply (amount, amountFromRate (Config ().FEE_DEFAULT_RATE_NATIVE), amount.issue ()) .mantissa (), Config ().FEE_DEFAULT_MIN_NATIVE); } if (jv.isMember (jv[jss::TransactionType].asString () == "ActiveAccount" ? jss::Reference : jss::Destination)) { auto const account = parseBase58<AccountID> ( jv[jv[jss::TransactionType].asString () == "ActiveAccount" ? jss::Reference : jss::Destination] .asString ()); if (!account) throw parse_error ( "unexpected invalid Destination"); if (!view.exists (keylet::account (*account))) fee += Config ().FEE_DEFAULT_CREATE; } } jv[jss::Fee] = std::to_string( fee); }
// <-- TER: Only returns tepPATH_PARTIAL if partialPaymentAllowed. TER RippleCalc::rippleCalculate () { JLOG (j_.trace) << "rippleCalc>" << " saMaxAmountReq_:" << saMaxAmountReq_ << " saDstAmountReq_:" << saDstAmountReq_; TER resultCode = temUNCERTAIN; permanentlyUnfundedOffers_.clear (); mumSource_.clear (); // YYY Might do basic checks on src and dst validity as per doPayment. // Incrementally search paths. if (inputFlags.defaultPathsAllowed) { if (!addPathState (STPath(), resultCode)) return resultCode; } else if (spsPaths_.empty ()) { JLOG (j_.debug) << "rippleCalc: Invalid transaction:" << "No paths and direct ripple not allowed."; return temRIPPLE_EMPTY; } // Build a default path. Use saDstAmountReq_ and saMaxAmountReq_ to imply // nodes. // XXX Might also make a XRP bridge by default. JLOG (j_.trace) << "rippleCalc: Paths in set: " << spsPaths_.size (); // Now expand the path state. for (auto const& spPath: spsPaths_) { if (!addPathState (spPath, resultCode)) return resultCode; } if (resultCode != tesSUCCESS) return (resultCode == temUNCERTAIN) ? terNO_LINE : resultCode; resultCode = temUNCERTAIN; actualAmountIn_ = saMaxAmountReq_.zeroed(); actualAmountOut_ = saDstAmountReq_.zeroed(); // When processing, we don't want to complicate directory walking with // deletion. const std::uint64_t uQualityLimit = inputFlags.limitQuality ? getRate (saDstAmountReq_, saMaxAmountReq_) : 0; // Offers that became unfunded. std::set<uint256> unfundedOffersFromBestPaths; int iPass = 0; while (resultCode == temUNCERTAIN) { int iBest = -1; int iDry = 0; // True, if ever computed multi-quality. bool multiQuality = false; // Find the best path. for (auto pathState : pathStateList_) { if (pathState->quality()) // Only do active paths. { // If computing the only non-dry path, compute multi-quality. multiQuality = ((pathStateList_.size () - iDry) == 1); // Update to current amount processed. pathState->reset (actualAmountIn_, actualAmountOut_); // Error if done, output met. PathCursor pc(*this, *pathState, multiQuality, j_); pc.nextIncrement (); // Compute increment. JLOG (j_.debug) << "rippleCalc: AFTER:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()); if (!pathState->quality()) { // Path was dry. ++iDry; } else if (pathState->outPass() == zero) { // Path is not dry, but moved no funds // This should never happen. Consider the path dry JLOG (j_.warning) << "rippelCalc: Non-dry path moves no funds"; assert (false); pathState->setQuality (0); ++iDry; } else { CondLog (!pathState->inPass() || !pathState->outPass(), lsDEBUG, RippleCalc) << "rippleCalc: better:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: better:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: Summary:" << " Pass: "******" Dry: " << iDry << " Paths: " << pathStateList_.size (); for (auto pathState: pathStateList_) { JLOG (j_.debug) << "rippleCalc: " << "Summary: " << pathState->index() << " rate: " << amountFromRate (pathState->quality()) << " quality:" << pathState->quality() << " best: " << (iBest == pathState->index ()); } } if (iBest >= 0) { // Apply best path. auto pathState = pathStateList_[iBest]; JLOG (j_.debug) << "rippleCalc: best:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"rippleCalc: TOO MUCH:" << " actualAmountOut_:" << actualAmountOut_ << " saDstAmountReq_:" << saDstAmountReq_; return tefEXCEPTION; // TEMPORARY assert (false); } else if (actualAmountIn_ != saMaxAmountReq_ && iDry != pathStateList_.size ()) { // Have not met requested amount or max send, try to do // more. Prepare for next pass. // // Merge best pass' umReverse. mumSource_.insert ( pathState->reverse().begin (), pathState->reverse().end ()); if (iPass >= PAYMENT_MAX_LOOPS) { // This payment is taking too many passes JLOG (j_.error) << "rippleCalc: pass limit"; resultCode = telFAILED_PROCESSING; } } else if (!inputFlags.partialPaymentAllowed) { // Have sent maximum allowed. Partial payment not allowed. resultCode = tecPATH_PARTIAL; } else { // Have sent maximum allowed. Partial payment allowed. Success. resultCode = tesSUCCESS; } } // Not done and ran out of paths. else if (!inputFlags.partialPaymentAllowed) { // Partial payment not allowed. resultCode = tecPATH_PARTIAL; } // Partial payment ok. else if (!actualAmountOut_) { // No payment at all. resultCode = tecPATH_DRY; } else { // Don't apply any payment increments resultCode = tesSUCCESS; } } if (resultCode == tesSUCCESS) { auto viewJ = logs_.journal ("View"); resultCode = deleteOffers(view, unfundedOffersFromBestPaths, viewJ); if (resultCode == tesSUCCESS) resultCode = deleteOffers(view, permanentlyUnfundedOffers_, viewJ); } // If isOpenLedger, then ledger is not final, can vote no. if (resultCode == telFAILED_PROCESSING && !inputFlags.isLedgerOpen) return tecFAILED_PROCESSING; return resultCode; }
// The reverse pass has been narrowing by credit available and inflating by fees // as it worked backwards. Now, for the current account node, take the actual // amount from previous and adjust forward balances. // // Perform balance adjustments between previous and current node. // - The previous node: specifies what to push through to current. // - All of previous output is consumed. // // Then, compute current node's output for next node. // - Current node: specify what to push through to next. // - Output to next node is computed as input minus quality or transfer fee. // - If next node is an offer and output is non-XRP then we are the issuer and // do not need to push funds. // - If next node is an offer and output is XRP then we need to deliver funds to // limbo. TER PathCursor::forwardLiquidityForAccount () const { TER resultCode = tesSUCCESS; auto const lastNodeIndex = pathState_.nodes().size () - 1; auto viewJ = rippleCalc_.logs_.journal ("View"); std::uint64_t uRateMax = 0; AccountID const& previousAccountID = previousNode().isAccount() ? previousNode().account_ : node().account_; // Offers are always issue. AccountID const& nextAccountID = nextNode().isAccount() ? nextNode().account_ : node().account_; std::uint32_t uQualityIn = nodeIndex_ ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : QUALITY_ONE; std::uint32_t uQualityOut = (nodeIndex_ == lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : QUALITY_ONE; // When looking backward (prv) for req we care about what we just // calculated: use fwd. // When looking forward (cur) for req we care about what was desired: use // rev. // For nextNode().isAccount() auto saPrvRedeemAct = previousNode().saFwdRedeem.zeroed(); auto saPrvIssueAct = previousNode().saFwdIssue.zeroed(); // For !previousNode().isAccount() auto saPrvDeliverAct = previousNode().saFwdDeliver.zeroed (); JLOG (j_.trace) << "forwardLiquidityForAccount> " << "nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " saPrvIssueReq:" << previousNode().saFwdIssue << " previousNode.saFwdDeliver:" << previousNode().saFwdDeliver << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saRevDeliver:" << node().saRevDeliver; // Ripple through account. if (previousNode().isAccount() && nextNode().isAccount()) { // Next is an account, must be rippling. if (!nodeIndex_) { // ^ --> ACCOUNT --> account // For the first node, calculate amount to ripple based on what is // available. node().saFwdRedeem = node().saRevRedeem; if (pathState_.inReq() >= zero) { // Limit by send max. node().saFwdRedeem = std::min ( node().saFwdRedeem, pathState_.inReq() - pathState_.inAct()); } pathState_.setInPass (node().saFwdRedeem); node().saFwdIssue = node().saFwdRedeem == node().saRevRedeem // Fully redeemed. ? node().saRevIssue : STAmount (node().saRevIssue); if (node().saFwdIssue && pathState_.inReq() >= zero) { // Limit by send max. node().saFwdIssue = std::min ( node().saFwdIssue, pathState_.inReq() - pathState_.inAct() - node().saFwdRedeem); } pathState_.setInPass (pathState_.inPass() + node().saFwdIssue); JLOG (j_.trace) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT --> account :" << " saInReq=" << pathState_.inReq() << " saInAct=" << pathState_.inAct() << " node.saFwdRedeem:" << node().saFwdRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saFwdIssue:" << node().saFwdIssue << " pathState_.saInPass:"******"forwardLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " previousAccountID=" << to_string (previousAccountID) << " node.account_=" << to_string (node().account_) << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " previousNode.saFwdIssue:" << previousNode().saFwdIssue; // Last node. Accept all funds. Calculate amount actually to credit. auto& saCurReceive = pathState_.outPass(); STAmount saIssueCrd = uQualityIn >= QUALITY_ONE ? previousNode().saFwdIssue // No fee. : mulRound ( previousNode().saFwdIssue, amountFromRate (uQualityIn), previousNode().saFwdIssue.issue (), true); // Amount to credit. // Amount to credit. Credit for less than received as a surcharge. pathState_.setOutPass (previousNode().saFwdRedeem + saIssueCrd); if (saCurReceive) { // Actually receive. resultCode = rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ); } else { // After applying quality, total payment was microscopic. resultCode = tecPATH_DRY; } } else { // account --> ACCOUNT --> account JLOG (j_.trace) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // Previous redeem part 1: redeem -> redeem if (previousNode().saFwdRedeem && node().saRevRedeem) // Previous wants to redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, QUALITY_ONE, uQualityOut, previousNode().saFwdRedeem, node().saRevRedeem, saPrvRedeemAct, node().saFwdRedeem, uRateMax); } // Previous issue part 1: issue -> redeem if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem != node().saFwdRedeem) // Current has more to redeem to next. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, uQualityIn, uQualityOut, previousNode().saFwdIssue, node().saRevRedeem, saPrvIssueAct, node().saFwdRedeem, uRateMax); } // Previous redeem part 2: redeem -> issue. if (previousNode().saFwdRedeem != saPrvRedeemAct // Previous still wants to redeem. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, QUALITY_ONE, rippleTransferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevIssue, saPrvRedeemAct, node().saFwdIssue, uRateMax); } // Previous issue part 2 : issue -> issue if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, uQualityIn, QUALITY_ONE, previousNode().saFwdIssue, node().saRevIssue, saPrvIssueAct, node().saFwdIssue, uRateMax); } STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; // Adjust prv --> cur balance : take all inbound resultCode = saProvide ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; } } else if (previousNode().isAccount() && !nextNode().isAccount()) { // Current account is issuer to next offer. // Determine deliver to offer amount. // Don't adjust outbound balances- keep funds with issuer as limbo. // If issuer hold's an offer owners inbound IOUs, there is no fee and // redeem/issue will transparently happen. if (nodeIndex_) { // Non-XRP, current node is the issuer. JLOG (j_.trace) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); // redeem -> issue/deliver. // Previous wants to redeem. // Current is issuing to an offer so leave funds in account as // "limbo". if (previousNode().saFwdRedeem) // Previous wants to redeem. { // Rate : 1.0 : transfer_rate // XXX Is having the transfer rate here correct? rippleLiquidity ( rippleCalc_, QUALITY_ONE, rippleTransferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevDeliver, saPrvRedeemAct, node().saFwdDeliver, uRateMax); } // issue -> issue/deliver if (previousNode().saFwdRedeem == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. && previousNode().saFwdIssue) // Previous wants to issue. To next must be ok. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, uQualityIn, QUALITY_ONE, previousNode().saFwdIssue, node().saRevDeliver, saPrvIssueAct, node().saFwdDeliver, uRateMax); } // Adjust prv --> cur balance : take all inbound resultCode = node().saFwdDeliver ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; // Didn't actually deliver anything. } else { // Delivering amount requested from downstream. node().saFwdDeliver = node().saRevDeliver; // If limited, then limit by send max and available. if (pathState_.inReq() >= zero) { // Limit by send max. node().saFwdDeliver = std::min ( node().saFwdDeliver, pathState_.inReq() - pathState_.inAct()); // Limit XRP by available. No limit for non-XRP as issuer. if (isXRP (node().issue_)) node().saFwdDeliver = std::min ( node().saFwdDeliver, accountHolds(view(), node().account_, xrpCurrency(), xrpAccount(), fhIGNORE_FREEZE, viewJ)); // XRP can't be frozen } // Record amount sent for pass. pathState_.setInPass (node().saFwdDeliver); if (!node().saFwdDeliver) { resultCode = tecPATH_DRY; } else if (!isXRP (node().issue_)) { // Non-XRP, current node is the issuer. // We could be delivering to multiple accounts, so we don't know // which ripple balance will be adjusted. Assume just issuing. JLOG (j_.trace) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- !XRP --> offer"; // As the issuer, would only issue. // Don't need to actually deliver. As from delivering leave in // the issuer as limbo. } else { JLOG (j_.trace) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- XRP --> offer"; // Deliver XRP to limbo. resultCode = accountSend(view(), node().account_, xrpAccount(), node().saFwdDeliver, viewJ); } } } else if (!previousNode().isAccount() && nextNode().isAccount()) { if (nodeIndex_ == lastNodeIndex) { // offer --> ACCOUNT --> $ JLOG (j_.trace) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> $ : " << previousNode().saFwdDeliver; // Amount to credit. pathState_.setOutPass (previousNode().saFwdDeliver); // No income balance adjustments necessary. The paying side inside // the offer paid to this account. } else { // offer --> ACCOUNT --> account JLOG (j_.trace) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // deliver -> redeem if (previousNode().saFwdDeliver && node().saRevRedeem) // Previous wants to deliver and can current redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, QUALITY_ONE, uQualityOut, previousNode().saFwdDeliver, node().saRevRedeem, saPrvDeliverAct, node().saFwdRedeem, uRateMax); } // deliver -> issue // Wants to redeem and current would and can issue. if (previousNode().saFwdDeliver != saPrvDeliverAct // Previous still wants to deliver. && node().saRevRedeem == node().saFwdRedeem // Current has more to redeem to next. && node().saRevIssue) // Current wants issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, QUALITY_ONE, rippleTransferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevIssue, saPrvDeliverAct, node().saFwdIssue, uRateMax); } // No income balance adjustments necessary. The paying side inside // the offer paid and the next link will receive. STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; if (!saProvide) resultCode = tecPATH_DRY; } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace) << "forwardLiquidityForAccount: offer --> ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); if (previousNode().saFwdDeliver && node().saRevDeliver) { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, QUALITY_ONE, rippleTransferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevDeliver, saPrvDeliverAct, node().saFwdDeliver, uRateMax); } // No income balance adjustments necessary. The paying side inside the // offer paid and the next link will receive. if (!node().saFwdDeliver) resultCode = tecPATH_DRY; } return resultCode; }
// <-- TER: Only returns tepPATH_PARTIAL if partialPaymentAllowed. TER DivvyCalc::divvyCalculate () { assert (mActiveLedger.isValid ()); WriteLog (lsTRACE, DivvyCalc) << "divvyCalc>" << " saMaxAmountReq_:" << saMaxAmountReq_ << " saDstAmountReq_:" << saDstAmountReq_; TER resultCode = temUNCERTAIN; permanentlyUnfundedOffers_.clear (); mumSource_.clear (); // YYY Might do basic checks on src and dst validity as per doPayment. // Incrementally search paths. if (inputFlags.defaultPathsAllowed) { if (!addPathState (STPath(), resultCode)) return resultCode; } else if (spsPaths_.empty ()) { WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: Invalid transaction:" << "No paths and direct divvy not allowed."; return temRIPPLE_EMPTY; } // Build a default path. Use saDstAmountReq_ and saMaxAmountReq_ to imply // nodes. // XXX Might also make a XDV bridge by default. WriteLog (lsTRACE, DivvyCalc) << "divvyCalc: Paths in set: " << spsPaths_.size (); // Now expand the path state. for (auto const& spPath: spsPaths_) { if (!addPathState (spPath, resultCode)) return resultCode; } if (resultCode != tesSUCCESS) return (resultCode == temUNCERTAIN) ? terNO_LINE : resultCode; resultCode = temUNCERTAIN; actualAmountIn_ = saMaxAmountReq_.zeroed(); actualAmountOut_ = saDstAmountReq_.zeroed(); // When processing, we don't want to complicate directory walking with // deletion. const std::uint64_t uQualityLimit = inputFlags.limitQuality ? getRate (saDstAmountReq_, saMaxAmountReq_) : 0; // Offers that became unfunded. OfferSet unfundedOffersFromBestPaths; int iPass = 0; while (resultCode == temUNCERTAIN) { int iBest = -1; LedgerEntrySet lesCheckpoint = mActiveLedger; int iDry = 0; // True, if ever computed multi-quality. bool multiQuality = false; // Find the best path. for (auto pathState : pathStateList_) { if (pathState->quality()) // Only do active paths. { // If computing the only non-dry path, compute multi-quality. multiQuality = ((pathStateList_.size () - iDry) == 1); // Update to current amount processed. pathState->reset (actualAmountIn_, actualAmountOut_); // Error if done, output met. PathCursor pc(*this, *pathState, multiQuality); pc.nextIncrement (lesCheckpoint); // Compute increment. WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: AFTER:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()); if (!pathState->quality()) { // Path was dry. ++iDry; } else if (pathState->outPass() == zero) { // Path is not dry, but moved no funds // This should never happen. Consider the path dry WriteLog (lsWARNING, DivvyCalc) << "rippelCalc: Non-dry path moves no funds"; assert (false); pathState->setQuality (0); ++iDry; } else { CondLog (!pathState->inPass() || !pathState->outPass(), lsDEBUG, DivvyCalc) << "divvyCalc: better:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: better:" << " mIndex=" << pathState->index() << " uQuality=" << pathState->quality() << " rate=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: Summary:" << " Pass: "******" Dry: " << iDry << " Paths: " << pathStateList_.size (); for (auto pathState: pathStateList_) { WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: " << "Summary: " << pathState->index() << " rate: " << amountFromRate (pathState->quality()) << " quality:" << pathState->quality() << " best: " << (iBest == pathState->index ()); } } if (iBest >= 0) { // Apply best path. auto pathState = pathStateList_[iBest]; WriteLog (lsDEBUG, DivvyCalc) << "divvyCalc: best:" << " uQuality=" << amountFromRate (pathState->quality()) << " inPass()=" << pathState->inPass() << " saOutPass="******"divvyCalc: TOO MUCH:" << " actualAmountOut_:" << actualAmountOut_ << " saDstAmountReq_:" << saDstAmountReq_; return tefEXCEPTION; // TEMPORARY assert (false); } else if (actualAmountIn_ != saMaxAmountReq_ && iDry != pathStateList_.size ()) { // Have not met requested amount or max send, try to do // more. Prepare for next pass. // // Merge best pass' umReverse. mumSource_.insert ( pathState->reverse().begin (), pathState->reverse().end ()); if (iPass >= PAYMENT_MAX_LOOPS) { // This payment is taking too many passes WriteLog (lsERROR, DivvyCalc) << "divvyCalc: pass limit"; resultCode = telFAILED_PROCESSING; } } else if (!inputFlags.partialPaymentAllowed) { // Have sent maximum allowed. Partial payment not allowed. resultCode = tecPATH_PARTIAL; } else { // Have sent maximum allowed. Partial payment allowed. Success. resultCode = tesSUCCESS; } } // Not done and ran out of paths. else if (!inputFlags.partialPaymentAllowed) { // Partial payment not allowed. resultCode = tecPATH_PARTIAL; } // Partial payment ok. else if (!actualAmountOut_) { // No payment at all. resultCode = tecPATH_DRY; } else { // We must restore the activeLedger from lesCheckpoint in the case // when iBest is -1 and just before the result is set to tesSUCCESS. mActiveLedger.swapWith (lesCheckpoint); resultCode = tesSUCCESS; } } if (resultCode == tesSUCCESS) { resultCode = deleteOffers(mActiveLedger, unfundedOffersFromBestPaths); if (resultCode == tesSUCCESS) resultCode = deleteOffers(mActiveLedger, permanentlyUnfundedOffers_); } // If isOpenLedger, then ledger is not final, can vote no. if (resultCode == telFAILED_PROCESSING && !inputFlags.isLedgerOpen) return tecFAILED_PROCESSING; return resultCode; }