diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 40f88cd6d..0853513e9 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -634,6 +634,7 @@ target_sources (rippled PRIVATE src/ripple/rpc/handlers/NFTOffers.cpp src/ripple/rpc/handlers/NodeToShard.cpp src/ripple/rpc/handlers/NoRippleCheck.cpp + src/ripple/rpc/handlers/OptionBookOffers.cpp src/ripple/rpc/handlers/OwnerInfo.cpp src/ripple/rpc/handlers/PathFind.cpp src/ripple/rpc/handlers/PayChanClaim.cpp diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 7a3175076..bd2f10603 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -350,6 +350,15 @@ public: unsigned int iLimit, Json::Value const& jvMarker, Json::Value& jvResult) override; + + void + getOptionBookPage( + std::shared_ptr& lpLedger, + STAmount const&, + std::uint32_t const& expiration, + unsigned int iLimit, + Json::Value const& jvMarker, + Json::Value& jvResult) override; // Ledger proposal/close functions. bool @@ -4581,6 +4590,114 @@ NetworkOPsImp::getBookPage( #endif +void +NetworkOPsImp::getOptionBookPage( + std::shared_ptr& lpLedger, + STAmount const& strikePrice, + std::uint32_t const& expiration, + unsigned int iLimit, + Json::Value const& jvMarker, + Json::Value& jvResult) +{ + Json::Value& jvOffers = (jvResult[jss::offers] = Json::Value(Json::arrayValue)); + + const uint256 uBookBase = getOptionBookBase( + strikePrice.getIssuer(), + strikePrice.getCurrency(), + strikePrice.mantissa(), + expiration); + const uint256 uBookEnd = getOptionQualityNext(uBookBase); + + uint256 uTipIndex = uBookBase; + + if (auto stream = m_journal.trace()) + { + stream << "getBookPage:" << strikePrice; + stream << "getBookPage: uBookBase=" << uBookBase; + stream << "getBookPage: uBookEnd=" << uBookEnd; + stream << "getBookPage: uTipIndex=" << uTipIndex; + } + + ReadView const& view = *lpLedger; + + bool bDone = false; + bool bDirectAdvance = true; + + std::shared_ptr sleOfferDir; + uint256 offerIndex; + unsigned int uBookEntry; + STAmount saDirRate; + + auto viewJ = app_.journal("View"); + + while (!bDone && iLimit-- > 0) + { + if (bDirectAdvance) + { + bDirectAdvance = false; + + JLOG(m_journal.trace()) << "getBookPage: bDirectAdvance"; + + auto const ledgerIndex = view.succ(uTipIndex, uBookEnd); + if (ledgerIndex) + { + sleOfferDir = view.read(keylet::page(*ledgerIndex)); + } + else + { + sleOfferDir.reset(); + } + + if (!sleOfferDir) + { + JLOG(m_journal.trace()) << "getBookPage: bDone"; + bDone = true; + } + else + { + uTipIndex = sleOfferDir->key(); + cdirFirst(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex); + + JLOG(m_journal.trace()) + << "getBookPage: uTipIndex=" << uTipIndex; + JLOG(m_journal.trace()) + << "getBookPage: offerIndex=" << offerIndex; + } + } + + if (!bDone) + { + auto sleOffer = view.read(keylet::optionOffer(offerIndex)); + + if (sleOffer) + { + STAmount premium = sleOffer->getFieldAmount(sfAmount); + auto const optionQuality = getOptionQuality(uTipIndex); + STAmount saDirRate = STAmount(premium.issue(), optionQuality); + + Json::Value jvOffer = sleOffer->getJson(JsonOptions::none); + + Json::Value& jvOf = jvOffers.append(jvOffer); + jvOf[jss::quality] = saDirRate.getText(); + } + else + { + JLOG(m_journal.warn()) << "Missing offer"; + } + + if (!cdirNext(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex)) + { + bDirectAdvance = true; + } + else + { + JLOG(m_journal.trace()) + << "getBookPage: offerIndex=" << offerIndex; + } + } + } +} + inline void NetworkOPsImp::collect_metrics() { diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index d53127ed3..33c985391 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -163,6 +163,15 @@ public: unsigned int iLimit, Json::Value const& jvMarker, Json::Value& jvResult) = 0; + + virtual void + getOptionBookPage( + std::shared_ptr& lpLedger, + STAmount const& strikePrice, + std::uint32_t const& expiration, + unsigned int iLimit, + Json::Value const& jvMarker, + Json::Value& jvResult) = 0; //-------------------------------------------------------------------------- diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 40d1b5282..343298c76 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -315,6 +315,12 @@ optionBook(AccountID const& issuer, Currency const& currency, std::uint64_t stri Keylet optionOffer(AccountID const& id, std::uint32_t seq) noexcept; +inline Keylet +optionOffer(uint256 const& key) noexcept +{ + return {ltOPTION_OFFER, key}; +} + Keylet optionQuality(Keylet const& k, std::uint64_t q) noexcept; diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 58bc59335..bc0c144b4 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -515,6 +515,7 @@ JSS(open); // out: handlers/Ledger JSS(open_ledger_cost); // out: SubmitTransaction JSS(open_ledger_fee); // out: TxQ JSS(open_ledger_level); // out: TxQ +JSS(option); // out: AccountObjects JSS(owner); // in: LedgerEntry, out: NetworkOPs JSS(owner_funds); // in/out: Ledger, NetworkOPs, AcceptedLedgerTx JSS(page_index); @@ -635,6 +636,7 @@ JSS(stop_history_tx_only); // in: Unsubscribe, stop history tx stream JSS(storedSeqs); // out: NodeToShardStatus JSS(streams); // in: Subscribe, Unsubscribe JSS(strict); // in: AccountCurrencies, AccountInfo +JSS(strike_price); // in: NetworkOPs JSS(sub_index); // in: LedgerEntry JSS(subcommand); // in: PathFind JSS(success); // rpc diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/ripple/rpc/handlers/AccountObjects.cpp index 26611ea87..992250b94 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/ripple/rpc/handlers/AccountObjects.cpp @@ -208,6 +208,7 @@ doAccountObjects(RPC::JsonContext& context) {jss::hook, ltHOOK}, {jss::payment_channel, ltPAYCHAN}, {jss::uri_token, ltURI_TOKEN}, + {jss::option, ltOPTION}, {jss::state, ltRIPPLE_STATE}}; typeFilter.emplace(); diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 739c069f3..d91db2ec9 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -103,6 +103,8 @@ doNodeToShard(RPC::JsonContext&); Json::Value doNoRippleCheck(RPC::JsonContext&); Json::Value +doOptionBookOffers(RPC::JsonContext&); +Json::Value doOwnerInfo(RPC::JsonContext&); Json::Value doPathFind(RPC::JsonContext&); diff --git a/src/ripple/rpc/handlers/OptionBookOffers.cpp b/src/ripple/rpc/handlers/OptionBookOffers.cpp new file mode 100644 index 000000000..da5da43be --- /dev/null +++ b/src/ripple/rpc/handlers/OptionBookOffers.cpp @@ -0,0 +1,160 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +Json::Value +doOptionBookOffers(RPC::JsonContext& 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 (context.app.getJobQueue().getJobCountGE(jtCLIENT) > 200) + return rpcError(rpcTOO_BUSY); + + std::shared_ptr lpLedger; + auto jvResult = RPC::lookupLedger(lpLedger, context); + + if (!lpLedger) + return jvResult; + + if (!context.params.isMember(jss::strike_price)) + return RPC::missing_field_error(jss::strike_price); + + Json::Value const& strike_price = context.params[jss::strike_price]; + + if (!strike_price.isObjectOrNull()) + return RPC::object_field_error(jss::strike_price); + + // if (!taker_gets.isObjectOrNull()) + // return RPC::object_field_error(jss::taker_gets); + + if (!strike_price.isMember(jss::currency)) + return RPC::missing_field_error("strike_price.currency"); + + if (!strike_price[jss::currency].isString()) + return RPC::expected_field_error("strike_price.currency", "string"); + + // 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, strike_price[jss::currency].asString())) + { + JLOG(context.j.info()) << "Bad strike_price currency."; + return RPC::make_error( + rpcSRC_CUR_MALFORMED, + "Invalid field 'strike_price.currency', bad currency."); + } + + AccountID pay_issuer; + + if (strike_price.isMember(jss::issuer)) + { + if (!strike_price[jss::issuer].isString()) + return RPC::expected_field_error("strike_price.issuer", "string"); + + if (!to_issuer(pay_issuer, strike_price[jss::issuer].asString())) + return RPC::make_error( + rpcSRC_ISR_MALFORMED, + "Invalid field 'strike_price.issuer', bad issuer."); + + if (pay_issuer == noAccount()) + return RPC::make_error( + rpcSRC_ISR_MALFORMED, + "Invalid field 'strike_price.issuer', bad issuer account one."); + } + else + { + pay_issuer = xrpAccount(); + } + + std::optional pay_value; + if (strike_price.isMember(jss::value)) + { + if (!strike_price[jss::value].isString()) + return RPC::expected_field_error("strike_price.value", "string"); + + pay_value = strike_price[jss::value].asInt(); + if (!pay_value) + return RPC::invalid_field_error(jss::value); + } + + if (isXRP(pay_currency) && !isXRP(pay_issuer)) + return RPC::make_error( + rpcSRC_ISR_MALFORMED, + "Unneeded field 'taker_pays.issuer' for " + "XRP currency specification."); + + if (!isXRP(pay_currency) && isXRP(pay_issuer)) + return RPC::make_error( + rpcSRC_ISR_MALFORMED, + "Invalid field 'taker_pays.issuer', expected non-XRP issuer."); + + std::optional expiration; + if (context.params.isMember(jss::expiration)) + { + if (!context.params[jss::expiration].isString()) + return RPC::expected_field_error(jss::expiration, "string"); + + expiration = context.params[jss::expiration].asInt(); + if (!expiration) + return RPC::invalid_field_error(jss::expiration); + } + + unsigned int limit; + if (auto err = readLimitField(limit, RPC::Tuning::bookOffers, context)) + return *err; + + 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.getOptionBookPage( + lpLedger, + STAmount(Issue(pay_currency, pay_issuer), pay_value ? *pay_value : 0), + expiration ? *expiration : 0, + limit, + jvMarker, + jvResult); + + context.loadType = Resource::feeMediumBurdenRPC; + + return jvResult; +} + +} // namespace ripple diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 103c8622b..d12763610 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -118,6 +118,7 @@ Handler const handlerArray[]{ {"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION}, {"node_to_shard", byRef(&doNodeToShard), Role::ADMIN, NO_CONDITION}, {"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION}, + {"option_book_offers", byRef(&doOptionBookOffers), Role::USER, NO_CONDITION}, {"owner_info", byRef(&doOwnerInfo), Role::USER, NEEDS_CURRENT_LEDGER}, {"peers", byRef(&doPeers), Role::ADMIN, NO_CONDITION}, {"path_find", byRef(&doPathFind), Role::USER, NEEDS_CURRENT_LEDGER},