Esempio n. 1
0
// This interface is deprecated.
Json::Value doRipplePathFind (RPC::Context& context)
{
    RPC::LegacyPathFind lpf (context.role == Role::ADMIN);
    if (!lpf.isOk ())
        return rpcError (rpcTOO_BUSY);

    context.loadType = Resource::feeHighBurdenRPC;

    RippleAddress raSrc;
    RippleAddress raDst;
    STAmount saDstAmount;
    Ledger::pointer lpLedger;

    Json::Value jvResult;

    if (getConfig().RUN_STANDALONE ||
        context.params.isMember(jss::ledger) ||
        context.params.isMember(jss::ledger_index) ||
        context.params.isMember(jss::ledger_hash))
    {
        // The caller specified a ledger
        jvResult = RPC::lookupLedger (
            context.params, lpLedger, context.netOps);
        if (!lpLedger)
            return jvResult;
    }

    if (!context.params.isMember (jss::source_account))
    {
        jvResult = rpcError (rpcSRC_ACT_MISSING);
    }
    else if (!context.params[jss::source_account].isString ()
             || !raSrc.setAccountID (
                 context.params[jss::source_account].asString ()))
    {
        jvResult = rpcError (rpcSRC_ACT_MALFORMED);
    }
    else if (!context.params.isMember (jss::destination_account))
    {
        jvResult = rpcError (rpcDST_ACT_MISSING);
    }
    else if (!context.params[jss::destination_account].isString ()
             || !raDst.setAccountID (
                 context.params[jss::destination_account].asString ()))
    {
        jvResult = rpcError (rpcDST_ACT_MALFORMED);
    }
    else if (
        // Parse saDstAmount.
        !context.params.isMember (jss::destination_amount)
        || ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount])
        || saDstAmount <= zero
        || (!isXRP(saDstAmount.getCurrency ())
            && (!saDstAmount.getIssuer () ||
                noAccount() == saDstAmount.getIssuer ())))
    {
        WriteLog (lsINFO, RPCHandler) << "Bad destination_amount.";
        jvResult    = rpcError (rpcINVALID_PARAMS);
    }
    else if (
        // Checks on source_currencies.
        context.params.isMember (jss::source_currencies)
        && (!context.params[jss::source_currencies].isArray ()
            || !context.params[jss::source_currencies].size ())
        // Don't allow empty currencies.
    )
    {
        WriteLog (lsINFO, RPCHandler) << "Bad source_currencies.";
        jvResult    = rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        context.loadType = Resource::feeHighBurdenRPC;
        RippleLineCache::pointer cache;

        if (lpLedger)
        {
            // The caller specified a ledger
            lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false);
            cache = std::make_shared<RippleLineCache>(lpLedger);
        }
        else
        {
            // The closed ledger is recent and any nodes made resident
            // have the best chance to persist
            lpLedger = context.netOps.getClosedLedger();
            cache = getApp().getPathRequests().getLineCache(lpLedger, false);
        }

        Json::Value     jvSrcCurrencies;

        if (context.params.isMember (jss::source_currencies))
        {
            jvSrcCurrencies = context.params[jss::source_currencies];
        }
        else
        {
            jvSrcCurrencies = buildSrcCurrencies(raSrc, cache);
        }

        // Fill in currencies destination will accept
        Json::Value jvDestCur (Json::arrayValue);

        // TODO(tom): this could be optimized the same way that
        // PathRequest::doUpdate() is - if we don't obsolete this code first.
        auto usDestCurrID = accountDestCurrencies (raDst, cache, true);
        for (auto const& uCurrency: usDestCurrID)
                jvDestCur.append (to_string (uCurrency));

        jvResult[jss::destination_currencies] = jvDestCur;
        jvResult[jss::destination_account] = raDst.humanAccountID ();

        int level = getConfig().PATH_SEARCH_OLD;
        if ((getConfig().PATH_SEARCH_MAX > level)
            && !getApp().getFeeTrack().isLoadedLocal())
        {
            ++level;
        }

        if (context.params.isMember(jss::search_depth)
            && context.params[jss::search_depth].isIntegral())
        {
            int rLev = context.params[jss::search_depth].asInt ();
            if ((rLev < level) || (context.role == Role::ADMIN))
                level = rLev;
        }

        auto contextPaths = context.params.isMember(jss::paths) ?
            boost::optional<Json::Value>(context.params[jss::paths]) :
                boost::optional<Json::Value>(boost::none);
        auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount, 
            lpLedger, jvSrcCurrencies, contextPaths, level);
        if (!pathFindResult.first)
            return pathFindResult.second;

        // Each alternative differs by source currency.
        jvResult[jss::alternatives] = pathFindResult.second;
    }

    WriteLog (lsDEBUG, RPCHandler)
            << boost::str (boost::format ("ripple_path_find< %s")
                           % jvResult);

    return jvResult;
}
Esempio n. 2
0
// This interface is deprecated.
Json::Value doRipplePathFind (RPC::Context& context)
{
    RPC::LegacyPathFind lpf (context.role == Role::ADMIN);
    if (!lpf.isOk ())
        return rpcError (rpcTOO_BUSY);

    context.loadType = Resource::feeHighBurdenRPC;

    RippleAddress raSrc;
    RippleAddress raDst;
    STAmount saDstAmount;
    Ledger::pointer lpLedger;

    Json::Value jvResult;

    if (getConfig().RUN_STANDALONE ||
        context.params.isMember(jss::ledger) ||
        context.params.isMember(jss::ledger_index) ||
        context.params.isMember(jss::ledger_hash))
    {
        // The caller specified a ledger
        jvResult = RPC::lookupLedger (
            context.params, lpLedger, context.netOps);
        if (!lpLedger)
            return jvResult;
    }

    if (!context.params.isMember ("source_account"))
    {
        jvResult = rpcError (rpcSRC_ACT_MISSING);
    }
    else if (!context.params["source_account"].isString ()
             || !raSrc.setAccountID (
                 context.params["source_account"].asString ()))
    {
        jvResult = rpcError (rpcSRC_ACT_MALFORMED);
    }
    else if (!context.params.isMember ("destination_account"))
    {
        jvResult = rpcError (rpcDST_ACT_MISSING);
    }
    else if (!context.params["destination_account"].isString ()
             || !raDst.setAccountID (
                 context.params["destination_account"].asString ()))
    {
        jvResult = rpcError (rpcDST_ACT_MALFORMED);
    }
    else if (
        // Parse saDstAmount.
        !context.params.isMember ("destination_amount")
        || ! amountFromJsonNoThrow(saDstAmount, context.params["destination_amount"])
        || saDstAmount <= zero
        || (!isXRP(saDstAmount.getCurrency ())
            && (!saDstAmount.getIssuer () ||
                noAccount() == saDstAmount.getIssuer ())))
    {
        WriteLog (lsINFO, RPCHandler) << "Bad destination_amount.";
        jvResult    = rpcError (rpcINVALID_PARAMS);
    }
    else if (
        // Checks on source_currencies.
        context.params.isMember ("source_currencies")
        && (!context.params["source_currencies"].isArray ()
            || !context.params["source_currencies"].size ())
        // Don't allow empty currencies.
    )
    {
        WriteLog (lsINFO, RPCHandler) << "Bad source_currencies.";
        jvResult    = rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        context.loadType = Resource::feeHighBurdenRPC;
        RippleLineCache::pointer cache;

        if (lpLedger)
        {
            // The caller specified a ledger
            lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false);
            cache = std::make_shared<RippleLineCache>(lpLedger);
        }
        else
        {
            // The closed ledger is recent and any nodes made resident
            // have the best chance to persist
            lpLedger = context.netOps.getClosedLedger();
            cache = getApp().getPathRequests().getLineCache(lpLedger, false);
        }

        Json::Value     jvSrcCurrencies;

        if (context.params.isMember ("source_currencies"))
        {
            jvSrcCurrencies = context.params["source_currencies"];
        }
        else
        {
            auto currencies = accountSourceCurrencies (raSrc, cache, true);
            jvSrcCurrencies = Json::Value (Json::arrayValue);

            for (auto const& uCurrency: currencies)
            {
                Json::Value jvCurrency (Json::objectValue);
                jvCurrency["currency"] = to_string(uCurrency);
                jvSrcCurrencies.append (jvCurrency);
            }
        }

        // Fill in currencies destination will accept
        Json::Value jvDestCur (Json::arrayValue);

        // TODO(tom): this could be optimized the same way that
        // PathRequest::doUpdate() is - if we don't obsolete this code first.
        auto usDestCurrID = accountDestCurrencies (raDst, cache, true);
        for (auto const& uCurrency: usDestCurrID)
                jvDestCur.append (to_string (uCurrency));

        jvResult["destination_currencies"] = jvDestCur;
        jvResult["destination_account"] = raDst.humanAccountID ();

        Json::Value jvArray (Json::arrayValue);

        int level = getConfig().PATH_SEARCH_OLD;
        if ((getConfig().PATH_SEARCH_MAX > level)
            && !getApp().getFeeTrack().isLoadedLocal())
        {
            ++level;
        }

        if (context.params.isMember("search_depth")
            && context.params["search_depth"].isIntegral())
        {
            int rLev = context.params["search_depth"].asInt ();
            if ((rLev < level) || (context.role == Role::ADMIN))
                level = rLev;
        }

        FindPaths fp (
            cache,
            raSrc.getAccountID(),
            raDst.getAccountID(),
            saDstAmount,
            level,
            4); // max paths

        for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i)
        {
            Json::Value jvSource        = jvSrcCurrencies[i];

            Currency uSrcCurrencyID;
            Account uSrcIssuerID;

            if (!jvSource.isObject ())
                return rpcError (rpcINVALID_PARAMS);

            // Parse mandatory currency.
            if (!jvSource.isMember ("currency")
                || !to_currency (
                    uSrcCurrencyID, jvSource["currency"].asString ()))
            {
                WriteLog (lsINFO, RPCHandler) << "Bad currency.";

                return rpcError (rpcSRC_CUR_MALFORMED);
            }

            if (uSrcCurrencyID.isNonZero ())
                uSrcIssuerID = raSrc.getAccountID ();

            // Parse optional issuer.
            if (jvSource.isMember ("issuer") &&
                ((!jvSource["issuer"].isString () ||
                  !to_issuer (uSrcIssuerID, jvSource["issuer"].asString ())) ||
                 (uSrcIssuerID.isZero () != uSrcCurrencyID.isZero ()) ||
                 (noAccount() == uSrcIssuerID)))
            {
                WriteLog (lsINFO, RPCHandler) << "Bad issuer.";
                return rpcError (rpcSRC_ISR_MALFORMED);
            }

            STPathSet spsComputed;
            if (context.params.isMember("paths"))
            {
                Json::Value pathSet = Json::objectValue;
                pathSet["Paths"] = context.params["paths"];
                STParsedJSONObject paths ("pathSet", pathSet);
                if (paths.object.get() == nullptr)
                    return paths.error;
                else
                {
                    spsComputed = paths.object.get()->getFieldPathSet (sfPaths);
                    WriteLog (lsTRACE, RPCHandler) << "ripple_path_find: Paths: " << spsComputed.getJson (0);
                }
            }

            STPath fullLiquidityPath;
            auto valid = fp.findPathsForIssue (
                {uSrcCurrencyID, uSrcIssuerID},
                spsComputed,
                fullLiquidityPath);
            if (!valid)
            {
                WriteLog (lsWARNING, RPCHandler)
                    << "ripple_path_find: No paths found.";
            }
            else
            {
                auto& issuer =
                    isXRP (uSrcIssuerID) ?
                        isXRP (uSrcCurrencyID) ? // Default to source account.
                            xrpAccount() :
                            Account (raSrc.getAccountID ())
                        : uSrcIssuerID;            // Use specifed issuer.

                STAmount saMaxAmount ({uSrcCurrencyID, issuer}, 1);
                saMaxAmount.negate ();

                LedgerEntrySet lesSandbox (lpLedger, tapNONE);

                auto rc = path::RippleCalc::rippleCalculate (
                    lesSandbox,
                    saMaxAmount,            // --> Amount to send is unlimited
                                            //     to get an estimate.
                    saDstAmount,            // --> Amount to deliver.
                    raDst.getAccountID (),  // --> Account to deliver to.
                    raSrc.getAccountID (),  // --> Account sending from.
                    spsComputed);           // --> Path set.

                WriteLog (lsWARNING, RPCHandler)
                    << "ripple_path_find:"
                    << " saMaxAmount=" << saMaxAmount
                    << " saDstAmount=" << saDstAmount
                    << " saMaxAmountAct=" << rc.actualAmountIn
                    << " saDstAmountAct=" << rc.actualAmountOut;

                if (fullLiquidityPath.size() > 0 &&
                    (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL))
                {
                    WriteLog (lsDEBUG, PathRequest)
                        << "Trying with an extra path element";

                    spsComputed.push_back (fullLiquidityPath);
                    lesSandbox.clear ();
                    rc = path::RippleCalc::rippleCalculate (
                        lesSandbox,
                        saMaxAmount,            // --> Amount to send is unlimited
                        //     to get an estimate.
                        saDstAmount,            // --> Amount to deliver.
                        raDst.getAccountID (),  // --> Account to deliver to.
                        raSrc.getAccountID (),  // --> Account sending from.
                        spsComputed);         // --> Path set.
                    WriteLog (lsDEBUG, PathRequest)
                        << "Extra path element gives "
                        << transHuman (rc.result ());
                }

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

                    STPathSet   spsCanonical;

                    // Reuse the expanded as it would need to be calcuated
                    // anyway to produce the canonical.  (At least unless we
                    // make a direct canonical.)

                    jvEntry["source_amount"] = rc.actualAmountIn.getJson (0);
                    jvEntry["paths_canonical"]  = Json::arrayValue;
                    jvEntry["paths_computed"]   = spsComputed.getJson (0);

                    jvArray.append (jvEntry);
                }
                else
                {
                    std::string strToken;
                    std::string strHuman;

                    transResultInfo (rc.result (), strToken, strHuman);

                    WriteLog (lsDEBUG, RPCHandler)
                        << "ripple_path_find: "
                        << strToken << " "
                        << strHuman << " "
                        << spsComputed.getJson (0);
                }
            }
        }

        // Each alternative differs by source currency.
        jvResult["alternatives"] = jvArray;
    }

    WriteLog (lsDEBUG, RPCHandler)
            << boost::str (boost::format ("ripple_path_find< %s")
                           % jvResult);

    return jvResult;
}
Esempio n. 3
0
// This interface is deprecated.
Json::Value doRipplePathFind (RPC::Context& context)
{
    if (context.app.config().PATH_SEARCH_MAX == 0)
        return rpcError (rpcNOT_SUPPORTED);

    context.loadType = Resource::feeHighBurdenRPC;

    std::shared_ptr <ReadView const> lpLedger;
    Json::Value jvResult;

    if (! context.app.config().standalone() &&
        ! context.params.isMember(jss::ledger) &&
        ! context.params.isMember(jss::ledger_index) &&
        ! context.params.isMember(jss::ledger_hash))
    {
        // No ledger specified, use pathfinding defaults
        // and dispatch to pathfinding engine
        if (context.app.getLedgerMaster().getValidatedLedgerAge() >
            RPC::Tuning::maxValidatedLedgerAge)
        {
            return rpcError (rpcNO_NETWORK);
        }

        PathRequest::pointer request;
        lpLedger = context.ledgerMaster.getClosedLedger();

        // It doesn't look like there's much odd happening here, but you should
        // be aware this code runs in a JobQueue::Coro, which is a coroutine.
        // And we may be flipping around between threads.  Here's an overview:
        //
        // 1. We're running doRipplePathFind() due to a call to
        //    ripple_path_find.  doRipplePathFind() is currently running
        //    inside of a JobQueue::Coro using a JobQueue thread.
        //
        // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the
        //    path-finding request.  That request will (probably) run at some
        //    indeterminate future time on a (probably different) JobQueue
        //    thread.
        //
        // 3. As a continuation from that path-finding JobQueue thread, the
        //    coroutine we're currently running in (!) is posted to the
        //    JobQueue.  Because it is a continuation, that post won't
        //    happen until the path-finding request completes.
        //
        // 4. Once the continuation is enqueued, and we have reason to think
        //    the path-finding job is likely to run, then the coroutine we're
        //    running in yield()s.  That means it surrenders its thread in
        //    the JobQueue.  The coroutine is suspended, but ready to run,
        //    because it is kept resident by a shared_ptr in the
        //    path-finding continuation.
        //
        // 5. If all goes well then path-finding runs on a JobQueue thread
        //    and executes its continuation.  The continuation posts this
        //    same coroutine (!) to the JobQueue.
        //
        // 6. When the JobQueue calls this coroutine, this coroutine resumes
        //    from the line below the coro->yield() and returns the
        //    path-finding result.
        //
        // With so many moving parts, what could go wrong?
        //
        // Just in terms of the JobQueue refusing to add jobs at shutdown
        // there are two specific things that can go wrong.
        //
        // 1. The path-finding Job queued by makeLegacyPathRequest() might be
        //    rejected (because we're shutting down).
        //
        //    Fortunately this problem can be addressed by looking at the
        //    return value of makeLegacyPathRequest().  If
        //    makeLegacyPathRequest() cannot get a thread to run the path-find
        //    on, then it returns an empty request.
        //
        // 2. The path-finding job might run, but the Coro::post() might be
        //    rejected by the JobQueue (because we're shutting down).
        //
        //    We handle this case by resuming (not posting) the Coro.
        //    By resuming the Coro, we allow the Coro to run to completion
        //    on the current thread instead of requiring that it run on a
        //    new thread from the JobQueue.
        //
        // Both of these failure modes are hard to recreate in a unit test
        // because they are so dependent on inter-thread timing.  However
        // the failure modes can be observed by synchronously (inside the
        // rippled source code) shutting down the application.  The code to
        // do so looks like this:
        //
        //   context.app.signalStop();
        //   while (! context.app.getJobQueue().jobCounter().joined()) { }
        //
        // The first line starts the process of shutting down the app.
        // The second line waits until no more jobs can be added to the
        // JobQueue before letting the thread continue.
        //
        // May 2017
        jvResult = context.app.getPathRequests().makeLegacyPathRequest (
            request,
            [&context] () {
                // Copying the shared_ptr keeps the coroutine alive up
                // through the return.  Otherwise the storage under the
                // captured reference could evaporate when we return from
                // coroCopy->resume().  This is not strictly necessary, but
                // will make maintenance easier.
                std::shared_ptr<JobQueue::Coro> coroCopy {context.coro};
                if (!coroCopy->post())
                {
                    // The post() failed, so we won't get a thread to let
                    // the Coro finish.  We'll call Coro::resume() so the
                    // Coro can finish on our thread.  Otherwise the
                    // application will hang on shutdown.
                    coroCopy->resume();
                }
            },
            context.consumer, lpLedger, context.params);
        if (request)
        {
            context.coro->yield();
            jvResult = request->doStatus (context.params);
        }

        return jvResult;
    }

    // The caller specified a ledger
    jvResult = RPC::lookupLedger (lpLedger, context);
    if (! lpLedger)
        return jvResult;

    RPC::LegacyPathFind lpf (isUnlimited (context.role), context.app);
    if (! lpf.isOk ())
        return rpcError (rpcTOO_BUSY);

    auto result = context.app.getPathRequests().doLegacyPathRequest (
        context.consumer, lpLedger, context.params);

    for (auto &fieldName : jvResult.getMemberNames ())
        result[fieldName] = std::move (jvResult[fieldName]);

    return result;
}