Example #1
0
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);
}
Example #2
0
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);
}
Example #3
0
// <-- 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;
}
Example #5
0
// <-- 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;
}