Exemple #1
0
Json::Value doPathFind (RPC::Context& context)
{
    auto lpLedger = context.ledgerMaster.getClosedLedger();

    if (!context.params.isMember (jss::subcommand) ||
        !context.params[jss::subcommand].isString ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }

    if (!context.infoSub)
        return rpcError (rpcNO_EVENTS);

    auto sSubCommand = context.params[jss::subcommand].asString ();

    if (sSubCommand == "create")
    {
        context.loadType = Resource::feeHighBurdenRPC;
        context.infoSub->clearPathRequest ();
        return context.app.getPathRequests().makePathRequest (
            context.infoSub, lpLedger, context.params);
    }

    if (sSubCommand == "close")
    {
        PathRequest::pointer request = context.infoSub->getPathRequest ();

        if (!request)
            return rpcError (rpcNO_PF_REQUEST);

        context.infoSub->clearPathRequest ();
        return request->doClose (context.params);
    }

    if (sSubCommand == "status")
    {
        PathRequest::pointer request = context.infoSub->getPathRequest ();

        if (!request)
            return rpcError (rpcNO_PF_REQUEST);

        return request->doStatus (context.params);
    }

    return rpcError (rpcINVALID_PARAMS);
}
Json::Value PathRequests::makePathRequest(
    boost::shared_ptr <InfoSub> const& subscriber,
    const boost::shared_ptr<Ledger>& inLedger,
    const Json::Value& requestJson)
{
    PathRequest::pointer req = boost::make_shared<PathRequest> (
                                   subscriber, ++mLastIdentifier, *this, mJournal);

    Ledger::pointer ledger = inLedger;
    RippleLineCache::pointer cache;

    {
        ScopedLockType sl (mLock);
        cache = getLineCache (ledger, false);
    }

    bool valid = false;
    Json::Value result = req->doCreate (ledger, cache, requestJson, valid);

    if (valid)
    {
        {
            ScopedLockType sl (mLock);

            // Insert after any older unserviced requests but before any serviced requests
            std::vector<PathRequest::wptr>::iterator it = mRequests.begin ();
            while (it != mRequests.end ())
            {
                PathRequest::pointer req = it->lock ();
                if (req && !req->isNew ())
                    break; // This request has been handled, we come before it

                // This is a newer request, we come after it
                ++it;
            }
            mRequests.insert (it, PathRequest::wptr (req));

        }
        subscriber->setPathRequest (req);
        getApp().getLedgerMaster().newPathRequest();
    }
    return result;
}
void PathRequests::updateAll (Ledger::ref inLedger, CancelCallback shouldCancel)
{
    std::vector<PathRequest::wptr> requests;

    LoadEvent::autoptr event (getApp().getJobQueue().getLoadEventAP(jtPATH_FIND, "PathRequest::updateAll"));

    // Get the ledger and cache we should be using
    Ledger::pointer ledger = inLedger;
    RippleLineCache::pointer cache;
    {
        ScopedLockType sl (mLock);
        requests = mRequests;
        cache = getLineCache (ledger, true);
    }

    bool newRequests = getApp().getLedgerMaster().isNewPathRequest();
    bool mustBreak = false;

    mJournal.trace << "updateAll seq=" << ledger->getLedgerSeq() << ", " <<
                   requests.size() << " requests";
    int processed = 0, removed = 0;

    do
    {

        BOOST_FOREACH (PathRequest::wref wRequest, requests)
        {
            if (shouldCancel())
                break;

            bool remove = true;
            PathRequest::pointer pRequest = wRequest.lock ();

            if (pRequest)
            {
                if (!pRequest->needsUpdate (newRequests, ledger->getLedgerSeq ()))
                    remove = false;
                else
                {
                    InfoSub::pointer ipSub = pRequest->getSubscriber ();
                    if (ipSub)
                    {
                        ipSub->getConsumer ().charge (Resource::feePathFindUpdate);
                        if (!ipSub->getConsumer ().warn ())
                        {
                            Json::Value update = pRequest->doUpdate (cache, false);
                            pRequest->updateComplete ();
                            update["type"] = "path_find";
                            ipSub->send (update, false);
                            remove = false;
                            ++processed;
                        }
                    }
                }
            }

            if (remove)
            {
                PathRequest::pointer pRequest = wRequest.lock ();

                ScopedLockType sl (mLock);

                // Remove any dangling weak pointers or weak pointers that refer to this path request.
                std::vector<PathRequest::wptr>::iterator it = mRequests.begin();
                while (it != mRequests.end())
                {
                    PathRequest::pointer itRequest = it->lock ();
                    if (!itRequest || (itRequest == pRequest))
                    {
                        ++removed;
                        it = mRequests.erase (it);
                    }
                    else
                        ++it;
                }
            }

            mustBreak = !newRequests && getApp().getLedgerMaster().isNewPathRequest();
            if (mustBreak) // We weren't handling new requests and then there was a new request
                break;

        }

        if (mustBreak)
        {   // a new request came in while we were working
            newRequests = true;
        }
        else if (newRequests)
        {   // we only did new requests, so we always need a last pass
            newRequests = getApp().getLedgerMaster().isNewPathRequest();
        }
        else
        {   // check if there are any new requests, otherwise we are done
            newRequests = getApp().getLedgerMaster().isNewPathRequest();
            if (!newRequests) // We did a full pass and there are no new requests
                return;
        }

        {
            // Get the latest requests, cache, and ledger for next pass
            ScopedLockType sl (mLock);

            if (mRequests.empty())
                break;
            requests = mRequests;

            cache = getLineCache (ledger, false);
        }

    }
    while (!shouldCancel ());

    mJournal.debug << "updateAll complete " << processed << " process and " <<
                   removed << " removed";
}
Exemple #4
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;

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

    Json::Value jvResult;

    if (! context.suspend ||
        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::lookupLedgerDeprecated (lpLedger, context);
        if (!lpLedger)
            return jvResult;
    }
    else
    {
        if (getApp().getLedgerMaster().getValidatedLedgerAge() >
            RPC::Tuning::maxValidatedLedgerAge)
        {
            return rpcError (rpcNO_NETWORK);
        }

        context.loadType = Resource::feeHighBurdenRPC;
        lpLedger = context.ledgerMaster.getClosedLedger();

        PathRequest::pointer request;
        suspend(context,
                [&request, &context, &jvResult, &lpLedger]
                (RPC::Callback const& callback)
        {
            jvResult = getApp().getPathRequests().makeLegacyPathRequest (
                request, callback, lpLedger, context.params);
            assert(callback);
            if (! request && callback)
                callback();
        });

        if (request)
            jvResult = request->doStatus (context.params);

        return jvResult;
    }

    if (!context.params.isMember (jss::source_account))
    {
        jvResult = rpcError (rpcSRC_ACT_MISSING);
    }
    else if (! deprecatedParseBase58(raSrc,
        context.params[jss::source_account]))
    {
        jvResult = rpcError (rpcSRC_ACT_MALFORMED);
    }
    else if (!context.params.isMember (jss::destination_account))
    {
        jvResult = rpcError (rpcDST_ACT_MISSING);
    }
    else if (! deprecatedParseBase58 (raDst,
        context.params[jss::destination_account]))
    {
        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.ledgerMaster.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] = getApp().accountIDCache().toBase58(raDst);

        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,
            jvSrcCurrencies, contextPaths, level);
        if (!pathFindResult.first)
            return pathFindResult.second;

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

    WriteLog (lsDEBUG, RPCHandler)
            << "ripple_path_find< %s" << jvResult;

    return jvResult;
}
// 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;
}