Example #1
0
TER computeForwardLiqudity (
    RippleCalc& rippleCalc,
    const unsigned int nodeIndex, PathState& pathState,
    const bool bMultiQuality)
{
    auto const& node = pathState.nodes()[nodeIndex];
    WriteLog (lsTRACE, RippleCalc)
        << "computeForwardLiqudity> nodeIndex=" << nodeIndex;

    TER resultCode = node.isAccount()
        ? computeForwardLiquidityForAccount (
            rippleCalc, nodeIndex, pathState, bMultiQuality)
        : computeForwardLiquidityForOffer (
              rippleCalc, nodeIndex, pathState, bMultiQuality);

    if (resultCode == tesSUCCESS && nodeIndex + 1 != pathState.nodes().size ())
        resultCode = computeForwardLiqudity (rippleCalc, nodeIndex + 1, pathState, bMultiQuality);

    if (resultCode == tesSUCCESS && !(pathState.inPass() && pathState.outPass()))
        resultCode = tecPATH_DRY;

    WriteLog (lsTRACE, RippleCalc)
        << "computeForwardLiqudity<"
        << " nodeIndex:" << nodeIndex
        << " resultCode:" << resultCode;

    return resultCode;
}
Example #2
0
/*!
  \details
  No detailed.
  */
SampledWavelengths WavelengthSampler::sampleRandomly(
    Sampler& sampler,
    PathState& path_state) noexcept
{
  using zisc::cast;
  constexpr uint sample_size = SampledWavelengths::size();
  constexpr Float inverse_probability = cast<Float>(CoreConfig::spectraSize()) /
                                        cast<Float>(sample_size);

  std::array<uint16, sample_size> wavelengths;
  for (uint i = 0; i < sample_size; ++i) {
    const Float offset = sampler.draw1D(path_state);
    path_state.setDimension(path_state.dimension() + 1);
    const Float position = cast<Float>(CoreConfig::spectraSize()) * offset;
    const uint index = cast<uint>(position);
    const uint16 wavelength = getWavelength(index);
    wavelengths[i] = wavelength;
  }
  std::sort(wavelengths.begin(), wavelengths.end());

  SampledWavelengths sampled_wavelengths;
  for (uint i = 0; i < sample_size; ++i)
    sampled_wavelengths.set(i, wavelengths[i], inverse_probability);
  sampled_wavelengths.selectPrimaryWavelength(sampler, path_state);
  return sampled_wavelengths;
}
Example #3
0
/*!
  */
SampledWavelengths WavelengthSampler::sampleRgb(
    Sampler& sampler,
    PathState& path_state) noexcept
{
  constexpr uint sample_size = SampledWavelengths::size();
  path_state.setDimension(path_state.dimension() + sample_size);

  SampledWavelengths sampled_wavelengths;
  sampled_wavelengths.set(0, CoreConfig::blueWavelength(), 1.0);
  sampled_wavelengths.set(1, CoreConfig::greenWavelength(), 1.0);
  sampled_wavelengths.set(2, CoreConfig::redWavelength(), 1.0);
  sampled_wavelengths.selectPrimaryWavelength(sampler, path_state);
  return sampled_wavelengths;

}
Example #4
0
/*!
  \details
  No detailed.
  */
SampledWavelengths WavelengthSampler::sampleRegularly(
    Sampler& sampler,
    PathState& path_state) noexcept
{
  using zisc::cast;
  constexpr uint sample_size = SampledWavelengths::size();
  constexpr Float interval = cast<Float>(CoreConfig::spectraSize()) /
                             cast<Float>(sample_size);
  constexpr Float inverse_probability = interval;

  SampledWavelengths sampled_wavelengths;
  const Float offset = sampler.draw1D(path_state);
  for (uint i = 0; i < sample_size; ++i) {
    path_state.setDimension(path_state.dimension() + 1);
    const uint index = cast<uint>(interval * (cast<Float>(i) + offset));
    const uint16 wavelength = getWavelength(index);
    sampled_wavelengths.set(i, wavelength, inverse_probability);
  }
  sampled_wavelengths.selectPrimaryWavelength(sampler, path_state);
  return sampled_wavelengths;
}
Example #5
0
TER computeReverseLiqudity (
    RippleCalc& rippleCalc,
    const unsigned int nodeIndex, PathState& pathState,
    const bool bMultiQuality)
{
    auto& node = pathState.nodes()[nodeIndex];

    // 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_ = STAmount::saFromRate (
        rippleCalc.mActiveLedger.rippleTransferRate (node.issuer_));

    WriteLog (lsTRACE, RippleCalc)
        << "computeReverseLiqudity>"
        << " nodeIndex=" << nodeIndex
        << " issuer_=" << RippleAddress::createHumanAccountID (node.issuer_)
        << " transferRate_=" << node.transferRate_;

    auto resultCode = node.isAccount()
        ? computeReverseLiquidityForAccount (
            rippleCalc, nodeIndex, pathState, bMultiQuality)
        : computeReverseLiquidityForOffer (
            rippleCalc, nodeIndex, pathState, bMultiQuality);

    // Do previous.
    if (resultCode == tesSUCCESS && nodeIndex)
    {
        // Continue in reverse.  TODO(tom): remove unnecessary recursion.
        resultCode = computeReverseLiqudity (
            rippleCalc, nodeIndex - 1, pathState, bMultiQuality);
    }

    WriteLog (lsTRACE, RippleCalc)
        << "computeReverseLiqudity< "
        << "nodeIndex=" << nodeIndex
        << " resultCode=%s" << transToken (resultCode)
        << "/" << resultCode;

    return resultCode;
}
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();

    Account const& previousAccountID = previousNodeIsAccount
        ? previousNode.account_ : node.account_;
    Account const& 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.issue ());

    // 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.
    auto saCurRedeemAct = saCurRedeemReq.zeroed();

    const STAmount& saCurIssueReq = node.saRevIssue;

    // Track the amount we actually redeem.
    auto saCurIssueAct = saCurIssueReq.zeroed();

    // For !nextNodeIsAccount
    const STAmount& saCurDeliverReq = node.saRevDeliver;
    auto saCurDeliverAct  = saCurDeliverReq.zeroed();

    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);
            auto saCurWantedAct = saCurWantedReq.zeroed ();

            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;
}
Example #7
0
void pathNext (
    RippleCalc& rippleCalc,
    PathState& pathState, const bool bMultiQuality,
    const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
{
    // The next state is what is available in preference order.
    // This is calculated when referenced accounts changed.
    pathState.clear();

    WriteLog (lsTRACE, RippleCalc)
        << "pathNext: Path In: " << pathState.getJson ();

    assert (pathState.nodes().size () >= 2);

    lesCurrent  = lesCheckpoint.duplicate ();  // Restore from checkpoint.

    for (unsigned int uIndex = pathState.nodes().size (); uIndex--;)
    {
        auto& node   = pathState.nodes()[uIndex];

        node.saRevRedeem.clear ();
        node.saRevIssue.clear ();
        node.saRevDeliver.clear ();
        node.saFwdDeliver.clear ();
    }

    pathState.setStatus(computeReverseLiquidity (
        rippleCalc, pathState, bMultiQuality));

    WriteLog (lsTRACE, RippleCalc)
        << "pathNext: Path after reverse: " << pathState.getJson ();

    if (tesSUCCESS == pathState.status())
    {
        // Do forward.
        lesCurrent = lesCheckpoint.duplicate ();   // Restore from checkpoint.

        pathState.setStatus(computeForwardLiquidity (
            rippleCalc, pathState, bMultiQuality));
    }

    if (tesSUCCESS == pathState.status())
    {
        CondLog (!pathState.inPass() || !pathState.outPass(), lsDEBUG, RippleCalc)
            << "pathNext: Error computeForwardLiquidity reported success for nothing:"
            << " saOutPass="******" inPass()=" << pathState.inPass();

        if (!pathState.outPass() || !pathState.inPass())
            throw std::runtime_error ("Made no progress.");

        // Calculate relative quality.
        pathState.setQuality(STAmount::getRate (
            pathState.outPass(), pathState.inPass()));

        WriteLog (lsTRACE, RippleCalc)
            << "pathNext: Path after forward: " << pathState.getJson ();
    }
    else
    {
        pathState.setQuality(0);
    }
}
Example #8
0
// OPTIMIZE: When calculating path increment, note if increment consumes all
// liquidity. No need to revisit path in the future if all liquidity is used.
//
// nodeAdvance advances through offers in an order book.
// If needed, advance to next funded offer.
// - Automatically advances to first offer.
// --> bEntryAdvance: true, to advance to next entry. false, recalculate.
// <-- uOfferIndex : 0=end of list.
TER nodeAdvance (
    RippleCalc& rippleCalc,
    const unsigned int          nodeIndex,
    PathState&                  pathState,
    const bool                  bMultiQuality,
    const bool                  bReverse)
{
    auto& previousNode = pathState.nodes()[nodeIndex - 1];
    auto& node = pathState.nodes()[nodeIndex];
    TER             resultCode       = tesSUCCESS;

    // Taker is the active party against an offer in the ledger - the entity
    // that is taking advantage of an offer in the order book.
    WriteLog (lsTRACE, RippleCalc)
            << "nodeAdvance: TakerPays:"
            << node.saTakerPays << " TakerGets:" << node.saTakerGets;

    int loopCount = 0;

    do
    {
        // VFALCO NOTE Why not use a for() loop?
        // VFALCO TODO The limit on loop iterations puts an
        //             upper limit on the number of different quality
        // levels (ratio of pay:get) that will be considered for one path.
        // Changing this value has repercusssions on validation and consensus.
        //
        if (++loopCount > NODE_ADVANCE_MAX_LOOPS)
        {
            WriteLog (lsWARNING, RippleCalc) << "Loop count exceeded";
            return tefEXCEPTION;
        }

        bool bDirectDirDirty = false;

        if (!node.currentDirectory_)
        {
            // Need to initialize current node.

            node.currentDirectory_.copyFrom(Ledger::getBookBase ({
                {previousNode.currency_, previousNode.issuer_},
                {node.currency_, node.issuer_}
            }));
            node.nextDirectory_.copyFrom(
                Ledger::getQualityNext (node.currentDirectory_));

            // TODO(tom): it seems impossible that any actual offers with
            // quality == 0 could occur - we should disallow them, and clear
            // sleDirectDir without the database call in the next line.
            node.sleDirectDir = rippleCalc.mActiveLedger.entryCache (
                                    ltDIR_NODE, node.currentDirectory_);

            // Associated vars are dirty, if found it.
            bDirectDirDirty = !!node.sleDirectDir;

            // Advance, if didn't find it. Normal not to be unable to lookup
            // firstdirectory. Maybe even skip this lookup.
            node.bDirectAdvance  = !node.sleDirectDir;
            node.bDirectRestart  = false;

            WriteLog (lsTRACE, RippleCalc)
                    << "nodeAdvance: Initialize node:"
                    << " node.currentDirectory_=" << node.currentDirectory_
                    <<" node.nextDirectory_=" << node.nextDirectory_
                    << " node.bDirectAdvance=" << node.bDirectAdvance;
        }

        if (node.bDirectAdvance || node.bDirectRestart)
        {
            // Get next quality.
            if (node.bDirectAdvance)
            {
                // This works because the Merkel radix tree is ordered by key so
                // we can go to the next one in O(1).
                node.currentDirectory_  = rippleCalc.mActiveLedger.getNextLedgerIndex (
                                              node.currentDirectory_, node.nextDirectory_);
            }

            bDirectDirDirty = true;
            node.bDirectAdvance  = false;
            node.bDirectRestart  = false;

            if (node.currentDirectory_ != zero)
            {
                // We didn't run off the end of this order book and found
                // another quality directory.
                WriteLog (lsTRACE, RippleCalc)
                        << "nodeAdvance: Quality advance: node.currentDirectory_="
                        << node.currentDirectory_;

                node.sleDirectDir = rippleCalc.mActiveLedger.entryCache (ltDIR_NODE, node.currentDirectory_);
            }
            else if (bReverse)
            {
                WriteLog (lsTRACE, RippleCalc)
                        << "nodeAdvance: No more offers.";

                node.offerIndex_ = 0;
                break;
            }
            else
            {
                // No more offers. Should be done rather than fall off end of
                // book.
                WriteLog (lsWARNING, RippleCalc)
                        << "nodeAdvance: Unreachable: "
                        << "Fell off end of order book.";
                // FIXME: why?
                return rippleCalc.mOpenLedger ? telFAILED_PROCESSING :
                       tecFAILED_PROCESSING;
            }
        }

        if (bDirectDirDirty)
        {
            // Our quality changed since last iteration.
            // Use the rate from the directory.
            node.saOfrRate = STAmount::setRate (Ledger::getQuality (node.currentDirectory_));
            // For correct ratio
            node.uEntry          = 0;
            node.bEntryAdvance   = true;

            WriteLog (lsTRACE, RippleCalc)
                    << "nodeAdvance: directory dirty: node.saOfrRate="
                    << node.saOfrRate;
        }

        if (!node.bEntryAdvance)
        {
            if (node.bFundsDirty)
            {
                // We were called again probably merely to update structure
                // variables.
                node.saTakerPays = node.sleOffer->getFieldAmount (sfTakerPays);
                node.saTakerGets = node.sleOffer->getFieldAmount (sfTakerGets);

                // Funds left.
                node.saOfferFunds = rippleCalc.mActiveLedger.accountFunds (
                                        node.offerOwnerAccount_, node.saTakerGets);
                node.bFundsDirty     = false;

                WriteLog (lsTRACE, RippleCalc)
                        << "nodeAdvance: funds dirty: node.saOfrRate="
                        << node.saOfrRate;
            }
            else
            {
                WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: as is";
            }
        }
        else if (!rippleCalc.mActiveLedger.dirNext (
                     node.currentDirectory_, node.sleDirectDir, node.uEntry, node.offerIndex_))
            // This is the only place that offerIndex_ changes.
        {
            // Failed to find an entry in directory.
            // Do another cur directory iff bMultiQuality
            if (bMultiQuality)
            {
                // We are allowed to process multiple qualities if this is the
                // only path.
                WriteLog (lsTRACE, RippleCalc)
                        << "nodeAdvance: next quality";
                node.bDirectAdvance  = true;  // Process next quality.
            }
            else if (!bReverse)
            {
                // We didn't run dry going backwards - why are we running dry
                // going forwards - this should be impossible!
                // TODO(tom): these warnings occur in production!  They
                // shouldn't.
                WriteLog (lsWARNING, RippleCalc)
                        << "nodeAdvance: unreachable: ran out of offers";
                return rippleCalc.mOpenLedger ? telFAILED_PROCESSING :
                       tecFAILED_PROCESSING;
            }
            else
            {
                // Ran off end of offers.
                node.bEntryAdvance   = false;        // Done.
                node.offerIndex_ = 0;            // Report no more entries.
            }
        }
        else
        {
            // Got a new offer.
            node.sleOffer = rippleCalc.mActiveLedger.entryCache (
                                ltOFFER, node.offerIndex_);

            if (!node.sleOffer)
            {
                // Corrupt directory that points to an entry that doesn't exist.
                // This has happened in production.
                WriteLog (lsWARNING, RippleCalc) <<
                                                 "Missing offer in directory";
                node.bEntryAdvance = true;
            }
            else
            {
                node.offerOwnerAccount_
                    = node.sleOffer->getFieldAccount160 (sfAccount);
                node.saTakerPays = node.sleOffer->getFieldAmount (sfTakerPays);
                node.saTakerGets = node.sleOffer->getFieldAmount (sfTakerGets);

                const AccountCurrencyIssuer asLine (
                    node.offerOwnerAccount_, node.currency_, node.issuer_);

                WriteLog (lsTRACE, RippleCalc)
                        << "nodeAdvance: offerOwnerAccount_="
                        << to_string (node.offerOwnerAccount_)
                        << " node.saTakerPays=" << node.saTakerPays
                        << " node.saTakerGets=" << node.saTakerGets
                        << " node.offerIndex_=" << node.offerIndex_;

                if (node.sleOffer->isFieldPresent (sfExpiration) &&
                        (node.sleOffer->getFieldU32 (sfExpiration) <=
                         rippleCalc.mActiveLedger.getLedger ()->getParentCloseTimeNC ()))
                {
                    // Offer is expired.
                    WriteLog (lsTRACE, RippleCalc)
                            << "nodeAdvance: expired offer";
                    rippleCalc.mUnfundedOffers.insert(node.offerIndex_);
                    continue;
                }

                if (node.saTakerPays <= zero || node.saTakerGets <= zero)
                {
                    // Offer has bad amounts. Offers should never have a bad
                    // amounts.

                    if (bReverse)
                    {
                        // Past internal error, offer had bad amounts.
                        // This has occurred in production.
                        WriteLog (lsWARNING, RippleCalc)
                                << "nodeAdvance: PAST INTERNAL ERROR"
                                << " REVERSE: OFFER NON-POSITIVE:"
                                << " node.saTakerPays=" << node.saTakerPays
                                << " node.saTakerGets=%s" << node.saTakerGets;

                        // Mark offer for always deletion.
                        rippleCalc.mUnfundedOffers.insert (node.offerIndex_);
                    }
                    else if (rippleCalc.mUnfundedOffers.find (node.offerIndex_)
                             != rippleCalc.mUnfundedOffers.end ())
                    {
                        // Past internal error, offer was found failed to place
                        // this in mUnfundedOffers.
                        // Just skip it. It will be deleted.
                        WriteLog (lsDEBUG, RippleCalc)
                                << "nodeAdvance: PAST INTERNAL ERROR "
                                << " FORWARD CONFIRM: OFFER NON-POSITIVE:"
                                << " node.saTakerPays=" << node.saTakerPays
                                << " node.saTakerGets=" << node.saTakerGets;

                    }
                    else
                    {
                        // Reverse should have previously put bad offer in list.
                        // An internal error previously left a bad offer.
                        WriteLog (lsWARNING, RippleCalc)
                                << "nodeAdvance: INTERNAL ERROR"

                                <<" FORWARD NEWLY FOUND: OFFER NON-POSITIVE:"
                                << " node.saTakerPays=" << node.saTakerPays
                                << " node.saTakerGets=" << node.saTakerGets;

                        // Don't process at all, things are in an unexpected
                        // state for this transactions.
                        resultCode = tefEXCEPTION;
                    }

                    continue;
                }

                // Allowed to access source from this node?
                //
                // XXX This can get called multiple times for same source in a
                // row, caching result would be nice.
                //
                // XXX Going forward could we fund something with a worse
                // quality which was previously skipped? Might need to check
                // quality.
                auto itForward = pathState.forward().find (asLine);
                const bool bFoundForward = itForward != pathState.forward().end ();

                // Only allow a source to be used once, in the first node
                // encountered from initial path scan.  This prevents
                // conflicting uses of the same balance when going reverse vs
                // forward.
                if (bFoundForward &&
                        itForward->second != nodeIndex &&
                        node.offerOwnerAccount_ != node.issuer_)
                {
                    // Temporarily unfunded. Another node uses this source,
                    // ignore in this offer.
                    WriteLog (lsTRACE, RippleCalc)
                            << "nodeAdvance: temporarily unfunded offer"
                            << " (forward)";
                    continue;
                }

                // This is overly strict. For contributions to past. We should
                // only count source if actually used.
                auto itReverse = pathState.reverse().find (asLine);
                bool bFoundReverse = itReverse != pathState.reverse().end ();

                // For this quality increment, only allow a source to be used
                // from a single node, in the first node encountered from
                // applying offers in reverse.
                if (bFoundReverse &&
                        itReverse->second != nodeIndex &&
                        node.offerOwnerAccount_ != node.issuer_)
                {
                    // Temporarily unfunded. Another node uses this source,
                    // ignore in this offer.
                    WriteLog (lsTRACE, RippleCalc)
                            << "nodeAdvance: temporarily unfunded offer"
                            <<" (reverse)";
                    continue;
                }

                // Determine if used in past.
                // We only need to know if it might need to be marked unfunded.
                auto itPast = rippleCalc.mumSource.find (asLine);
                bool bFoundPast = (itPast != rippleCalc.mumSource.end ());

                // Only the current node is allowed to use the source.

                node.saOfferFunds = rippleCalc.mActiveLedger.accountFunds
                                    (node.offerOwnerAccount_, node.saTakerGets); // Funds held.

                if (node.saOfferFunds <= zero)
                {
                    // Offer is unfunded.
                    WriteLog (lsTRACE, RippleCalc)
                            << "nodeAdvance: unfunded offer";

                    if (bReverse && !bFoundReverse && !bFoundPast)
                    {
                        // Never mentioned before, clearly just: found unfunded.
                        // That is, even if this offer fails due to fill or kill
                        // still do deletions.
                        // Mark offer for always deletion.
                        rippleCalc.mUnfundedOffers.insert (node.offerIndex_);
                    }
                    else
                    {
                        // Moving forward, don't need to insert again
                        // Or, already found it.
                    }

                    // YYY Could verify offer is correct place for unfundeds.
                    continue;
                }

                if (bReverse            // Need to remember reverse mention.
                        && !bFoundPast      // Not mentioned in previous passes.
                        && !bFoundReverse)  // New to pass.
                {
                    // Consider source mentioned by current path state.
                    WriteLog (lsTRACE, RippleCalc)
                            << "nodeAdvance: remember="
                            <<  to_string (node.offerOwnerAccount_)
                            << "/"
                            << to_string (node.currency_)
                            << "/"
                            << to_string (node.issuer_);

                    pathState.reverse().insert (std::make_pair (asLine, nodeIndex));
                }

                node.bFundsDirty     = false;
                node.bEntryAdvance   = false;
            }
        }
    }
    while (resultCode == tesSUCCESS && (node.bEntryAdvance || node.bDirectAdvance));

    if (resultCode == tesSUCCESS)
    {
        WriteLog (lsTRACE, RippleCalc)
                << "nodeAdvance: node.offerIndex_=" << node.offerIndex_;
    }
    else
    {
        WriteLog (lsDEBUG, RippleCalc)
                << "nodeAdvance: resultCode=" << transToken (resultCode);
    }

    return resultCode;
}