Пример #1
0
void
stpath_append_one (STPath& st,
    jtx::BookSpec const& book)
{
    st.push_back(STPathElement({
        boost::none, book.currency, book.account }));
}
Пример #2
0
void
stpath_append_one (STPath& st,
    jtx::IOU const& iou)
{
    st.push_back(STPathElement({
        iou.account.id(), iou.currency, boost::none }));
}
Пример #3
0
// Set to an expanded path.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP
void PathState::setExpanded (
    const LedgerEntrySet&   lesSource,
    const STPath&           spSourcePath,
    const uint160&          uReceiverID,
    const uint160&          uSenderID
)
{
    uQuality    = 1;            // Mark path as active.

    const uint160   uMaxCurrencyID  = saInReq.getCurrency ();
    const uint160   uMaxIssuerID    = saInReq.getIssuer ();

    const uint160   uOutCurrencyID  = saOutReq.getCurrency ();
    const uint160   uOutIssuerID    = saOutReq.getIssuer ();
    const uint160   uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_STR;   // Sender is always issuer for non-STR.

    WriteLog (lsTRACE, RippleCalc) << "setExpanded> " << spSourcePath.getJson (0);

    lesEntries  = lesSource.duplicate ();

    terStatus   = tesSUCCESS;

    // STR with issuer is malformed.
    if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
        terStatus   = temBAD_PATH;

    // Push sending node.
    // For non-STR, issuer is always sending account.
    // - Trying to expand, not-compact.
    // - Every issuer will be traversed through.
    if (tesSUCCESS == terStatus)
        terStatus   = pushNode (
                          !!uMaxCurrencyID
                          ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
                          : STPathElement::typeAccount | STPathElement::typeCurrency,
                          uSenderID,
                          uMaxCurrencyID,                                 // Max specifies the currency.
                          uSenderIssuerID);

    WriteLog (lsDEBUG, RippleCalc) << "setExpanded: pushed:" <<
        " account=" << RippleAddress::createHumanAccountID (uSenderID) <<
        " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
        " issuer=" << RippleAddress::createHumanAccountID (uSenderIssuerID);

    if (tesSUCCESS == terStatus
            && uMaxIssuerID != uSenderIssuerID)                 // Issuer was not same as sender.
    {
        // May have an implied account node.
        // - If it was STR, then issuers would have matched.

        // Figure out next node properties for implied node.
        const uint160   uNxtCurrencyID  = spSourcePath.size ()
                                          ? spSourcePath.getElement (0).getCurrency () // Use next node.
                                          : uOutCurrencyID;                           // Use send.
        const uint160   uNxtAccountID   = spSourcePath.size ()
                                          ? spSourcePath.getElement (0).getAccountID ()
                                          : !!uOutCurrencyID
                                          ? uOutIssuerID == uReceiverID
                                          ? uReceiverID
                                          : uOutIssuerID                      // Use implied node.
                                  : ACCOUNT_STR;

        WriteLog (lsDEBUG, RippleCalc) << "setExpanded: implied check:" <<
            " uMaxIssuerID=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
            " uSenderIssuerID=" << RippleAddress::createHumanAccountID (uSenderIssuerID) <<
            " uNxtCurrencyID=" << STAmount::createHumanCurrency (uNxtCurrencyID) <<
            " uNxtAccountID=" << RippleAddress::createHumanAccountID (uNxtAccountID);

        // Can't just use push implied, because it can't compensate for next account.
        if (!uNxtCurrencyID                         // Next is STR, offer next. Must go through issuer.
                || uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next...
                || uMaxIssuerID != uNxtAccountID)   // Next is not implied issuer
        {
            WriteLog (lsDEBUG, RippleCalc) << "setExpanded: sender implied:" <<
                " account=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
                " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
                " issuer=" << RippleAddress::createHumanAccountID (uMaxIssuerID);

            // Add account implied by SendMax.
            terStatus   = pushNode (
                !!uMaxCurrencyID
                    ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
                    : STPathElement::typeAccount | STPathElement::typeCurrency,
                uMaxIssuerID,
                uMaxCurrencyID,
                uMaxIssuerID);
        }
    }

    BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
    {
        if (tesSUCCESS == terStatus)
        {
            WriteLog (lsTRACE, RippleCalc) << "setExpanded: element in path";
            terStatus   = pushNode (
                speElement.getNodeType (), speElement.getAccountID (), 
                speElement.getCurrency (), speElement.getIssuerID ());
        }
    }

    const Node&  pnPrv           = vpnNodes.back ();

    if (tesSUCCESS == terStatus
            && !!uOutCurrencyID                         // Next is not STR
            && uOutIssuerID != uReceiverID              // Out issuer is not receiver
            && (pnPrv.uCurrencyID != uOutCurrencyID     // Previous will be an offer.
                || pnPrv.uAccountID != uOutIssuerID))   // Need the implied issuer.
    {
        // Add implied account.
        WriteLog (lsDEBUG, RippleCalc) << "setExpanded: receiver implied:" <<
            " account=" << RippleAddress::createHumanAccountID (uOutIssuerID) <<
            " currency=" << STAmount::createHumanCurrency (uOutCurrencyID) <<
            " issuer=" << RippleAddress::createHumanAccountID (uOutIssuerID);

        terStatus   = pushNode (
            !!uOutCurrencyID
                ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
                : STPathElement::typeAccount | STPathElement::typeCurrency,
            uOutIssuerID,
            uOutCurrencyID,
            uOutIssuerID);
    }

    if (tesSUCCESS == terStatus)
    {
        // Create receiver node.
        // Last node is always an account.

        terStatus   = pushNode (
            !!uOutCurrencyID
                ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
                : STPathElement::typeAccount | STPathElement::typeCurrency,
            uReceiverID,                                    // Receive to output
            uOutCurrencyID,                                 // Desired currency
            uReceiverID);
    }

    if (tesSUCCESS == terStatus)
    {
        // Look for first mention of source in nodes and detect loops.
        // Note: The output is not allowed to be a source.

        const unsigned int  uNodes  = vpnNodes.size ();

        for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
        {
            const Node&  pnCur   = vpnNodes[uNode];

            if (!umForward.insert (std::make_pair (std::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
            {
                // Failed to insert. Have a loop.
                WriteLog (lsDEBUG, RippleCalc) <<
                    "setExpanded: loop detected: " << getJson ();

                terStatus   = temBAD_PATH_LOOP;
            }
        }
    }

    WriteLog (lsDEBUG, RippleCalc) << "setExpanded:" <<
        " in=" << STAmount::createHumanCurrency (uMaxCurrencyID) <<
        "/" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
        " out=" << STAmount::createHumanCurrency (uOutCurrencyID) <<
        "/" << RippleAddress::createHumanAccountID (uOutIssuerID) <<
        ": " << getJson ();
}
Пример #4
0
// Set this object to be an expanded path from spSourcePath - take the implied
// nodes and makes them explicit.  It also sanitizes the path.
//
// There are only two types of nodes: account nodes and order books nodes.
//
// You can infer some nodes automatically.  If you're paying me bitstamp USD,
// then there must be an intermediate bitstamp node.
//
// If you have accounts A and B, and they're delivery currency issued by C, then
// there must be a node with account C in the middle.
//
// If you're paying USD and getting bitcoins, there has to be an order book in
// between.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH,
// or temBAD_PATH_LOOP
TER PathState::expandPath (
    STPath const& spSourcePath,
    AccountID const& uReceiverID,
    AccountID const& uSenderID)
{
    uQuality = 1;            // Mark path as active.

    Currency const& uMaxCurrencyID = saInReq.getCurrency ();
    AccountID const& uMaxIssuerID = saInReq.getIssuer ();

    Currency const& currencyOutID = saOutReq.getCurrency ();
    AccountID const& issuerOutID = saOutReq.getIssuer ();
    AccountID const& uSenderIssuerID
        = isXRP(uMaxCurrencyID) ? xrpAccount() : uSenderID;
    // Sender is always issuer for non-XRP.

    JLOG (j_.trace)
        << "expandPath> " << spSourcePath.getJson (0);

    terStatus = tesSUCCESS;

    // XRP with issuer is malformed.
    if ((isXRP (uMaxCurrencyID) && !isXRP (uMaxIssuerID))
        || (isXRP (currencyOutID) && !isXRP (issuerOutID)))
    {
        JLOG (j_.debug)
            << "expandPath> issuer with XRP";
        terStatus   = temBAD_PATH;
    }

    // Push sending node.
    // For non-XRP, issuer is always sending account.
    // - Trying to expand, not-compact.
    // - Every issuer will be traversed through.
    if (terStatus == tesSUCCESS)
    {
        terStatus   = pushNode (
            !isXRP(uMaxCurrencyID)
            ? STPathElement::typeAccount | STPathElement::typeCurrency |
              STPathElement::typeIssuer
            : STPathElement::typeAccount | STPathElement::typeCurrency,
            uSenderID,
            uMaxCurrencyID, // Max specifies the currency.
            uSenderIssuerID);
    }

    JLOG (j_.debug)
        << "expandPath: pushed:"
        << " account=" << uSenderID
        << " currency=" << uMaxCurrencyID
        << " issuer=" << uSenderIssuerID;

    // Issuer was not same as sender.
    if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID)
    {
        // May have an implied account node.
        // - If it was XRP, then issuers would have matched.

        // Figure out next node properties for implied node.
        const auto uNxtCurrencyID  = spSourcePath.size ()
                ? Currency(spSourcePath.front ().getCurrency ())
                // Use next node.
                : currencyOutID;
                // Use send.

        // TODO(tom): complexify this next logic further in case someone
        // understands it.
        const auto nextAccountID   = spSourcePath.size ()
                ? AccountID(spSourcePath. front ().getAccountID ())
                : !isXRP(currencyOutID)
                ? (issuerOutID == uReceiverID)
                ? AccountID(uReceiverID)
                : AccountID(issuerOutID)                      // Use implied node.
                : xrpAccount();

        JLOG (j_.debug)
            << "expandPath: implied check:"
            << " uMaxIssuerID=" << uMaxIssuerID
            << " uSenderIssuerID=" << uSenderIssuerID
            << " uNxtCurrencyID=" << uNxtCurrencyID
            << " nextAccountID=" << nextAccountID;

        // Can't just use push implied, because it can't compensate for next
        // account.
        if (!uNxtCurrencyID
            // Next is XRP, offer next. Must go through issuer.
            || uMaxCurrencyID != uNxtCurrencyID
            // Next is different currency, offer next...
            || uMaxIssuerID != nextAccountID)
            // Next is not implied issuer
        {
            JLOG (j_.debug)
                << "expandPath: sender implied:"
                << " account=" << uMaxIssuerID
                << " currency=" << uMaxCurrencyID
                << " issuer=" << uMaxIssuerID;

            // Add account implied by SendMax.
            terStatus = pushNode (
                !isXRP(uMaxCurrencyID)
                    ? STPathElement::typeAccount | STPathElement::typeCurrency |
                      STPathElement::typeIssuer
                    : STPathElement::typeAccount | STPathElement::typeCurrency,
                uMaxIssuerID,
                uMaxCurrencyID,
                uMaxIssuerID);
        }
    }

    for (auto & speElement: spSourcePath)
    {
        if (terStatus == tesSUCCESS)
        {
            JLOG (j_.trace) << "expandPath: element in path";
            terStatus = pushNode (
                speElement.getNodeType (), speElement.getAccountID (),
                speElement.getCurrency (), speElement.getIssuerID ());
        }
    }

    if (terStatus == tesSUCCESS
        && !isXRP(currencyOutID)               // Next is not XRP
        && issuerOutID != uReceiverID)         // Out issuer is not receiver
    {
        assert (!nodes_.empty ());

        auto const& backNode = nodes_.back ();

        if (backNode.issue_.currency != currencyOutID  // Previous will be offer
            || backNode.account_ != issuerOutID)       // Need implied issuer
        {
            // Add implied account.
            JLOG (j_.debug)
                << "expandPath: receiver implied:"
                << " account=" << issuerOutID
                << " currency=" << currencyOutID
                << " issuer=" << issuerOutID;

            terStatus   = pushNode (
                !isXRP(currencyOutID)
                    ? STPathElement::typeAccount | STPathElement::typeCurrency |
                      STPathElement::typeIssuer
                    : STPathElement::typeAccount | STPathElement::typeCurrency,
                issuerOutID,
                currencyOutID,
                issuerOutID);
        }
    }

    if (terStatus == tesSUCCESS)
    {
        // Create receiver node.
        // Last node is always an account.

        terStatus   = pushNode (
            !isXRP(currencyOutID)
                ? STPathElement::typeAccount | STPathElement::typeCurrency |
                   STPathElement::typeIssuer
               : STPathElement::typeAccount | STPathElement::typeCurrency,
            uReceiverID,                                    // Receive to output
            currencyOutID,                                 // Desired currency
            uReceiverID);
    }

    if (terStatus == tesSUCCESS)
    {
        // Look for first mention of source in nodes and detect loops.
        // Note: The output is not allowed to be a source.
        unsigned int index = 0;
        for (auto& node: nodes_)
        {
            AccountIssue accountIssue (node.account_, node.issue_);
            if (!umForward.insert ({accountIssue, index++}).second)
            {
                // Failed to insert. Have a loop.
                JLOG (j_.debug) <<
                    "expandPath: loop detected: " << getJson ();

                terStatus = temBAD_PATH_LOOP;
                break;
            }
        }
    }

    JLOG (j_.debug)
        << "expandPath:"
        << " in=" << uMaxCurrencyID
        << "/" << uMaxIssuerID
        << " out=" << currencyOutID
        << "/" << issuerOutID
        << ": " << getJson ();
    return terStatus;
}
Пример #5
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 =
                usAccountSourceCurrencies (raSrcAccount, cache, true);
        bool sameAccount = raSrcAccount == raDstAccount;
        for (auto const& c: usCurrencies)
        {
            if (!sameAccount || (c != saDstAmount.getCurrency ()))
            {
                if (c.isZero ())
                    sourceCurrencies.insert (std::make_pair (c, xrpAccount()));
                else
                    sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ()));
            }
        }
    }

    jvStatus["source_account"] = raSrcAccount.humanAccountID ();
    jvStatus["destination_account"] = raDstAccount.humanAccountID ();
    jvStatus["destination_amount"] = saDstAmount.getJson (0);

    if (!jvId.isNull ())
        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;

    for (auto const& currIssuer: sourceCurrencies)
    {
        {
            STAmount test ({currIssuer.first, currIssuer.second}, 1);
            if (m_journal.debug)
            {
                m_journal.debug
                        << iIdentifier
                        << " Trying to find paths: " << test.getFullText ();
            }
        }
        bool valid;
        STPathSet& spsPaths = mContext[currIssuer];
        Pathfinder pf (cache, raSrcAccount, raDstAccount,
                       currIssuer.first, currIssuer.second, saDstAmount, valid);
        CondLog (!valid, lsDEBUG, PathRequest)
                << iIdentifier << " PF request not valid";

        STPath extraPath;
        if (valid && pf.findPaths (iLevel, 4, spsPaths, extraPath))
        {
            LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE);
            PathState::List pathStateList;
            STAmount saMaxAmountAct;
            STAmount saDstAmountAct;
            auto& account = currIssuer.second.isNonZero ()
                    ? Account(currIssuer.second)
                    : isXRP (currIssuer.first)
                        ? xrpAccount()
                        : raSrcAccount.getAccountID ();
            STAmount saMaxAmount ({currIssuer.first, account}, 1);

            saMaxAmount.negate ();
            m_journal.debug << iIdentifier << " Paths found, calling rippleCalc";
            TER resultCode = path::rippleCalculate (
                lesSandbox, saMaxAmountAct, saDstAmountAct,
                pathStateList, saMaxAmount, saDstAmount,
                raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
                spsPaths, false, false, false, true);

            if ((extraPath.size() > 0) && ((resultCode == terNO_LINE) || (resultCode == tecPATH_PARTIAL)))
            {
                m_journal.debug
                        << iIdentifier << " Trying with an extra path element";
                spsPaths.addPath(extraPath);
                pathStateList.clear ();
                resultCode = path::rippleCalculate (
                    lesSandbox, saMaxAmountAct, saDstAmountAct,
                    pathStateList, saMaxAmount, saDstAmount,
                    raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
                    spsPaths, false, false, false, true);
                m_journal.debug
                        << iIdentifier << " Extra path element gives "
                        << transHuman (resultCode);
            }

            if (resultCode == tesSUCCESS)
            {
                Json::Value jvEntry (Json::objectValue);
                jvEntry["source_amount"]    = saMaxAmountAct.getJson (0);
                jvEntry["paths_computed"]   = spsPaths.getJson (0);
                found  = true;
                jvArray.append (jvEntry);
            }
            else
            {
                m_journal.debug << iIdentifier << " rippleCalc returns "
                                << transHuman (resultCode);
            }
        }
        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["alternatives"] = jvArray;
    return jvStatus;
}
Пример #6
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;
}
Пример #7
0
void
stpath_append_one (STPath& st,
    STPathElement const& pe)
{
    st.push_back(pe);
}
Пример #8
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;
}
Пример #9
0
bool STParsedJSON::parse (std::string const& json_name,
    Json::Value const& json, SField::ref inName, int depth,
        std::unique_ptr <STObject>& sub_object)
{
    if (! json.isObject ())
    {
        error = not_an_object (json_name);
        return false;
    }

    SField::ptr name (&inName);

    boost::ptr_vector<SerializedType> data;
    Json::Value::Members members (json.getMemberNames ());

    for (Json::Value::Members::iterator it (members.begin ());
        it != members.end (); ++it)
    {
        std::string const& fieldName = *it;
        Json::Value const& value = json [fieldName];

        SField::ref field = SField::getField (fieldName);

        if (field == sfInvalid)
        {
            error = unknown_field (json_name, fieldName);
            return false;
        }

        switch (field.fieldType)
        {
        case STI_UINT8:
            try
            {
                if (value.isString ())
                {
                    // VFALCO TODO wtf?
                }
                else if (value.isInt ())
                {
                    if (value.asInt () < 0 || value.asInt () > 255)
                    {
                        error = out_of_range (json_name, fieldName);
                        return false;
                    }

                    data.push_back (new STUInt8 (field,
                        range_check_cast <unsigned char> (
                            value.asInt (), 0, 255)));
                }
                else if (value.isUInt ())
                {
                    if (value.asUInt () > 255)
                    {
                        error = out_of_range (json_name, fieldName);
                        return false;
                    }

                    data.push_back (new STUInt8 (field,
                        range_check_cast <unsigned char> (
                            value.asUInt (), 0, 255)));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_UINT16:
            try
            {
                if (value.isString ())
                {
                    std::string strValue = value.asString ();

                    if (! strValue.empty () &&
                        ((strValue[0] < '0') || (strValue[0] > '9')))
                    {
                        if (field == sfTransactionType)
                        {
                            TxType const txType (TxFormats::getInstance()->
                                findTypeByName (strValue));

                            data.push_back (new STUInt16 (field,
                                static_cast <std::uint16_t> (txType)));

                            if (*name == sfGeneric)
                                name = &sfTransaction;
                        }
                        else if (field == sfLedgerEntryType)
                        {
                            LedgerEntryType const type (LedgerFormats::getInstance()->
                                findTypeByName (strValue));

                            data.push_back (new STUInt16 (field,
                                static_cast <std::uint16_t> (type)));

                            if (*name == sfGeneric)
                                name = &sfLedgerEntry;
                        }
                        else
                        {
                            error = invalid_data (json_name, fieldName);
                            return false;
                        }
                    }
                    else
                    {
                        data.push_back (new STUInt16 (field,
                            beast::lexicalCastThrow <std::uint16_t> (strValue)));
                    }
                }
                else if (value.isInt ())
                {
                    data.push_back (new STUInt16 (field,
                        range_check_cast <std::uint16_t> (
                            value.asInt (), 0, 65535)));
                }
                else if (value.isUInt ())
                {
                    data.push_back (new STUInt16 (field,
                        range_check_cast <std::uint16_t> (
                            value.asUInt (), 0, 65535)));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_UINT32:
            try
            {
                if (value.isString ())
                {
                    data.push_back (new STUInt32 (field,
                        beast::lexicalCastThrow <std::uint32_t> (value.asString ())));
                }
                else if (value.isInt ())
                {
                    data.push_back (new STUInt32 (field,
                        range_check_cast <std::uint32_t> (value.asInt (), 0u, 4294967295u)));
                }
                else if (value.isUInt ())
                {
                    data.push_back (new STUInt32 (field,
                        static_cast <std::uint32_t> (value.asUInt ())));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_UINT64:
            try
            {
                if (value.isString ())
                {
                    data.push_back (new STUInt64 (field,
                        uintFromHex (value.asString ())));
                }
                else if (value.isInt ())
                {
                    data.push_back (new STUInt64 (field,
                        range_check_cast<std::uint64_t> (
                            value.asInt (), 0, 18446744073709551615ull)));
                }
                else if (value.isUInt ())
                {
                    data.push_back (new STUInt64 (field,
                        static_cast <std::uint64_t> (value.asUInt ())));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_HASH128:
            try
            {
                if (value.isString ())
                {
                    data.push_back (new STHash128 (field, value.asString ()));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_HASH160:
            try
            {
                if (value.isString ())
                {
                    data.push_back (new STHash160 (field, value.asString ()));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_HASH256:
            try
            {
                if (value.isString ())
                {
                    data.push_back (new STHash256 (field, value.asString ()));
                }
                else
                {
                    error = bad_type (json_name, fieldName);
                    return false;
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_VL:
            if (! value.isString ())
            {
                error = bad_type (json_name, fieldName);
                return false;
            }

            try
            {
                std::pair<Blob, bool> ret(strUnHex (value.asString ()));

                if (!ret.second)
                    throw std::invalid_argument ("invalid data");

                data.push_back (new STVariableLength (field, ret.first));
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_AMOUNT:
            try
            {
                data.push_back (new STAmount (field, value));
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_VECTOR256:
            if (! value.isArray ())
            {
                error = array_expected (json_name, fieldName);
                return false;
            }
            
            try
            {
                data.push_back (new STVector256 (field));
                STVector256* tail (dynamic_cast <STVector256*> (&data.back ()));
                assert (tail);

                for (Json::UInt i = 0; !json.isValidIndex (i); ++i)
                {
                    uint256 s;
                    s.SetHex (json[i].asString ());
                    tail->addValue (s);
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_PATHSET:
            if (!value.isArray ())
            {
                error = array_expected (json_name, fieldName);
                return false;
            }

            try
            {
                data.push_back (new STPathSet (field));
                STPathSet* tail = dynamic_cast <STPathSet*> (&data.back ());
                assert (tail);

                for (Json::UInt i = 0; value.isValidIndex (i); ++i)
                {
                    STPath p;

                    if (!value[i].isArray ())
                    {
                        std::stringstream ss;
                        ss << fieldName << "[" << i << "]";
                        error = array_expected (json_name, ss.str ());
                        return false;
                    }

                    for (Json::UInt j = 0; value[i].isValidIndex (j); ++j)
                    {
                        std::stringstream ss;
                        ss << fieldName << "[" << i << "][" << j << "]";
                        std::string const element_name (
                            json_name + "." + ss.str());

                        // each element in this path has some combination of account,
                        // currency, or issuer

                        Json::Value pathEl = value[i][j];

                        if (!pathEl.isObject ())
                        {
                            error = not_an_object (element_name);
                            return false;
                        }

                        const Json::Value& account  = pathEl["account"];
                        const Json::Value& currency = pathEl["currency"];
                        const Json::Value& issuer   = pathEl["issuer"];
                        bool hasCurrency            = false;
                        uint160 uAccount, uCurrency, uIssuer;

                        if (! account.isNull ())
                        {
                            // human account id
                            if (! account.isString ())
                            {
                                error = string_expected (element_name, "account");
                                return false;
                            }

                            std::string const strValue (account.asString ());

                            if (value.size () == 40) // 160-bit hex account value
                                uAccount.SetHex (strValue);

                            {
                                RippleAddress a;

                                if (! a.setAccountID (strValue))
                                {
                                    error = invalid_data (element_name, "account");
                                    return false;
                                }

                                uAccount = a.getAccountID ();
                            }
                        }

                        if (!currency.isNull ())
                        {
                            // human currency
                            if (!currency.isString ())
                            {
                                error = string_expected (element_name, "currency");
                                return false;
                            }

                            hasCurrency = true;

                            if (currency.asString ().size () == 40)
                            {
                                uCurrency.SetHex (currency.asString ());
                            }
                            else if (!STAmount::currencyFromString (
                                uCurrency, currency.asString ()))
                            {
                                error = invalid_data (element_name, "currency");
                                return false;
                            }
                        }

                        if (!issuer.isNull ())
                        {
                            // human account id
                            if (!issuer.isString ())
                            {
                                error = string_expected (element_name, "issuer");
                                return false;
                            }

                            if (issuer.asString ().size () == 40)
                            {
                                uIssuer.SetHex (issuer.asString ());
                            }
                            else
                            {
                                RippleAddress a;

                                if (!a.setAccountID (issuer.asString ()))
                                {
                                    error = invalid_data (element_name, "issuer");
                                    return false;
                                }

                                uIssuer = a.getAccountID ();
                            }
                        }

                        p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency));
                    }

                    tail->addPath (p);
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_ACCOUNT:
        {
            if (! value.isString ())
            {
                error = bad_type (json_name, fieldName);
                return false;
            }

            std::string strValue = value.asString ();

            try
            {
                if (value.size () == 40) // 160-bit hex account value
                {
                    uint160 v;
                    v.SetHex (strValue);
                    data.push_back (new STAccount (field, v));
                }
                else
                {
                    // ripple address
                    RippleAddress a;

                    if (!a.setAccountID (strValue))
                    {
                        error = invalid_data (json_name, fieldName);
                        return false;
                    }

                    data.push_back (new STAccount (field, a.getAccountID ()));
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }
        }
        break;

        case STI_OBJECT:
        case STI_TRANSACTION:
        case STI_LEDGERENTRY:
        case STI_VALIDATION:
            if (! value.isObject ())
            {
                error = not_an_object (json_name, fieldName);
                return false;
            }

            if (depth > 64)
            {
                error = too_deep (json_name, fieldName);
                return false;
            }

            try
            {
                std::unique_ptr <STObject> sub_object_;
                bool const success (parse (json_name + "." + fieldName,
                    value, field, depth + 1, sub_object_));
                if (! success)
                    return false;
                data.push_back (sub_object_.release ());
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        case STI_ARRAY:
            if (! value.isArray ())
            {
                error = array_expected (json_name, fieldName);
                return false;
            }

            try
            {
                data.push_back (new STArray (field));
                STArray* tail = dynamic_cast<STArray*> (&data.back ());
                assert (tail);

                for (Json::UInt i = 0; value.isValidIndex (i); ++i)
                {
                    bool const isObject (value[i].isObject());
                    bool const singleKey (isObject
                        ? value [i].size() == 1
                        : true);

                    if (!isObject || !singleKey)
                    {
                        std::stringstream ss;
                        ss << json_name << "." << fieldName << "[" << i << "]";
                        error = singleton_expected (ss.str ());
                        return false;
                    }

                    // TODO: There doesn't seem to be a nice way to get just the
                    // first/only key in an object without copying all keys into
                    // a vector
                    std::string const objectName (value[i].getMemberNames()[0]);;
                    SField::ref       nameField (SField::getField(objectName));

                    if (nameField == sfInvalid)
                    {
                        error = unknown_field (json_name, objectName);
                        return false;
                    }

                    Json::Value const objectFields (value[i][objectName]);

                    std::unique_ptr <STObject> sub_object_;
                    {
                        std::stringstream ss;
                        ss << json_name << "." << fieldName <<
                            "[" << i << "]." << objectName;
                        bool const success (parse (ss.str (), objectFields,
                            nameField, depth + 1, sub_object_));
                        if (! success)
                            return false;
                    }
                    tail->push_back (*sub_object_);
                }
            }
            catch (...)
            {
                error = invalid_data (json_name, fieldName);
                return false;
            }

            break;

        default:
            error = bad_type (json_name, fieldName);
            return false;
        }
    }

    sub_object.reset (new STObject (*name, data));
    return true;
}
Пример #10
0
std::pair<bool, Json::Value>
ripplePathFind(RippleLineCache::pointer const& cache, 
  RippleAddress const& raSrc, RippleAddress const& raDst,
    STAmount const& saDstAmount, Ledger::pointer const& lpLedger, 
      Json::Value const& jvSrcCurrencies, 
        boost::optional<Json::Value> const& contextPaths, int const& level)
{
    FindPaths fp(
        cache,
        raSrc.getAccountID(),
        raDst.getAccountID(),
        saDstAmount,
        level,
        4); // max paths

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

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

        Currency uSrcCurrencyID;
        Account uSrcIssuerID;

        if (!jvSource.isObject())
            return std::make_pair(false, rpcError(rpcINVALID_PARAMS));

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

            return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED));
        }

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

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

        STPathSet spsComputed;
        if (contextPaths)
        {
            Json::Value pathSet = Json::objectValue;
            pathSet[jss::Paths] = contextPaths.get();
            STParsedJSONObject paths("pathSet", pathSet);
            if (paths.object.get() == nullptr)
                return std::make_pair(false, 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[jss::source_amount] = rc.actualAmountIn.getJson(0);
                jvEntry[jss::paths_canonical] = Json::arrayValue;
                jvEntry[jss::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);
            }
        }
    }

    return std::make_pair(true, jvArray);
}
Пример #11
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;
}