Exemple #1
0
Currency to_currency(std::string const& code)
{
    Currency currency;
    if (!to_currency(currency, code))
        currency = noCurrency();
    return currency;
}
Exemple #2
0
void GncPriceImport::settings (const CsvPriceImpSettings& settings)
{
    /* First apply file format as this may recreate the tokenizer */
    file_format (settings.m_file_format);
    /* Only then apply the other settings */
    m_settings = settings;
    from_commodity (m_settings.m_from_commodity);
    to_currency (m_settings.m_to_currency);
    encoding (m_settings.m_encoding);

    if (file_format() == GncImpFileFormat::CSV)
        separators (m_settings.m_separators);
    else if (file_format() == GncImpFileFormat::FIXED_WIDTH)
    {
        auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
        fwtok->columns (m_settings.m_column_widths);
    }
    try
    {
        tokenize(false);
    }
    catch (...)
    { };

    /* Tokenizing will clear column types, reset them here
     * based on the loaded settings.
     */
    std::copy_n (settings.m_column_types_price.begin(),
            std::min (m_settings.m_column_types_price.size(), settings.m_column_types_price.size()),
            m_settings.m_column_types_price.begin());
}
Exemple #3
0
IOU
Account::operator[](std::string const& s) const
{
    auto const currency = to_currency(s);
    assert(currency != noCurrency());
    return IOU(*this, currency);
}
Exemple #4
0
void
GncPriceImport::set_column_type_price (uint32_t position, GncPricePropType type, bool force)
{
    if (position >= m_settings.m_column_types_price.size())
        return;

    auto old_type = m_settings.m_column_types_price[position];
    if ((type == old_type) && !force)
        return; /* Nothing to do */

    // Column types should be unique, so remove any previous occurrence of the new type
    std::replace(m_settings.m_column_types_price.begin(), m_settings.m_column_types_price.end(),
            type, GncPricePropType::NONE);

    m_settings.m_column_types_price.at (position) = type;

    // If the user has set a 'commodity from' column, we can't have a commodity from selected
    if (type == GncPricePropType::FROM_COMMODITY)
        from_commodity (nullptr);

    // If the user has set a 'currency to' column, we can't have a currency to selected
    if (type == GncPricePropType::TO_CURRENCY)
        to_currency (nullptr);

    /* Update the preparsed data */
    for (auto parsed_lines_it = m_parsed_lines.begin();
            parsed_lines_it != m_parsed_lines.end();
            ++parsed_lines_it)
    {
        /* Reset date and currency formats for each price props object
         * to ensure column updates use the most recent one
         */
        std::get<PL_PREPRICE>(*parsed_lines_it)->set_date_format (m_settings.m_date_format);
        std::get<PL_PREPRICE>(*parsed_lines_it)->set_currency_format (m_settings.m_currency_format);

        uint32_t row = parsed_lines_it - m_parsed_lines.begin();

        /* If the column type actually changed, first reset the property
         * represented by the old column type
         */
        if (old_type != type)
        {
            auto old_col = std::get<PL_INPUT>(*parsed_lines_it).size(); // Deliberately out of bounds to trigger a reset!
            if ((old_type > GncPricePropType::NONE)
                    && (old_type <= GncPricePropType::PRICE_PROPS))
                update_price_props (row, old_col, old_type);
        }
        /* Then set the property represented by the new column type */
        if ((type > GncPricePropType::NONE)
                && (type <= GncPricePropType::PRICE_PROPS))
            update_price_props (row, position, type);

        /* Report errors if there are any */
        auto price_errors = std::get<PL_PREPRICE>(*parsed_lines_it)->errors();
        std::get<PL_ERROR>(*parsed_lines_it) =
                price_errors +
                (price_errors.empty() ? std::string() : "\n");
    }
}
Exemple #5
0
Json::Value doBookOffers (RPC::Context& context)
{
    // VFALCO TODO Here is a terrible place for this kind of business
    //             logic. It needs to be moved elsewhere and documented,
    //             and encapsulated into a function.
    if (getApp().getJobQueue ().getJobCountGE (jtCLIENT) > 200)
        return rpcError (rpcTOO_BUSY);

    Ledger::pointer lpLedger;
    Json::Value jvResult (
        RPC::lookupLedger (context.params, lpLedger, context.netOps));

    if (!lpLedger)
        return jvResult;

    if (!context.params.isMember (jss::taker_pays))
        return RPC::missing_field_error (jss::taker_pays);

    if (!context.params.isMember (jss::taker_gets))
        return RPC::missing_field_error (jss::taker_gets);

    if (!context.params[jss::taker_pays].isObject ())
        return RPC::object_field_error (jss::taker_pays);

    if (!context.params[jss::taker_gets].isObject ())
        return RPC::object_field_error (jss::taker_gets);

    Json::Value const& taker_pays (context.params[jss::taker_pays]);

    if (!taker_pays.isMember (jss::currency))
        return RPC::missing_field_error ("taker_pays.currency");

    if (! taker_pays [jss::currency].isString ())
        return RPC::expected_field_error ("taker_pays.currency", "string");

    Json::Value const& taker_gets = context.params[jss::taker_gets];

    if (! taker_gets.isMember (jss::currency))
        return RPC::missing_field_error ("taker_gets.currency");

    if (! taker_gets [jss::currency].isString ())
        return RPC::expected_field_error ("taker_gets.currency", "string");

    Currency pay_currency;

    if (!to_currency (pay_currency, taker_pays [jss::currency].asString ()))
    {
        WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
        return RPC::make_error (rpcSRC_CUR_MALFORMED,
            "Invalid field 'taker_pays.currency', bad currency.");
    }

    Currency get_currency;

    if (!to_currency (get_currency, taker_gets [jss::currency].asString ()))
    {
        WriteLog (lsINFO, RPCHandler) << "Bad taker_gets currency.";
        return RPC::make_error (rpcDST_AMT_MALFORMED,
            "Invalid field 'taker_gets.currency', bad currency.");
    }

    AccountID pay_issuer;

    if (taker_pays.isMember (jss::issuer))
    {
        if (! taker_pays [jss::issuer].isString())
            return RPC::expected_field_error ("taker_pays.issuer", "string");

        if (!to_issuer(
            pay_issuer, taker_pays [jss::issuer].asString ()))
            return RPC::make_error (rpcSRC_ISR_MALFORMED,
                "Invalid field 'taker_pays.issuer', bad issuer.");

        if (pay_issuer == noAccount ())
            return RPC::make_error (rpcSRC_ISR_MALFORMED,
                "Invalid field 'taker_pays.issuer', bad issuer account one.");
    }
    else
    {
        pay_issuer = xdvAccount ();
    }

    if (isXDV (pay_currency) && ! isXDV (pay_issuer))
        return RPC::make_error (
            rpcSRC_ISR_MALFORMED, "Unneeded field 'taker_pays.issuer' for "
            "XDV currency specification.");

    if (!isXDV (pay_currency) && isXDV (pay_issuer))
        return RPC::make_error (rpcSRC_ISR_MALFORMED,
            "Invalid field 'taker_pays.issuer', expected non-XDV issuer.");

    AccountID get_issuer;

    if (taker_gets.isMember (jss::issuer))
    {
        if (! taker_gets [jss::issuer].isString())
            return RPC::expected_field_error ("taker_gets.issuer", "string");

        if (! to_issuer (
            get_issuer, taker_gets [jss::issuer].asString ()))
            return RPC::make_error (rpcDST_ISR_MALFORMED,
                "Invalid field 'taker_gets.issuer', bad issuer.");

        if (get_issuer == noAccount ())
            return RPC::make_error (rpcDST_ISR_MALFORMED,
                "Invalid field 'taker_gets.issuer', bad issuer account one.");
    }
    else
    {
        get_issuer = xdvAccount ();
    }


    if (isXDV (get_currency) && ! isXDV (get_issuer))
        return RPC::make_error (rpcDST_ISR_MALFORMED,
            "Unneeded field 'taker_gets.issuer' for "
                               "XDV currency specification.");

    if (!isXDV (get_currency) && isXDV (get_issuer))
        return RPC::make_error (rpcDST_ISR_MALFORMED,
            "Invalid field 'taker_gets.issuer', expected non-XDV issuer.");

    DivvyAddress raTakerID;

    if (context.params.isMember (jss::taker))
    {
        if (! context.params [jss::taker].isString ())
            return RPC::expected_field_error (jss::taker, "string");

        if (! raTakerID.setAccountID (context.params [jss::taker].asString ()))
            return RPC::invalid_field_error (jss::taker);
    }
    else
    {
        raTakerID.setAccountID (noAccount());
    }

    if (pay_currency == get_currency && pay_issuer == get_issuer)
    {
        WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays.";
        return RPC::make_error (rpcBAD_MARKET);
    }

    unsigned int iLimit;
    if (context.params.isMember (jss::limit))
    {
        auto const& jvLimit (context.params[jss::limit]);

        if (! jvLimit.isIntegral ())
            return RPC::expected_field_error (jss::limit, "unsigned integer");

        iLimit = jvLimit.isUInt () ? jvLimit.asUInt () :
            std::max (0, jvLimit.asInt ());
    }
    else
    {
        iLimit = 0;
    }

    bool const bProof (context.params.isMember (jss::proof));

    Json::Value const jvMarker (context.params.isMember (jss::marker)
        ? context.params[jss::marker]
        : Json::Value (Json::nullValue));

    context.netOps.getBookPage (
        context.role == Role::ADMIN,
        lpLedger,
        {{pay_currency, pay_issuer}, {get_currency, get_issuer}},
        raTakerID.getAccountID (), bProof, iLimit, jvMarker, jvResult);

    context.loadType = Resource::feeMediumBurdenRPC;

    return jvResult;
}
Exemple #6
0
Json::Value doSubscribe (RPC::Context& context)
{
    InfoSub::pointer ispSub;
    Json::Value jvResult (Json::objectValue);

    if (!context.infoSub && !context.params.isMember (jss::url))
    {
        // Must be a JSON-RPC call.
        WriteLog (lsINFO, RPCHandler)
            << "doSubscribe: RPC subscribe requires a url";

        return rpcError (rpcINVALID_PARAMS);
    }

    if (context.params.isMember (jss::url))
    {
        if (context.role != Role::ADMIN)
            return rpcError (rpcNO_PERMISSION);

        std::string strUrl      = context.params[jss::url].asString ();
        std::string strUsername = context.params.isMember (jss::url_username) ?
                context.params[jss::url_username].asString () : "";
        std::string strPassword = context.params.isMember (jss::url_password) ?
                context.params[jss::url_password].asString () : "";

        // DEPRECATED
        if (context.params.isMember (jss::username))
            strUsername = context.params[jss::username].asString ();

        // DEPRECATED
        if (context.params.isMember (jss::password))
            strPassword = context.params[jss::password].asString ();

        ispSub  = context.netOps.findRpcSub (strUrl);

        if (!ispSub)
        {
            WriteLog (lsDEBUG, RPCHandler)
                << "doSubscribe: building: " << strUrl;

            RPCSub::pointer rspSub = RPCSub::New (getApp ().getOPs (),
                getApp ().getIOService (), getApp ().getJobQueue (),
                    strUrl, strUsername, strPassword);
            ispSub  = context.netOps.addRpcSub (
                strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub));
        }
        else
        {
            WriteLog (lsTRACE, RPCHandler)
                << "doSubscribe: reusing: " << strUrl;

            if (context.params.isMember (jss::username))
                dynamic_cast<RPCSub*> (&*ispSub)->setUsername (strUsername);

            if (context.params.isMember (jss::password))
                dynamic_cast<RPCSub*> (&*ispSub)->setPassword (strPassword);
        }
    }
    else
    {
        ispSub  = context.infoSub;
    }

    if (!context.params.isMember (jss::streams))
    {
    }
    else if (!context.params[jss::streams].isArray ())
    {
        WriteLog (lsINFO, RPCHandler)
            << "doSubscribe: streams requires an array.";

        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        for (auto& it: context.params[jss::streams])
        {
            if (it.isString ())
            {
                std::string streamName = it.asString ();

                if (streamName == "server")
                {
                    context.netOps.subServer (ispSub, jvResult,
                        context.role == Role::ADMIN);
                }
                else if (streamName == "ledger")
                {
                    context.netOps.subLedger (ispSub, jvResult);
                }
                else if (streamName == "transactions")
                {
                    context.netOps.subTransactions (ispSub);
                }
                else if (streamName == "transactions_proposed"
                         || streamName == "rt_transactions") // DEPRECATED
                {
                    context.netOps.subRTTransactions (ispSub);
                }
                else
                {
                    jvResult[jss::error]   = "unknownStream";
                }
            }
            else
            {
                jvResult[jss::error]   = "malformedStream";
            }
        }
    }

    auto strAccountsProposed =
               context.params.isMember (jss::accounts_proposed)
               ? jss::accounts_proposed : jss::rt_accounts;  // DEPRECATED

    if (!context.params.isMember (strAccountsProposed))
    {
    }
    else if (!context.params[strAccountsProposed].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        auto ids  = RPC::parseAccountIds (context.params[strAccountsProposed]);

        if (ids.empty ())
            jvResult[jss::error] = "malformedAccount";
        else
            context.netOps.subAccount (ispSub, ids, true);
    }

    if (!context.params.isMember (jss::accounts))
    {
    }
    else if (!context.params[jss::accounts].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        auto ids  = RPC::parseAccountIds (context.params[jss::accounts]);

        if (ids.empty ())
        {
            jvResult[jss::error]   = "malformedAccount";
        }
        else
        {
            context.netOps.subAccount (ispSub, ids, false);
            WriteLog (lsDEBUG, RPCHandler)
                << "doSubscribe: accounts: " << ids.size ();
        }
    }

    if (!context.params.isMember (jss::books))
    {
    }
    else if (!context.params[jss::books].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        for (auto& j: context.params[jss::books])
        {
            if (!j.isObject ()
                    || !j.isMember (jss::taker_pays)
                    || !j.isMember (jss::taker_gets)
                    || !j[jss::taker_pays].isObject ()
                    || !j[jss::taker_gets].isObject ())
                return rpcError (rpcINVALID_PARAMS);

            Book book;
            bool bBoth =
                    (j.isMember (jss::both) && j[jss::both].asBool ()) ||
                    (j.isMember (jss::both_sides) && j[jss::both_sides].asBool ());
            bool bSnapshot =
                    (j.isMember (jss::snapshot) && j[jss::snapshot].asBool ()) ||
                    (j.isMember (jss::state_now) && j[jss::state_now].asBool ());
            // TODO(tom): both_sides and state_now are apparently deprecated...
            // where is this documented?

            Json::Value taker_pays = j[jss::taker_pays];
            Json::Value taker_gets = j[jss::taker_gets];

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

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_pays.isMember (jss::issuer))
                      && (!taker_pays[jss::issuer].isString ()
                          || !to_issuer (book.in.account,
                                         taker_pays[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.in.currency != !book.in.account)
                     || noAccount() == book.in.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";

                return rpcError (rpcSRC_ISR_MALFORMED);
            }

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

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_gets.isMember (jss::issuer))
                      && (!taker_gets[jss::issuer].isString ()
                          || !to_issuer (book.out.account,
                                         taker_gets[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.out.currency != !book.out.account)
                     || noAccount() == book.out.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";

                return rpcError (rpcDST_ISR_MALFORMED);
            }

            if (book.in.currency == book.out.currency
                    && book.in.account == book.out.account)
            {
                WriteLog (lsINFO, RPCHandler)
                    << "taker_gets same as taker_pays.";
                return rpcError (rpcBAD_MARKET);
            }

            RippleAddress   raTakerID;

            if (!j.isMember (jss::taker))
                raTakerID.setAccountID (noAccount());
            else if (!raTakerID.setAccountID (j[jss::taker].asString ()))
                return rpcError (rpcBAD_ISSUER);

            if (!isConsistent (book))
            {
                WriteLog (lsWARNING, RPCHandler) << "Bad market: " << book;
                return rpcError (rpcBAD_MARKET);
            }

            context.netOps.subBook (ispSub, book);

            if (bBoth)
                context.netOps.subBook (ispSub, reversed (book));

            if (bSnapshot)
            {
                context.loadType = Resource::feeMediumBurdenRPC;
                auto lpLedger = getApp().getLedgerMaster ().
                        getPublishedLedger ();
                if (lpLedger)
                {
                    const Json::Value jvMarker = Json::Value (Json::nullValue);
                    Json::Value jvOffers (Json::objectValue);

                    auto add = [&](Json::StaticString field)
                    {
                        context.netOps.getBookPage (context.role == Role::ADMIN,
                            lpLedger, field == jss::asks ? reversed (book) : book,
                            raTakerID.getAccountID(), false, 0, jvMarker,
                            jvOffers);

                        if (jvResult.isMember (field))
                        {
                            Json::Value& results (jvResult[field]);
                            for (auto const& e : jvOffers[jss::offers])
                                results.append (e);
                        }
                        else
                        {
                            jvResult[field] = jvOffers[jss::offers];
                        }
                    };

                    if (bBoth)
                    {
                        add (jss::bids);
                        add (jss::asks);
                    }
                    else
                    {
                        add (jss::offers);
                    }
                }
            }
        }
    }

    return jvResult;
}
Exemple #7
0
Json::Value doSubscribe (RPC::Context& context)
{
    auto lock = getApp().masterLock();

    // FIXME: This needs to release the master lock immediately
    // Subscriptions need to be protected by their own lock

    InfoSub::pointer ispSub;
    Json::Value jvResult (Json::objectValue);
    std::uint32_t uLedgerIndex = context.params_.isMember (jss::ledger_index)
            && context.params_[jss::ledger_index].isNumeric ()
            ? context.params_[jss::ledger_index].asUInt ()
            : 0;

    if (!context.infoSub_ && !context.params_.isMember ("url"))
    {
        // Must be a JSON-RPC call.
        WriteLog (lsINFO, RPCHandler)
            << "doSubscribe: RPC subscribe requires a url";

        return rpcError (rpcINVALID_PARAMS);
    }

    if (context.params_.isMember ("url"))
    {
        if (context.role_ != Config::ADMIN)
            return rpcError (rpcNO_PERMISSION);

        std::string strUrl      = context.params_["url"].asString ();
        std::string strUsername = context.params_.isMember ("url_username") ?
                context.params_["url_username"].asString () : "";
        std::string strPassword = context.params_.isMember ("url_password") ?
                context.params_["url_password"].asString () : "";

        // DEPRECATED
        if (context.params_.isMember ("username"))
            strUsername = context.params_["username"].asString ();

        // DEPRECATED
        if (context.params_.isMember ("password"))
            strPassword = context.params_["password"].asString ();

        ispSub  = context.netOps_.findRpcSub (strUrl);

        if (!ispSub)
        {
            WriteLog (lsDEBUG, RPCHandler)
                << "doSubscribe: building: " << strUrl;

            RPCSub::pointer rspSub = RPCSub::New (getApp ().getOPs (),
                getApp ().getIOService (), getApp ().getJobQueue (),
                    strUrl, strUsername, strPassword);
            ispSub  = context.netOps_.addRpcSub (
                strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub));
        }
        else
        {
            WriteLog (lsTRACE, RPCHandler)
                << "doSubscribe: reusing: " << strUrl;

            if (context.params_.isMember ("username"))
                dynamic_cast<RPCSub*> (&*ispSub)->setUsername (strUsername);

            if (context.params_.isMember ("password"))
                dynamic_cast<RPCSub*> (&*ispSub)->setPassword (strPassword);
        }
    }
    else
    {
        ispSub  = context.infoSub_;
    }

    if (!context.params_.isMember ("streams"))
    {
    }
    else if (!context.params_["streams"].isArray ())
    {
        WriteLog (lsINFO, RPCHandler)
            << "doSubscribe: streams requires an array.";

        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        for (auto& it: context.params_["streams"])
        {
            if (it.isString ())
            {
                std::string streamName = it.asString ();

                if (streamName == "server")
                {
                    context.netOps_.subServer (ispSub, jvResult,
                        context.role_ == Config::ADMIN);
                }
                else if (streamName == "ledger")
                {
                    context.netOps_.subLedger (ispSub, jvResult);
                }
                else if (streamName == "transactions")
                {
                    context.netOps_.subTransactions (ispSub);
                }
                else if (streamName == "transactions_proposed"
                         || streamName == "rt_transactions") // DEPRECATED
                {
                    context.netOps_.subRTTransactions (ispSub);
                }
                else
                {
                    jvResult[jss::error]   = "unknownStream";
                }
            }
            else
            {
                jvResult[jss::error]   = "malformedStream";
            }
        }
    }

    std::string strAccountsProposed =
               context.params_.isMember ("accounts_proposed")
               ? "accounts_proposed" : "rt_accounts";  // DEPRECATED

    if (!context.params_.isMember (strAccountsProposed))
    {
    }
    else if (!context.params_[strAccountsProposed].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        auto ids  = RPC::parseAccountIds (context.params_[strAccountsProposed]);

        if (ids.empty ())
            jvResult[jss::error] = "malformedAccount";
        else
            context.netOps_.subAccount (ispSub, ids, uLedgerIndex, true);
    }

    if (!context.params_.isMember ("accounts"))
    {
    }
    else if (!context.params_["accounts"].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        auto ids  = RPC::parseAccountIds (context.params_["accounts"]);

        if (ids.empty ())
        {
            jvResult[jss::error]   = "malformedAccount";
        }
        else
        {
            context.netOps_.subAccount (ispSub, ids, uLedgerIndex, false);
            WriteLog (lsDEBUG, RPCHandler)
                << "doSubscribe: accounts: " << ids.size ();
        }
    }

    bool bHaveMasterLock = true;
    if (!context.params_.isMember ("books"))
    {
    }
    else if (!context.params_["books"].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        for (auto& j: context.params_["books"])
        {
            if (!j.isObject ()
                    || !j.isMember (jss::taker_pays)
                    || !j.isMember (jss::taker_gets)
                    || !j[jss::taker_pays].isObject ()
                    || !j[jss::taker_gets].isObject ())
                return rpcError (rpcINVALID_PARAMS);

            Book book;
            bool bBoth =
                    (j.isMember ("both") && j["both"].asBool ()) ||
                    (j.isMember ("both_sides") && j["both_sides"].asBool ());
            bool bSnapshot =
                    (j.isMember ("snapshot") && j["snapshot"].asBool ()) ||
                    (j.isMember ("state_now") && j["state_now"].asBool ());
            // TODO(tom): both_sides and state_now are apparently deprecated...
            // where is this documented?

            Json::Value taker_pays = j[jss::taker_pays];
            Json::Value taker_gets = j[jss::taker_gets];

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

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_pays.isMember (jss::issuer))
                      && (!taker_pays[jss::issuer].isString ()
                          || !to_issuer (book.in.account,
                                         taker_pays[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.in.currency != !book.in.account)
                     || noAccount() == book.in.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";

                return rpcError (rpcSRC_ISR_MALFORMED);
            }

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

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_gets.isMember (jss::issuer))
                      && (!taker_gets[jss::issuer].isString ()
                          || !to_issuer (book.out.account,
                                         taker_gets[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.out.currency != !book.out.account)
                     || noAccount() == book.out.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";

                return rpcError (rpcDST_ISR_MALFORMED);
            }

            if (book.in.currency == book.out.currency
                    && book.in.account == book.out.account)
            {
                WriteLog (lsINFO, RPCHandler)
                    << "taker_gets same as taker_pays.";
                return rpcError (rpcBAD_MARKET);
            }

            RippleAddress   raTakerID;

            if (!j.isMember ("taker"))
                raTakerID.setAccountID (noAccount());
            else if (!raTakerID.setAccountID (j["taker"].asString ()))
                return rpcError (rpcBAD_ISSUER);

            if (!isConsistent (book) || !isConsistentVBC(book))
            {
                WriteLog (lsWARNING, RPCHandler) << "Bad market: " << book;
                return rpcError (rpcBAD_MARKET);
            }

            context.netOps_.subBook (ispSub, book);

            if (bBoth)
                context.netOps_.subBook (ispSub, book);

            if (bSnapshot)
            {
                if (bHaveMasterLock)
                {
                    lock->unlock ();
                    bHaveMasterLock = false;
                }

                context.loadType_ = Resource::feeMediumBurdenRPC;
                auto lpLedger = getApp().getLedgerMaster ().
                        getPublishedLedger ();
                if (lpLedger)
                {
                    const Json::Value jvMarker = Json::Value (Json::nullValue);

                    if (bBoth)
                    {
                        Json::Value jvBids (Json::objectValue);
                        Json::Value jvAsks (Json::objectValue);

                        context.netOps_.getBookPage (
                            lpLedger, book, raTakerID.getAccountID (), false, 0,
                            jvMarker, jvBids);

                        if (jvBids.isMember (jss::offers))
                            jvResult[jss::bids] = jvBids[jss::offers];

                        context.netOps_.getBookPage (
                            lpLedger, book, raTakerID.getAccountID (),
                            false, 0, jvMarker, jvAsks);

                        if (jvAsks.isMember (jss::offers))
                            jvResult[jss::asks] = jvAsks[jss::offers];
                    }
                    else
                    {
                        context.netOps_.getBookPage (
                            lpLedger, book, raTakerID.getAccountID (), false, 0,
                            jvMarker, jvResult);
                    }
                }
            }
        }
    }

    return jvResult;
}
Exemple #8
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;
}
int PathRequest::parseJson (const Json::Value& jvParams, bool complete)
{
    int ret = PFR_PJ_NOCHANGE;

    if (jvParams.isMember ("source_account"))
    {
        if (!raSrcAccount.setAccountID (jvParams["source_account"].asString ()))
        {
            jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcSRC_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember ("destination_account"))
    {
        if (!raDstAccount.setAccountID (jvParams["destination_account"].asString ()))
        {
            jvStatus = rpcError (rpcDST_ACT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcDST_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember ("destination_amount"))
    {
        if (!saDstAmount.bSetJson (jvParams["destination_amount"]) ||
                (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) ||
                (saDstAmount.getCurrency () == badCurrency()) ||
                saDstAmount <= zero)
        {
            jvStatus = rpcError (rpcDST_AMT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcDST_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember ("source_currencies"))
    {
        const Json::Value& jvSrcCur = jvParams["source_currencies"];

        if (!jvSrcCur.isArray ())
        {
            jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
            return PFR_PJ_INVALID;
        }

        sciSourceCurrencies.clear ();

        for (unsigned i = 0; i < jvSrcCur.size (); ++i)
        {
            const Json::Value& jvCur = jvSrcCur[i];
            Currency uCur;
            Account uIss;

            if (!jvCur.isObject() || !jvCur.isMember ("currency") ||
                !to_currency (uCur, jvCur["currency"].asString ()))
            {
                jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
                return PFR_PJ_INVALID;
            }

            if (jvCur.isMember ("issuer") &&
                !to_issuer (uIss, jvCur["issuer"].asString ()))
            {
                jvStatus = rpcError (rpcSRC_ISR_MALFORMED);
            }

            if (uCur.isZero () && uIss.isNonZero ())
            {
                jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
                return PFR_PJ_INVALID;
            }

            sciSourceCurrencies.insert ({uCur, uIss});
        }
    }

    if (jvParams.isMember ("id"))
        jvId = jvParams["id"];

    return ret;
}
// {
//   account: [<account>|<account_public_key>]
//   peer: [<account>|<account_public_key>]
//   currency: <currency>
//   ledger_hash : <ledger>
//   ledger_index : <ledger_index>
// }
json::value doaccountasset (rpc::context& context)
{
    auto const& params(context.params);
    if (!params.ismember(jss::account))
        return rpc::missing_field_error("account");
    if (!params.ismember(jss::peer))
        return rpc::missing_field_error("peer");
    if (!params.ismember(jss::currency))
        return rpc::missing_field_error("currency");

    ledger::pointer ledger;
    json::value result(rpc::lookupledger(params, ledger, context.netops));
    if (!ledger)
        return result;

    std::string strident(params[jss::account].asstring());
    bool bindex(params.ismember(jss::account_index));
    int iindex(bindex ? params[jss::account_index].asuint() : 0);
    rippleaddress rippleaddress;

    json::value const jv(rpc::accountfromstring(ledger, rippleaddress, bindex,
                                                strident, iindex, false, context.netops));
    if (!jv.empty()) {
        for (json::value::const_iterator it(jv.begin()); it != jv.end(); ++it)
            result[it.membername()] = it.key();

        return result;
    }

    if (!ledger->hasaccount(rippleaddress))
        return rpcerror(rpcact_not_found);

    std::string strpeer(params.ismember(jss::peer) ? params[jss::peer].asstring() : "");
    bool bpeerindex(params.ismember(jss::peer_index));
    int ipeerindex(bindex ? params[jss::peer_index].asuint() : 0);
    rippleaddress rippleaddresspeer;

    json::value const jvpeer(rpc::accountfromstring(ledger, rippleaddresspeer, bpeerindex,
                                                    strpeer, ipeerindex, false, context.netops));
    if (!jvpeer.empty()) {
        return result;
    }

    if (!ledger->hasaccount(rippleaddresspeer))
        return rpcerror(rpcact_not_found);

    currency ucurrency;
    if (!to_currency(ucurrency, params[jss::currency].asstring()))
        return rpc::make_error(rpcsrc_cur_malformed, "invalid field 'currency', bad currency.");

    account const& raaccount(rippleaddress.getaccountid());
    account const& rapeeraccount(rippleaddresspeer.getaccountid());

    uint256 unodeindex = getripplestateindex(raaccount, rapeeraccount, ucurrency);
    auto slenode = context.netops.getslei(ledger, unodeindex);
    auto const line(ripplestate::makeitem(raaccount, slenode));

    if (line == nullptr ||
        raaccount != line->getaccountid() ||
        rapeeraccount != line->getaccountidpeer())
        return result;

    json::value jsonlines(json::arrayvalue);
    addline(jsonlines, *line, ledger);
    result[jss::lines] = jsonlines[0u];
    
    json::value& jsonassetstates(result[jss::states] = json::arrayvalue);
    
    // get asset_states for currency asset.
    if (assetcurrency() == line->getbalance().getcurrency()) {
        auto lpledger = std::make_shared<ledger> (std::ref (*ledger), false);
        ledgerentryset les(lpledger, tapnone);
        auto sleripplestate = les.entrycache(ltripple_state, getripplestateindex(raaccount, rapeeraccount, assetcurrency()));
        les.assetrelease(raaccount, rapeeraccount, assetcurrency(), sleripplestate);

        uint256 baseindex = getassetstateindex(line->getaccountid(), line->getaccountidpeer(), assetcurrency());
        uint256 assetstateindex = getqualityindex(baseindex);
        uint256 assetstateend = getqualitynext(assetstateindex);

        for (;;) {
            auto const& sle = les.entrycache(ltasset_state, assetstateindex);
            if (sle) {
                stamount amount = sle->getfieldamount(sfamount);
                stamount released = sle->getfieldamount(sfdeliveredamount);

                if (sle->getfieldaccount160(sfaccount) == line->getaccountidpeer()) {
                    amount.negate();
                    released.negate();
                }

                auto reserved = released ? amount - released : amount;

                json::value& state(jsonassetstates.append(json::objectvalue));
                state[jss::date] = static_cast<json::uint>(getquality(assetstateindex));
                state[jss::amount] = amount.gettext();
                state[jss::reserve] = reserved.gettext();
            }

            auto const nextassetstate(
                les.getnextledgerindex(assetstateindex, assetstateend));

            if (nextassetstate.iszero())
                break;

            assetstateindex = nextassetstate;
        }
    }

    context.loadtype = resource::feemediumburdenrpc;
    return result;
}
Exemple #11
0
// FIXME: This leaks RPCSub objects for JSON-RPC.  Shouldn't matter for anyone
// sane.
Json::Value doUnsubscribe (RPC::Context& context)
{

    InfoSub::pointer ispSub;
    Json::Value jvResult (Json::objectValue);

    if (!context.infoSub && !context.params.isMember (jss::url))
    {
        // Must be a JSON-RPC call.
        return rpcError (rpcINVALID_PARAMS);
    }

    if (context.params.isMember (jss::url))
    {
        if (context.role != Role::ADMIN)
            return rpcError (rpcNO_PERMISSION);

        std::string strUrl  = context.params[jss::url].asString ();
        ispSub  = context.netOps.findRpcSub (strUrl);

        if (!ispSub)
            return jvResult;
    }
    else
    {
        ispSub  = context.infoSub;
    }

    if (context.params.isMember (jss::streams))
    {
        for (auto& it: context.params[jss::streams])
        {
            if (it.isString ())
            {
                std::string streamName = it.asString ();

                if (streamName == "server")
                    context.netOps.unsubServer (ispSub->getSeq ());

                else if (streamName == "ledger")
                    context.netOps.unsubLedger (ispSub->getSeq ());

                else if (streamName == "transactions")
                    context.netOps.unsubTransactions (ispSub->getSeq ());

                else if (streamName == "transactions_proposed"
                         || streamName == "rt_transactions") // DEPRECATED
                    context.netOps.unsubRTTransactions (ispSub->getSeq ());

                else
                    jvResult[jss::error] = "Unknown stream: " + streamName;
            }
            else
            {
                jvResult[jss::error]   = "malformedSteam";
            }
        }
    }

    if (context.params.isMember (jss::accounts_proposed)
        || context.params.isMember (jss::rt_accounts))
    {
        auto accounts  = RPC::parseAccountIds (
                    context.params.isMember (jss::accounts_proposed)
                    ? context.params[jss::accounts_proposed]
                    : context.params[jss::rt_accounts]); // DEPRECATED

        if (accounts.empty ())
            jvResult[jss::error]   = "malformedAccount";
        else
            context.netOps.unsubAccount (ispSub, accounts, true);
    }

    if (context.params.isMember (jss::accounts))
    {
        auto accounts  = RPC::parseAccountIds (context.params[jss::accounts]);

        if (accounts.empty ())
            jvResult[jss::error]   = "malformedAccount";
        else
            context.netOps.unsubAccount (ispSub, accounts, false);
    }

    if (!context.params.isMember (jss::books))
    {
    }
    else if (!context.params[jss::books].isArray ())
    {
        return rpcError (rpcINVALID_PARAMS);
    }
    else
    {
        for (auto& jv: context.params[jss::books])
        {
            if (!jv.isObject ()
                    || !jv.isMember (jss::taker_pays)
                    || !jv.isMember (jss::taker_gets)
                    || !jv[jss::taker_pays].isObject ()
                    || !jv[jss::taker_gets].isObject ())
                return rpcError (rpcINVALID_PARAMS);

            bool bBoth = (jv.isMember (jss::both) && jv[jss::both].asBool ()) ||
                    (jv.isMember (jss::both_sides) && jv[jss::both_sides].asBool ());
            // both_sides is deprecated.

            Json::Value taker_pays = jv[jss::taker_pays];
            Json::Value taker_gets = jv[jss::taker_gets];

            Book book;

            // Parse mandatory currency.
            if (!taker_pays.isMember (jss::currency)
                || !to_currency (
                    book.in.currency, taker_pays[jss::currency].asString ()))
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_pays.isMember (jss::issuer))
                      && (!taker_pays[jss::issuer].isString ()
                          || !to_issuer (
                              book.in.account, taker_pays[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || !isConsistent (book.in)
                     || noAccount() == book.in.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";

                return rpcError (rpcSRC_ISR_MALFORMED);
            }

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

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_gets.isMember (jss::issuer))
                      && (!taker_gets[jss::issuer].isString ()
                          || !to_issuer (book.out.account,
                                         taker_gets[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || !isConsistent (book.out)
                     || noAccount() == book.out.account)
            {
                WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";

                return rpcError (rpcDST_ISR_MALFORMED);
            }

            if (book.in == book.out)
            {
                WriteLog (lsINFO, RPCHandler)
                    << "taker_gets same as taker_pays.";
                return rpcError (rpcBAD_MARKET);
            }

            context.netOps.unsubBook (ispSub->getSeq (), book);

            if (bBoth)
                context.netOps.unsubBook (ispSub->getSeq (), book);
        }
    }

    return jvResult;
}
Json::Value doSubscribe (RPC::Context& context)
{
    InfoSub::pointer ispSub;
    Json::Value jvResult (Json::objectValue);

    if (! context.infoSub && ! context.params.isMember(jss::url))
    {
        // Must be a JSON-RPC call.
        JLOG(context.j.info) << "doSubscribe: RPC subscribe requires a url";
        return rpcError (rpcINVALID_PARAMS);
    }

    if (context.params.isMember(jss::url))
    {
        if (context.role != Role::ADMIN)
            return rpcError(rpcNO_PERMISSION);

        std::string strUrl = context.params[jss::url].asString ();
        std::string strUsername = context.params.isMember (jss::url_username) ?
                context.params[jss::url_username].asString () : "";
        std::string strPassword = context.params.isMember (jss::url_password) ?
                context.params[jss::url_password].asString () : "";

        // DEPRECATED
        if (context.params.isMember (jss::username))
            strUsername = context.params[jss::username].asString ();

        // DEPRECATED
        if (context.params.isMember (jss::password))
            strPassword = context.params[jss::password].asString ();

        ispSub = context.netOps.findRpcSub(strUrl);
        if (! ispSub)
        {
            JLOG (context.j.debug)
                << "doSubscribe: building: " << strUrl;

            auto rspSub = make_RPCSub (context.app.getOPs (),
                context.app.getIOService (), context.app.getJobQueue (),
                    strUrl, strUsername, strPassword, context.app.logs ());
            ispSub  = context.netOps.addRpcSub (
                strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub));
        }
        else
        {
            JLOG (context.j.trace)
                << "doSubscribe: reusing: " << strUrl;

            if (auto rpcSub = std::dynamic_pointer_cast<RPCSub> (ispSub))
            {
                // Why do we need to check isMember against jss::username and
                // jss::password here instead of just setting the username and
                // the password? What about url_username and url_password?
                if (context.params.isMember (jss::username))
                    rpcSub->setUsername (strUsername);

                if (context.params.isMember (jss::password))
                    rpcSub->setPassword (strPassword);
            }
        }
    }
    else
    {
        ispSub  = context.infoSub;
    }

    if (context.params.isMember (jss::streams))
    {
        if (! context.params[jss::streams].isArray ())
        {
            JLOG (context.j.info)
                << "doSubscribe: streams requires an array.";
            return rpcError (rpcINVALID_PARAMS);
        }

        for (auto const& it: context.params[jss::streams])
        {
            if (! it.isString())
                return rpcError(rpcSTREAM_MALFORMED);

            std::string streamName = it.asString ();
            if (streamName == "server")
            {
                context.netOps.subServer (ispSub, jvResult,
                    context.role == Role::ADMIN);
            }
            else if (streamName == "ledger")
            {
                context.netOps.subLedger (ispSub, jvResult);
            }
            else if (streamName == "transactions")
            {
                context.netOps.subTransactions (ispSub);
            }
            else if (streamName == "transactions_proposed" ||
                streamName == "rt_transactions") // DEPRECATED
            {
                context.netOps.subRTTransactions (ispSub);
            }
            else if (streamName == "validations")
            {
                context.netOps.subValidations (ispSub);
            }
            else if (streamName == "peer_status")
            {
                if (context.role != Role::ADMIN)
                    return rpcError(rpcNO_PERMISSION);
                context.netOps.subPeerStatus (ispSub);
            }
            else
            {
                return rpcError(rpcSTREAM_MALFORMED);
            }
        }
    }

    auto accountsProposed = context.params.isMember(jss::accounts_proposed)
        ? jss::accounts_proposed : jss::rt_accounts;  // DEPRECATED
    if (context.params.isMember(accountsProposed))
    {
        if (! context.params[accountsProposed].isArray())
            return rpcError(rpcINVALID_PARAMS);

        auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
        if (ids.empty())
            return rpcError(rpcACT_MALFORMED);
        context.netOps.subAccount(ispSub, ids, true);
    }

    if (context.params.isMember(jss::accounts))
    {
        if (! context.params[jss::accounts].isArray())
            return rpcError(rpcINVALID_PARAMS);

        auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
        if (ids.empty())
            return rpcError(rpcACT_MALFORMED);
        context.netOps.subAccount(ispSub, ids, false);
        JLOG(context.j.debug) << "doSubscribe: accounts: " << ids.size();
    }

    if (context.params.isMember(jss::books))
    {
        if (! context.params[jss::books].isArray())
            return rpcError (rpcINVALID_PARAMS);

        for (auto& j: context.params[jss::books])
        {
            if (!j.isObject ()
                    || !j.isMember (jss::taker_pays)
                    || !j.isMember (jss::taker_gets)
                    || !j[jss::taker_pays].isObject ()
                    || !j[jss::taker_gets].isObject ())
                return rpcError (rpcINVALID_PARAMS);

            Book book;
            bool bBoth =
                    (j.isMember (jss::both) && j[jss::both].asBool ()) ||
                    (j.isMember (jss::both_sides) && j[jss::both_sides].asBool ());
            bool bSnapshot =
                    (j.isMember (jss::snapshot) && j[jss::snapshot].asBool ()) ||
                    (j.isMember (jss::state_now) && j[jss::state_now].asBool ());
            // TODO(tom): both_sides and state_now are apparently deprecated...
            // where is this documented?

            Json::Value taker_pays = j[jss::taker_pays];
            Json::Value taker_gets = j[jss::taker_gets];

            // Parse mandatory currency.
            if (!taker_pays.isMember (jss::currency)
                    || !to_currency (book.in.currency,
                                     taker_pays[jss::currency].asString ()))
            {
                JLOG (context.j.info) << "Bad taker_pays currency.";

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_pays.isMember (jss::issuer))
                      && (!taker_pays[jss::issuer].isString ()
                          || !to_issuer (book.in.account,
                                         taker_pays[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.in.currency != !book.in.account)
                     || noAccount() == book.in.account)
            {
                JLOG (context.j.info) << "Bad taker_pays issuer.";

                return rpcError (rpcSRC_ISR_MALFORMED);
            }

            // Parse mandatory currency.
            if (!taker_gets.isMember (jss::currency)
                    || !to_currency (book.out.currency,
                                     taker_gets[jss::currency].asString ()))
            {
                JLOG (context.j.info) << "Bad taker_pays currency.";

                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_gets.isMember (jss::issuer))
                      && (!taker_gets[jss::issuer].isString ()
                          || !to_issuer (book.out.account,
                                         taker_gets[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || (!book.out.currency != !book.out.account)
                     || noAccount() == book.out.account)
            {
                JLOG (context.j.info) << "Bad taker_gets issuer.";

                return rpcError (rpcDST_ISR_MALFORMED);
            }

            if (book.in.currency == book.out.currency
                    && book.in.account == book.out.account)
            {
                JLOG (context.j.info)
                    << "taker_gets same as taker_pays.";
                return rpcError (rpcBAD_MARKET);
            }

            boost::optional<AccountID> takerID;

            if (j.isMember (jss::taker))
            {
                takerID = parseBase58<AccountID>(
                    j[jss::taker].asString());
                if (! takerID)
                    return rpcError (rpcBAD_ISSUER);
            }

            if (!isConsistent (book))
            {
                JLOG (context.j.warning) << "Bad market: " << book;
                return rpcError (rpcBAD_MARKET);
            }

            context.netOps.subBook (ispSub, book);

            if (bBoth)
                context.netOps.subBook (ispSub, book);

            if (bSnapshot)
            {
                context.loadType = Resource::feeMediumBurdenRPC;
                std::shared_ptr<ReadView const> lpLedger
                        = context.app.getLedgerMaster().getPublishedLedger();
                if (lpLedger)
                {
                    const Json::Value jvMarker = Json::Value (Json::nullValue);
                    Json::Value jvOffers (Json::objectValue);

                    auto add = [&](Json::StaticString field)
                    {
                        context.netOps.getBookPage (isUnlimited (context.role),
                            lpLedger, field == jss::asks ? reversed (book) : book,
                            takerID ? *takerID : noAccount(), false, 0, jvMarker,
                            jvOffers);

                        if (jvResult.isMember (field))
                        {
                            Json::Value& results (jvResult[field]);
                            for (auto const& e : jvOffers[jss::offers])
                                results.append (e);
                        }
                        else
                        {
                            jvResult[field] = jvOffers[jss::offers];
                        }
                    };

                    if (bBoth)
                    {
                        add (jss::bids);
                        add (jss::asks);
                    }
                    else
                    {
                        add (jss::offers);
                    }
                }
            }
        }
    }

    return jvResult;
}
Exemple #13
0
Json::Value doUnsubscribe (RPC::Context& context)
{

    InfoSub::pointer ispSub;
    Json::Value jvResult (Json::objectValue);
    bool removeUrl {false};

    if (! context.infoSub && ! context.params.isMember(jss::url))
    {
        // Must be a JSON-RPC call.
        return rpcError(rpcINVALID_PARAMS);
    }

    if (context.params.isMember(jss::url))
    {
        if (context.role != Role::ADMIN)
            return rpcError(rpcNO_PERMISSION);

        std::string strUrl = context.params[jss::url].asString ();
        ispSub = context.netOps.findRpcSub (strUrl);
        if (! ispSub)
            return jvResult;
        removeUrl = true;
    }
    else
    {
        ispSub = context.infoSub;
    }

    if (context.params.isMember (jss::streams))
    {
        if (! context.params[jss::streams].isArray ())
            return rpcError (rpcINVALID_PARAMS);

        for (auto& it: context.params[jss::streams])
        {
            if (! it.isString())
                return rpcError(rpcSTREAM_MALFORMED);

            std::string streamName = it.asString ();
            if (streamName == "server")
            {
                context.netOps.unsubServer (ispSub->getSeq ());
            }
            else if (streamName == "ledger")
            {
                context.netOps.unsubLedger (ispSub->getSeq ());
            }
            else if (streamName == "manifests")
            {
                context.netOps.unsubManifests (ispSub->getSeq ());
            }
            else if (streamName == "transactions")
            {
                context.netOps.unsubTransactions (ispSub->getSeq ());
            }
            else if (streamName == "transactions_proposed"
                || streamName == "rt_transactions") // DEPRECATED
            {
                context.netOps.unsubRTTransactions (ispSub->getSeq ());
            }
            else if (streamName == "validations")
            {
                context.netOps.unsubValidations (ispSub->getSeq ());
            }
            else if (streamName == "peer_status")
            {
                context.netOps.unsubPeerStatus (ispSub->getSeq ());
            }
            else
            {
                return rpcError(rpcSTREAM_MALFORMED);
            }
        }
    }

    auto accountsProposed = context.params.isMember(jss::accounts_proposed)
        ? jss::accounts_proposed : jss::rt_accounts;  // DEPRECATED
    if (context.params.isMember(accountsProposed))
    {
        if (! context.params[accountsProposed].isArray())
            return rpcError(rpcINVALID_PARAMS);

        auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
        if (ids.empty())
            return rpcError(rpcACT_MALFORMED);
        context.netOps.unsubAccount(ispSub, ids, true);
    }

    if (context.params.isMember(jss::accounts))
    {
        if (! context.params[jss::accounts].isArray())
            return rpcError(rpcINVALID_PARAMS);

        auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
        if (ids.empty())
            return rpcError(rpcACT_MALFORMED);
        context.netOps.unsubAccount(ispSub, ids, false);
    }

    if (context.params.isMember(jss::books))
    {
        if (! context.params[jss::books].isArray())
            return rpcError(rpcINVALID_PARAMS);

        for (auto& jv: context.params[jss::books])
        {
            if (! jv.isObject() ||
                ! jv.isMember(jss::taker_pays) ||
                ! jv.isMember(jss::taker_gets) ||
                ! jv[jss::taker_pays].isObject() ||
                ! jv[jss::taker_gets].isObject())
            {
                return rpcError(rpcINVALID_PARAMS);
            }

            Json::Value taker_pays = jv[jss::taker_pays];
            Json::Value taker_gets = jv[jss::taker_gets];

            Book book;

            // Parse mandatory currency.
            if (!taker_pays.isMember (jss::currency)
                || !to_currency (
                    book.in.currency, taker_pays[jss::currency].asString ()))
            {
                JLOG (context.j.info()) << "Bad taker_pays currency.";
                return rpcError (rpcSRC_CUR_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_pays.isMember (jss::issuer))
                      && (!taker_pays[jss::issuer].isString ()
                          || !to_issuer (
                              book.in.account, taker_pays[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || !isConsistent (book.in)
                     || noAccount() == book.in.account)
            {
                JLOG (context.j.info()) << "Bad taker_pays issuer.";

                return rpcError (rpcSRC_ISR_MALFORMED);
            }

            // Parse mandatory currency.
            if (!taker_gets.isMember (jss::currency)
                    || !to_currency (book.out.currency,
                                     taker_gets[jss::currency].asString ()))
            {
                JLOG (context.j.info()) << "Bad taker_gets currency.";

                return rpcError (rpcDST_AMT_MALFORMED);
            }
            // Parse optional issuer.
            else if (((taker_gets.isMember (jss::issuer))
                      && (!taker_gets[jss::issuer].isString ()
                          || !to_issuer (book.out.account,
                                         taker_gets[jss::issuer].asString ())))
                     // Don't allow illegal issuers.
                     || !isConsistent (book.out)
                     || noAccount() == book.out.account)
            {
                JLOG (context.j.info()) << "Bad taker_gets issuer.";

                return rpcError (rpcDST_ISR_MALFORMED);
            }

            if (book.in == book.out)
            {
                JLOG (context.j.info())
                    << "taker_gets same as taker_pays.";
                return rpcError (rpcBAD_MARKET);
            }

            context.netOps.unsubBook (ispSub->getSeq (), book);

            // both_sides is deprecated.
            if ((jv.isMember(jss::both) && jv[jss::both].asBool()) ||
                (jv.isMember(jss::both_sides) && jv[jss::both_sides].asBool()))
            {
                context.netOps.unsubBook(ispSub->getSeq(), reversed(book));
            }
        }
    }

    if (removeUrl)
    {
        context.netOps.tryRemoveRpcSub(context.params[jss::url].asString ());
    }

    return jvResult;
}
Exemple #14
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);
}
Exemple #15
0
int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
{
    int ret = PFR_PJ_NOCHANGE;

    if (jvParams.isMember (jss::source_account))
    {
        raSrcAccount = parseBase58<AccountID>(
            jvParams[jss::source_account].asString());
        if (! raSrcAccount)
        {
            jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcSRC_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember (jss::destination_account))
    {
        raDstAccount = parseBase58<AccountID>(
            jvParams[jss::destination_account].asString());
        if (! raDstAccount)
        {
            jvStatus = rpcError (rpcDST_ACT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcDST_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember (jss::destination_amount))
    {
        if (! amountFromJsonNoThrow (
                saDstAmount, jvParams[jss::destination_amount]) ||
            (saDstAmount.getCurrency ().isZero () &&
             saDstAmount.getIssuer ().isNonZero ()) ||
            (saDstAmount.getCurrency () == badCurrency ()) ||
            saDstAmount <= zero)
        {
            jvStatus = rpcError (rpcDST_AMT_MALFORMED);
            return PFR_PJ_INVALID;
        }
    }
    else if (complete)
    {
        jvStatus = rpcError (rpcDST_ACT_MISSING);
        return PFR_PJ_INVALID;
    }

    if (jvParams.isMember (jss::source_currencies))
    {
        Json::Value const& jvSrcCur = jvParams[jss::source_currencies];

        if (!jvSrcCur.isArray ())
        {
            jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
            return PFR_PJ_INVALID;
        }

        sciSourceCurrencies.clear ();

        for (unsigned i = 0; i < jvSrcCur.size (); ++i)
        {
            Json::Value const& jvCur = jvSrcCur[i];
            Currency uCur;
            AccountID uIss;

            if (!jvCur.isObject() || !jvCur.isMember (jss::currency) ||
                !to_currency (uCur, jvCur[jss::currency].asString ()))
            {
                jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
                return PFR_PJ_INVALID;
            }

            if (jvCur.isMember (jss::issuer) &&
                !to_issuer (uIss, jvCur[jss::issuer].asString ()))
            {
                jvStatus = rpcError (rpcSRC_ISR_MALFORMED);
            }

            if (uCur.isZero () && uIss.isNonZero ())
            {
                jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
                return PFR_PJ_INVALID;
            }

            if (uCur.isNonZero() && uIss.isZero())
            {
                uIss = *raSrcAccount;
            }

            sciSourceCurrencies.insert ({uCur, uIss});
        }
    }

    if (jvParams.isMember ("id"))
        jvId = jvParams["id"];

    return ret;
}