예제 #1
0
bool
PathRequest::findPaths (std::shared_ptr<RippleLineCache> const& cache,
    int const level, Json::Value& jvArray)
{
    auto sourceCurrencies = sciSourceCurrencies;
    if (sourceCurrencies.empty ())
    {
        auto currencies = accountSourceCurrencies(*raSrcAccount, cache, true);
        bool const sameAccount = *raSrcAccount == *raDstAccount;
        for (auto const& c : currencies)
        {
            if (! sameAccount || c != saDstAmount.getCurrency())
            {
                if (sourceCurrencies.size() >= RPC::Tuning::max_auto_src_cur)
                    return false;
                sourceCurrencies.insert(
                    {c, c.isZero() ? xrpAccount() : *raSrcAccount});
            }
        }
    }

    auto const dst_amount = convert_all_ ?
        STAmount(saDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset)
            : saDstAmount;
    hash_map<Currency, std::unique_ptr<Pathfinder>> currency_map;
    for (auto const& issue : sourceCurrencies)
    {
        JLOG(m_journal.debug())
            << iIdentifier
            << " Trying to find paths: "
            << STAmount(issue, 1).getFullText();

        auto& pathfinder = getPathFinder(cache, currency_map,
            issue.currency, dst_amount, level);
        if (! pathfinder)
        {
            assert(false);
            JLOG(m_journal.debug()) << iIdentifier << " No paths found";
            continue;
        }

        STPath fullLiquidityPath;
        auto ps = pathfinder->getBestPaths(max_paths_,
            fullLiquidityPath, mContext[issue], issue.account);
        mContext[issue] = ps;

        auto& sourceAccount = ! isXRP(issue.account)
            ? issue.account
            : isXRP(issue.currency)
            ? xrpAccount()
            : *raSrcAccount;
        STAmount saMaxAmount = saSendMax.value_or(
            STAmount({issue.currency, sourceAccount}, 1u, 0, true));

        JLOG(m_journal.debug()) << iIdentifier
            << " Paths found, calling rippleCalc";

        path::RippleCalc::Input rcInput;
        if (convert_all_)
            rcInput.partialPaymentAllowed = true;
        auto sandbox = std::make_unique<PaymentSandbox>
            (&*cache->getLedger(), tapNONE);
        auto rc = path::RippleCalc::rippleCalculate(
            *sandbox,
            saMaxAmount,    // --> Amount to send is unlimited
                            //     to get an estimate.
            dst_amount,     // --> Amount to deliver.
            *raDstAccount,  // --> Account to deliver to.
            *raSrcAccount,  // --> Account sending from.
            ps,             // --> Path set.
            app_.logs(),
            app_.config(),
            &rcInput);

        if (! convert_all_ &&
            ! fullLiquidityPath.empty() &&
            (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL))
        {
            JLOG(m_journal.debug()) << iIdentifier
                << " Trying with an extra path element";

            ps.push_back(fullLiquidityPath);
            sandbox = std::make_unique<PaymentSandbox>
                (&*cache->getLedger(), tapNONE);
            rc = path::RippleCalc::rippleCalculate(
                *sandbox,
                saMaxAmount,    // --> Amount to send is unlimited
                                //     to get an estimate.
                dst_amount,     // --> Amount to deliver.
                *raDstAccount,  // --> Account to deliver to.
                *raSrcAccount,  // --> Account sending from.
                ps,             // --> Path set.
                app_.logs(),
                app_.config());

            if (rc.result() != tesSUCCESS)
            {
                JLOG(m_journal.warn()) << iIdentifier
                    << " Failed with covering path "
                    << transHuman(rc.result());
            }
            else
            {
                JLOG(m_journal.debug()) << iIdentifier
                    << " Extra path element gives "
                    << transHuman(rc.result());
            }
        }

        if (rc.result () == tesSUCCESS)
        {
            Json::Value jvEntry (Json::objectValue);
            rc.actualAmountIn.setIssuer (sourceAccount);
            jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0);
            jvEntry[jss::paths_computed] = ps.getJson(0);

            if (convert_all_)
                jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(0);

            if (hasCompletion ())
            {
                // Old ripple_path_find API requires this
                jvEntry[jss::paths_canonical] = Json::arrayValue;
            }

            jvArray.append (jvEntry);
        }
        else
        {
            JLOG(m_journal.debug()) << iIdentifier << " rippleCalc returns "
                << transHuman(rc.result());
        }
    }

    /*  The resource fee is based on the number of source currencies used.
        The minimum cost is 50 and the maximum is 400. The cost increases
        after four source currencies, 50 - (4 * 4) = 34.
    */
    int const size = sourceCurrencies.size();
    consumer_.charge({boost::algorithm::clamp(size * size + 34, 50, 400),
        "path update"});
    return true;
}
예제 #2
0
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
{
    m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal");

    ScopedLockType sl (mLock);

    if (!isValid (cache))
        return jvStatus;
    jvStatus = Json::objectValue;

    auto sourceCurrencies = sciSourceCurrencies;

    if (sourceCurrencies.empty ())
    {
        auto usCurrencies =
                accountSourceCurrencies (*raSrcAccount, cache, true);
        bool sameAccount = *raSrcAccount == *raDstAccount;
        for (auto const& c: usCurrencies)
        {
            if (!sameAccount || (c != saDstAmount.getCurrency ()))
            {
                if (c.isZero ())
                    sourceCurrencies.insert ({c, xrpAccount()});
                else
                    sourceCurrencies.insert ({c, *raSrcAccount});
            }
        }
    }

    if (hasCompletion ())
    {
        // Old ripple_path_find API gives destination_currencies
        auto& destCurrencies = (jvStatus[jss::destination_currencies] = Json::arrayValue);
        auto usCurrencies = accountDestCurrencies (*raDstAccount, cache, true);
        for (auto const& c : usCurrencies)
            destCurrencies.append (to_string (c));
    }

    jvStatus[jss::source_account] = getApp().accountIDCache().toBase58(*raSrcAccount);
    jvStatus[jss::destination_account] = getApp().accountIDCache().toBase58(*raDstAccount);
    jvStatus[jss::destination_amount] = saDstAmount.getJson (0);
    jvStatus[jss::full_reply] = ! fast;

    if (jvId)
        jvStatus["id"] = jvId;

    Json::Value jvArray = Json::arrayValue;

    int iLevel = iLastLevel;
    bool loaded = getApp().getFeeTrack().isLoadedLocal();

    if (iLevel == 0)
    {
        // first pass
        if (loaded || fast)
            iLevel = getConfig().PATH_SEARCH_FAST;
        else
            iLevel = getConfig().PATH_SEARCH;
    }
    else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast)
    {
        // leaving fast pathfinding
        iLevel = getConfig().PATH_SEARCH;
        if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
            --iLevel;
    }
    else if (bLastSuccess)
    {
        // decrement, if possible
        if (iLevel > getConfig().PATH_SEARCH ||
            (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)))
            --iLevel;
    }
    else
    {
        // adjust as needed
        if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX))
            ++iLevel;
        if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
            --iLevel;
    }

    m_journal.debug << iIdentifier << " processing at level " << iLevel;

    bool found = false;

    FindPaths fp (
        cache,
        *raSrcAccount,
        *raDstAccount,
        saDstAmount,
        iLevel,
        4);  // iMaxPaths
    for (auto const& currIssuer: sourceCurrencies)
    {
        {
            STAmount test (currIssuer, 1);
            if (m_journal.debug)
            {
                m_journal.debug
                        << iIdentifier
                        << " Trying to find paths: " << test.getFullText ();
            }
        }
        STPathSet& spsPaths = mContext[currIssuer];
        STPath fullLiquidityPath;
        auto valid = fp.findPathsForIssue (
            currIssuer,
            spsPaths,
            fullLiquidityPath);
        CondLog (!valid, lsDEBUG, PathRequest)
                << iIdentifier << " PF request not valid";

        if (valid)
        {
            boost::optional<PaymentSandbox> sandbox;
            sandbox.emplace(&*cache->getLedger(), tapNONE);

            auto& sourceAccount = !isXRP (currIssuer.account)
                    ? currIssuer.account
                    : isXRP (currIssuer.currency)
                        ? xrpAccount()
                        : *raSrcAccount;
            STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1);

            saMaxAmount.negate ();
            m_journal.debug << iIdentifier
                            << " Paths found, calling rippleCalc";
            auto rc = path::RippleCalc::rippleCalculate (
                *sandbox,
                saMaxAmount,
                saDstAmount,
                *raDstAccount,
                *raSrcAccount,
                spsPaths);

            if (!fullLiquidityPath.empty() &&
                (rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL))
            {
                m_journal.debug
                        << iIdentifier << " Trying with an extra path element";
                spsPaths.push_back (fullLiquidityPath);
                sandbox.emplace(&*cache->getLedger(), tapNONE);
                rc = path::RippleCalc::rippleCalculate (
                    *sandbox,
                    saMaxAmount,
                    saDstAmount,
                    *raDstAccount,
                    *raSrcAccount,
                    spsPaths);
                if (rc.result () != tesSUCCESS)
                    m_journal.warning
                        << iIdentifier << " Failed with covering path "
                        << transHuman (rc.result ());
                else
                    m_journal.debug
                        << iIdentifier << " Extra path element gives "
                        << transHuman (rc.result ());
            }

            if (rc.result () == tesSUCCESS)
            {
                Json::Value jvEntry (Json::objectValue);
                rc.actualAmountIn.setIssuer (sourceAccount);

                jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0);
                jvEntry[jss::paths_computed] = spsPaths.getJson (0);

                if (hasCompletion ())
                {
                    // Old ripple_path_find API requires this
                    jvEntry[jss::paths_canonical] = Json::arrayValue;
                }

                found  = true;
                jvArray.append (jvEntry);
            }
            else
            {
                m_journal.debug << iIdentifier << " rippleCalc returns "
                    << transHuman (rc.result ());
            }
        }
        else
        {
            m_journal.debug << iIdentifier << " No paths found";
        }
    }

    iLastLevel = iLevel;
    bLastSuccess = found;

    if (fast && ptQuickReply.is_not_a_date_time())
    {
        ptQuickReply = boost::posix_time::microsec_clock::universal_time();
        mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds());
    }
    else if (!fast && ptFullReply.is_not_a_date_time())
    {
        ptFullReply = boost::posix_time::microsec_clock::universal_time();
        mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds());
    }

    jvStatus[jss::alternatives] = jvArray;
    return jvStatus;
}